{"version":2,"initialFileContents":[["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","da39a3e"],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","da39a3e"],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/Cargo.toml","da39a3e"],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","da39a3e"],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/Cargo.toml","da39a3e"],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","da39a3e"],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/Cargo.toml","da39a3e"],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","da39a3e"],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/Cargo.toml","da39a3e"],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","da39a3e"],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","da39a3e"],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs","da39a3e"],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/.cargo/config.toml","da39a3e"],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","da39a3e"],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","da39a3e"],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/usb_hid.rs","da39a3e"],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","da39a3e"],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/memory.x","da39a3e"],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_firmware.ps1","da39a3e"],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/host_hid_smoke_test.ps1","da39a3e"],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","da39a3e"],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","da39a3e"],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/.gitignore","57832e1"],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/.cargo/config.toml","da39a3e"],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/build.rs","da39a3e"],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/driver.vrdrivermanifest","da39a3e"],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","da39a3e"],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/localization/en_us.json","da39a3e"],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","da39a3e"],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","da39a3e"],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","da39a3e"],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","da39a3e"],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/README.md","6429938"],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","da39a3e"],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_scaffold.rs","5a2ae78"],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","da39a3e"],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/lib.rs","1a1a750"],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/driver.vrdrivermanifest","fd051e2"],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","5de8eb1"],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/big_haptic_profile.json","31dfbf2"],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","da39a3e"],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/driver.vrresources","da39a3e"],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/settings/default.vrsettings","da39a3e"],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","da39a3e"]],"timeline":{"checkpoints":[{"checkpointId":"b73d44bb-9987-4494-8825-0da28f9f8d16","epoch":0,"label":"Initial State","description":"Starting point before any edits"},{"checkpointId":"98cf55d9-b83d-4703-8d7b-a904cc85974b","requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":1,"label":"Request request_706ce3a9-9340-4022-af72-dfadd5a6c579"},{"checkpointId":"7a83b7b7-0462-4066-96fb-c69d2b9c2d88","requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":109,"label":"Request request_a6e9ece0-d739-40fb-bfd8-8080e026389e"},{"checkpointId":"c7724c61-e0a5-4bba-8109-708eea1a4732","requestId":"request_32e09b23-ccf2-430e-8354-01b3c60ea6ea","epoch":350,"label":"Request request_32e09b23-ccf2-430e-8354-01b3c60ea6ea"},{"checkpointId":"e70cb731-97b4-430f-ae54-97eabcd777f0","requestId":"request_32e09b23-ccf2-430e-8354-01b3c60ea6ea","undoStopId":"626be665-0d0a-4da2-9c9b-a8be1c44513e","epoch":351,"label":"Request request_32e09b23-ccf2-430e-8354-01b3c60ea6ea - Stop 626be665-0d0a-4da2-9c9b-a8be1c44513e"},{"checkpointId":"bf0faf7a-3c07-4e3c-bfbf-b20ab41df883","requestId":"request_32e09b23-ccf2-430e-8354-01b3c60ea6ea","undoStopId":"e876c9a7-01f6-4eeb-a150-1e2968927c4c","epoch":354,"label":"Request request_32e09b23-ccf2-430e-8354-01b3c60ea6ea - Stop e876c9a7-01f6-4eeb-a150-1e2968927c4c"},{"checkpointId":"728f8186-e56e-4926-8ee5-458f4ca225ca","requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":357,"label":"Request request_da61cd7c-da91-4ac6-9f3e-da45d99473b8"},{"checkpointId":"e3509ecc-3593-4c3a-ab7d-b8731ef5b7a8","requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","undoStopId":"505cbb17-4a54-49ad-a9f2-f41f4e2731e8","epoch":358,"label":"Request request_da61cd7c-da91-4ac6-9f3e-da45d99473b8 - Stop 505cbb17-4a54-49ad-a9f2-f41f4e2731e8"},{"checkpointId":"a22b58f2-6a00-4a5f-a40b-c8e2dfc004f4","requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","undoStopId":"08543cd5-f56f-4509-953c-f4b13b2fd77e","epoch":422,"label":"Request request_da61cd7c-da91-4ac6-9f3e-da45d99473b8 - Stop 08543cd5-f56f-4509-953c-f4b13b2fd77e"},{"checkpointId":"85372cc8-e258-4a5e-9ff6-a4b5c0fa6a09","requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":425,"label":"Request request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f"},{"checkpointId":"e24f5cd6-2f11-43ea-8a16-5efe6049f846","requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","undoStopId":"591a751a-481d-4421-9206-1dafba212632","epoch":514,"label":"Request request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f - Stop 591a751a-481d-4421-9206-1dafba212632"},{"checkpointId":"52fe3ae5-2b91-4639-8ffd-262fc975b67a","requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":517,"label":"Request request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95"},{"checkpointId":"6855ce7c-bc69-4042-a807-b0abddba9dae","requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","undoStopId":"641a4431-8804-414d-8141-4994bb9d6813","epoch":518,"label":"Request request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95 - Stop 641a4431-8804-414d-8141-4994bb9d6813"},{"checkpointId":"13b049a5-af45-4f96-833e-1c19dad4d620","requestId":"request_f230f2e9-ae40-4210-8fac-0fb1fa22f069","epoch":603,"label":"Request request_f230f2e9-ae40-4210-8fac-0fb1fa22f069"},{"checkpointId":"305da18e-3261-4989-b2b3-70ae4a49f448","requestId":"request_91e0ff5b-3f8e-4584-83d9-e232e969c2aa","epoch":604,"label":"Request request_91e0ff5b-3f8e-4584-83d9-e232e969c2aa"},{"checkpointId":"2fc4bf0b-816c-405d-9f85-fe7a4a86a108","requestId":"request_91e0ff5b-3f8e-4584-83d9-e232e969c2aa","undoStopId":"ac5775d9-30ce-48f6-bf30-1f72d49b3665","epoch":605,"label":"Request request_91e0ff5b-3f8e-4584-83d9-e232e969c2aa - Stop ac5775d9-30ce-48f6-bf30-1f72d49b3665"},{"checkpointId":"332b9ba8-ac20-46ef-ac1f-8a36ac0c6d0b","requestId":"request_670e73e8-7055-44d2-a172-e3c97407fa56","epoch":608,"label":"Request request_670e73e8-7055-44d2-a172-e3c97407fa56"},{"checkpointId":"64b3a3e2-f5bc-4b97-9ab5-0a478412c181","requestId":"request_670e73e8-7055-44d2-a172-e3c97407fa56","undoStopId":"ef466e95-4ab5-43c9-ac4e-50063d9c8311","epoch":609,"label":"Request request_670e73e8-7055-44d2-a172-e3c97407fa56 - Stop ef466e95-4ab5-43c9-ac4e-50063d9c8311"},{"checkpointId":"24e62114-4919-4091-8ad6-b53d7415d8a4","requestId":"request_670e73e8-7055-44d2-a172-e3c97407fa56","undoStopId":"5522d2cb-5637-4e91-96ff-83ac323f8775","epoch":612,"label":"Request request_670e73e8-7055-44d2-a172-e3c97407fa56 - Stop 5522d2cb-5637-4e91-96ff-83ac323f8775"},{"checkpointId":"43c6b0e0-c234-4c1a-9893-52c809397a39","requestId":"request_0d1e6e8d-7dbf-467e-af2c-f0fbd5aa689f","epoch":615,"label":"Request request_0d1e6e8d-7dbf-467e-af2c-f0fbd5aa689f"},{"checkpointId":"92b4c917-df35-42f1-9641-2922de180cb8","requestId":"request_0d1e6e8d-7dbf-467e-af2c-f0fbd5aa689f","undoStopId":"3b341109-eb47-4f8a-aef2-71c05465a34b","epoch":616,"label":"Request request_0d1e6e8d-7dbf-467e-af2c-f0fbd5aa689f - Stop 3b341109-eb47-4f8a-aef2-71c05465a34b"},{"checkpointId":"8eda820b-5a8b-41ce-8343-10e130069726","requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":619,"label":"Request request_7d935091-4101-48a6-9ae5-52e365038840"},{"checkpointId":"58523467-e828-4abb-a1f0-2e5e582c07ef","requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","undoStopId":"03ad8058-1b19-4e26-93cf-3a90e3b49f9b","epoch":620,"label":"Request request_7d935091-4101-48a6-9ae5-52e365038840 - Stop 03ad8058-1b19-4e26-93cf-3a90e3b49f9b"},{"checkpointId":"cb3c05e1-98f4-459e-bb0f-b12b2071b7cf","requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","undoStopId":"9750c1fe-283c-4c55-95b8-ddd8ee1c9dab","epoch":623,"label":"Request request_7d935091-4101-48a6-9ae5-52e365038840 - Stop 9750c1fe-283c-4c55-95b8-ddd8ee1c9dab"},{"checkpointId":"35baca63-01ae-422e-88ef-f156f1880c0e","requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","undoStopId":"65759e09-989c-486b-91d2-f3dc7821e3fb","epoch":639,"label":"Request request_7d935091-4101-48a6-9ae5-52e365038840 - Stop 65759e09-989c-486b-91d2-f3dc7821e3fb"},{"checkpointId":"e164c2d1-a203-4eb5-bff5-a497ff8c83db","requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","undoStopId":"d66c16d3-035d-4f4f-8068-38635d857023","epoch":642,"label":"Request request_7d935091-4101-48a6-9ae5-52e365038840 - Stop d66c16d3-035d-4f4f-8068-38635d857023"},{"checkpointId":"f2f1a3ad-a603-41e5-b597-4f89350726d7","requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","undoStopId":"03d775bc-d776-42a9-a0bd-7ddb622c03bf","epoch":645,"label":"Request request_7d935091-4101-48a6-9ae5-52e365038840 - Stop 03d775bc-d776-42a9-a0bd-7ddb622c03bf"},{"checkpointId":"cde6afdf-325c-495d-ba3b-9e5bec6e7462","requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":726,"label":"Request request_4692cace-c687-4f02-91bd-cae5b881aab8"},{"checkpointId":"60f0e81e-de07-4359-9f8d-4040fbd17167","requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","undoStopId":"3fcf3231-fa28-4769-b6ff-941f5103f312","epoch":814,"label":"Request request_4692cace-c687-4f02-91bd-cae5b881aab8 - Stop 3fcf3231-fa28-4769-b6ff-941f5103f312"},{"checkpointId":"d1ee0f5e-b4a7-4f8e-bb71-bd5ea7507a89","requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","undoStopId":"d8b85850-80b4-4a91-a987-7eddb7c0b8b6","epoch":817,"label":"Request request_4692cace-c687-4f02-91bd-cae5b881aab8 - Stop d8b85850-80b4-4a91-a987-7eddb7c0b8b6"},{"checkpointId":"deaa9151-e325-472d-94ce-7e0ef152d72e","requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","undoStopId":"a14b7c14-54bf-4809-a4f8-88f6826c988c","epoch":820,"label":"Request request_4692cace-c687-4f02-91bd-cae5b881aab8 - Stop a14b7c14-54bf-4809-a4f8-88f6826c988c"},{"checkpointId":"fe3a5e13-5878-4474-a3a3-fa3d3110a62d","requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","undoStopId":"61e3066f-3aa5-45bb-8371-c08809e03f95","epoch":822,"label":"Request request_4692cace-c687-4f02-91bd-cae5b881aab8 - Stop 61e3066f-3aa5-45bb-8371-c08809e03f95"},{"checkpointId":"e6af572a-3ac9-4ed0-b23b-1ae674c632bd","requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","undoStopId":"5e0adf7c-4a13-4868-a214-81ca402841e5","epoch":824,"label":"Request request_4692cace-c687-4f02-91bd-cae5b881aab8 - Stop 5e0adf7c-4a13-4868-a214-81ca402841e5"},{"checkpointId":"ef8ca2ed-2040-43fc-803a-398c006c4c47","requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","undoStopId":"8d724824-2746-45de-9a91-dc21a85005cd","epoch":826,"label":"Request request_4692cace-c687-4f02-91bd-cae5b881aab8 - Stop 8d724824-2746-45de-9a91-dc21a85005cd"},{"checkpointId":"285ee167-6f66-4132-bf67-eb527571dbea","requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","undoStopId":"a9416812-c8c6-4361-a65d-dec8892be0a9","epoch":828,"label":"Request request_4692cace-c687-4f02-91bd-cae5b881aab8 - Stop a9416812-c8c6-4361-a65d-dec8892be0a9"},{"checkpointId":"e1bf73d5-f8e5-4985-a8cd-20f4138cb6c4","requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","undoStopId":"002586e7-e91f-4dcf-a990-fe4b01193705","epoch":830,"label":"Request request_4692cace-c687-4f02-91bd-cae5b881aab8 - Stop 002586e7-e91f-4dcf-a990-fe4b01193705"},{"checkpointId":"65a233e1-907a-4d3a-97b3-d4927a8c2e8b","requestId":"request_5ffebef5-f6de-4fca-a46a-31d52f96616e","epoch":833,"label":"Request request_5ffebef5-f6de-4fca-a46a-31d52f96616e"},{"checkpointId":"04b91c2c-c4ed-4604-a748-4bc9f795d97a","requestId":"request_5ffebef5-f6de-4fca-a46a-31d52f96616e","undoStopId":"c803bcb4-0cbb-438d-8c8f-0aa69ce55614","epoch":846,"label":"Request request_5ffebef5-f6de-4fca-a46a-31d52f96616e - Stop c803bcb4-0cbb-438d-8c8f-0aa69ce55614"},{"checkpointId":"2e1f10d7-9d61-43d1-9b24-3e0bf4d755e5","requestId":"request_128f3af0-ca12-4644-bf4a-f57b14bf85bb","epoch":849,"label":"Request request_128f3af0-ca12-4644-bf4a-f57b14bf85bb"},{"checkpointId":"8109af91-dd83-4ed1-884e-496430295245","requestId":"request_128f3af0-ca12-4644-bf4a-f57b14bf85bb","undoStopId":"d1eff7d0-d72b-457c-a8c5-cd91b562bf4e","epoch":850,"label":"Request request_128f3af0-ca12-4644-bf4a-f57b14bf85bb - Stop d1eff7d0-d72b-457c-a8c5-cd91b562bf4e"},{"checkpointId":"024b7fee-39a8-4298-93c4-fdf59d5c54fd","requestId":"request_92d7c45c-4795-4c66-b59e-3d209f7a5944","epoch":853,"label":"Request request_92d7c45c-4795-4c66-b59e-3d209f7a5944"},{"checkpointId":"e0a78966-40c6-4caf-92da-9811c2ead9b8","requestId":"request_92d7c45c-4795-4c66-b59e-3d209f7a5944","undoStopId":"22f442f3-46e7-436b-abfa-a3f5adcce8d6","epoch":854,"label":"Request request_92d7c45c-4795-4c66-b59e-3d209f7a5944 - Stop 22f442f3-46e7-436b-abfa-a3f5adcce8d6"},{"checkpointId":"dcb4a116-2c8a-4e8b-ae78-4d257f409a2b","requestId":"request_92d7c45c-4795-4c66-b59e-3d209f7a5944","undoStopId":"59299617-4deb-40df-b8a1-ac74c4b0173d","epoch":857,"label":"Request request_92d7c45c-4795-4c66-b59e-3d209f7a5944 - Stop 59299617-4deb-40df-b8a1-ac74c4b0173d"},{"checkpointId":"5bdd3cd0-cde6-4a7e-b713-01ff6344d8d8","requestId":"request_92d7c45c-4795-4c66-b59e-3d209f7a5944","undoStopId":"38f5b1f6-ffe5-4bc9-886c-686d465db782","epoch":859,"label":"Request request_92d7c45c-4795-4c66-b59e-3d209f7a5944 - Stop 38f5b1f6-ffe5-4bc9-886c-686d465db782"},{"checkpointId":"75393ec6-ddc4-4afa-8c27-61d7c30eb88f","requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":861,"label":"Request request_c21a4968-1030-4d03-9ff1-b0cf37dc305a"},{"checkpointId":"32a98f20-d147-47c9-aff4-4f6e79a4de32","requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","undoStopId":"63da9cd8-3a6e-45bb-9432-34c1577d1e97","epoch":862,"label":"Request request_c21a4968-1030-4d03-9ff1-b0cf37dc305a - Stop 63da9cd8-3a6e-45bb-9432-34c1577d1e97"},{"checkpointId":"fbdb13b5-0ed0-4fce-8560-7be5fa403a3b","requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","undoStopId":"bac098a2-0f7a-482f-ae9a-e0518ff81227","epoch":957,"label":"Request request_c21a4968-1030-4d03-9ff1-b0cf37dc305a - Stop bac098a2-0f7a-482f-ae9a-e0518ff81227"},{"checkpointId":"9039f120-97c0-4302-92c1-8a5e1362ecc2","requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","undoStopId":"d5e454fc-3b6f-4eb9-87cc-654264be8af9","epoch":959,"label":"Request request_c21a4968-1030-4d03-9ff1-b0cf37dc305a - Stop d5e454fc-3b6f-4eb9-87cc-654264be8af9"},{"checkpointId":"1c52aa65-4ab5-4b96-937b-18ed74672cd6","requestId":"request_8b8bee65-e67e-436f-8cf9-9e7a5e2a4cbf","epoch":961,"label":"Request request_8b8bee65-e67e-436f-8cf9-9e7a5e2a4cbf"},{"checkpointId":"dcdc0eac-c49e-432b-a282-0421a4bf6254","requestId":"request_8b8bee65-e67e-436f-8cf9-9e7a5e2a4cbf","undoStopId":"86b5d613-ab18-4ef2-bd45-a01ee9a52cb1","epoch":962,"label":"Request request_8b8bee65-e67e-436f-8cf9-9e7a5e2a4cbf - Stop 86b5d613-ab18-4ef2-bd45-a01ee9a52cb1"},{"checkpointId":"0f075277-d1ad-46c0-a32e-d4a1cf9ef242","requestId":"request_8b8bee65-e67e-436f-8cf9-9e7a5e2a4cbf","undoStopId":"a56432ac-9b82-493c-9fc0-9c0913f2a83f","epoch":965,"label":"Request request_8b8bee65-e67e-436f-8cf9-9e7a5e2a4cbf - Stop a56432ac-9b82-493c-9fc0-9c0913f2a83f"},{"checkpointId":"a51a430f-a03f-405f-a458-5e4fea594768","requestId":"request_8b8bee65-e67e-436f-8cf9-9e7a5e2a4cbf","undoStopId":"de86a755-6edc-4ef8-b19f-f40f47a2b67b","epoch":968,"label":"Request request_8b8bee65-e67e-436f-8cf9-9e7a5e2a4cbf - Stop de86a755-6edc-4ef8-b19f-f40f47a2b67b"},{"checkpointId":"4a4914b7-e0e9-4929-8cd1-a41bf7b8e2fd","requestId":"request_c2dbf0bc-9220-41ac-8de0-ebcec63de2a8","epoch":971,"label":"Request request_c2dbf0bc-9220-41ac-8de0-ebcec63de2a8"},{"checkpointId":"367f166e-133f-43c5-8a5f-aea4b4ffcf31","requestId":"request_c2dbf0bc-9220-41ac-8de0-ebcec63de2a8","undoStopId":"1d791372-b078-47d1-bbec-8abe17151d05","epoch":972,"label":"Request request_c2dbf0bc-9220-41ac-8de0-ebcec63de2a8 - Stop 1d791372-b078-47d1-bbec-8abe17151d05"},{"checkpointId":"cc68927a-6b5e-4bd5-9b3e-e1ed1f1498ab","requestId":"request_c2dbf0bc-9220-41ac-8de0-ebcec63de2a8","undoStopId":"7bafdeac-9194-4b3e-a883-ed5051a94018","epoch":975,"label":"Request request_c2dbf0bc-9220-41ac-8de0-ebcec63de2a8 - Stop 7bafdeac-9194-4b3e-a883-ed5051a94018"},{"checkpointId":"b359b7c2-9e71-496f-a716-cd006078cdb0","requestId":"request_c2dbf0bc-9220-41ac-8de0-ebcec63de2a8","undoStopId":"6dbce6fa-1734-42a5-a76a-36fb948a8804","epoch":978,"label":"Request request_c2dbf0bc-9220-41ac-8de0-ebcec63de2a8 - Stop 6dbce6fa-1734-42a5-a76a-36fb948a8804"},{"checkpointId":"a59f4c37-4c8f-40de-ad6d-51c2d6efc43c","requestId":"request_c2dbf0bc-9220-41ac-8de0-ebcec63de2a8","undoStopId":"06a13de2-ff0e-4c42-b2fe-ce4bb4938b5e","epoch":981,"label":"Request request_c2dbf0bc-9220-41ac-8de0-ebcec63de2a8 - Stop 06a13de2-ff0e-4c42-b2fe-ce4bb4938b5e"},{"checkpointId":"f99fe29b-a6ee-4472-a124-c97239914fb4","requestId":"request_05ad5ed7-be85-45fb-af40-007422c0606f","epoch":983,"label":"Request request_05ad5ed7-be85-45fb-af40-007422c0606f"},{"checkpointId":"0c2d570c-b105-4c35-93a2-071b0ddde73f","requestId":"request_05ad5ed7-be85-45fb-af40-007422c0606f","undoStopId":"fee94441-bf3b-4d7a-9a85-dc6db86bbae8","epoch":984,"label":"Request request_05ad5ed7-be85-45fb-af40-007422c0606f - Stop fee94441-bf3b-4d7a-9a85-dc6db86bbae8"},{"checkpointId":"8ef48395-d463-49e8-a3d2-e05cfa0c6b59","requestId":"request_05ad5ed7-be85-45fb-af40-007422c0606f","undoStopId":"40e5d0c0-7be3-4966-a02e-d207dceebb0f","epoch":987,"label":"Request request_05ad5ed7-be85-45fb-af40-007422c0606f - Stop 40e5d0c0-7be3-4966-a02e-d207dceebb0f"},{"checkpointId":"c51f77d1-890d-4ec7-9490-f2dfb95fefd1","requestId":"request_05ad5ed7-be85-45fb-af40-007422c0606f","undoStopId":"07d6e2ac-35fb-4d36-ae13-e9e2e8962ed0","epoch":989,"label":"Request request_05ad5ed7-be85-45fb-af40-007422c0606f - Stop 07d6e2ac-35fb-4d36-ae13-e9e2e8962ed0"},{"checkpointId":"12fc5b67-f1ff-4cd1-8e24-c28b27712f4b","requestId":"request_05ad5ed7-be85-45fb-af40-007422c0606f","undoStopId":"e179b969-5efb-4a53-adbc-df953b5457ce","epoch":991,"label":"Request request_05ad5ed7-be85-45fb-af40-007422c0606f - Stop e179b969-5efb-4a53-adbc-df953b5457ce"},{"checkpointId":"1134657c-9cee-499d-88ab-5b656e86df1b","requestId":"request_05ad5ed7-be85-45fb-af40-007422c0606f","undoStopId":"5ae75a8c-74d0-4af4-aa47-d2d195142a34","epoch":993,"label":"Request request_05ad5ed7-be85-45fb-af40-007422c0606f - Stop 5ae75a8c-74d0-4af4-aa47-d2d195142a34"},{"checkpointId":"b7a13ae7-2339-4545-a360-4c5ffd596660","requestId":"request_814a4084-5479-45cb-b1bb-93faa91ce807","epoch":995,"label":"Request request_814a4084-5479-45cb-b1bb-93faa91ce807"},{"checkpointId":"0df8cabf-dea6-404d-b2c7-f9cc4cceea48","requestId":"request_814a4084-5479-45cb-b1bb-93faa91ce807","undoStopId":"1f1b6b06-5372-44da-b3e4-f57b9a61e1a1","epoch":996,"label":"Request request_814a4084-5479-45cb-b1bb-93faa91ce807 - Stop 1f1b6b06-5372-44da-b3e4-f57b9a61e1a1"},{"checkpointId":"e6650406-ab4d-43ac-b739-85207784e9dd","requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":999,"label":"Request request_3cb8f730-b84c-454e-9cfe-afd5a70a9203"},{"checkpointId":"8650959c-6cf0-4883-b4e1-70c07d1610a4","requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","undoStopId":"6ac44104-a3d3-4946-a6d1-94abf327cfc7","epoch":1341,"label":"Request request_3cb8f730-b84c-454e-9cfe-afd5a70a9203 - Stop 6ac44104-a3d3-4946-a6d1-94abf327cfc7"},{"checkpointId":"e49f6bc3-4310-4ff3-b8b1-22f65477a839","requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","undoStopId":"f7db4007-16bc-48a3-b535-44573aed62e7","epoch":1344,"label":"Request request_3cb8f730-b84c-454e-9cfe-afd5a70a9203 - Stop f7db4007-16bc-48a3-b535-44573aed62e7"},{"checkpointId":"6e606605-2025-4e72-8d0b-494dba69701e","requestId":"request_d489ba23-ce50-4bec-9632-8fe576c78ee8","epoch":1346,"label":"Request request_d489ba23-ce50-4bec-9632-8fe576c78ee8"},{"checkpointId":"f0b88b25-70e4-4d4f-9add-81c467ec4e8a","requestId":"request_d489ba23-ce50-4bec-9632-8fe576c78ee8","undoStopId":"48dfa40d-87dd-4dd7-8688-5af51dabe593","epoch":1347,"label":"Request request_d489ba23-ce50-4bec-9632-8fe576c78ee8 - Stop 48dfa40d-87dd-4dd7-8688-5af51dabe593"},{"checkpointId":"2e310f4b-4ca3-4473-9fd1-05521118fb3c","requestId":"request_d489ba23-ce50-4bec-9632-8fe576c78ee8","undoStopId":"e5dc9435-4250-4167-a8eb-f09a4f2ef6b7","epoch":1350,"label":"Request request_d489ba23-ce50-4bec-9632-8fe576c78ee8 - Stop e5dc9435-4250-4167-a8eb-f09a4f2ef6b7"},{"checkpointId":"a27989bf-e00f-4ca9-b629-493dbb0ed831","requestId":"request_d489ba23-ce50-4bec-9632-8fe576c78ee8","undoStopId":"9b7f08db-1f2a-454c-8c9f-9ff4a0064e46","epoch":1353,"label":"Request request_d489ba23-ce50-4bec-9632-8fe576c78ee8 - Stop 9b7f08db-1f2a-454c-8c9f-9ff4a0064e46"},{"checkpointId":"ae39c630-731c-4829-a96f-615318afdf1a","requestId":"request_a50ae47b-0ab6-4f4c-9668-6b3916eef14c","epoch":1356,"label":"Request request_a50ae47b-0ab6-4f4c-9668-6b3916eef14c"},{"checkpointId":"cdb9b3f6-dbfa-4ff0-9a8a-4bb841aa3a0d","requestId":"request_a50ae47b-0ab6-4f4c-9668-6b3916eef14c","undoStopId":"ded303e6-1348-4e01-befe-fdbb6c780166","epoch":1357,"label":"Request request_a50ae47b-0ab6-4f4c-9668-6b3916eef14c - Stop ded303e6-1348-4e01-befe-fdbb6c780166"},{"checkpointId":"b81907e3-d560-4065-bcc7-dcf3b9e45feb","requestId":"request_a50ae47b-0ab6-4f4c-9668-6b3916eef14c","undoStopId":"d365c9e2-118c-42e6-8d1e-64b7a130af16","epoch":1360,"label":"Request request_a50ae47b-0ab6-4f4c-9668-6b3916eef14c - Stop d365c9e2-118c-42e6-8d1e-64b7a130af16"},{"checkpointId":"ffe5f6b3-4184-4f9b-859f-566438c56ee7","requestId":"request_a50ae47b-0ab6-4f4c-9668-6b3916eef14c","undoStopId":"56cc542f-3d05-4d10-806b-d41358f279a6","epoch":1363,"label":"Request request_a50ae47b-0ab6-4f4c-9668-6b3916eef14c - Stop 56cc542f-3d05-4d10-806b-d41358f279a6"},{"checkpointId":"0c82eca2-0aed-4e84-a6e0-0dd651c8d60a","requestId":"request_a50ae47b-0ab6-4f4c-9668-6b3916eef14c","undoStopId":"09285d7b-ad4a-4797-bd8b-41511a004bc6","epoch":1365,"label":"Request request_a50ae47b-0ab6-4f4c-9668-6b3916eef14c - Stop 09285d7b-ad4a-4797-bd8b-41511a004bc6"},{"checkpointId":"6f1e2ba6-33db-42e3-97b7-9a206d6628e1","requestId":"request_a50ae47b-0ab6-4f4c-9668-6b3916eef14c","undoStopId":"a0bef8f1-a897-4ced-9265-cb2a6bf2790b","epoch":1368,"label":"Request request_a50ae47b-0ab6-4f4c-9668-6b3916eef14c - Stop a0bef8f1-a897-4ced-9265-cb2a6bf2790b"},{"checkpointId":"a9f2c403-1c83-4356-a3d7-50fd8ebfb473","requestId":"request_c2e7802d-6ac3-42bb-bedd-9832d01edc73","epoch":1370,"label":"Request request_c2e7802d-6ac3-42bb-bedd-9832d01edc73"},{"checkpointId":"171ebe8f-3aae-46cc-a66b-ea7f7774a164","requestId":"request_35127224-e6a7-4c31-8952-d8bd8bf5d5fe","epoch":1371,"label":"Request request_35127224-e6a7-4c31-8952-d8bd8bf5d5fe"},{"checkpointId":"47cc02d1-9e42-4e74-8e82-cd78ec8c9714","requestId":"request_35127224-e6a7-4c31-8952-d8bd8bf5d5fe","undoStopId":"b237f407-c039-4855-8856-555333915950","epoch":1416,"label":"Request request_35127224-e6a7-4c31-8952-d8bd8bf5d5fe - Stop b237f407-c039-4855-8856-555333915950"},{"checkpointId":"125d9bce-6c7f-48fc-9ee2-649a479b8be8","requestId":"request_4cf34ef4-97c9-4186-aa5c-97f3c64954dc","epoch":1419,"label":"Request request_4cf34ef4-97c9-4186-aa5c-97f3c64954dc"},{"checkpointId":"bdb99451-3b15-4c19-82fc-85b2a493e061","requestId":"request_dc7aedcd-b1dc-443b-8063-d3b5527768d7","epoch":1420,"label":"Request request_dc7aedcd-b1dc-443b-8063-d3b5527768d7"},{"checkpointId":"177e2113-e37c-4c17-b8a5-70c9c9825367","requestId":"request_dc7aedcd-b1dc-443b-8063-d3b5527768d7","undoStopId":"6e187660-cd5a-444b-8604-063a76f88a66","epoch":1421,"label":"Request request_dc7aedcd-b1dc-443b-8063-d3b5527768d7 - Stop 6e187660-cd5a-444b-8604-063a76f88a66"},{"checkpointId":"6f5325f1-737c-4450-a717-44b455862709","requestId":"request_dc7aedcd-b1dc-443b-8063-d3b5527768d7","undoStopId":"bb6f3afc-0b2c-4e71-bed4-92f9de3d9ef0","epoch":1424,"label":"Request request_dc7aedcd-b1dc-443b-8063-d3b5527768d7 - Stop bb6f3afc-0b2c-4e71-bed4-92f9de3d9ef0"},{"checkpointId":"5b9436d5-c489-4f83-be28-f5bf6ab67be8","requestId":"request_dc7aedcd-b1dc-443b-8063-d3b5527768d7","undoStopId":"8efe0a0e-864a-4e60-a183-685ac3e8a41e","epoch":1427,"label":"Request request_dc7aedcd-b1dc-443b-8063-d3b5527768d7 - Stop 8efe0a0e-864a-4e60-a183-685ac3e8a41e"},{"checkpointId":"deec4df1-359c-44e0-be59-6432ef52444c","requestId":"request_dc7aedcd-b1dc-443b-8063-d3b5527768d7","undoStopId":"1517bbad-c1ce-40b4-b26a-07efc3a0754c","epoch":1430,"label":"Request request_dc7aedcd-b1dc-443b-8063-d3b5527768d7 - Stop 1517bbad-c1ce-40b4-b26a-07efc3a0754c"},{"checkpointId":"6d6aa228-9086-493f-bcdc-2fc27901f1e2","requestId":"request_dc7aedcd-b1dc-443b-8063-d3b5527768d7","undoStopId":"48cd2523-43d0-47bc-a11a-1acdd6996ce8","epoch":1433,"label":"Request request_dc7aedcd-b1dc-443b-8063-d3b5527768d7 - Stop 48cd2523-43d0-47bc-a11a-1acdd6996ce8"},{"checkpointId":"47c93b2b-78a3-427f-b7f0-77c1a09d7b97","requestId":"request_72a7d041-925f-4bbf-a717-feff5a61422d","epoch":1436,"label":"Request request_72a7d041-925f-4bbf-a717-feff5a61422d"},{"checkpointId":"6386c374-917c-447d-bd02-4d053ab0aa60","requestId":"request_72a7d041-925f-4bbf-a717-feff5a61422d","undoStopId":"1b1fb432-d7b8-430f-a552-6bb37a9a5fbe","epoch":1437,"label":"Request request_72a7d041-925f-4bbf-a717-feff5a61422d - Stop 1b1fb432-d7b8-430f-a552-6bb37a9a5fbe"},{"checkpointId":"482a9286-d2eb-45d0-887a-6ed4b4be6f15","requestId":"request_7c1fcecb-8a9a-46b2-9072-3ea1069fbf7d","epoch":1440,"label":"Request request_7c1fcecb-8a9a-46b2-9072-3ea1069fbf7d"},{"checkpointId":"4fe22f63-1b91-4a70-b4c5-52933e1f30e5","requestId":"request_7c1fcecb-8a9a-46b2-9072-3ea1069fbf7d","undoStopId":"2dab4068-35a5-45e6-ba45-2b0657125e6a","epoch":1441,"label":"Request request_7c1fcecb-8a9a-46b2-9072-3ea1069fbf7d - Stop 2dab4068-35a5-45e6-ba45-2b0657125e6a"},{"checkpointId":"442fa464-233a-4d4e-9c1c-9d1d89068931","requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1444,"label":"Request request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8"},{"checkpointId":"14c7861d-b55d-4e4d-a2c6-f8eea2aab737","requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","undoStopId":"c26db1d2-36b8-40fd-8630-fcb18e09c248","epoch":1498,"label":"Request request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8 - Stop c26db1d2-36b8-40fd-8630-fcb18e09c248"},{"checkpointId":"702a4747-4b56-429d-8973-f3ffa3b6852b","requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","undoStopId":"e14df9c5-d70a-4954-b526-2b4ccaaa1605","epoch":1501,"label":"Request request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8 - Stop e14df9c5-d70a-4954-b526-2b4ccaaa1605"},{"checkpointId":"bcae6c8d-6cc4-4eb6-b46d-5ddda7e94e70","requestId":"request_0837dd8d-03e4-419a-ab9a-44ad661c4a6c","epoch":1503,"label":"Request request_0837dd8d-03e4-419a-ab9a-44ad661c4a6c"},{"checkpointId":"e28ce59c-64d8-4b06-bd1a-36942aa78ddc","requestId":"request_0837dd8d-03e4-419a-ab9a-44ad661c4a6c","undoStopId":"36b0e6d8-0af2-4127-a341-4372774159a6","epoch":1504,"label":"Request request_0837dd8d-03e4-419a-ab9a-44ad661c4a6c - Stop 36b0e6d8-0af2-4127-a341-4372774159a6"},{"checkpointId":"ccab3a22-9df2-4cdc-a41e-331395cb8827","requestId":"request_0837dd8d-03e4-419a-ab9a-44ad661c4a6c","undoStopId":"1061fefa-bcc3-4fb2-9f9c-4f989bd66d25","epoch":1507,"label":"Request request_0837dd8d-03e4-419a-ab9a-44ad661c4a6c - Stop 1061fefa-bcc3-4fb2-9f9c-4f989bd66d25"},{"checkpointId":"7f1616b2-fc28-4bc4-8d21-309b55fa926c","requestId":"request_244f6538-b0e5-4f3c-84eb-c094cc01ec2c","epoch":1510,"label":"Request request_244f6538-b0e5-4f3c-84eb-c094cc01ec2c"},{"checkpointId":"2d58ba0e-1c91-44c8-8259-5cdce1ce3f06","requestId":"request_244f6538-b0e5-4f3c-84eb-c094cc01ec2c","undoStopId":"2cf94747-a063-4e98-9af7-22cfa2ce9e50","epoch":1511,"label":"Request request_244f6538-b0e5-4f3c-84eb-c094cc01ec2c - Stop 2cf94747-a063-4e98-9af7-22cfa2ce9e50"},{"checkpointId":"3f198fd5-2d44-4fd5-a69d-1e8a04c7c9a8","requestId":"request_244f6538-b0e5-4f3c-84eb-c094cc01ec2c","undoStopId":"afca1dcb-ab99-47d3-a566-8f40316d5556","epoch":1514,"label":"Request request_244f6538-b0e5-4f3c-84eb-c094cc01ec2c - Stop afca1dcb-ab99-47d3-a566-8f40316d5556"},{"checkpointId":"e37a4074-bbb4-4027-90f5-31c14b33e76d","requestId":"request_244f6538-b0e5-4f3c-84eb-c094cc01ec2c","undoStopId":"e29d663b-e33c-4c94-ac6e-ec0c0260c400","epoch":1516,"label":"Request request_244f6538-b0e5-4f3c-84eb-c094cc01ec2c - Stop e29d663b-e33c-4c94-ac6e-ec0c0260c400"},{"checkpointId":"3c976c4a-f6ad-4600-8b5c-75895b03cac7","requestId":"request_244f6538-b0e5-4f3c-84eb-c094cc01ec2c","undoStopId":"4a209d44-826f-46d2-81ce-acf00e894dc3","epoch":1518,"label":"Request request_244f6538-b0e5-4f3c-84eb-c094cc01ec2c - Stop 4a209d44-826f-46d2-81ce-acf00e894dc3"},{"checkpointId":"7a4e4692-52aa-48f9-a2ca-8fe49849a89c","requestId":"request_cd9235f7-4b6a-4823-ab9f-74e85fd0e64c","epoch":1521,"label":"Request request_cd9235f7-4b6a-4823-ab9f-74e85fd0e64c"},{"checkpointId":"a1ca7c02-9023-4ea6-83e0-dcf72e5b6294","requestId":"request_cd9235f7-4b6a-4823-ab9f-74e85fd0e64c","undoStopId":"35f3813d-1be2-4350-973e-b476ca458f2f","epoch":1522,"label":"Request request_cd9235f7-4b6a-4823-ab9f-74e85fd0e64c - Stop 35f3813d-1be2-4350-973e-b476ca458f2f"},{"checkpointId":"79d98061-7393-4552-8b7d-e7c8836d1475","requestId":"request_cd9235f7-4b6a-4823-ab9f-74e85fd0e64c","undoStopId":"75f6fde5-e20a-410a-93b0-afad512163f1","epoch":1525,"label":"Request request_cd9235f7-4b6a-4823-ab9f-74e85fd0e64c - Stop 75f6fde5-e20a-410a-93b0-afad512163f1"},{"checkpointId":"7acc5f36-9a1e-44b8-9d20-d6e9970fa866","requestId":"request_cd9235f7-4b6a-4823-ab9f-74e85fd0e64c","undoStopId":"f8a5c320-8950-430c-9151-431d1f53aa21","epoch":1528,"label":"Request request_cd9235f7-4b6a-4823-ab9f-74e85fd0e64c - Stop f8a5c320-8950-430c-9151-431d1f53aa21"},{"checkpointId":"62e45b0d-c8d6-4371-903d-e5c0fbc35c06","requestId":"request_9bcbe4ab-720b-4660-919b-ab4be1cdb158","epoch":1531,"label":"Request request_9bcbe4ab-720b-4660-919b-ab4be1cdb158"},{"checkpointId":"69c68ae3-5e9e-49bf-b444-80b81d72e3fb","requestId":"request_9bcbe4ab-720b-4660-919b-ab4be1cdb158","undoStopId":"7317fae0-0134-44c4-8387-2b1f7fb44df2","epoch":1532,"label":"Request request_9bcbe4ab-720b-4660-919b-ab4be1cdb158 - Stop 7317fae0-0134-44c4-8387-2b1f7fb44df2"},{"checkpointId":"abae47f7-9a58-42d5-900c-50ccdccdf763","requestId":"request_9bcbe4ab-720b-4660-919b-ab4be1cdb158","undoStopId":"0f6502ce-cba1-4b48-88da-7643fa106531","epoch":1535,"label":"Request request_9bcbe4ab-720b-4660-919b-ab4be1cdb158 - Stop 0f6502ce-cba1-4b48-88da-7643fa106531"},{"checkpointId":"347b26d0-eaf6-43e2-82a4-14139852eee0","requestId":"request_2b97d49c-137d-4738-aaf4-66378596ea73","epoch":1538,"label":"Request request_2b97d49c-137d-4738-aaf4-66378596ea73"},{"checkpointId":"da53bf9f-1d0d-4769-9dcb-0a2cf3b6e5cc","requestId":"request_2b97d49c-137d-4738-aaf4-66378596ea73","undoStopId":"f3ff18ec-1bc3-4125-abe4-e34c02f70786","epoch":1539,"label":"Request request_2b97d49c-137d-4738-aaf4-66378596ea73 - Stop f3ff18ec-1bc3-4125-abe4-e34c02f70786"},{"checkpointId":"f9a9fd0d-b4d1-4a8e-8141-0ec750621ef3","requestId":"request_2b97d49c-137d-4738-aaf4-66378596ea73","undoStopId":"d2c73cd9-bdf5-45f5-9a30-947760e9102a","epoch":1542,"label":"Request request_2b97d49c-137d-4738-aaf4-66378596ea73 - Stop d2c73cd9-bdf5-45f5-9a30-947760e9102a"},{"checkpointId":"a404e610-f3c3-4e44-b0d0-1a2eb54e3a3c","requestId":"request_ed61c233-e170-4e17-8961-1cf0caa56eb3","epoch":1545,"label":"Request request_ed61c233-e170-4e17-8961-1cf0caa56eb3"},{"checkpointId":"ffa01a2c-b4fe-4f3d-b016-2ff6c7d5e70a","requestId":"request_ed61c233-e170-4e17-8961-1cf0caa56eb3","undoStopId":"09a802fa-2647-46f8-9da4-ec8bf0eaf73e","epoch":1546,"label":"Request request_ed61c233-e170-4e17-8961-1cf0caa56eb3 - Stop 09a802fa-2647-46f8-9da4-ec8bf0eaf73e"},{"checkpointId":"db232c8a-00da-4b77-b6ff-0c3047794843","requestId":"request_3718300f-4171-4bf2-8ae7-b39ae522ec11","epoch":1549,"label":"Request request_3718300f-4171-4bf2-8ae7-b39ae522ec11"},{"checkpointId":"b58d2c16-37ef-4e42-afb1-331c662e5bb3","requestId":"request_3718300f-4171-4bf2-8ae7-b39ae522ec11","undoStopId":"da1cc05c-f062-4d67-ad0d-d2bece840d92","epoch":1550,"label":"Request request_3718300f-4171-4bf2-8ae7-b39ae522ec11 - Stop da1cc05c-f062-4d67-ad0d-d2bece840d92"},{"checkpointId":"c19c18ff-c1d2-491b-a9f0-315c923384b3","requestId":"request_bcc1b2c2-e5c6-4c6f-9444-e7f9f5abb7a4","epoch":1553,"label":"Request request_bcc1b2c2-e5c6-4c6f-9444-e7f9f5abb7a4"},{"checkpointId":"363078d1-f747-41bd-a1a1-894483e72984","requestId":"request_bcc1b2c2-e5c6-4c6f-9444-e7f9f5abb7a4","undoStopId":"d28f0c9a-63da-4082-861c-f19f5dbf2745","epoch":1554,"label":"Request request_bcc1b2c2-e5c6-4c6f-9444-e7f9f5abb7a4 - Stop d28f0c9a-63da-4082-861c-f19f5dbf2745"},{"checkpointId":"ca19c7ba-da1c-49c6-b100-ca8365e5f3ca","requestId":"request_bcc1b2c2-e5c6-4c6f-9444-e7f9f5abb7a4","undoStopId":"406346be-bb36-45f8-aebc-78f331e239dd","epoch":1557,"label":"Request request_bcc1b2c2-e5c6-4c6f-9444-e7f9f5abb7a4 - Stop 406346be-bb36-45f8-aebc-78f331e239dd"},{"checkpointId":"011925fb-04c0-4534-bd6b-7290b5cf91a9","requestId":"request_bcc1b2c2-e5c6-4c6f-9444-e7f9f5abb7a4","undoStopId":"81984c53-f914-4053-8164-ec8e110c2247","epoch":1560,"label":"Request request_bcc1b2c2-e5c6-4c6f-9444-e7f9f5abb7a4 - Stop 81984c53-f914-4053-8164-ec8e110c2247"},{"checkpointId":"76da850a-c02c-4c31-882e-fcf9b070907b","requestId":"request_66e3538f-7fed-4c58-b7f0-e5edecfabab8","epoch":1562,"label":"Request request_66e3538f-7fed-4c58-b7f0-e5edecfabab8"},{"checkpointId":"357dabe5-d617-45fa-9027-d39157cba3e0","requestId":"request_66e3538f-7fed-4c58-b7f0-e5edecfabab8","undoStopId":"ce674d38-c92c-4bc4-8baa-e01a043fc47d","epoch":1563,"label":"Request request_66e3538f-7fed-4c58-b7f0-e5edecfabab8 - Stop ce674d38-c92c-4bc4-8baa-e01a043fc47d"},{"checkpointId":"05a28b1d-c892-4d78-a0af-044f043c4aaf","requestId":"request_66e3538f-7fed-4c58-b7f0-e5edecfabab8","undoStopId":"4f16db1b-31ad-4880-b683-df92edae9c18","epoch":1566,"label":"Request request_66e3538f-7fed-4c58-b7f0-e5edecfabab8 - Stop 4f16db1b-31ad-4880-b683-df92edae9c18"},{"checkpointId":"b7bc0192-df83-4560-a6b5-2d58760b9f69","requestId":"request_940afaca-5401-4dbf-a524-3fba59fd2ac5","epoch":1569,"label":"Request request_940afaca-5401-4dbf-a524-3fba59fd2ac5"},{"checkpointId":"9c6fdc7b-d265-417c-84cc-dd40fe824b74","requestId":"request_940afaca-5401-4dbf-a524-3fba59fd2ac5","undoStopId":"ab7cbb77-9855-432a-8353-bdd536a56679","epoch":1570,"label":"Request request_940afaca-5401-4dbf-a524-3fba59fd2ac5 - Stop ab7cbb77-9855-432a-8353-bdd536a56679"},{"checkpointId":"cb3cd693-b4c6-43d0-a1e0-fe1410aa3ebb","requestId":"request_940afaca-5401-4dbf-a524-3fba59fd2ac5","undoStopId":"97c579d4-4693-4b5d-ae1e-caf6703a548b","epoch":1573,"label":"Request request_940afaca-5401-4dbf-a524-3fba59fd2ac5 - Stop 97c579d4-4693-4b5d-ae1e-caf6703a548b"},{"checkpointId":"1d62ae01-d9d4-4e4b-bc1f-950b615cfa00","requestId":"request_067cff51-56fd-4149-8442-72ebf7390cc5","epoch":1576,"label":"Request request_067cff51-56fd-4149-8442-72ebf7390cc5"},{"checkpointId":"96b7a1ac-3358-44c4-bf5a-ef77d0641feb","requestId":"request_1d9fb0bc-867e-467e-86a5-8e6b0b5a1c0d","epoch":1577,"label":"Request request_1d9fb0bc-867e-467e-86a5-8e6b0b5a1c0d"},{"checkpointId":"338b4bf6-cfa8-46f9-a1d8-ef77ee2809bd","requestId":"request_1d9fb0bc-867e-467e-86a5-8e6b0b5a1c0d","undoStopId":"74f86514-7c91-4292-9619-980552a7187d","epoch":1578,"label":"Request request_1d9fb0bc-867e-467e-86a5-8e6b0b5a1c0d - Stop 74f86514-7c91-4292-9619-980552a7187d"},{"checkpointId":"e765c3f8-9fb2-46f3-8f05-110c8db4428b","requestId":"request_2b3cf4b2-947f-4212-8eb2-fe50d56814f0","epoch":1581,"label":"Request request_2b3cf4b2-947f-4212-8eb2-fe50d56814f0"},{"checkpointId":"6bde87a4-a3fc-40a9-9e44-73b9010b0232","requestId":"request_b0ebfe2c-99b2-4212-9657-77428a9baa8d","epoch":1582,"label":"Request request_b0ebfe2c-99b2-4212-9657-77428a9baa8d"},{"checkpointId":"ad2b3d58-3bec-4366-bb6c-b48d2c17d42d","requestId":"request_b0ebfe2c-99b2-4212-9657-77428a9baa8d","undoStopId":"bb91886a-a669-498a-a342-201ce72e0c4d","epoch":1583,"label":"Request request_b0ebfe2c-99b2-4212-9657-77428a9baa8d - Stop bb91886a-a669-498a-a342-201ce72e0c4d"},{"checkpointId":"5b7e338d-266a-40b4-9cd6-454267a5ce1d","requestId":"request_f47e2855-d76f-4d49-b524-bdc5e267fde9","epoch":1586,"label":"Request request_f47e2855-d76f-4d49-b524-bdc5e267fde9"},{"checkpointId":"e12bc212-8481-405c-880d-6cd6a6b69810","requestId":"request_f47e2855-d76f-4d49-b524-bdc5e267fde9","undoStopId":"133d9de5-789c-4fbb-8b61-2c02ee90f72e","epoch":1587,"label":"Request request_f47e2855-d76f-4d49-b524-bdc5e267fde9 - Stop 133d9de5-789c-4fbb-8b61-2c02ee90f72e"},{"checkpointId":"e7f80172-ddb6-4e42-b8be-6be504ec2b65","requestId":"request_38f8befb-ed20-48d4-a718-4a697ba92587","epoch":1590,"label":"Request request_38f8befb-ed20-48d4-a718-4a697ba92587"},{"checkpointId":"1803fb7c-0ec1-47e6-b0cc-fa78467cf370","requestId":"request_38f8befb-ed20-48d4-a718-4a697ba92587","undoStopId":"850adf58-2f33-4d0a-b3fc-eb85b0ee684e","epoch":1591,"label":"Request request_38f8befb-ed20-48d4-a718-4a697ba92587 - Stop 850adf58-2f33-4d0a-b3fc-eb85b0ee684e"},{"checkpointId":"3dd2727a-b463-4522-90a9-edfacad242d8","requestId":"request_38f8befb-ed20-48d4-a718-4a697ba92587","undoStopId":"772dd8ae-082f-4149-8878-a202f76a7ec8","epoch":1594,"label":"Request request_38f8befb-ed20-48d4-a718-4a697ba92587 - Stop 772dd8ae-082f-4149-8878-a202f76a7ec8"},{"checkpointId":"631a33ba-97b8-44f2-88cc-eb767bac934c","requestId":"request_10cae230-7b11-4b8d-94f0-da978bd0fbe4","epoch":1597,"label":"Request request_10cae230-7b11-4b8d-94f0-da978bd0fbe4"},{"checkpointId":"fc63209c-e8eb-4889-b7a8-1e2100fc856b","requestId":"request_10cae230-7b11-4b8d-94f0-da978bd0fbe4","undoStopId":"91ae7a43-07cd-49c1-877e-c3f3a9dc2e68","epoch":1598,"label":"Request request_10cae230-7b11-4b8d-94f0-da978bd0fbe4 - Stop 91ae7a43-07cd-49c1-877e-c3f3a9dc2e68"},{"checkpointId":"0a2c99f1-111a-4a15-9cf0-a4895430b8df","requestId":"request_bb69abcc-71e3-469c-8c5d-1b8769eea60e","epoch":1601,"label":"Request request_bb69abcc-71e3-469c-8c5d-1b8769eea60e"},{"checkpointId":"efefbbf1-d1d6-4a79-8207-6040212db9a4","requestId":"request_bb69abcc-71e3-469c-8c5d-1b8769eea60e","undoStopId":"18d9feb4-07f0-449b-9b2b-3748e7fdaba8","epoch":1602,"label":"Request request_bb69abcc-71e3-469c-8c5d-1b8769eea60e - Stop 18d9feb4-07f0-449b-9b2b-3748e7fdaba8"},{"checkpointId":"0d5899c7-799a-4fec-b4ed-4b782b734137","requestId":"request_38df11d7-df4d-47e8-996c-ce006e7e6d59","epoch":1605,"label":"Request request_38df11d7-df4d-47e8-996c-ce006e7e6d59"},{"checkpointId":"df836def-ce5a-4a0d-8b30-f4a806362319","requestId":"request_38df11d7-df4d-47e8-996c-ce006e7e6d59","undoStopId":"c111983a-5ec2-481a-9fdd-76b97baa35cb","epoch":1606,"label":"Request request_38df11d7-df4d-47e8-996c-ce006e7e6d59 - Stop c111983a-5ec2-481a-9fdd-76b97baa35cb"},{"checkpointId":"57b0a8ed-8b94-40d6-9a12-b80c343ae28b","requestId":"request_a7dd4e41-c8fa-4e43-bca4-89ec5b632f41","epoch":1609,"label":"Request request_a7dd4e41-c8fa-4e43-bca4-89ec5b632f41"},{"checkpointId":"30a90f95-fbeb-4d35-87d4-66a66bb4f649","requestId":"request_a7dd4e41-c8fa-4e43-bca4-89ec5b632f41","undoStopId":"1837b034-b580-413e-98da-a532a21fb6ad","epoch":1610,"label":"Request request_a7dd4e41-c8fa-4e43-bca4-89ec5b632f41 - Stop 1837b034-b580-413e-98da-a532a21fb6ad"},{"checkpointId":"3c137484-be7a-485e-a846-8b96369e1810","requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1613,"label":"Request request_7a4b86e1-4322-4b4e-9754-ee5435807546"},{"checkpointId":"32f55415-cb28-4667-b360-52e2a798236d","requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","undoStopId":"55f07611-f22d-40fe-b106-a51e8cf8525f","epoch":1614,"label":"Request request_7a4b86e1-4322-4b4e-9754-ee5435807546 - Stop 55f07611-f22d-40fe-b106-a51e8cf8525f"},{"checkpointId":"fa757381-1a9f-4350-b332-59a7c312729f","requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","undoStopId":"ca1fc2e3-c279-47bc-bc93-56793fc7fbe5","epoch":1617,"label":"Request request_7a4b86e1-4322-4b4e-9754-ee5435807546 - Stop ca1fc2e3-c279-47bc-bc93-56793fc7fbe5"},{"checkpointId":"3f714b40-3e87-4d98-94d9-1edde66ba17e","requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","undoStopId":"4b1374eb-b72d-4053-9fec-53590d5ee0e8","epoch":1620,"label":"Request request_7a4b86e1-4322-4b4e-9754-ee5435807546 - Stop 4b1374eb-b72d-4053-9fec-53590d5ee0e8"},{"checkpointId":"7d3c9de8-9b7c-4a38-af85-41bb2e94ca4b","requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","undoStopId":"8d436372-0feb-4bbb-8f70-f8639456804e","epoch":1682,"label":"Request request_7a4b86e1-4322-4b4e-9754-ee5435807546 - Stop 8d436372-0feb-4bbb-8f70-f8639456804e"},{"checkpointId":"f8f9b323-299b-43e6-9884-cbba43c2d59d","requestId":"request_6f13d5f3-b3de-4f61-a09f-3c6ee71f5876","epoch":1684,"label":"Request request_6f13d5f3-b3de-4f61-a09f-3c6ee71f5876"},{"checkpointId":"5db9e092-68fc-47d1-8bdb-11829d0c34ea","requestId":"request_6f13d5f3-b3de-4f61-a09f-3c6ee71f5876","undoStopId":"ed0550c7-681d-4bb0-99c5-79e9366bdeba","epoch":1685,"label":"Request request_6f13d5f3-b3de-4f61-a09f-3c6ee71f5876 - Stop ed0550c7-681d-4bb0-99c5-79e9366bdeba"},{"checkpointId":"902458ad-e378-47b2-91bb-fa1bf1b60112","requestId":"request_6f13d5f3-b3de-4f61-a09f-3c6ee71f5876","undoStopId":"8cd2eec0-e8a6-4c83-87f2-cab33df4a3a0","epoch":1688,"label":"Request request_6f13d5f3-b3de-4f61-a09f-3c6ee71f5876 - Stop 8cd2eec0-e8a6-4c83-87f2-cab33df4a3a0"},{"checkpointId":"b359cc15-8706-4d10-81fc-a728bee59f0a","requestId":"request_6f13d5f3-b3de-4f61-a09f-3c6ee71f5876","undoStopId":"406ed575-da5c-40e0-95d5-0fc9032a4265","epoch":1691,"label":"Request request_6f13d5f3-b3de-4f61-a09f-3c6ee71f5876 - Stop 406ed575-da5c-40e0-95d5-0fc9032a4265"},{"checkpointId":"45eb3e73-8ddf-4184-b768-aeb95dd9350d","requestId":"request_6f13d5f3-b3de-4f61-a09f-3c6ee71f5876","undoStopId":"c526df3e-7eb6-4d00-aed3-f6e5e9bac089","epoch":1693,"label":"Request request_6f13d5f3-b3de-4f61-a09f-3c6ee71f5876 - Stop c526df3e-7eb6-4d00-aed3-f6e5e9bac089"},{"checkpointId":"2bf592ee-51d2-4df8-b29e-9bcc6a2a4031","requestId":"request_6f13d5f3-b3de-4f61-a09f-3c6ee71f5876","undoStopId":"e61bd58f-f657-409c-9ae0-22f1336f2b1b","epoch":1695,"label":"Request request_6f13d5f3-b3de-4f61-a09f-3c6ee71f5876 - Stop e61bd58f-f657-409c-9ae0-22f1336f2b1b"},{"checkpointId":"4e54eabf-ad79-4fc9-8f55-30d4be86c7c6","requestId":"request_6f13d5f3-b3de-4f61-a09f-3c6ee71f5876","undoStopId":"09c6f8b9-0c01-47e5-9bd1-e6430d72319a","epoch":1697,"label":"Request request_6f13d5f3-b3de-4f61-a09f-3c6ee71f5876 - Stop 09c6f8b9-0c01-47e5-9bd1-e6430d72319a"},{"checkpointId":"2fe3ebee-4643-4963-894d-b3a292c10e9d","requestId":"request_253d5ff6-6f1d-4b2e-83a2-f98328aa01b9","epoch":1699,"label":"Request request_253d5ff6-6f1d-4b2e-83a2-f98328aa01b9"},{"checkpointId":"e4266512-e555-4582-bd70-79814621dec9","requestId":"request_253d5ff6-6f1d-4b2e-83a2-f98328aa01b9","undoStopId":"9c5f678e-68dd-4b8e-9cb8-6c113af2425b","epoch":1700,"label":"Request request_253d5ff6-6f1d-4b2e-83a2-f98328aa01b9 - Stop 9c5f678e-68dd-4b8e-9cb8-6c113af2425b"},{"checkpointId":"b9612a6c-b5b9-4f76-aec6-cff6da0af3e6","requestId":"request_253d5ff6-6f1d-4b2e-83a2-f98328aa01b9","undoStopId":"7d8b6e73-4051-4f18-917d-192e8ff558c2","epoch":1703,"label":"Request request_253d5ff6-6f1d-4b2e-83a2-f98328aa01b9 - Stop 7d8b6e73-4051-4f18-917d-192e8ff558c2"},{"checkpointId":"7ce63c1a-9760-4657-8d0c-d4244f127761","requestId":"request_253d5ff6-6f1d-4b2e-83a2-f98328aa01b9","undoStopId":"379c9e02-88a1-440c-8289-f0e7b46a8a9e","epoch":1706,"label":"Request request_253d5ff6-6f1d-4b2e-83a2-f98328aa01b9 - Stop 379c9e02-88a1-440c-8289-f0e7b46a8a9e"},{"checkpointId":"5330d942-fbe5-4de0-86b5-a0d5c21e4b9e","requestId":"request_253d5ff6-6f1d-4b2e-83a2-f98328aa01b9","undoStopId":"314e67ed-5d4f-4ee7-a036-3973517bb310","epoch":1709,"label":"Request request_253d5ff6-6f1d-4b2e-83a2-f98328aa01b9 - Stop 314e67ed-5d4f-4ee7-a036-3973517bb310"},{"checkpointId":"da3d809b-8e2a-4d5f-a2fc-30659bd0f163","requestId":"request_52caa11c-9347-4b92-bae5-d4d2c73cbf1f","epoch":1712,"label":"Request request_52caa11c-9347-4b92-bae5-d4d2c73cbf1f"},{"checkpointId":"57ce1858-e969-4b6c-8d81-9264176cd857","requestId":"request_52caa11c-9347-4b92-bae5-d4d2c73cbf1f","undoStopId":"18a3badb-7e84-4b3a-8328-78bba44887ed","epoch":1713,"label":"Request request_52caa11c-9347-4b92-bae5-d4d2c73cbf1f - Stop 18a3badb-7e84-4b3a-8328-78bba44887ed"},{"checkpointId":"152ff8f9-ac55-46ed-839e-1ce93c4f1303","requestId":"request_52caa11c-9347-4b92-bae5-d4d2c73cbf1f","undoStopId":"2a7eee01-2975-4a55-b8d1-ff41790a0c05","epoch":1716,"label":"Request request_52caa11c-9347-4b92-bae5-d4d2c73cbf1f - Stop 2a7eee01-2975-4a55-b8d1-ff41790a0c05"},{"checkpointId":"8db46a9d-98d4-4ede-83ed-669b9faacb59","requestId":"request_52caa11c-9347-4b92-bae5-d4d2c73cbf1f","undoStopId":"a0efd8e9-7d90-4a3a-8236-c48bcd13db74","epoch":1718,"label":"Request request_52caa11c-9347-4b92-bae5-d4d2c73cbf1f - Stop a0efd8e9-7d90-4a3a-8236-c48bcd13db74"},{"checkpointId":"cc8fd4e5-4788-410a-b264-1a2393e61046","requestId":"request_52caa11c-9347-4b92-bae5-d4d2c73cbf1f","undoStopId":"923975fe-47a1-49a6-af89-e0ae1a29f342","epoch":1720,"label":"Request request_52caa11c-9347-4b92-bae5-d4d2c73cbf1f - Stop 923975fe-47a1-49a6-af89-e0ae1a29f342"},{"checkpointId":"d6c12b5c-0a0c-4711-be36-e199145cb4f5","requestId":"request_52caa11c-9347-4b92-bae5-d4d2c73cbf1f","undoStopId":"06fc5bb8-3857-4508-a8da-9646c3b52e27","epoch":1722,"label":"Request request_52caa11c-9347-4b92-bae5-d4d2c73cbf1f - Stop 06fc5bb8-3857-4508-a8da-9646c3b52e27"},{"checkpointId":"1943b96a-6da1-4186-8ead-407b842b13fc","requestId":"request_f1f75ee3-7832-415f-ba36-db253bd15323","epoch":1724,"label":"Request request_f1f75ee3-7832-415f-ba36-db253bd15323"},{"checkpointId":"9b97f8ea-1f74-4568-b22d-c1ae64f36c7a","requestId":"request_f1f75ee3-7832-415f-ba36-db253bd15323","undoStopId":"56939e2a-ff4b-4c52-b1a3-1197952fc69a","epoch":1725,"label":"Request request_f1f75ee3-7832-415f-ba36-db253bd15323 - Stop 56939e2a-ff4b-4c52-b1a3-1197952fc69a"},{"checkpointId":"affa5163-6a78-4ba8-b2de-f1914340aea4","requestId":"request_19ae42fe-2a8e-4927-9a95-27d34479b063","epoch":1728,"label":"Request request_19ae42fe-2a8e-4927-9a95-27d34479b063"},{"checkpointId":"1606040c-ae60-4c6c-a41c-7400e37371db","requestId":"request_19ae42fe-2a8e-4927-9a95-27d34479b063","undoStopId":"e44354ad-8a6f-42ef-885f-547acb6d99a0","epoch":1729,"label":"Request request_19ae42fe-2a8e-4927-9a95-27d34479b063 - Stop e44354ad-8a6f-42ef-885f-547acb6d99a0"},{"checkpointId":"81325efb-b544-47f6-adb5-3bf430789f0f","requestId":"request_19ae42fe-2a8e-4927-9a95-27d34479b063","undoStopId":"a378f5bc-7f86-47e5-97b1-681891b99e6a","epoch":1732,"label":"Request request_19ae42fe-2a8e-4927-9a95-27d34479b063 - Stop a378f5bc-7f86-47e5-97b1-681891b99e6a"},{"checkpointId":"0702973f-39db-4d8c-bce8-18b275ee9d09","requestId":"request_ea2c63b4-ffe0-48a8-aaf1-044afe929342","epoch":1734,"label":"Request request_ea2c63b4-ffe0-48a8-aaf1-044afe929342"},{"checkpointId":"2e7352e9-a1cc-495f-8398-a28593b55dc9","requestId":"request_ea2c63b4-ffe0-48a8-aaf1-044afe929342","undoStopId":"28ff632a-78db-433f-ad3e-77ebdb7ad217","epoch":1735,"label":"Request request_ea2c63b4-ffe0-48a8-aaf1-044afe929342 - Stop 28ff632a-78db-433f-ad3e-77ebdb7ad217"},{"checkpointId":"a63668a4-d72f-43c6-8d8f-3b70120b1cf8","requestId":"request_9a5be07b-e7a2-40f4-9005-8520832f2361","epoch":1738,"label":"Request request_9a5be07b-e7a2-40f4-9005-8520832f2361"},{"checkpointId":"c71b20bd-eb6b-4dec-abb1-4d6763bc9049","requestId":"request_9a5be07b-e7a2-40f4-9005-8520832f2361","undoStopId":"efc99784-d18f-4055-8e5c-341534a70c36","epoch":1739,"label":"Request request_9a5be07b-e7a2-40f4-9005-8520832f2361 - Stop efc99784-d18f-4055-8e5c-341534a70c36"},{"checkpointId":"a41b8551-cf5e-4184-9b42-8da60bfc13a8","requestId":"request_9a5be07b-e7a2-40f4-9005-8520832f2361","undoStopId":"15feefae-1df0-4535-905d-298b8d741025","epoch":1742,"label":"Request request_9a5be07b-e7a2-40f4-9005-8520832f2361 - Stop 15feefae-1df0-4535-905d-298b8d741025"},{"checkpointId":"b62b531b-d9a0-4952-bb0a-7b5f26123592","requestId":"request_9a5be07b-e7a2-40f4-9005-8520832f2361","undoStopId":"84e5efb9-64e7-4f4f-8db3-8a419485d66c","epoch":1744,"label":"Request request_9a5be07b-e7a2-40f4-9005-8520832f2361 - Stop 84e5efb9-64e7-4f4f-8db3-8a419485d66c"},{"checkpointId":"e45f9de3-4ae5-4c20-89e7-289078d91f97","requestId":"request_44941874-eadc-4ec9-81a4-51cfe2a1ad4e","epoch":1746,"label":"Request request_44941874-eadc-4ec9-81a4-51cfe2a1ad4e"},{"checkpointId":"5ba2dd36-1aa3-4ba6-993f-353edcc15871","requestId":"request_44941874-eadc-4ec9-81a4-51cfe2a1ad4e","undoStopId":"368349f9-e670-4e55-9632-eb3253b9df95","epoch":1747,"label":"Request request_44941874-eadc-4ec9-81a4-51cfe2a1ad4e - Stop 368349f9-e670-4e55-9632-eb3253b9df95"},{"checkpointId":"39951b39-74fa-454d-bd15-363fb3b9b923","requestId":"request_44941874-eadc-4ec9-81a4-51cfe2a1ad4e","undoStopId":"71d6b514-61bb-476a-85bc-cb5d484a937a","epoch":1750,"label":"Request request_44941874-eadc-4ec9-81a4-51cfe2a1ad4e - Stop 71d6b514-61bb-476a-85bc-cb5d484a937a"},{"checkpointId":"7bc8a160-ef7c-418f-8598-d448d74f252d","requestId":"request_9257a937-1bc9-4027-ad1c-0dd52ffd875f","epoch":1752,"label":"Request request_9257a937-1bc9-4027-ad1c-0dd52ffd875f"},{"checkpointId":"46f0e977-3ddf-4cd3-b12e-78b1ba0b98a3","requestId":"request_9257a937-1bc9-4027-ad1c-0dd52ffd875f","undoStopId":"8c6c1a5a-9120-479c-904e-397905eed3f8","epoch":1753,"label":"Request request_9257a937-1bc9-4027-ad1c-0dd52ffd875f - Stop 8c6c1a5a-9120-479c-904e-397905eed3f8"},{"checkpointId":"bcec10fa-b276-4404-8d0e-6b847ed1768d","requestId":"request_cdd53ada-76bc-4a42-a740-f974d081fa95","epoch":1756,"label":"Request request_cdd53ada-76bc-4a42-a740-f974d081fa95"},{"checkpointId":"a9c99559-1130-4d0b-998a-950b3515255d","requestId":"request_cdd53ada-76bc-4a42-a740-f974d081fa95","undoStopId":"2bbff8a6-d251-4c17-8d39-c65fc76413f5","epoch":1757,"label":"Request request_cdd53ada-76bc-4a42-a740-f974d081fa95 - Stop 2bbff8a6-d251-4c17-8d39-c65fc76413f5"},{"checkpointId":"a681988c-367a-4d97-85c6-6a0bd0f0b3e8","requestId":"request_cdd53ada-76bc-4a42-a740-f974d081fa95","undoStopId":"9c346c7b-7ccf-44ef-866b-ec3530615e1d","epoch":1760,"label":"Request request_cdd53ada-76bc-4a42-a740-f974d081fa95 - Stop 9c346c7b-7ccf-44ef-866b-ec3530615e1d"},{"checkpointId":"2bfdff43-825c-470b-8267-883536aef019","requestId":"request_cdd53ada-76bc-4a42-a740-f974d081fa95","undoStopId":"ad58611b-dd33-47e0-8864-4a7bdbb1cb55","epoch":1762,"label":"Request request_cdd53ada-76bc-4a42-a740-f974d081fa95 - Stop ad58611b-dd33-47e0-8864-4a7bdbb1cb55"},{"checkpointId":"02ba6ac1-0151-461a-b235-e5b1307086a5","requestId":"request_cdd53ada-76bc-4a42-a740-f974d081fa95","undoStopId":"0f1c7c75-50f5-44f6-9030-9e9f9b26d16f","epoch":1764,"label":"Request request_cdd53ada-76bc-4a42-a740-f974d081fa95 - Stop 0f1c7c75-50f5-44f6-9030-9e9f9b26d16f"},{"checkpointId":"ab88c1e3-95f2-46b5-9c1b-8439594af3e0","requestId":"request_cdd53ada-76bc-4a42-a740-f974d081fa95","undoStopId":"8afe3c5e-819d-4750-a815-554b6102c12d","epoch":1766,"label":"Request request_cdd53ada-76bc-4a42-a740-f974d081fa95 - Stop 8afe3c5e-819d-4750-a815-554b6102c12d"},{"checkpointId":"026537da-1f33-4794-921e-e4cdfea89400","requestId":"request_ae3b7ebe-5e42-40b0-acca-788a8aa0cfec","epoch":1768,"label":"Request request_ae3b7ebe-5e42-40b0-acca-788a8aa0cfec"},{"checkpointId":"58bbd427-1cb8-4374-a50a-07c8e0577ef3","requestId":"request_ae3b7ebe-5e42-40b0-acca-788a8aa0cfec","undoStopId":"aab31ca9-b0c5-47f4-b817-e78187682402","epoch":1769,"label":"Request request_ae3b7ebe-5e42-40b0-acca-788a8aa0cfec - Stop aab31ca9-b0c5-47f4-b817-e78187682402"},{"checkpointId":"2dacf019-e2bd-4a1a-93bd-c1202e4592a8","requestId":"request_ae3b7ebe-5e42-40b0-acca-788a8aa0cfec","undoStopId":"ce2a3076-ec88-49ec-bd62-8589a26a9d09","epoch":1772,"label":"Request request_ae3b7ebe-5e42-40b0-acca-788a8aa0cfec - Stop ce2a3076-ec88-49ec-bd62-8589a26a9d09"},{"checkpointId":"08072ed3-6068-49e1-aeff-69225d3784db","requestId":"request_ae3b7ebe-5e42-40b0-acca-788a8aa0cfec","undoStopId":"8f9a7ee6-fcc8-480b-83dd-7e4c27f2bc17","epoch":1775,"label":"Request request_ae3b7ebe-5e42-40b0-acca-788a8aa0cfec - Stop 8f9a7ee6-fcc8-480b-83dd-7e4c27f2bc17"},{"checkpointId":"ff006bdf-2898-4181-8a9b-3136c62eefe8","requestId":"request_ae3b7ebe-5e42-40b0-acca-788a8aa0cfec","undoStopId":"214349f0-3c00-4347-8d5d-3437cc43d9eb","epoch":1777,"label":"Request request_ae3b7ebe-5e42-40b0-acca-788a8aa0cfec - Stop 214349f0-3c00-4347-8d5d-3437cc43d9eb"},{"checkpointId":"08150e98-0333-4bbd-a3c1-6702cff1944d","requestId":"request_61a7ab1c-d8bf-428d-b25c-57a9185ebc48","epoch":1780,"label":"Request request_61a7ab1c-d8bf-428d-b25c-57a9185ebc48"},{"checkpointId":"eadc3015-bf4a-498f-ac7a-da8afec12441","requestId":"request_61a7ab1c-d8bf-428d-b25c-57a9185ebc48","undoStopId":"18cf5466-3d90-4fc3-81f4-df16e7517d37","epoch":1781,"label":"Request request_61a7ab1c-d8bf-428d-b25c-57a9185ebc48 - Stop 18cf5466-3d90-4fc3-81f4-df16e7517d37"},{"checkpointId":"7abb9d5e-c8f4-45d7-a9d5-8d59e06e4750","requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1784,"label":"Request request_645697bc-271a-4407-9dc4-d557e69d94d4"},{"checkpointId":"8079ad82-afc6-405f-8bb1-0a6eb1decf33","requestId":"request_303b5792-8ec1-4d5c-ab9a-948563589794","epoch":1987,"label":"Request request_303b5792-8ec1-4d5c-ab9a-948563589794"},{"checkpointId":"5b269f70-9f02-40d8-94cd-d8384e72c9a6","requestId":"request_303b5792-8ec1-4d5c-ab9a-948563589794","undoStopId":"02ea8001-cf1b-4e29-9151-51464538f21f","epoch":1988,"label":"Request request_303b5792-8ec1-4d5c-ab9a-948563589794 - Stop 02ea8001-cf1b-4e29-9151-51464538f21f"},{"checkpointId":"3acfaab3-8be9-4757-852f-cdc407ff3fb3","requestId":"request_b6a9fea6-2b15-4a2c-b36a-d1f17a3c9cb3","epoch":1991,"label":"Request request_b6a9fea6-2b15-4a2c-b36a-d1f17a3c9cb3"}],"currentEpoch":1992,"fileBaselines":[["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs::request_32e09b23-ccf2-430e-8354-01b3c60ea6ea",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","scheme":"file"},"requestId":"request_32e09b23-ccf2-430e-8354-01b3c60ea6ea","content":"#![cfg_attr(not(feature = \"std\"), no_std)]\r\n\r\npub const HID_REPORT_LEN: usize = 4;\r\n\r\npub mod command {\r\n    pub const SET_INTENSITY: u8 = 0x01;\r\n    pub const TRIGGER_ROM_EFFECT: u8 = 0x02;\r\n    pub const STOP: u8 = 0x03;\r\n}\r\n\r\n#[derive(Clone, Copy, Debug, Eq, PartialEq)]\r\npub struct HapticPacket {\r\n    pub command_id: u8,\r\n    pub intensity: u8,\r\n    pub arg_lo: u8,\r\n    pub arg_hi: u8,\r\n}\r\n\r\nimpl HapticPacket {\r\n    pub const fn new(command_id: u8, intensity: u8, arg_lo: u8, arg_hi: u8) -> Self {\r\n        Self {\r\n            command_id,\r\n            intensity,\r\n            arg_lo,\r\n            arg_hi,\r\n        }\r\n    }\r\n\r\n    pub const fn to_bytes(self) -> [u8; HID_REPORT_LEN] {\r\n        [self.command_id, self.intensity, self.arg_lo, self.arg_hi]\r\n    }\r\n\r\n    pub const fn from_bytes(bytes: [u8; HID_REPORT_LEN]) -> Self {\r\n        Self {\r\n            command_id: bytes[0],\r\n            intensity: bytes[1],\r\n            arg_lo: bytes[2],\r\n            arg_hi: bytes[3],\r\n        }\r\n    }\r\n}\r\n","epoch":352,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs::request_32e09b23-ccf2-430e-8354-01b3c60ea6ea",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-cli\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","scheme":"file"},"requestId":"request_32e09b23-ccf2-430e-8354-01b3c60ea6ea","content":"use anyhow::Result;\r\nuse clap::Parser;\r\nuse haptics_protocol::HapticPacket;\r\n\r\n#[derive(Debug, Parser)]\r\n#[command(name = \"hid-cli\")]\r\n#[command(about = \"Send starter haptic packets over HID\")]\r\nstruct Cli {\r\n    #[arg(long, default_value_t = 0x239A)]\r\n    vid: u16,\r\n    #[arg(long, default_value_t = 0x80F0)]\r\n    pid: u16,\r\n    #[arg(long, default_value_t = 0x01)]\r\n    cmd: u8,\r\n    #[arg(long, default_value_t = 0)]\r\n    intensity: u8,\r\n}\r\n\r\nfn main() -> Result<()> {\r\n    let args = Cli::parse();\r\n    let bridge = hid_bridge::HidBridge::new()?;\r\n    let packet = HapticPacket::new(args.cmd, args.intensity, 0, 0);\r\n    bridge.send_packet(args.vid, args.pid, packet)?;\r\n    Ok(())\r\n}\r\n","epoch":355,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs::request_da61cd7c-da91-4ac6-9f3e-da45d99473b8",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-bridge\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","content":"use haptics_protocol::HapticPacket;\r\nuse thiserror::Error;\r\n\r\n#[derive(Debug, Error)]\r\npub enum BridgeError {\r\n    #[error(\"HID error: {0}\")]\r\n    Hid(#[from] hidapi::HidError),\r\n}\r\n\r\npub struct HidBridge {\r\n    _api: hidapi::HidApi,\r\n}\r\n\r\nimpl HidBridge {\r\n    pub fn new() -> Result<Self, BridgeError> {\r\n        let api = hidapi::HidApi::new()?;\r\n        Ok(Self { _api: api })\r\n    }\r\n\r\n    pub fn send_packet(&self, _vid: u16, _pid: u16, _packet: HapticPacket) -> Result<(), BridgeError> {\r\n        // Starter stub: open by VID/PID and write raw report bytes.\r\n        Ok(())\r\n    }\r\n}\r\n","epoch":359,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs::request_da61cd7c-da91-4ac6-9f3e-da45d99473b8",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","content":"#![no_std]\r\n#![no_main]\r\n\r\nuse cortex_m_rt::entry;\r\nuse panic_halt as _;\r\n\r\n#[entry]\r\nfn main() -> ! {\r\n    loop {\r\n        cortex_m::asm::nop();\r\n    }\r\n}\r\n","epoch":423,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs::request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","content":"#![no_std]\r\n#![no_main]\r\n\r\nuse cortex_m_rt::entry;\r\nuse haptics_protocol::command;\r\nuse panic_halt as _;\r\n\r\nmod drv2605l_parser;\r\n\r\n#[entry]\r\nfn main() -> ! {\r\n    // Starter integration smoke check for the parser module.\r\n    let sample_report = [command::STOP, 0, 0, 0];\r\n    let _ = drv2605l_parser::parse_command(&sample_report);\r\n\r\n    loop {\r\n        cortex_m::asm::nop();\r\n    }\r\n}\r\n","epoch":515,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs::request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","content":"#![no_std]\r\n#![no_main]\r\n\r\nuse cortex_m_rt::entry;\r\nuse embedded_hal::i2c::I2c;\r\nuse haptics_protocol::command;\r\nuse panic_halt as _;\r\n\r\nmod drv2605l;\r\nmod drv2605l_parser;\r\n\r\nfn handle_hid_report<I2C>(\r\n    drv: &mut drv2605l::Drv2605l<I2C>,\r\n    report: &[u8],\r\n) -> Result<(), drv2605l::DispatchError<I2C::Error>>\r\nwhere\r\n    I2C: I2c,\r\n{\r\n    let cmd = drv2605l_parser::parse_command(report).map_err(drv2605l::DispatchError::Parse)?;\r\n    drv.apply_command(cmd).map_err(drv2605l::DispatchError::I2c)\r\n}\r\n\r\n#[entry]\r\nfn main() -> ! {\r\n    // Starter integration smoke check for the parser module.\r\n    // In the USB HID output-report callback, call `handle_hid_report(...)`.\r\n    let sample_report = [command::STOP, 0, 0, 0];\r\n    let _ = drv2605l_parser::parse_command(&sample_report);\r\n\r\n    loop {\r\n        cortex_m::asm::nop();\r\n    }\r\n}\r\n","epoch":519,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs::request_91e0ff5b-3f8e-4584-83d9-e232e969c2aa",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_91e0ff5b-3f8e-4584-83d9-e232e969c2aa","content":"//! SAMD21 platform bring-up scaffold.\r\n//!\r\n//! This module documents where SERCOM2 I2C and USB HID integration should be\r\n//! initialized for the QT Py SAMD21 target.\r\n//!\r\n//! Intended wiring:\r\n//! - I2C SDA: PA08 (SERCOM2/PAD0)\r\n//! - I2C SCL: PA09 (SERCOM2/PAD1)\r\n//! - DRV2605L address: 0x5A\r\n//!\r\n//! Integration flow:\r\n//! 1) Initialize clocks and pins\r\n//! 2) Bring up SERCOM2 as I2C master at 400kHz\r\n//! 3) Construct `crate::drv2605l::Drv2605l` from I2C peripheral\r\n//! 4) Initialize USB device + HID class\r\n//! 5) On each received HID OUT report, call `crate::on_hid_output_report(...)`\r\n\r\n#[allow(dead_code)]\r\npub const I2C_TARGET_HZ: u32 = 400_000;\r\n\r\n#[allow(dead_code)]\r\npub const QT_PY_SDA_PIN: &str = \"PA08\";\r\n#[allow(dead_code)]\r\npub const QT_PY_SCL_PIN: &str = \"PA09\";\r\n\r\n#[cfg(any())]\r\nmod reference_only_example {\r\n    use atsamd_hal as hal;\r\n\r\n    // This block is intentionally disabled (`cfg(any())`) until the concrete\r\n    // board setup is finalized for your exact atsamd-hal version.\r\n    //\r\n    // Pseudocode shape:\r\n    //\r\n    // let mut peripherals = hal::pac::Peripherals::take().unwrap();\r\n    // let mut core = hal::pac::CorePeripherals::take().unwrap();\r\n    // let mut clocks = GenericClockController::with_internal_32kosc(\r\n    //     peripherals.GCLK,\r\n    //     &mut peripherals.MCLK,\r\n    //     &mut peripherals.OSC32KCTRL,\r\n    //     &mut peripherals.OSCCTRL,\r\n    //     &mut peripherals.NVMCTRL,\r\n    // );\r\n    //\r\n    // let pins = hal::Pins::new(peripherals.PORT);\r\n    // let sda = pins.d4.into_mode::<hal::gpio::FunctionC>(); // PA08\r\n    // let scl = pins.d5.into_mode::<hal::gpio::FunctionC>(); // PA09\r\n    //\r\n    // let i2c = hal::sercom::I2CMaster2::new(\r\n    //     &clocks.sercom2_core(&gclk0).unwrap(),\r\n    //     I2C_TARGET_HZ.hz(),\r\n    //     peripherals.SERCOM2,\r\n    //     &mut peripherals.MCLK,\r\n    //     sda,\r\n    //     scl,\r\n    // );\r\n    //\r\n    // let mut drv = crate::drv2605l::Drv2605l::new(i2c);\r\n    // drv.set_rtp_mode().ok();\r\n    //\r\n    // // USB event loop:\r\n    // // if hid.pull_raw_output(&mut buf).is_ok() {\r\n    // //     let _ = crate::on_hid_output_report(&mut drv, &buf[..len]);\r\n    // // }\r\n}\r\n","epoch":606,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs::request_670e73e8-7055-44d2-a172-e3c97407fa56",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_670e73e8-7055-44d2-a172-e3c97407fa56","content":"//! ATSAMD21 (Adafruit QT Py) bring-up helpers.\r\n//!\r\n//! Built from docs.rs API references:\r\n//! - atsamd-hal `sercom::i2c` (v0.21)\r\n//! - atsamd-hal `usb::UsbBus` (v0.21)\r\n//! - usbd-hid `HIDClass` output report APIs (v0.8)\r\n//!\r\n//! QT Py SAMD21 board notes:\r\n//! - STEMMA/Qwiic I2C pins are PA16/PA17 on this BSP mapping.\r\n//! - USB D-/D+ are PA24/PA25.\r\n\r\nuse atsamd_hal::{\r\n    clock::GenericClockController,\r\n    gpio::{PA16, PA17, Pins},\r\n    pac,\r\n    prelude::*,\r\n    sercom::{\r\n        i2c,\r\n        Sercom1,\r\n    },\r\n    time::Hertz,\r\n    usb::UsbBus,\r\n};\r\nuse usb_device::bus::UsbBusAllocator;\r\n\r\npub const I2C_TARGET_HZ: u32 = 400_000;\r\npub const QT_PY_SDA_PIN: &str = \"PA16\";\r\npub const QT_PY_SCL_PIN: &str = \"PA17\";\r\n\r\npub type QtPyI2cPads = i2c::PadsFromIds<Sercom1, PA16, PA17>;\r\npub type QtPyI2cConfig = i2c::Config<QtPyI2cPads>;\r\npub type QtPyI2c = i2c::I2c<QtPyI2cConfig>;\r\n\r\n/// Configure SERCOM1 I2C at 400kHz for QT Py's STEMMA/Qwiic pins.\r\npub fn init_qtpy_i2c(\r\n    pm: &pac::PM,\r\n    sercom1: pac::SERCOM1,\r\n    pins: Pins,\r\n    sercom_core_freq: Hertz,\r\n) -> QtPyI2c {\r\n    let pads = i2c::Pads::<Sercom1>::new(pins.pa16, pins.pa17);\r\n    i2c::Config::new(pm, sercom1, pads, sercom_core_freq)\r\n        .baud(I2C_TARGET_HZ.Hz())\r\n        .enable()\r\n}\r\n\r\n/// Configure USB bus allocator (PA24/PA25) for HID device classes.\r\npub fn init_usb_bus(\r\n    clocks: &mut GenericClockController,\r\n    pm: &mut pac::PM,\r\n    usb: pac::USB,\r\n    pins: Pins,\r\n) -> UsbBusAllocator<UsbBus> {\r\n    let gclk0 = clocks.gclk0();\r\n    let usb_clock = clocks.usb(&gclk0).expect(\"USB clock unavailable\");\r\n    UsbBusAllocator::new(UsbBus::new(&usb_clock, pm, pins.pa24, pins.pa25, usb))\r\n}\r\n\r\n/// One-place reminder for the runtime wiring inside your USB poll loop.\r\npub fn dispatch_hid_out_report<I2C>(\r\n    drv: &mut crate::drv2605l::Drv2605l<I2C>,\r\n    report: &[u8],\r\n) -> Result<(), crate::drv2605l::DispatchError<I2C::Error>>\r\nwhere\r\n    I2C: embedded_hal::i2c::I2c,\r\n{\r\n    crate::on_hid_output_report(drv, report)\r\n}\r\n","epoch":610,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs::request_670e73e8-7055-44d2-a172-e3c97407fa56",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs","scheme":"file"},"requestId":"request_670e73e8-7055-44d2-a172-e3c97407fa56","content":"#![no_std]\r\n#![no_main]\r\n\r\nuse cortex_m_rt::entry;\r\nuse embedded_hal::i2c::I2c;\r\nuse haptics_protocol::command;\r\nuse panic_halt as _;\r\n\r\nmod drv2605l;\r\nmod drv2605l_parser;\r\nmod platform_samd21;\r\nmod usb_hid;\r\n\r\npub(crate) fn handle_hid_report<I2C>(\r\n    drv: &mut drv2605l::Drv2605l<I2C>,\r\n    report: &[u8],\r\n) -> Result<(), drv2605l::DispatchError<I2C::Error>>\r\nwhere\r\n    I2C: I2c,\r\n{\r\n    let cmd = drv2605l_parser::parse_command(report).map_err(drv2605l::DispatchError::Parse)?;\r\n    drv.apply_command(cmd).map_err(drv2605l::DispatchError::I2c)\r\n}\r\n\r\n/// Call this from the USB HID output-report receive path.\r\npub(crate) fn on_hid_output_report<I2C>(\r\n    drv: &mut drv2605l::Drv2605l<I2C>,\r\n    report: &[u8],\r\n) -> Result<(), drv2605l::DispatchError<I2C::Error>>\r\nwhere\r\n    I2C: I2c,\r\n{\r\n    usb_hid::process_output_report(drv, report)\r\n}\r\n\r\n#[entry]\r\nfn main() -> ! {\r\n    // Starter integration smoke check for the parser module.\r\n    // For full hardware bring-up, use helpers in `platform_samd21` and invoke\r\n    // `on_hid_output_report(...)` when an OUT report arrives.\r\n    let sample_report = [command::STOP, 0, 0, 0];\r\n    let _ = drv2605l_parser::parse_command(&sample_report);\r\n\r\n    loop {\r\n        cortex_m::asm::nop();\r\n    }\r\n}\r\n","epoch":613,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml::request_0d1e6e8d-7dbf-467e-af2c-f0fbd5aa689f",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","scheme":"file"},"requestId":"request_0d1e6e8d-7dbf-467e-af2c-f0fbd5aa689f","content":"[package]\r\nname = \"qtpy-samd21-fw\"\r\nversion = \"0.1.0\"\r\nedition.workspace = true\r\nlicense.workspace = true\r\nauthors.workspace = true\r\n\r\n[dependencies]\r\natsamd-hal = { version = \"0.21\", default-features = false, features = [\"samd21e18a\", \"usb\"] }\r\ncortex-m = \"0.7\"\r\ncortex-m-rt = \"0.7\"\r\nembedded-hal = \"1\"\r\nhaptics-protocol = { path = \"../../shared/haptics-protocol\", default-features = false }\r\npanic-halt = \"0.2\"\r\nusb-device = \"0.3\"\r\nusbd-hid = \"0.8\"\r\n\r\n[profile.release]\r\ncodegen-units = 1\r\nlto = true\r\nopt-level = \"s\"\r\npanic = \"abort\"\r\n","epoch":617,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml::request_7d935091-4101-48a6-9ae5-52e365038840",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","content":"[workspace]\r\nresolver = \"2\"\r\nmembers = [\r\n  \"shared/haptics-protocol\",\r\n  \"host/hid-bridge\",\r\n  \"host/hid-cli\",\r\n  \"openvr-driver/rust-haptic-driver\",\r\n  \"firmware/qtpy-samd21\",\r\n]\r\ndefault-members = [\r\n  \"shared/haptics-protocol\",\r\n  \"host/hid-bridge\",\r\n  \"host/hid-cli\",\r\n  \"openvr-driver/rust-haptic-driver\",\r\n]\r\n\r\n[workspace.package]\r\nedition = \"2021\"\r\nauthors = [\"qt-py-haptics contributors\"]\r\nlicense = \"MIT OR Apache-2.0\"\r\n\r\n[workspace.dependencies]\r\nanyhow = \"1\"\r\nclap = { version = \"4\", features = [\"derive\"] }\r\nhidapi = \"2\"\r\nlog = \"0.4\"\r\nthiserror = \"2\"\r\n","epoch":621,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml::request_7d935091-4101-48a6-9ae5-52e365038840",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","content":"[package]\r\nname = \"qtpy-samd21-fw\"\r\nversion = \"0.1.0\"\r\nedition.workspace = true\r\nlicense.workspace = true\r\nauthors.workspace = true\r\n\r\n[dependencies]\r\natsamd-hal = { version = \"0.21\", default-features = false, features = [\"samd21e\", \"usb\"] }\r\ncortex-m = \"0.7\"\r\ncortex-m-rt = \"0.7\"\r\nembedded-hal = \"1\"\r\nhaptics-protocol = { path = \"../../shared/haptics-protocol\", default-features = false }\r\npanic-halt = \"0.2\"\r\nusb-device = \"0.3\"\r\nusbd-hid = \"0.8\"\r\n\r\n[profile.release]\r\ncodegen-units = 1\r\nlto = true\r\nopt-level = \"s\"\r\npanic = \"abort\"\r\n","epoch":624,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs::request_7d935091-4101-48a6-9ae5-52e365038840",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","content":"//! ATSAMD21 (Adafruit QT Py) bring-up helpers.\r\n//!\r\n//! Built from docs.rs API references:\r\n//! - atsamd-hal `sercom::i2c` (v0.21)\r\n//! - atsamd-hal `usb::UsbBus` (v0.21)\r\n//! - usbd-hid `HIDClass` output report APIs (v0.8)\r\n//!\r\n//! QT Py SAMD21 board notes:\r\n//! - STEMMA/Qwiic I2C pins are PA16/PA17 on this BSP mapping.\r\n//! - USB D-/D+ are PA24/PA25.\r\n\r\nuse atsamd_hal::{\r\n    clock::GenericClockController,\r\n    gpio::{AnyPin, PA16, PA17, PA24, PA25},\r\n    pac,\r\n    prelude::*,\r\n    sercom::{\r\n        i2c,\r\n        Sercom1,\r\n    },\r\n    time::Hertz,\r\n    usb::UsbBus,\r\n};\r\nuse usb_device::bus::UsbBusAllocator;\r\n\r\npub const I2C_TARGET_HZ: u32 = 400_000;\r\npub const QT_PY_SDA_PIN: &str = \"PA16\";\r\npub const QT_PY_SCL_PIN: &str = \"PA17\";\r\n\r\npub type QtPyI2cPads = i2c::PadsFromIds<Sercom1, PA16, PA17>;\r\npub type QtPyI2cConfig = i2c::Config<QtPyI2cPads>;\r\npub type QtPyI2c = i2c::I2c<QtPyI2cConfig>;\r\n\r\npub const RAW_HID_REPORT_DESCRIPTOR: &[u8] = &[\r\n    0x06, 0x00, 0xff, // Usage Page (Vendor Defined)\r\n    0x09, 0x01, // Usage (0x01)\r\n    0xa1, 0x01, // Collection (Application)\r\n    0x15, 0x00, //   Logical Minimum (0)\r\n    0x26, 0xff, 0x00, //   Logical Maximum (255)\r\n    0x75, 0x08, //   Report Size (8)\r\n    0x95, 0x04, //   Report Count (4 bytes payload)\r\n    0x09, 0x01, //   Usage (0x01)\r\n    0x81, 0x02, //   Input (Data,Var,Abs)\r\n    0x95, 0x04, //   Report Count (4 bytes payload)\r\n    0x09, 0x01, //   Usage (0x01)\r\n    0x91, 0x02, //   Output (Data,Var,Abs)\r\n    0xc0, // End Collection\r\n];\r\n\r\n/// Configure SERCOM1 I2C at 400kHz for QT Py's STEMMA/Qwiic pins.\r\npub fn init_qtpy_i2c(\r\n    pm: &pac::PM,\r\n    sercom1: pac::SERCOM1,\r\n    sda: impl AnyPin<Id = PA16>,\r\n    scl: impl AnyPin<Id = PA17>,\r\n    sercom_core_freq: Hertz,\r\n) -> QtPyI2c {\r\n    let pads = i2c::Pads::<Sercom1>::new(sda, scl);\r\n    i2c::Config::new(pm, sercom1, pads, sercom_core_freq)\r\n        .baud(I2C_TARGET_HZ.Hz())\r\n        .enable()\r\n}\r\n\r\n/// Configure USB bus allocator (PA24/PA25) for HID device classes.\r\npub fn init_usb_bus(\r\n    clocks: &mut GenericClockController,\r\n    pm: &mut pac::PM,\r\n    usb: pac::USB,\r\n    dm: impl AnyPin<Id = PA24>,\r\n    dp: impl AnyPin<Id = PA25>,\r\n) -> UsbBusAllocator<UsbBus> {\r\n    let gclk0 = clocks.gclk0();\r\n    let usb_clock = clocks.usb(&gclk0).expect(\"USB clock unavailable\");\r\n    UsbBusAllocator::new(UsbBus::new(&usb_clock, pm, dm, dp, usb))\r\n}\r\n\r\n/// One-place reminder for the runtime wiring inside your USB poll loop.\r\npub fn dispatch_hid_out_report<I2C>(\r\n    drv: &mut crate::drv2605l::Drv2605l<I2C>,\r\n    report: &[u8],\r\n) -> Result<(), crate::drv2605l::DispatchError<I2C::Error>>\r\nwhere\r\n    I2C: embedded_hal::i2c::I2c,\r\n{\r\n    crate::on_hid_output_report(drv, report)\r\n}\r\n","epoch":640,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs::request_7d935091-4101-48a6-9ae5-52e365038840",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","content":"#![no_std]\r\n#![no_main]\r\n\r\nuse atsamd_hal::{\r\n    clock::GenericClockController,\r\n    gpio::Pins,\r\n    pac,\r\n};\r\nuse cortex_m_rt::entry;\r\nuse embedded_hal::i2c::I2c;\r\nuse haptics_protocol::{\r\n    command,\r\n    DEFAULT_USB_PID,\r\n    DEFAULT_USB_VID,\r\n    HID_REPORT_LEN,\r\n};\r\nuse panic_halt as _;\r\nuse usb_device::device::{\r\n    UsbDeviceBuilder,\r\n    UsbVidPid,\r\n};\r\nuse usbd_hid::hid_class::HIDClass;\r\n\r\nmod drv2605l;\r\nmod drv2605l_parser;\r\nmod platform_samd21;\r\nmod usb_hid;\r\n\r\npub(crate) fn handle_hid_report<I2C>(\r\n    drv: &mut drv2605l::Drv2605l<I2C>,\r\n    report: &[u8],\r\n) -> Result<(), drv2605l::DispatchError<I2C::Error>>\r\nwhere\r\n    I2C: I2c,\r\n{\r\n    let cmd = drv2605l_parser::parse_command(report).map_err(drv2605l::DispatchError::Parse)?;\r\n    drv.apply_command(cmd).map_err(drv2605l::DispatchError::I2c)\r\n}\r\n\r\n/// Call this from the USB HID output-report receive path.\r\npub(crate) fn on_hid_output_report<I2C>(\r\n    drv: &mut drv2605l::Drv2605l<I2C>,\r\n    report: &[u8],\r\n) -> Result<(), drv2605l::DispatchError<I2C::Error>>\r\nwhere\r\n    I2C: I2c,\r\n{\r\n    usb_hid::process_output_report(drv, report)\r\n}\r\n\r\n#[entry]\r\nfn main() -> ! {\r\n    let mut peripherals = match pac::Peripherals::take() {\r\n        Some(p) => p,\r\n        None => loop {\r\n            cortex_m::asm::nop();\r\n        },\r\n    };\r\n\r\n    let mut clocks = GenericClockController::with_internal_32kosc(\r\n        peripherals.GCLK,\r\n        &mut peripherals.PM,\r\n        &mut peripherals.SYSCTRL,\r\n        &mut peripherals.NVMCTRL,\r\n    );\r\n\r\n    let gclk0 = clocks.gclk0();\r\n    let sercom1_core = clocks\r\n        .sercom1_core(&gclk0)\r\n        .expect(\"SERCOM1 core clock unavailable\");\r\n\r\n    let pins = Pins::new(peripherals.PORT);\r\n\r\n    let i2c = platform_samd21::init_qtpy_i2c(\r\n        &peripherals.PM,\r\n        peripherals.SERCOM1,\r\n        pins.pa16,\r\n        pins.pa17,\r\n        sercom1_core.freq(),\r\n    );\r\n    let mut drv = drv2605l::Drv2605l::new(i2c);\r\n    let _ = drv.set_rtp_mode();\r\n\r\n    let usb_bus = platform_samd21::init_usb_bus(\r\n        &mut clocks,\r\n        &mut peripherals.PM,\r\n        peripherals.USB,\r\n        pins.pa24,\r\n        pins.pa25,\r\n    );\r\n\r\n    let mut hid = HIDClass::new(&usb_bus, platform_samd21::RAW_HID_REPORT_DESCRIPTOR, 1);\r\n    let mut usb_dev = UsbDeviceBuilder::new(\r\n        &usb_bus,\r\n        UsbVidPid(DEFAULT_USB_VID, DEFAULT_USB_PID),\r\n    )\r\n    .manufacturer(\"qt-py-haptics\")\r\n    .product(\"QT Py Haptics\")\r\n    .serial_number(\"qtpy-samd21\")\r\n    .device_class(0)\r\n    .build();\r\n\r\n    // Keep a parser smoke-check packet for fast sanity checks.\r\n    let sample_report = [command::STOP, 0, 0, 0];\r\n    let _ = drv2605l_parser::parse_command(&sample_report);\r\n\r\n    loop {\r\n        if usb_dev.poll(&mut [&mut hid]) {\r\n            let mut report = [0u8; HID_REPORT_LEN + 1];\r\n            if let Ok(count) = hid.pull_raw_output(&mut report) {\r\n                let _ = platform_samd21::dispatch_hid_out_report(&mut drv, &report[..count]);\r\n            }\r\n        }\r\n    }\r\n}\r\n","epoch":643,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md::request_4692cace-c687-4f02-91bd-cae5b881aab8",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\FLASH_AND_TEST.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","content":"# QT Py SAMD21 Firmware: Flash + Smoke Test\r\n\r\n## Current status\r\n\r\nThe firmware now builds for `thumbv6m-none-eabi` and includes:\r\n- USB HID OUT report polling\r\n- HID packet parser\r\n- DRV2605L command dispatch over I2C\r\n\r\n## Build\r\n\r\nUse:\r\n- `tools/scripts/build_firmware.ps1`\r\n\r\n## Flashing options\r\n\r\n### Option A (recommended for convenience): UF2 bootloader drag-and-drop\r\n\r\n1. Build release firmware.\r\n2. Convert ELF to UF2 (requires `cargo-binutils` + `llvm-tools` + `uf2conv.py` or equivalent).\r\n3. Double-tap reset on QT Py to mount `QTPYBOOT`.\r\n4. Copy UF2 file to the mounted drive.\r\n\r\n> Note: This repository does not yet include a pinned UF2 conversion tool script.\r\n\r\n### Option B: SWD + probe-rs\r\n\r\nIf you have SWD access, flash directly with probe tooling.\r\n\r\n## Host smoke test\r\n\r\nAfter board reconnects as USB HID, run:\r\n- `tools/scripts/host_hid_smoke_test.ps1`\r\n\r\nDefault smoke packet:\r\n- Command `0x01` (`SET_INTENSITY`)\r\n- Intensity `64`\r\n- VID/PID `0x35bd` / `0xfe01`\r\n\r\n## Next recommended firmware increments\r\n\r\n1. DRV2605L init sequence (mode exit standby, optional calibration)\r\n2. Better error handling/recovery for I2C bus faults\r\n3. Optional HID IN status report path (ack/error counters)\r\n4. Real waveform/time semantics for ROM effect + duration handling\r\n","epoch":815,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/.gitignore::request_4692cace-c687-4f02-91bd-cae5b881aab8",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\.gitignore","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/.gitignore","path":"/C:/Users/decid/Documents/projects/qt-py-haptics/.gitignore","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","content":"# Rust / Cargo\r\n/target/\r\n**/*.rs.bk\r\nCargo.lock\r\nstderr\r\nstdout\r\n\r\n# IDEs\r\n.idea/\r\n.vscode/\r\n*.swp\r\n*.swo\r\n\r\n# OS\r\n.DS_Store\r\nThumbs.db","epoch":818,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml::request_4692cace-c687-4f02-91bd-cae5b881aab8",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","content":"[package]\r\nname = \"qtpy-samd21-fw\"\r\nversion = \"0.1.0\"\r\nedition.workspace = true\r\nlicense.workspace = true\r\nauthors.workspace = true\r\n\r\n[dependencies]\r\natsamd-hal = { version = \"0.21\", default-features = false, features = [\"samd21e\", \"usb\"] }\r\ncortex-m = \"0.7\"\r\ncortex-m-rt = \"0.7\"\r\nembedded-hal = \"1\"\r\nhaptics-protocol = { path = \"../../shared/haptics-protocol\", default-features = false }\r\npanic-halt = \"0.2\"\r\nusb-device = \"0.3\"\r\nusbd-hid = \"0.8\"\r\n","epoch":831,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml::request_5ffebef5-f6de-4fca-a46a-31d52f96616e",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","scheme":"file"},"requestId":"request_5ffebef5-f6de-4fca-a46a-31d52f96616e","content":"[package]\r\nname = \"qtpy-samd21-fw\"\r\nversion = \"0.1.0\"\r\nedition.workspace = true\r\nlicense.workspace = true\r\nauthors.workspace = true\r\n\r\n[dependencies]\r\natsamd-hal = { version = \"0.21\", default-features = false, features = [\"samd21e\", \"samd21e-rt\", \"usb\"] }\r\ncortex-m = \"0.7\"\r\ncortex-m-rt = \"0.7\"\r\nembedded-hal = \"1\"\r\nhaptics-protocol = { path = \"../../shared/haptics-protocol\", default-features = false }\r\npanic-halt = \"0.2\"\r\nusb-device = \"0.3\"\r\nusbd-hid = \"0.8\"\r\n","epoch":847,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/host_hid_smoke_test.ps1::request_128f3af0-ca12-4644-bf4a-f57b14bf85bb",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\host_hid_smoke_test.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/host_hid_smoke_test.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/host_hid_smoke_test.ps1","scheme":"file"},"requestId":"request_128f3af0-ca12-4644-bf4a-f57b14bf85bb","content":"param(\r\n    [UInt16]$Vid = 0x35bd,\r\n    [UInt16]$Pid = 0xfe01,\r\n    [byte]$Command = 0x01,\r\n    [byte]$Intensity = 64\r\n)\r\n\r\n$ErrorActionPreference = \"Stop\"\r\n$root = \"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\"\r\nSet-Location $root\r\n\r\ncargo run -p hid-cli -- --vid $Vid --pid $Pid --cmd $Command --intensity $Intensity\r\n","epoch":851,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/host_hid_smoke_test.ps1::request_92d7c45c-4795-4c66-b59e-3d209f7a5944",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\host_hid_smoke_test.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/host_hid_smoke_test.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/host_hid_smoke_test.ps1","scheme":"file"},"requestId":"request_92d7c45c-4795-4c66-b59e-3d209f7a5944","content":"param(\r\n    [UInt16]$Vid = 0x35bd,\r\n    [Alias('Pid')]\r\n    [UInt16]$ProductId = 0xfe01,\r\n    [byte]$Command = 0x01,\r\n    [byte]$Intensity = 64\r\n)\r\n\r\n$ErrorActionPreference = \"Stop\"\r\n$root = \"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\"\r\nSet-Location $root\r\n\r\ncargo run -p hid-cli -- --vid $Vid --pid $ProductId --cmd $Command --intensity $Intensity\r\n","epoch":855,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs::request_c21a4968-1030-4d03-9ff1-b0cf37dc305a",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","content":"use core::ffi::c_void;\r\n\r\n#[no_mangle]\r\npub extern \"C\" fn HmdDriverFactory(\r\n    _p_interface_name: *const i8,\r\n    _p_return_code: *mut i32,\r\n) -> *mut c_void {\r\n    core::ptr::null_mut()\r\n}\r\n","epoch":863,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/driver.vrdrivermanifest::request_8b8bee65-e67e-436f-8cf9-9e7a5e2a4cbf",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\driver.vrdrivermanifest","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/driver.vrdrivermanifest","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/driver.vrdrivermanifest","scheme":"file"},"requestId":"request_8b8bee65-e67e-436f-8cf9-9e7a5e2a4cbf","content":"{\r\n  \"always_activate\": true,\r\n  \"name\": \"rust_haptic_driver\",\r\n  \"resource_only\": false,\r\n  \"resource_directory\": \"resources\"\r\n}\r\n","epoch":963,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json::request_8b8bee65-e67e-436f-8cf9-9e7a5e2a4cbf",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\resources\\input\\rust_haptic_profile.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","scheme":"file"},"requestId":"request_8b8bee65-e67e-436f-8cf9-9e7a5e2a4cbf","content":"{\r\n  \"jsonid\": \"input_profile\",\r\n  \"controller_type\": \"rust_haptic_driver\",\r\n  \"input_bindingui_mode\": \"controller_handed\",\r\n  \"input_source\": {\r\n    \"haptic\": {\r\n      \"type\": \"vibration\",\r\n      \"binding_image_point\": [0.5, 0.5]\r\n    }\r\n  }\r\n}\r\n","epoch":966,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/localization/en_us.json::request_8b8bee65-e67e-436f-8cf9-9e7a5e2a4cbf",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\resources\\localization\\en_us.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/localization/en_us.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/localization/en_us.json","scheme":"file"},"requestId":"request_8b8bee65-e67e-436f-8cf9-9e7a5e2a4cbf","content":"{\r\n  \"language_tag\": \"en_US\",\r\n  \"rust_haptic_driver\": \"Rust Haptic Driver\",\r\n  \"rust_haptic_driver_input_profile\": \"Rust Haptic Driver Input\",\r\n  \"/devices/rust_haptic_driver/prop/modelnumber_string\": \"QT Py SAMD21 Haptic\",\r\n  \"/devices/rust_haptic_driver/prop/serialnumber_string\": \"qtpy-samd21-haptic\"\r\n}\r\n","epoch":969,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/Cargo.toml::request_c2dbf0bc-9220-41ac-8de0-ebcec63de2a8",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/Cargo.toml","scheme":"file"},"requestId":"request_c2dbf0bc-9220-41ac-8de0-ebcec63de2a8","content":"[package]\r\nname = \"rust-haptic-driver\"\r\nversion = \"0.1.0\"\r\nedition.workspace = true\r\nlicense.workspace = true\r\nauthors.workspace = true\r\n\r\n[lib]\r\ncrate-type = [\"cdylib\"]\r\n\r\n[dependencies]\r\nhid-bridge = { path = \"../../host/hid-bridge\" }\r\nhaptics-protocol = { path = \"../../shared/haptics-protocol\" }\r\nthiserror.workspace = true\r\n","epoch":973,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs::request_c2dbf0bc-9220-41ac-8de0-ebcec63de2a8",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","scheme":"file"},"requestId":"request_c2dbf0bc-9220-41ac-8de0-ebcec63de2a8","content":"use core::ffi::c_void;\r\nuse core::ffi::CStr;\r\nuse std::os::raw::c_char;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\nconst SERVER_PROVIDER_INTERFACE: &str = \"IServerTrackedDeviceProvider_005\";\r\n\r\n#[repr(i32)]\r\nenum DriverReturnCode {\r\n    None = 0,\r\n    Unknown = 1,\r\n    InitInterfaceNotFound = 105,\r\n}\r\n\r\n#[repr(C)]\r\nstruct StubProvider;\r\n\r\nstatic STUB_PROVIDER: StubProvider = StubProvider;\r\n\r\nfn set_return_code(out: *mut i32, code: DriverReturnCode) {\r\n    if !out.is_null() {\r\n        // SAFETY: caller owns output pointer according to OpenVR factory ABI.\r\n        unsafe {\r\n            *out = code as i32;\r\n        }\r\n    }\r\n}\r\n\r\nfn read_interface_name(ptr: *const i8) -> Option<&'static str> {\r\n    if ptr.is_null() {\r\n        return None;\r\n    }\r\n\r\n    // SAFETY: OpenVR passes a NUL-terminated C string.\r\n    let cstr = unsafe { CStr::from_ptr(ptr) };\r\n    let Ok(name) = cstr.to_str() else {\r\n        return None;\r\n    };\r\n\r\n    // Keep only known literals in static storage path.\r\n    if name == SERVER_PROVIDER_INTERFACE {\r\n        Some(SERVER_PROVIDER_INTERFACE)\r\n    } else {\r\n        None\r\n    }\r\n}\r\n\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn HmdDriverFactory(\r\n    p_interface_name: *const c_char,\r\n    p_return_code: *mut i32,\r\n) -> *mut c_void {\r\n    let Some(interface_name) = read_interface_name(p_interface_name) else {\r\n        set_return_code(p_return_code, DriverReturnCode::InitInterfaceNotFound);\r\n        return core::ptr::null_mut();\r\n    };\r\n\r\n    if interface_name == SERVER_PROVIDER_INTERFACE {\r\n        set_return_code(p_return_code, DriverReturnCode::None);\r\n        return (&STUB_PROVIDER as *const StubProvider).cast_mut().cast();\r\n    }\r\n\r\n    set_return_code(p_return_code, DriverReturnCode::Unknown);\r\n    core::ptr::null_mut()\r\n}\r\n\r\n/// Test-only export for early integration while full OpenVR vtable glue is in progress.\r\n/// Returns 0 on success, non-zero on failure.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn RustHapticDriver_SendAmplitude(amplitude: f32) -> i32 {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    let packet = HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0);\r\n\r\n    match hid_bridge::HidBridge::new().and_then(|bridge| {\r\n        bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet)\r\n    }) {\r\n        Ok(()) => 0,\r\n        Err(_) => 1,\r\n    }\r\n}\r\n\r\n/// Explicit stop command helper for testing and recovery.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn RustHapticDriver_Stop() -> i32 {\r\n    let packet = HapticPacket::new(command::STOP, 0, 0, 0);\r\n\r\n    match hid_bridge::HidBridge::new().and_then(|bridge| {\r\n        bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet)\r\n    }) {\r\n        Ok(()) => 0,\r\n        Err(_) => 1,\r\n    }\r\n}\r\n","epoch":976,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md::request_c2dbf0bc-9220-41ac-8de0-ebcec63de2a8",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","scheme":"file"},"requestId":"request_c2dbf0bc-9220-41ac-8de0-ebcec63de2a8","content":"# rust-haptic-driver\r\n\r\nInitial OpenVR driver scaffold in Rust.\r\n\r\n## Current capabilities\r\n\r\n- Exports `HmdDriverFactory` with minimal interface-name validation\r\n- Exposes DLL-callable haptics test exports:\r\n  - `RustHapticDriver_SendAmplitude(float)`\r\n  - `RustHapticDriver_Stop()`\r\n- Sends packets to firmware through shared `hid-bridge`\r\n\r\n## Build\r\n\r\nFrom workspace root:\r\n\r\n- `cargo build -p rust-haptic-driver --release`\r\n\r\nOutput DLL path:\r\n\r\n- `target/release/rust_haptic_driver.dll`\r\n\r\n## SteamVR layout\r\n\r\nDriver folder should include:\r\n\r\n- `driver.vrdrivermanifest`\r\n- `resources/`\r\n- `bin/win64/driver_rust_haptic_driver.dll`\r\n\r\n## Register with SteamVR\r\n\r\nTypical command:\r\n\r\n- `vrpathreg.exe adddriver <path-to-openvr-driver/rust-haptic-driver>`\r\n\r\n## Next implementation step\r\n\r\nImplement concrete C++ ABI-compatible vtables for:\r\n\r\n- `IServerTrackedDeviceProvider`\r\n- `ITrackedDeviceServerDriver`\r\n\r\nand route `TriggerHapticVibration` events to `RustHapticDriver_SendAmplitude` logic.\r\n","epoch":979,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs::request_05ad5ed7-be85-45fb-af40-007422c0606f",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","scheme":"file"},"requestId":"request_05ad5ed7-be85-45fb-af40-007422c0606f","content":"use core::ffi::c_void;\r\nuse core::ffi::CStr;\r\nuse std::os::raw::c_char;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\nconst SERVER_PROVIDER_INTERFACE: &str = \"IServerTrackedDeviceProvider_005\";\r\n\r\n#[repr(i32)]\r\nenum DriverReturnCode {\r\n    None = 0,\r\n    Unknown = 1,\r\n    InitInterfaceNotFound = 105,\r\n}\r\n\r\n#[repr(C)]\r\nstruct StubProvider;\r\n\r\nstatic STUB_PROVIDER: StubProvider = StubProvider;\r\n\r\nfn set_return_code(out: *mut i32, code: DriverReturnCode) {\r\n    if !out.is_null() {\r\n        // SAFETY: caller owns output pointer according to OpenVR factory ABI.\r\n        unsafe {\r\n            *out = code as i32;\r\n        }\r\n    }\r\n}\r\n\r\nfn read_interface_name(ptr: *const i8) -> Option<&'static str> {\r\n    if ptr.is_null() {\r\n        return None;\r\n    }\r\n\r\n    // SAFETY: OpenVR passes a NUL-terminated C string.\r\n    let cstr = unsafe { CStr::from_ptr(ptr) };\r\n    let Ok(name) = cstr.to_str() else {\r\n        return None;\r\n    };\r\n\r\n    // Keep only known literals in static storage path.\r\n    if name == SERVER_PROVIDER_INTERFACE {\r\n        Some(SERVER_PROVIDER_INTERFACE)\r\n    } else {\r\n        None\r\n    }\r\n}\r\n\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn HmdDriverFactory(\r\n    p_interface_name: *const c_char,\r\n    p_return_code: *mut i32,\r\n) -> *mut c_void {\r\n    let Some(interface_name) = read_interface_name(p_interface_name) else {\r\n        set_return_code(p_return_code, DriverReturnCode::InitInterfaceNotFound);\r\n        return core::ptr::null_mut();\r\n    };\r\n\r\n    if interface_name == SERVER_PROVIDER_INTERFACE {\r\n        set_return_code(p_return_code, DriverReturnCode::None);\r\n        return (&STUB_PROVIDER as *const StubProvider).cast_mut().cast();\r\n    }\r\n\r\n    set_return_code(p_return_code, DriverReturnCode::Unknown);\r\n    core::ptr::null_mut()\r\n}\r\n\r\n/// Test-only export for early integration while full OpenVR vtable glue is in progress.\r\n/// Returns 0 on success, non-zero on failure.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_SendAmplitude(amplitude: f32) -> i32 {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    let packet = HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0);\r\n\r\n    match hid_bridge::HidBridge::new().and_then(|bridge| {\r\n        bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet)\r\n    }) {\r\n        Ok(()) => 0,\r\n        Err(_) => 1,\r\n    }\r\n}\r\n\r\n/// Explicit stop command helper for testing and recovery.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_Stop() -> i32 {\r\n    let packet = HapticPacket::new(command::STOP, 0, 0, 0);\r\n\r\n    match hid_bridge::HidBridge::new().and_then(|bridge| {\r\n        bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet)\r\n    }) {\r\n        Ok(()) => 0,\r\n        Err(_) => 1,\r\n    }\r\n}\r\n","epoch":985,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs::request_814a4084-5479-45cb-b1bb-93faa91ce807",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","scheme":"file"},"requestId":"request_814a4084-5479-45cb-b1bb-93faa91ce807","content":"use core::ffi::c_void;\r\nuse core::ffi::CStr;\r\nuse core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU32, Ordering};\r\nuse std::os::raw::c_char;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\nconst SERVER_PROVIDER_INTERFACE: &str = \"IServerTrackedDeviceProvider_005\";\r\nconst TRACKED_DEVICE_INTERFACE: &str = \"ITrackedDeviceServerDriver_005\";\r\n\r\n#[repr(i32)]\r\nenum DriverReturnCode {\r\n    None = 0,\r\n    Unknown = 1,\r\n    InitInterfaceNotFound = 105,\r\n}\r\n\r\ntype EvRInitError = i32;\r\nconst VR_INIT_ERROR_NONE: EvRInitError = 0;\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nstruct DriverPose {\r\n    // Placeholder for OpenVR DriverPose_t. Will be replaced with exact layout.\r\n    _reserved: [u8; 128],\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerTrackedDeviceProviderVTable {\r\n    init: extern \"C\" fn(*mut ServerTrackedDeviceProvider, *mut c_void) -> EvRInitError,\r\n    cleanup: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    get_interface_versions:\r\n        extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> *const *const c_char,\r\n    run_frame: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    should_block_standby_mode: extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> bool,\r\n    enter_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    leave_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n}\r\n\r\n#[repr(C)]\r\nstruct TrackedDeviceServerDriverVTable {\r\n    activate: extern \"C\" fn(*mut TrackedDeviceServerDriver, u32) -> EvRInitError,\r\n    deactivate: extern \"C\" fn(*mut TrackedDeviceServerDriver),\r\n    enter_standby: extern \"C\" fn(*mut TrackedDeviceServerDriver),\r\n    get_component:\r\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char) -> *mut c_void,\r\n    debug_request:\r\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char, *mut c_char, u32),\r\n    get_pose: extern \"C\" fn(*mut TrackedDeviceServerDriver) -> DriverPose,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerTrackedDeviceProvider {\r\n    vtable: *const ServerTrackedDeviceProviderVTable,\r\n}\r\nunsafe impl Sync for ServerTrackedDeviceProvider {}\r\n\r\n#[repr(C)]\r\nstruct TrackedDeviceServerDriver {\r\n    vtable: *const TrackedDeviceServerDriverVTable,\r\n}\r\nunsafe impl Sync for TrackedDeviceServerDriver {}\r\n\r\n#[repr(C)]\r\nstruct InterfaceVersions([*const c_char; 3]);\r\nunsafe impl Sync for InterfaceVersions {}\r\n\r\nstatic HOST_CONTEXT: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DEVICE_INDEX: AtomicU32 = AtomicU32::new(u32::MAX);\r\nstatic DEVICE_ACTIVE: AtomicBool = AtomicBool::new(false);\r\n\r\nconst PROVIDER_IFACE_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_005\\0\";\r\nconst DEVICE_IFACE_CSTR: &[u8] = b\"ITrackedDeviceServerDriver_005\\0\";\r\n\r\nstatic PROVIDER_INTERFACE_VERSIONS: InterfaceVersions = InterfaceVersions([\r\n    PROVIDER_IFACE_CSTR.as_ptr().cast(),\r\n    DEVICE_IFACE_CSTR.as_ptr().cast(),\r\n    core::ptr::null(),\r\n]);\r\n\r\nextern \"C\" fn provider_init(\r\n    _this: *mut ServerTrackedDeviceProvider,\r\n    driver_context: *mut c_void,\r\n) -> EvRInitError {\r\n    HOST_CONTEXT.store(driver_context, Ordering::SeqCst);\r\n    VR_INIT_ERROR_NONE\r\n}\r\n\r\nextern \"C\" fn provider_cleanup(_this: *mut ServerTrackedDeviceProvider) {\r\n    HOST_CONTEXT.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\r\n    let _ = send_stop();\r\n}\r\n\r\nextern \"C\" fn provider_get_interface_versions(\r\n    _this: *mut ServerTrackedDeviceProvider,\r\n) -> *const *const c_char {\r\n    PROVIDER_INTERFACE_VERSIONS.0.as_ptr()\r\n}\r\n\r\nextern \"C\" fn provider_run_frame(_this: *mut ServerTrackedDeviceProvider) {\r\n    // TODO: Pump driver events from vrserver and handle haptic vibration events.\r\n}\r\n\r\nextern \"C\" fn provider_should_block_standby_mode(_this: *mut ServerTrackedDeviceProvider) -> bool {\r\n    false\r\n}\r\n\r\nextern \"C\" fn provider_enter_standby(_this: *mut ServerTrackedDeviceProvider) {}\r\n\r\nextern \"C\" fn provider_leave_standby(_this: *mut ServerTrackedDeviceProvider) {}\r\n\r\nextern \"C\" fn device_activate(_this: *mut TrackedDeviceServerDriver, object_id: u32) -> EvRInitError {\r\n    DEVICE_INDEX.store(object_id, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(true, Ordering::SeqCst);\r\n    VR_INIT_ERROR_NONE\r\n}\r\n\r\nextern \"C\" fn device_deactivate(_this: *mut TrackedDeviceServerDriver) {\r\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\r\n    let _ = send_stop();\r\n}\r\n\r\nextern \"C\" fn device_enter_standby(_this: *mut TrackedDeviceServerDriver) {\r\n    let _ = send_stop();\r\n}\r\n\r\nextern \"C\" fn device_get_component(\r\n    _this: *mut TrackedDeviceServerDriver,\r\n    _component_name: *const c_char,\r\n) -> *mut c_void {\r\n    core::ptr::null_mut()\r\n}\r\n\r\nextern \"C\" fn device_debug_request(\r\n    _this: *mut TrackedDeviceServerDriver,\r\n    _request: *const c_char,\r\n    response: *mut c_char,\r\n    response_size: u32,\r\n) {\r\n    if response.is_null() || response_size == 0 {\r\n        return;\r\n    }\r\n\r\n    // Always return an empty C-string for now.\r\n    // SAFETY: `response` points to caller-provided writable buffer.\r\n    unsafe {\r\n        *response = 0;\r\n    }\r\n}\r\n\r\nextern \"C\" fn device_get_pose(_this: *mut TrackedDeviceServerDriver) -> DriverPose {\r\n    DriverPose {\r\n        _reserved: [0; 128],\r\n    }\r\n}\r\n\r\nstatic PROVIDER_VTABLE: ServerTrackedDeviceProviderVTable = ServerTrackedDeviceProviderVTable {\r\n    init: provider_init,\r\n    cleanup: provider_cleanup,\r\n    get_interface_versions: provider_get_interface_versions,\r\n    run_frame: provider_run_frame,\r\n    should_block_standby_mode: provider_should_block_standby_mode,\r\n    enter_standby: provider_enter_standby,\r\n    leave_standby: provider_leave_standby,\r\n};\r\n\r\nstatic DEVICE_VTABLE: TrackedDeviceServerDriverVTable = TrackedDeviceServerDriverVTable {\r\n    activate: device_activate,\r\n    deactivate: device_deactivate,\r\n    enter_standby: device_enter_standby,\r\n    get_component: device_get_component,\r\n    debug_request: device_debug_request,\r\n    get_pose: device_get_pose,\r\n};\r\n\r\nstatic PROVIDER: ServerTrackedDeviceProvider = ServerTrackedDeviceProvider {\r\n    vtable: &PROVIDER_VTABLE,\r\n};\r\n\r\n#[allow(dead_code)]\r\nstatic DEVICE: TrackedDeviceServerDriver = TrackedDeviceServerDriver {\r\n    vtable: &DEVICE_VTABLE,\r\n};\r\n\r\nfn send_packet(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    hid_bridge::HidBridge::new()\r\n        .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n}\r\n\r\nfn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\nfn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    send_packet(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn set_return_code(out: *mut i32, code: DriverReturnCode) {\r\n    if !out.is_null() {\r\n        // SAFETY: caller owns output pointer according to OpenVR factory ABI.\r\n        unsafe {\r\n            *out = code as i32;\r\n        }\r\n    }\r\n}\r\n\r\nfn read_interface_name(ptr: *const i8) -> Option<&'static str> {\r\n    if ptr.is_null() {\r\n        return None;\r\n    }\r\n\r\n    // SAFETY: OpenVR passes a NUL-terminated C string.\r\n    let cstr = unsafe { CStr::from_ptr(ptr) };\r\n    let Ok(name) = cstr.to_str() else {\r\n        return None;\r\n    };\r\n\r\n    // Keep only known literals in static storage path.\r\n    if name == SERVER_PROVIDER_INTERFACE {\r\n        return Some(SERVER_PROVIDER_INTERFACE);\r\n    }\r\n    if name == TRACKED_DEVICE_INTERFACE {\r\n        return Some(TRACKED_DEVICE_INTERFACE);\r\n    }\r\n\r\n    None\r\n}\r\n\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn HmdDriverFactory(\r\n    p_interface_name: *const c_char,\r\n    p_return_code: *mut i32,\r\n) -> *mut c_void {\r\n    let Some(interface_name) = read_interface_name(p_interface_name) else {\r\n        set_return_code(p_return_code, DriverReturnCode::InitInterfaceNotFound);\r\n        return core::ptr::null_mut();\r\n    };\r\n\r\n    if interface_name == SERVER_PROVIDER_INTERFACE {\r\n        set_return_code(p_return_code, DriverReturnCode::None);\r\n        return (&PROVIDER as *const ServerTrackedDeviceProvider)\r\n            .cast_mut()\r\n            .cast();\r\n    }\r\n\r\n    if interface_name == TRACKED_DEVICE_INTERFACE {\r\n        set_return_code(p_return_code, DriverReturnCode::None);\r\n        return (&DEVICE as *const TrackedDeviceServerDriver)\r\n            .cast_mut()\r\n            .cast();\r\n    }\r\n\r\n    set_return_code(p_return_code, DriverReturnCode::Unknown);\r\n    core::ptr::null_mut()\r\n}\r\n\r\n/// Test-only export for early integration while full OpenVR vtable glue is in progress.\r\n/// Returns 0 on success, non-zero on failure.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_SendAmplitude(amplitude: f32) -> i32 {\r\n    match send_amplitude(amplitude) {\r\n        Ok(()) => 0,\r\n        Err(_) => 1,\r\n    }\r\n}\r\n\r\n/// Explicit stop command helper for testing and recovery.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_Stop() -> i32 {\r\n    match send_stop() {\r\n        Ok(()) => 0,\r\n        Err(_) => 1,\r\n    }\r\n}\r\n","epoch":997,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs::request_3cb8f730-b84c-454e-9cfe-afd5a70a9203",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","content":"use core::ffi::c_void;\r\nuse core::ffi::CStr;\r\nuse core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU32, AtomicU64, Ordering};\r\nuse std::os::raw::c_char;\r\nuse std::sync::{Mutex, OnceLock};\r\nuse std::time::Instant;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\nconst SERVER_PROVIDER_INTERFACE: &str = \"IServerTrackedDeviceProvider_005\";\r\nconst TRACKED_DEVICE_INTERFACE: &str = \"ITrackedDeviceServerDriver_005\";\r\n\r\n#[repr(i32)]\r\nenum DriverReturnCode {\r\n    None = 0,\r\n    Unknown = 1,\r\n    InitInterfaceNotFound = 105,\r\n}\r\n\r\ntype EvRInitError = i32;\r\nconst VR_INIT_ERROR_NONE: EvRInitError = 0;\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\npub struct HapticVibrationRequest {\r\n    pub amplitude: f32,\r\n    pub duration_seconds: f32,\r\n    pub frequency: f32,\r\n}\r\n\r\ntype PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nstruct DriverPose {\r\n    // Placeholder for OpenVR DriverPose_t. Will be replaced with exact layout.\r\n    _reserved: [u8; 128],\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerTrackedDeviceProviderVTable {\r\n    init: extern \"C\" fn(*mut ServerTrackedDeviceProvider, *mut c_void) -> EvRInitError,\r\n    cleanup: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    get_interface_versions:\r\n        extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> *const *const c_char,\r\n    run_frame: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    should_block_standby_mode: extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> bool,\r\n    enter_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    leave_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n}\r\n\r\n#[repr(C)]\r\nstruct TrackedDeviceServerDriverVTable {\r\n    activate: extern \"C\" fn(*mut TrackedDeviceServerDriver, u32) -> EvRInitError,\r\n    deactivate: extern \"C\" fn(*mut TrackedDeviceServerDriver),\r\n    enter_standby: extern \"C\" fn(*mut TrackedDeviceServerDriver),\r\n    get_component:\r\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char) -> *mut c_void,\r\n    debug_request:\r\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char, *mut c_char, u32),\r\n    get_pose: extern \"C\" fn(*mut TrackedDeviceServerDriver) -> DriverPose,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerTrackedDeviceProvider {\r\n    vtable: *const ServerTrackedDeviceProviderVTable,\r\n}\r\nunsafe impl Sync for ServerTrackedDeviceProvider {}\r\n\r\n#[repr(C)]\r\nstruct TrackedDeviceServerDriver {\r\n    vtable: *const TrackedDeviceServerDriverVTable,\r\n}\r\nunsafe impl Sync for TrackedDeviceServerDriver {}\r\n\r\n#[repr(C)]\r\nstruct InterfaceVersions([*const c_char; 3]);\r\nunsafe impl Sync for InterfaceVersions {}\r\n\r\nstatic HOST_CONTEXT: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DEVICE_INDEX: AtomicU32 = AtomicU32::new(u32::MAX);\r\nstatic DEVICE_ACTIVE: AtomicBool = AtomicBool::new(false);\r\nstatic STOP_DEADLINE_MS: AtomicU64 = AtomicU64::new(0);\r\nstatic START_INSTANT: OnceLock<Instant> = OnceLock::new();\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\n\r\nconst PROVIDER_IFACE_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_005\\0\";\r\nconst DEVICE_IFACE_CSTR: &[u8] = b\"ITrackedDeviceServerDriver_005\\0\";\r\n\r\nstatic PROVIDER_INTERFACE_VERSIONS: InterfaceVersions = InterfaceVersions([\r\n    PROVIDER_IFACE_CSTR.as_ptr().cast(),\r\n    DEVICE_IFACE_CSTR.as_ptr().cast(),\r\n    core::ptr::null(),\r\n]);\r\n\r\nextern \"C\" fn provider_init(\r\n    _this: *mut ServerTrackedDeviceProvider,\r\n    driver_context: *mut c_void,\r\n) -> EvRInitError {\r\n    HOST_CONTEXT.store(driver_context, Ordering::SeqCst);\r\n    VR_INIT_ERROR_NONE\r\n}\r\n\r\nextern \"C\" fn provider_cleanup(_this: *mut ServerTrackedDeviceProvider) {\r\n    HOST_CONTEXT.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\r\n    let _ = send_stop();\r\n}\r\n\r\nextern \"C\" fn provider_get_interface_versions(\r\n    _this: *mut ServerTrackedDeviceProvider,\r\n) -> *const *const c_char {\r\n    PROVIDER_INTERFACE_VERSIONS.0.as_ptr()\r\n}\r\n\r\nextern \"C\" fn provider_run_frame(_this: *mut ServerTrackedDeviceProvider) {\r\n    process_haptic_callback();\r\n    process_pending_stop();\r\n}\r\n\r\nextern \"C\" fn provider_should_block_standby_mode(_this: *mut ServerTrackedDeviceProvider) -> bool {\r\n    false\r\n}\r\n\r\nextern \"C\" fn provider_enter_standby(_this: *mut ServerTrackedDeviceProvider) {}\r\n\r\nextern \"C\" fn provider_leave_standby(_this: *mut ServerTrackedDeviceProvider) {}\r\n\r\nextern \"C\" fn device_activate(_this: *mut TrackedDeviceServerDriver, object_id: u32) -> EvRInitError {\r\n    DEVICE_INDEX.store(object_id, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(true, Ordering::SeqCst);\r\n    VR_INIT_ERROR_NONE\r\n}\r\n\r\nextern \"C\" fn device_deactivate(_this: *mut TrackedDeviceServerDriver) {\r\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\r\n    let _ = send_stop();\r\n}\r\n\r\nextern \"C\" fn device_enter_standby(_this: *mut TrackedDeviceServerDriver) {\r\n    let _ = send_stop();\r\n}\r\n\r\nextern \"C\" fn device_get_component(\r\n    _this: *mut TrackedDeviceServerDriver,\r\n    _component_name: *const c_char,\r\n) -> *mut c_void {\r\n    core::ptr::null_mut()\r\n}\r\n\r\nextern \"C\" fn device_debug_request(\r\n    _this: *mut TrackedDeviceServerDriver,\r\n    _request: *const c_char,\r\n    response: *mut c_char,\r\n    response_size: u32,\r\n) {\r\n    if response.is_null() || response_size == 0 {\r\n        return;\r\n    }\r\n\r\n    // Always return an empty C-string for now.\r\n    // SAFETY: `response` points to caller-provided writable buffer.\r\n    unsafe {\r\n        *response = 0;\r\n    }\r\n}\r\n\r\nextern \"C\" fn device_get_pose(_this: *mut TrackedDeviceServerDriver) -> DriverPose {\r\n    DriverPose {\r\n        _reserved: [0; 128],\r\n    }\r\n}\r\n\r\nstatic PROVIDER_VTABLE: ServerTrackedDeviceProviderVTable = ServerTrackedDeviceProviderVTable {\r\n    init: provider_init,\r\n    cleanup: provider_cleanup,\r\n    get_interface_versions: provider_get_interface_versions,\r\n    run_frame: provider_run_frame,\r\n    should_block_standby_mode: provider_should_block_standby_mode,\r\n    enter_standby: provider_enter_standby,\r\n    leave_standby: provider_leave_standby,\r\n};\r\n\r\nstatic DEVICE_VTABLE: TrackedDeviceServerDriverVTable = TrackedDeviceServerDriverVTable {\r\n    activate: device_activate,\r\n    deactivate: device_deactivate,\r\n    enter_standby: device_enter_standby,\r\n    get_component: device_get_component,\r\n    debug_request: device_debug_request,\r\n    get_pose: device_get_pose,\r\n};\r\n\r\nstatic PROVIDER: ServerTrackedDeviceProvider = ServerTrackedDeviceProvider {\r\n    vtable: &PROVIDER_VTABLE,\r\n};\r\n\r\n#[allow(dead_code)]\r\nstatic DEVICE: TrackedDeviceServerDriver = TrackedDeviceServerDriver {\r\n    vtable: &DEVICE_VTABLE,\r\n};\r\n\r\nfn send_packet(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    hid_bridge::HidBridge::new()\r\n        .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n}\r\n\r\nfn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\nfn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    send_packet(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn now_ms() -> u64 {\r\n    let start = START_INSTANT.get_or_init(Instant::now);\r\n    let ms = Instant::now().duration_since(*start).as_millis();\r\n    ms.min(u128::from(u64::MAX)) as u64\r\n}\r\n\r\nfn schedule_stop_after(duration_seconds: f32) {\r\n    if duration_seconds <= 0.0 {\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let duration_ms = (duration_seconds * 1000.0).round().max(1.0) as u64;\r\n    let deadline = now_ms().saturating_add(duration_ms);\r\n    STOP_DEADLINE_MS.store(deadline, Ordering::SeqCst);\r\n}\r\n\r\nfn process_pending_stop() {\r\n    let deadline = STOP_DEADLINE_MS.load(Ordering::SeqCst);\r\n    if deadline == 0 {\r\n        return;\r\n    }\r\n\r\n    if now_ms() >= deadline {\r\n        let _ = send_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\nfn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        let _ = send_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let _ = send_amplitude(req.amplitude);\r\n    schedule_stop_after(req.duration_seconds);\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    // Process a bounded number of queued events per frame.\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\nfn set_return_code(out: *mut i32, code: DriverReturnCode) {\r\n    if !out.is_null() {\r\n        // SAFETY: caller owns output pointer according to OpenVR factory ABI.\r\n        unsafe {\r\n            *out = code as i32;\r\n        }\r\n    }\r\n}\r\n\r\nfn read_interface_name(ptr: *const i8) -> Option<&'static str> {\r\n    if ptr.is_null() {\r\n        return None;\r\n    }\r\n\r\n    // SAFETY: OpenVR passes a NUL-terminated C string.\r\n    let cstr = unsafe { CStr::from_ptr(ptr) };\r\n    let Ok(name) = cstr.to_str() else {\r\n        return None;\r\n    };\r\n\r\n    // Keep only known literals in static storage path.\r\n    if name == SERVER_PROVIDER_INTERFACE {\r\n        return Some(SERVER_PROVIDER_INTERFACE);\r\n    }\r\n    if name == TRACKED_DEVICE_INTERFACE {\r\n        return Some(TRACKED_DEVICE_INTERFACE);\r\n    }\r\n\r\n    None\r\n}\r\n\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn HmdDriverFactory(\r\n    p_interface_name: *const c_char,\r\n    p_return_code: *mut i32,\r\n) -> *mut c_void {\r\n    let Some(interface_name) = read_interface_name(p_interface_name) else {\r\n        set_return_code(p_return_code, DriverReturnCode::InitInterfaceNotFound);\r\n        return core::ptr::null_mut();\r\n    };\r\n\r\n    if interface_name == SERVER_PROVIDER_INTERFACE {\r\n        set_return_code(p_return_code, DriverReturnCode::None);\r\n        return (&PROVIDER as *const ServerTrackedDeviceProvider)\r\n            .cast_mut()\r\n            .cast();\r\n    }\r\n\r\n    if interface_name == TRACKED_DEVICE_INTERFACE {\r\n        set_return_code(p_return_code, DriverReturnCode::None);\r\n        return (&DEVICE as *const TrackedDeviceServerDriver)\r\n            .cast_mut()\r\n            .cast();\r\n    }\r\n\r\n    set_return_code(p_return_code, DriverReturnCode::Unknown);\r\n    core::ptr::null_mut()\r\n}\r\n\r\n/// Test-only export for early integration while full OpenVR vtable glue is in progress.\r\n/// Returns 0 on success, non-zero on failure.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_SendAmplitude(amplitude: f32) -> i32 {\r\n    match send_amplitude(amplitude) {\r\n        Ok(()) => 0,\r\n        Err(_) => 1,\r\n    }\r\n}\r\n\r\n/// Explicit stop command helper for testing and recovery.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_Stop() -> i32 {\r\n    match send_stop() {\r\n        Ok(()) => 0,\r\n        Err(_) => 1,\r\n    }\r\n}\r\n\r\n/// Temporary bridge: host/OpenVR shim can register a callback polled each frame.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_SetPollHapticCallback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n\r\n/// Temporary direct injection helper while event wiring is being built.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_InjectHaptic(\r\n    amplitude: f32,\r\n    duration_seconds: f32,\r\n    frequency: f32,\r\n) {\r\n    handle_haptic_request(HapticVibrationRequest {\r\n        amplitude,\r\n        duration_seconds,\r\n        frequency,\r\n    });\r\n}\r\n","epoch":1342,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs::request_d489ba23-ce50-4bec-9632-8fe576c78ee8",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_d489ba23-ce50-4bec-9632-8fe576c78ee8","content":"use core::sync::atomic::{AtomicU64, Ordering};\r\nuse std::sync::{Mutex, OnceLock};\r\nuse std::time::Instant;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\npub struct HapticVibrationRequest {\r\n    pub amplitude: f32,\r\n    pub duration_seconds: f32,\r\n    pub frequency: f32,\r\n}\r\n\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic STOP_DEADLINE_MS: AtomicU64 = AtomicU64::new(0);\r\nstatic START_INSTANT: OnceLock<Instant> = OnceLock::new();\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\n\r\nfn send_packet(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    hid_bridge::HidBridge::new()\r\n        .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n}\r\n\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    send_packet(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn now_ms() -> u64 {\r\n    let start = START_INSTANT.get_or_init(Instant::now);\r\n    let ms = Instant::now().duration_since(*start).as_millis();\r\n    ms.min(u128::from(u64::MAX)) as u64\r\n}\r\n\r\nfn schedule_stop_after(duration_seconds: f32) {\r\n    if duration_seconds <= 0.0 {\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let duration_ms = (duration_seconds * 1000.0).round().max(1.0) as u64;\r\n    let deadline = now_ms().saturating_add(duration_ms);\r\n    STOP_DEADLINE_MS.store(deadline, Ordering::SeqCst);\r\n}\r\n\r\nfn process_pending_stop() {\r\n    let deadline = STOP_DEADLINE_MS.load(Ordering::SeqCst);\r\n    if deadline == 0 {\r\n        return;\r\n    }\r\n\r\n    if now_ms() >= deadline {\r\n        let _ = send_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        let _ = send_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let _ = send_amplitude(req.amplitude);\r\n    schedule_stop_after(req.duration_seconds);\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n    process_pending_stop();\r\n}\r\n\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","epoch":1348,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs::request_d489ba23-ce50-4bec-9632-8fe576c78ee8",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_d489ba23-ce50-4bec-9632-8fe576c78ee8","content":"use core::ffi::{c_void, CStr};\r\nuse core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU32, Ordering};\r\nuse std::os::raw::c_char;\r\n\r\nuse crate::haptics;\r\n\r\nconst SERVER_PROVIDER_INTERFACE: &str = \"IServerTrackedDeviceProvider_005\";\r\nconst TRACKED_DEVICE_INTERFACE: &str = \"ITrackedDeviceServerDriver_005\";\r\n\r\n#[repr(i32)]\r\nenum DriverReturnCode {\r\n    None = 0,\r\n    Unknown = 1,\r\n    InitInterfaceNotFound = 105,\r\n}\r\n\r\ntype EvRInitError = i32;\r\nconst VR_INIT_ERROR_NONE: EvRInitError = 0;\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nstruct DriverPose {\r\n    _reserved: [u8; 128],\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerTrackedDeviceProviderVTable {\r\n    init: extern \"C\" fn(*mut ServerTrackedDeviceProvider, *mut c_void) -> EvRInitError,\r\n    cleanup: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    get_interface_versions:\r\n        extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> *const *const c_char,\r\n    run_frame: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    should_block_standby_mode: extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> bool,\r\n    enter_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    leave_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n}\r\n\r\n#[repr(C)]\r\nstruct TrackedDeviceServerDriverVTable {\r\n    activate: extern \"C\" fn(*mut TrackedDeviceServerDriver, u32) -> EvRInitError,\r\n    deactivate: extern \"C\" fn(*mut TrackedDeviceServerDriver),\r\n    enter_standby: extern \"C\" fn(*mut TrackedDeviceServerDriver),\r\n    get_component:\r\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char) -> *mut c_void,\r\n    debug_request:\r\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char, *mut c_char, u32),\r\n    get_pose: extern \"C\" fn(*mut TrackedDeviceServerDriver) -> DriverPose,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerTrackedDeviceProvider {\r\n    vtable: *const ServerTrackedDeviceProviderVTable,\r\n}\r\nunsafe impl Sync for ServerTrackedDeviceProvider {}\r\n\r\n#[repr(C)]\r\nstruct TrackedDeviceServerDriver {\r\n    vtable: *const TrackedDeviceServerDriverVTable,\r\n}\r\nunsafe impl Sync for TrackedDeviceServerDriver {}\r\n\r\n#[repr(C)]\r\nstruct InterfaceVersions([*const c_char; 3]);\r\nunsafe impl Sync for InterfaceVersions {}\r\n\r\nstatic HOST_CONTEXT: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DEVICE_INDEX: AtomicU32 = AtomicU32::new(u32::MAX);\r\nstatic DEVICE_ACTIVE: AtomicBool = AtomicBool::new(false);\r\n\r\nconst PROVIDER_IFACE_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_005\\0\";\r\nconst DEVICE_IFACE_CSTR: &[u8] = b\"ITrackedDeviceServerDriver_005\\0\";\r\n\r\nstatic PROVIDER_INTERFACE_VERSIONS: InterfaceVersions = InterfaceVersions([\r\n    PROVIDER_IFACE_CSTR.as_ptr().cast(),\r\n    DEVICE_IFACE_CSTR.as_ptr().cast(),\r\n    core::ptr::null(),\r\n]);\r\n\r\nextern \"C\" fn provider_init(\r\n    _this: *mut ServerTrackedDeviceProvider,\r\n    driver_context: *mut c_void,\r\n) -> EvRInitError {\r\n    HOST_CONTEXT.store(driver_context, Ordering::SeqCst);\r\n    VR_INIT_ERROR_NONE\r\n}\r\n\r\nextern \"C\" fn provider_cleanup(_this: *mut ServerTrackedDeviceProvider) {\r\n    HOST_CONTEXT.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\r\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn provider_get_interface_versions(\r\n    _this: *mut ServerTrackedDeviceProvider,\r\n) -> *const *const c_char {\r\n    PROVIDER_INTERFACE_VERSIONS.0.as_ptr()\r\n}\r\n\r\nextern \"C\" fn provider_run_frame(_this: *mut ServerTrackedDeviceProvider) {\r\n    haptics::on_frame();\r\n}\r\n\r\nextern \"C\" fn provider_should_block_standby_mode(_this: *mut ServerTrackedDeviceProvider) -> bool {\r\n    false\r\n}\r\n\r\nextern \"C\" fn provider_enter_standby(_this: *mut ServerTrackedDeviceProvider) {}\r\n\r\nextern \"C\" fn provider_leave_standby(_this: *mut ServerTrackedDeviceProvider) {}\r\n\r\nextern \"C\" fn device_activate(_this: *mut TrackedDeviceServerDriver, object_id: u32) -> EvRInitError {\r\n    DEVICE_INDEX.store(object_id, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(true, Ordering::SeqCst);\r\n    VR_INIT_ERROR_NONE\r\n}\r\n\r\nextern \"C\" fn device_deactivate(_this: *mut TrackedDeviceServerDriver) {\r\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\r\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn device_enter_standby(_this: *mut TrackedDeviceServerDriver) {\r\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn device_get_component(\r\n    _this: *mut TrackedDeviceServerDriver,\r\n    _component_name: *const c_char,\r\n) -> *mut c_void {\r\n    core::ptr::null_mut()\r\n}\r\n\r\nextern \"C\" fn device_debug_request(\r\n    _this: *mut TrackedDeviceServerDriver,\r\n    _request: *const c_char,\r\n    response: *mut c_char,\r\n    response_size: u32,\r\n) {\r\n    if response.is_null() || response_size == 0 {\r\n        return;\r\n    }\r\n    unsafe {\r\n        *response = 0;\r\n    }\r\n}\r\n\r\nextern \"C\" fn device_get_pose(_this: *mut TrackedDeviceServerDriver) -> DriverPose {\r\n    DriverPose { _reserved: [0; 128] }\r\n}\r\n\r\nstatic PROVIDER_VTABLE: ServerTrackedDeviceProviderVTable = ServerTrackedDeviceProviderVTable {\r\n    init: provider_init,\r\n    cleanup: provider_cleanup,\r\n    get_interface_versions: provider_get_interface_versions,\r\n    run_frame: provider_run_frame,\r\n    should_block_standby_mode: provider_should_block_standby_mode,\r\n    enter_standby: provider_enter_standby,\r\n    leave_standby: provider_leave_standby,\r\n};\r\n\r\nstatic DEVICE_VTABLE: TrackedDeviceServerDriverVTable = TrackedDeviceServerDriverVTable {\r\n    activate: device_activate,\r\n    deactivate: device_deactivate,\r\n    enter_standby: device_enter_standby,\r\n    get_component: device_get_component,\r\n    debug_request: device_debug_request,\r\n    get_pose: device_get_pose,\r\n};\r\n\r\nstatic PROVIDER: ServerTrackedDeviceProvider = ServerTrackedDeviceProvider {\r\n    vtable: &PROVIDER_VTABLE,\r\n};\r\n\r\nstatic DEVICE: TrackedDeviceServerDriver = TrackedDeviceServerDriver {\r\n    vtable: &DEVICE_VTABLE,\r\n};\r\n\r\nfn set_return_code(out: *mut i32, code: DriverReturnCode) {\r\n    if !out.is_null() {\r\n        unsafe {\r\n            *out = code as i32;\r\n        }\r\n    }\r\n}\r\n\r\nfn read_interface_name(ptr: *const i8) -> Option<&'static str> {\r\n    if ptr.is_null() {\r\n        return None;\r\n    }\r\n\r\n    let cstr = unsafe { CStr::from_ptr(ptr) };\r\n    let Ok(name) = cstr.to_str() else {\r\n        return None;\r\n    };\r\n\r\n    if name == SERVER_PROVIDER_INTERFACE {\r\n        return Some(SERVER_PROVIDER_INTERFACE);\r\n    }\r\n    if name == TRACKED_DEVICE_INTERFACE {\r\n        return Some(TRACKED_DEVICE_INTERFACE);\r\n    }\r\n\r\n    None\r\n}\r\n\r\npub fn hmd_driver_factory(\r\n    p_interface_name: *const c_char,\r\n    p_return_code: *mut i32,\r\n) -> *mut c_void {\r\n    let Some(interface_name) = read_interface_name(p_interface_name) else {\r\n        set_return_code(p_return_code, DriverReturnCode::InitInterfaceNotFound);\r\n        return core::ptr::null_mut();\r\n    };\r\n\r\n    if interface_name == SERVER_PROVIDER_INTERFACE {\r\n        set_return_code(p_return_code, DriverReturnCode::None);\r\n        return (&PROVIDER as *const ServerTrackedDeviceProvider)\r\n            .cast_mut()\r\n            .cast();\r\n    }\r\n\r\n    if interface_name == TRACKED_DEVICE_INTERFACE {\r\n        set_return_code(p_return_code, DriverReturnCode::None);\r\n        return (&DEVICE as *const TrackedDeviceServerDriver)\r\n            .cast_mut()\r\n            .cast();\r\n    }\r\n\r\n    set_return_code(p_return_code, DriverReturnCode::Unknown);\r\n    core::ptr::null_mut()\r\n}\r\n","epoch":1351,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs::request_d489ba23-ce50-4bec-9632-8fe576c78ee8",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","scheme":"file"},"requestId":"request_d489ba23-ce50-4bec-9632-8fe576c78ee8","content":"use core::ffi::c_void;\r\nuse std::os::raw::c_char;\r\n\r\nmod haptics;\r\nmod openvr_scaffold;\r\n\r\npub use haptics::{HapticVibrationRequest, PollHapticCallback};\r\n\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn HmdDriverFactory(\r\n    p_interface_name: *const c_char,\r\n    p_return_code: *mut i32,\r\n) -> *mut c_void {\r\n    openvr_scaffold::hmd_driver_factory(p_interface_name, p_return_code)\r\n}\r\n\r\n/// Test-only export for early integration while full OpenVR vtable glue is in progress.\r\n/// Returns 0 on success, non-zero on failure.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_SendAmplitude(amplitude: f32) -> i32 {\r\n    match haptics::send_amplitude(amplitude) {\r\n        Ok(()) => 0,\r\n        Err(_) => 1,\r\n    }\r\n}\r\n\r\n/// Explicit stop command helper for testing and recovery.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_Stop() -> i32 {\r\n    match haptics::send_stop() {\r\n        Ok(()) => 0,\r\n        Err(_) => 1,\r\n    }\r\n}\r\n\r\n/// Temporary bridge: host/OpenVR shim can register a callback polled each frame.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_SetPollHapticCallback(cb: Option<PollHapticCallback>) {\r\n    haptics::set_poll_haptic_callback(cb);\r\n}\r\n\r\n/// Temporary direct injection helper while event wiring is being built.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_InjectHaptic(\r\n    amplitude: f32,\r\n    duration_seconds: f32,\r\n    frequency: f32,\r\n) {\r\n    haptics::handle_haptic_request(HapticVibrationRequest {\r\n        amplitude,\r\n        duration_seconds,\r\n        frequency,\r\n    });\r\n}\r\n","epoch":1354,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs::request_a50ae47b-0ab6-4f4c-9668-6b3916eef14c",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","scheme":"file"},"requestId":"request_a50ae47b-0ab6-4f4c-9668-6b3916eef14c","content":"//! Big Haptic Driver crate entrypoints.\r\n//!\r\n//! `lib.rs` stays intentionally thin: it exposes C ABI exports and delegates\r\n//! implementation details to focused modules.\r\n\r\nuse core::ffi::c_void;\r\nuse std::os::raw::c_char;\r\n\r\nmod haptics;\r\nmod openvr_scaffold;\r\n\r\npub use haptics::{HapticVibrationRequest, PollHapticCallback};\r\n\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn HmdDriverFactory(\r\n    p_interface_name: *const c_char,\r\n    p_return_code: *mut i32,\r\n) -> *mut c_void {\r\n    openvr_scaffold::hmd_driver_factory(p_interface_name, p_return_code)\r\n}\r\n\r\n/// Test-only export for early integration while full OpenVR vtable glue is in progress.\r\n/// Returns 0 on success, non-zero on failure.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_SendAmplitude(amplitude: f32) -> i32 {\r\n    match haptics::send_amplitude(amplitude) {\r\n        Ok(()) => 0,\r\n        Err(_) => 1,\r\n    }\r\n}\r\n\r\n/// Explicit stop command helper for testing and recovery.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_Stop() -> i32 {\r\n    match haptics::send_stop() {\r\n        Ok(()) => 0,\r\n        Err(_) => 1,\r\n    }\r\n}\r\n\r\n/// Temporary bridge: host/OpenVR shim can register a callback polled each frame.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_SetPollHapticCallback(cb: Option<PollHapticCallback>) {\r\n    haptics::set_poll_haptic_callback(cb);\r\n}\r\n\r\n/// Temporary direct injection helper while event wiring is being built.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_InjectHaptic(\r\n    amplitude: f32,\r\n    duration_seconds: f32,\r\n    frequency: f32,\r\n) {\r\n    haptics::handle_haptic_request(HapticVibrationRequest {\r\n        amplitude,\r\n        duration_seconds,\r\n        frequency,\r\n    });\r\n}\r\n","epoch":1358,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs::request_a50ae47b-0ab6-4f4c-9668-6b3916eef14c",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_a50ae47b-0ab6-4f4c-9668-6b3916eef14c","content":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse core::sync::atomic::{AtomicU64, Ordering};\r\nuse std::sync::{Mutex, OnceLock};\r\nuse std::time::Instant;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\npub struct HapticVibrationRequest {\r\n    pub amplitude: f32,\r\n    pub duration_seconds: f32,\r\n    pub frequency: f32,\r\n}\r\n\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic STOP_DEADLINE_MS: AtomicU64 = AtomicU64::new(0);\r\nstatic START_INSTANT: OnceLock<Instant> = OnceLock::new();\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\n\r\nfn send_packet(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    hid_bridge::HidBridge::new()\r\n        .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n}\r\n\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    send_packet(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn now_ms() -> u64 {\r\n    let start = START_INSTANT.get_or_init(Instant::now);\r\n    let ms = Instant::now().duration_since(*start).as_millis();\r\n    ms.min(u128::from(u64::MAX)) as u64\r\n}\r\n\r\nfn schedule_stop_after(duration_seconds: f32) {\r\n    if duration_seconds <= 0.0 {\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let duration_ms = (duration_seconds * 1000.0).round().max(1.0) as u64;\r\n    let deadline = now_ms().saturating_add(duration_ms);\r\n    STOP_DEADLINE_MS.store(deadline, Ordering::SeqCst);\r\n}\r\n\r\nfn process_pending_stop() {\r\n    let deadline = STOP_DEADLINE_MS.load(Ordering::SeqCst);\r\n    if deadline == 0 {\r\n        return;\r\n    }\r\n\r\n    if now_ms() >= deadline {\r\n        let _ = send_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        let _ = send_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let _ = send_amplitude(req.amplitude);\r\n    schedule_stop_after(req.duration_seconds);\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n    process_pending_stop();\r\n}\r\n\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","epoch":1361,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs::request_a50ae47b-0ab6-4f4c-9668-6b3916eef14c",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_a50ae47b-0ab6-4f4c-9668-6b3916eef14c","content":"//! OpenVR ABI/vtable scaffolding for the Big Haptic Driver.\r\n//!\r\n//! This module owns C ABI exports and interface routing for SteamVR-facing\r\n//! provider/device entry points, while delegating haptics behavior to\r\n//! the `haptics` module.\r\n\r\nuse core::ffi::{c_void, CStr};\r\nuse core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU32, Ordering};\r\nuse std::os::raw::c_char;\r\n\r\nuse crate::haptics;\r\n\r\nconst SERVER_PROVIDER_INTERFACE: &str = \"IServerTrackedDeviceProvider_005\";\r\nconst TRACKED_DEVICE_INTERFACE: &str = \"ITrackedDeviceServerDriver_005\";\r\n\r\n#[repr(i32)]\r\nenum DriverReturnCode {\r\n    None = 0,\r\n    Unknown = 1,\r\n    InitInterfaceNotFound = 105,\r\n}\r\n\r\ntype EvRInitError = i32;\r\nconst VR_INIT_ERROR_NONE: EvRInitError = 0;\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nstruct DriverPose {\r\n    _reserved: [u8; 128],\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerTrackedDeviceProviderVTable {\r\n    init: extern \"C\" fn(*mut ServerTrackedDeviceProvider, *mut c_void) -> EvRInitError,\r\n    cleanup: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    get_interface_versions:\r\n        extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> *const *const c_char,\r\n    run_frame: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    should_block_standby_mode: extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> bool,\r\n    enter_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    leave_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n}\r\n\r\n#[repr(C)]\r\nstruct TrackedDeviceServerDriverVTable {\r\n    activate: extern \"C\" fn(*mut TrackedDeviceServerDriver, u32) -> EvRInitError,\r\n    deactivate: extern \"C\" fn(*mut TrackedDeviceServerDriver),\r\n    enter_standby: extern \"C\" fn(*mut TrackedDeviceServerDriver),\r\n    get_component:\r\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char) -> *mut c_void,\r\n    debug_request:\r\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char, *mut c_char, u32),\r\n    get_pose: extern \"C\" fn(*mut TrackedDeviceServerDriver) -> DriverPose,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerTrackedDeviceProvider {\r\n    vtable: *const ServerTrackedDeviceProviderVTable,\r\n}\r\nunsafe impl Sync for ServerTrackedDeviceProvider {}\r\n\r\n#[repr(C)]\r\nstruct TrackedDeviceServerDriver {\r\n    vtable: *const TrackedDeviceServerDriverVTable,\r\n}\r\nunsafe impl Sync for TrackedDeviceServerDriver {}\r\n\r\n#[repr(C)]\r\nstruct InterfaceVersions([*const c_char; 3]);\r\nunsafe impl Sync for InterfaceVersions {}\r\n\r\nstatic HOST_CONTEXT: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DEVICE_INDEX: AtomicU32 = AtomicU32::new(u32::MAX);\r\nstatic DEVICE_ACTIVE: AtomicBool = AtomicBool::new(false);\r\n\r\nconst PROVIDER_IFACE_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_005\\0\";\r\nconst DEVICE_IFACE_CSTR: &[u8] = b\"ITrackedDeviceServerDriver_005\\0\";\r\n\r\nstatic PROVIDER_INTERFACE_VERSIONS: InterfaceVersions = InterfaceVersions([\r\n    PROVIDER_IFACE_CSTR.as_ptr().cast(),\r\n    DEVICE_IFACE_CSTR.as_ptr().cast(),\r\n    core::ptr::null(),\r\n]);\r\n\r\nextern \"C\" fn provider_init(\r\n    _this: *mut ServerTrackedDeviceProvider,\r\n    driver_context: *mut c_void,\r\n) -> EvRInitError {\r\n    HOST_CONTEXT.store(driver_context, Ordering::SeqCst);\r\n    VR_INIT_ERROR_NONE\r\n}\r\n\r\nextern \"C\" fn provider_cleanup(_this: *mut ServerTrackedDeviceProvider) {\r\n    HOST_CONTEXT.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\r\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn provider_get_interface_versions(\r\n    _this: *mut ServerTrackedDeviceProvider,\r\n) -> *const *const c_char {\r\n    PROVIDER_INTERFACE_VERSIONS.0.as_ptr()\r\n}\r\n\r\nextern \"C\" fn provider_run_frame(_this: *mut ServerTrackedDeviceProvider) {\r\n    haptics::on_frame();\r\n}\r\n\r\nextern \"C\" fn provider_should_block_standby_mode(_this: *mut ServerTrackedDeviceProvider) -> bool {\r\n    false\r\n}\r\n\r\nextern \"C\" fn provider_enter_standby(_this: *mut ServerTrackedDeviceProvider) {}\r\n\r\nextern \"C\" fn provider_leave_standby(_this: *mut ServerTrackedDeviceProvider) {}\r\n\r\nextern \"C\" fn device_activate(_this: *mut TrackedDeviceServerDriver, object_id: u32) -> EvRInitError {\r\n    DEVICE_INDEX.store(object_id, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(true, Ordering::SeqCst);\r\n    VR_INIT_ERROR_NONE\r\n}\r\n\r\nextern \"C\" fn device_deactivate(_this: *mut TrackedDeviceServerDriver) {\r\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\r\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn device_enter_standby(_this: *mut TrackedDeviceServerDriver) {\r\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn device_get_component(\r\n    _this: *mut TrackedDeviceServerDriver,\r\n    _component_name: *const c_char,\r\n) -> *mut c_void {\r\n    core::ptr::null_mut()\r\n}\r\n\r\nextern \"C\" fn device_debug_request(\r\n    _this: *mut TrackedDeviceServerDriver,\r\n    _request: *const c_char,\r\n    response: *mut c_char,\r\n    response_size: u32,\r\n) {\r\n    if response.is_null() || response_size == 0 {\r\n        return;\r\n    }\r\n    unsafe {\r\n        *response = 0;\r\n    }\r\n}\r\n\r\nextern \"C\" fn device_get_pose(_this: *mut TrackedDeviceServerDriver) -> DriverPose {\r\n    DriverPose { _reserved: [0; 128] }\r\n}\r\n\r\nstatic PROVIDER_VTABLE: ServerTrackedDeviceProviderVTable = ServerTrackedDeviceProviderVTable {\r\n    init: provider_init,\r\n    cleanup: provider_cleanup,\r\n    get_interface_versions: provider_get_interface_versions,\r\n    run_frame: provider_run_frame,\r\n    should_block_standby_mode: provider_should_block_standby_mode,\r\n    enter_standby: provider_enter_standby,\r\n    leave_standby: provider_leave_standby,\r\n};\r\n\r\nstatic DEVICE_VTABLE: TrackedDeviceServerDriverVTable = TrackedDeviceServerDriverVTable {\r\n    activate: device_activate,\r\n    deactivate: device_deactivate,\r\n    enter_standby: device_enter_standby,\r\n    get_component: device_get_component,\r\n    debug_request: device_debug_request,\r\n    get_pose: device_get_pose,\r\n};\r\n\r\nstatic PROVIDER: ServerTrackedDeviceProvider = ServerTrackedDeviceProvider {\r\n    vtable: &PROVIDER_VTABLE,\r\n};\r\n\r\nstatic DEVICE: TrackedDeviceServerDriver = TrackedDeviceServerDriver {\r\n    vtable: &DEVICE_VTABLE,\r\n};\r\n\r\nfn set_return_code(out: *mut i32, code: DriverReturnCode) {\r\n    if !out.is_null() {\r\n        unsafe {\r\n            *out = code as i32;\r\n        }\r\n    }\r\n}\r\n\r\nfn read_interface_name(ptr: *const i8) -> Option<&'static str> {\r\n    if ptr.is_null() {\r\n        return None;\r\n    }\r\n\r\n    let cstr = unsafe { CStr::from_ptr(ptr) };\r\n    let Ok(name) = cstr.to_str() else {\r\n        return None;\r\n    };\r\n\r\n    if name == SERVER_PROVIDER_INTERFACE {\r\n        return Some(SERVER_PROVIDER_INTERFACE);\r\n    }\r\n    if name == TRACKED_DEVICE_INTERFACE {\r\n        return Some(TRACKED_DEVICE_INTERFACE);\r\n    }\r\n\r\n    None\r\n}\r\n\r\npub fn hmd_driver_factory(\r\n    p_interface_name: *const c_char,\r\n    p_return_code: *mut i32,\r\n) -> *mut c_void {\r\n    let Some(interface_name) = read_interface_name(p_interface_name) else {\r\n        set_return_code(p_return_code, DriverReturnCode::InitInterfaceNotFound);\r\n        return core::ptr::null_mut();\r\n    };\r\n\r\n    if interface_name == SERVER_PROVIDER_INTERFACE {\r\n        set_return_code(p_return_code, DriverReturnCode::None);\r\n        return (&PROVIDER as *const ServerTrackedDeviceProvider)\r\n            .cast_mut()\r\n            .cast();\r\n    }\r\n\r\n    if interface_name == TRACKED_DEVICE_INTERFACE {\r\n        set_return_code(p_return_code, DriverReturnCode::None);\r\n        return (&DEVICE as *const TrackedDeviceServerDriver)\r\n            .cast_mut()\r\n            .cast();\r\n    }\r\n\r\n    set_return_code(p_return_code, DriverReturnCode::Unknown);\r\n    core::ptr::null_mut()\r\n}\r\n","epoch":1366,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md::request_35127224-e6a7-4c31-8952-d8bd8bf5d5fe",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","scheme":"file"},"requestId":"request_35127224-e6a7-4c31-8952-d8bd8bf5d5fe","content":"# big-haptic-driver\r\n\r\nInitial OpenVR driver scaffold in Rust.\r\n\r\n## Current capabilities\r\n\r\n- Exports `HmdDriverFactory` with minimal interface-name validation\r\n- Exposes DLL-callable haptics test exports:\r\n  - `BigHapticDriver_SendAmplitude(float)`\r\n  - `BigHapticDriver_Stop()`\r\n- Sends packets to firmware through shared `hid-bridge`\r\n\r\n## Build\r\n\r\nFrom workspace root:\r\n\r\n- `cargo build -p big-haptic-driver --release`\r\n\r\nOutput DLL path:\r\n\r\n- `target/release/big_haptic_driver.dll`\r\n\r\n## SteamVR layout\r\n\r\nDriver folder should include:\r\n\r\n- `driver.vrdrivermanifest`\r\n- `resources/`\r\n- `bin/win64/driver_big_haptic_driver.dll`\r\n\r\n## Register with SteamVR\r\n\r\nTypical command:\r\n\r\n- `vrpathreg.exe adddriver <path-to-openvr-driver/rust-haptic-driver>`\r\n\r\n## Next implementation step\r\n\r\nImplement concrete C++ ABI-compatible vtables for:\r\n\r\n- `IServerTrackedDeviceProvider`\r\n- `ITrackedDeviceServerDriver`\r\n\r\nand route `TriggerHapticVibration` events to `BigHapticDriver_SendAmplitude` logic.\r\n","epoch":1417,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml::request_dc7aedcd-b1dc-443b-8063-d3b5527768d7",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","scheme":"file"},"requestId":"request_dc7aedcd-b1dc-443b-8063-d3b5527768d7","content":"[workspace]\r\nresolver = \"2\"\r\nmembers = [\r\n  \"shared/haptics-protocol\",\r\n  \"host/hid-bridge\",\r\n  \"host/hid-cli\",\r\n  \"openvr-driver/rust-haptic-driver\",\r\n  \"firmware/qtpy-samd21\",\r\n]\r\ndefault-members = [\r\n  \"shared/haptics-protocol\",\r\n  \"host/hid-bridge\",\r\n  \"host/hid-cli\",\r\n  \"openvr-driver/rust-haptic-driver\",\r\n]\r\n\r\n[workspace.package]\r\nedition = \"2021\"\r\nauthors = [\"qt-py-haptics contributors\"]\r\nlicense = \"MIT OR Apache-2.0\"\r\n\r\n[workspace.dependencies]\r\nanyhow = \"1\"\r\nclap = { version = \"4\", features = [\"derive\"] }\r\nhidapi = \"2\"\r\nlog = \"0.4\"\r\nthiserror = \"2\"\r\n\r\n[profile.release]\r\ncodegen-units = 1\r\nlto = true\r\nopt-level = \"s\"\r\npanic = \"abort\"\r\n","epoch":1422,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/.gitignore::request_dc7aedcd-b1dc-443b-8063-d3b5527768d7",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\.gitignore","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/.gitignore","path":"/C:/Users/decid/Documents/projects/qt-py-haptics/.gitignore","scheme":"file"},"requestId":"request_dc7aedcd-b1dc-443b-8063-d3b5527768d7","content":"# Rust / Cargo\r\n/target/\r\n/build/\r\n**/*.rs.bk\r\nCargo.lock\r\nstderr\r\nstdout\r\nopenvr-driver/rust-haptic-driver/bin/win64/driver_big_haptic_driver.dll\r\n\r\n# IDEs\r\n.idea/\r\n.vscode/\r\n*.swp\r\n*.swo\r\n\r\n# OS\r\n.DS_Store\r\nThumbs.db\r\n\r\n# Tools\r\ntools/uf2/uf2conv.py\r\ntools/uf2/uf2families.json\r\n","epoch":1425,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1::request_dc7aedcd-b1dc-443b-8063-d3b5527768d7",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_deploy_openvr_driver.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","scheme":"file"},"requestId":"request_dc7aedcd-b1dc-443b-8063-d3b5527768d7","content":"[CmdletBinding()]\r\nparam(\r\n    [ValidateSet(\"debug\", \"release\")]\r\n    [string]$Profile = \"release\"\r\n)\r\n\r\n$ErrorActionPreference = \"Stop\"\r\n\r\n$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot \"..\\..\" )).Path\r\n$DriverRoot = Join-Path $RepoRoot \"openvr-driver\\rust-haptic-driver\"\r\n$DeployDir = Join-Path $DriverRoot \"bin\\win64\"\r\n$BuiltDll = Join-Path $RepoRoot \"target\\$Profile\\big_haptic_driver.dll\"\r\n$DeployDll = Join-Path $DeployDir \"driver_big_haptic_driver.dll\"\r\n\r\nif (-not (Test-Path $DeployDir)) {\r\n    New-Item -ItemType Directory -Path $DeployDir -Force | Out-Null\r\n}\r\n\r\nPush-Location $RepoRoot\r\ntry {\r\n    Write-Host \"Building big-haptic-driver ($Profile)...\"\r\n\r\n    $cargoArgs = @(\"build\", \"-p\", \"big-haptic-driver\")\r\n    if ($Profile -eq \"release\") {\r\n        $cargoArgs += \"--release\"\r\n    }\r\n\r\n    & cargo @cargoArgs\r\n    if ($LASTEXITCODE -ne 0) {\r\n        throw \"Cargo build failed with exit code $LASTEXITCODE\"\r\n    }\r\n\r\n    if (-not (Test-Path $BuiltDll)) {\r\n        throw \"Built DLL not found: $BuiltDll\"\r\n    }\r\n\r\n    Copy-Item -Path $BuiltDll -Destination $DeployDll -Force\r\n    Write-Host \"Deployed: $DeployDll\"\r\n}\r\nfinally {\r\n    Pop-Location\r\n}\r\n","epoch":1428,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/README.md::request_dc7aedcd-b1dc-443b-8063-d3b5527768d7",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/README.md","scheme":"file"},"requestId":"request_dc7aedcd-b1dc-443b-8063-d3b5527768d7","content":"# big-haptic-driver\r\n\r\nInitial OpenVR driver scaffold in Rust.\r\n\r\n## Current capabilities\r\n\r\n- Exports `HmdDriverFactory` with minimal interface-name validation\r\n- Exposes DLL-callable haptics test exports:\r\n  - `BigHapticDriver_SendAmplitude(float)`\r\n  - `BigHapticDriver_Stop()`\r\n- Sends packets to firmware through shared `hid-bridge`\r\n\r\n## Build\r\n\r\nFrom workspace root:\r\n\r\n- `cargo build -p big-haptic-driver --release`\r\n\r\nOutput DLL path:\r\n\r\n- `target/release/big_haptic_driver.dll`\r\n\r\n## Build + deploy (one command)\r\n\r\nFrom workspace root:\r\n\r\n- `./tools/scripts/build_and_deploy_openvr_driver.ps1`\r\n\r\nThis builds the crate and copies the DLL to:\r\n\r\n- `openvr-driver/rust-haptic-driver/bin/win64/driver_big_haptic_driver.dll`\r\n\r\n## SteamVR layout\r\n\r\nDriver folder should include:\r\n\r\n- `driver.vrdrivermanifest`\r\n- `resources/`\r\n- `bin/win64/driver_big_haptic_driver.dll`\r\n\r\n## Register with SteamVR\r\n\r\nTypical command:\r\n\r\n- `vrpathreg.exe adddriver <path-to-openvr-driver/rust-haptic-driver>`\r\n\r\n## Next implementation step\r\n\r\nImplement concrete C++ ABI-compatible vtables for:\r\n\r\n- `IServerTrackedDeviceProvider`\r\n- `ITrackedDeviceServerDriver`\r\n\r\nand route `TriggerHapticVibration` events to `BigHapticDriver_SendAmplitude` logic.\r\n","epoch":1431,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md::request_dc7aedcd-b1dc-443b-8063-d3b5527768d7",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_dc7aedcd-b1dc-443b-8063-d3b5527768d7","content":"# qt-py-haptics: Directory Structure Outline\r\n\r\nThis repository is organized around four major layers from the implementation plan:\r\n1) Embedded firmware, 2) Host communication, 3) OpenVR driver, 4) Integration/deployment.\r\n\r\n## Proposed Hierarchy\r\n\r\n```text\r\nqt-py-haptics/\r\n├─ .github/\r\n│  └─ workflows/\r\n├─ build/\r\n├─ docs/\r\n│  ├─ Haptic System Implementation Plan.md\r\n│  └─ Research TXT.txt\r\n├─ firmware/\r\n│  └─ qtpy-samd21/\r\n│     ├─ .cargo/\r\n│     ├─ src/\r\n│     ├─ examples/\r\n│     └─ tests/\r\n├─ hardware/\r\n│  ├─ boards/\r\n│  │  └─ qtpy-samd21/\r\n│  ├─ drivers/\r\n│  │  └─ drv2605l/\r\n│  └─ wiring/\r\n├─ host/\r\n│  ├─ hid-cli/\r\n│  │  └─ src/\r\n│  └─ hid-bridge/\r\n│     └─ src/\r\n├─ openvr-driver/\r\n│  └─ rust-haptic-driver/\r\n│     ├─ bin/\r\n│     │  └─ win64/\r\n│     ├─ resources/\r\n│     │  ├─ input/\r\n│     │  └─ localization/\r\n│     └─ src/\r\n├─ shared/\r\n│  └─ haptics-protocol/\r\n│     └─ src/\r\n├─ tests/\r\n│  ├─ integration/\r\n│  └─ latency/\r\n└─ tools/\r\n   ├─ scripts/\r\n   └─ steamvr/\r\n```\r\n\r\n## Folder Purposes\r\n\r\n### .github/workflows\r\nCI pipelines (firmware checks, host/unit tests, formatting/linting, release packaging).\r\n\r\n### build\r\nGenerated artifacts, temporary outputs, and local packaging/staging files.\r\n\r\n### docs\r\nArchitecture docs, protocol notes, calibration procedures, and implementation plans.\r\n\r\n### firmware/qtpy-samd21\r\nRust `no_std` firmware for ATSAMD21 (USB HID + I2C control of DRV2605L).\r\n- `.cargo/`: target config (`thumbv6m-none-eabi`), linker settings.\r\n- `src/`: main firmware modules (USB task, I2C task, command parser).\r\n- `examples/`: bring-up tools (I2C scan, DRV2605L sanity checks).\r\n- `tests/`: embedded-focused test harness patterns and host-driven firmware tests.\r\n\r\n### hardware\r\nHardware-specific assets.\r\n- `boards/qtpy-samd21/`: pin maps, power notes, bootloader/flash instructions.\r\n- `drivers/drv2605l/`: register map references, effect tuning tables.\r\n- `wiring/`: connection diagrams (QT Py ↔ DRV2605L ↔ actuator).\r\n\r\n### host\r\nHost-side utilities and communication libraries.\r\n- `hid-cli/`: command-line utility to send and inspect HID packets.\r\n- `hid-bridge/`: reusable Rust library for HID discovery, packet tx/rx, retries.\r\n\r\n### openvr-driver/rust-haptic-driver\r\nSteamVR/OpenVR driver DLL project in Rust.\r\n- `src/`: provider/device interface implementation and event handling.\r\n- `resources/input/`: input profile JSON for haptic component bindings.\r\n- `resources/localization/`: localized strings used by SteamVR UI.\r\n- `bin/win64/`: built DLL placement and deploy-ready runtime layout.\r\n\r\n### shared/haptics-protocol\r\nSingle source of truth for the HID packet schema, command IDs, and encoding helpers used by firmware + host + driver.\r\n\r\n### tests\r\nCross-layer validation.\r\n- `integration/`: end-to-end tests (OpenVR event → HID → firmware behavior).\r\n- `latency/`: timing and jitter benchmarks for haptic response.\r\n\r\n### tools\r\nOperational scripts and deployment helpers.\r\n- `scripts/`: utility scripts (format, flash, smoke test, packaging).\r\n- `steamvr/`: driver registration templates and helper assets for `vrpathreg`.\r\n\r\n## Notes\r\n\r\n- Keep protocol definitions centralized in `shared/haptics-protocol` to avoid drift.\r\n- Keep deployment-facing assets (`manifest`, input profile, localization) under `openvr-driver/rust-haptic-driver/resources`.\r\n- Place generated binaries and temporary build output in `build/` and avoid committing transient files.\r\n","epoch":1434,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/README.md::request_72a7d041-925f-4bbf-a717-feff5a61422d",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/README.md","scheme":"file"},"requestId":"request_72a7d041-925f-4bbf-a717-feff5a61422d","content":"# big-haptic-driver\r\n\r\nInitial OpenVR driver scaffold in Rust.\r\n\r\n## Current capabilities\r\n\r\n- Exports `HmdDriverFactory` with minimal interface-name validation\r\n- Exposes DLL-callable haptics test exports:\r\n  - `BigHapticDriver_SendAmplitude(float)`\r\n  - `BigHapticDriver_Stop()`\r\n- Sends packets to firmware through shared `hid-bridge`\r\n\r\n## Build\r\n\r\nFrom workspace root:\r\n\r\n- `cargo build -p big-haptic-driver --release`\r\n\r\nOutput DLL path:\r\n\r\n- `target/release/big_haptic_driver.dll`\r\n\r\n## Build + deploy (one command)\r\n\r\nFrom workspace root:\r\n\r\n- `./tools/scripts/build_and_deploy_openvr_driver.ps1`\r\n\r\nThis builds the crate and copies the DLL to:\r\n\r\n- `openvr-driver/big-haptic-driver/bin/win64/driver_big_haptic_driver.dll`\r\n\r\n## SteamVR layout\r\n\r\nDriver folder should include:\r\n\r\n- `driver.vrdrivermanifest`\r\n- `resources/`\r\n- `bin/win64/driver_big_haptic_driver.dll`\r\n\r\n## Register with SteamVR\r\n\r\nTypical command:\r\n\r\n- `vrpathreg.exe adddriver <path-to-openvr-driver/big-haptic-driver>`\r\n\r\n## Next implementation step\r\n\r\nImplement concrete C++ ABI-compatible vtables for:\r\n\r\n- `IServerTrackedDeviceProvider`\r\n- `ITrackedDeviceServerDriver`\r\n\r\nand route `TriggerHapticVibration` events to `BigHapticDriver_SendAmplitude` logic.\r\n","epoch":1438,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/README.md::request_7c1fcecb-8a9a-46b2-9072-3ea1069fbf7d",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/README.md","scheme":"file"},"requestId":"request_7c1fcecb-8a9a-46b2-9072-3ea1069fbf7d","content":"# big-haptic-driver\r\n\r\nInitial OpenVR driver scaffold in Rust.\r\n\r\n## Current capabilities\r\n\r\n- Exports `HmdDriverFactory` with minimal interface-name validation\r\n- Exposes DLL-callable haptics test exports:\r\n  - `BigHapticDriver_SendAmplitude(float)`\r\n  - `BigHapticDriver_Stop()`\r\n- Sends packets to firmware through shared `hid-bridge`\r\n\r\n## Build\r\n\r\nFrom workspace root:\r\n\r\n- `cargo build -p big-haptic-driver --release`\r\n\r\nOutput DLL path:\r\n\r\n- `target/release/big_haptic_driver.dll`\r\n\r\n## Build + deploy (one command)\r\n\r\nFrom workspace root:\r\n\r\n- `./tools/scripts/build_and_deploy_openvr_driver.ps1`\r\n\r\nThis builds the crate and copies the DLL to:\r\n\r\n- `openvr-driver/big-haptic-driver/bin/win64/driver_big_haptic_driver.dll`\r\n\r\n## SteamVR layout\r\n\r\nDriver folder should include:\r\n\r\n- `driver.vrdrivermanifest`\r\n- `resources/`\r\n- `resources/input/big_haptic_profile.json`\r\n- `resources/localization/en_us.json`\r\n- `bin/win64/driver_big_haptic_driver.dll`\r\n\r\n## Register with SteamVR\r\n\r\nTypical command:\r\n\r\n- `vrpathreg.exe adddriver <path-to-openvr-driver/big-haptic-driver>`\r\n\r\n## Next implementation step\r\n\r\nImplement concrete C++ ABI-compatible vtables for:\r\n\r\n- `IServerTrackedDeviceProvider`\r\n- `ITrackedDeviceServerDriver`\r\n\r\nand route `TriggerHapticVibration` events to `BigHapticDriver_SendAmplitude` logic.\r\n","epoch":1442,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/README.md::request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/README.md","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","content":"# big-haptic-driver\r\n\r\nInitial OpenVR driver scaffold in Rust.\r\n\r\n## Current capabilities\r\n\r\n- Exports `HmdDriverFactory` with minimal interface-name validation\r\n- Exposes DLL-callable haptics test exports:\r\n  - `BigHapticDriver_SendAmplitude(float)`\r\n  - `BigHapticDriver_Stop()`\r\n- Sends packets to firmware through shared `hid-bridge`\r\n\r\n## Build\r\n\r\nFrom workspace root:\r\n\r\n- `cargo build -p big-haptic-driver --release`\r\n\r\nOutput DLL path:\r\n\r\n- `target/release/big_haptic_driver.dll`\r\n\r\n## Build + deploy (one command)\r\n\r\nFrom workspace root:\r\n\r\n- `./tools/scripts/build_and_deploy_openvr_driver.ps1`\r\n\r\nThis builds the crate and copies the DLL to:\r\n\r\n- `openvr-driver/big-haptic-driver/bin/win64/driver_big_haptic_driver.dll`\r\n\r\n## SteamVR layout\r\n\r\nDriver folder should include:\r\n\r\n- `driver.vrdrivermanifest`\r\n- `resources/`\r\n- `resources/input/big_haptic_profile.json`\r\n- `resources/localization/en_us.json`\r\n- `bin/win64/driver_big_haptic_driver.dll`\r\n\r\n## Register with SteamVR\r\n\r\nPowerShell command (note the `&` call operator before the quoted exe path):\r\n\r\n- `& \"C:\\Program Files (x86)\\Steam\\steamapps\\common\\SteamVR\\bin\\win64\\vrpathreg.exe\" adddriver \"C:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\"`\r\n\r\nTo verify registration:\r\n\r\n- `& \"C:\\Program Files (x86)\\Steam\\steamapps\\common\\SteamVR\\bin\\win64\\vrpathreg.exe\"`\r\n\r\nTo remove registration:\r\n\r\n- `& \"C:\\Program Files (x86)\\Steam\\steamapps\\common\\SteamVR\\bin\\win64\\vrpathreg.exe\" removedriver \"C:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\"`\r\n\r\n## Next implementation step\r\n\r\nImplement concrete C++ ABI-compatible vtables for:\r\n\r\n- `IServerTrackedDeviceProvider`\r\n- `ITrackedDeviceServerDriver`\r\n\r\nand route `TriggerHapticVibration` events to `BigHapticDriver_SendAmplitude` logic.\r\n","epoch":1499,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1::request_0837dd8d-03e4-419a-ab9a-44ad661c4a6c",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_0837dd8d-03e4-419a-ab9a-44ad661c4a6c","content":"[CmdletBinding()]\r\nparam(\r\n    [float]$Amplitude = 0.35,\r\n    [float]$DurationSeconds = 0.20,\r\n    [float]$Frequency = 160.0,\r\n    [switch]$Stop\r\n)\r\n\r\n$ErrorActionPreference = \"Stop\"\r\n\r\n$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot \"..\\..\" )).Path\r\n$DriverBin = Join-Path $RepoRoot \"openvr-driver\\big-haptic-driver\\bin\\win64\"\r\n$DriverDll = Join-Path $DriverBin \"driver_big_haptic_driver.dll\"\r\n\r\nif (-not (Test-Path $DriverDll)) {\r\n    throw \"Driver DLL not found: $DriverDll`nRun .\\tools\\scripts\\build_and_deploy_openvr_driver.ps1 first.\"\r\n}\r\n\r\n$originalPath = $env:PATH\r\nPush-Location\r\ntry {\r\n    $env:PATH = \"$DriverBin;$env:PATH\"\r\n    Set-Location $DriverBin\r\n\r\n    Add-Type -TypeDefinition @\"\r\nusing System;\r\nusing System.Runtime.InteropServices;\r\n\r\npublic static class BigHapticNative {\r\n    [DllImport(\"driver_big_haptic_driver.dll\", CallingConvention = CallingConvention.Cdecl)]\r\n    public static extern int BigHapticDriver_SendAmplitude(float amplitude);\r\n\r\n    [DllImport(\"driver_big_haptic_driver.dll\", CallingConvention = CallingConvention.Cdecl)]\r\n    public static extern int BigHapticDriver_Stop();\r\n\r\n    [DllImport(\"driver_big_haptic_driver.dll\", CallingConvention = CallingConvention.Cdecl)]\r\n    public static extern void BigHapticDriver_InjectHaptic(float amplitude, float duration_seconds, float frequency);\r\n}\r\n\"@\r\n\r\n    if ($Stop) {\r\n        $rc = [BigHapticNative]::BigHapticDriver_Stop()\r\n        Write-Host \"Stop rc: $rc\"\r\n        return\r\n    }\r\n\r\n    [BigHapticNative]::BigHapticDriver_InjectHaptic($Amplitude, $DurationSeconds, $Frequency)\r\n    Write-Host \"Injected haptic amplitude=$Amplitude duration_s=$DurationSeconds freq_hz=$Frequency\"\r\n}\r\nfinally {\r\n    $env:PATH = $originalPath\r\n    Pop-Location\r\n}\r\n","epoch":1505,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/README.md::request_0837dd8d-03e4-419a-ab9a-44ad661c4a6c",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/README.md","scheme":"file"},"requestId":"request_0837dd8d-03e4-419a-ab9a-44ad661c4a6c","content":"# big-haptic-driver\r\n\r\nInitial OpenVR driver scaffold in Rust.\r\n\r\n## Current capabilities\r\n\r\n- Exports `HmdDriverFactory` with minimal interface-name validation\r\n- Exposes DLL-callable haptics test exports:\r\n  - `BigHapticDriver_SendAmplitude(float)`\r\n  - `BigHapticDriver_Stop()`\r\n- Sends packets to firmware through shared `hid-bridge`\r\n\r\n## Build\r\n\r\nFrom workspace root:\r\n\r\n- `cargo build -p big-haptic-driver --release`\r\n\r\nOutput DLL path:\r\n\r\n- `target/release/big_haptic_driver.dll`\r\n\r\n## Build + deploy (one command)\r\n\r\nFrom workspace root:\r\n\r\n- `./tools/scripts/build_and_deploy_openvr_driver.ps1`\r\n\r\nThis builds the crate and copies the DLL to:\r\n\r\n- `openvr-driver/big-haptic-driver/bin/win64/driver_big_haptic_driver.dll`\r\n\r\n## SteamVR layout\r\n\r\nDriver folder should include:\r\n\r\n- `driver.vrdrivermanifest`\r\n- `resources/`\r\n- `resources/input/big_haptic_profile.json`\r\n- `resources/localization/en_us.json`\r\n- `bin/win64/driver_big_haptic_driver.dll`\r\n\r\n## Register with SteamVR\r\n\r\nPowerShell command (note the `&` call operator before the quoted exe path):\r\n\r\n- `& \"C:\\Program Files (x86)\\Steam\\steamapps\\common\\SteamVR\\bin\\win64\\vrpathreg.exe\" adddriver \"C:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\"`\r\n\r\nTo verify registration:\r\n\r\n- `& \"C:\\Program Files (x86)\\Steam\\steamapps\\common\\SteamVR\\bin\\win64\\vrpathreg.exe\"`\r\n\r\nTo remove registration:\r\n\r\n- `& \"C:\\Program Files (x86)\\Steam\\steamapps\\common\\SteamVR\\bin\\win64\\vrpathreg.exe\" removedriver \"C:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\"`\r\n\r\n## Next implementation step\r\n\r\nImplement concrete C++ ABI-compatible vtables for:\r\n\r\n- `IServerTrackedDeviceProvider`\r\n- `ITrackedDeviceServerDriver`\r\n\r\nand route `TriggerHapticVibration` events to `BigHapticDriver_SendAmplitude` logic.\r\n\r\n## Testing status (important)\r\n\r\nThe driver currently registers with SteamVR, but it does **not** yet create a fully usable tracked controller/input device in SteamVR bindings UI.\r\n\r\nSo, end-to-end OpenVR binding-triggered haptics is not available yet.\r\n\r\nTemporary direct test (bypasses SteamVR input bindings):\r\n\r\n- `./tools/scripts/test_big_haptic_driver_exports.ps1 -Amplitude 0.35 -DurationSeconds 0.20 -Frequency 160`\r\n\r\nStop command:\r\n\r\n- `./tools/scripts/test_big_haptic_driver_exports.ps1 -Stop`\r\n","epoch":1508,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_scaffold.rs::request_244f6538-b0e5-4f3c-84eb-c094cc01ec2c",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_244f6538-b0e5-4f3c-84eb-c094cc01ec2c","content":"//! OpenVR ABI/vtable scaffolding for the Big Haptic Driver.\r\n//!\r\n//! This module owns C ABI exports and interface routing for SteamVR-facing\r\n//! provider/device entry points, while delegating haptics behavior to\r\n//! the `haptics` module.\r\n\r\nuse core::ffi::{c_void, CStr};\r\nuse core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU32, Ordering};\r\nuse std::os::raw::c_char;\r\n\r\nuse crate::haptics;\r\n\r\nconst SERVER_PROVIDER_INTERFACE: &str = \"IServerTrackedDeviceProvider_005\";\r\nconst TRACKED_DEVICE_INTERFACE: &str = \"ITrackedDeviceServerDriver_005\";\r\n\r\n#[repr(i32)]\r\nenum DriverReturnCode {\r\n    None = 0,\r\n    Unknown = 1,\r\n    InitInterfaceNotFound = 105,\r\n}\r\n\r\ntype EvRInitError = i32;\r\nconst VR_INIT_ERROR_NONE: EvRInitError = 0;\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nstruct DriverPose {\r\n    _reserved: [u8; 128],\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerTrackedDeviceProviderVTable {\r\n    init: extern \"C\" fn(*mut ServerTrackedDeviceProvider, *mut c_void) -> EvRInitError,\r\n    cleanup: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    get_interface_versions:\r\n        extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> *const *const c_char,\r\n    run_frame: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    should_block_standby_mode: extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> bool,\r\n    enter_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    leave_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n}\r\n\r\n#[repr(C)]\r\nstruct TrackedDeviceServerDriverVTable {\r\n    activate: extern \"C\" fn(*mut TrackedDeviceServerDriver, u32) -> EvRInitError,\r\n    deactivate: extern \"C\" fn(*mut TrackedDeviceServerDriver),\r\n    enter_standby: extern \"C\" fn(*mut TrackedDeviceServerDriver),\r\n    get_component:\r\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char) -> *mut c_void,\r\n    debug_request:\r\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char, *mut c_char, u32),\r\n    get_pose: extern \"C\" fn(*mut TrackedDeviceServerDriver) -> DriverPose,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerTrackedDeviceProvider {\r\n    vtable: *const ServerTrackedDeviceProviderVTable,\r\n}\r\nunsafe impl Sync for ServerTrackedDeviceProvider {}\r\n\r\n#[repr(C)]\r\nstruct TrackedDeviceServerDriver {\r\n    vtable: *const TrackedDeviceServerDriverVTable,\r\n}\r\nunsafe impl Sync for TrackedDeviceServerDriver {}\r\n\r\n#[repr(C)]\r\nstruct InterfaceVersions([*const c_char; 3]);\r\nunsafe impl Sync for InterfaceVersions {}\r\n\r\nstatic HOST_CONTEXT: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DEVICE_INDEX: AtomicU32 = AtomicU32::new(u32::MAX);\r\nstatic DEVICE_ACTIVE: AtomicBool = AtomicBool::new(false);\r\n\r\nconst PROVIDER_IFACE_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_005\\0\";\r\nconst DEVICE_IFACE_CSTR: &[u8] = b\"ITrackedDeviceServerDriver_005\\0\";\r\n\r\nstatic PROVIDER_INTERFACE_VERSIONS: InterfaceVersions = InterfaceVersions([\r\n    PROVIDER_IFACE_CSTR.as_ptr().cast(),\r\n    DEVICE_IFACE_CSTR.as_ptr().cast(),\r\n    core::ptr::null(),\r\n]);\r\n\r\nextern \"C\" fn provider_init(\r\n    _this: *mut ServerTrackedDeviceProvider,\r\n    driver_context: *mut c_void,\r\n) -> EvRInitError {\r\n    HOST_CONTEXT.store(driver_context, Ordering::SeqCst);\r\n    VR_INIT_ERROR_NONE\r\n}\r\n\r\nextern \"C\" fn provider_cleanup(_this: *mut ServerTrackedDeviceProvider) {\r\n    HOST_CONTEXT.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\r\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn provider_get_interface_versions(\r\n    _this: *mut ServerTrackedDeviceProvider,\r\n) -> *const *const c_char {\r\n    PROVIDER_INTERFACE_VERSIONS.0.as_ptr()\r\n}\r\n\r\nextern \"C\" fn provider_run_frame(_this: *mut ServerTrackedDeviceProvider) {\r\n    haptics::on_frame();\r\n}\r\n\r\nextern \"C\" fn provider_should_block_standby_mode(_this: *mut ServerTrackedDeviceProvider) -> bool {\r\n    false\r\n}\r\n\r\nextern \"C\" fn provider_enter_standby(_this: *mut ServerTrackedDeviceProvider) {}\r\n\r\nextern \"C\" fn provider_leave_standby(_this: *mut ServerTrackedDeviceProvider) {}\r\n\r\nextern \"C\" fn device_activate(_this: *mut TrackedDeviceServerDriver, object_id: u32) -> EvRInitError {\r\n    DEVICE_INDEX.store(object_id, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(true, Ordering::SeqCst);\r\n    VR_INIT_ERROR_NONE\r\n}\r\n\r\nextern \"C\" fn device_deactivate(_this: *mut TrackedDeviceServerDriver) {\r\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\r\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn device_enter_standby(_this: *mut TrackedDeviceServerDriver) {\r\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn device_get_component(\r\n    _this: *mut TrackedDeviceServerDriver,\r\n    _component_name: *const c_char,\r\n) -> *mut c_void {\r\n    core::ptr::null_mut()\r\n}\r\n\r\nextern \"C\" fn device_debug_request(\r\n    _this: *mut TrackedDeviceServerDriver,\r\n    _request: *const c_char,\r\n    response: *mut c_char,\r\n    response_size: u32,\r\n) {\r\n    if response.is_null() || response_size == 0 {\r\n        return;\r\n    }\r\n    unsafe {\r\n        *response = 0;\r\n    }\r\n}\r\n\r\nextern \"C\" fn device_get_pose(_this: *mut TrackedDeviceServerDriver) -> DriverPose {\r\n    DriverPose { _reserved: [0; 128] }\r\n}\r\n\r\nstatic PROVIDER_VTABLE: ServerTrackedDeviceProviderVTable = ServerTrackedDeviceProviderVTable {\r\n    init: provider_init,\r\n    cleanup: provider_cleanup,\r\n    get_interface_versions: provider_get_interface_versions,\r\n    run_frame: provider_run_frame,\r\n    should_block_standby_mode: provider_should_block_standby_mode,\r\n    enter_standby: provider_enter_standby,\r\n    leave_standby: provider_leave_standby,\r\n};\r\n\r\nstatic DEVICE_VTABLE: TrackedDeviceServerDriverVTable = TrackedDeviceServerDriverVTable {\r\n    activate: device_activate,\r\n    deactivate: device_deactivate,\r\n    enter_standby: device_enter_standby,\r\n    get_component: device_get_component,\r\n    debug_request: device_debug_request,\r\n    get_pose: device_get_pose,\r\n};\r\n\r\nstatic PROVIDER: ServerTrackedDeviceProvider = ServerTrackedDeviceProvider {\r\n    vtable: &PROVIDER_VTABLE,\r\n};\r\n\r\nstatic DEVICE: TrackedDeviceServerDriver = TrackedDeviceServerDriver {\r\n    vtable: &DEVICE_VTABLE,\r\n};\r\n\r\nfn set_return_code(out: *mut i32, code: DriverReturnCode) {\r\n    if !out.is_null() {\r\n        unsafe {\r\n            *out = code as i32;\r\n        }\r\n    }\r\n}\r\n\r\nfn read_interface_name(ptr: *const i8) -> Option<&'static str> {\r\n    if ptr.is_null() {\r\n        return None;\r\n    }\r\n\r\n    let cstr = unsafe { CStr::from_ptr(ptr) };\r\n    let Ok(name) = cstr.to_str() else {\r\n        return None;\r\n    };\r\n\r\n    if name == SERVER_PROVIDER_INTERFACE {\r\n        return Some(SERVER_PROVIDER_INTERFACE);\r\n    }\r\n    if name == TRACKED_DEVICE_INTERFACE {\r\n        return Some(TRACKED_DEVICE_INTERFACE);\r\n    }\r\n\r\n    None\r\n}\r\n\r\n/// Resolves OpenVR interface requests to static provider/device instances.\r\n///\r\n/// On match, writes `DriverReturnCode::None` to `p_return_code` and returns a\r\n/// stable interface pointer. On failure, writes an error code and returns null.\r\npub fn hmd_driver_factory(\r\n    p_interface_name: *const c_char,\r\n    p_return_code: *mut i32,\r\n) -> *mut c_void {\r\n    let Some(interface_name) = read_interface_name(p_interface_name) else {\r\n        set_return_code(p_return_code, DriverReturnCode::InitInterfaceNotFound);\r\n        return core::ptr::null_mut();\r\n    };\r\n\r\n    if interface_name == SERVER_PROVIDER_INTERFACE {\r\n        set_return_code(p_return_code, DriverReturnCode::None);\r\n        return (&PROVIDER as *const ServerTrackedDeviceProvider)\r\n            .cast_mut()\r\n            .cast();\r\n    }\r\n\r\n    if interface_name == TRACKED_DEVICE_INTERFACE {\r\n        set_return_code(p_return_code, DriverReturnCode::None);\r\n        return (&DEVICE as *const TrackedDeviceServerDriver)\r\n            .cast_mut()\r\n            .cast();\r\n    }\r\n\r\n    set_return_code(p_return_code, DriverReturnCode::Unknown);\r\n    core::ptr::null_mut()\r\n}\r\n","epoch":1512,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/README.md::request_244f6538-b0e5-4f3c-84eb-c094cc01ec2c",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/README.md","scheme":"file"},"requestId":"request_244f6538-b0e5-4f3c-84eb-c094cc01ec2c","content":"# big-haptic-driver\r\n\r\nInitial OpenVR driver scaffold in Rust.\r\n\r\n## Current capabilities\r\n\r\n- Exports `HmdDriverFactory` with minimal interface-name validation\r\n- Exposes DLL-callable haptics test exports:\r\n  - `BigHapticDriver_SendAmplitude(float)`\r\n  - `BigHapticDriver_Stop()`\r\n- Sends packets to firmware through shared `hid-bridge`\r\n\r\n## Build\r\n\r\nFrom workspace root:\r\n\r\n- `cargo build -p big-haptic-driver --release`\r\n\r\nOutput DLL path:\r\n\r\n- `target/release/big_haptic_driver.dll`\r\n\r\n## Build + deploy (one command)\r\n\r\nFrom workspace root:\r\n\r\n- `./tools/scripts/build_and_deploy_openvr_driver.ps1`\r\n\r\nThis builds the crate and copies the DLL to:\r\n\r\n- `openvr-driver/big-haptic-driver/bin/win64/driver_big_haptic_driver.dll`\r\n\r\n## SteamVR layout\r\n\r\nDriver folder should include:\r\n\r\n- `driver.vrdrivermanifest`\r\n- `resources/`\r\n- `resources/input/big_haptic_profile.json`\r\n- `resources/localization/en_us.json`\r\n- `bin/win64/driver_big_haptic_driver.dll`\r\n\r\n## Register with SteamVR\r\n\r\nPowerShell command (note the `&` call operator before the quoted exe path):\r\n\r\n- `& \"C:\\Program Files (x86)\\Steam\\steamapps\\common\\SteamVR\\bin\\win64\\vrpathreg.exe\" adddriver \"C:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\"`\r\n\r\nTo verify registration:\r\n\r\n- `& \"C:\\Program Files (x86)\\Steam\\steamapps\\common\\SteamVR\\bin\\win64\\vrpathreg.exe\"`\r\n\r\nTo remove registration:\r\n\r\n- `& \"C:\\Program Files (x86)\\Steam\\steamapps\\common\\SteamVR\\bin\\win64\\vrpathreg.exe\" removedriver \"C:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\"`\r\n\r\n## Next implementation step\r\n\r\nImplement concrete C++ ABI-compatible vtables for:\r\n\r\n- `IServerTrackedDeviceProvider`\r\n- `ITrackedDeviceServerDriver`\r\n\r\nand route `TriggerHapticVibration` events to `BigHapticDriver_SendAmplitude` logic.\r\n\r\n## Testing status (important)\r\n\r\nThe driver currently registers with SteamVR, but it does **not** yet create a fully usable tracked controller/input device in SteamVR bindings UI.\r\n\r\nSo, end-to-end OpenVR binding-triggered haptics is not available yet.\r\n\r\nTemporary direct test (bypasses SteamVR input bindings):\r\n\r\n- `./tools/scripts/test_big_haptic_driver_exports.ps1 -Amplitude 0.35 -DurationSeconds 0.20 -Frequency 160`\r\n\r\nNote: in this direct-export mode, SteamVR frame pumping is bypassed, so the script performs an explicit delayed stop for `-DurationSeconds` unless `-NoAutoStop` is supplied.\r\n\r\nStop command:\r\n\r\n- `./tools/scripts/test_big_haptic_driver_exports.ps1 -Stop`\r\n","epoch":1519,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_scaffold.rs::request_cd9235f7-4b6a-4823-ab9f-74e85fd0e64c",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_cd9235f7-4b6a-4823-ab9f-74e85fd0e64c","content":"//! OpenVR ABI/vtable scaffolding for the Big Haptic Driver.\r\n//!\r\n//! This module owns C ABI exports and interface routing for SteamVR-facing\r\n//! provider/device entry points, while delegating haptics behavior to\r\n//! the `haptics` module.\r\n\r\nuse core::ffi::{c_void, CStr};\r\nuse core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU32, AtomicU64, Ordering};\r\nuse std::os::raw::c_char;\r\n\r\nuse crate::haptics;\r\n\r\nconst SERVER_PROVIDER_INTERFACE: &str = \"IServerTrackedDeviceProvider_004\";\r\nconst SERVER_PROVIDER_INTERFACE_ALT: &str = \"IServerTrackedDeviceProvider_005\";\r\nconst TRACKED_DEVICE_INTERFACE: &str = \"ITrackedDeviceServerDriver_005\";\r\n\r\nconst IVR_SERVER_DRIVER_HOST_VERSION: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\nconst IVR_DRIVER_INPUT_VERSION: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_PROPERTIES_VERSION: &[u8] = b\"IVRProperties_001\\0\";\r\n\r\nconst TRACKED_DEVICE_CLASS_CONTROLLER: i32 = 2;\r\nconst VREVENT_INPUT_HAPTIC_VIBRATION: u32 = 1700;\r\nconst VR_INIT_ERROR_DRIVER_FAILED: EvRInitError = 200;\r\n\r\nconst PROP_MODEL_NUMBER_STRING: u32 = 1001;\r\nconst PROP_SERIAL_NUMBER_STRING: u32 = 1002;\r\nconst PROP_INPUT_PROFILE_PATH_STRING: u32 = 1037;\r\nconst PROP_CONTROLLER_TYPE_STRING: u32 = 7000;\r\n\r\nconst K_UN_STRING_PROPERTY_TAG: u32 = 5;\r\nconst PROPERTY_WRITE_SET: i32 = 0;\r\n\r\nconst DEVICE_SERIAL_CSTR: &[u8] = b\"qtpy-samd21-haptic-001\\0\";\r\nconst DEVICE_MODEL_CSTR: &[u8] = b\"QT Py SAMD21 Haptic\\0\";\r\nconst DEVICE_CONTROLLER_TYPE_CSTR: &[u8] = b\"big_haptic_driver\\0\";\r\nconst DEVICE_INPUT_PROFILE_CSTR: &[u8] = b\"{big_haptic_driver}/input/big_haptic_profile.json\\0\";\r\nconst DEVICE_HAPTIC_PATH_CSTR: &[u8] = b\"/output/haptic\\0\";\r\n\r\n#[repr(i32)]\r\nenum DriverReturnCode {\r\n    None = 0,\r\n    Unknown = 1,\r\n    InitInterfaceNotFound = 105,\r\n}\r\n\r\ntype EvRInitError = i32;\r\nconst VR_INIT_ERROR_NONE: EvRInitError = 0;\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nstruct DriverPose {\r\n    _reserved: [u8; 128],\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREventHapticVibration {\r\n    container_handle: u64,\r\n    component_handle: u64,\r\n    duration_seconds: f32,\r\n    frequency: f32,\r\n    amplitude: f32,\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nunion VREventData {\r\n    haptic_vibration: VREventHapticVibration,\r\n    reserved: [u8; 48],\r\n}\r\n\r\nimpl Default for VREventData {\r\n    fn default() -> Self {\r\n        Self { reserved: [0; 48] }\r\n    }\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREvent {\r\n    event_type: u32,\r\n    tracked_device_index: u32,\r\n    event_age_seconds: f32,\r\n    data: VREventData,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContextVTable {\r\n    get_generic_interface:\r\n        extern \"C\" fn(*mut DriverContext, *const c_char, *mut EvRInitError) -> *mut c_void,\r\n    get_driver_handle: extern \"C\" fn(*mut DriverContext) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContext {\r\n    vtable: *const DriverContextVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHostVTable {\r\n    tracked_device_added:\r\n        extern \"C\" fn(*mut ServerDriverHost, *const c_char, i32, *mut TrackedDeviceServerDriver) -> bool,\r\n    tracked_device_pose_updated: extern \"C\" fn(*mut ServerDriverHost, u32, *const DriverPose, u32),\r\n    vsync_event: extern \"C\" fn(*mut ServerDriverHost, f64),\r\n    vendor_specific_event: extern \"C\" fn(*mut ServerDriverHost, u32, u32, *const VREventData, f64),\r\n    is_exiting: extern \"C\" fn(*mut ServerDriverHost) -> bool,\r\n    poll_next_event: extern \"C\" fn(*mut ServerDriverHost, *mut VREvent, u32) -> bool,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHost {\r\n    vtable: *const ServerDriverHostVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverInputVTable {\r\n    create_boolean_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n    update_boolean_component: extern \"C\" fn(*mut DriverInput, u64, bool, f64) -> i32,\r\n    create_scalar_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64, i32, i32) -> i32,\r\n    update_scalar_component: extern \"C\" fn(*mut DriverInput, u64, f32, f64) -> i32,\r\n    create_haptic_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverInput {\r\n    vtable: *const DriverInputVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct PropertiesVTable {\r\n    read_property_batch: extern \"C\" fn(*mut Properties, u64, *mut c_void, u32) -> i32,\r\n    write_property_batch: extern \"C\" fn(*mut Properties, u64, *mut PropertyWrite, u32) -> i32,\r\n    get_prop_error_name_from_enum: extern \"C\" fn(*mut Properties, i32) -> *const c_char,\r\n    tracked_device_to_property_container: extern \"C\" fn(*mut Properties, u32) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct Properties {\r\n    vtable: *const PropertiesVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct PropertyWrite {\r\n    prop: u32,\r\n    write_type: i32,\r\n    set_error: i32,\r\n    pv_buffer: *mut c_void,\r\n    un_buffer_size: u32,\r\n    un_tag: u32,\r\n    e_error: i32,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerTrackedDeviceProviderVTable {\r\n    init: extern \"C\" fn(*mut ServerTrackedDeviceProvider, *mut c_void) -> EvRInitError,\r\n    cleanup: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    get_interface_versions:\r\n        extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> *const *const c_char,\r\n    run_frame: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    should_block_standby_mode: extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> bool,\r\n    enter_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    leave_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n}\r\n\r\n#[repr(C)]\r\nstruct TrackedDeviceServerDriverVTable {\r\n    activate: extern \"C\" fn(*mut TrackedDeviceServerDriver, u32) -> EvRInitError,\r\n    deactivate: extern \"C\" fn(*mut TrackedDeviceServerDriver),\r\n    enter_standby: extern \"C\" fn(*mut TrackedDeviceServerDriver),\r\n    get_component:\r\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char) -> *mut c_void,\r\n    debug_request:\r\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char, *mut c_char, u32),\r\n    get_pose: extern \"C\" fn(*mut TrackedDeviceServerDriver) -> DriverPose,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerTrackedDeviceProvider {\r\n    vtable: *const ServerTrackedDeviceProviderVTable,\r\n}\r\nunsafe impl Sync for ServerTrackedDeviceProvider {}\r\n\r\n#[repr(C)]\r\nstruct TrackedDeviceServerDriver {\r\n    vtable: *const TrackedDeviceServerDriverVTable,\r\n}\r\nunsafe impl Sync for TrackedDeviceServerDriver {}\r\n\r\n#[repr(C)]\r\nstruct InterfaceVersions([*const c_char; 10]);\r\nunsafe impl Sync for InterfaceVersions {}\r\n\r\nstatic HOST_CONTEXT: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DEVICE_INDEX: AtomicU32 = AtomicU32::new(u32::MAX);\r\nstatic DEVICE_ACTIVE: AtomicBool = AtomicBool::new(false);\r\nstatic DEVICE_REGISTERED: AtomicBool = AtomicBool::new(false);\r\n\r\nstatic SERVER_HOST_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DRIVER_INPUT_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic PROPERTIES_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\n\r\nstatic DEVICE_CONTAINER: AtomicU64 = AtomicU64::new(0);\r\nstatic HAPTIC_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\n\r\nconst PROVIDER_IFACE_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_004\\0\";\r\nconst PROVIDER_IFACE_ALT_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_005\\0\";\r\nconst DEVICE_IFACE_CSTR: &[u8] = b\"ITrackedDeviceServerDriver_005\\0\";\r\n\r\nconst IVR_PROPERTIES_CSTR: &[u8] = b\"IVRProperties_001\\0\";\r\nconst IVR_DRIVER_INPUT_CSTR: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_SERVER_DRIVER_HOST_CSTR: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\n\r\nconst IVR_SETTINGS_CSTR: &[u8] = b\"IVRSettings_003\\0\";\r\nconst IVR_DRIVER_MANAGER_CSTR: &[u8] = b\"IVRDriverManager_001\\0\";\r\nconst IVR_RESOURCES_CSTR: &[u8] = b\"IVRResources_001\\0\";\r\n\r\nstatic INTERFACE_VERSIONS: InterfaceVersions = InterfaceVersions([\r\n    IVR_SETTINGS_CSTR.as_ptr().cast(),\r\n    DEVICE_IFACE_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_CSTR.as_ptr().cast(),\r\n    IVR_PROPERTIES_CSTR.as_ptr().cast(),\r\n    IVR_SERVER_DRIVER_HOST_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_MANAGER_CSTR.as_ptr().cast(),\r\n    IVR_RESOURCES_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_INPUT_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_ALT_CSTR.as_ptr().cast(),\r\n    core::ptr::null(),\r\n]);\r\n\r\nfn get_server_host() -> Option<*mut ServerDriverHost> {\r\n    let ptr = SERVER_HOST_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_driver_input() -> Option<*mut DriverInput> {\r\n    let ptr = DRIVER_INPUT_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_properties() -> Option<*mut Properties> {\r\n    let ptr = PROPERTIES_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn write_string_property(props: *mut Properties, container: u64, prop: u32, value_nul: &'static [u8]) {\r\n    if props.is_null() || value_nul.is_empty() {\r\n        return;\r\n    }\r\n\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: value_nul.as_ptr().cast_mut().cast(),\r\n        un_buffer_size: value_nul.len() as u32,\r\n        un_tag: K_UN_STRING_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn register_tracked_device_if_needed() {\r\n    if DEVICE_REGISTERED.load(Ordering::SeqCst) {\r\n        return;\r\n    }\r\n\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    let added = unsafe {\r\n        ((*(*host).vtable).tracked_device_added)(\r\n            host,\r\n            DEVICE_SERIAL_CSTR.as_ptr().cast(),\r\n            TRACKED_DEVICE_CLASS_CONTROLLER,\r\n            (&DEVICE as *const TrackedDeviceServerDriver).cast_mut(),\r\n        )\r\n    };\r\n\r\n    if added {\r\n        DEVICE_REGISTERED.store(true, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\nfn resolve_driver_interfaces(driver_context: *mut c_void) -> bool {\r\n    if driver_context.is_null() {\r\n        return false;\r\n    }\r\n\r\n    let ctx = driver_context.cast::<DriverContext>();\r\n    let mut err = VR_INIT_ERROR_NONE;\r\n\r\n    let get_iface = unsafe { (*(*ctx).vtable).get_generic_interface };\r\n\r\n    let host = get_iface(\r\n        ctx,\r\n        IVR_SERVER_DRIVER_HOST_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let driver_input = get_iface(\r\n        ctx,\r\n        IVR_DRIVER_INPUT_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let properties = get_iface(\r\n        ctx,\r\n        IVR_PROPERTIES_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    SERVER_HOST_IFACE.store(host, Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(driver_input, Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(properties, Ordering::SeqCst);\r\n\r\n    !host.is_null() && !driver_input.is_null() && !properties.is_null()\r\n}\r\n\r\nfn route_haptic_events() {\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    loop {\r\n        let mut event = VREvent::default();\r\n        let ok = unsafe {\r\n            ((*(*host).vtable).poll_next_event)(\r\n                host,\r\n                &mut event as *mut VREvent,\r\n                core::mem::size_of::<VREvent>() as u32,\r\n            )\r\n        };\r\n\r\n        if !ok {\r\n            break;\r\n        }\r\n\r\n        if event.event_type != VREVENT_INPUT_HAPTIC_VIBRATION {\r\n            continue;\r\n        }\r\n\r\n        let hv = unsafe { event.data.haptic_vibration };\r\n\r\n        let container = DEVICE_CONTAINER.load(Ordering::SeqCst);\r\n        if container != 0 && hv.container_handle != 0 && hv.container_handle != container {\r\n            continue;\r\n        }\r\n\r\n        let haptic_component = HAPTIC_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n        if haptic_component != 0 && hv.component_handle != 0 && hv.component_handle != haptic_component {\r\n            continue;\r\n        }\r\n\r\n        haptics::handle_haptic_request(haptics::HapticVibrationRequest {\r\n            amplitude: hv.amplitude,\r\n            duration_seconds: hv.duration_seconds,\r\n            frequency: hv.frequency,\r\n        });\r\n    }\r\n}\r\n\r\nextern \"C\" fn provider_init(\r\n    _this: *mut ServerTrackedDeviceProvider,\r\n    driver_context: *mut c_void,\r\n) -> EvRInitError {\r\n    HOST_CONTEXT.store(driver_context, Ordering::SeqCst);\r\n\r\n    if !resolve_driver_interfaces(driver_context) {\r\n        return VR_INIT_ERROR_DRIVER_FAILED;\r\n    }\r\n\r\n    register_tracked_device_if_needed();\r\n\r\n    VR_INIT_ERROR_NONE\r\n}\r\n\r\nextern \"C\" fn provider_cleanup(_this: *mut ServerTrackedDeviceProvider) {\r\n    HOST_CONTEXT.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\r\n    DEVICE_REGISTERED.store(false, Ordering::SeqCst);\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    SERVER_HOST_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn provider_get_interface_versions(\r\n    _this: *mut ServerTrackedDeviceProvider,\r\n) -> *const *const c_char {\r\n    INTERFACE_VERSIONS.0.as_ptr()\r\n}\r\n\r\nextern \"C\" fn provider_run_frame(_this: *mut ServerTrackedDeviceProvider) {\r\n    route_haptic_events();\r\n    haptics::on_frame();\r\n}\r\n\r\nextern \"C\" fn provider_should_block_standby_mode(_this: *mut ServerTrackedDeviceProvider) -> bool {\r\n    false\r\n}\r\n\r\nextern \"C\" fn provider_enter_standby(_this: *mut ServerTrackedDeviceProvider) {}\r\n\r\nextern \"C\" fn provider_leave_standby(_this: *mut ServerTrackedDeviceProvider) {}\r\n\r\nextern \"C\" fn device_activate(_this: *mut TrackedDeviceServerDriver, object_id: u32) -> EvRInitError {\r\n    DEVICE_INDEX.store(object_id, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(true, Ordering::SeqCst);\r\n\r\n    let Some(props) = get_properties() else {\r\n        return VR_INIT_ERROR_DRIVER_FAILED;\r\n    };\r\n\r\n    let container = unsafe { ((*(*props).vtable).tracked_device_to_property_container)(props, object_id) };\r\n    DEVICE_CONTAINER.store(container, Ordering::SeqCst);\r\n\r\n    write_string_property(props, container, PROP_MODEL_NUMBER_STRING, DEVICE_MODEL_CSTR);\r\n    write_string_property(props, container, PROP_SERIAL_NUMBER_STRING, DEVICE_SERIAL_CSTR);\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_INPUT_PROFILE_PATH_STRING,\r\n        DEVICE_INPUT_PROFILE_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_CONTROLLER_TYPE_STRING,\r\n        DEVICE_CONTROLLER_TYPE_CSTR,\r\n    );\r\n\r\n    if let Some(input) = get_driver_input() {\r\n        let mut handle = 0_u64;\r\n        let rc = unsafe {\r\n            ((*(*input).vtable).create_haptic_component)(\r\n                input,\r\n                container,\r\n                DEVICE_HAPTIC_PATH_CSTR.as_ptr().cast(),\r\n                &mut handle as *mut u64,\r\n            )\r\n        };\r\n\r\n        if rc == 0 {\r\n            HAPTIC_COMPONENT_HANDLE.store(handle, Ordering::SeqCst);\r\n        }\r\n    }\r\n\r\n    VR_INIT_ERROR_NONE\r\n}\r\n\r\nextern \"C\" fn device_deactivate(_this: *mut TrackedDeviceServerDriver) {\r\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn device_enter_standby(_this: *mut TrackedDeviceServerDriver) {\r\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn device_get_component(\r\n    _this: *mut TrackedDeviceServerDriver,\r\n    _component_name: *const c_char,\r\n) -> *mut c_void {\r\n    core::ptr::null_mut()\r\n}\r\n\r\nextern \"C\" fn device_debug_request(\r\n    _this: *mut TrackedDeviceServerDriver,\r\n    _request: *const c_char,\r\n    response: *mut c_char,\r\n    response_size: u32,\r\n) {\r\n    if response.is_null() || response_size == 0 {\r\n        return;\r\n    }\r\n    unsafe {\r\n        *response = 0;\r\n    }\r\n}\r\n\r\nextern \"C\" fn device_get_pose(_this: *mut TrackedDeviceServerDriver) -> DriverPose {\r\n    DriverPose { _reserved: [0; 128] }\r\n}\r\n\r\nstatic PROVIDER_VTABLE: ServerTrackedDeviceProviderVTable = ServerTrackedDeviceProviderVTable {\r\n    init: provider_init,\r\n    cleanup: provider_cleanup,\r\n    get_interface_versions: provider_get_interface_versions,\r\n    run_frame: provider_run_frame,\r\n    should_block_standby_mode: provider_should_block_standby_mode,\r\n    enter_standby: provider_enter_standby,\r\n    leave_standby: provider_leave_standby,\r\n};\r\n\r\nstatic DEVICE_VTABLE: TrackedDeviceServerDriverVTable = TrackedDeviceServerDriverVTable {\r\n    activate: device_activate,\r\n    deactivate: device_deactivate,\r\n    enter_standby: device_enter_standby,\r\n    get_component: device_get_component,\r\n    debug_request: device_debug_request,\r\n    get_pose: device_get_pose,\r\n};\r\n\r\nstatic PROVIDER: ServerTrackedDeviceProvider = ServerTrackedDeviceProvider {\r\n    vtable: &PROVIDER_VTABLE,\r\n};\r\n\r\nstatic DEVICE: TrackedDeviceServerDriver = TrackedDeviceServerDriver {\r\n    vtable: &DEVICE_VTABLE,\r\n};\r\n\r\nfn set_return_code(out: *mut i32, code: DriverReturnCode) {\r\n    if !out.is_null() {\r\n        unsafe {\r\n            *out = code as i32;\r\n        }\r\n    }\r\n}\r\n\r\nfn read_interface_name(ptr: *const i8) -> Option<&'static str> {\r\n    if ptr.is_null() {\r\n        return None;\r\n    }\r\n\r\n    let cstr = unsafe { CStr::from_ptr(ptr) };\r\n    let Ok(name) = cstr.to_str() else {\r\n        return None;\r\n    };\r\n\r\n    if name == SERVER_PROVIDER_INTERFACE {\r\n        return Some(SERVER_PROVIDER_INTERFACE);\r\n    }\r\n    if name == SERVER_PROVIDER_INTERFACE_ALT {\r\n        return Some(SERVER_PROVIDER_INTERFACE);\r\n    }\r\n    if name == TRACKED_DEVICE_INTERFACE {\r\n        return Some(TRACKED_DEVICE_INTERFACE);\r\n    }\r\n\r\n    None\r\n}\r\n\r\n/// Resolves OpenVR interface requests to static provider/device instances.\r\n///\r\n/// On match, writes `DriverReturnCode::None` to `p_return_code` and returns a\r\n/// stable interface pointer. On failure, writes an error code and returns null.\r\npub fn hmd_driver_factory(\r\n    p_interface_name: *const c_char,\r\n    p_return_code: *mut i32,\r\n) -> *mut c_void {\r\n    let Some(interface_name) = read_interface_name(p_interface_name) else {\r\n        set_return_code(p_return_code, DriverReturnCode::InitInterfaceNotFound);\r\n        return core::ptr::null_mut();\r\n    };\r\n\r\n    if interface_name == SERVER_PROVIDER_INTERFACE {\r\n        set_return_code(p_return_code, DriverReturnCode::None);\r\n        return (&PROVIDER as *const ServerTrackedDeviceProvider)\r\n            .cast_mut()\r\n            .cast();\r\n    }\r\n\r\n    if interface_name == TRACKED_DEVICE_INTERFACE {\r\n        set_return_code(p_return_code, DriverReturnCode::None);\r\n        return (&DEVICE as *const TrackedDeviceServerDriver)\r\n            .cast_mut()\r\n            .cast();\r\n    }\r\n\r\n    set_return_code(p_return_code, DriverReturnCode::Unknown);\r\n    core::ptr::null_mut()\r\n}\r\n","epoch":1526,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/lib.rs::request_cd9235f7-4b6a-4823-ab9f-74e85fd0e64c",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/lib.rs","scheme":"file"},"requestId":"request_cd9235f7-4b6a-4823-ab9f-74e85fd0e64c","content":"//! Big Haptic Driver crate entrypoints.\r\n//!\r\n//! `lib.rs` stays intentionally thin: it exposes C ABI exports and delegates\r\n//! implementation details to focused modules.\r\n\r\nuse core::ffi::c_void;\r\nuse std::os::raw::c_char;\r\n\r\nmod haptics;\r\nmod openvr_scaffold;\r\n\r\npub use haptics::{HapticVibrationRequest, PollHapticCallback};\r\n\r\n/// OpenVR factory entrypoint expected by SteamVR.\r\n///\r\n/// Routes interface-name requests to the internal provider/device singletons\r\n/// and writes an OpenVR-style init return code to `p_return_code`.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn HmdDriverFactory(\r\n    p_interface_name: *const c_char,\r\n    p_return_code: *mut i32,\r\n) -> *mut c_void {\r\n    openvr_scaffold::hmd_driver_factory(p_interface_name, p_return_code)\r\n}\r\n\r\n/// Test-only export for early integration while full OpenVR vtable glue is in progress.\r\n/// Returns 0 on success, non-zero on failure.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_SendAmplitude(amplitude: f32) -> i32 {\r\n    match haptics::send_amplitude(amplitude) {\r\n        Ok(()) => 0,\r\n        Err(_) => 1,\r\n    }\r\n}\r\n\r\n/// Explicit stop command helper for testing and recovery.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_Stop() -> i32 {\r\n    match haptics::send_stop() {\r\n        Ok(()) => 0,\r\n        Err(_) => 1,\r\n    }\r\n}\r\n\r\n/// Temporary bridge: host/OpenVR shim can register a callback polled each frame.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_SetPollHapticCallback(cb: Option<PollHapticCallback>) {\r\n    haptics::set_poll_haptic_callback(cb);\r\n}\r\n\r\n/// Temporary direct injection helper while event wiring is being built.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_InjectHaptic(\r\n    amplitude: f32,\r\n    duration_seconds: f32,\r\n    frequency: f32,\r\n) {\r\n    haptics::handle_haptic_request(HapticVibrationRequest {\r\n        amplitude,\r\n        duration_seconds,\r\n        frequency,\r\n    });\r\n}\r\n","epoch":1529,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_scaffold.rs::request_9bcbe4ab-720b-4660-919b-ab4be1cdb158",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_9bcbe4ab-720b-4660-919b-ab4be1cdb158","content":"//! OpenVR ABI/vtable scaffolding for the Big Haptic Driver.\r\n//!\r\n//! This module owns C ABI exports and interface routing for SteamVR-facing\r\n//! provider/device entry points, while delegating runtime integration\r\n//! (device registration, component setup, and event routing) to\r\n//! `openvr_runtime`.\r\n\r\nuse core::ffi::{c_void, CStr};\r\nuse core::sync::atomic::{AtomicBool, AtomicU32, Ordering};\r\nuse std::os::raw::c_char;\r\n\r\nuse crate::haptics;\r\nuse crate::openvr_runtime;\r\n\r\nconst SERVER_PROVIDER_INTERFACE: &str = \"IServerTrackedDeviceProvider_004\";\r\nconst SERVER_PROVIDER_INTERFACE_ALT: &str = \"IServerTrackedDeviceProvider_005\";\r\nconst TRACKED_DEVICE_INTERFACE: &str = \"ITrackedDeviceServerDriver_005\";\r\nconst VR_INIT_ERROR_DRIVER_FAILED: EvRInitError = 200;\r\n\r\n#[repr(i32)]\r\nenum DriverReturnCode {\r\n    None = 0,\r\n    Unknown = 1,\r\n    InitInterfaceNotFound = 105,\r\n}\r\n\r\ntype EvRInitError = i32;\r\nconst VR_INIT_ERROR_NONE: EvRInitError = 0;\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nstruct DriverPose {\r\n    _reserved: [u8; 128],\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerTrackedDeviceProviderVTable {\r\n    init: extern \"C\" fn(*mut ServerTrackedDeviceProvider, *mut c_void) -> EvRInitError,\r\n    cleanup: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    get_interface_versions:\r\n        extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> *const *const c_char,\r\n    run_frame: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    should_block_standby_mode: extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> bool,\r\n    enter_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    leave_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n}\r\n\r\n#[repr(C)]\r\nstruct TrackedDeviceServerDriverVTable {\r\n    activate: extern \"C\" fn(*mut TrackedDeviceServerDriver, u32) -> EvRInitError,\r\n    deactivate: extern \"C\" fn(*mut TrackedDeviceServerDriver),\r\n    enter_standby: extern \"C\" fn(*mut TrackedDeviceServerDriver),\r\n    get_component:\r\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char) -> *mut c_void,\r\n    debug_request:\r\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char, *mut c_char, u32),\r\n    get_pose: extern \"C\" fn(*mut TrackedDeviceServerDriver) -> DriverPose,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerTrackedDeviceProvider {\r\n    vtable: *const ServerTrackedDeviceProviderVTable,\r\n}\r\nunsafe impl Sync for ServerTrackedDeviceProvider {}\r\n\r\n#[repr(C)]\r\nstruct TrackedDeviceServerDriver {\r\n    vtable: *const TrackedDeviceServerDriverVTable,\r\n}\r\nunsafe impl Sync for TrackedDeviceServerDriver {}\r\n\r\nstatic DEVICE_INDEX: AtomicU32 = AtomicU32::new(u32::MAX);\r\nstatic DEVICE_ACTIVE: AtomicBool = AtomicBool::new(false);\r\n\r\nextern \"C\" fn provider_init(\r\n    _this: *mut ServerTrackedDeviceProvider,\r\n    driver_context: *mut c_void,\r\n) -> EvRInitError {\r\n    let ok = openvr_runtime::initialize(\r\n        driver_context,\r\n        (&DEVICE as *const TrackedDeviceServerDriver).cast_mut().cast(),\r\n    );\r\n    if !ok {\r\n        return VR_INIT_ERROR_DRIVER_FAILED;\r\n    }\r\n\r\n    VR_INIT_ERROR_NONE\r\n}\r\n\r\nextern \"C\" fn provider_cleanup(_this: *mut ServerTrackedDeviceProvider) {\r\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\r\n    openvr_runtime::cleanup();\r\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn provider_get_interface_versions(\r\n    _this: *mut ServerTrackedDeviceProvider,\r\n) -> *const *const c_char {\r\n    openvr_runtime::interface_versions_ptr()\r\n}\r\n\r\nextern \"C\" fn provider_run_frame(_this: *mut ServerTrackedDeviceProvider) {\r\n    openvr_runtime::route_haptic_events();\r\n    haptics::on_frame();\r\n}\r\n\r\nextern \"C\" fn provider_should_block_standby_mode(_this: *mut ServerTrackedDeviceProvider) -> bool {\r\n    false\r\n}\r\n\r\nextern \"C\" fn provider_enter_standby(_this: *mut ServerTrackedDeviceProvider) {}\r\n\r\nextern \"C\" fn provider_leave_standby(_this: *mut ServerTrackedDeviceProvider) {}\r\n\r\nextern \"C\" fn device_activate(_this: *mut TrackedDeviceServerDriver, object_id: u32) -> EvRInitError {\r\n    DEVICE_INDEX.store(object_id, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(true, Ordering::SeqCst);\r\n\r\n    if !openvr_runtime::activate_device(object_id) {\r\n        return VR_INIT_ERROR_DRIVER_FAILED;\r\n    }\r\n\r\n    VR_INIT_ERROR_NONE\r\n}\r\n\r\nextern \"C\" fn device_deactivate(_this: *mut TrackedDeviceServerDriver) {\r\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\r\n    openvr_runtime::deactivate_device();\r\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn device_enter_standby(_this: *mut TrackedDeviceServerDriver) {\r\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn device_get_component(\r\n    _this: *mut TrackedDeviceServerDriver,\r\n    _component_name: *const c_char,\r\n) -> *mut c_void {\r\n    core::ptr::null_mut()\r\n}\r\n\r\nextern \"C\" fn device_debug_request(\r\n    _this: *mut TrackedDeviceServerDriver,\r\n    _request: *const c_char,\r\n    response: *mut c_char,\r\n    response_size: u32,\r\n) {\r\n    if response.is_null() || response_size == 0 {\r\n        return;\r\n    }\r\n    unsafe {\r\n        *response = 0;\r\n    }\r\n}\r\n\r\nextern \"C\" fn device_get_pose(_this: *mut TrackedDeviceServerDriver) -> DriverPose {\r\n    DriverPose { _reserved: [0; 128] }\r\n}\r\n\r\nstatic PROVIDER_VTABLE: ServerTrackedDeviceProviderVTable = ServerTrackedDeviceProviderVTable {\r\n    init: provider_init,\r\n    cleanup: provider_cleanup,\r\n    get_interface_versions: provider_get_interface_versions,\r\n    run_frame: provider_run_frame,\r\n    should_block_standby_mode: provider_should_block_standby_mode,\r\n    enter_standby: provider_enter_standby,\r\n    leave_standby: provider_leave_standby,\r\n};\r\n\r\nstatic DEVICE_VTABLE: TrackedDeviceServerDriverVTable = TrackedDeviceServerDriverVTable {\r\n    activate: device_activate,\r\n    deactivate: device_deactivate,\r\n    enter_standby: device_enter_standby,\r\n    get_component: device_get_component,\r\n    debug_request: device_debug_request,\r\n    get_pose: device_get_pose,\r\n};\r\n\r\nstatic PROVIDER: ServerTrackedDeviceProvider = ServerTrackedDeviceProvider {\r\n    vtable: &PROVIDER_VTABLE,\r\n};\r\n\r\nstatic DEVICE: TrackedDeviceServerDriver = TrackedDeviceServerDriver {\r\n    vtable: &DEVICE_VTABLE,\r\n};\r\n\r\nfn set_return_code(out: *mut i32, code: DriverReturnCode) {\r\n    if !out.is_null() {\r\n        unsafe {\r\n            *out = code as i32;\r\n        }\r\n    }\r\n}\r\n\r\nfn read_interface_name(ptr: *const i8) -> Option<&'static str> {\r\n    if ptr.is_null() {\r\n        return None;\r\n    }\r\n\r\n    let cstr = unsafe { CStr::from_ptr(ptr) };\r\n    let Ok(name) = cstr.to_str() else {\r\n        return None;\r\n    };\r\n\r\n    if name == SERVER_PROVIDER_INTERFACE {\r\n        return Some(SERVER_PROVIDER_INTERFACE);\r\n    }\r\n    if name == SERVER_PROVIDER_INTERFACE_ALT {\r\n        return Some(SERVER_PROVIDER_INTERFACE);\r\n    }\r\n    if name == TRACKED_DEVICE_INTERFACE {\r\n        return Some(TRACKED_DEVICE_INTERFACE);\r\n    }\r\n\r\n    None\r\n}\r\n\r\n/// Resolves OpenVR interface requests to static provider/device instances.\r\n///\r\n/// On match, writes `DriverReturnCode::None` to `p_return_code` and returns a\r\n/// stable interface pointer. On failure, writes an error code and returns null.\r\npub fn hmd_driver_factory(\r\n    p_interface_name: *const c_char,\r\n    p_return_code: *mut i32,\r\n) -> *mut c_void {\r\n    let Some(interface_name) = read_interface_name(p_interface_name) else {\r\n        set_return_code(p_return_code, DriverReturnCode::InitInterfaceNotFound);\r\n        return core::ptr::null_mut();\r\n    };\r\n\r\n    if interface_name == SERVER_PROVIDER_INTERFACE {\r\n        set_return_code(p_return_code, DriverReturnCode::None);\r\n        return (&PROVIDER as *const ServerTrackedDeviceProvider)\r\n            .cast_mut()\r\n            .cast();\r\n    }\r\n\r\n    if interface_name == TRACKED_DEVICE_INTERFACE {\r\n        set_return_code(p_return_code, DriverReturnCode::None);\r\n        return (&DEVICE as *const TrackedDeviceServerDriver)\r\n            .cast_mut()\r\n            .cast();\r\n    }\r\n\r\n    set_return_code(p_return_code, DriverReturnCode::Unknown);\r\n    core::ptr::null_mut()\r\n}\r\n","epoch":1533,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs::request_9bcbe4ab-720b-4660-919b-ab4be1cdb158",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\openvr_runtime.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","scheme":"file"},"requestId":"request_9bcbe4ab-720b-4660-919b-ab4be1cdb158","content":"//! OpenVR runtime integration helpers.\r\n//!\r\n//! This module owns driver-context interface resolution, tracked-device\r\n//! registration, component/property setup, and OpenVR haptic event routing.\r\n\r\nuse core::ffi::c_void;\r\nuse core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU64, Ordering};\r\nuse std::os::raw::c_char;\r\n\r\nuse crate::haptics;\r\n\r\ntype EvRInitError = i32;\r\n\r\nconst IVR_SERVER_DRIVER_HOST_VERSION: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\nconst IVR_DRIVER_INPUT_VERSION: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_PROPERTIES_VERSION: &[u8] = b\"IVRProperties_001\\0\";\r\n\r\nconst TRACKED_DEVICE_CLASS_CONTROLLER: i32 = 2;\r\nconst VREVENT_INPUT_HAPTIC_VIBRATION: u32 = 1700;\r\n\r\nconst PROP_MODEL_NUMBER_STRING: u32 = 1001;\r\nconst PROP_SERIAL_NUMBER_STRING: u32 = 1002;\r\nconst PROP_INPUT_PROFILE_PATH_STRING: u32 = 1037;\r\nconst PROP_CONTROLLER_TYPE_STRING: u32 = 7000;\r\n\r\nconst K_UN_STRING_PROPERTY_TAG: u32 = 5;\r\nconst PROPERTY_WRITE_SET: i32 = 0;\r\n\r\nconst DEVICE_SERIAL_CSTR: &[u8] = b\"qtpy-samd21-haptic-001\\0\";\r\nconst DEVICE_MODEL_CSTR: &[u8] = b\"QT Py SAMD21 Haptic\\0\";\r\nconst DEVICE_CONTROLLER_TYPE_CSTR: &[u8] = b\"big_haptic_driver\\0\";\r\nconst DEVICE_INPUT_PROFILE_CSTR: &[u8] = b\"{big_haptic_driver}/input/big_haptic_profile.json\\0\";\r\nconst DEVICE_HAPTIC_PATH_CSTR: &[u8] = b\"/output/haptic\\0\";\r\n\r\nconst PROVIDER_IFACE_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_004\\0\";\r\nconst PROVIDER_IFACE_ALT_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_005\\0\";\r\nconst DEVICE_IFACE_CSTR: &[u8] = b\"ITrackedDeviceServerDriver_005\\0\";\r\n\r\nconst IVR_PROPERTIES_CSTR: &[u8] = b\"IVRProperties_001\\0\";\r\nconst IVR_DRIVER_INPUT_CSTR: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_SERVER_DRIVER_HOST_CSTR: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\n\r\nconst IVR_SETTINGS_CSTR: &[u8] = b\"IVRSettings_003\\0\";\r\nconst IVR_DRIVER_MANAGER_CSTR: &[u8] = b\"IVRDriverManager_001\\0\";\r\nconst IVR_RESOURCES_CSTR: &[u8] = b\"IVRResources_001\\0\";\r\n\r\n#[repr(C)]\r\nstruct InterfaceVersions([*const c_char; 10]);\r\nunsafe impl Sync for InterfaceVersions {}\r\n\r\nstatic INTERFACE_VERSIONS: InterfaceVersions = InterfaceVersions([\r\n    IVR_SETTINGS_CSTR.as_ptr().cast(),\r\n    DEVICE_IFACE_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_CSTR.as_ptr().cast(),\r\n    IVR_PROPERTIES_CSTR.as_ptr().cast(),\r\n    IVR_SERVER_DRIVER_HOST_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_MANAGER_CSTR.as_ptr().cast(),\r\n    IVR_RESOURCES_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_INPUT_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_ALT_CSTR.as_ptr().cast(),\r\n    core::ptr::null(),\r\n]);\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREventHapticVibration {\r\n    container_handle: u64,\r\n    component_handle: u64,\r\n    duration_seconds: f32,\r\n    frequency: f32,\r\n    amplitude: f32,\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nunion VREventData {\r\n    haptic_vibration: VREventHapticVibration,\r\n    reserved: [u8; 48],\r\n}\r\n\r\nimpl Default for VREventData {\r\n    fn default() -> Self {\r\n        Self { reserved: [0; 48] }\r\n    }\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREvent {\r\n    event_type: u32,\r\n    tracked_device_index: u32,\r\n    event_age_seconds: f32,\r\n    data: VREventData,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContextVTable {\r\n    get_generic_interface:\r\n        extern \"C\" fn(*mut DriverContext, *const c_char, *mut EvRInitError) -> *mut c_void,\r\n    get_driver_handle: extern \"C\" fn(*mut DriverContext) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContext {\r\n    vtable: *const DriverContextVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHostVTable {\r\n    tracked_device_added:\r\n        extern \"C\" fn(*mut ServerDriverHost, *const c_char, i32, *mut c_void) -> bool,\r\n    tracked_device_pose_updated: extern \"C\" fn(*mut ServerDriverHost, u32, *const c_void, u32),\r\n    vsync_event: extern \"C\" fn(*mut ServerDriverHost, f64),\r\n    vendor_specific_event: extern \"C\" fn(*mut ServerDriverHost, u32, u32, *const VREventData, f64),\r\n    is_exiting: extern \"C\" fn(*mut ServerDriverHost) -> bool,\r\n    poll_next_event: extern \"C\" fn(*mut ServerDriverHost, *mut VREvent, u32) -> bool,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHost {\r\n    vtable: *const ServerDriverHostVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverInputVTable {\r\n    create_boolean_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n    update_boolean_component: extern \"C\" fn(*mut DriverInput, u64, bool, f64) -> i32,\r\n    create_scalar_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64, i32, i32) -> i32,\r\n    update_scalar_component: extern \"C\" fn(*mut DriverInput, u64, f32, f64) -> i32,\r\n    create_haptic_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverInput {\r\n    vtable: *const DriverInputVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct PropertiesVTable {\r\n    read_property_batch: extern \"C\" fn(*mut Properties, u64, *mut c_void, u32) -> i32,\r\n    write_property_batch: extern \"C\" fn(*mut Properties, u64, *mut PropertyWrite, u32) -> i32,\r\n    get_prop_error_name_from_enum: extern \"C\" fn(*mut Properties, i32) -> *const c_char,\r\n    tracked_device_to_property_container: extern \"C\" fn(*mut Properties, u32) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct Properties {\r\n    vtable: *const PropertiesVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct PropertyWrite {\r\n    prop: u32,\r\n    write_type: i32,\r\n    set_error: i32,\r\n    pv_buffer: *mut c_void,\r\n    un_buffer_size: u32,\r\n    un_tag: u32,\r\n    e_error: i32,\r\n}\r\n\r\nstatic DEVICE_REGISTERED: AtomicBool = AtomicBool::new(false);\r\n\r\nstatic SERVER_HOST_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DRIVER_INPUT_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic PROPERTIES_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\n\r\nstatic DEVICE_CONTAINER: AtomicU64 = AtomicU64::new(0);\r\nstatic HAPTIC_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\n\r\npub fn interface_versions_ptr() -> *const *const c_char {\r\n    INTERFACE_VERSIONS.0.as_ptr()\r\n}\r\n\r\nfn get_server_host() -> Option<*mut ServerDriverHost> {\r\n    let ptr = SERVER_HOST_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_driver_input() -> Option<*mut DriverInput> {\r\n    let ptr = DRIVER_INPUT_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_properties() -> Option<*mut Properties> {\r\n    let ptr = PROPERTIES_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn write_string_property(props: *mut Properties, container: u64, prop: u32, value_nul: &'static [u8]) {\r\n    if props.is_null() || value_nul.is_empty() {\r\n        return;\r\n    }\r\n\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: value_nul.as_ptr().cast_mut().cast(),\r\n        un_buffer_size: value_nul.len() as u32,\r\n        un_tag: K_UN_STRING_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn register_tracked_device_if_needed(device_ptr: *mut c_void) {\r\n    if DEVICE_REGISTERED.load(Ordering::SeqCst) {\r\n        return;\r\n    }\r\n\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    let added = unsafe {\r\n        ((*(*host).vtable).tracked_device_added)(\r\n            host,\r\n            DEVICE_SERIAL_CSTR.as_ptr().cast(),\r\n            TRACKED_DEVICE_CLASS_CONTROLLER,\r\n            device_ptr,\r\n        )\r\n    };\r\n\r\n    if added {\r\n        DEVICE_REGISTERED.store(true, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\npub fn initialize(driver_context: *mut c_void, device_ptr: *mut c_void) -> bool {\r\n    if driver_context.is_null() {\r\n        return false;\r\n    }\r\n\r\n    let ctx = driver_context.cast::<DriverContext>();\r\n    let mut err = 0;\r\n\r\n    let get_iface = unsafe { (*(*ctx).vtable).get_generic_interface };\r\n\r\n    let host = get_iface(\r\n        ctx,\r\n        IVR_SERVER_DRIVER_HOST_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let driver_input = get_iface(\r\n        ctx,\r\n        IVR_DRIVER_INPUT_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let properties = get_iface(\r\n        ctx,\r\n        IVR_PROPERTIES_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    SERVER_HOST_IFACE.store(host, Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(driver_input, Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(properties, Ordering::SeqCst);\r\n\r\n    let ok = !host.is_null() && !driver_input.is_null() && !properties.is_null();\r\n    if !ok {\r\n        return false;\r\n    }\r\n\r\n    register_tracked_device_if_needed(device_ptr);\r\n    true\r\n}\r\n\r\npub fn cleanup() {\r\n    DEVICE_REGISTERED.store(false, Ordering::SeqCst);\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    SERVER_HOST_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n}\r\n\r\npub fn route_haptic_events() {\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    loop {\r\n        let mut event = VREvent::default();\r\n        let ok = unsafe {\r\n            ((*(*host).vtable).poll_next_event)(\r\n                host,\r\n                &mut event as *mut VREvent,\r\n                core::mem::size_of::<VREvent>() as u32,\r\n            )\r\n        };\r\n\r\n        if !ok {\r\n            break;\r\n        }\r\n\r\n        if event.event_type != VREVENT_INPUT_HAPTIC_VIBRATION {\r\n            continue;\r\n        }\r\n\r\n        let hv = unsafe { event.data.haptic_vibration };\r\n\r\n        let container = DEVICE_CONTAINER.load(Ordering::SeqCst);\r\n        if container != 0 && hv.container_handle != 0 && hv.container_handle != container {\r\n            continue;\r\n        }\r\n\r\n        let haptic_component = HAPTIC_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n        if haptic_component != 0 && hv.component_handle != 0 && hv.component_handle != haptic_component {\r\n            continue;\r\n        }\r\n\r\n        haptics::handle_haptic_request(haptics::HapticVibrationRequest {\r\n            amplitude: hv.amplitude,\r\n            duration_seconds: hv.duration_seconds,\r\n            frequency: hv.frequency,\r\n        });\r\n    }\r\n}\r\n\r\npub fn activate_device(object_id: u32) -> bool {\r\n    let Some(props) = get_properties() else {\r\n        return false;\r\n    };\r\n\r\n    let container = unsafe { ((*(*props).vtable).tracked_device_to_property_container)(props, object_id) };\r\n    DEVICE_CONTAINER.store(container, Ordering::SeqCst);\r\n\r\n    write_string_property(props, container, PROP_MODEL_NUMBER_STRING, DEVICE_MODEL_CSTR);\r\n    write_string_property(props, container, PROP_SERIAL_NUMBER_STRING, DEVICE_SERIAL_CSTR);\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_INPUT_PROFILE_PATH_STRING,\r\n        DEVICE_INPUT_PROFILE_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_CONTROLLER_TYPE_STRING,\r\n        DEVICE_CONTROLLER_TYPE_CSTR,\r\n    );\r\n\r\n    if let Some(input) = get_driver_input() {\r\n        let mut handle = 0_u64;\r\n        let rc = unsafe {\r\n            ((*(*input).vtable).create_haptic_component)(\r\n                input,\r\n                container,\r\n                DEVICE_HAPTIC_PATH_CSTR.as_ptr().cast(),\r\n                &mut handle as *mut u64,\r\n            )\r\n        };\r\n\r\n        if rc == 0 {\r\n            HAPTIC_COMPONENT_HANDLE.store(handle, Ordering::SeqCst);\r\n        }\r\n    }\r\n\r\n    true\r\n}\r\n\r\npub fn deactivate_device() {\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n}\r\n","epoch":1536,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/driver.vrdrivermanifest::request_2b97d49c-137d-4738-aaf4-66378596ea73",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\driver.vrdrivermanifest","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/driver.vrdrivermanifest","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/driver.vrdrivermanifest","scheme":"file"},"requestId":"request_2b97d49c-137d-4738-aaf4-66378596ea73","content":"{\r\n  \"always_activate\": true,\r\n  \"name\": \"big_haptic_driver\",\r\n  \"resource_only\": false,\r\n  \"resource_directory\": \"resources\"\r\n}\r\n","epoch":1540,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs::request_2b97d49c-137d-4738-aaf4-66378596ea73",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\openvr_runtime.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","scheme":"file"},"requestId":"request_2b97d49c-137d-4738-aaf4-66378596ea73","content":"//! OpenVR runtime integration helpers.\r\n//!\r\n//! This module owns driver-context interface resolution, tracked-device\r\n//! registration, component/property setup, and OpenVR haptic event routing.\r\n\r\nuse core::ffi::c_void;\r\nuse core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU64, Ordering};\r\nuse std::os::raw::c_char;\r\n\r\nuse crate::haptics;\r\n\r\ntype EvRInitError = i32;\r\n\r\nconst IVR_SERVER_DRIVER_HOST_VERSION: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\nconst IVR_DRIVER_INPUT_VERSION: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_PROPERTIES_VERSION: &[u8] = b\"IVRProperties_001\\0\";\r\n\r\nconst TRACKED_DEVICE_CLASS_CONTROLLER: i32 = 2;\r\nconst VREVENT_INPUT_HAPTIC_VIBRATION: u32 = 1700;\r\n\r\nconst PROP_MODEL_NUMBER_STRING: u32 = 1001;\r\nconst PROP_SERIAL_NUMBER_STRING: u32 = 1002;\r\nconst PROP_INPUT_PROFILE_PATH_STRING: u32 = 1037;\r\nconst PROP_CONTROLLER_TYPE_STRING: u32 = 7000;\r\n\r\nconst K_UN_STRING_PROPERTY_TAG: u32 = 5;\r\nconst PROPERTY_WRITE_SET: i32 = 0;\r\n\r\nconst DEVICE_SERIAL_CSTR: &[u8] = b\"qtpy-samd21-haptic-001\\0\";\r\nconst DEVICE_MODEL_CSTR: &[u8] = b\"QT Py SAMD21 Haptic\\0\";\r\nconst DEVICE_CONTROLLER_TYPE_CSTR: &[u8] = b\"big_haptic_driver\\0\";\r\nconst DEVICE_INPUT_PROFILE_CSTR: &[u8] = b\"{big_haptic_driver}/input/big_haptic_profile.json\\0\";\r\nconst DEVICE_HAPTIC_PATH_CSTR: &[u8] = b\"/output/haptic\\0\";\r\n\r\nconst PROVIDER_IFACE_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_004\\0\";\r\nconst PROVIDER_IFACE_ALT_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_005\\0\";\r\nconst DEVICE_IFACE_CSTR: &[u8] = b\"ITrackedDeviceServerDriver_005\\0\";\r\n\r\nconst IVR_PROPERTIES_CSTR: &[u8] = b\"IVRProperties_001\\0\";\r\nconst IVR_DRIVER_INPUT_CSTR: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_SERVER_DRIVER_HOST_CSTR: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\n\r\nconst IVR_SETTINGS_CSTR: &[u8] = b\"IVRSettings_003\\0\";\r\nconst IVR_DRIVER_MANAGER_CSTR: &[u8] = b\"IVRDriverManager_001\\0\";\r\nconst IVR_RESOURCES_CSTR: &[u8] = b\"IVRResources_001\\0\";\r\n\r\n#[repr(C)]\r\nstruct InterfaceVersions([*const c_char; 10]);\r\nunsafe impl Sync for InterfaceVersions {}\r\n\r\nstatic INTERFACE_VERSIONS: InterfaceVersions = InterfaceVersions([\r\n    IVR_SETTINGS_CSTR.as_ptr().cast(),\r\n    DEVICE_IFACE_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_CSTR.as_ptr().cast(),\r\n    IVR_PROPERTIES_CSTR.as_ptr().cast(),\r\n    IVR_SERVER_DRIVER_HOST_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_MANAGER_CSTR.as_ptr().cast(),\r\n    IVR_RESOURCES_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_INPUT_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_ALT_CSTR.as_ptr().cast(),\r\n    core::ptr::null(),\r\n]);\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREventHapticVibration {\r\n    container_handle: u64,\r\n    component_handle: u64,\r\n    duration_seconds: f32,\r\n    frequency: f32,\r\n    amplitude: f32,\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nunion VREventData {\r\n    haptic_vibration: VREventHapticVibration,\r\n    reserved: [u8; 48],\r\n}\r\n\r\nimpl Default for VREventData {\r\n    fn default() -> Self {\r\n        Self { reserved: [0; 48] }\r\n    }\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREvent {\r\n    event_type: u32,\r\n    tracked_device_index: u32,\r\n    event_age_seconds: f32,\r\n    data: VREventData,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContextVTable {\r\n    get_generic_interface:\r\n        extern \"C\" fn(*mut DriverContext, *const c_char, *mut EvRInitError) -> *mut c_void,\r\n    get_driver_handle: extern \"C\" fn(*mut DriverContext) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContext {\r\n    vtable: *const DriverContextVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHostVTable {\r\n    tracked_device_added:\r\n        extern \"C\" fn(*mut ServerDriverHost, *const c_char, i32, *mut c_void) -> bool,\r\n    tracked_device_pose_updated: extern \"C\" fn(*mut ServerDriverHost, u32, *const c_void, u32),\r\n    vsync_event: extern \"C\" fn(*mut ServerDriverHost, f64),\r\n    vendor_specific_event: extern \"C\" fn(*mut ServerDriverHost, u32, u32, *const VREventData, f64),\r\n    is_exiting: extern \"C\" fn(*mut ServerDriverHost) -> bool,\r\n    poll_next_event: extern \"C\" fn(*mut ServerDriverHost, *mut VREvent, u32) -> bool,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHost {\r\n    vtable: *const ServerDriverHostVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverInputVTable {\r\n    create_boolean_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n    update_boolean_component: extern \"C\" fn(*mut DriverInput, u64, bool, f64) -> i32,\r\n    create_scalar_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64, i32, i32) -> i32,\r\n    update_scalar_component: extern \"C\" fn(*mut DriverInput, u64, f32, f64) -> i32,\r\n    create_haptic_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverInput {\r\n    vtable: *const DriverInputVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct PropertiesVTable {\r\n    read_property_batch: extern \"C\" fn(*mut Properties, u64, *mut c_void, u32) -> i32,\r\n    write_property_batch: extern \"C\" fn(*mut Properties, u64, *mut PropertyWrite, u32) -> i32,\r\n    get_prop_error_name_from_enum: extern \"C\" fn(*mut Properties, i32) -> *const c_char,\r\n    tracked_device_to_property_container: extern \"C\" fn(*mut Properties, u32) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct Properties {\r\n    vtable: *const PropertiesVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct PropertyWrite {\r\n    prop: u32,\r\n    write_type: i32,\r\n    set_error: i32,\r\n    pv_buffer: *mut c_void,\r\n    un_buffer_size: u32,\r\n    un_tag: u32,\r\n    e_error: i32,\r\n}\r\n\r\nstatic DEVICE_REGISTERED: AtomicBool = AtomicBool::new(false);\r\n\r\nstatic SERVER_HOST_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DRIVER_INPUT_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic PROPERTIES_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\n\r\nstatic DEVICE_CONTAINER: AtomicU64 = AtomicU64::new(0);\r\nstatic HAPTIC_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\n\r\npub fn interface_versions_ptr() -> *const *const c_char {\r\n    INTERFACE_VERSIONS.0.as_ptr()\r\n}\r\n\r\nfn get_server_host() -> Option<*mut ServerDriverHost> {\r\n    let ptr = SERVER_HOST_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_driver_input() -> Option<*mut DriverInput> {\r\n    let ptr = DRIVER_INPUT_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_properties() -> Option<*mut Properties> {\r\n    let ptr = PROPERTIES_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn write_string_property(props: *mut Properties, container: u64, prop: u32, value_nul: &'static [u8]) {\r\n    if props.is_null() || value_nul.is_empty() {\r\n        return;\r\n    }\r\n\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: value_nul.as_ptr().cast_mut().cast(),\r\n        un_buffer_size: value_nul.len() as u32,\r\n        un_tag: K_UN_STRING_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn register_tracked_device_if_needed(device_ptr: *mut c_void) {\r\n    if DEVICE_REGISTERED.load(Ordering::SeqCst) {\r\n        return;\r\n    }\r\n\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    let added = unsafe {\r\n        ((*(*host).vtable).tracked_device_added)(\r\n            host,\r\n            DEVICE_SERIAL_CSTR.as_ptr().cast(),\r\n            TRACKED_DEVICE_CLASS_CONTROLLER,\r\n            device_ptr,\r\n        )\r\n    };\r\n\r\n    if added {\r\n        DEVICE_REGISTERED.store(true, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\npub fn initialize(driver_context: *mut c_void, device_ptr: *mut c_void) -> bool {\r\n    if driver_context.is_null() {\r\n        return false;\r\n    }\r\n\r\n    let ctx = driver_context.cast::<DriverContext>();\r\n    let mut err = 0;\r\n\r\n    let get_iface = unsafe { (*(*ctx).vtable).get_generic_interface };\r\n\r\n    let host = get_iface(\r\n        ctx,\r\n        IVR_SERVER_DRIVER_HOST_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let driver_input = get_iface(\r\n        ctx,\r\n        IVR_DRIVER_INPUT_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let properties = get_iface(\r\n        ctx,\r\n        IVR_PROPERTIES_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    SERVER_HOST_IFACE.store(host, Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(driver_input, Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(properties, Ordering::SeqCst);\r\n\r\n    let ok = !host.is_null() && !driver_input.is_null() && !properties.is_null();\r\n    if !ok {\r\n        return false;\r\n    }\r\n\r\n    register_tracked_device_if_needed(device_ptr);\r\n    true\r\n}\r\n\r\npub fn cleanup() {\r\n    DEVICE_REGISTERED.store(false, Ordering::SeqCst);\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    SERVER_HOST_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n}\r\n\r\npub fn route_haptic_events() {\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    loop {\r\n        let mut event = VREvent::default();\r\n        let ok = unsafe {\r\n            ((*(*host).vtable).poll_next_event)(\r\n                host,\r\n                &mut event as *mut VREvent,\r\n                core::mem::size_of::<VREvent>() as u32,\r\n            )\r\n        };\r\n\r\n        if !ok {\r\n            break;\r\n        }\r\n\r\n        if event.event_type != VREVENT_INPUT_HAPTIC_VIBRATION {\r\n            continue;\r\n        }\r\n\r\n        let hv = unsafe { event.data.haptic_vibration };\r\n\r\n        let container = DEVICE_CONTAINER.load(Ordering::SeqCst);\r\n        if container != 0 && hv.container_handle != 0 && hv.container_handle != container {\r\n            continue;\r\n        }\r\n\r\n        let haptic_component = HAPTIC_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n        if haptic_component != 0 && hv.component_handle != 0 && hv.component_handle != haptic_component {\r\n            continue;\r\n        }\r\n\r\n        haptics::handle_haptic_request(haptics::HapticVibrationRequest {\r\n            amplitude: hv.amplitude,\r\n            duration_seconds: hv.duration_seconds,\r\n            frequency: hv.frequency,\r\n        });\r\n    }\r\n}\r\n\r\npub fn push_pose_update(object_id: u32, pose: *const c_void, pose_size: u32) {\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    if pose.is_null() || pose_size == 0 {\r\n        return;\r\n    }\r\n\r\n    unsafe {\r\n        ((*(*host).vtable).tracked_device_pose_updated)(host, object_id, pose, pose_size);\r\n    }\r\n}\r\n\r\npub fn activate_device(object_id: u32) -> bool {\r\n    let Some(props) = get_properties() else {\r\n        return false;\r\n    };\r\n\r\n    let container = unsafe { ((*(*props).vtable).tracked_device_to_property_container)(props, object_id) };\r\n    DEVICE_CONTAINER.store(container, Ordering::SeqCst);\r\n\r\n    write_string_property(props, container, PROP_MODEL_NUMBER_STRING, DEVICE_MODEL_CSTR);\r\n    write_string_property(props, container, PROP_SERIAL_NUMBER_STRING, DEVICE_SERIAL_CSTR);\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_INPUT_PROFILE_PATH_STRING,\r\n        DEVICE_INPUT_PROFILE_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_CONTROLLER_TYPE_STRING,\r\n        DEVICE_CONTROLLER_TYPE_CSTR,\r\n    );\r\n\r\n    if let Some(input) = get_driver_input() {\r\n        let mut handle = 0_u64;\r\n        let rc = unsafe {\r\n            ((*(*input).vtable).create_haptic_component)(\r\n                input,\r\n                container,\r\n                DEVICE_HAPTIC_PATH_CSTR.as_ptr().cast(),\r\n                &mut handle as *mut u64,\r\n            )\r\n        };\r\n\r\n        if rc == 0 {\r\n            HAPTIC_COMPONENT_HANDLE.store(handle, Ordering::SeqCst);\r\n        }\r\n    }\r\n\r\n    true\r\n}\r\n\r\npub fn deactivate_device() {\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n}\r\n","epoch":1543,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs::request_ed61c233-e170-4e17-8961-1cf0caa56eb3",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_ed61c233-e170-4e17-8961-1cf0caa56eb3","content":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse core::sync::atomic::{AtomicU64, Ordering};\r\nuse std::sync::{Mutex, OnceLock};\r\nuse std::time::Instant;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic STOP_DEADLINE_MS: AtomicU64 = AtomicU64::new(0);\r\nstatic START_INSTANT: OnceLock<Instant> = OnceLock::new();\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\n\r\nfn send_packet(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    hid_bridge::HidBridge::new()\r\n        .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    send_packet(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn now_ms() -> u64 {\r\n    let start = START_INSTANT.get_or_init(Instant::now);\r\n    let ms = Instant::now().duration_since(*start).as_millis();\r\n    ms.min(u128::from(u64::MAX)) as u64\r\n}\r\n\r\nfn schedule_stop_after(duration_seconds: f32) {\r\n    if duration_seconds <= 0.0 {\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let duration_ms = (duration_seconds * 1000.0).round().max(1.0) as u64;\r\n    let deadline = now_ms().saturating_add(duration_ms);\r\n    STOP_DEADLINE_MS.store(deadline, Ordering::SeqCst);\r\n}\r\n\r\nfn process_pending_stop() {\r\n    let deadline = STOP_DEADLINE_MS.load(Ordering::SeqCst);\r\n    if deadline == 0 {\r\n        return;\r\n    }\r\n\r\n    if now_ms() >= deadline {\r\n        let _ = send_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        let _ = send_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let _ = send_amplitude(req.amplitude);\r\n    schedule_stop_after(req.duration_seconds);\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n    process_pending_stop();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","epoch":1547,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs::request_3718300f-4171-4bf2-8ae7-b39ae522ec11",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3718300f-4171-4bf2-8ae7-b39ae522ec11","content":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse core::sync::atomic::{AtomicU64, Ordering};\r\nuse std::sync::{Mutex, OnceLock};\r\nuse std::time::Instant;\r\nuse std::time::Duration;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic STOP_DEADLINE_MS: AtomicU64 = AtomicU64::new(0);\r\nstatic STOP_TIMER_TOKEN: AtomicU64 = AtomicU64::new(0);\r\nstatic START_INSTANT: OnceLock<Instant> = OnceLock::new();\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\n\r\nfn send_packet(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    hid_bridge::HidBridge::new()\r\n        .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    send_packet(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn now_ms() -> u64 {\r\n    let start = START_INSTANT.get_or_init(Instant::now);\r\n    let ms = Instant::now().duration_since(*start).as_millis();\r\n    ms.min(u128::from(u64::MAX)) as u64\r\n}\r\n\r\nfn schedule_stop_after(duration_seconds: f32) {\r\n    if duration_seconds <= 0.0 {\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let duration_ms = (duration_seconds * 1000.0).round().max(1.0) as u64;\r\n    let deadline = now_ms().saturating_add(duration_ms);\r\n    STOP_DEADLINE_MS.store(deadline, Ordering::SeqCst);\r\n\r\n    let token = STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst).saturating_add(1);\r\n    std::thread::spawn(move || {\r\n        std::thread::sleep(Duration::from_millis(duration_ms));\r\n\r\n        if STOP_TIMER_TOKEN.load(Ordering::SeqCst) != token {\r\n            return;\r\n        }\r\n\r\n        let _ = send_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    });\r\n}\r\n\r\nfn process_pending_stop() {\r\n    let deadline = STOP_DEADLINE_MS.load(Ordering::SeqCst);\r\n    if deadline == 0 {\r\n        return;\r\n    }\r\n\r\n    if now_ms() >= deadline {\r\n        let _ = send_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        let _ = send_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let _ = send_amplitude(req.amplitude);\r\n    schedule_stop_after(req.duration_seconds);\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n    process_pending_stop();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","epoch":1551,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs::request_bcc1b2c2-e5c6-4c6f-9444-e7f9f5abb7a4",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_bcc1b2c2-e5c6-4c6f-9444-e7f9f5abb7a4","content":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse core::sync::atomic::{AtomicU64, Ordering};\r\nuse std::sync::{Mutex, OnceLock};\r\nuse std::time::Instant;\r\nuse std::time::Duration;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic STOP_DEADLINE_MS: AtomicU64 = AtomicU64::new(0);\r\nstatic STOP_TIMER_TOKEN: AtomicU64 = AtomicU64::new(0);\r\nstatic START_INSTANT: OnceLock<Instant> = OnceLock::new();\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\nstatic ACTIVE_PULSE: Mutex<Option<ActivePulse>> = Mutex::new(None);\r\n\r\n#[derive(Clone, Copy)]\r\nstruct ActivePulse {\r\n    amplitude: f32,\r\n    frequency: f32,\r\n    deadline_ms: u64,\r\n}\r\n\r\nfn send_packet(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    hid_bridge::HidBridge::new()\r\n        .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    send_packet(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn now_ms() -> u64 {\r\n    let start = START_INSTANT.get_or_init(Instant::now);\r\n    let ms = Instant::now().duration_since(*start).as_millis();\r\n    ms.min(u128::from(u64::MAX)) as u64\r\n}\r\n\r\nfn clear_active_pulse() {\r\n    if let Ok(mut guard) = ACTIVE_PULSE.lock() {\r\n        *guard = None;\r\n    }\r\n}\r\n\r\nfn pulse_is_duplicate_while_active(amplitude: f32, frequency: f32, now_ms: u64) -> bool {\r\n    let Ok(guard) = ACTIVE_PULSE.lock() else {\r\n        return false;\r\n    };\r\n\r\n    let Some(active) = *guard else {\r\n        return false;\r\n    };\r\n\r\n    if now_ms >= active.deadline_ms {\r\n        return false;\r\n    }\r\n\r\n    let amp_close = (active.amplitude - amplitude).abs() <= 0.01;\r\n    let freq_close = (active.frequency - frequency).abs() <= 1.0;\r\n    amp_close && freq_close\r\n}\r\n\r\nfn set_active_pulse(amplitude: f32, frequency: f32, deadline_ms: u64) {\r\n    if let Ok(mut guard) = ACTIVE_PULSE.lock() {\r\n        *guard = Some(ActivePulse {\r\n            amplitude,\r\n            frequency,\r\n            deadline_ms,\r\n        });\r\n    }\r\n}\r\n\r\nfn schedule_stop_after(duration_seconds: f32) {\r\n    if duration_seconds <= 0.0 {\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        clear_active_pulse();\r\n        return;\r\n    }\r\n\r\n    let bounded_duration_seconds = duration_seconds.clamp(0.0, 5.0);\r\n    let duration_ms = (bounded_duration_seconds * 1000.0).round().max(1.0) as u64;\r\n    let deadline = now_ms().saturating_add(duration_ms);\r\n    STOP_DEADLINE_MS.store(deadline, Ordering::SeqCst);\r\n\r\n    let token = STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst).saturating_add(1);\r\n    std::thread::spawn(move || {\r\n        std::thread::sleep(Duration::from_millis(duration_ms));\r\n\r\n        if STOP_TIMER_TOKEN.load(Ordering::SeqCst) != token {\r\n            return;\r\n        }\r\n\r\n        let _ = send_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        clear_active_pulse();\r\n    });\r\n}\r\n\r\nfn process_pending_stop() {\r\n    let deadline = STOP_DEADLINE_MS.load(Ordering::SeqCst);\r\n    if deadline == 0 {\r\n        return;\r\n    }\r\n\r\n    if now_ms() >= deadline {\r\n        let _ = send_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        clear_active_pulse();\r\n    }\r\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        let _ = send_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        clear_active_pulse();\r\n        return;\r\n    }\r\n\r\n    let amplitude = req.amplitude.clamp(0.0, 1.0);\r\n    let frequency = req.frequency;\r\n    let now = now_ms();\r\n\r\n    if pulse_is_duplicate_while_active(amplitude, frequency, now) {\r\n        return;\r\n    }\r\n\r\n    let _ = send_amplitude(amplitude);\r\n    schedule_stop_after(req.duration_seconds);\r\n\r\n    let deadline_ms = STOP_DEADLINE_MS.load(Ordering::SeqCst);\r\n    if deadline_ms != 0 {\r\n        set_active_pulse(amplitude, frequency, deadline_ms);\r\n    }\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n    process_pending_stop();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","epoch":1555,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs::request_bcc1b2c2-e5c6-4c6f-9444-e7f9f5abb7a4",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\openvr_runtime.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","scheme":"file"},"requestId":"request_bcc1b2c2-e5c6-4c6f-9444-e7f9f5abb7a4","content":"//! OpenVR runtime integration helpers.\r\n//!\r\n//! This module owns driver-context interface resolution, tracked-device\r\n//! registration, component/property setup, and OpenVR haptic event routing.\r\n\r\nuse core::ffi::c_void;\r\nuse core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU64, Ordering};\r\nuse std::os::raw::c_char;\r\n\r\nuse crate::haptics;\r\n\r\ntype EvRInitError = i32;\r\n\r\nconst IVR_SERVER_DRIVER_HOST_VERSION: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\nconst IVR_DRIVER_INPUT_VERSION: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_PROPERTIES_VERSION: &[u8] = b\"IVRProperties_001\\0\";\r\n\r\nconst TRACKED_DEVICE_CLASS_CONTROLLER: i32 = 2;\r\nconst VREVENT_INPUT_HAPTIC_VIBRATION: u32 = 1700;\r\n\r\nconst PROP_MODEL_NUMBER_STRING: u32 = 1001;\r\nconst PROP_SERIAL_NUMBER_STRING: u32 = 1002;\r\nconst PROP_TRACKING_SYSTEM_NAME_STRING: u32 = 1000;\r\nconst PROP_REGISTERED_DEVICE_TYPE_STRING: u32 = 1036;\r\nconst PROP_INPUT_PROFILE_PATH_STRING: u32 = 1037;\r\nconst PROP_CONTROLLER_TYPE_STRING: u32 = 7000;\r\nconst PROP_CONTROLLER_ROLE_HINT_INT32: u32 = 3007;\r\nconst PROP_HAS_CONTROLLER_COMPONENT_BOOL: u32 = 6003;\r\n\r\nconst K_UN_BOOL_PROPERTY_TAG: u32 = 4;\r\nconst K_UN_INT32_PROPERTY_TAG: u32 = 2;\r\nconst K_UN_STRING_PROPERTY_TAG: u32 = 5;\r\nconst PROPERTY_WRITE_SET: i32 = 0;\r\n\r\nconst DEVICE_SERIAL_CSTR: &[u8] = b\"qtpy-samd21-haptic-001\\0\";\r\nconst DEVICE_MODEL_CSTR: &[u8] = b\"QT Py SAMD21 Haptic\\0\";\r\nconst DEVICE_TRACKING_SYSTEM_NAME_CSTR: &[u8] = b\"big_haptic_driver\\0\";\r\nconst DEVICE_REGISTERED_TYPE_CSTR: &[u8] = b\"big_haptic_driver/controller\\0\";\r\nconst DEVICE_CONTROLLER_TYPE_CSTR: &[u8] = b\"big_haptic_driver\\0\";\r\nconst DEVICE_INPUT_PROFILE_CSTR: &[u8] = b\"{big_haptic_driver}/input/big_haptic_profile.json\\0\";\r\nconst DEVICE_HAPTIC_PATH_CSTR: &[u8] = b\"/output/haptic\\0\";\r\n\r\nconst PROVIDER_IFACE_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_004\\0\";\r\nconst PROVIDER_IFACE_ALT_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_005\\0\";\r\nconst DEVICE_IFACE_CSTR: &[u8] = b\"ITrackedDeviceServerDriver_005\\0\";\r\n\r\nconst IVR_PROPERTIES_CSTR: &[u8] = b\"IVRProperties_001\\0\";\r\nconst IVR_DRIVER_INPUT_CSTR: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_SERVER_DRIVER_HOST_CSTR: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\n\r\nconst IVR_SETTINGS_CSTR: &[u8] = b\"IVRSettings_003\\0\";\r\nconst IVR_DRIVER_MANAGER_CSTR: &[u8] = b\"IVRDriverManager_001\\0\";\r\nconst IVR_RESOURCES_CSTR: &[u8] = b\"IVRResources_001\\0\";\r\n\r\n#[repr(C)]\r\nstruct InterfaceVersions([*const c_char; 10]);\r\nunsafe impl Sync for InterfaceVersions {}\r\n\r\nstatic INTERFACE_VERSIONS: InterfaceVersions = InterfaceVersions([\r\n    IVR_SETTINGS_CSTR.as_ptr().cast(),\r\n    DEVICE_IFACE_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_CSTR.as_ptr().cast(),\r\n    IVR_PROPERTIES_CSTR.as_ptr().cast(),\r\n    IVR_SERVER_DRIVER_HOST_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_MANAGER_CSTR.as_ptr().cast(),\r\n    IVR_RESOURCES_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_INPUT_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_ALT_CSTR.as_ptr().cast(),\r\n    core::ptr::null(),\r\n]);\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREventHapticVibration {\r\n    container_handle: u64,\r\n    component_handle: u64,\r\n    duration_seconds: f32,\r\n    frequency: f32,\r\n    amplitude: f32,\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nunion VREventData {\r\n    haptic_vibration: VREventHapticVibration,\r\n    reserved: [u8; 48],\r\n}\r\n\r\nimpl Default for VREventData {\r\n    fn default() -> Self {\r\n        Self { reserved: [0; 48] }\r\n    }\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREvent {\r\n    event_type: u32,\r\n    tracked_device_index: u32,\r\n    event_age_seconds: f32,\r\n    data: VREventData,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContextVTable {\r\n    get_generic_interface:\r\n        extern \"C\" fn(*mut DriverContext, *const c_char, *mut EvRInitError) -> *mut c_void,\r\n    get_driver_handle: extern \"C\" fn(*mut DriverContext) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContext {\r\n    vtable: *const DriverContextVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHostVTable {\r\n    tracked_device_added:\r\n        extern \"C\" fn(*mut ServerDriverHost, *const c_char, i32, *mut c_void) -> bool,\r\n    tracked_device_pose_updated: extern \"C\" fn(*mut ServerDriverHost, u32, *const c_void, u32),\r\n    vsync_event: extern \"C\" fn(*mut ServerDriverHost, f64),\r\n    vendor_specific_event: extern \"C\" fn(*mut ServerDriverHost, u32, u32, *const VREventData, f64),\r\n    is_exiting: extern \"C\" fn(*mut ServerDriverHost) -> bool,\r\n    poll_next_event: extern \"C\" fn(*mut ServerDriverHost, *mut VREvent, u32) -> bool,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHost {\r\n    vtable: *const ServerDriverHostVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverInputVTable {\r\n    create_boolean_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n    update_boolean_component: extern \"C\" fn(*mut DriverInput, u64, bool, f64) -> i32,\r\n    create_scalar_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64, i32, i32) -> i32,\r\n    update_scalar_component: extern \"C\" fn(*mut DriverInput, u64, f32, f64) -> i32,\r\n    create_haptic_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverInput {\r\n    vtable: *const DriverInputVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct PropertiesVTable {\r\n    read_property_batch: extern \"C\" fn(*mut Properties, u64, *mut c_void, u32) -> i32,\r\n    write_property_batch: extern \"C\" fn(*mut Properties, u64, *mut PropertyWrite, u32) -> i32,\r\n    get_prop_error_name_from_enum: extern \"C\" fn(*mut Properties, i32) -> *const c_char,\r\n    tracked_device_to_property_container: extern \"C\" fn(*mut Properties, u32) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct Properties {\r\n    vtable: *const PropertiesVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct PropertyWrite {\r\n    prop: u32,\r\n    write_type: i32,\r\n    set_error: i32,\r\n    pv_buffer: *mut c_void,\r\n    un_buffer_size: u32,\r\n    un_tag: u32,\r\n    e_error: i32,\r\n}\r\n\r\nstatic DEVICE_REGISTERED: AtomicBool = AtomicBool::new(false);\r\n\r\nstatic SERVER_HOST_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DRIVER_INPUT_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic PROPERTIES_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\n\r\nstatic DEVICE_CONTAINER: AtomicU64 = AtomicU64::new(0);\r\nstatic HAPTIC_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\n\r\npub fn interface_versions_ptr() -> *const *const c_char {\r\n    INTERFACE_VERSIONS.0.as_ptr()\r\n}\r\n\r\nfn get_server_host() -> Option<*mut ServerDriverHost> {\r\n    let ptr = SERVER_HOST_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_driver_input() -> Option<*mut DriverInput> {\r\n    let ptr = DRIVER_INPUT_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_properties() -> Option<*mut Properties> {\r\n    let ptr = PROPERTIES_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn write_string_property(props: *mut Properties, container: u64, prop: u32, value_nul: &'static [u8]) {\r\n    if props.is_null() || value_nul.is_empty() {\r\n        return;\r\n    }\r\n\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: value_nul.as_ptr().cast_mut().cast(),\r\n        un_buffer_size: value_nul.len() as u32,\r\n        un_tag: K_UN_STRING_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn write_bool_property(props: *mut Properties, container: u64, prop: u32, value: bool) {\r\n    if props.is_null() {\r\n        return;\r\n    }\r\n\r\n    let mut bool_value = value;\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: (&mut bool_value as *mut bool).cast(),\r\n        un_buffer_size: core::mem::size_of::<bool>() as u32,\r\n        un_tag: K_UN_BOOL_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn write_int32_property(props: *mut Properties, container: u64, prop: u32, value: i32) {\r\n    if props.is_null() {\r\n        return;\r\n    }\r\n\r\n    let mut int_value = value;\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: (&mut int_value as *mut i32).cast(),\r\n        un_buffer_size: core::mem::size_of::<i32>() as u32,\r\n        un_tag: K_UN_INT32_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn register_tracked_device_if_needed(device_ptr: *mut c_void) {\r\n    if DEVICE_REGISTERED.load(Ordering::SeqCst) {\r\n        return;\r\n    }\r\n\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    let added = unsafe {\r\n        ((*(*host).vtable).tracked_device_added)(\r\n            host,\r\n            DEVICE_SERIAL_CSTR.as_ptr().cast(),\r\n            TRACKED_DEVICE_CLASS_CONTROLLER,\r\n            device_ptr,\r\n        )\r\n    };\r\n\r\n    if added {\r\n        DEVICE_REGISTERED.store(true, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\npub fn initialize(driver_context: *mut c_void, device_ptr: *mut c_void) -> bool {\r\n    if driver_context.is_null() {\r\n        return false;\r\n    }\r\n\r\n    let ctx = driver_context.cast::<DriverContext>();\r\n    let mut err = 0;\r\n\r\n    let get_iface = unsafe { (*(*ctx).vtable).get_generic_interface };\r\n\r\n    let host = get_iface(\r\n        ctx,\r\n        IVR_SERVER_DRIVER_HOST_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let driver_input = get_iface(\r\n        ctx,\r\n        IVR_DRIVER_INPUT_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let properties = get_iface(\r\n        ctx,\r\n        IVR_PROPERTIES_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    SERVER_HOST_IFACE.store(host, Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(driver_input, Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(properties, Ordering::SeqCst);\r\n\r\n    let ok = !host.is_null() && !driver_input.is_null() && !properties.is_null();\r\n    if !ok {\r\n        return false;\r\n    }\r\n\r\n    register_tracked_device_if_needed(device_ptr);\r\n    true\r\n}\r\n\r\npub fn cleanup() {\r\n    DEVICE_REGISTERED.store(false, Ordering::SeqCst);\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    SERVER_HOST_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n}\r\n\r\npub fn route_haptic_events() {\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    loop {\r\n        let mut event = VREvent::default();\r\n        let ok = unsafe {\r\n            ((*(*host).vtable).poll_next_event)(\r\n                host,\r\n                &mut event as *mut VREvent,\r\n                core::mem::size_of::<VREvent>() as u32,\r\n            )\r\n        };\r\n\r\n        if !ok {\r\n            break;\r\n        }\r\n\r\n        if event.event_type != VREVENT_INPUT_HAPTIC_VIBRATION {\r\n            continue;\r\n        }\r\n\r\n        let hv = unsafe { event.data.haptic_vibration };\r\n\r\n        let container = DEVICE_CONTAINER.load(Ordering::SeqCst);\r\n        if container != 0 && hv.container_handle != 0 && hv.container_handle != container {\r\n            continue;\r\n        }\r\n\r\n        let haptic_component = HAPTIC_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n        if haptic_component != 0 && hv.component_handle != 0 && hv.component_handle != haptic_component {\r\n            continue;\r\n        }\r\n\r\n        haptics::handle_haptic_request(haptics::HapticVibrationRequest {\r\n            amplitude: hv.amplitude,\r\n            duration_seconds: hv.duration_seconds,\r\n            frequency: hv.frequency,\r\n        });\r\n    }\r\n}\r\n\r\npub fn push_pose_update(object_id: u32, pose: *const c_void, pose_size: u32) {\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    if pose.is_null() || pose_size == 0 {\r\n        return;\r\n    }\r\n\r\n    unsafe {\r\n        ((*(*host).vtable).tracked_device_pose_updated)(host, object_id, pose, pose_size);\r\n    }\r\n}\r\n\r\npub fn activate_device(object_id: u32) -> bool {\r\n    let Some(props) = get_properties() else {\r\n        return false;\r\n    };\r\n\r\n    let container = unsafe { ((*(*props).vtable).tracked_device_to_property_container)(props, object_id) };\r\n    DEVICE_CONTAINER.store(container, Ordering::SeqCst);\r\n\r\n    write_string_property(props, container, PROP_MODEL_NUMBER_STRING, DEVICE_MODEL_CSTR);\r\n    write_string_property(props, container, PROP_SERIAL_NUMBER_STRING, DEVICE_SERIAL_CSTR);\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_TRACKING_SYSTEM_NAME_STRING,\r\n        DEVICE_TRACKING_SYSTEM_NAME_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_REGISTERED_DEVICE_TYPE_STRING,\r\n        DEVICE_REGISTERED_TYPE_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_INPUT_PROFILE_PATH_STRING,\r\n        DEVICE_INPUT_PROFILE_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_CONTROLLER_TYPE_STRING,\r\n        DEVICE_CONTROLLER_TYPE_CSTR,\r\n    );\r\n    write_bool_property(props, container, PROP_HAS_CONTROLLER_COMPONENT_BOOL, true);\r\n    write_int32_property(props, container, PROP_CONTROLLER_ROLE_HINT_INT32, 1);\r\n\r\n    if let Some(input) = get_driver_input() {\r\n        let mut handle = 0_u64;\r\n        let rc = unsafe {\r\n            ((*(*input).vtable).create_haptic_component)(\r\n                input,\r\n                container,\r\n                DEVICE_HAPTIC_PATH_CSTR.as_ptr().cast(),\r\n                &mut handle as *mut u64,\r\n            )\r\n        };\r\n\r\n        if rc == 0 {\r\n            HAPTIC_COMPONENT_HANDLE.store(handle, Ordering::SeqCst);\r\n        }\r\n    }\r\n\r\n    true\r\n}\r\n\r\npub fn deactivate_device() {\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n}\r\n","epoch":1558,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs::request_66e3538f-7fed-4c58-b7f0-e5edecfabab8",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_66e3538f-7fed-4c58-b7f0-e5edecfabab8","content":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse core::sync::atomic::{AtomicU64, Ordering};\r\nuse std::sync::{Mutex, OnceLock};\r\nuse std::time::Instant;\r\nuse std::time::Duration;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic STOP_DEADLINE_MS: AtomicU64 = AtomicU64::new(0);\r\nstatic STOP_TIMER_TOKEN: AtomicU64 = AtomicU64::new(0);\r\nstatic START_INSTANT: OnceLock<Instant> = OnceLock::new();\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\n\r\nfn send_packet(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    hid_bridge::HidBridge::new()\r\n        .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    send_packet(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn now_ms() -> u64 {\r\n    let start = START_INSTANT.get_or_init(Instant::now);\r\n    let ms = Instant::now().duration_since(*start).as_millis();\r\n    ms.min(u128::from(u64::MAX)) as u64\r\n}\r\n\r\nfn schedule_stop_after(duration_seconds: f32) {\r\n    if duration_seconds <= 0.0 {\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let duration_ms = (duration_seconds * 1000.0).round().max(1.0) as u64;\r\n    let deadline = now_ms().saturating_add(duration_ms);\r\n    STOP_DEADLINE_MS.store(deadline, Ordering::SeqCst);\r\n\r\n    let token = STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst).saturating_add(1);\r\n    std::thread::spawn(move || {\r\n        std::thread::sleep(Duration::from_millis(duration_ms));\r\n\r\n        if STOP_TIMER_TOKEN.load(Ordering::SeqCst) != token {\r\n            return;\r\n        }\r\n\r\n        let _ = send_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    });\r\n}\r\n\r\nfn process_pending_stop() {\r\n    let deadline = STOP_DEADLINE_MS.load(Ordering::SeqCst);\r\n    if deadline == 0 {\r\n        return;\r\n    }\r\n\r\n    if now_ms() >= deadline {\r\n        let _ = send_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        let _ = send_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let _ = send_amplitude(req.amplitude);\r\n    schedule_stop_after(req.duration_seconds);\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n    process_pending_stop();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","epoch":1564,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs::request_66e3538f-7fed-4c58-b7f0-e5edecfabab8",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\openvr_runtime.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","scheme":"file"},"requestId":"request_66e3538f-7fed-4c58-b7f0-e5edecfabab8","content":"//! OpenVR runtime integration helpers.\r\n//!\r\n//! This module owns driver-context interface resolution, tracked-device\r\n//! registration, component/property setup, and OpenVR haptic event routing.\r\n\r\nuse core::ffi::c_void;\r\nuse core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU64, Ordering};\r\nuse std::ffi::CString;\r\nuse std::os::raw::c_char;\r\n\r\nuse crate::haptics;\r\n\r\ntype EvRInitError = i32;\r\n\r\nconst IVR_SERVER_DRIVER_HOST_VERSION: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\nconst IVR_DRIVER_INPUT_VERSION: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_PROPERTIES_VERSION: &[u8] = b\"IVRProperties_001\\0\";\r\nconst IVR_DRIVER_LOG_VERSION: &[u8] = b\"IVRDriverLog_001\\0\";\r\n\r\nconst TRACKED_DEVICE_CLASS_CONTROLLER: i32 = 2;\r\nconst VREVENT_INPUT_HAPTIC_VIBRATION: u32 = 1700;\r\n\r\nconst PROP_MODEL_NUMBER_STRING: u32 = 1001;\r\nconst PROP_SERIAL_NUMBER_STRING: u32 = 1002;\r\nconst PROP_TRACKING_SYSTEM_NAME_STRING: u32 = 1000;\r\nconst PROP_REGISTERED_DEVICE_TYPE_STRING: u32 = 1036;\r\nconst PROP_INPUT_PROFILE_PATH_STRING: u32 = 1037;\r\nconst PROP_CONTROLLER_TYPE_STRING: u32 = 7000;\r\nconst PROP_CONTROLLER_ROLE_HINT_INT32: u32 = 3007;\r\nconst PROP_HAS_CONTROLLER_COMPONENT_BOOL: u32 = 6003;\r\n\r\nconst K_UN_BOOL_PROPERTY_TAG: u32 = 4;\r\nconst K_UN_INT32_PROPERTY_TAG: u32 = 2;\r\nconst K_UN_STRING_PROPERTY_TAG: u32 = 5;\r\nconst PROPERTY_WRITE_SET: i32 = 0;\r\n\r\nconst DEVICE_SERIAL_CSTR: &[u8] = b\"qtpy-samd21-haptic-001\\0\";\r\nconst DEVICE_MODEL_CSTR: &[u8] = b\"QT Py SAMD21 Haptic\\0\";\r\nconst DEVICE_TRACKING_SYSTEM_NAME_CSTR: &[u8] = b\"big_haptic_driver\\0\";\r\nconst DEVICE_REGISTERED_TYPE_CSTR: &[u8] = b\"big_haptic_driver/controller\\0\";\r\nconst DEVICE_CONTROLLER_TYPE_CSTR: &[u8] = b\"big_haptic_driver\\0\";\r\nconst DEVICE_INPUT_PROFILE_CSTR: &[u8] = b\"{big_haptic_driver}/input/big_haptic_profile.json\\0\";\r\nconst DEVICE_HAPTIC_PATH_CSTR: &[u8] = b\"/output/haptic\\0\";\r\n\r\nconst PROVIDER_IFACE_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_004\\0\";\r\nconst PROVIDER_IFACE_ALT_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_005\\0\";\r\nconst DEVICE_IFACE_CSTR: &[u8] = b\"ITrackedDeviceServerDriver_005\\0\";\r\n\r\nconst IVR_PROPERTIES_CSTR: &[u8] = b\"IVRProperties_001\\0\";\r\nconst IVR_DRIVER_INPUT_CSTR: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_SERVER_DRIVER_HOST_CSTR: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\nconst IVR_DRIVER_LOG_CSTR: &[u8] = b\"IVRDriverLog_001\\0\";\r\n\r\nconst IVR_SETTINGS_CSTR: &[u8] = b\"IVRSettings_003\\0\";\r\nconst IVR_DRIVER_MANAGER_CSTR: &[u8] = b\"IVRDriverManager_001\\0\";\r\nconst IVR_RESOURCES_CSTR: &[u8] = b\"IVRResources_001\\0\";\r\n\r\n#[repr(C)]\r\nstruct InterfaceVersions([*const c_char; 11]);\r\nunsafe impl Sync for InterfaceVersions {}\r\n\r\nstatic INTERFACE_VERSIONS: InterfaceVersions = InterfaceVersions([\r\n    IVR_SETTINGS_CSTR.as_ptr().cast(),\r\n    DEVICE_IFACE_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_CSTR.as_ptr().cast(),\r\n    IVR_PROPERTIES_CSTR.as_ptr().cast(),\r\n    IVR_SERVER_DRIVER_HOST_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_LOG_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_MANAGER_CSTR.as_ptr().cast(),\r\n    IVR_RESOURCES_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_INPUT_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_ALT_CSTR.as_ptr().cast(),\r\n    core::ptr::null(),\r\n]);\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREventHapticVibration {\r\n    container_handle: u64,\r\n    component_handle: u64,\r\n    duration_seconds: f32,\r\n    frequency: f32,\r\n    amplitude: f32,\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nunion VREventData {\r\n    haptic_vibration: VREventHapticVibration,\r\n    reserved: [u8; 48],\r\n}\r\n\r\nimpl Default for VREventData {\r\n    fn default() -> Self {\r\n        Self { reserved: [0; 48] }\r\n    }\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREvent {\r\n    event_type: u32,\r\n    tracked_device_index: u32,\r\n    event_age_seconds: f32,\r\n    data: VREventData,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContextVTable {\r\n    get_generic_interface:\r\n        extern \"C\" fn(*mut DriverContext, *const c_char, *mut EvRInitError) -> *mut c_void,\r\n    get_driver_handle: extern \"C\" fn(*mut DriverContext) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContext {\r\n    vtable: *const DriverContextVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHostVTable {\r\n    tracked_device_added:\r\n        extern \"C\" fn(*mut ServerDriverHost, *const c_char, i32, *mut c_void) -> bool,\r\n    tracked_device_pose_updated: extern \"C\" fn(*mut ServerDriverHost, u32, *const c_void, u32),\r\n    vsync_event: extern \"C\" fn(*mut ServerDriverHost, f64),\r\n    vendor_specific_event: extern \"C\" fn(*mut ServerDriverHost, u32, u32, *const VREventData, f64),\r\n    is_exiting: extern \"C\" fn(*mut ServerDriverHost) -> bool,\r\n    poll_next_event: extern \"C\" fn(*mut ServerDriverHost, *mut VREvent, u32) -> bool,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHost {\r\n    vtable: *const ServerDriverHostVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverLogVTable {\r\n    log: extern \"C\" fn(*mut DriverLog, *const c_char),\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverLog {\r\n    vtable: *const DriverLogVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverInputVTable {\r\n    create_boolean_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n    update_boolean_component: extern \"C\" fn(*mut DriverInput, u64, bool, f64) -> i32,\r\n    create_scalar_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64, i32, i32) -> i32,\r\n    update_scalar_component: extern \"C\" fn(*mut DriverInput, u64, f32, f64) -> i32,\r\n    create_haptic_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverInput {\r\n    vtable: *const DriverInputVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct PropertiesVTable {\r\n    read_property_batch: extern \"C\" fn(*mut Properties, u64, *mut c_void, u32) -> i32,\r\n    write_property_batch: extern \"C\" fn(*mut Properties, u64, *mut PropertyWrite, u32) -> i32,\r\n    get_prop_error_name_from_enum: extern \"C\" fn(*mut Properties, i32) -> *const c_char,\r\n    tracked_device_to_property_container: extern \"C\" fn(*mut Properties, u32) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct Properties {\r\n    vtable: *const PropertiesVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct PropertyWrite {\r\n    prop: u32,\r\n    write_type: i32,\r\n    set_error: i32,\r\n    pv_buffer: *mut c_void,\r\n    un_buffer_size: u32,\r\n    un_tag: u32,\r\n    e_error: i32,\r\n}\r\n\r\nstatic DEVICE_REGISTERED: AtomicBool = AtomicBool::new(false);\r\n\r\nstatic SERVER_HOST_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DRIVER_INPUT_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic PROPERTIES_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DRIVER_LOG_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\n\r\nstatic DEVICE_CONTAINER: AtomicU64 = AtomicU64::new(0);\r\nstatic HAPTIC_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\n\r\npub fn interface_versions_ptr() -> *const *const c_char {\r\n    INTERFACE_VERSIONS.0.as_ptr()\r\n}\r\n\r\nfn get_server_host() -> Option<*mut ServerDriverHost> {\r\n    let ptr = SERVER_HOST_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_driver_input() -> Option<*mut DriverInput> {\r\n    let ptr = DRIVER_INPUT_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_properties() -> Option<*mut Properties> {\r\n    let ptr = PROPERTIES_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_driver_log() -> Option<*mut DriverLog> {\r\n    let ptr = DRIVER_LOG_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn log_driver_message(message: &str) {\r\n    let Some(driver_log) = get_driver_log() else {\r\n        return;\r\n    };\r\n\r\n    let Ok(c_message) = CString::new(message) else {\r\n        return;\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*driver_log).vtable).log)(driver_log, c_message.as_ptr());\r\n    }\r\n}\r\n\r\nfn write_string_property(props: *mut Properties, container: u64, prop: u32, value_nul: &'static [u8]) {\r\n    if props.is_null() || value_nul.is_empty() {\r\n        return;\r\n    }\r\n\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: value_nul.as_ptr().cast_mut().cast(),\r\n        un_buffer_size: value_nul.len() as u32,\r\n        un_tag: K_UN_STRING_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn write_bool_property(props: *mut Properties, container: u64, prop: u32, value: bool) {\r\n    if props.is_null() {\r\n        return;\r\n    }\r\n\r\n    let mut bool_value = value;\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: (&mut bool_value as *mut bool).cast(),\r\n        un_buffer_size: core::mem::size_of::<bool>() as u32,\r\n        un_tag: K_UN_BOOL_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn write_int32_property(props: *mut Properties, container: u64, prop: u32, value: i32) {\r\n    if props.is_null() {\r\n        return;\r\n    }\r\n\r\n    let mut int_value = value;\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: (&mut int_value as *mut i32).cast(),\r\n        un_buffer_size: core::mem::size_of::<i32>() as u32,\r\n        un_tag: K_UN_INT32_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn register_tracked_device_if_needed(device_ptr: *mut c_void) {\r\n    if DEVICE_REGISTERED.load(Ordering::SeqCst) {\r\n        return;\r\n    }\r\n\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    let added = unsafe {\r\n        ((*(*host).vtable).tracked_device_added)(\r\n            host,\r\n            DEVICE_SERIAL_CSTR.as_ptr().cast(),\r\n            TRACKED_DEVICE_CLASS_CONTROLLER,\r\n            device_ptr,\r\n        )\r\n    };\r\n\r\n    if added {\r\n        DEVICE_REGISTERED.store(true, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\npub fn initialize(driver_context: *mut c_void, device_ptr: *mut c_void) -> bool {\r\n    if driver_context.is_null() {\r\n        return false;\r\n    }\r\n\r\n    let ctx = driver_context.cast::<DriverContext>();\r\n    let mut err = 0;\r\n\r\n    let get_iface = unsafe { (*(*ctx).vtable).get_generic_interface };\r\n\r\n    let host = get_iface(\r\n        ctx,\r\n        IVR_SERVER_DRIVER_HOST_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let driver_input = get_iface(\r\n        ctx,\r\n        IVR_DRIVER_INPUT_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let properties = get_iface(\r\n        ctx,\r\n        IVR_PROPERTIES_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let driver_log = get_iface(\r\n        ctx,\r\n        IVR_DRIVER_LOG_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    SERVER_HOST_IFACE.store(host, Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(driver_input, Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(properties, Ordering::SeqCst);\r\n    DRIVER_LOG_IFACE.store(driver_log, Ordering::SeqCst);\r\n\r\n    let ok = !host.is_null() && !driver_input.is_null() && !properties.is_null();\r\n    if !ok {\r\n        return false;\r\n    }\r\n\r\n    log_driver_message(&format!(\r\n        \"[big_haptic_driver] OpenVR init ok; sizeof(VREvent)={}, sizeof(VREventHapticVibration)={}\",\r\n        core::mem::size_of::<VREvent>(),\r\n        core::mem::size_of::<VREventHapticVibration>()\r\n    ));\r\n\r\n    register_tracked_device_if_needed(device_ptr);\r\n    true\r\n}\r\n\r\npub fn cleanup() {\r\n    DEVICE_REGISTERED.store(false, Ordering::SeqCst);\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    SERVER_HOST_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DRIVER_LOG_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n}\r\n\r\npub fn route_haptic_events() {\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    loop {\r\n        let mut event = VREvent::default();\r\n        let ok = unsafe {\r\n            ((*(*host).vtable).poll_next_event)(\r\n                host,\r\n                &mut event as *mut VREvent,\r\n                core::mem::size_of::<VREvent>() as u32,\r\n            )\r\n        };\r\n\r\n        if !ok {\r\n            break;\r\n        }\r\n\r\n        if event.event_type != VREVENT_INPUT_HAPTIC_VIBRATION {\r\n            continue;\r\n        }\r\n\r\n        let hv = unsafe { event.data.haptic_vibration };\r\n\r\n        log_driver_message(&format!(\r\n            \"[big_haptic_driver] haptic event: dev={} container={} component={} duration_s={:.6} (0x{:08x}) freq={:.3} amp={:.6} (0x{:08x})\",\r\n            event.tracked_device_index,\r\n            hv.container_handle,\r\n            hv.component_handle,\r\n            hv.duration_seconds,\r\n            hv.duration_seconds.to_bits(),\r\n            hv.frequency,\r\n            hv.amplitude,\r\n            hv.amplitude.to_bits()\r\n        ));\r\n\r\n        let container = DEVICE_CONTAINER.load(Ordering::SeqCst);\r\n        if container != 0 && hv.container_handle != 0 && hv.container_handle != container {\r\n            log_driver_message(&format!(\r\n                \"[big_haptic_driver] skipped haptic event: container mismatch event={} driver={}\",\r\n                hv.container_handle, container\r\n            ));\r\n            continue;\r\n        }\r\n\r\n        let haptic_component = HAPTIC_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n        if haptic_component != 0 && hv.component_handle != 0 && hv.component_handle != haptic_component {\r\n            log_driver_message(&format!(\r\n                \"[big_haptic_driver] skipped haptic event: component mismatch event={} driver={}\",\r\n                hv.component_handle, haptic_component\r\n            ));\r\n            continue;\r\n        }\r\n\r\n        log_driver_message(\"[big_haptic_driver] forwarding haptic request to HID layer\");\r\n\r\n        haptics::handle_haptic_request(haptics::HapticVibrationRequest {\r\n            amplitude: hv.amplitude,\r\n            duration_seconds: hv.duration_seconds,\r\n            frequency: hv.frequency,\r\n        });\r\n    }\r\n}\r\n\r\npub fn push_pose_update(object_id: u32, pose: *const c_void, pose_size: u32) {\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    if pose.is_null() || pose_size == 0 {\r\n        return;\r\n    }\r\n\r\n    unsafe {\r\n        ((*(*host).vtable).tracked_device_pose_updated)(host, object_id, pose, pose_size);\r\n    }\r\n}\r\n\r\npub fn activate_device(object_id: u32) -> bool {\r\n    let Some(props) = get_properties() else {\r\n        return false;\r\n    };\r\n\r\n    let container = unsafe { ((*(*props).vtable).tracked_device_to_property_container)(props, object_id) };\r\n    DEVICE_CONTAINER.store(container, Ordering::SeqCst);\r\n\r\n    write_string_property(props, container, PROP_MODEL_NUMBER_STRING, DEVICE_MODEL_CSTR);\r\n    write_string_property(props, container, PROP_SERIAL_NUMBER_STRING, DEVICE_SERIAL_CSTR);\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_TRACKING_SYSTEM_NAME_STRING,\r\n        DEVICE_TRACKING_SYSTEM_NAME_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_REGISTERED_DEVICE_TYPE_STRING,\r\n        DEVICE_REGISTERED_TYPE_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_INPUT_PROFILE_PATH_STRING,\r\n        DEVICE_INPUT_PROFILE_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_CONTROLLER_TYPE_STRING,\r\n        DEVICE_CONTROLLER_TYPE_CSTR,\r\n    );\r\n    write_bool_property(props, container, PROP_HAS_CONTROLLER_COMPONENT_BOOL, true);\r\n    write_int32_property(props, container, PROP_CONTROLLER_ROLE_HINT_INT32, 1);\r\n\r\n    if let Some(input) = get_driver_input() {\r\n        let mut handle = 0_u64;\r\n        let rc = unsafe {\r\n            ((*(*input).vtable).create_haptic_component)(\r\n                input,\r\n                container,\r\n                DEVICE_HAPTIC_PATH_CSTR.as_ptr().cast(),\r\n                &mut handle as *mut u64,\r\n            )\r\n        };\r\n\r\n        if rc == 0 {\r\n            HAPTIC_COMPONENT_HANDLE.store(handle, Ordering::SeqCst);\r\n        }\r\n    }\r\n\r\n    true\r\n}\r\n\r\npub fn deactivate_device() {\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n}\r\n","epoch":1567,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs::request_940afaca-5401-4dbf-a524-3fba59fd2ac5",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_940afaca-5401-4dbf-a524-3fba59fd2ac5","content":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse core::sync::atomic::{AtomicU64, Ordering};\r\nuse std::sync::{Mutex, OnceLock};\r\nuse std::time::Instant;\r\nuse std::time::Duration;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic STOP_DEADLINE_MS: AtomicU64 = AtomicU64::new(0);\r\nstatic STOP_TIMER_TOKEN: AtomicU64 = AtomicU64::new(0);\r\nstatic START_INSTANT: OnceLock<Instant> = OnceLock::new();\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\n\r\nconst DEFAULT_IMPLICIT_PULSE_SECONDS: f32 = 0.03;\r\nconst MIN_IMPLICIT_PULSE_SECONDS: f32 = 0.005;\r\nconst MAX_IMPLICIT_PULSE_SECONDS: f32 = 0.03;\r\n\r\nfn send_packet(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    hid_bridge::HidBridge::new()\r\n        .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    send_packet(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn now_ms() -> u64 {\r\n    let start = START_INSTANT.get_or_init(Instant::now);\r\n    let ms = Instant::now().duration_since(*start).as_millis();\r\n    ms.min(u128::from(u64::MAX)) as u64\r\n}\r\n\r\nfn schedule_stop_after(duration_seconds: f32) {\r\n    if duration_seconds <= 0.0 {\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let duration_ms = (duration_seconds * 1000.0).round().max(1.0) as u64;\r\n    let deadline = now_ms().saturating_add(duration_ms);\r\n    STOP_DEADLINE_MS.store(deadline, Ordering::SeqCst);\r\n\r\n    let token = STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst).saturating_add(1);\r\n    std::thread::spawn(move || {\r\n        std::thread::sleep(Duration::from_millis(duration_ms));\r\n\r\n        if STOP_TIMER_TOKEN.load(Ordering::SeqCst) != token {\r\n            return;\r\n        }\r\n\r\n        let _ = send_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    });\r\n}\r\n\r\nfn resolve_effective_duration_seconds(req: HapticVibrationRequest) -> f32 {\r\n    if req.duration_seconds > 0.0 {\r\n        return req.duration_seconds;\r\n    }\r\n\r\n    if req.frequency > 0.0 {\r\n        return (1.0 / req.frequency).clamp(MIN_IMPLICIT_PULSE_SECONDS, MAX_IMPLICIT_PULSE_SECONDS);\r\n    }\r\n\r\n    DEFAULT_IMPLICIT_PULSE_SECONDS\r\n}\r\n\r\nfn process_pending_stop() {\r\n    let deadline = STOP_DEADLINE_MS.load(Ordering::SeqCst);\r\n    if deadline == 0 {\r\n        return;\r\n    }\r\n\r\n    if now_ms() >= deadline {\r\n        let _ = send_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        let _ = send_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let _ = send_amplitude(req.amplitude);\r\n    let effective_duration = resolve_effective_duration_seconds(req);\r\n    schedule_stop_after(effective_duration);\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n    process_pending_stop();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","epoch":1571,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs::request_940afaca-5401-4dbf-a524-3fba59fd2ac5",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\openvr_runtime.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","scheme":"file"},"requestId":"request_940afaca-5401-4dbf-a524-3fba59fd2ac5","content":"//! OpenVR runtime integration helpers.\r\n//!\r\n//! This module owns driver-context interface resolution, tracked-device\r\n//! registration, component/property setup, and OpenVR haptic event routing.\r\n\r\nuse core::ffi::c_void;\r\nuse core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU64, Ordering};\r\nuse std::ffi::CString;\r\nuse std::os::raw::c_char;\r\n\r\nuse crate::haptics;\r\n\r\ntype EvRInitError = i32;\r\n\r\nconst IVR_SERVER_DRIVER_HOST_VERSION: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\nconst IVR_DRIVER_INPUT_VERSION: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_PROPERTIES_VERSION: &[u8] = b\"IVRProperties_001\\0\";\r\nconst IVR_DRIVER_LOG_VERSION: &[u8] = b\"IVRDriverLog_001\\0\";\r\n\r\nconst TRACKED_DEVICE_CLASS_CONTROLLER: i32 = 2;\r\nconst VREVENT_INPUT_HAPTIC_VIBRATION: u32 = 1700;\r\n\r\nconst PROP_MODEL_NUMBER_STRING: u32 = 1001;\r\nconst PROP_SERIAL_NUMBER_STRING: u32 = 1002;\r\nconst PROP_TRACKING_SYSTEM_NAME_STRING: u32 = 1000;\r\nconst PROP_REGISTERED_DEVICE_TYPE_STRING: u32 = 1036;\r\nconst PROP_INPUT_PROFILE_PATH_STRING: u32 = 1037;\r\nconst PROP_CONTROLLER_TYPE_STRING: u32 = 7000;\r\nconst PROP_CONTROLLER_ROLE_HINT_INT32: u32 = 3007;\r\nconst PROP_HAS_CONTROLLER_COMPONENT_BOOL: u32 = 6003;\r\n\r\nconst K_UN_BOOL_PROPERTY_TAG: u32 = 4;\r\nconst K_UN_INT32_PROPERTY_TAG: u32 = 2;\r\nconst K_UN_STRING_PROPERTY_TAG: u32 = 5;\r\nconst PROPERTY_WRITE_SET: i32 = 0;\r\n\r\nconst DEVICE_SERIAL_CSTR: &[u8] = b\"qtpy-samd21-haptic-001\\0\";\r\nconst DEVICE_MODEL_CSTR: &[u8] = b\"QT Py SAMD21 Haptic\\0\";\r\nconst DEVICE_TRACKING_SYSTEM_NAME_CSTR: &[u8] = b\"big_haptic_driver\\0\";\r\nconst DEVICE_REGISTERED_TYPE_CSTR: &[u8] = b\"big_haptic_driver/controller\\0\";\r\nconst DEVICE_CONTROLLER_TYPE_CSTR: &[u8] = b\"big_haptic_driver\\0\";\r\nconst DEVICE_INPUT_PROFILE_CSTR: &[u8] = b\"{big_haptic_driver}/input/big_haptic_profile.json\\0\";\r\nconst DEVICE_HAPTIC_PATH_CSTR: &[u8] = b\"/output/haptic\\0\";\r\n\r\nconst PROVIDER_IFACE_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_004\\0\";\r\nconst PROVIDER_IFACE_ALT_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_005\\0\";\r\nconst DEVICE_IFACE_CSTR: &[u8] = b\"ITrackedDeviceServerDriver_005\\0\";\r\n\r\nconst IVR_PROPERTIES_CSTR: &[u8] = b\"IVRProperties_001\\0\";\r\nconst IVR_DRIVER_INPUT_CSTR: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_SERVER_DRIVER_HOST_CSTR: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\nconst IVR_DRIVER_LOG_CSTR: &[u8] = b\"IVRDriverLog_001\\0\";\r\n\r\nconst IVR_SETTINGS_CSTR: &[u8] = b\"IVRSettings_003\\0\";\r\nconst IVR_DRIVER_MANAGER_CSTR: &[u8] = b\"IVRDriverManager_001\\0\";\r\nconst IVR_RESOURCES_CSTR: &[u8] = b\"IVRResources_001\\0\";\r\n\r\n#[repr(C)]\r\nstruct InterfaceVersions([*const c_char; 11]);\r\nunsafe impl Sync for InterfaceVersions {}\r\n\r\nstatic INTERFACE_VERSIONS: InterfaceVersions = InterfaceVersions([\r\n    IVR_SETTINGS_CSTR.as_ptr().cast(),\r\n    DEVICE_IFACE_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_CSTR.as_ptr().cast(),\r\n    IVR_PROPERTIES_CSTR.as_ptr().cast(),\r\n    IVR_SERVER_DRIVER_HOST_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_LOG_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_MANAGER_CSTR.as_ptr().cast(),\r\n    IVR_RESOURCES_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_INPUT_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_ALT_CSTR.as_ptr().cast(),\r\n    core::ptr::null(),\r\n]);\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREventHapticVibration {\r\n    container_handle: u64,\r\n    component_handle: u64,\r\n    duration_seconds: f32,\r\n    frequency: f32,\r\n    amplitude: f32,\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nunion VREventData {\r\n    haptic_vibration: VREventHapticVibration,\r\n    reserved: [u8; 48],\r\n}\r\n\r\nimpl Default for VREventData {\r\n    fn default() -> Self {\r\n        Self { reserved: [0; 48] }\r\n    }\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREvent {\r\n    event_type: u32,\r\n    tracked_device_index: u32,\r\n    event_age_seconds: f32,\r\n    data: VREventData,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContextVTable {\r\n    get_generic_interface:\r\n        extern \"C\" fn(*mut DriverContext, *const c_char, *mut EvRInitError) -> *mut c_void,\r\n    get_driver_handle: extern \"C\" fn(*mut DriverContext) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContext {\r\n    vtable: *const DriverContextVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHostVTable {\r\n    tracked_device_added:\r\n        extern \"C\" fn(*mut ServerDriverHost, *const c_char, i32, *mut c_void) -> bool,\r\n    tracked_device_pose_updated: extern \"C\" fn(*mut ServerDriverHost, u32, *const c_void, u32),\r\n    vsync_event: extern \"C\" fn(*mut ServerDriverHost, f64),\r\n    vendor_specific_event: extern \"C\" fn(*mut ServerDriverHost, u32, u32, *const VREventData, f64),\r\n    is_exiting: extern \"C\" fn(*mut ServerDriverHost) -> bool,\r\n    poll_next_event: extern \"C\" fn(*mut ServerDriverHost, *mut VREvent, u32) -> bool,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHost {\r\n    vtable: *const ServerDriverHostVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverLogVTable {\r\n    log: extern \"C\" fn(*mut DriverLog, *const c_char),\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverLog {\r\n    vtable: *const DriverLogVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverInputVTable {\r\n    create_boolean_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n    update_boolean_component: extern \"C\" fn(*mut DriverInput, u64, bool, f64) -> i32,\r\n    create_scalar_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64, i32, i32) -> i32,\r\n    update_scalar_component: extern \"C\" fn(*mut DriverInput, u64, f32, f64) -> i32,\r\n    create_haptic_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverInput {\r\n    vtable: *const DriverInputVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct PropertiesVTable {\r\n    read_property_batch: extern \"C\" fn(*mut Properties, u64, *mut c_void, u32) -> i32,\r\n    write_property_batch: extern \"C\" fn(*mut Properties, u64, *mut PropertyWrite, u32) -> i32,\r\n    get_prop_error_name_from_enum: extern \"C\" fn(*mut Properties, i32) -> *const c_char,\r\n    tracked_device_to_property_container: extern \"C\" fn(*mut Properties, u32) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct Properties {\r\n    vtable: *const PropertiesVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct PropertyWrite {\r\n    prop: u32,\r\n    write_type: i32,\r\n    set_error: i32,\r\n    pv_buffer: *mut c_void,\r\n    un_buffer_size: u32,\r\n    un_tag: u32,\r\n    e_error: i32,\r\n}\r\n\r\nstatic DEVICE_REGISTERED: AtomicBool = AtomicBool::new(false);\r\n\r\nstatic SERVER_HOST_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DRIVER_INPUT_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic PROPERTIES_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DRIVER_LOG_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\n\r\nstatic DEVICE_CONTAINER: AtomicU64 = AtomicU64::new(0);\r\nstatic HAPTIC_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\n\r\npub fn interface_versions_ptr() -> *const *const c_char {\r\n    INTERFACE_VERSIONS.0.as_ptr()\r\n}\r\n\r\nfn get_server_host() -> Option<*mut ServerDriverHost> {\r\n    let ptr = SERVER_HOST_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_driver_input() -> Option<*mut DriverInput> {\r\n    let ptr = DRIVER_INPUT_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_properties() -> Option<*mut Properties> {\r\n    let ptr = PROPERTIES_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_driver_log() -> Option<*mut DriverLog> {\r\n    let ptr = DRIVER_LOG_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn log_driver_message(message: &str) {\r\n    let Some(driver_log) = get_driver_log() else {\r\n        return;\r\n    };\r\n\r\n    let Ok(c_message) = CString::new(message) else {\r\n        return;\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*driver_log).vtable).log)(driver_log, c_message.as_ptr());\r\n    }\r\n}\r\n\r\nfn write_string_property(props: *mut Properties, container: u64, prop: u32, value_nul: &'static [u8]) {\r\n    if props.is_null() || value_nul.is_empty() {\r\n        return;\r\n    }\r\n\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: value_nul.as_ptr().cast_mut().cast(),\r\n        un_buffer_size: value_nul.len() as u32,\r\n        un_tag: K_UN_STRING_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn write_bool_property(props: *mut Properties, container: u64, prop: u32, value: bool) {\r\n    if props.is_null() {\r\n        return;\r\n    }\r\n\r\n    let mut bool_value = value;\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: (&mut bool_value as *mut bool).cast(),\r\n        un_buffer_size: core::mem::size_of::<bool>() as u32,\r\n        un_tag: K_UN_BOOL_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn write_int32_property(props: *mut Properties, container: u64, prop: u32, value: i32) {\r\n    if props.is_null() {\r\n        return;\r\n    }\r\n\r\n    let mut int_value = value;\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: (&mut int_value as *mut i32).cast(),\r\n        un_buffer_size: core::mem::size_of::<i32>() as u32,\r\n        un_tag: K_UN_INT32_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn register_tracked_device_if_needed(device_ptr: *mut c_void) {\r\n    if DEVICE_REGISTERED.load(Ordering::SeqCst) {\r\n        return;\r\n    }\r\n\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    let added = unsafe {\r\n        ((*(*host).vtable).tracked_device_added)(\r\n            host,\r\n            DEVICE_SERIAL_CSTR.as_ptr().cast(),\r\n            TRACKED_DEVICE_CLASS_CONTROLLER,\r\n            device_ptr,\r\n        )\r\n    };\r\n\r\n    if added {\r\n        DEVICE_REGISTERED.store(true, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\npub fn initialize(driver_context: *mut c_void, device_ptr: *mut c_void) -> bool {\r\n    if driver_context.is_null() {\r\n        return false;\r\n    }\r\n\r\n    let ctx = driver_context.cast::<DriverContext>();\r\n    let mut err = 0;\r\n\r\n    let get_iface = unsafe { (*(*ctx).vtable).get_generic_interface };\r\n\r\n    let host = get_iface(\r\n        ctx,\r\n        IVR_SERVER_DRIVER_HOST_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let driver_input = get_iface(\r\n        ctx,\r\n        IVR_DRIVER_INPUT_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let properties = get_iface(\r\n        ctx,\r\n        IVR_PROPERTIES_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let driver_log = get_iface(\r\n        ctx,\r\n        IVR_DRIVER_LOG_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    SERVER_HOST_IFACE.store(host, Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(driver_input, Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(properties, Ordering::SeqCst);\r\n    DRIVER_LOG_IFACE.store(driver_log, Ordering::SeqCst);\r\n\r\n    let ok = !host.is_null() && !driver_input.is_null() && !properties.is_null();\r\n    if !ok {\r\n        return false;\r\n    }\r\n\r\n    log_driver_message(&format!(\r\n        \"[big_haptic_driver] OpenVR init ok; sizeof(VREvent)={}, sizeof(VREventHapticVibration)={}\",\r\n        core::mem::size_of::<VREvent>(),\r\n        core::mem::size_of::<VREventHapticVibration>()\r\n    ));\r\n\r\n    register_tracked_device_if_needed(device_ptr);\r\n    true\r\n}\r\n\r\npub fn cleanup() {\r\n    DEVICE_REGISTERED.store(false, Ordering::SeqCst);\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    SERVER_HOST_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DRIVER_LOG_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n}\r\n\r\npub fn route_haptic_events() {\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    loop {\r\n        let mut event = VREvent::default();\r\n        let ok = unsafe {\r\n            ((*(*host).vtable).poll_next_event)(\r\n                host,\r\n                &mut event as *mut VREvent,\r\n                core::mem::size_of::<VREvent>() as u32,\r\n            )\r\n        };\r\n\r\n        if !ok {\r\n            break;\r\n        }\r\n\r\n        if event.event_type != VREVENT_INPUT_HAPTIC_VIBRATION {\r\n            continue;\r\n        }\r\n\r\n        let hv = unsafe { event.data.haptic_vibration };\r\n\r\n        log_driver_message(&format!(\r\n            \"[big_haptic_driver] haptic event: dev={} container={} component={} duration_s={:.6} (0x{:08x}) freq={:.3} amp={:.6} (0x{:08x})\",\r\n            event.tracked_device_index,\r\n            hv.container_handle,\r\n            hv.component_handle,\r\n            hv.duration_seconds,\r\n            hv.duration_seconds.to_bits(),\r\n            hv.frequency,\r\n            hv.amplitude,\r\n            hv.amplitude.to_bits()\r\n        ));\r\n\r\n        if hv.amplitude > 0.0 && hv.duration_seconds <= 0.0 {\r\n            log_driver_message(&format!(\r\n                \"[big_haptic_driver] note: OpenVR sent non-positive duration; driver will apply implicit finite pulse (freq={:.3})\",\r\n                hv.frequency\r\n            ));\r\n        }\r\n\r\n        let container = DEVICE_CONTAINER.load(Ordering::SeqCst);\r\n        if container != 0 && hv.container_handle != 0 && hv.container_handle != container {\r\n            log_driver_message(&format!(\r\n                \"[big_haptic_driver] skipped haptic event: container mismatch event={} driver={}\",\r\n                hv.container_handle, container\r\n            ));\r\n            continue;\r\n        }\r\n\r\n        let haptic_component = HAPTIC_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n        if haptic_component != 0 && hv.component_handle != 0 && hv.component_handle != haptic_component {\r\n            log_driver_message(&format!(\r\n                \"[big_haptic_driver] skipped haptic event: component mismatch event={} driver={}\",\r\n                hv.component_handle, haptic_component\r\n            ));\r\n            continue;\r\n        }\r\n\r\n        log_driver_message(\"[big_haptic_driver] forwarding haptic request to HID layer\");\r\n\r\n        haptics::handle_haptic_request(haptics::HapticVibrationRequest {\r\n            amplitude: hv.amplitude,\r\n            duration_seconds: hv.duration_seconds,\r\n            frequency: hv.frequency,\r\n        });\r\n    }\r\n}\r\n\r\npub fn push_pose_update(object_id: u32, pose: *const c_void, pose_size: u32) {\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    if pose.is_null() || pose_size == 0 {\r\n        return;\r\n    }\r\n\r\n    unsafe {\r\n        ((*(*host).vtable).tracked_device_pose_updated)(host, object_id, pose, pose_size);\r\n    }\r\n}\r\n\r\npub fn activate_device(object_id: u32) -> bool {\r\n    let Some(props) = get_properties() else {\r\n        return false;\r\n    };\r\n\r\n    let container = unsafe { ((*(*props).vtable).tracked_device_to_property_container)(props, object_id) };\r\n    DEVICE_CONTAINER.store(container, Ordering::SeqCst);\r\n\r\n    write_string_property(props, container, PROP_MODEL_NUMBER_STRING, DEVICE_MODEL_CSTR);\r\n    write_string_property(props, container, PROP_SERIAL_NUMBER_STRING, DEVICE_SERIAL_CSTR);\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_TRACKING_SYSTEM_NAME_STRING,\r\n        DEVICE_TRACKING_SYSTEM_NAME_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_REGISTERED_DEVICE_TYPE_STRING,\r\n        DEVICE_REGISTERED_TYPE_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_INPUT_PROFILE_PATH_STRING,\r\n        DEVICE_INPUT_PROFILE_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_CONTROLLER_TYPE_STRING,\r\n        DEVICE_CONTROLLER_TYPE_CSTR,\r\n    );\r\n    write_bool_property(props, container, PROP_HAS_CONTROLLER_COMPONENT_BOOL, true);\r\n    write_int32_property(props, container, PROP_CONTROLLER_ROLE_HINT_INT32, 1);\r\n\r\n    if let Some(input) = get_driver_input() {\r\n        let mut handle = 0_u64;\r\n        let rc = unsafe {\r\n            ((*(*input).vtable).create_haptic_component)(\r\n                input,\r\n                container,\r\n                DEVICE_HAPTIC_PATH_CSTR.as_ptr().cast(),\r\n                &mut handle as *mut u64,\r\n            )\r\n        };\r\n\r\n        if rc == 0 {\r\n            HAPTIC_COMPONENT_HANDLE.store(handle, Ordering::SeqCst);\r\n        }\r\n    }\r\n\r\n    true\r\n}\r\n\r\npub fn deactivate_device() {\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n}\r\n","epoch":1574,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs::request_1d9fb0bc-867e-467e-86a5-8e6b0b5a1c0d",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_1d9fb0bc-867e-467e-86a5-8e6b0b5a1c0d","content":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse core::sync::atomic::{AtomicU64, Ordering};\r\nuse std::sync::{Mutex, OnceLock};\r\nuse std::time::Instant;\r\nuse std::time::Duration;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic STOP_DEADLINE_MS: AtomicU64 = AtomicU64::new(0);\r\nstatic STOP_TIMER_TOKEN: AtomicU64 = AtomicU64::new(0);\r\nstatic START_INSTANT: OnceLock<Instant> = OnceLock::new();\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\n\r\nconst OPENVR_MIN_FREQUENCY_HZ: f32 = 1_000_000.0 / 65_535.0;\r\nconst OPENVR_MAX_FREQUENCY_HZ: f32 = 1_000_000.0 / 300.0;\r\nconst OPENVR_MAX_DURATION_SECONDS: f32 = 10.0;\r\n\r\nfn send_packet(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    hid_bridge::HidBridge::new()\r\n        .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    send_packet(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn now_ms() -> u64 {\r\n    let start = START_INSTANT.get_or_init(Instant::now);\r\n    let ms = Instant::now().duration_since(*start).as_millis();\r\n    ms.min(u128::from(u64::MAX)) as u64\r\n}\r\n\r\nfn schedule_stop_after(duration_seconds: f32) {\r\n    if duration_seconds <= 0.0 {\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let duration_ms = (duration_seconds * 1000.0).round().max(1.0) as u64;\r\n    let deadline = now_ms().saturating_add(duration_ms);\r\n    STOP_DEADLINE_MS.store(deadline, Ordering::SeqCst);\r\n\r\n    let token = STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst).saturating_add(1);\r\n    std::thread::spawn(move || {\r\n        std::thread::sleep(Duration::from_millis(duration_ms));\r\n\r\n        if STOP_TIMER_TOKEN.load(Ordering::SeqCst) != token {\r\n            return;\r\n        }\r\n\r\n        let _ = send_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    });\r\n}\r\n\r\nfn resolve_effective_duration_seconds(duration_seconds: f32, frequency_hz: f32) -> f32 {\r\n    let clamped_duration = duration_seconds.clamp(0.0, OPENVR_MAX_DURATION_SECONDS);\r\n    if clamped_duration > 0.0 {\r\n        return clamped_duration;\r\n    }\r\n\r\n    // OpenVR guidance: when duration is 0, trigger exactly one pulse.\r\n    // We approximate one pulse as half a period at the requested frequency.\r\n    (0.5 / frequency_hz).max(0.001)\r\n}\r\n\r\nfn process_pending_stop() {\r\n    let deadline = STOP_DEADLINE_MS.load(Ordering::SeqCst);\r\n    if deadline == 0 {\r\n        return;\r\n    }\r\n\r\n    if now_ms() >= deadline {\r\n        let _ = send_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        let _ = send_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    if req.frequency <= 0.0 {\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        let _ = send_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let amplitude = req.amplitude.clamp(0.0, 1.0);\r\n    let frequency_hz = req\r\n        .frequency\r\n        .clamp(OPENVR_MIN_FREQUENCY_HZ, OPENVR_MAX_FREQUENCY_HZ);\r\n\r\n    let _ = send_amplitude(amplitude);\r\n    let effective_duration = resolve_effective_duration_seconds(req.duration_seconds, frequency_hz);\r\n    schedule_stop_after(effective_duration);\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n    process_pending_stop();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","epoch":1579,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs::request_b0ebfe2c-99b2-4212-9657-77428a9baa8d",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_b0ebfe2c-99b2-4212-9657-77428a9baa8d","content":"use embedded_hal::i2c::I2c;\r\n\r\nuse crate::drv2605l_parser::Drv2605lCommand;\r\n\r\npub const DRV2605L_I2C_ADDR: u8 = 0x5a;\r\n\r\nconst REG_MODE: u8 = 0x01;\r\nconst REG_RTP_INPUT: u8 = 0x02;\r\nconst REG_LIBRARY_SELECTION: u8 = 0x03;\r\nconst REG_WAVEFORM_SEQ1: u8 = 0x04;\r\nconst REG_WAVEFORM_SEQ2: u8 = 0x05;\r\nconst REG_GO: u8 = 0x0c;\r\n\r\nconst MODE_INTERNAL_TRIGGER: u8 = 0x00;\r\nconst MODE_REALTIME_PLAYBACK: u8 = 0x05;\r\n\r\n#[derive(Clone, Copy, Debug, Eq, PartialEq)]\r\npub enum DispatchError<E> {\r\n    Parse(crate::drv2605l_parser::ParseError),\r\n    I2c(E),\r\n}\r\n\r\npub struct Drv2605l<I2C> {\r\n    i2c: I2C,\r\n    address: u8,\r\n}\r\n\r\nimpl<I2C> Drv2605l<I2C>\r\nwhere\r\n    I2C: I2c,\r\n{\r\n    pub fn new(i2c: I2C) -> Self {\r\n        Self {\r\n            i2c,\r\n            address: DRV2605L_I2C_ADDR,\r\n        }\r\n    }\r\n\r\n    pub fn with_address(i2c: I2C, address: u8) -> Self {\r\n        Self { i2c, address }\r\n    }\r\n\r\n    pub fn release(self) -> I2C {\r\n        self.i2c\r\n    }\r\n\r\n    pub fn write_register(&mut self, register: u8, value: u8) -> Result<(), I2C::Error> {\r\n        self.i2c.write(self.address, &[register, value])\r\n    }\r\n\r\n    pub fn set_rtp_mode(&mut self) -> Result<(), I2C::Error> {\r\n        self.write_register(REG_MODE, MODE_REALTIME_PLAYBACK)\r\n    }\r\n\r\n    pub fn set_rtp_input(&mut self, intensity: u8) -> Result<(), I2C::Error> {\r\n        self.write_register(REG_RTP_INPUT, intensity)\r\n    }\r\n\r\n    pub fn trigger_rom_effect(&mut self, effect_id: u8) -> Result<(), I2C::Error> {\r\n        self.write_register(REG_MODE, MODE_INTERNAL_TRIGGER)?;\r\n        self.write_register(REG_LIBRARY_SELECTION, 1)?;\r\n        self.write_register(REG_WAVEFORM_SEQ1, effect_id)?;\r\n        self.write_register(REG_WAVEFORM_SEQ2, 0)?;\r\n        self.write_register(REG_GO, 1)\r\n    }\r\n\r\n    pub fn stop(&mut self) -> Result<(), I2C::Error> {\r\n        self.write_register(REG_GO, 0)?;\r\n        self.set_rtp_mode()?;\r\n        self.set_rtp_input(0)\r\n    }\r\n\r\n    pub fn apply_command(&mut self, cmd: Drv2605lCommand) -> Result<(), I2C::Error> {\r\n        match cmd {\r\n            Drv2605lCommand::SetIntensity { intensity } => {\r\n                self.set_rtp_mode()?;\r\n                self.set_rtp_input(intensity)\r\n            }\r\n            Drv2605lCommand::TriggerRomEffect { effect_id, intensity: _ } => {\r\n                self.trigger_rom_effect(effect_id)\r\n            }\r\n            Drv2605lCommand::Stop => self.stop(),\r\n            Drv2605lCommand::Unknown { .. } => Ok(()),\r\n        }\r\n    }\r\n}\r\n","epoch":1584,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs::request_f47e2855-d76f-4d49-b524-bdc5e267fde9",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_f47e2855-d76f-4d49-b524-bdc5e267fde9","content":"use embedded_hal::i2c::I2c;\r\n\r\nuse crate::drv2605l_parser::Drv2605lCommand;\r\n\r\npub const DRV2605L_I2C_ADDR: u8 = 0x5a;\r\n\r\nconst REG_MODE: u8 = 0x01;\r\nconst REG_RTP_INPUT: u8 = 0x02;\r\nconst REG_LIBRARY_SELECTION: u8 = 0x03;\r\nconst REG_WAVEFORM_SEQ1: u8 = 0x04;\r\nconst REG_WAVEFORM_SEQ2: u8 = 0x05;\r\nconst REG_GO: u8 = 0x0c;\r\n\r\nconst MODE_INTERNAL_TRIGGER: u8 = 0x00;\r\nconst MODE_REALTIME_PLAYBACK: u8 = 0x05;\r\n\r\n#[derive(Clone, Copy, Debug, Eq, PartialEq)]\r\npub enum DispatchError<E> {\r\n    Parse(crate::drv2605l_parser::ParseError),\r\n    I2c(E),\r\n}\r\n\r\npub struct Drv2605l<I2C> {\r\n    i2c: I2C,\r\n    address: u8,\r\n    last_rtp_intensity: u8,\r\n}\r\n\r\nimpl<I2C> Drv2605l<I2C>\r\nwhere\r\n    I2C: I2c,\r\n{\r\n    pub fn new(i2c: I2C) -> Self {\r\n        Self {\r\n            i2c,\r\n            address: DRV2605L_I2C_ADDR,\r\n            last_rtp_intensity: 0,\r\n        }\r\n    }\r\n\r\n    pub fn with_address(i2c: I2C, address: u8) -> Self {\r\n        Self {\r\n            i2c,\r\n            address,\r\n            last_rtp_intensity: 0,\r\n        }\r\n    }\r\n\r\n    pub fn release(self) -> I2C {\r\n        self.i2c\r\n    }\r\n\r\n    pub fn write_register(&mut self, register: u8, value: u8) -> Result<(), I2C::Error> {\r\n        self.i2c.write(self.address, &[register, value])\r\n    }\r\n\r\n    fn write_register_retry(&mut self, register: u8, value: u8) -> Result<(), I2C::Error> {\r\n        // Transient NACKs can occur during bursty command streams; one retry\r\n        // improves robustness without significantly increasing command latency.\r\n        if let Err(_first_err) = self.write_register(register, value) {\r\n            return self.write_register(register, value);\r\n        }\r\n        Ok(())\r\n    }\r\n\r\n    pub fn set_rtp_mode(&mut self) -> Result<(), I2C::Error> {\r\n        self.write_register_retry(REG_MODE, MODE_REALTIME_PLAYBACK)\r\n    }\r\n\r\n    pub fn set_rtp_input(&mut self, intensity: u8) -> Result<(), I2C::Error> {\r\n        self.write_register_retry(REG_RTP_INPUT, intensity)\r\n    }\r\n\r\n    pub fn trigger_rom_effect(&mut self, effect_id: u8) -> Result<(), I2C::Error> {\r\n        self.write_register_retry(REG_MODE, MODE_INTERNAL_TRIGGER)?;\r\n        self.write_register_retry(REG_LIBRARY_SELECTION, 1)?;\r\n        self.write_register_retry(REG_WAVEFORM_SEQ1, effect_id)?;\r\n        self.write_register_retry(REG_WAVEFORM_SEQ2, 0)?;\r\n        self.write_register_retry(REG_GO, 1)\r\n    }\r\n\r\n    pub fn stop(&mut self) -> Result<(), I2C::Error> {\r\n        self.write_register_retry(REG_GO, 0)?;\r\n        self.set_rtp_mode()?;\r\n        self.set_rtp_input(0)?;\r\n        self.last_rtp_intensity = 0;\r\n        Ok(())\r\n    }\r\n\r\n    fn apply_set_intensity(&mut self, intensity: u8) -> Result<(), I2C::Error> {\r\n        self.set_rtp_mode()?;\r\n\r\n        // Force a low->high edge for repeated pulses. Some actuators can feel\r\n        // like they \"drop out\" when only receiving the same non-zero RTP value.\r\n        if intensity > 0 && self.last_rtp_intensity > 0 {\r\n            self.set_rtp_input(0)?;\r\n            self.last_rtp_intensity = 0;\r\n        }\r\n\r\n        self.set_rtp_input(intensity)?;\r\n        self.last_rtp_intensity = intensity;\r\n        Ok(())\r\n    }\r\n\r\n    fn recover_after_error(&mut self) {\r\n        let _ = self.set_rtp_mode();\r\n        let _ = self.set_rtp_input(0);\r\n        self.last_rtp_intensity = 0;\r\n    }\r\n\r\n    pub fn apply_command(&mut self, cmd: Drv2605lCommand) -> Result<(), I2C::Error> {\r\n        let result = match cmd {\r\n            Drv2605lCommand::SetIntensity { intensity } => {\r\n                if intensity == 0 {\r\n                    self.stop()\r\n                } else {\r\n                    self.apply_set_intensity(intensity)\r\n                }\r\n            }\r\n            Drv2605lCommand::TriggerRomEffect { effect_id, intensity: _ } => {\r\n                self.trigger_rom_effect(effect_id)\r\n            }\r\n            Drv2605lCommand::Stop => self.stop(),\r\n            Drv2605lCommand::Unknown { .. } => Ok(()),\r\n        };\r\n\r\n        if result.is_err() {\r\n            self.recover_after_error();\r\n        }\r\n\r\n        result\r\n    }\r\n}\r\n","epoch":1588,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs::request_38f8befb-ed20-48d4-a718-4a697ba92587",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\openvr_runtime.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","scheme":"file"},"requestId":"request_38f8befb-ed20-48d4-a718-4a697ba92587","content":"//! OpenVR runtime integration helpers.\r\n//!\r\n//! This module owns driver-context interface resolution, tracked-device\r\n//! registration, component/property setup, and OpenVR haptic event routing.\r\n\r\nuse core::ffi::c_void;\r\nuse core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU64, Ordering};\r\nuse std::ffi::CString;\r\nuse std::os::raw::c_char;\r\n\r\nuse crate::haptics;\r\n\r\ntype EvRInitError = i32;\r\n\r\nconst IVR_SERVER_DRIVER_HOST_VERSION: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\nconst IVR_DRIVER_INPUT_VERSION: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_PROPERTIES_VERSION: &[u8] = b\"IVRProperties_001\\0\";\r\nconst IVR_DRIVER_LOG_VERSION: &[u8] = b\"IVRDriverLog_001\\0\";\r\n\r\nconst TRACKED_DEVICE_CLASS_CONTROLLER: i32 = 2;\r\nconst VREVENT_INPUT_HAPTIC_VIBRATION: u32 = 1700;\r\n\r\nconst PROP_MODEL_NUMBER_STRING: u32 = 1001;\r\nconst PROP_SERIAL_NUMBER_STRING: u32 = 1002;\r\nconst PROP_TRACKING_SYSTEM_NAME_STRING: u32 = 1000;\r\nconst PROP_REGISTERED_DEVICE_TYPE_STRING: u32 = 1036;\r\nconst PROP_INPUT_PROFILE_PATH_STRING: u32 = 1037;\r\nconst PROP_CONTROLLER_TYPE_STRING: u32 = 7000;\r\nconst PROP_CONTROLLER_ROLE_HINT_INT32: u32 = 3007;\r\nconst PROP_HAS_CONTROLLER_COMPONENT_BOOL: u32 = 6003;\r\n\r\nconst K_UN_BOOL_PROPERTY_TAG: u32 = 4;\r\nconst K_UN_INT32_PROPERTY_TAG: u32 = 2;\r\nconst K_UN_STRING_PROPERTY_TAG: u32 = 5;\r\nconst PROPERTY_WRITE_SET: i32 = 0;\r\n\r\nconst DEVICE_SERIAL_CSTR: &[u8] = b\"qtpy-samd21-haptic-001\\0\";\r\nconst DEVICE_MODEL_CSTR: &[u8] = b\"QT Py SAMD21 Haptic\\0\";\r\nconst DEVICE_TRACKING_SYSTEM_NAME_CSTR: &[u8] = b\"big_haptic_driver\\0\";\r\nconst DEVICE_REGISTERED_TYPE_CSTR: &[u8] = b\"big_haptic_driver/controller\\0\";\r\nconst DEVICE_CONTROLLER_TYPE_CSTR: &[u8] = b\"big_haptic_driver\\0\";\r\nconst DEVICE_INPUT_PROFILE_CSTR: &[u8] = b\"{big_haptic_driver}/input/big_haptic_profile.json\\0\";\r\nconst DEVICE_HAPTIC_PATH_CSTR: &[u8] = b\"/output/haptic\\0\";\r\n\r\nconst PROVIDER_IFACE_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_004\\0\";\r\nconst PROVIDER_IFACE_ALT_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_005\\0\";\r\nconst DEVICE_IFACE_CSTR: &[u8] = b\"ITrackedDeviceServerDriver_005\\0\";\r\n\r\nconst IVR_PROPERTIES_CSTR: &[u8] = b\"IVRProperties_001\\0\";\r\nconst IVR_DRIVER_INPUT_CSTR: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_SERVER_DRIVER_HOST_CSTR: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\nconst IVR_DRIVER_LOG_CSTR: &[u8] = b\"IVRDriverLog_001\\0\";\r\n\r\nconst IVR_SETTINGS_CSTR: &[u8] = b\"IVRSettings_003\\0\";\r\nconst IVR_DRIVER_MANAGER_CSTR: &[u8] = b\"IVRDriverManager_001\\0\";\r\nconst IVR_RESOURCES_CSTR: &[u8] = b\"IVRResources_001\\0\";\r\n\r\n#[repr(C)]\r\nstruct InterfaceVersions([*const c_char; 11]);\r\nunsafe impl Sync for InterfaceVersions {}\r\n\r\nstatic INTERFACE_VERSIONS: InterfaceVersions = InterfaceVersions([\r\n    IVR_SETTINGS_CSTR.as_ptr().cast(),\r\n    DEVICE_IFACE_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_CSTR.as_ptr().cast(),\r\n    IVR_PROPERTIES_CSTR.as_ptr().cast(),\r\n    IVR_SERVER_DRIVER_HOST_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_LOG_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_MANAGER_CSTR.as_ptr().cast(),\r\n    IVR_RESOURCES_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_INPUT_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_ALT_CSTR.as_ptr().cast(),\r\n    core::ptr::null(),\r\n]);\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREventHapticVibration {\r\n    container_handle: u64,\r\n    component_handle: u64,\r\n    duration_seconds: f32,\r\n    frequency: f32,\r\n    amplitude: f32,\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nunion VREventData {\r\n    haptic_vibration: VREventHapticVibration,\r\n    reserved: [u8; 48],\r\n}\r\n\r\nimpl Default for VREventData {\r\n    fn default() -> Self {\r\n        Self { reserved: [0; 48] }\r\n    }\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREvent {\r\n    event_type: u32,\r\n    tracked_device_index: u32,\r\n    event_age_seconds: f32,\r\n    data: VREventData,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContextVTable {\r\n    get_generic_interface:\r\n        extern \"C\" fn(*mut DriverContext, *const c_char, *mut EvRInitError) -> *mut c_void,\r\n    get_driver_handle: extern \"C\" fn(*mut DriverContext) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContext {\r\n    vtable: *const DriverContextVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHostVTable {\r\n    tracked_device_added:\r\n        extern \"C\" fn(*mut ServerDriverHost, *const c_char, i32, *mut c_void) -> bool,\r\n    tracked_device_pose_updated: extern \"C\" fn(*mut ServerDriverHost, u32, *const c_void, u32),\r\n    vsync_event: extern \"C\" fn(*mut ServerDriverHost, f64),\r\n    vendor_specific_event: extern \"C\" fn(*mut ServerDriverHost, u32, u32, *const VREventData, f64),\r\n    is_exiting: extern \"C\" fn(*mut ServerDriverHost) -> bool,\r\n    poll_next_event: extern \"C\" fn(*mut ServerDriverHost, *mut VREvent, u32) -> bool,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHost {\r\n    vtable: *const ServerDriverHostVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverLogVTable {\r\n    log: extern \"C\" fn(*mut DriverLog, *const c_char),\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverLog {\r\n    vtable: *const DriverLogVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverInputVTable {\r\n    create_boolean_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n    update_boolean_component: extern \"C\" fn(*mut DriverInput, u64, bool, f64) -> i32,\r\n    create_scalar_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64, i32, i32) -> i32,\r\n    update_scalar_component: extern \"C\" fn(*mut DriverInput, u64, f32, f64) -> i32,\r\n    create_haptic_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverInput {\r\n    vtable: *const DriverInputVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct PropertiesVTable {\r\n    read_property_batch: extern \"C\" fn(*mut Properties, u64, *mut c_void, u32) -> i32,\r\n    write_property_batch: extern \"C\" fn(*mut Properties, u64, *mut PropertyWrite, u32) -> i32,\r\n    get_prop_error_name_from_enum: extern \"C\" fn(*mut Properties, i32) -> *const c_char,\r\n    tracked_device_to_property_container: extern \"C\" fn(*mut Properties, u32) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct Properties {\r\n    vtable: *const PropertiesVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct PropertyWrite {\r\n    prop: u32,\r\n    write_type: i32,\r\n    set_error: i32,\r\n    pv_buffer: *mut c_void,\r\n    un_buffer_size: u32,\r\n    un_tag: u32,\r\n    e_error: i32,\r\n}\r\n\r\nstatic DEVICE_REGISTERED: AtomicBool = AtomicBool::new(false);\r\n\r\nstatic SERVER_HOST_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DRIVER_INPUT_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic PROPERTIES_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DRIVER_LOG_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\n\r\nstatic DEVICE_CONTAINER: AtomicU64 = AtomicU64::new(0);\r\nstatic HAPTIC_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\nstatic HAPTIC_EVENT_SEQ: AtomicU64 = AtomicU64::new(0);\r\n\r\npub fn interface_versions_ptr() -> *const *const c_char {\r\n    INTERFACE_VERSIONS.0.as_ptr()\r\n}\r\n\r\nfn get_server_host() -> Option<*mut ServerDriverHost> {\r\n    let ptr = SERVER_HOST_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_driver_input() -> Option<*mut DriverInput> {\r\n    let ptr = DRIVER_INPUT_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_properties() -> Option<*mut Properties> {\r\n    let ptr = PROPERTIES_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_driver_log() -> Option<*mut DriverLog> {\r\n    let ptr = DRIVER_LOG_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn log_driver_message(message: &str) {\r\n    let Some(driver_log) = get_driver_log() else {\r\n        return;\r\n    };\r\n\r\n    let Ok(c_message) = CString::new(message) else {\r\n        return;\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*driver_log).vtable).log)(driver_log, c_message.as_ptr());\r\n    }\r\n}\r\n\r\nfn write_string_property(props: *mut Properties, container: u64, prop: u32, value_nul: &'static [u8]) {\r\n    if props.is_null() || value_nul.is_empty() {\r\n        return;\r\n    }\r\n\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: value_nul.as_ptr().cast_mut().cast(),\r\n        un_buffer_size: value_nul.len() as u32,\r\n        un_tag: K_UN_STRING_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn write_bool_property(props: *mut Properties, container: u64, prop: u32, value: bool) {\r\n    if props.is_null() {\r\n        return;\r\n    }\r\n\r\n    let mut bool_value = value;\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: (&mut bool_value as *mut bool).cast(),\r\n        un_buffer_size: core::mem::size_of::<bool>() as u32,\r\n        un_tag: K_UN_BOOL_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn write_int32_property(props: *mut Properties, container: u64, prop: u32, value: i32) {\r\n    if props.is_null() {\r\n        return;\r\n    }\r\n\r\n    let mut int_value = value;\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: (&mut int_value as *mut i32).cast(),\r\n        un_buffer_size: core::mem::size_of::<i32>() as u32,\r\n        un_tag: K_UN_INT32_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn register_tracked_device_if_needed(device_ptr: *mut c_void) {\r\n    if DEVICE_REGISTERED.load(Ordering::SeqCst) {\r\n        return;\r\n    }\r\n\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    let added = unsafe {\r\n        ((*(*host).vtable).tracked_device_added)(\r\n            host,\r\n            DEVICE_SERIAL_CSTR.as_ptr().cast(),\r\n            TRACKED_DEVICE_CLASS_CONTROLLER,\r\n            device_ptr,\r\n        )\r\n    };\r\n\r\n    if added {\r\n        DEVICE_REGISTERED.store(true, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\npub fn initialize(driver_context: *mut c_void, device_ptr: *mut c_void) -> bool {\r\n    if driver_context.is_null() {\r\n        return false;\r\n    }\r\n\r\n    let ctx = driver_context.cast::<DriverContext>();\r\n    let mut err = 0;\r\n\r\n    let get_iface = unsafe { (*(*ctx).vtable).get_generic_interface };\r\n\r\n    let host = get_iface(\r\n        ctx,\r\n        IVR_SERVER_DRIVER_HOST_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let driver_input = get_iface(\r\n        ctx,\r\n        IVR_DRIVER_INPUT_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let properties = get_iface(\r\n        ctx,\r\n        IVR_PROPERTIES_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let driver_log = get_iface(\r\n        ctx,\r\n        IVR_DRIVER_LOG_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    SERVER_HOST_IFACE.store(host, Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(driver_input, Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(properties, Ordering::SeqCst);\r\n    DRIVER_LOG_IFACE.store(driver_log, Ordering::SeqCst);\r\n\r\n    let ok = !host.is_null() && !driver_input.is_null() && !properties.is_null();\r\n    if !ok {\r\n        return false;\r\n    }\r\n\r\n    log_driver_message(&format!(\r\n        \"[big_haptic_driver] OpenVR init ok; sizeof(VREvent)={}, sizeof(VREventHapticVibration)={}\",\r\n        core::mem::size_of::<VREvent>(),\r\n        core::mem::size_of::<VREventHapticVibration>()\r\n    ));\r\n\r\n    register_tracked_device_if_needed(device_ptr);\r\n    true\r\n}\r\n\r\npub fn cleanup() {\r\n    DEVICE_REGISTERED.store(false, Ordering::SeqCst);\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    SERVER_HOST_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DRIVER_LOG_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n}\r\n\r\npub fn route_haptic_events() {\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    loop {\r\n        let mut event = VREvent::default();\r\n        let ok = unsafe {\r\n            ((*(*host).vtable).poll_next_event)(\r\n                host,\r\n                &mut event as *mut VREvent,\r\n                core::mem::size_of::<VREvent>() as u32,\r\n            )\r\n        };\r\n\r\n        if !ok {\r\n            break;\r\n        }\r\n\r\n        if event.event_type != VREVENT_INPUT_HAPTIC_VIBRATION {\r\n            continue;\r\n        }\r\n\r\n        let hv = unsafe { event.data.haptic_vibration };\r\n        let seq = HAPTIC_EVENT_SEQ.fetch_add(1, Ordering::SeqCst).saturating_add(1);\r\n\r\n        log_driver_message(&format!(\r\n            \"[big_haptic_driver] haptic event #{}: dev={} age_s={:.6} container={} component={} duration_s={:.6} (0x{:08x}) freq={:.3} amp={:.6} (0x{:08x})\",\r\n            seq,\r\n            event.tracked_device_index,\r\n            event.event_age_seconds,\r\n            hv.container_handle,\r\n            hv.component_handle,\r\n            hv.duration_seconds,\r\n            hv.duration_seconds.to_bits(),\r\n            hv.frequency,\r\n            hv.amplitude,\r\n            hv.amplitude.to_bits()\r\n        ));\r\n\r\n        if hv.amplitude > 0.0 && hv.duration_seconds <= 0.0 {\r\n            log_driver_message(&format!(\r\n                \"[big_haptic_driver] note: OpenVR sent duration<=0; per OpenVR guidance driver will emit one finite pulse (freq={:.3})\",\r\n                hv.frequency\r\n            ));\r\n        }\r\n\r\n        let container = DEVICE_CONTAINER.load(Ordering::SeqCst);\r\n        if container != 0 && hv.container_handle != 0 && hv.container_handle != container {\r\n            log_driver_message(&format!(\r\n                \"[big_haptic_driver] skipped haptic event: container mismatch event={} driver={}\",\r\n                hv.container_handle, container\r\n            ));\r\n            continue;\r\n        }\r\n\r\n        let haptic_component = HAPTIC_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n        if haptic_component != 0 && hv.component_handle != 0 && hv.component_handle != haptic_component {\r\n            log_driver_message(&format!(\r\n                \"[big_haptic_driver] skipped haptic event: component mismatch event={} driver={}\",\r\n                hv.component_handle, haptic_component\r\n            ));\r\n            continue;\r\n        }\r\n\r\n        log_driver_message(\"[big_haptic_driver] forwarding haptic request to HID layer\");\r\n\r\n        haptics::handle_haptic_request(haptics::HapticVibrationRequest {\r\n            amplitude: hv.amplitude,\r\n            duration_seconds: hv.duration_seconds,\r\n            frequency: hv.frequency,\r\n        });\r\n    }\r\n}\r\n\r\npub fn push_pose_update(object_id: u32, pose: *const c_void, pose_size: u32) {\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    if pose.is_null() || pose_size == 0 {\r\n        return;\r\n    }\r\n\r\n    unsafe {\r\n        ((*(*host).vtable).tracked_device_pose_updated)(host, object_id, pose, pose_size);\r\n    }\r\n}\r\n\r\npub fn activate_device(object_id: u32) -> bool {\r\n    let Some(props) = get_properties() else {\r\n        return false;\r\n    };\r\n\r\n    let container = unsafe { ((*(*props).vtable).tracked_device_to_property_container)(props, object_id) };\r\n    DEVICE_CONTAINER.store(container, Ordering::SeqCst);\r\n\r\n    write_string_property(props, container, PROP_MODEL_NUMBER_STRING, DEVICE_MODEL_CSTR);\r\n    write_string_property(props, container, PROP_SERIAL_NUMBER_STRING, DEVICE_SERIAL_CSTR);\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_TRACKING_SYSTEM_NAME_STRING,\r\n        DEVICE_TRACKING_SYSTEM_NAME_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_REGISTERED_DEVICE_TYPE_STRING,\r\n        DEVICE_REGISTERED_TYPE_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_INPUT_PROFILE_PATH_STRING,\r\n        DEVICE_INPUT_PROFILE_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_CONTROLLER_TYPE_STRING,\r\n        DEVICE_CONTROLLER_TYPE_CSTR,\r\n    );\r\n    write_bool_property(props, container, PROP_HAS_CONTROLLER_COMPONENT_BOOL, true);\r\n    write_int32_property(props, container, PROP_CONTROLLER_ROLE_HINT_INT32, 1);\r\n\r\n    if let Some(input) = get_driver_input() {\r\n        let mut handle = 0_u64;\r\n        let rc = unsafe {\r\n            ((*(*input).vtable).create_haptic_component)(\r\n                input,\r\n                container,\r\n                DEVICE_HAPTIC_PATH_CSTR.as_ptr().cast(),\r\n                &mut handle as *mut u64,\r\n            )\r\n        };\r\n\r\n        if rc == 0 {\r\n            HAPTIC_COMPONENT_HANDLE.store(handle, Ordering::SeqCst);\r\n        }\r\n    }\r\n\r\n    true\r\n}\r\n\r\npub fn deactivate_device() {\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n}\r\n","epoch":1592,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs::request_38f8befb-ed20-48d4-a718-4a697ba92587",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_38f8befb-ed20-48d4-a718-4a697ba92587","content":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse core::sync::atomic::{AtomicU64, Ordering};\r\nuse std::sync::{Mutex, OnceLock};\r\nuse std::time::Instant;\r\nuse std::time::Duration;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic STOP_DEADLINE_MS: AtomicU64 = AtomicU64::new(0);\r\nstatic STOP_TIMER_TOKEN: AtomicU64 = AtomicU64::new(0);\r\nstatic START_INSTANT: OnceLock<Instant> = OnceLock::new();\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\n\r\nconst OPENVR_MIN_FREQUENCY_HZ: f32 = 1_000_000.0 / 65_535.0;\r\nconst OPENVR_MAX_FREQUENCY_HZ: f32 = 1_000_000.0 / 300.0;\r\nconst OPENVR_MAX_DURATION_SECONDS: f32 = 10.0;\r\nconst HARDWARE_MIN_PULSE_SECONDS: f32 = 0.02;\r\n\r\nfn send_packet(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    hid_bridge::HidBridge::new()\r\n        .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    send_packet(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn now_ms() -> u64 {\r\n    let start = START_INSTANT.get_or_init(Instant::now);\r\n    let ms = Instant::now().duration_since(*start).as_millis();\r\n    ms.min(u128::from(u64::MAX)) as u64\r\n}\r\n\r\nfn schedule_stop_after(duration_seconds: f32) {\r\n    if duration_seconds <= 0.0 {\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let duration_ms = (duration_seconds * 1000.0).round().max(1.0) as u64;\r\n    let deadline = now_ms().saturating_add(duration_ms);\r\n    STOP_DEADLINE_MS.store(deadline, Ordering::SeqCst);\r\n\r\n    let token = STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst).saturating_add(1);\r\n    std::thread::spawn(move || {\r\n        std::thread::sleep(Duration::from_millis(duration_ms));\r\n\r\n        if STOP_TIMER_TOKEN.load(Ordering::SeqCst) != token {\r\n            return;\r\n        }\r\n\r\n        let _ = send_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    });\r\n}\r\n\r\nfn resolve_effective_duration_seconds(duration_seconds: f32, frequency_hz: f32) -> f32 {\r\n    let clamped_duration = duration_seconds.clamp(0.0, OPENVR_MAX_DURATION_SECONDS);\r\n    if clamped_duration > 0.0 {\r\n        return clamped_duration;\r\n    }\r\n\r\n    // OpenVR guidance: when duration is 0, trigger exactly one pulse.\r\n    // We approximate one pulse as half a period at the requested frequency,\r\n    // but enforce a hardware minimum so pulses are actually perceptible.\r\n    (0.5 / frequency_hz).max(HARDWARE_MIN_PULSE_SECONDS)\r\n}\r\n\r\nfn process_pending_stop() {\r\n    let deadline = STOP_DEADLINE_MS.load(Ordering::SeqCst);\r\n    if deadline == 0 {\r\n        return;\r\n    }\r\n\r\n    if now_ms() >= deadline {\r\n        let _ = send_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        let _ = send_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    if req.frequency <= 0.0 {\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        let _ = send_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let amplitude = req.amplitude.clamp(0.0, 1.0);\r\n    let frequency_hz = req\r\n        .frequency\r\n        .clamp(OPENVR_MIN_FREQUENCY_HZ, OPENVR_MAX_FREQUENCY_HZ);\r\n\r\n    let _ = send_amplitude(amplitude);\r\n    let effective_duration = resolve_effective_duration_seconds(req.duration_seconds, frequency_hz);\r\n    schedule_stop_after(effective_duration);\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n    process_pending_stop();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","epoch":1595,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs::request_10cae230-7b11-4b8d-94f0-da978bd0fbe4",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_10cae230-7b11-4b8d-94f0-da978bd0fbe4","content":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse core::sync::atomic::{AtomicU64, Ordering};\r\nuse std::sync::{Mutex, OnceLock};\r\nuse std::time::Instant;\r\nuse std::time::Duration;\r\nuse std::thread;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic STOP_DEADLINE_MS: AtomicU64 = AtomicU64::new(0);\r\nstatic STOP_TIMER_TOKEN: AtomicU64 = AtomicU64::new(0);\r\nstatic START_INSTANT: OnceLock<Instant> = OnceLock::new();\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\n\r\nconst OPENVR_MIN_FREQUENCY_HZ: f32 = 1_000_000.0 / 65_535.0;\r\nconst OPENVR_MAX_FREQUENCY_HZ: f32 = 1_000_000.0 / 300.0;\r\nconst OPENVR_MAX_DURATION_SECONDS: f32 = 10.0;\r\nconst HARDWARE_MIN_PULSE_SECONDS: f32 = 0.02;\r\nconst HID_SEND_ATTEMPTS: usize = 3;\r\nconst HID_SEND_RETRY_DELAY_MS: u64 = 2;\r\n\r\nfn log_haptics(message: &str) {\r\n    crate::openvr_runtime::log_driver_message(message);\r\n}\r\n\r\nfn send_packet(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    let mut last_error = None;\r\n\r\n    for attempt in 1..=HID_SEND_ATTEMPTS {\r\n        match hid_bridge::HidBridge::new()\r\n            .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n        {\r\n            Ok(()) => return Ok(()),\r\n            Err(err) => {\r\n                last_error = Some(err);\r\n                if attempt < HID_SEND_ATTEMPTS {\r\n                    thread::sleep(Duration::from_millis(HID_SEND_RETRY_DELAY_MS));\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    let err = last_error.expect(\"HID send attempts exhausted without captured error\");\r\n    log_haptics(&format!(\r\n        \"haptics: HID send failed after {} attempt(s); cmd=0x{:02X}, intensity={}, arg_lo={}, arg_hi={}, err={}\",\r\n        HID_SEND_ATTEMPTS,\r\n        packet.command_id,\r\n        packet.intensity,\r\n        packet.arg_lo,\r\n        packet.arg_hi,\r\n        err\r\n    ));\r\n    Err(err)\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    send_packet(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn now_ms() -> u64 {\r\n    let start = START_INSTANT.get_or_init(Instant::now);\r\n    let ms = Instant::now().duration_since(*start).as_millis();\r\n    ms.min(u128::from(u64::MAX)) as u64\r\n}\r\n\r\nfn schedule_stop_after(duration_seconds: f32) {\r\n    if duration_seconds <= 0.0 {\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let duration_ms = (duration_seconds * 1000.0).round().max(1.0) as u64;\r\n    let deadline = now_ms().saturating_add(duration_ms);\r\n    STOP_DEADLINE_MS.store(deadline, Ordering::SeqCst);\r\n\r\n    let token = STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst).saturating_add(1);\r\n    std::thread::spawn(move || {\r\n        std::thread::sleep(Duration::from_millis(duration_ms));\r\n\r\n        if STOP_TIMER_TOKEN.load(Ordering::SeqCst) != token {\r\n            return;\r\n        }\r\n\r\n        if let Err(err) = send_stop() {\r\n            log_haptics(&format!(\"haptics: delayed stop failed: {}\", err));\r\n        }\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    });\r\n}\r\n\r\nfn resolve_effective_duration_seconds(duration_seconds: f32, frequency_hz: f32) -> f32 {\r\n    let clamped_duration = duration_seconds.clamp(0.0, OPENVR_MAX_DURATION_SECONDS);\r\n    if clamped_duration > 0.0 {\r\n        return clamped_duration;\r\n    }\r\n\r\n    // OpenVR guidance: when duration is 0, trigger exactly one pulse.\r\n    // We approximate one pulse as half a period at the requested frequency,\r\n    // but enforce a hardware minimum so pulses are actually perceptible.\r\n    (0.5 / frequency_hz).max(HARDWARE_MIN_PULSE_SECONDS)\r\n}\r\n\r\nfn process_pending_stop() {\r\n    let deadline = STOP_DEADLINE_MS.load(Ordering::SeqCst);\r\n    if deadline == 0 {\r\n        return;\r\n    }\r\n\r\n    if now_ms() >= deadline {\r\n        if let Err(err) = send_stop() {\r\n            log_haptics(&format!(\"haptics: frame stop failed: {}\", err));\r\n        }\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        if let Err(err) = send_stop() {\r\n            log_haptics(&format!(\"haptics: stop-on-zero-amplitude failed: {}\", err));\r\n        }\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    if req.frequency <= 0.0 {\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        if let Err(err) = send_stop() {\r\n            log_haptics(&format!(\"haptics: stop-on-nonpositive-frequency failed: {}\", err));\r\n        }\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let amplitude = req.amplitude.clamp(0.0, 1.0);\r\n    let frequency_hz = req\r\n        .frequency\r\n        .clamp(OPENVR_MIN_FREQUENCY_HZ, OPENVR_MAX_FREQUENCY_HZ);\r\n\r\n    if let Err(err) = send_amplitude(amplitude) {\r\n        log_haptics(&format!(\r\n            \"haptics: send_amplitude failed; amp={:.6}, freq={:.3}, duration_s={:.6}, err={}\",\r\n            amplitude,\r\n            frequency_hz,\r\n            req.duration_seconds,\r\n            err\r\n        ));\r\n        return;\r\n    }\r\n    let effective_duration = resolve_effective_duration_seconds(req.duration_seconds, frequency_hz);\r\n    schedule_stop_after(effective_duration);\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n    process_pending_stop();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","epoch":1599,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs::request_bb69abcc-71e3-469c-8c5d-1b8769eea60e",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_bb69abcc-71e3-469c-8c5d-1b8769eea60e","content":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse core::sync::atomic::{AtomicU64, Ordering};\r\nuse std::sync::{Mutex, OnceLock};\r\nuse std::time::Instant;\r\nuse std::time::Duration;\r\nuse std::thread;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic STOP_DEADLINE_MS: AtomicU64 = AtomicU64::new(0);\r\nstatic STOP_TIMER_TOKEN: AtomicU64 = AtomicU64::new(0);\r\nstatic START_INSTANT: OnceLock<Instant> = OnceLock::new();\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\nstatic HID_SEND_LOCK: Mutex<()> = Mutex::new(());\r\n\r\nconst OPENVR_MIN_FREQUENCY_HZ: f32 = 1_000_000.0 / 65_535.0;\r\nconst OPENVR_MAX_FREQUENCY_HZ: f32 = 1_000_000.0 / 300.0;\r\nconst OPENVR_MAX_DURATION_SECONDS: f32 = 10.0;\r\nconst HARDWARE_MIN_PULSE_SECONDS: f32 = 0.02;\r\nconst HID_SEND_ATTEMPTS: usize = 3;\r\nconst HID_SEND_RETRY_DELAY_MS: u64 = 2;\r\n\r\nfn log_haptics(message: &str) {\r\n    crate::openvr_runtime::log_driver_message(message);\r\n}\r\n\r\nfn send_packet(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    let _hid_guard = HID_SEND_LOCK.lock().ok();\r\n\r\n    let mut last_error = None;\r\n\r\n    for attempt in 1..=HID_SEND_ATTEMPTS {\r\n        match hid_bridge::HidBridge::new()\r\n            .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n        {\r\n            Ok(()) => return Ok(()),\r\n            Err(err) => {\r\n                last_error = Some(err);\r\n                if attempt < HID_SEND_ATTEMPTS {\r\n                    thread::sleep(Duration::from_millis(HID_SEND_RETRY_DELAY_MS));\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    let err = last_error.expect(\"HID send attempts exhausted without captured error\");\r\n    log_haptics(&format!(\r\n        \"haptics: HID send failed after {} attempt(s); cmd=0x{:02X}, intensity={}, arg_lo={}, arg_hi={}, err={}\",\r\n        HID_SEND_ATTEMPTS,\r\n        packet.command_id,\r\n        packet.intensity,\r\n        packet.arg_lo,\r\n        packet.arg_hi,\r\n        err\r\n    ));\r\n    Err(err)\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    send_packet(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn now_ms() -> u64 {\r\n    let start = START_INSTANT.get_or_init(Instant::now);\r\n    let ms = Instant::now().duration_since(*start).as_millis();\r\n    ms.min(u128::from(u64::MAX)) as u64\r\n}\r\n\r\nfn schedule_stop_after(duration_seconds: f32) {\r\n    if duration_seconds <= 0.0 {\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let duration_ms = (duration_seconds * 1000.0).round().max(1.0) as u64;\r\n    let deadline = now_ms().saturating_add(duration_ms);\r\n    STOP_DEADLINE_MS.store(deadline, Ordering::SeqCst);\r\n\r\n    let token = STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst).saturating_add(1);\r\n    std::thread::spawn(move || {\r\n        std::thread::sleep(Duration::from_millis(duration_ms));\r\n\r\n        if STOP_TIMER_TOKEN.load(Ordering::SeqCst) != token {\r\n            return;\r\n        }\r\n\r\n        if let Err(err) = send_stop() {\r\n            log_haptics(&format!(\"haptics: delayed stop failed: {}\", err));\r\n        }\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    });\r\n}\r\n\r\nfn resolve_effective_duration_seconds(duration_seconds: f32, frequency_hz: f32) -> f32 {\r\n    let clamped_duration = duration_seconds.clamp(0.0, OPENVR_MAX_DURATION_SECONDS);\r\n    if clamped_duration > 0.0 {\r\n        return clamped_duration;\r\n    }\r\n\r\n    // OpenVR guidance: when duration is 0, trigger exactly one pulse.\r\n    // We approximate one pulse as half a period at the requested frequency,\r\n    // but enforce a hardware minimum so pulses are actually perceptible.\r\n    (0.5 / frequency_hz).max(HARDWARE_MIN_PULSE_SECONDS)\r\n}\r\n\r\nfn process_pending_stop() {\r\n    let deadline = STOP_DEADLINE_MS.load(Ordering::SeqCst);\r\n    if deadline == 0 {\r\n        return;\r\n    }\r\n\r\n    if now_ms() >= deadline {\r\n        if let Err(err) = send_stop() {\r\n            log_haptics(&format!(\"haptics: frame stop failed: {}\", err));\r\n        }\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        if let Err(err) = send_stop() {\r\n            log_haptics(&format!(\"haptics: stop-on-zero-amplitude failed: {}\", err));\r\n        }\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    if req.frequency <= 0.0 {\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        if let Err(err) = send_stop() {\r\n            log_haptics(&format!(\"haptics: stop-on-nonpositive-frequency failed: {}\", err));\r\n        }\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let amplitude = req.amplitude.clamp(0.0, 1.0);\r\n    let frequency_hz = req\r\n        .frequency\r\n        .clamp(OPENVR_MIN_FREQUENCY_HZ, OPENVR_MAX_FREQUENCY_HZ);\r\n\r\n    if let Err(err) = send_amplitude(amplitude) {\r\n        log_haptics(&format!(\r\n            \"haptics: send_amplitude failed; amp={:.6}, freq={:.3}, duration_s={:.6}, err={}\",\r\n            amplitude,\r\n            frequency_hz,\r\n            req.duration_seconds,\r\n            err\r\n        ));\r\n        return;\r\n    }\r\n    let effective_duration = resolve_effective_duration_seconds(req.duration_seconds, frequency_hz);\r\n    schedule_stop_after(effective_duration);\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n    process_pending_stop();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","epoch":1603,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs::request_38df11d7-df4d-47e8-996c-ce006e7e6d59",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_38df11d7-df4d-47e8-996c-ce006e7e6d59","content":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse core::sync::atomic::{AtomicU64, Ordering};\r\nuse std::sync::{Mutex, OnceLock};\r\nuse std::time::Instant;\r\nuse std::time::Duration;\r\nuse std::thread;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic STOP_DEADLINE_MS: AtomicU64 = AtomicU64::new(0);\r\nstatic STOP_TIMER_TOKEN: AtomicU64 = AtomicU64::new(0);\r\nstatic START_INSTANT: OnceLock<Instant> = OnceLock::new();\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\nstatic HID_SEND_LOCK: Mutex<()> = Mutex::new(());\r\n\r\nconst OPENVR_MIN_FREQUENCY_HZ: f32 = 1_000_000.0 / 65_535.0;\r\nconst OPENVR_MAX_FREQUENCY_HZ: f32 = 1_000_000.0 / 300.0;\r\nconst OPENVR_MAX_DURATION_SECONDS: f32 = 10.0;\r\nconst HARDWARE_MIN_PULSE_SECONDS: f32 = 0.02;\r\nconst IDENTIFY_SIGNATURE_FREQUENCY_HZ: f32 = 200.0;\r\nconst IDENTIFY_SIGNATURE_FREQUENCY_TOLERANCE_HZ: f32 = 2.0;\r\nconst IDENTIFY_SIGNATURE_MAX_AMPLITUDE: f32 = 0.13;\r\nconst IDENTIFY_MIN_AMPLITUDE: f32 = 0.30;\r\nconst IDENTIFY_MIN_PULSE_SECONDS: f32 = 0.03;\r\nconst HID_SEND_ATTEMPTS: usize = 3;\r\nconst HID_SEND_RETRY_DELAY_MS: u64 = 2;\r\n\r\nfn log_haptics(message: &str) {\r\n    crate::openvr_runtime::log_driver_message(message);\r\n}\r\n\r\nfn send_packet(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    let _hid_guard = HID_SEND_LOCK.lock().ok();\r\n\r\n    let mut last_error = None;\r\n\r\n    for attempt in 1..=HID_SEND_ATTEMPTS {\r\n        match hid_bridge::HidBridge::new()\r\n            .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n        {\r\n            Ok(()) => return Ok(()),\r\n            Err(err) => {\r\n                last_error = Some(err);\r\n                if attempt < HID_SEND_ATTEMPTS {\r\n                    thread::sleep(Duration::from_millis(HID_SEND_RETRY_DELAY_MS));\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    let err = last_error.expect(\"HID send attempts exhausted without captured error\");\r\n    log_haptics(&format!(\r\n        \"haptics: HID send failed after {} attempt(s); cmd=0x{:02X}, intensity={}, arg_lo={}, arg_hi={}, err={}\",\r\n        HID_SEND_ATTEMPTS,\r\n        packet.command_id,\r\n        packet.intensity,\r\n        packet.arg_lo,\r\n        packet.arg_hi,\r\n        err\r\n    ));\r\n    Err(err)\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    send_packet(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn now_ms() -> u64 {\r\n    let start = START_INSTANT.get_or_init(Instant::now);\r\n    let ms = Instant::now().duration_since(*start).as_millis();\r\n    ms.min(u128::from(u64::MAX)) as u64\r\n}\r\n\r\nfn schedule_stop_after(duration_seconds: f32) {\r\n    if duration_seconds <= 0.0 {\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let duration_ms = (duration_seconds * 1000.0).round().max(1.0) as u64;\r\n    let deadline = now_ms().saturating_add(duration_ms);\r\n    STOP_DEADLINE_MS.store(deadline, Ordering::SeqCst);\r\n\r\n    let token = STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst).saturating_add(1);\r\n    std::thread::spawn(move || {\r\n        std::thread::sleep(Duration::from_millis(duration_ms));\r\n\r\n        if STOP_TIMER_TOKEN.load(Ordering::SeqCst) != token {\r\n            return;\r\n        }\r\n\r\n        if let Err(err) = send_stop() {\r\n            log_haptics(&format!(\"haptics: delayed stop failed: {}\", err));\r\n        }\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    });\r\n}\r\n\r\nfn resolve_effective_duration_seconds(duration_seconds: f32, frequency_hz: f32) -> f32 {\r\n    let clamped_duration = duration_seconds.clamp(0.0, OPENVR_MAX_DURATION_SECONDS);\r\n    if clamped_duration > 0.0 {\r\n        return clamped_duration;\r\n    }\r\n\r\n    // OpenVR guidance: when duration is 0, trigger exactly one pulse.\r\n    // We approximate one pulse as half a period at the requested frequency,\r\n    // but enforce a hardware minimum so pulses are actually perceptible.\r\n    (0.5 / frequency_hz).max(HARDWARE_MIN_PULSE_SECONDS)\r\n}\r\n\r\nfn looks_like_identify_campaign(req: HapticVibrationRequest) -> bool {\r\n    req.duration_seconds <= 0.0\r\n        && (req.frequency - IDENTIFY_SIGNATURE_FREQUENCY_HZ).abs()\r\n            <= IDENTIFY_SIGNATURE_FREQUENCY_TOLERANCE_HZ\r\n        && req.amplitude > 0.0\r\n        && req.amplitude <= IDENTIFY_SIGNATURE_MAX_AMPLITUDE\r\n}\r\n\r\nfn process_pending_stop() {\r\n    let deadline = STOP_DEADLINE_MS.load(Ordering::SeqCst);\r\n    if deadline == 0 {\r\n        return;\r\n    }\r\n\r\n    if now_ms() >= deadline {\r\n        if let Err(err) = send_stop() {\r\n            log_haptics(&format!(\"haptics: frame stop failed: {}\", err));\r\n        }\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        if let Err(err) = send_stop() {\r\n            log_haptics(&format!(\"haptics: stop-on-zero-amplitude failed: {}\", err));\r\n        }\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    if req.frequency <= 0.0 {\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        if let Err(err) = send_stop() {\r\n            log_haptics(&format!(\"haptics: stop-on-nonpositive-frequency failed: {}\", err));\r\n        }\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let amplitude = req.amplitude.clamp(0.0, 1.0);\r\n    let frequency_hz = req\r\n        .frequency\r\n        .clamp(OPENVR_MIN_FREQUENCY_HZ, OPENVR_MAX_FREQUENCY_HZ);\r\n\r\n    let is_identify_campaign = looks_like_identify_campaign(req);\r\n    let effective_amplitude = if is_identify_campaign {\r\n        amplitude.max(IDENTIFY_MIN_AMPLITUDE)\r\n    } else {\r\n        amplitude\r\n    };\r\n\r\n    if let Err(err) = send_amplitude(effective_amplitude) {\r\n        log_haptics(&format!(\r\n            \"haptics: send_amplitude failed; amp={:.6}, freq={:.3}, duration_s={:.6}, err={}\",\r\n            effective_amplitude,\r\n            frequency_hz,\r\n            req.duration_seconds,\r\n            err\r\n        ));\r\n        return;\r\n    }\r\n\r\n    let mut effective_duration =\r\n        resolve_effective_duration_seconds(req.duration_seconds, frequency_hz);\r\n    if is_identify_campaign {\r\n        effective_duration = effective_duration.max(IDENTIFY_MIN_PULSE_SECONDS);\r\n    }\r\n\r\n    schedule_stop_after(effective_duration);\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n    process_pending_stop();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","epoch":1607,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs::request_a7dd4e41-c8fa-4e43-bca4-89ec5b632f41",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_a7dd4e41-c8fa-4e43-bca4-89ec5b632f41","content":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse core::sync::atomic::{AtomicU64, Ordering};\r\nuse std::sync::{Mutex, OnceLock};\r\nuse std::time::Instant;\r\nuse std::time::Duration;\r\nuse std::thread;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic STOP_DEADLINE_MS: AtomicU64 = AtomicU64::new(0);\r\nstatic STOP_TIMER_TOKEN: AtomicU64 = AtomicU64::new(0);\r\nstatic START_INSTANT: OnceLock<Instant> = OnceLock::new();\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\nstatic HID_SEND_LOCK: Mutex<()> = Mutex::new(());\r\nstatic IDENTIFY_CAMPAIGN_DEADLINE_MS: AtomicU64 = AtomicU64::new(0);\r\n\r\nconst OPENVR_MIN_FREQUENCY_HZ: f32 = 1_000_000.0 / 65_535.0;\r\nconst OPENVR_MAX_FREQUENCY_HZ: f32 = 1_000_000.0 / 300.0;\r\nconst OPENVR_MAX_DURATION_SECONDS: f32 = 10.0;\r\nconst HARDWARE_MIN_PULSE_SECONDS: f32 = 0.02;\r\nconst IDENTIFY_SIGNATURE_FREQUENCY_HZ: f32 = 200.0;\r\nconst IDENTIFY_SIGNATURE_FREQUENCY_TOLERANCE_HZ: f32 = 2.0;\r\nconst IDENTIFY_SIGNATURE_MAX_AMPLITUDE: f32 = 0.13;\r\nconst IDENTIFY_MIN_AMPLITUDE: f32 = 0.30;\r\nconst IDENTIFY_PULSE_SECONDS: f32 = 0.50;\r\nconst IDENTIFY_CAMPAIGN_WINDOW_MS: u64 = 22_000;\r\nconst HID_SEND_ATTEMPTS: usize = 3;\r\nconst HID_SEND_RETRY_DELAY_MS: u64 = 2;\r\n\r\nfn log_haptics(message: &str) {\r\n    crate::openvr_runtime::log_driver_message(message);\r\n}\r\n\r\nfn send_packet(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    let _hid_guard = HID_SEND_LOCK.lock().ok();\r\n\r\n    let mut last_error = None;\r\n\r\n    for attempt in 1..=HID_SEND_ATTEMPTS {\r\n        match hid_bridge::HidBridge::new()\r\n            .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n        {\r\n            Ok(()) => return Ok(()),\r\n            Err(err) => {\r\n                last_error = Some(err);\r\n                if attempt < HID_SEND_ATTEMPTS {\r\n                    thread::sleep(Duration::from_millis(HID_SEND_RETRY_DELAY_MS));\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    let err = last_error.expect(\"HID send attempts exhausted without captured error\");\r\n    log_haptics(&format!(\r\n        \"haptics: HID send failed after {} attempt(s); cmd=0x{:02X}, intensity={}, arg_lo={}, arg_hi={}, err={}\",\r\n        HID_SEND_ATTEMPTS,\r\n        packet.command_id,\r\n        packet.intensity,\r\n        packet.arg_lo,\r\n        packet.arg_hi,\r\n        err\r\n    ));\r\n    Err(err)\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    send_packet(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn now_ms() -> u64 {\r\n    let start = START_INSTANT.get_or_init(Instant::now);\r\n    let ms = Instant::now().duration_since(*start).as_millis();\r\n    ms.min(u128::from(u64::MAX)) as u64\r\n}\r\n\r\nfn schedule_stop_after(duration_seconds: f32) {\r\n    if duration_seconds <= 0.0 {\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let duration_ms = (duration_seconds * 1000.0).round().max(1.0) as u64;\r\n    let deadline = now_ms().saturating_add(duration_ms);\r\n    STOP_DEADLINE_MS.store(deadline, Ordering::SeqCst);\r\n\r\n    let token = STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst).saturating_add(1);\r\n    std::thread::spawn(move || {\r\n        std::thread::sleep(Duration::from_millis(duration_ms));\r\n\r\n        if STOP_TIMER_TOKEN.load(Ordering::SeqCst) != token {\r\n            return;\r\n        }\r\n\r\n        if let Err(err) = send_stop() {\r\n            log_haptics(&format!(\"haptics: delayed stop failed: {}\", err));\r\n        }\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    });\r\n}\r\n\r\nfn resolve_effective_duration_seconds(duration_seconds: f32, frequency_hz: f32) -> f32 {\r\n    let clamped_duration = duration_seconds.clamp(0.0, OPENVR_MAX_DURATION_SECONDS);\r\n    if clamped_duration > 0.0 {\r\n        return clamped_duration;\r\n    }\r\n\r\n    // OpenVR guidance: when duration is 0, trigger exactly one pulse.\r\n    // We approximate one pulse as half a period at the requested frequency,\r\n    // but enforce a hardware minimum so pulses are actually perceptible.\r\n    (0.5 / frequency_hz).max(HARDWARE_MIN_PULSE_SECONDS)\r\n}\r\n\r\nfn looks_like_identify_campaign(req: HapticVibrationRequest) -> bool {\r\n    req.duration_seconds <= 0.0\r\n        && (req.frequency - IDENTIFY_SIGNATURE_FREQUENCY_HZ).abs()\r\n            <= IDENTIFY_SIGNATURE_FREQUENCY_TOLERANCE_HZ\r\n        && req.amplitude > 0.0\r\n        && req.amplitude <= IDENTIFY_SIGNATURE_MAX_AMPLITUDE\r\n}\r\n\r\nfn process_pending_stop() {\r\n    let deadline = STOP_DEADLINE_MS.load(Ordering::SeqCst);\r\n    if deadline == 0 {\r\n        return;\r\n    }\r\n\r\n    if now_ms() >= deadline {\r\n        if let Err(err) = send_stop() {\r\n            log_haptics(&format!(\"haptics: frame stop failed: {}\", err));\r\n        }\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        if let Err(err) = send_stop() {\r\n            log_haptics(&format!(\"haptics: stop-on-zero-amplitude failed: {}\", err));\r\n        }\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    if req.frequency <= 0.0 {\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        if let Err(err) = send_stop() {\r\n            log_haptics(&format!(\"haptics: stop-on-nonpositive-frequency failed: {}\", err));\r\n        }\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let amplitude = req.amplitude.clamp(0.0, 1.0);\r\n    let frequency_hz = req\r\n        .frequency\r\n        .clamp(OPENVR_MIN_FREQUENCY_HZ, OPENVR_MAX_FREQUENCY_HZ);\r\n\r\n    let is_identify_campaign = looks_like_identify_campaign(req);\r\n\r\n    if is_identify_campaign {\r\n        let now = now_ms();\r\n        let active_until = IDENTIFY_CAMPAIGN_DEADLINE_MS.load(Ordering::SeqCst);\r\n        if now < active_until {\r\n            return;\r\n        }\r\n\r\n        IDENTIFY_CAMPAIGN_DEADLINE_MS\r\n            .store(now.saturating_add(IDENTIFY_CAMPAIGN_WINDOW_MS), Ordering::SeqCst);\r\n    }\r\n\r\n    let effective_amplitude = if is_identify_campaign {\r\n        amplitude.max(IDENTIFY_MIN_AMPLITUDE)\r\n    } else {\r\n        amplitude\r\n    };\r\n\r\n    if let Err(err) = send_amplitude(effective_amplitude) {\r\n        log_haptics(&format!(\r\n            \"haptics: send_amplitude failed; amp={:.6}, freq={:.3}, duration_s={:.6}, err={}\",\r\n            effective_amplitude,\r\n            frequency_hz,\r\n            req.duration_seconds,\r\n            err\r\n        ));\r\n        return;\r\n    }\r\n\r\n    let mut effective_duration =\r\n        resolve_effective_duration_seconds(req.duration_seconds, frequency_hz);\r\n    if is_identify_campaign {\r\n        effective_duration = IDENTIFY_PULSE_SECONDS;\r\n    }\r\n\r\n    schedule_stop_after(effective_duration);\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n    process_pending_stop();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","epoch":1611,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs::request_7a4b86e1-4322-4b4e-9754-ee5435807546",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\openvr_runtime.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","content":"//! OpenVR runtime integration helpers.\r\n//!\r\n//! This module owns driver-context interface resolution, tracked-device\r\n//! registration, component/property setup, and OpenVR haptic event routing.\r\n\r\nuse core::ffi::c_void;\r\nuse core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU64, Ordering};\r\nuse std::ffi::CString;\r\nuse std::os::raw::c_char;\r\n\r\nuse crate::haptics;\r\n\r\ntype EvRInitError = i32;\r\n\r\nconst IVR_SERVER_DRIVER_HOST_VERSION: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\nconst IVR_DRIVER_INPUT_VERSION: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_PROPERTIES_VERSION: &[u8] = b\"IVRProperties_001\\0\";\r\nconst IVR_DRIVER_LOG_VERSION: &[u8] = b\"IVRDriverLog_001\\0\";\r\n\r\nconst TRACKED_DEVICE_CLASS_CONTROLLER: i32 = 2;\r\nconst VREVENT_INPUT_HAPTIC_VIBRATION: u32 = 1700;\r\n\r\nconst PROP_MODEL_NUMBER_STRING: u32 = 1001;\r\nconst PROP_SERIAL_NUMBER_STRING: u32 = 1002;\r\nconst PROP_TRACKING_SYSTEM_NAME_STRING: u32 = 1000;\r\nconst PROP_REGISTERED_DEVICE_TYPE_STRING: u32 = 1036;\r\nconst PROP_INPUT_PROFILE_PATH_STRING: u32 = 1037;\r\nconst PROP_CONTROLLER_TYPE_STRING: u32 = 7000;\r\nconst PROP_CONTROLLER_ROLE_HINT_INT32: u32 = 3007;\r\nconst PROP_HAS_CONTROLLER_COMPONENT_BOOL: u32 = 6003;\r\n\r\nconst K_UN_BOOL_PROPERTY_TAG: u32 = 4;\r\nconst K_UN_INT32_PROPERTY_TAG: u32 = 2;\r\nconst K_UN_STRING_PROPERTY_TAG: u32 = 5;\r\nconst PROPERTY_WRITE_SET: i32 = 0;\r\n\r\nconst DEVICE_SERIAL_CSTR: &[u8] = b\"qtpy-samd21-haptic-001\\0\";\r\nconst DEVICE_MODEL_CSTR: &[u8] = b\"QT Py SAMD21 Haptic\\0\";\r\nconst DEVICE_TRACKING_SYSTEM_NAME_CSTR: &[u8] = b\"big_haptic_driver\\0\";\r\nconst DEVICE_REGISTERED_TYPE_CSTR: &[u8] = b\"big_haptic_driver/controller\\0\";\r\nconst DEVICE_CONTROLLER_TYPE_CSTR: &[u8] = b\"big_haptic_driver\\0\";\r\nconst DEVICE_INPUT_PROFILE_CSTR: &[u8] = b\"{big_haptic_driver}/input/big_haptic_profile.json\\0\";\r\nconst DEVICE_HAPTIC_PATH_CSTR: &[u8] = b\"/output/haptic\\0\";\r\n\r\nconst PROVIDER_IFACE_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_004\\0\";\r\nconst PROVIDER_IFACE_ALT_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_005\\0\";\r\nconst DEVICE_IFACE_CSTR: &[u8] = b\"ITrackedDeviceServerDriver_005\\0\";\r\n\r\nconst IVR_PROPERTIES_CSTR: &[u8] = b\"IVRProperties_001\\0\";\r\nconst IVR_DRIVER_INPUT_CSTR: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_SERVER_DRIVER_HOST_CSTR: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\nconst IVR_DRIVER_LOG_CSTR: &[u8] = b\"IVRDriverLog_001\\0\";\r\n\r\nconst IVR_SETTINGS_CSTR: &[u8] = b\"IVRSettings_003\\0\";\r\nconst IVR_DRIVER_MANAGER_CSTR: &[u8] = b\"IVRDriverManager_001\\0\";\r\nconst IVR_RESOURCES_CSTR: &[u8] = b\"IVRResources_001\\0\";\r\n\r\n#[repr(C)]\r\nstruct InterfaceVersions([*const c_char; 11]);\r\nunsafe impl Sync for InterfaceVersions {}\r\n\r\nstatic INTERFACE_VERSIONS: InterfaceVersions = InterfaceVersions([\r\n    IVR_SETTINGS_CSTR.as_ptr().cast(),\r\n    DEVICE_IFACE_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_CSTR.as_ptr().cast(),\r\n    IVR_PROPERTIES_CSTR.as_ptr().cast(),\r\n    IVR_SERVER_DRIVER_HOST_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_LOG_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_MANAGER_CSTR.as_ptr().cast(),\r\n    IVR_RESOURCES_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_INPUT_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_ALT_CSTR.as_ptr().cast(),\r\n    core::ptr::null(),\r\n]);\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREventHapticVibration {\r\n    container_handle: u64,\r\n    component_handle: u64,\r\n    duration_seconds: f32,\r\n    frequency: f32,\r\n    amplitude: f32,\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nunion VREventData {\r\n    haptic_vibration: VREventHapticVibration,\r\n    reserved: [u8; 48],\r\n}\r\n\r\nimpl Default for VREventData {\r\n    fn default() -> Self {\r\n        Self { reserved: [0; 48] }\r\n    }\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREvent {\r\n    event_type: u32,\r\n    tracked_device_index: u32,\r\n    event_age_seconds: f32,\r\n    data: VREventData,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContextVTable {\r\n    get_generic_interface:\r\n        extern \"C\" fn(*mut DriverContext, *const c_char, *mut EvRInitError) -> *mut c_void,\r\n    get_driver_handle: extern \"C\" fn(*mut DriverContext) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContext {\r\n    vtable: *const DriverContextVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHostVTable {\r\n    tracked_device_added:\r\n        extern \"C\" fn(*mut ServerDriverHost, *const c_char, i32, *mut c_void) -> bool,\r\n    tracked_device_pose_updated: extern \"C\" fn(*mut ServerDriverHost, u32, *const c_void, u32),\r\n    vsync_event: extern \"C\" fn(*mut ServerDriverHost, f64),\r\n    vendor_specific_event: extern \"C\" fn(*mut ServerDriverHost, u32, u32, *const VREventData, f64),\r\n    is_exiting: extern \"C\" fn(*mut ServerDriverHost) -> bool,\r\n    poll_next_event: extern \"C\" fn(*mut ServerDriverHost, *mut VREvent, u32) -> bool,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHost {\r\n    vtable: *const ServerDriverHostVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverLogVTable {\r\n    log: extern \"C\" fn(*mut DriverLog, *const c_char),\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverLog {\r\n    vtable: *const DriverLogVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverInputVTable {\r\n    create_boolean_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n    update_boolean_component: extern \"C\" fn(*mut DriverInput, u64, bool, f64) -> i32,\r\n    create_scalar_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64, i32, i32) -> i32,\r\n    update_scalar_component: extern \"C\" fn(*mut DriverInput, u64, f32, f64) -> i32,\r\n    create_haptic_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverInput {\r\n    vtable: *const DriverInputVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct PropertiesVTable {\r\n    read_property_batch: extern \"C\" fn(*mut Properties, u64, *mut c_void, u32) -> i32,\r\n    write_property_batch: extern \"C\" fn(*mut Properties, u64, *mut PropertyWrite, u32) -> i32,\r\n    get_prop_error_name_from_enum: extern \"C\" fn(*mut Properties, i32) -> *const c_char,\r\n    tracked_device_to_property_container: extern \"C\" fn(*mut Properties, u32) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct Properties {\r\n    vtable: *const PropertiesVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct PropertyWrite {\r\n    prop: u32,\r\n    write_type: i32,\r\n    set_error: i32,\r\n    pv_buffer: *mut c_void,\r\n    un_buffer_size: u32,\r\n    un_tag: u32,\r\n    e_error: i32,\r\n}\r\n\r\nstatic DEVICE_REGISTERED: AtomicBool = AtomicBool::new(false);\r\n\r\nstatic SERVER_HOST_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DRIVER_INPUT_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic PROPERTIES_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DRIVER_LOG_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\n\r\nstatic DEVICE_CONTAINER: AtomicU64 = AtomicU64::new(0);\r\nstatic HAPTIC_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\nstatic HAPTIC_EVENT_SEQ: AtomicU64 = AtomicU64::new(0);\r\n\r\npub fn interface_versions_ptr() -> *const *const c_char {\r\n    INTERFACE_VERSIONS.0.as_ptr()\r\n}\r\n\r\nfn get_server_host() -> Option<*mut ServerDriverHost> {\r\n    let ptr = SERVER_HOST_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_driver_input() -> Option<*mut DriverInput> {\r\n    let ptr = DRIVER_INPUT_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_properties() -> Option<*mut Properties> {\r\n    let ptr = PROPERTIES_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_driver_log() -> Option<*mut DriverLog> {\r\n    let ptr = DRIVER_LOG_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\npub(crate) fn log_driver_message(message: &str) {\r\n    let Some(driver_log) = get_driver_log() else {\r\n        return;\r\n    };\r\n\r\n    let Ok(c_message) = CString::new(message) else {\r\n        return;\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*driver_log).vtable).log)(driver_log, c_message.as_ptr());\r\n    }\r\n}\r\n\r\nfn write_string_property(props: *mut Properties, container: u64, prop: u32, value_nul: &'static [u8]) {\r\n    if props.is_null() || value_nul.is_empty() {\r\n        return;\r\n    }\r\n\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: value_nul.as_ptr().cast_mut().cast(),\r\n        un_buffer_size: value_nul.len() as u32,\r\n        un_tag: K_UN_STRING_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn write_bool_property(props: *mut Properties, container: u64, prop: u32, value: bool) {\r\n    if props.is_null() {\r\n        return;\r\n    }\r\n\r\n    let mut bool_value = value;\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: (&mut bool_value as *mut bool).cast(),\r\n        un_buffer_size: core::mem::size_of::<bool>() as u32,\r\n        un_tag: K_UN_BOOL_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn write_int32_property(props: *mut Properties, container: u64, prop: u32, value: i32) {\r\n    if props.is_null() {\r\n        return;\r\n    }\r\n\r\n    let mut int_value = value;\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: (&mut int_value as *mut i32).cast(),\r\n        un_buffer_size: core::mem::size_of::<i32>() as u32,\r\n        un_tag: K_UN_INT32_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn register_tracked_device_if_needed(device_ptr: *mut c_void) {\r\n    if DEVICE_REGISTERED.load(Ordering::SeqCst) {\r\n        return;\r\n    }\r\n\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    let added = unsafe {\r\n        ((*(*host).vtable).tracked_device_added)(\r\n            host,\r\n            DEVICE_SERIAL_CSTR.as_ptr().cast(),\r\n            TRACKED_DEVICE_CLASS_CONTROLLER,\r\n            device_ptr,\r\n        )\r\n    };\r\n\r\n    if added {\r\n        DEVICE_REGISTERED.store(true, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\npub fn initialize(driver_context: *mut c_void, device_ptr: *mut c_void) -> bool {\r\n    if driver_context.is_null() {\r\n        return false;\r\n    }\r\n\r\n    let ctx = driver_context.cast::<DriverContext>();\r\n    let mut err = 0;\r\n\r\n    let get_iface = unsafe { (*(*ctx).vtable).get_generic_interface };\r\n\r\n    let host = get_iface(\r\n        ctx,\r\n        IVR_SERVER_DRIVER_HOST_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let driver_input = get_iface(\r\n        ctx,\r\n        IVR_DRIVER_INPUT_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let properties = get_iface(\r\n        ctx,\r\n        IVR_PROPERTIES_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let driver_log = get_iface(\r\n        ctx,\r\n        IVR_DRIVER_LOG_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    SERVER_HOST_IFACE.store(host, Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(driver_input, Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(properties, Ordering::SeqCst);\r\n    DRIVER_LOG_IFACE.store(driver_log, Ordering::SeqCst);\r\n\r\n    let ok = !host.is_null() && !driver_input.is_null() && !properties.is_null();\r\n    if !ok {\r\n        return false;\r\n    }\r\n\r\n    log_driver_message(&format!(\r\n        \"[big_haptic_driver] OpenVR init ok; sizeof(VREvent)={}, sizeof(VREventHapticVibration)={}\",\r\n        core::mem::size_of::<VREvent>(),\r\n        core::mem::size_of::<VREventHapticVibration>()\r\n    ));\r\n\r\n    register_tracked_device_if_needed(device_ptr);\r\n    true\r\n}\r\n\r\npub fn cleanup() {\r\n    DEVICE_REGISTERED.store(false, Ordering::SeqCst);\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    SERVER_HOST_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DRIVER_LOG_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n}\r\n\r\npub fn route_haptic_events() {\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    loop {\r\n        let mut event = VREvent::default();\r\n        let ok = unsafe {\r\n            ((*(*host).vtable).poll_next_event)(\r\n                host,\r\n                &mut event as *mut VREvent,\r\n                core::mem::size_of::<VREvent>() as u32,\r\n            )\r\n        };\r\n\r\n        if !ok {\r\n            break;\r\n        }\r\n\r\n        if event.event_type != VREVENT_INPUT_HAPTIC_VIBRATION {\r\n            continue;\r\n        }\r\n\r\n        let hv = unsafe { event.data.haptic_vibration };\r\n        let seq = HAPTIC_EVENT_SEQ.fetch_add(1, Ordering::SeqCst).saturating_add(1);\r\n\r\n        log_driver_message(&format!(\r\n            \"[big_haptic_driver] haptic event #{}: dev={} age_s={:.6} container={} component={} duration_s={:.6} (0x{:08x}) freq={:.3} amp={:.6} (0x{:08x})\",\r\n            seq,\r\n            event.tracked_device_index,\r\n            event.event_age_seconds,\r\n            hv.container_handle,\r\n            hv.component_handle,\r\n            hv.duration_seconds,\r\n            hv.duration_seconds.to_bits(),\r\n            hv.frequency,\r\n            hv.amplitude,\r\n            hv.amplitude.to_bits()\r\n        ));\r\n\r\n        if hv.amplitude > 0.0 && hv.duration_seconds <= 0.0 {\r\n            log_driver_message(&format!(\r\n                \"[big_haptic_driver] note: OpenVR sent duration<=0; per OpenVR guidance driver will emit one finite pulse (freq={:.3})\",\r\n                hv.frequency\r\n            ));\r\n        }\r\n\r\n        let container = DEVICE_CONTAINER.load(Ordering::SeqCst);\r\n        if container != 0 && hv.container_handle != 0 && hv.container_handle != container {\r\n            log_driver_message(&format!(\r\n                \"[big_haptic_driver] skipped haptic event: container mismatch event={} driver={}\",\r\n                hv.container_handle, container\r\n            ));\r\n            continue;\r\n        }\r\n\r\n        let haptic_component = HAPTIC_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n        if haptic_component != 0 && hv.component_handle != 0 && hv.component_handle != haptic_component {\r\n            log_driver_message(&format!(\r\n                \"[big_haptic_driver] skipped haptic event: component mismatch event={} driver={}\",\r\n                hv.component_handle, haptic_component\r\n            ));\r\n            continue;\r\n        }\r\n\r\n        log_driver_message(\"[big_haptic_driver] forwarding haptic request to HID layer\");\r\n\r\n        haptics::handle_haptic_request(haptics::HapticVibrationRequest {\r\n            amplitude: hv.amplitude,\r\n            duration_seconds: hv.duration_seconds,\r\n            frequency: hv.frequency,\r\n        });\r\n    }\r\n}\r\n\r\npub fn push_pose_update(object_id: u32, pose: *const c_void, pose_size: u32) {\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    if pose.is_null() || pose_size == 0 {\r\n        return;\r\n    }\r\n\r\n    unsafe {\r\n        ((*(*host).vtable).tracked_device_pose_updated)(host, object_id, pose, pose_size);\r\n    }\r\n}\r\n\r\npub fn activate_device(object_id: u32) -> bool {\r\n    let Some(props) = get_properties() else {\r\n        return false;\r\n    };\r\n\r\n    let container = unsafe { ((*(*props).vtable).tracked_device_to_property_container)(props, object_id) };\r\n    DEVICE_CONTAINER.store(container, Ordering::SeqCst);\r\n\r\n    write_string_property(props, container, PROP_MODEL_NUMBER_STRING, DEVICE_MODEL_CSTR);\r\n    write_string_property(props, container, PROP_SERIAL_NUMBER_STRING, DEVICE_SERIAL_CSTR);\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_TRACKING_SYSTEM_NAME_STRING,\r\n        DEVICE_TRACKING_SYSTEM_NAME_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_REGISTERED_DEVICE_TYPE_STRING,\r\n        DEVICE_REGISTERED_TYPE_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_INPUT_PROFILE_PATH_STRING,\r\n        DEVICE_INPUT_PROFILE_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_CONTROLLER_TYPE_STRING,\r\n        DEVICE_CONTROLLER_TYPE_CSTR,\r\n    );\r\n    write_bool_property(props, container, PROP_HAS_CONTROLLER_COMPONENT_BOOL, true);\r\n    write_int32_property(props, container, PROP_CONTROLLER_ROLE_HINT_INT32, 1);\r\n\r\n    if let Some(input) = get_driver_input() {\r\n        let mut handle = 0_u64;\r\n        let rc = unsafe {\r\n            ((*(*input).vtable).create_haptic_component)(\r\n                input,\r\n                container,\r\n                DEVICE_HAPTIC_PATH_CSTR.as_ptr().cast(),\r\n                &mut handle as *mut u64,\r\n            )\r\n        };\r\n\r\n        if rc == 0 {\r\n            HAPTIC_COMPONENT_HANDLE.store(handle, Ordering::SeqCst);\r\n        }\r\n    }\r\n\r\n    true\r\n}\r\n\r\npub fn deactivate_device() {\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n}\r\n","epoch":1615,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_scaffold.rs::request_7a4b86e1-4322-4b4e-9754-ee5435807546",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","content":"//! OpenVR ABI/vtable scaffolding for the Big Haptic Driver.\r\n//!\r\n//! This module owns C ABI exports and interface routing for SteamVR-facing\r\n//! provider/device entry points, while delegating runtime integration\r\n//! (device registration, component setup, and event routing) to\r\n//! `openvr_runtime`.\r\n\r\nuse core::ffi::{c_void, CStr};\r\nuse core::sync::atomic::{AtomicBool, AtomicU32, Ordering};\r\nuse std::os::raw::c_char;\r\n\r\nuse crate::haptics;\r\nuse crate::openvr_runtime;\r\n\r\nconst SERVER_PROVIDER_INTERFACE: &str = \"IServerTrackedDeviceProvider_004\";\r\nconst SERVER_PROVIDER_INTERFACE_ALT: &str = \"IServerTrackedDeviceProvider_005\";\r\nconst TRACKED_DEVICE_INTERFACE: &str = \"ITrackedDeviceServerDriver_005\";\r\nconst VR_INIT_ERROR_DRIVER_FAILED: EvRInitError = 200;\r\n\r\n#[repr(i32)]\r\nenum DriverReturnCode {\r\n    None = 0,\r\n    Unknown = 1,\r\n    InitInterfaceNotFound = 105,\r\n}\r\n\r\ntype EvRInitError = i32;\r\nconst VR_INIT_ERROR_NONE: EvRInitError = 0;\r\nconst TRACKING_RESULT_RUNNING_OK: i32 = 200;\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nstruct DriverPoseQuaternion {\r\n    w: f64,\r\n    x: f64,\r\n    y: f64,\r\n    z: f64,\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nstruct DriverPose {\r\n    pose_time_offset: f64,\r\n    q_world_from_driver_rotation: DriverPoseQuaternion,\r\n    vec_world_from_driver_translation: [f64; 3],\r\n    q_driver_from_head_rotation: DriverPoseQuaternion,\r\n    vec_driver_from_head_translation: [f64; 3],\r\n    vec_position: [f64; 3],\r\n    vec_velocity: [f64; 3],\r\n    vec_acceleration: [f64; 3],\r\n    q_rotation: DriverPoseQuaternion,\r\n    vec_angular_velocity: [f64; 3],\r\n    vec_angular_acceleration: [f64; 3],\r\n    result: i32,\r\n    pose_is_valid: bool,\r\n    will_drift_in_yaw: bool,\r\n    should_apply_head_model: bool,\r\n    device_is_connected: bool,\r\n}\r\n\r\nconst IDENTITY_QUATERNION: DriverPoseQuaternion = DriverPoseQuaternion {\r\n    w: 1.0,\r\n    x: 0.0,\r\n    y: 0.0,\r\n    z: 0.0,\r\n};\r\n\r\nfn connected_controller_pose() -> DriverPose {\r\n    DriverPose {\r\n        pose_time_offset: 0.0,\r\n        q_world_from_driver_rotation: IDENTITY_QUATERNION,\r\n        vec_world_from_driver_translation: [0.0, 0.0, 0.0],\r\n        q_driver_from_head_rotation: IDENTITY_QUATERNION,\r\n        vec_driver_from_head_translation: [0.0, 0.0, 0.0],\r\n        vec_position: [0.0, 1.2, -0.4],\r\n        vec_velocity: [0.0, 0.0, 0.0],\r\n        vec_acceleration: [0.0, 0.0, 0.0],\r\n        q_rotation: IDENTITY_QUATERNION,\r\n        vec_angular_velocity: [0.0, 0.0, 0.0],\r\n        vec_angular_acceleration: [0.0, 0.0, 0.0],\r\n        result: TRACKING_RESULT_RUNNING_OK,\r\n        pose_is_valid: true,\r\n        will_drift_in_yaw: false,\r\n        should_apply_head_model: false,\r\n        device_is_connected: true,\r\n    }\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerTrackedDeviceProviderVTable {\r\n    init: extern \"C\" fn(*mut ServerTrackedDeviceProvider, *mut c_void) -> EvRInitError,\r\n    cleanup: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    get_interface_versions:\r\n        extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> *const *const c_char,\r\n    run_frame: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    should_block_standby_mode: extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> bool,\r\n    enter_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    leave_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n}\r\n\r\n#[repr(C)]\r\nstruct TrackedDeviceServerDriverVTable {\r\n    activate: extern \"C\" fn(*mut TrackedDeviceServerDriver, u32) -> EvRInitError,\r\n    deactivate: extern \"C\" fn(*mut TrackedDeviceServerDriver),\r\n    enter_standby: extern \"C\" fn(*mut TrackedDeviceServerDriver),\r\n    get_component:\r\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char) -> *mut c_void,\r\n    debug_request:\r\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char, *mut c_char, u32),\r\n    get_pose: extern \"C\" fn(*mut TrackedDeviceServerDriver) -> DriverPose,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerTrackedDeviceProvider {\r\n    vtable: *const ServerTrackedDeviceProviderVTable,\r\n}\r\nunsafe impl Sync for ServerTrackedDeviceProvider {}\r\n\r\n#[repr(C)]\r\nstruct TrackedDeviceServerDriver {\r\n    vtable: *const TrackedDeviceServerDriverVTable,\r\n}\r\nunsafe impl Sync for TrackedDeviceServerDriver {}\r\n\r\nstatic DEVICE_INDEX: AtomicU32 = AtomicU32::new(u32::MAX);\r\nstatic DEVICE_ACTIVE: AtomicBool = AtomicBool::new(false);\r\n\r\nextern \"C\" fn provider_init(\r\n    _this: *mut ServerTrackedDeviceProvider,\r\n    driver_context: *mut c_void,\r\n) -> EvRInitError {\r\n    let ok = openvr_runtime::initialize(\r\n        driver_context,\r\n        (&DEVICE as *const TrackedDeviceServerDriver).cast_mut().cast(),\r\n    );\r\n    if !ok {\r\n        return VR_INIT_ERROR_DRIVER_FAILED;\r\n    }\r\n\r\n    VR_INIT_ERROR_NONE\r\n}\r\n\r\nextern \"C\" fn provider_cleanup(_this: *mut ServerTrackedDeviceProvider) {\r\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\r\n    openvr_runtime::cleanup();\r\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn provider_get_interface_versions(\r\n    _this: *mut ServerTrackedDeviceProvider,\r\n) -> *const *const c_char {\r\n    openvr_runtime::interface_versions_ptr()\r\n}\r\n\r\nextern \"C\" fn provider_run_frame(_this: *mut ServerTrackedDeviceProvider) {\r\n    if DEVICE_ACTIVE.load(Ordering::SeqCst) {\r\n        let object_id = DEVICE_INDEX.load(Ordering::SeqCst);\r\n        if object_id != u32::MAX {\r\n            let pose = connected_controller_pose();\r\n            openvr_runtime::push_pose_update(\r\n                object_id,\r\n                (&pose as *const DriverPose).cast(),\r\n                core::mem::size_of::<DriverPose>() as u32,\r\n            );\r\n        }\r\n    }\r\n\r\n    openvr_runtime::route_haptic_events();\r\n    haptics::on_frame();\r\n}\r\n\r\nextern \"C\" fn provider_should_block_standby_mode(_this: *mut ServerTrackedDeviceProvider) -> bool {\r\n    false\r\n}\r\n\r\nextern \"C\" fn provider_enter_standby(_this: *mut ServerTrackedDeviceProvider) {}\r\n\r\nextern \"C\" fn provider_leave_standby(_this: *mut ServerTrackedDeviceProvider) {}\r\n\r\nextern \"C\" fn device_activate(_this: *mut TrackedDeviceServerDriver, object_id: u32) -> EvRInitError {\r\n    DEVICE_INDEX.store(object_id, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(true, Ordering::SeqCst);\r\n\r\n    if !openvr_runtime::activate_device(object_id) {\r\n        return VR_INIT_ERROR_DRIVER_FAILED;\r\n    }\r\n\r\n    let pose = connected_controller_pose();\r\n    openvr_runtime::push_pose_update(\r\n        object_id,\r\n        (&pose as *const DriverPose).cast(),\r\n        core::mem::size_of::<DriverPose>() as u32,\r\n    );\r\n\r\n    VR_INIT_ERROR_NONE\r\n}\r\n\r\nextern \"C\" fn device_deactivate(_this: *mut TrackedDeviceServerDriver) {\r\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\r\n    openvr_runtime::deactivate_device();\r\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn device_enter_standby(_this: *mut TrackedDeviceServerDriver) {\r\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn device_get_component(\r\n    _this: *mut TrackedDeviceServerDriver,\r\n    _component_name: *const c_char,\r\n) -> *mut c_void {\r\n    core::ptr::null_mut()\r\n}\r\n\r\nextern \"C\" fn device_debug_request(\r\n    _this: *mut TrackedDeviceServerDriver,\r\n    _request: *const c_char,\r\n    response: *mut c_char,\r\n    response_size: u32,\r\n) {\r\n    if response.is_null() || response_size == 0 {\r\n        return;\r\n    }\r\n    unsafe {\r\n        *response = 0;\r\n    }\r\n}\r\n\r\nextern \"C\" fn device_get_pose(_this: *mut TrackedDeviceServerDriver) -> DriverPose {\r\n    connected_controller_pose()\r\n}\r\n\r\nstatic PROVIDER_VTABLE: ServerTrackedDeviceProviderVTable = ServerTrackedDeviceProviderVTable {\r\n    init: provider_init,\r\n    cleanup: provider_cleanup,\r\n    get_interface_versions: provider_get_interface_versions,\r\n    run_frame: provider_run_frame,\r\n    should_block_standby_mode: provider_should_block_standby_mode,\r\n    enter_standby: provider_enter_standby,\r\n    leave_standby: provider_leave_standby,\r\n};\r\n\r\nstatic DEVICE_VTABLE: TrackedDeviceServerDriverVTable = TrackedDeviceServerDriverVTable {\r\n    activate: device_activate,\r\n    deactivate: device_deactivate,\r\n    enter_standby: device_enter_standby,\r\n    get_component: device_get_component,\r\n    debug_request: device_debug_request,\r\n    get_pose: device_get_pose,\r\n};\r\n\r\nstatic PROVIDER: ServerTrackedDeviceProvider = ServerTrackedDeviceProvider {\r\n    vtable: &PROVIDER_VTABLE,\r\n};\r\n\r\nstatic DEVICE: TrackedDeviceServerDriver = TrackedDeviceServerDriver {\r\n    vtable: &DEVICE_VTABLE,\r\n};\r\n\r\nfn set_return_code(out: *mut i32, code: DriverReturnCode) {\r\n    if !out.is_null() {\r\n        unsafe {\r\n            *out = code as i32;\r\n        }\r\n    }\r\n}\r\n\r\nfn read_interface_name(ptr: *const i8) -> Option<&'static str> {\r\n    if ptr.is_null() {\r\n        return None;\r\n    }\r\n\r\n    let cstr = unsafe { CStr::from_ptr(ptr) };\r\n    let Ok(name) = cstr.to_str() else {\r\n        return None;\r\n    };\r\n\r\n    if name == SERVER_PROVIDER_INTERFACE {\r\n        return Some(SERVER_PROVIDER_INTERFACE);\r\n    }\r\n    if name == SERVER_PROVIDER_INTERFACE_ALT {\r\n        return Some(SERVER_PROVIDER_INTERFACE);\r\n    }\r\n    if name == TRACKED_DEVICE_INTERFACE {\r\n        return Some(TRACKED_DEVICE_INTERFACE);\r\n    }\r\n\r\n    None\r\n}\r\n\r\n/// Resolves OpenVR interface requests to static provider/device instances.\r\n///\r\n/// On match, writes `DriverReturnCode::None` to `p_return_code` and returns a\r\n/// stable interface pointer. On failure, writes an error code and returns null.\r\npub fn hmd_driver_factory(\r\n    p_interface_name: *const c_char,\r\n    p_return_code: *mut i32,\r\n) -> *mut c_void {\r\n    let Some(interface_name) = read_interface_name(p_interface_name) else {\r\n        set_return_code(p_return_code, DriverReturnCode::InitInterfaceNotFound);\r\n        return core::ptr::null_mut();\r\n    };\r\n\r\n    if interface_name == SERVER_PROVIDER_INTERFACE {\r\n        set_return_code(p_return_code, DriverReturnCode::None);\r\n        return (&PROVIDER as *const ServerTrackedDeviceProvider)\r\n            .cast_mut()\r\n            .cast();\r\n    }\r\n\r\n    if interface_name == TRACKED_DEVICE_INTERFACE {\r\n        set_return_code(p_return_code, DriverReturnCode::None);\r\n        return (&DEVICE as *const TrackedDeviceServerDriver)\r\n            .cast_mut()\r\n            .cast();\r\n    }\r\n\r\n    set_return_code(p_return_code, DriverReturnCode::Unknown);\r\n    core::ptr::null_mut()\r\n}\r\n","epoch":1618,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/big_haptic_profile.json::request_7a4b86e1-4322-4b4e-9754-ee5435807546",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\input\\big_haptic_profile.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/big_haptic_profile.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/big_haptic_profile.json","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","content":"{\r\n  \"jsonid\": \"input_profile\",\r\n  \"controller_type\": \"big_haptic_driver\",\r\n  \"input_bindingui_mode\": \"controller_handed\",\r\n  \"input_source\": {\r\n    \"haptic\": {\r\n      \"type\": \"vibration\",\r\n      \"binding_image_point\": [0.5, 0.5]\r\n    }\r\n  }\r\n}\r\n","epoch":1621,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_scaffold.rs::request_6f13d5f3-b3de-4f61-a09f-3c6ee71f5876",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_6f13d5f3-b3de-4f61-a09f-3c6ee71f5876","content":"//! OpenVR ABI/vtable scaffolding for the Big Haptic Driver.\r\n//!\r\n//! This module owns C ABI exports and interface routing for SteamVR-facing\r\n//! provider/device entry points, while delegating runtime integration\r\n//! (device registration, component setup, and event routing) to\r\n//! `openvr_runtime`.\r\n\r\nuse core::ffi::{c_void, CStr};\r\nuse core::sync::atomic::{AtomicBool, AtomicU32, Ordering};\r\nuse std::os::raw::c_char;\r\n\r\nuse crate::haptics;\r\nuse crate::openvr_runtime;\r\n\r\nconst SERVER_PROVIDER_INTERFACE: &str = \"IServerTrackedDeviceProvider_004\";\r\nconst SERVER_PROVIDER_INTERFACE_ALT: &str = \"IServerTrackedDeviceProvider_005\";\r\nconst TRACKED_DEVICE_INTERFACE: &str = \"ITrackedDeviceServerDriver_005\";\r\nconst VR_INIT_ERROR_DRIVER_FAILED: EvRInitError = 200;\r\n\r\n#[repr(i32)]\r\nenum DriverReturnCode {\r\n    None = 0,\r\n    Unknown = 1,\r\n    InitInterfaceNotFound = 105,\r\n}\r\n\r\ntype EvRInitError = i32;\r\nconst VR_INIT_ERROR_NONE: EvRInitError = 0;\r\nconst TRACKING_RESULT_RUNNING_OK: i32 = 200;\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nstruct DriverPoseQuaternion {\r\n    w: f64,\r\n    x: f64,\r\n    y: f64,\r\n    z: f64,\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nstruct DriverPose {\r\n    pose_time_offset: f64,\r\n    q_world_from_driver_rotation: DriverPoseQuaternion,\r\n    vec_world_from_driver_translation: [f64; 3],\r\n    q_driver_from_head_rotation: DriverPoseQuaternion,\r\n    vec_driver_from_head_translation: [f64; 3],\r\n    vec_position: [f64; 3],\r\n    vec_velocity: [f64; 3],\r\n    vec_acceleration: [f64; 3],\r\n    q_rotation: DriverPoseQuaternion,\r\n    vec_angular_velocity: [f64; 3],\r\n    vec_angular_acceleration: [f64; 3],\r\n    result: i32,\r\n    pose_is_valid: bool,\r\n    will_drift_in_yaw: bool,\r\n    should_apply_head_model: bool,\r\n    device_is_connected: bool,\r\n}\r\n\r\nconst IDENTITY_QUATERNION: DriverPoseQuaternion = DriverPoseQuaternion {\r\n    w: 1.0,\r\n    x: 0.0,\r\n    y: 0.0,\r\n    z: 0.0,\r\n};\r\n\r\nfn connected_controller_pose() -> DriverPose {\r\n    if let Some(hmd_pose) = openvr_runtime::sample_hmd_pose() {\r\n        let rotation = hmd_pose.rotation;\r\n\r\n        let right = [rotation[0][0], rotation[1][0], rotation[2][0]];\r\n        let up = [rotation[0][1], rotation[1][1], rotation[2][1]];\r\n        let forward = [-rotation[0][2], -rotation[1][2], -rotation[2][2]];\r\n\r\n        let position = [\r\n            hmd_pose.position_m[0] + forward[0] * 0.38 + right[0] * -0.18 + up[0] * -0.18,\r\n            hmd_pose.position_m[1] + forward[1] * 0.38 + right[1] * -0.18 + up[1] * -0.18,\r\n            hmd_pose.position_m[2] + forward[2] * 0.38 + right[2] * -0.18 + up[2] * -0.18,\r\n        ];\r\n\r\n        let q_rotation = quaternion_from_rotation_matrix(rotation);\r\n\r\n        return DriverPose {\r\n            pose_time_offset: 0.0,\r\n            q_world_from_driver_rotation: IDENTITY_QUATERNION,\r\n            vec_world_from_driver_translation: [0.0, 0.0, 0.0],\r\n            q_driver_from_head_rotation: IDENTITY_QUATERNION,\r\n            vec_driver_from_head_translation: [0.0, 0.0, 0.0],\r\n            vec_position: [position[0] as f64, position[1] as f64, position[2] as f64],\r\n            vec_velocity: [0.0, 0.0, 0.0],\r\n            vec_acceleration: [0.0, 0.0, 0.0],\r\n            q_rotation,\r\n            vec_angular_velocity: [0.0, 0.0, 0.0],\r\n            vec_angular_acceleration: [0.0, 0.0, 0.0],\r\n            result: TRACKING_RESULT_RUNNING_OK,\r\n            pose_is_valid: true,\r\n            will_drift_in_yaw: false,\r\n            should_apply_head_model: false,\r\n            device_is_connected: true,\r\n        };\r\n    }\r\n\r\n    DriverPose {\r\n        pose_time_offset: 0.0,\r\n        q_world_from_driver_rotation: IDENTITY_QUATERNION,\r\n        vec_world_from_driver_translation: [0.0, 0.0, 0.0],\r\n        q_driver_from_head_rotation: IDENTITY_QUATERNION,\r\n        vec_driver_from_head_translation: [0.0, 0.0, 0.0],\r\n        vec_position: [0.0, 1.2, -0.4],\r\n        vec_velocity: [0.0, 0.0, 0.0],\r\n        vec_acceleration: [0.0, 0.0, 0.0],\r\n        q_rotation: IDENTITY_QUATERNION,\r\n        vec_angular_velocity: [0.0, 0.0, 0.0],\r\n        vec_angular_acceleration: [0.0, 0.0, 0.0],\r\n        result: TRACKING_RESULT_RUNNING_OK,\r\n        pose_is_valid: true,\r\n        will_drift_in_yaw: false,\r\n        should_apply_head_model: false,\r\n        device_is_connected: true,\r\n    }\r\n}\r\n\r\nfn quaternion_from_rotation_matrix(m: [[f32; 3]; 3]) -> DriverPoseQuaternion {\r\n    let m00 = m[0][0] as f64;\r\n    let m01 = m[0][1] as f64;\r\n    let m02 = m[0][2] as f64;\r\n    let m10 = m[1][0] as f64;\r\n    let m11 = m[1][1] as f64;\r\n    let m12 = m[1][2] as f64;\r\n    let m20 = m[2][0] as f64;\r\n    let m21 = m[2][1] as f64;\r\n    let m22 = m[2][2] as f64;\r\n\r\n    let trace = m00 + m11 + m22;\r\n    if trace > 0.0 {\r\n        let s = (trace + 1.0).sqrt() * 2.0;\r\n        return DriverPoseQuaternion {\r\n            w: 0.25 * s,\r\n            x: (m21 - m12) / s,\r\n            y: (m02 - m20) / s,\r\n            z: (m10 - m01) / s,\r\n        };\r\n    }\r\n\r\n    if m00 > m11 && m00 > m22 {\r\n        let s = (1.0 + m00 - m11 - m22).sqrt() * 2.0;\r\n        return DriverPoseQuaternion {\r\n            w: (m21 - m12) / s,\r\n            x: 0.25 * s,\r\n            y: (m01 + m10) / s,\r\n            z: (m02 + m20) / s,\r\n        };\r\n    }\r\n\r\n    if m11 > m22 {\r\n        let s = (1.0 + m11 - m00 - m22).sqrt() * 2.0;\r\n        return DriverPoseQuaternion {\r\n            w: (m02 - m20) / s,\r\n            x: (m01 + m10) / s,\r\n            y: 0.25 * s,\r\n            z: (m12 + m21) / s,\r\n        };\r\n    }\r\n\r\n    let s = (1.0 + m22 - m00 - m11).sqrt() * 2.0;\r\n    DriverPoseQuaternion {\r\n        w: (m10 - m01) / s,\r\n        x: (m02 + m20) / s,\r\n        y: (m12 + m21) / s,\r\n        z: 0.25 * s,\r\n    }\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerTrackedDeviceProviderVTable {\r\n    init: extern \"C\" fn(*mut ServerTrackedDeviceProvider, *mut c_void) -> EvRInitError,\r\n    cleanup: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    get_interface_versions:\r\n        extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> *const *const c_char,\r\n    run_frame: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    should_block_standby_mode: extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> bool,\r\n    enter_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    leave_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n}\r\n\r\n#[repr(C)]\r\nstruct TrackedDeviceServerDriverVTable {\r\n    activate: extern \"C\" fn(*mut TrackedDeviceServerDriver, u32) -> EvRInitError,\r\n    deactivate: extern \"C\" fn(*mut TrackedDeviceServerDriver),\r\n    enter_standby: extern \"C\" fn(*mut TrackedDeviceServerDriver),\r\n    get_component:\r\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char) -> *mut c_void,\r\n    debug_request:\r\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char, *mut c_char, u32),\r\n    get_pose: extern \"C\" fn(*mut TrackedDeviceServerDriver) -> DriverPose,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerTrackedDeviceProvider {\r\n    vtable: *const ServerTrackedDeviceProviderVTable,\r\n}\r\nunsafe impl Sync for ServerTrackedDeviceProvider {}\r\n\r\n#[repr(C)]\r\nstruct TrackedDeviceServerDriver {\r\n    vtable: *const TrackedDeviceServerDriverVTable,\r\n}\r\nunsafe impl Sync for TrackedDeviceServerDriver {}\r\n\r\nstatic DEVICE_INDEX: AtomicU32 = AtomicU32::new(u32::MAX);\r\nstatic DEVICE_ACTIVE: AtomicBool = AtomicBool::new(false);\r\n\r\nextern \"C\" fn provider_init(\r\n    _this: *mut ServerTrackedDeviceProvider,\r\n    driver_context: *mut c_void,\r\n) -> EvRInitError {\r\n    let ok = openvr_runtime::initialize(\r\n        driver_context,\r\n        (&DEVICE as *const TrackedDeviceServerDriver).cast_mut().cast(),\r\n    );\r\n    if !ok {\r\n        return VR_INIT_ERROR_DRIVER_FAILED;\r\n    }\r\n\r\n    VR_INIT_ERROR_NONE\r\n}\r\n\r\nextern \"C\" fn provider_cleanup(_this: *mut ServerTrackedDeviceProvider) {\r\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\r\n    openvr_runtime::cleanup();\r\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn provider_get_interface_versions(\r\n    _this: *mut ServerTrackedDeviceProvider,\r\n) -> *const *const c_char {\r\n    openvr_runtime::interface_versions_ptr()\r\n}\r\n\r\nextern \"C\" fn provider_run_frame(_this: *mut ServerTrackedDeviceProvider) {\r\n    if DEVICE_ACTIVE.load(Ordering::SeqCst) {\r\n        let object_id = DEVICE_INDEX.load(Ordering::SeqCst);\r\n        if object_id != u32::MAX {\r\n            let pose = connected_controller_pose();\r\n            openvr_runtime::push_pose_update(\r\n                object_id,\r\n                (&pose as *const DriverPose).cast(),\r\n                core::mem::size_of::<DriverPose>() as u32,\r\n            );\r\n            openvr_runtime::update_pose_components();\r\n        }\r\n    }\r\n\r\n    openvr_runtime::route_haptic_events();\r\n    haptics::on_frame();\r\n}\r\n\r\nextern \"C\" fn provider_should_block_standby_mode(_this: *mut ServerTrackedDeviceProvider) -> bool {\r\n    false\r\n}\r\n\r\nextern \"C\" fn provider_enter_standby(_this: *mut ServerTrackedDeviceProvider) {}\r\n\r\nextern \"C\" fn provider_leave_standby(_this: *mut ServerTrackedDeviceProvider) {}\r\n\r\nextern \"C\" fn device_activate(_this: *mut TrackedDeviceServerDriver, object_id: u32) -> EvRInitError {\r\n    DEVICE_INDEX.store(object_id, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(true, Ordering::SeqCst);\r\n\r\n    if !openvr_runtime::activate_device(object_id) {\r\n        return VR_INIT_ERROR_DRIVER_FAILED;\r\n    }\r\n\r\n    let pose = connected_controller_pose();\r\n    openvr_runtime::push_pose_update(\r\n        object_id,\r\n        (&pose as *const DriverPose).cast(),\r\n        core::mem::size_of::<DriverPose>() as u32,\r\n    );\r\n\r\n    VR_INIT_ERROR_NONE\r\n}\r\n\r\nextern \"C\" fn device_deactivate(_this: *mut TrackedDeviceServerDriver) {\r\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\r\n    openvr_runtime::deactivate_device();\r\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn device_enter_standby(_this: *mut TrackedDeviceServerDriver) {\r\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn device_get_component(\r\n    _this: *mut TrackedDeviceServerDriver,\r\n    _component_name: *const c_char,\r\n) -> *mut c_void {\r\n    core::ptr::null_mut()\r\n}\r\n\r\nextern \"C\" fn device_debug_request(\r\n    _this: *mut TrackedDeviceServerDriver,\r\n    _request: *const c_char,\r\n    response: *mut c_char,\r\n    response_size: u32,\r\n) {\r\n    if response.is_null() || response_size == 0 {\r\n        return;\r\n    }\r\n    unsafe {\r\n        *response = 0;\r\n    }\r\n}\r\n\r\nextern \"C\" fn device_get_pose(_this: *mut TrackedDeviceServerDriver) -> DriverPose {\r\n    connected_controller_pose()\r\n}\r\n\r\nstatic PROVIDER_VTABLE: ServerTrackedDeviceProviderVTable = ServerTrackedDeviceProviderVTable {\r\n    init: provider_init,\r\n    cleanup: provider_cleanup,\r\n    get_interface_versions: provider_get_interface_versions,\r\n    run_frame: provider_run_frame,\r\n    should_block_standby_mode: provider_should_block_standby_mode,\r\n    enter_standby: provider_enter_standby,\r\n    leave_standby: provider_leave_standby,\r\n};\r\n\r\nstatic DEVICE_VTABLE: TrackedDeviceServerDriverVTable = TrackedDeviceServerDriverVTable {\r\n    activate: device_activate,\r\n    deactivate: device_deactivate,\r\n    enter_standby: device_enter_standby,\r\n    get_component: device_get_component,\r\n    debug_request: device_debug_request,\r\n    get_pose: device_get_pose,\r\n};\r\n\r\nstatic PROVIDER: ServerTrackedDeviceProvider = ServerTrackedDeviceProvider {\r\n    vtable: &PROVIDER_VTABLE,\r\n};\r\n\r\nstatic DEVICE: TrackedDeviceServerDriver = TrackedDeviceServerDriver {\r\n    vtable: &DEVICE_VTABLE,\r\n};\r\n\r\nfn set_return_code(out: *mut i32, code: DriverReturnCode) {\r\n    if !out.is_null() {\r\n        unsafe {\r\n            *out = code as i32;\r\n        }\r\n    }\r\n}\r\n\r\nfn read_interface_name(ptr: *const i8) -> Option<&'static str> {\r\n    if ptr.is_null() {\r\n        return None;\r\n    }\r\n\r\n    let cstr = unsafe { CStr::from_ptr(ptr) };\r\n    let Ok(name) = cstr.to_str() else {\r\n        return None;\r\n    };\r\n\r\n    if name == SERVER_PROVIDER_INTERFACE {\r\n        return Some(SERVER_PROVIDER_INTERFACE);\r\n    }\r\n    if name == SERVER_PROVIDER_INTERFACE_ALT {\r\n        return Some(SERVER_PROVIDER_INTERFACE);\r\n    }\r\n    if name == TRACKED_DEVICE_INTERFACE {\r\n        return Some(TRACKED_DEVICE_INTERFACE);\r\n    }\r\n\r\n    None\r\n}\r\n\r\n/// Resolves OpenVR interface requests to static provider/device instances.\r\n///\r\n/// On match, writes `DriverReturnCode::None` to `p_return_code` and returns a\r\n/// stable interface pointer. On failure, writes an error code and returns null.\r\npub fn hmd_driver_factory(\r\n    p_interface_name: *const c_char,\r\n    p_return_code: *mut i32,\r\n) -> *mut c_void {\r\n    let Some(interface_name) = read_interface_name(p_interface_name) else {\r\n        set_return_code(p_return_code, DriverReturnCode::InitInterfaceNotFound);\r\n        return core::ptr::null_mut();\r\n    };\r\n\r\n    if interface_name == SERVER_PROVIDER_INTERFACE {\r\n        set_return_code(p_return_code, DriverReturnCode::None);\r\n        return (&PROVIDER as *const ServerTrackedDeviceProvider)\r\n            .cast_mut()\r\n            .cast();\r\n    }\r\n\r\n    if interface_name == TRACKED_DEVICE_INTERFACE {\r\n        set_return_code(p_return_code, DriverReturnCode::None);\r\n        return (&DEVICE as *const TrackedDeviceServerDriver)\r\n            .cast_mut()\r\n            .cast();\r\n    }\r\n\r\n    set_return_code(p_return_code, DriverReturnCode::Unknown);\r\n    core::ptr::null_mut()\r\n}\r\n","epoch":1686,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs::request_6f13d5f3-b3de-4f61-a09f-3c6ee71f5876",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\openvr_runtime.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","scheme":"file"},"requestId":"request_6f13d5f3-b3de-4f61-a09f-3c6ee71f5876","content":"//! OpenVR runtime integration helpers.\r\n//!\r\n//! This module owns driver-context interface resolution, tracked-device\r\n//! registration, component/property setup, and OpenVR haptic event routing.\r\n\r\nuse core::ffi::c_void;\r\nuse core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU64, Ordering};\r\nuse std::ffi::CString;\r\nuse std::os::raw::c_char;\r\n\r\nuse crate::haptics;\r\n\r\ntype EvRInitError = i32;\r\n\r\nconst IVR_SERVER_DRIVER_HOST_VERSION: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\nconst IVR_DRIVER_INPUT_VERSION: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_PROPERTIES_VERSION: &[u8] = b\"IVRProperties_001\\0\";\r\nconst IVR_DRIVER_LOG_VERSION: &[u8] = b\"IVRDriverLog_001\\0\";\r\n\r\nconst TRACKED_DEVICE_CLASS_CONTROLLER: i32 = 2;\r\nconst VREVENT_INPUT_HAPTIC_VIBRATION: u32 = 1700;\r\n\r\nconst PROP_MODEL_NUMBER_STRING: u32 = 1001;\r\nconst PROP_SERIAL_NUMBER_STRING: u32 = 1002;\r\nconst PROP_TRACKING_SYSTEM_NAME_STRING: u32 = 1000;\r\nconst PROP_REGISTERED_DEVICE_TYPE_STRING: u32 = 1036;\r\nconst PROP_INPUT_PROFILE_PATH_STRING: u32 = 1037;\r\nconst PROP_CONTROLLER_TYPE_STRING: u32 = 7000;\r\nconst PROP_CONTROLLER_ROLE_HINT_INT32: u32 = 3007;\r\nconst PROP_HAS_CONTROLLER_COMPONENT_BOOL: u32 = 6003;\r\nconst TRACKED_DEVICE_INDEX_HMD: u32 = 0;\r\n\r\nconst K_UN_BOOL_PROPERTY_TAG: u32 = 4;\r\nconst K_UN_INT32_PROPERTY_TAG: u32 = 2;\r\nconst K_UN_STRING_PROPERTY_TAG: u32 = 5;\r\nconst PROPERTY_WRITE_SET: i32 = 0;\r\n\r\nconst DEVICE_SERIAL_CSTR: &[u8] = b\"qtpy-samd21-haptic-001\\0\";\r\nconst DEVICE_MODEL_CSTR: &[u8] = b\"QT Py SAMD21 Haptic\\0\";\r\nconst DEVICE_TRACKING_SYSTEM_NAME_CSTR: &[u8] = b\"big_haptic_driver\\0\";\r\nconst DEVICE_REGISTERED_TYPE_CSTR: &[u8] = b\"big_haptic_driver/controller\\0\";\r\nconst DEVICE_CONTROLLER_TYPE_CSTR: &[u8] = b\"big_haptic_driver\\0\";\r\nconst DEVICE_INPUT_PROFILE_CSTR: &[u8] = b\"{big_haptic_driver}/input/big_haptic_profile.json\\0\";\r\nconst DEVICE_HAPTIC_PATH_CSTR: &[u8] = b\"/output/haptic\\0\";\r\nconst DEVICE_POSE_RAW_PATH_CSTR: &[u8] = b\"/pose/raw\\0\";\r\nconst DEVICE_POSE_TIP_PATH_CSTR: &[u8] = b\"/pose/tip\\0\";\r\n\r\nconst PROVIDER_IFACE_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_004\\0\";\r\nconst PROVIDER_IFACE_ALT_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_005\\0\";\r\nconst DEVICE_IFACE_CSTR: &[u8] = b\"ITrackedDeviceServerDriver_005\\0\";\r\n\r\nconst IVR_PROPERTIES_CSTR: &[u8] = b\"IVRProperties_001\\0\";\r\nconst IVR_DRIVER_INPUT_CSTR: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_SERVER_DRIVER_HOST_CSTR: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\nconst IVR_DRIVER_LOG_CSTR: &[u8] = b\"IVRDriverLog_001\\0\";\r\n\r\nconst IVR_SETTINGS_CSTR: &[u8] = b\"IVRSettings_003\\0\";\r\nconst IVR_DRIVER_MANAGER_CSTR: &[u8] = b\"IVRDriverManager_001\\0\";\r\nconst IVR_RESOURCES_CSTR: &[u8] = b\"IVRResources_001\\0\";\r\n\r\n#[repr(C)]\r\nstruct InterfaceVersions([*const c_char; 11]);\r\nunsafe impl Sync for InterfaceVersions {}\r\n\r\nstatic INTERFACE_VERSIONS: InterfaceVersions = InterfaceVersions([\r\n    IVR_SETTINGS_CSTR.as_ptr().cast(),\r\n    DEVICE_IFACE_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_CSTR.as_ptr().cast(),\r\n    IVR_PROPERTIES_CSTR.as_ptr().cast(),\r\n    IVR_SERVER_DRIVER_HOST_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_LOG_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_MANAGER_CSTR.as_ptr().cast(),\r\n    IVR_RESOURCES_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_INPUT_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_ALT_CSTR.as_ptr().cast(),\r\n    core::ptr::null(),\r\n]);\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREventHapticVibration {\r\n    container_handle: u64,\r\n    component_handle: u64,\r\n    duration_seconds: f32,\r\n    frequency: f32,\r\n    amplitude: f32,\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nunion VREventData {\r\n    haptic_vibration: VREventHapticVibration,\r\n    reserved: [u8; 48],\r\n}\r\n\r\nimpl Default for VREventData {\r\n    fn default() -> Self {\r\n        Self { reserved: [0; 48] }\r\n    }\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREvent {\r\n    event_type: u32,\r\n    tracked_device_index: u32,\r\n    event_age_seconds: f32,\r\n    data: VREventData,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContextVTable {\r\n    get_generic_interface:\r\n        extern \"C\" fn(*mut DriverContext, *const c_char, *mut EvRInitError) -> *mut c_void,\r\n    get_driver_handle: extern \"C\" fn(*mut DriverContext) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContext {\r\n    vtable: *const DriverContextVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHostVTable {\r\n    tracked_device_added:\r\n        extern \"C\" fn(*mut ServerDriverHost, *const c_char, i32, *mut c_void) -> bool,\r\n    tracked_device_pose_updated: extern \"C\" fn(*mut ServerDriverHost, u32, *const c_void, u32),\r\n    vsync_event: extern \"C\" fn(*mut ServerDriverHost, f64),\r\n    vendor_specific_event: extern \"C\" fn(*mut ServerDriverHost, u32, u32, *const VREventData, f64),\r\n    is_exiting: extern \"C\" fn(*mut ServerDriverHost) -> bool,\r\n    poll_next_event: extern \"C\" fn(*mut ServerDriverHost, *mut VREvent, u32) -> bool,\r\n    get_raw_tracked_device_poses:\r\n        extern \"C\" fn(*mut ServerDriverHost, f32, *mut TrackedDevicePose, u32),\r\n    request_restart:\r\n        extern \"C\" fn(*mut ServerDriverHost, *const c_char, *const c_char, *const c_char, *const c_char),\r\n    get_frame_timings: extern \"C\" fn(*mut ServerDriverHost, *mut c_void, u32) -> u32,\r\n    set_display_eye_to_head:\r\n        extern \"C\" fn(*mut ServerDriverHost, u32, *const c_void, *const c_void),\r\n    set_display_projection_raw:\r\n        extern \"C\" fn(*mut ServerDriverHost, u32, *const c_void, *const c_void),\r\n    set_recommended_render_target_size: extern \"C\" fn(*mut ServerDriverHost, u32, u32, u32),\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHost {\r\n    vtable: *const ServerDriverHostVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverLogVTable {\r\n    log: extern \"C\" fn(*mut DriverLog, *const c_char),\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverLog {\r\n    vtable: *const DriverLogVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverInputVTable {\r\n    create_boolean_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n    update_boolean_component: extern \"C\" fn(*mut DriverInput, u64, bool, f64) -> i32,\r\n    create_scalar_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64, i32, i32) -> i32,\r\n    update_scalar_component: extern \"C\" fn(*mut DriverInput, u64, f32, f64) -> i32,\r\n    create_haptic_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n    create_skeleton_component: extern \"C\" fn(\r\n        *mut DriverInput,\r\n        u64,\r\n        *const c_char,\r\n        *const c_char,\r\n        *const c_char,\r\n        i32,\r\n        *const c_void,\r\n        u32,\r\n        *mut u64,\r\n    ) -> i32,\r\n    update_skeleton_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, i32, *const c_void, u32) -> i32,\r\n    create_pose_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n    update_pose_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const HmdMatrix34, f64) -> i32,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverInput {\r\n    vtable: *const DriverInputVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct PropertiesVTable {\r\n    read_property_batch: extern \"C\" fn(*mut Properties, u64, *mut c_void, u32) -> i32,\r\n    write_property_batch: extern \"C\" fn(*mut Properties, u64, *mut PropertyWrite, u32) -> i32,\r\n    get_prop_error_name_from_enum: extern \"C\" fn(*mut Properties, i32) -> *const c_char,\r\n    tracked_device_to_property_container: extern \"C\" fn(*mut Properties, u32) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct Properties {\r\n    vtable: *const PropertiesVTable,\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct HmdMatrix34 {\r\n    m: [[f32; 4]; 3],\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct TrackedDevicePose {\r\n    m_device_to_absolute_tracking: HmdMatrix34,\r\n    v_velocity: [f32; 3],\r\n    v_angular_velocity: [f32; 3],\r\n    e_tracking_result: i32,\r\n    b_pose_is_valid: bool,\r\n    b_device_is_connected: bool,\r\n}\r\n\r\n#[derive(Clone, Copy, Default)]\r\npub struct RuntimeHmdPose {\r\n    pub position_m: [f32; 3],\r\n    pub rotation: [[f32; 3]; 3],\r\n}\r\n\r\n#[repr(C)]\r\nstruct PropertyWrite {\r\n    prop: u32,\r\n    write_type: i32,\r\n    set_error: i32,\r\n    pv_buffer: *mut c_void,\r\n    un_buffer_size: u32,\r\n    un_tag: u32,\r\n    e_error: i32,\r\n}\r\n\r\nstatic DEVICE_REGISTERED: AtomicBool = AtomicBool::new(false);\r\n\r\nstatic SERVER_HOST_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DRIVER_INPUT_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic PROPERTIES_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DRIVER_LOG_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\n\r\nstatic DEVICE_CONTAINER: AtomicU64 = AtomicU64::new(0);\r\nstatic HAPTIC_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\nstatic POSE_RAW_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\nstatic POSE_TIP_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\nstatic HAPTIC_EVENT_SEQ: AtomicU64 = AtomicU64::new(0);\r\n\r\npub fn interface_versions_ptr() -> *const *const c_char {\r\n    INTERFACE_VERSIONS.0.as_ptr()\r\n}\r\n\r\nfn get_server_host() -> Option<*mut ServerDriverHost> {\r\n    let ptr = SERVER_HOST_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_driver_input() -> Option<*mut DriverInput> {\r\n    let ptr = DRIVER_INPUT_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_properties() -> Option<*mut Properties> {\r\n    let ptr = PROPERTIES_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_driver_log() -> Option<*mut DriverLog> {\r\n    let ptr = DRIVER_LOG_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\npub(crate) fn log_driver_message(message: &str) {\r\n    let Some(driver_log) = get_driver_log() else {\r\n        return;\r\n    };\r\n\r\n    let Ok(c_message) = CString::new(message) else {\r\n        return;\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*driver_log).vtable).log)(driver_log, c_message.as_ptr());\r\n    }\r\n}\r\n\r\nfn write_string_property(props: *mut Properties, container: u64, prop: u32, value_nul: &'static [u8]) {\r\n    if props.is_null() || value_nul.is_empty() {\r\n        return;\r\n    }\r\n\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: value_nul.as_ptr().cast_mut().cast(),\r\n        un_buffer_size: value_nul.len() as u32,\r\n        un_tag: K_UN_STRING_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn write_bool_property(props: *mut Properties, container: u64, prop: u32, value: bool) {\r\n    if props.is_null() {\r\n        return;\r\n    }\r\n\r\n    let mut bool_value = value;\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: (&mut bool_value as *mut bool).cast(),\r\n        un_buffer_size: core::mem::size_of::<bool>() as u32,\r\n        un_tag: K_UN_BOOL_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn write_int32_property(props: *mut Properties, container: u64, prop: u32, value: i32) {\r\n    if props.is_null() {\r\n        return;\r\n    }\r\n\r\n    let mut int_value = value;\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: (&mut int_value as *mut i32).cast(),\r\n        un_buffer_size: core::mem::size_of::<i32>() as u32,\r\n        un_tag: K_UN_INT32_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn register_tracked_device_if_needed(device_ptr: *mut c_void) {\r\n    if DEVICE_REGISTERED.load(Ordering::SeqCst) {\r\n        return;\r\n    }\r\n\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    let added = unsafe {\r\n        ((*(*host).vtable).tracked_device_added)(\r\n            host,\r\n            DEVICE_SERIAL_CSTR.as_ptr().cast(),\r\n            TRACKED_DEVICE_CLASS_CONTROLLER,\r\n            device_ptr,\r\n        )\r\n    };\r\n\r\n    if added {\r\n        DEVICE_REGISTERED.store(true, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\npub fn initialize(driver_context: *mut c_void, device_ptr: *mut c_void) -> bool {\r\n    if driver_context.is_null() {\r\n        return false;\r\n    }\r\n\r\n    let ctx = driver_context.cast::<DriverContext>();\r\n    let mut err = 0;\r\n\r\n    let get_iface = unsafe { (*(*ctx).vtable).get_generic_interface };\r\n\r\n    let host = get_iface(\r\n        ctx,\r\n        IVR_SERVER_DRIVER_HOST_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let driver_input = get_iface(\r\n        ctx,\r\n        IVR_DRIVER_INPUT_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let properties = get_iface(\r\n        ctx,\r\n        IVR_PROPERTIES_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let driver_log = get_iface(\r\n        ctx,\r\n        IVR_DRIVER_LOG_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    SERVER_HOST_IFACE.store(host, Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(driver_input, Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(properties, Ordering::SeqCst);\r\n    DRIVER_LOG_IFACE.store(driver_log, Ordering::SeqCst);\r\n\r\n    let ok = !host.is_null() && !driver_input.is_null() && !properties.is_null();\r\n    if !ok {\r\n        return false;\r\n    }\r\n\r\n    log_driver_message(&format!(\r\n        \"[big_haptic_driver] OpenVR init ok; sizeof(VREvent)={}, sizeof(VREventHapticVibration)={}\",\r\n        core::mem::size_of::<VREvent>(),\r\n        core::mem::size_of::<VREventHapticVibration>()\r\n    ));\r\n\r\n    register_tracked_device_if_needed(device_ptr);\r\n    true\r\n}\r\n\r\npub fn cleanup() {\r\n    DEVICE_REGISTERED.store(false, Ordering::SeqCst);\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    POSE_RAW_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    POSE_TIP_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    SERVER_HOST_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DRIVER_LOG_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n}\r\n\r\npub fn route_haptic_events() {\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    loop {\r\n        let mut event = VREvent::default();\r\n        let ok = unsafe {\r\n            ((*(*host).vtable).poll_next_event)(\r\n                host,\r\n                &mut event as *mut VREvent,\r\n                core::mem::size_of::<VREvent>() as u32,\r\n            )\r\n        };\r\n\r\n        if !ok {\r\n            break;\r\n        }\r\n\r\n        if event.event_type != VREVENT_INPUT_HAPTIC_VIBRATION {\r\n            continue;\r\n        }\r\n\r\n        let hv = unsafe { event.data.haptic_vibration };\r\n        let seq = HAPTIC_EVENT_SEQ.fetch_add(1, Ordering::SeqCst).saturating_add(1);\r\n\r\n        log_driver_message(&format!(\r\n            \"[big_haptic_driver] haptic event #{}: dev={} age_s={:.6} container={} component={} duration_s={:.6} (0x{:08x}) freq={:.3} amp={:.6} (0x{:08x})\",\r\n            seq,\r\n            event.tracked_device_index,\r\n            event.event_age_seconds,\r\n            hv.container_handle,\r\n            hv.component_handle,\r\n            hv.duration_seconds,\r\n            hv.duration_seconds.to_bits(),\r\n            hv.frequency,\r\n            hv.amplitude,\r\n            hv.amplitude.to_bits()\r\n        ));\r\n\r\n        if hv.amplitude > 0.0 && hv.duration_seconds <= 0.0 {\r\n            log_driver_message(&format!(\r\n                \"[big_haptic_driver] note: OpenVR sent duration<=0; per OpenVR guidance driver will emit one finite pulse (freq={:.3})\",\r\n                hv.frequency\r\n            ));\r\n        }\r\n\r\n        let container = DEVICE_CONTAINER.load(Ordering::SeqCst);\r\n        if container != 0 && hv.container_handle != 0 && hv.container_handle != container {\r\n            log_driver_message(&format!(\r\n                \"[big_haptic_driver] skipped haptic event: container mismatch event={} driver={}\",\r\n                hv.container_handle, container\r\n            ));\r\n            continue;\r\n        }\r\n\r\n        let haptic_component = HAPTIC_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n        if haptic_component != 0 && hv.component_handle != 0 && hv.component_handle != haptic_component {\r\n            log_driver_message(&format!(\r\n                \"[big_haptic_driver] skipped haptic event: component mismatch event={} driver={}\",\r\n                hv.component_handle, haptic_component\r\n            ));\r\n            continue;\r\n        }\r\n\r\n        log_driver_message(\"[big_haptic_driver] forwarding haptic request to HID layer\");\r\n\r\n        haptics::handle_haptic_request(haptics::HapticVibrationRequest {\r\n            amplitude: hv.amplitude,\r\n            duration_seconds: hv.duration_seconds,\r\n            frequency: hv.frequency,\r\n        });\r\n    }\r\n}\r\n\r\npub fn push_pose_update(object_id: u32, pose: *const c_void, pose_size: u32) {\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    if pose.is_null() || pose_size == 0 {\r\n        return;\r\n    }\r\n\r\n    unsafe {\r\n        ((*(*host).vtable).tracked_device_pose_updated)(host, object_id, pose, pose_size);\r\n    }\r\n}\r\n\r\npub fn sample_hmd_pose() -> Option<RuntimeHmdPose> {\r\n    let host = get_server_host()?;\r\n\r\n    let mut poses = [TrackedDevicePose::default(); 1];\r\n    unsafe {\r\n        ((*(*host).vtable).get_raw_tracked_device_poses)(\r\n            host,\r\n            0.0,\r\n            poses.as_mut_ptr(),\r\n            poses.len() as u32,\r\n        );\r\n    }\r\n\r\n    let hmd = poses[TRACKED_DEVICE_INDEX_HMD as usize];\r\n    if !hmd.b_device_is_connected || !hmd.b_pose_is_valid {\r\n        return None;\r\n    }\r\n\r\n    Some(RuntimeHmdPose {\r\n        position_m: [\r\n            hmd.m_device_to_absolute_tracking.m[0][3],\r\n            hmd.m_device_to_absolute_tracking.m[1][3],\r\n            hmd.m_device_to_absolute_tracking.m[2][3],\r\n        ],\r\n        rotation: [\r\n            [\r\n                hmd.m_device_to_absolute_tracking.m[0][0],\r\n                hmd.m_device_to_absolute_tracking.m[0][1],\r\n                hmd.m_device_to_absolute_tracking.m[0][2],\r\n            ],\r\n            [\r\n                hmd.m_device_to_absolute_tracking.m[1][0],\r\n                hmd.m_device_to_absolute_tracking.m[1][1],\r\n                hmd.m_device_to_absolute_tracking.m[1][2],\r\n            ],\r\n            [\r\n                hmd.m_device_to_absolute_tracking.m[2][0],\r\n                hmd.m_device_to_absolute_tracking.m[2][1],\r\n                hmd.m_device_to_absolute_tracking.m[2][2],\r\n            ],\r\n        ],\r\n    })\r\n}\r\n\r\npub fn update_pose_components() {\r\n    let Some(input) = get_driver_input() else {\r\n        return;\r\n    };\r\n\r\n    let raw_handle = POSE_RAW_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n    let tip_handle = POSE_TIP_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n\r\n    let raw_offset = HmdMatrix34 {\r\n        m: [\r\n            [1.0, 0.0, 0.0, 0.0],\r\n            [0.0, 1.0, 0.0, 0.0],\r\n            [0.0, 0.0, 1.0, 0.0],\r\n        ],\r\n    };\r\n    let tip_offset = HmdMatrix34 {\r\n        m: [\r\n            [1.0, 0.0, 0.0, 0.0],\r\n            [0.0, 1.0, 0.0, 0.0],\r\n            [0.0, 0.0, 1.0, -0.04],\r\n        ],\r\n    };\r\n\r\n    if raw_handle != 0 {\r\n        unsafe {\r\n            ((*(*input).vtable).update_pose_component)(input, raw_handle, &raw_offset, 0.0);\r\n        }\r\n    }\r\n\r\n    if tip_handle != 0 {\r\n        unsafe {\r\n            ((*(*input).vtable).update_pose_component)(input, tip_handle, &tip_offset, 0.0);\r\n        }\r\n    }\r\n}\r\n\r\npub fn activate_device(object_id: u32) -> bool {\r\n    let Some(props) = get_properties() else {\r\n        return false;\r\n    };\r\n\r\n    let container = unsafe { ((*(*props).vtable).tracked_device_to_property_container)(props, object_id) };\r\n    DEVICE_CONTAINER.store(container, Ordering::SeqCst);\r\n\r\n    write_string_property(props, container, PROP_MODEL_NUMBER_STRING, DEVICE_MODEL_CSTR);\r\n    write_string_property(props, container, PROP_SERIAL_NUMBER_STRING, DEVICE_SERIAL_CSTR);\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_TRACKING_SYSTEM_NAME_STRING,\r\n        DEVICE_TRACKING_SYSTEM_NAME_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_REGISTERED_DEVICE_TYPE_STRING,\r\n        DEVICE_REGISTERED_TYPE_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_INPUT_PROFILE_PATH_STRING,\r\n        DEVICE_INPUT_PROFILE_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_CONTROLLER_TYPE_STRING,\r\n        DEVICE_CONTROLLER_TYPE_CSTR,\r\n    );\r\n    write_bool_property(props, container, PROP_HAS_CONTROLLER_COMPONENT_BOOL, true);\r\n    write_int32_property(props, container, PROP_CONTROLLER_ROLE_HINT_INT32, 2);\r\n\r\n    if let Some(input) = get_driver_input() {\r\n        let mut handle = 0_u64;\r\n        let rc = unsafe {\r\n            ((*(*input).vtable).create_haptic_component)(\r\n                input,\r\n                container,\r\n                DEVICE_HAPTIC_PATH_CSTR.as_ptr().cast(),\r\n                &mut handle as *mut u64,\r\n            )\r\n        };\r\n\r\n        if rc == 0 {\r\n            HAPTIC_COMPONENT_HANDLE.store(handle, Ordering::SeqCst);\r\n        }\r\n\r\n        let mut pose_raw_handle = 0_u64;\r\n        let rc_raw = unsafe {\r\n            ((*(*input).vtable).create_pose_component)(\r\n                input,\r\n                container,\r\n                DEVICE_POSE_RAW_PATH_CSTR.as_ptr().cast(),\r\n                &mut pose_raw_handle as *mut u64,\r\n            )\r\n        };\r\n        if rc_raw == 0 {\r\n            POSE_RAW_COMPONENT_HANDLE.store(pose_raw_handle, Ordering::SeqCst);\r\n        }\r\n\r\n        let mut pose_tip_handle = 0_u64;\r\n        let rc_tip = unsafe {\r\n            ((*(*input).vtable).create_pose_component)(\r\n                input,\r\n                container,\r\n                DEVICE_POSE_TIP_PATH_CSTR.as_ptr().cast(),\r\n                &mut pose_tip_handle as *mut u64,\r\n            )\r\n        };\r\n        if rc_tip == 0 {\r\n            POSE_TIP_COMPONENT_HANDLE.store(pose_tip_handle, Ordering::SeqCst);\r\n        }\r\n\r\n        update_pose_components();\r\n    }\r\n\r\n    true\r\n}\r\n\r\npub fn deactivate_device() {\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    POSE_RAW_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    POSE_TIP_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n}\r\n","epoch":1689,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs::request_253d5ff6-6f1d-4b2e-83a2-f98328aa01b9",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\openvr_runtime.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","scheme":"file"},"requestId":"request_253d5ff6-6f1d-4b2e-83a2-f98328aa01b9","content":"//! OpenVR runtime integration helpers.\r\n//!\r\n//! This module owns driver-context interface resolution, tracked-device\r\n//! registration, component/property setup, and OpenVR haptic event routing.\r\n\r\nuse core::ffi::c_void;\r\nuse core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU64, Ordering};\r\nuse std::ffi::CString;\r\nuse std::os::raw::c_char;\r\n\r\nuse crate::haptics;\r\n\r\ntype EvRInitError = i32;\r\n\r\nconst IVR_SERVER_DRIVER_HOST_VERSION: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\nconst IVR_DRIVER_INPUT_VERSION: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_PROPERTIES_VERSION: &[u8] = b\"IVRProperties_001\\0\";\r\nconst IVR_DRIVER_LOG_VERSION: &[u8] = b\"IVRDriverLog_001\\0\";\r\n\r\nconst TRACKED_DEVICE_CLASS_CONTROLLER: i32 = 2;\r\nconst VREVENT_INPUT_HAPTIC_VIBRATION: u32 = 1700;\r\n\r\nconst PROP_MODEL_NUMBER_STRING: u32 = 1001;\r\nconst PROP_SERIAL_NUMBER_STRING: u32 = 1002;\r\nconst PROP_TRACKING_SYSTEM_NAME_STRING: u32 = 1000;\r\nconst PROP_REGISTERED_DEVICE_TYPE_STRING: u32 = 1036;\r\nconst PROP_INPUT_PROFILE_PATH_STRING: u32 = 1037;\r\nconst PROP_CONTROLLER_TYPE_STRING: u32 = 7000;\r\nconst PROP_CONTROLLER_ROLE_HINT_INT32: u32 = 3007;\r\nconst PROP_HAS_CONTROLLER_COMPONENT_BOOL: u32 = 6003;\r\nconst TRACKED_DEVICE_INDEX_HMD: u32 = 0;\r\n\r\nconst K_UN_BOOL_PROPERTY_TAG: u32 = 4;\r\nconst K_UN_INT32_PROPERTY_TAG: u32 = 2;\r\nconst K_UN_STRING_PROPERTY_TAG: u32 = 5;\r\nconst PROPERTY_WRITE_SET: i32 = 0;\r\n\r\nconst DEVICE_SERIAL_CSTR: &[u8] = b\"qtpy-samd21-haptic-001\\0\";\r\nconst DEVICE_MODEL_CSTR: &[u8] = b\"QT Py SAMD21 Haptic\\0\";\r\nconst DEVICE_TRACKING_SYSTEM_NAME_CSTR: &[u8] = b\"big_haptic_driver\\0\";\r\nconst DEVICE_REGISTERED_TYPE_CSTR: &[u8] = b\"big_haptic_driver/controller\\0\";\r\nconst DEVICE_CONTROLLER_TYPE_CSTR: &[u8] = b\"big_haptic_driver\\0\";\r\nconst DEVICE_INPUT_PROFILE_CSTR: &[u8] = b\"{big_haptic_driver}/input/big_haptic_profile.json\\0\";\r\nconst DEVICE_HAPTIC_PATH_CSTR: &[u8] = b\"/output/haptic\\0\";\r\nconst DEVICE_POSE_RAW_PATH_CSTR: &[u8] = b\"/pose/raw\\0\";\r\nconst DEVICE_POSE_TIP_PATH_CSTR: &[u8] = b\"/pose/tip\\0\";\r\nconst DEVICE_SYSTEM_CLICK_PATH_CSTR: &[u8] = b\"/input/system/click\\0\";\r\nconst DEVICE_A_CLICK_PATH_CSTR: &[u8] = b\"/input/a/click\\0\";\r\nconst DEVICE_TRIGGER_CLICK_PATH_CSTR: &[u8] = b\"/input/trigger/click\\0\";\r\nconst DEVICE_TRIGGER_VALUE_PATH_CSTR: &[u8] = b\"/input/trigger/value\\0\";\r\n\r\nconst VR_SCALAR_TYPE_ABSOLUTE: i32 = 0;\r\nconst VR_SCALAR_UNITS_NORMALIZED_ONE_SIDED: i32 = 0;\r\n\r\nconst PROVIDER_IFACE_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_004\\0\";\r\nconst PROVIDER_IFACE_ALT_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_005\\0\";\r\nconst DEVICE_IFACE_CSTR: &[u8] = b\"ITrackedDeviceServerDriver_005\\0\";\r\n\r\nconst IVR_PROPERTIES_CSTR: &[u8] = b\"IVRProperties_001\\0\";\r\nconst IVR_DRIVER_INPUT_CSTR: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_SERVER_DRIVER_HOST_CSTR: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\nconst IVR_DRIVER_LOG_CSTR: &[u8] = b\"IVRDriverLog_001\\0\";\r\n\r\nconst IVR_SETTINGS_CSTR: &[u8] = b\"IVRSettings_003\\0\";\r\nconst IVR_DRIVER_MANAGER_CSTR: &[u8] = b\"IVRDriverManager_001\\0\";\r\nconst IVR_RESOURCES_CSTR: &[u8] = b\"IVRResources_001\\0\";\r\n\r\n#[repr(C)]\r\nstruct InterfaceVersions([*const c_char; 11]);\r\nunsafe impl Sync for InterfaceVersions {}\r\n\r\nstatic INTERFACE_VERSIONS: InterfaceVersions = InterfaceVersions([\r\n    IVR_SETTINGS_CSTR.as_ptr().cast(),\r\n    DEVICE_IFACE_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_CSTR.as_ptr().cast(),\r\n    IVR_PROPERTIES_CSTR.as_ptr().cast(),\r\n    IVR_SERVER_DRIVER_HOST_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_LOG_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_MANAGER_CSTR.as_ptr().cast(),\r\n    IVR_RESOURCES_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_INPUT_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_ALT_CSTR.as_ptr().cast(),\r\n    core::ptr::null(),\r\n]);\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREventHapticVibration {\r\n    container_handle: u64,\r\n    component_handle: u64,\r\n    duration_seconds: f32,\r\n    frequency: f32,\r\n    amplitude: f32,\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nunion VREventData {\r\n    haptic_vibration: VREventHapticVibration,\r\n    reserved: [u8; 48],\r\n}\r\n\r\nimpl Default for VREventData {\r\n    fn default() -> Self {\r\n        Self { reserved: [0; 48] }\r\n    }\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREvent {\r\n    event_type: u32,\r\n    tracked_device_index: u32,\r\n    event_age_seconds: f32,\r\n    data: VREventData,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContextVTable {\r\n    get_generic_interface:\r\n        extern \"C\" fn(*mut DriverContext, *const c_char, *mut EvRInitError) -> *mut c_void,\r\n    get_driver_handle: extern \"C\" fn(*mut DriverContext) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContext {\r\n    vtable: *const DriverContextVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHostVTable {\r\n    tracked_device_added:\r\n        extern \"C\" fn(*mut ServerDriverHost, *const c_char, i32, *mut c_void) -> bool,\r\n    tracked_device_pose_updated: extern \"C\" fn(*mut ServerDriverHost, u32, *const c_void, u32),\r\n    vsync_event: extern \"C\" fn(*mut ServerDriverHost, f64),\r\n    vendor_specific_event: extern \"C\" fn(*mut ServerDriverHost, u32, u32, *const VREventData, f64),\r\n    is_exiting: extern \"C\" fn(*mut ServerDriverHost) -> bool,\r\n    poll_next_event: extern \"C\" fn(*mut ServerDriverHost, *mut VREvent, u32) -> bool,\r\n    get_raw_tracked_device_poses:\r\n        extern \"C\" fn(*mut ServerDriverHost, f32, *mut TrackedDevicePose, u32),\r\n    request_restart:\r\n        extern \"C\" fn(*mut ServerDriverHost, *const c_char, *const c_char, *const c_char, *const c_char),\r\n    get_frame_timings: extern \"C\" fn(*mut ServerDriverHost, *mut c_void, u32) -> u32,\r\n    set_display_eye_to_head:\r\n        extern \"C\" fn(*mut ServerDriverHost, u32, *const c_void, *const c_void),\r\n    set_display_projection_raw:\r\n        extern \"C\" fn(*mut ServerDriverHost, u32, *const c_void, *const c_void),\r\n    set_recommended_render_target_size: extern \"C\" fn(*mut ServerDriverHost, u32, u32, u32),\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHost {\r\n    vtable: *const ServerDriverHostVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverLogVTable {\r\n    log: extern \"C\" fn(*mut DriverLog, *const c_char),\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverLog {\r\n    vtable: *const DriverLogVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverInputVTable {\r\n    create_boolean_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n    update_boolean_component: extern \"C\" fn(*mut DriverInput, u64, bool, f64) -> i32,\r\n    create_scalar_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64, i32, i32) -> i32,\r\n    update_scalar_component: extern \"C\" fn(*mut DriverInput, u64, f32, f64) -> i32,\r\n    create_haptic_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n    create_skeleton_component: extern \"C\" fn(\r\n        *mut DriverInput,\r\n        u64,\r\n        *const c_char,\r\n        *const c_char,\r\n        *const c_char,\r\n        i32,\r\n        *const c_void,\r\n        u32,\r\n        *mut u64,\r\n    ) -> i32,\r\n    update_skeleton_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, i32, *const c_void, u32) -> i32,\r\n    create_pose_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n    update_pose_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const HmdMatrix34, f64) -> i32,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverInput {\r\n    vtable: *const DriverInputVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct PropertiesVTable {\r\n    read_property_batch: extern \"C\" fn(*mut Properties, u64, *mut c_void, u32) -> i32,\r\n    write_property_batch: extern \"C\" fn(*mut Properties, u64, *mut PropertyWrite, u32) -> i32,\r\n    get_prop_error_name_from_enum: extern \"C\" fn(*mut Properties, i32) -> *const c_char,\r\n    tracked_device_to_property_container: extern \"C\" fn(*mut Properties, u32) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct Properties {\r\n    vtable: *const PropertiesVTable,\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct HmdMatrix34 {\r\n    m: [[f32; 4]; 3],\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct TrackedDevicePose {\r\n    m_device_to_absolute_tracking: HmdMatrix34,\r\n    v_velocity: [f32; 3],\r\n    v_angular_velocity: [f32; 3],\r\n    e_tracking_result: i32,\r\n    b_pose_is_valid: bool,\r\n    b_device_is_connected: bool,\r\n}\r\n\r\n#[derive(Clone, Copy, Default)]\r\npub struct RuntimeHmdPose {\r\n    pub position_m: [f32; 3],\r\n    pub rotation: [[f32; 3]; 3],\r\n}\r\n\r\n#[repr(C)]\r\nstruct PropertyWrite {\r\n    prop: u32,\r\n    write_type: i32,\r\n    set_error: i32,\r\n    pv_buffer: *mut c_void,\r\n    un_buffer_size: u32,\r\n    un_tag: u32,\r\n    e_error: i32,\r\n}\r\n\r\nstatic DEVICE_REGISTERED: AtomicBool = AtomicBool::new(false);\r\n\r\nstatic SERVER_HOST_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DRIVER_INPUT_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic PROPERTIES_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DRIVER_LOG_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\n\r\nstatic DEVICE_CONTAINER: AtomicU64 = AtomicU64::new(0);\r\nstatic HAPTIC_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\nstatic SYSTEM_CLICK_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\nstatic A_CLICK_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\nstatic TRIGGER_CLICK_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\nstatic TRIGGER_VALUE_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\nstatic POSE_RAW_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\nstatic POSE_TIP_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\nstatic HAPTIC_EVENT_SEQ: AtomicU64 = AtomicU64::new(0);\r\n\r\npub fn interface_versions_ptr() -> *const *const c_char {\r\n    INTERFACE_VERSIONS.0.as_ptr()\r\n}\r\n\r\nfn get_server_host() -> Option<*mut ServerDriverHost> {\r\n    let ptr = SERVER_HOST_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_driver_input() -> Option<*mut DriverInput> {\r\n    let ptr = DRIVER_INPUT_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_properties() -> Option<*mut Properties> {\r\n    let ptr = PROPERTIES_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_driver_log() -> Option<*mut DriverLog> {\r\n    let ptr = DRIVER_LOG_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\npub(crate) fn log_driver_message(message: &str) {\r\n    let Some(driver_log) = get_driver_log() else {\r\n        return;\r\n    };\r\n\r\n    let Ok(c_message) = CString::new(message) else {\r\n        return;\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*driver_log).vtable).log)(driver_log, c_message.as_ptr());\r\n    }\r\n}\r\n\r\nfn write_string_property(props: *mut Properties, container: u64, prop: u32, value_nul: &'static [u8]) {\r\n    if props.is_null() || value_nul.is_empty() {\r\n        return;\r\n    }\r\n\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: value_nul.as_ptr().cast_mut().cast(),\r\n        un_buffer_size: value_nul.len() as u32,\r\n        un_tag: K_UN_STRING_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn write_bool_property(props: *mut Properties, container: u64, prop: u32, value: bool) {\r\n    if props.is_null() {\r\n        return;\r\n    }\r\n\r\n    let mut bool_value = value;\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: (&mut bool_value as *mut bool).cast(),\r\n        un_buffer_size: core::mem::size_of::<bool>() as u32,\r\n        un_tag: K_UN_BOOL_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn write_int32_property(props: *mut Properties, container: u64, prop: u32, value: i32) {\r\n    if props.is_null() {\r\n        return;\r\n    }\r\n\r\n    let mut int_value = value;\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: (&mut int_value as *mut i32).cast(),\r\n        un_buffer_size: core::mem::size_of::<i32>() as u32,\r\n        un_tag: K_UN_INT32_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn register_tracked_device_if_needed(device_ptr: *mut c_void) {\r\n    if DEVICE_REGISTERED.load(Ordering::SeqCst) {\r\n        return;\r\n    }\r\n\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    let added = unsafe {\r\n        ((*(*host).vtable).tracked_device_added)(\r\n            host,\r\n            DEVICE_SERIAL_CSTR.as_ptr().cast(),\r\n            TRACKED_DEVICE_CLASS_CONTROLLER,\r\n            device_ptr,\r\n        )\r\n    };\r\n\r\n    if added {\r\n        DEVICE_REGISTERED.store(true, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\npub fn initialize(driver_context: *mut c_void, device_ptr: *mut c_void) -> bool {\r\n    if driver_context.is_null() {\r\n        return false;\r\n    }\r\n\r\n    let ctx = driver_context.cast::<DriverContext>();\r\n    let mut err = 0;\r\n\r\n    let get_iface = unsafe { (*(*ctx).vtable).get_generic_interface };\r\n\r\n    let host = get_iface(\r\n        ctx,\r\n        IVR_SERVER_DRIVER_HOST_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let driver_input = get_iface(\r\n        ctx,\r\n        IVR_DRIVER_INPUT_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let properties = get_iface(\r\n        ctx,\r\n        IVR_PROPERTIES_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let driver_log = get_iface(\r\n        ctx,\r\n        IVR_DRIVER_LOG_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    SERVER_HOST_IFACE.store(host, Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(driver_input, Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(properties, Ordering::SeqCst);\r\n    DRIVER_LOG_IFACE.store(driver_log, Ordering::SeqCst);\r\n\r\n    let ok = !host.is_null() && !driver_input.is_null() && !properties.is_null();\r\n    if !ok {\r\n        return false;\r\n    }\r\n\r\n    log_driver_message(&format!(\r\n        \"[big_haptic_driver] OpenVR init ok; sizeof(VREvent)={}, sizeof(VREventHapticVibration)={}\",\r\n        core::mem::size_of::<VREvent>(),\r\n        core::mem::size_of::<VREventHapticVibration>()\r\n    ));\r\n\r\n    register_tracked_device_if_needed(device_ptr);\r\n    true\r\n}\r\n\r\npub fn cleanup() {\r\n    DEVICE_REGISTERED.store(false, Ordering::SeqCst);\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    SYSTEM_CLICK_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    A_CLICK_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    TRIGGER_CLICK_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    TRIGGER_VALUE_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    POSE_RAW_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    POSE_TIP_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    SERVER_HOST_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DRIVER_LOG_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n}\r\n\r\npub fn route_haptic_events() {\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    loop {\r\n        let mut event = VREvent::default();\r\n        let ok = unsafe {\r\n            ((*(*host).vtable).poll_next_event)(\r\n                host,\r\n                &mut event as *mut VREvent,\r\n                core::mem::size_of::<VREvent>() as u32,\r\n            )\r\n        };\r\n\r\n        if !ok {\r\n            break;\r\n        }\r\n\r\n        if event.event_type != VREVENT_INPUT_HAPTIC_VIBRATION {\r\n            continue;\r\n        }\r\n\r\n        let hv = unsafe { event.data.haptic_vibration };\r\n        let seq = HAPTIC_EVENT_SEQ.fetch_add(1, Ordering::SeqCst).saturating_add(1);\r\n\r\n        log_driver_message(&format!(\r\n            \"[big_haptic_driver] haptic event #{}: dev={} age_s={:.6} container={} component={} duration_s={:.6} (0x{:08x}) freq={:.3} amp={:.6} (0x{:08x})\",\r\n            seq,\r\n            event.tracked_device_index,\r\n            event.event_age_seconds,\r\n            hv.container_handle,\r\n            hv.component_handle,\r\n            hv.duration_seconds,\r\n            hv.duration_seconds.to_bits(),\r\n            hv.frequency,\r\n            hv.amplitude,\r\n            hv.amplitude.to_bits()\r\n        ));\r\n\r\n        if hv.amplitude > 0.0 && hv.duration_seconds <= 0.0 {\r\n            log_driver_message(&format!(\r\n                \"[big_haptic_driver] note: OpenVR sent duration<=0; per OpenVR guidance driver will emit one finite pulse (freq={:.3})\",\r\n                hv.frequency\r\n            ));\r\n        }\r\n\r\n        let container = DEVICE_CONTAINER.load(Ordering::SeqCst);\r\n        if container != 0 && hv.container_handle != 0 && hv.container_handle != container {\r\n            log_driver_message(&format!(\r\n                \"[big_haptic_driver] skipped haptic event: container mismatch event={} driver={}\",\r\n                hv.container_handle, container\r\n            ));\r\n            continue;\r\n        }\r\n\r\n        let haptic_component = HAPTIC_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n        if haptic_component != 0 && hv.component_handle != 0 && hv.component_handle != haptic_component {\r\n            log_driver_message(&format!(\r\n                \"[big_haptic_driver] skipped haptic event: component mismatch event={} driver={}\",\r\n                hv.component_handle, haptic_component\r\n            ));\r\n            continue;\r\n        }\r\n\r\n        log_driver_message(\"[big_haptic_driver] forwarding haptic request to HID layer\");\r\n\r\n        haptics::handle_haptic_request(haptics::HapticVibrationRequest {\r\n            amplitude: hv.amplitude,\r\n            duration_seconds: hv.duration_seconds,\r\n            frequency: hv.frequency,\r\n        });\r\n    }\r\n}\r\n\r\npub fn push_pose_update(object_id: u32, pose: *const c_void, pose_size: u32) {\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    if pose.is_null() || pose_size == 0 {\r\n        return;\r\n    }\r\n\r\n    unsafe {\r\n        ((*(*host).vtable).tracked_device_pose_updated)(host, object_id, pose, pose_size);\r\n    }\r\n}\r\n\r\npub fn sample_hmd_pose() -> Option<RuntimeHmdPose> {\r\n    let host = get_server_host()?;\r\n\r\n    let mut poses = [TrackedDevicePose::default(); 1];\r\n    unsafe {\r\n        ((*(*host).vtable).get_raw_tracked_device_poses)(\r\n            host,\r\n            0.0,\r\n            poses.as_mut_ptr(),\r\n            poses.len() as u32,\r\n        );\r\n    }\r\n\r\n    let hmd = poses[TRACKED_DEVICE_INDEX_HMD as usize];\r\n    if !hmd.b_device_is_connected || !hmd.b_pose_is_valid {\r\n        return None;\r\n    }\r\n\r\n    Some(RuntimeHmdPose {\r\n        position_m: [\r\n            hmd.m_device_to_absolute_tracking.m[0][3],\r\n            hmd.m_device_to_absolute_tracking.m[1][3],\r\n            hmd.m_device_to_absolute_tracking.m[2][3],\r\n        ],\r\n        rotation: [\r\n            [\r\n                hmd.m_device_to_absolute_tracking.m[0][0],\r\n                hmd.m_device_to_absolute_tracking.m[0][1],\r\n                hmd.m_device_to_absolute_tracking.m[0][2],\r\n            ],\r\n            [\r\n                hmd.m_device_to_absolute_tracking.m[1][0],\r\n                hmd.m_device_to_absolute_tracking.m[1][1],\r\n                hmd.m_device_to_absolute_tracking.m[1][2],\r\n            ],\r\n            [\r\n                hmd.m_device_to_absolute_tracking.m[2][0],\r\n                hmd.m_device_to_absolute_tracking.m[2][1],\r\n                hmd.m_device_to_absolute_tracking.m[2][2],\r\n            ],\r\n        ],\r\n    })\r\n}\r\n\r\npub fn update_pose_components() {\r\n    let Some(input) = get_driver_input() else {\r\n        return;\r\n    };\r\n\r\n    let raw_handle = POSE_RAW_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n    let tip_handle = POSE_TIP_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n\r\n    let raw_offset = HmdMatrix34 {\r\n        m: [\r\n            [1.0, 0.0, 0.0, 0.0],\r\n            [0.0, 1.0, 0.0, 0.0],\r\n            [0.0, 0.0, 1.0, 0.0],\r\n        ],\r\n    };\r\n    let tip_offset = HmdMatrix34 {\r\n        m: [\r\n            [1.0, 0.0, 0.0, 0.0],\r\n            [0.0, 1.0, 0.0, 0.0],\r\n            [0.0, 0.0, 1.0, -0.04],\r\n        ],\r\n    };\r\n\r\n    if raw_handle != 0 {\r\n        unsafe {\r\n            ((*(*input).vtable).update_pose_component)(input, raw_handle, &raw_offset, 0.0);\r\n        }\r\n    }\r\n\r\n    if tip_handle != 0 {\r\n        unsafe {\r\n            ((*(*input).vtable).update_pose_component)(input, tip_handle, &tip_offset, 0.0);\r\n        }\r\n    }\r\n}\r\n\r\npub fn update_input_components() {\r\n    let Some(input) = get_driver_input() else {\r\n        return;\r\n    };\r\n\r\n    let timestamp = 0.0;\r\n    let system_click_handle = SYSTEM_CLICK_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n    let a_click_handle = A_CLICK_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n    let trigger_click_handle = TRIGGER_CLICK_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n    let trigger_value_handle = TRIGGER_VALUE_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n\r\n    if system_click_handle != 0 {\r\n        unsafe {\r\n            ((*(*input).vtable).update_boolean_component)(input, system_click_handle, false, timestamp);\r\n        }\r\n    }\r\n\r\n    if a_click_handle != 0 {\r\n        unsafe {\r\n            ((*(*input).vtable).update_boolean_component)(input, a_click_handle, false, timestamp);\r\n        }\r\n    }\r\n\r\n    if trigger_click_handle != 0 {\r\n        unsafe {\r\n            ((*(*input).vtable).update_boolean_component)(input, trigger_click_handle, false, timestamp);\r\n        }\r\n    }\r\n\r\n    if trigger_value_handle != 0 {\r\n        unsafe {\r\n            ((*(*input).vtable).update_scalar_component)(input, trigger_value_handle, 0.0, timestamp);\r\n        }\r\n    }\r\n}\r\n\r\npub fn activate_device(object_id: u32) -> bool {\r\n    let Some(props) = get_properties() else {\r\n        return false;\r\n    };\r\n\r\n    let container = unsafe { ((*(*props).vtable).tracked_device_to_property_container)(props, object_id) };\r\n    DEVICE_CONTAINER.store(container, Ordering::SeqCst);\r\n\r\n    write_string_property(props, container, PROP_MODEL_NUMBER_STRING, DEVICE_MODEL_CSTR);\r\n    write_string_property(props, container, PROP_SERIAL_NUMBER_STRING, DEVICE_SERIAL_CSTR);\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_TRACKING_SYSTEM_NAME_STRING,\r\n        DEVICE_TRACKING_SYSTEM_NAME_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_REGISTERED_DEVICE_TYPE_STRING,\r\n        DEVICE_REGISTERED_TYPE_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_INPUT_PROFILE_PATH_STRING,\r\n        DEVICE_INPUT_PROFILE_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_CONTROLLER_TYPE_STRING,\r\n        DEVICE_CONTROLLER_TYPE_CSTR,\r\n    );\r\n    write_bool_property(props, container, PROP_HAS_CONTROLLER_COMPONENT_BOOL, true);\r\n    write_int32_property(props, container, PROP_CONTROLLER_ROLE_HINT_INT32, 2);\r\n\r\n    if let Some(input) = get_driver_input() {\r\n        let mut system_click_handle = 0_u64;\r\n        let rc_system = unsafe {\r\n            ((*(*input).vtable).create_boolean_component)(\r\n                input,\r\n                container,\r\n                DEVICE_SYSTEM_CLICK_PATH_CSTR.as_ptr().cast(),\r\n                &mut system_click_handle as *mut u64,\r\n            )\r\n        };\r\n        if rc_system == 0 {\r\n            SYSTEM_CLICK_COMPONENT_HANDLE.store(system_click_handle, Ordering::SeqCst);\r\n        }\r\n\r\n        let mut a_click_handle = 0_u64;\r\n        let rc_a = unsafe {\r\n            ((*(*input).vtable).create_boolean_component)(\r\n                input,\r\n                container,\r\n                DEVICE_A_CLICK_PATH_CSTR.as_ptr().cast(),\r\n                &mut a_click_handle as *mut u64,\r\n            )\r\n        };\r\n        if rc_a == 0 {\r\n            A_CLICK_COMPONENT_HANDLE.store(a_click_handle, Ordering::SeqCst);\r\n        }\r\n\r\n        let mut trigger_click_handle = 0_u64;\r\n        let rc_trigger_click = unsafe {\r\n            ((*(*input).vtable).create_boolean_component)(\r\n                input,\r\n                container,\r\n                DEVICE_TRIGGER_CLICK_PATH_CSTR.as_ptr().cast(),\r\n                &mut trigger_click_handle as *mut u64,\r\n            )\r\n        };\r\n        if rc_trigger_click == 0 {\r\n            TRIGGER_CLICK_COMPONENT_HANDLE.store(trigger_click_handle, Ordering::SeqCst);\r\n        }\r\n\r\n        let mut trigger_value_handle = 0_u64;\r\n        let rc_trigger_value = unsafe {\r\n            ((*(*input).vtable).create_scalar_component)(\r\n                input,\r\n                container,\r\n                DEVICE_TRIGGER_VALUE_PATH_CSTR.as_ptr().cast(),\r\n                &mut trigger_value_handle as *mut u64,\r\n                VR_SCALAR_TYPE_ABSOLUTE,\r\n                VR_SCALAR_UNITS_NORMALIZED_ONE_SIDED,\r\n            )\r\n        };\r\n        if rc_trigger_value == 0 {\r\n            TRIGGER_VALUE_COMPONENT_HANDLE.store(trigger_value_handle, Ordering::SeqCst);\r\n        }\r\n\r\n        let mut handle = 0_u64;\r\n        let rc = unsafe {\r\n            ((*(*input).vtable).create_haptic_component)(\r\n                input,\r\n                container,\r\n                DEVICE_HAPTIC_PATH_CSTR.as_ptr().cast(),\r\n                &mut handle as *mut u64,\r\n            )\r\n        };\r\n\r\n        if rc == 0 {\r\n            HAPTIC_COMPONENT_HANDLE.store(handle, Ordering::SeqCst);\r\n        }\r\n\r\n        let mut pose_raw_handle = 0_u64;\r\n        let rc_raw = unsafe {\r\n            ((*(*input).vtable).create_pose_component)(\r\n                input,\r\n                container,\r\n                DEVICE_POSE_RAW_PATH_CSTR.as_ptr().cast(),\r\n                &mut pose_raw_handle as *mut u64,\r\n            )\r\n        };\r\n        if rc_raw == 0 {\r\n            POSE_RAW_COMPONENT_HANDLE.store(pose_raw_handle, Ordering::SeqCst);\r\n        }\r\n\r\n        let mut pose_tip_handle = 0_u64;\r\n        let rc_tip = unsafe {\r\n            ((*(*input).vtable).create_pose_component)(\r\n                input,\r\n                container,\r\n                DEVICE_POSE_TIP_PATH_CSTR.as_ptr().cast(),\r\n                &mut pose_tip_handle as *mut u64,\r\n            )\r\n        };\r\n        if rc_tip == 0 {\r\n            POSE_TIP_COMPONENT_HANDLE.store(pose_tip_handle, Ordering::SeqCst);\r\n        }\r\n\r\n        update_input_components();\r\n        update_pose_components();\r\n    }\r\n\r\n    true\r\n}\r\n\r\npub fn deactivate_device() {\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    POSE_RAW_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    POSE_TIP_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n}\r\n","epoch":1701,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_scaffold.rs::request_253d5ff6-6f1d-4b2e-83a2-f98328aa01b9",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_253d5ff6-6f1d-4b2e-83a2-f98328aa01b9","content":"//! OpenVR ABI/vtable scaffolding for the Big Haptic Driver.\r\n//!\r\n//! This module owns C ABI exports and interface routing for SteamVR-facing\r\n//! provider/device entry points, while delegating runtime integration\r\n//! (device registration, component setup, and event routing) to\r\n//! `openvr_runtime`.\r\n\r\nuse core::ffi::{c_void, CStr};\r\nuse core::sync::atomic::{AtomicBool, AtomicU32, Ordering};\r\nuse std::os::raw::c_char;\r\n\r\nuse crate::haptics;\r\nuse crate::openvr_runtime;\r\n\r\nconst SERVER_PROVIDER_INTERFACE: &str = \"IServerTrackedDeviceProvider_004\";\r\nconst SERVER_PROVIDER_INTERFACE_ALT: &str = \"IServerTrackedDeviceProvider_005\";\r\nconst TRACKED_DEVICE_INTERFACE: &str = \"ITrackedDeviceServerDriver_005\";\r\nconst VR_INIT_ERROR_DRIVER_FAILED: EvRInitError = 200;\r\n\r\n#[repr(i32)]\r\nenum DriverReturnCode {\r\n    None = 0,\r\n    Unknown = 1,\r\n    InitInterfaceNotFound = 105,\r\n}\r\n\r\ntype EvRInitError = i32;\r\nconst VR_INIT_ERROR_NONE: EvRInitError = 0;\r\nconst TRACKING_RESULT_RUNNING_OK: i32 = 200;\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nstruct DriverPoseQuaternion {\r\n    w: f64,\r\n    x: f64,\r\n    y: f64,\r\n    z: f64,\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nstruct DriverPose {\r\n    pose_time_offset: f64,\r\n    q_world_from_driver_rotation: DriverPoseQuaternion,\r\n    vec_world_from_driver_translation: [f64; 3],\r\n    q_driver_from_head_rotation: DriverPoseQuaternion,\r\n    vec_driver_from_head_translation: [f64; 3],\r\n    vec_position: [f64; 3],\r\n    vec_velocity: [f64; 3],\r\n    vec_acceleration: [f64; 3],\r\n    q_rotation: DriverPoseQuaternion,\r\n    vec_angular_velocity: [f64; 3],\r\n    vec_angular_acceleration: [f64; 3],\r\n    result: i32,\r\n    pose_is_valid: bool,\r\n    will_drift_in_yaw: bool,\r\n    should_apply_head_model: bool,\r\n    device_is_connected: bool,\r\n}\r\n\r\nconst IDENTITY_QUATERNION: DriverPoseQuaternion = DriverPoseQuaternion {\r\n    w: 1.0,\r\n    x: 0.0,\r\n    y: 0.0,\r\n    z: 0.0,\r\n};\r\n\r\nfn connected_controller_pose() -> DriverPose {\r\n    if let Some(hmd_pose) = openvr_runtime::sample_hmd_pose() {\r\n        let rotation = hmd_pose.rotation;\r\n        let position = hmd_pose.position_m;\r\n\r\n        let q_rotation = quaternion_from_rotation_matrix(rotation);\r\n\r\n        return DriverPose {\r\n            pose_time_offset: 0.0,\r\n            q_world_from_driver_rotation: IDENTITY_QUATERNION,\r\n            vec_world_from_driver_translation: [0.0, 0.0, 0.0],\r\n            q_driver_from_head_rotation: IDENTITY_QUATERNION,\r\n            vec_driver_from_head_translation: [0.0, 0.0, 0.0],\r\n            vec_position: [position[0] as f64, position[1] as f64, position[2] as f64],\r\n            vec_velocity: [0.0, 0.0, 0.0],\r\n            vec_acceleration: [0.0, 0.0, 0.0],\r\n            q_rotation,\r\n            vec_angular_velocity: [0.0, 0.0, 0.0],\r\n            vec_angular_acceleration: [0.0, 0.0, 0.0],\r\n            result: TRACKING_RESULT_RUNNING_OK,\r\n            pose_is_valid: true,\r\n            will_drift_in_yaw: false,\r\n            should_apply_head_model: false,\r\n            device_is_connected: true,\r\n        };\r\n    }\r\n\r\n    DriverPose {\r\n        pose_time_offset: 0.0,\r\n        q_world_from_driver_rotation: IDENTITY_QUATERNION,\r\n        vec_world_from_driver_translation: [0.0, 0.0, 0.0],\r\n        q_driver_from_head_rotation: IDENTITY_QUATERNION,\r\n        vec_driver_from_head_translation: [0.0, 0.0, 0.0],\r\n        vec_position: [0.0, 1.2, -0.4],\r\n        vec_velocity: [0.0, 0.0, 0.0],\r\n        vec_acceleration: [0.0, 0.0, 0.0],\r\n        q_rotation: IDENTITY_QUATERNION,\r\n        vec_angular_velocity: [0.0, 0.0, 0.0],\r\n        vec_angular_acceleration: [0.0, 0.0, 0.0],\r\n        result: TRACKING_RESULT_RUNNING_OK,\r\n        pose_is_valid: true,\r\n        will_drift_in_yaw: false,\r\n        should_apply_head_model: false,\r\n        device_is_connected: true,\r\n    }\r\n}\r\n\r\nfn quaternion_from_rotation_matrix(m: [[f32; 3]; 3]) -> DriverPoseQuaternion {\r\n    let m00 = m[0][0] as f64;\r\n    let m01 = m[0][1] as f64;\r\n    let m02 = m[0][2] as f64;\r\n    let m10 = m[1][0] as f64;\r\n    let m11 = m[1][1] as f64;\r\n    let m12 = m[1][2] as f64;\r\n    let m20 = m[2][0] as f64;\r\n    let m21 = m[2][1] as f64;\r\n    let m22 = m[2][2] as f64;\r\n\r\n    let trace = m00 + m11 + m22;\r\n    if trace > 0.0 {\r\n        let s = (trace + 1.0).sqrt() * 2.0;\r\n        return DriverPoseQuaternion {\r\n            w: 0.25 * s,\r\n            x: (m21 - m12) / s,\r\n            y: (m02 - m20) / s,\r\n            z: (m10 - m01) / s,\r\n        };\r\n    }\r\n\r\n    if m00 > m11 && m00 > m22 {\r\n        let s = (1.0 + m00 - m11 - m22).sqrt() * 2.0;\r\n        return DriverPoseQuaternion {\r\n            w: (m21 - m12) / s,\r\n            x: 0.25 * s,\r\n            y: (m01 + m10) / s,\r\n            z: (m02 + m20) / s,\r\n        };\r\n    }\r\n\r\n    if m11 > m22 {\r\n        let s = (1.0 + m11 - m00 - m22).sqrt() * 2.0;\r\n        return DriverPoseQuaternion {\r\n            w: (m02 - m20) / s,\r\n            x: (m01 + m10) / s,\r\n            y: 0.25 * s,\r\n            z: (m12 + m21) / s,\r\n        };\r\n    }\r\n\r\n    let s = (1.0 + m22 - m00 - m11).sqrt() * 2.0;\r\n    DriverPoseQuaternion {\r\n        w: (m10 - m01) / s,\r\n        x: (m02 + m20) / s,\r\n        y: (m12 + m21) / s,\r\n        z: 0.25 * s,\r\n    }\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerTrackedDeviceProviderVTable {\r\n    init: extern \"C\" fn(*mut ServerTrackedDeviceProvider, *mut c_void) -> EvRInitError,\r\n    cleanup: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    get_interface_versions:\r\n        extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> *const *const c_char,\r\n    run_frame: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    should_block_standby_mode: extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> bool,\r\n    enter_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    leave_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n}\r\n\r\n#[repr(C)]\r\nstruct TrackedDeviceServerDriverVTable {\r\n    activate: extern \"C\" fn(*mut TrackedDeviceServerDriver, u32) -> EvRInitError,\r\n    deactivate: extern \"C\" fn(*mut TrackedDeviceServerDriver),\r\n    enter_standby: extern \"C\" fn(*mut TrackedDeviceServerDriver),\r\n    get_component:\r\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char) -> *mut c_void,\r\n    debug_request:\r\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char, *mut c_char, u32),\r\n    get_pose: extern \"C\" fn(*mut TrackedDeviceServerDriver) -> DriverPose,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerTrackedDeviceProvider {\r\n    vtable: *const ServerTrackedDeviceProviderVTable,\r\n}\r\nunsafe impl Sync for ServerTrackedDeviceProvider {}\r\n\r\n#[repr(C)]\r\nstruct TrackedDeviceServerDriver {\r\n    vtable: *const TrackedDeviceServerDriverVTable,\r\n}\r\nunsafe impl Sync for TrackedDeviceServerDriver {}\r\n\r\nstatic DEVICE_INDEX: AtomicU32 = AtomicU32::new(u32::MAX);\r\nstatic DEVICE_ACTIVE: AtomicBool = AtomicBool::new(false);\r\n\r\nextern \"C\" fn provider_init(\r\n    _this: *mut ServerTrackedDeviceProvider,\r\n    driver_context: *mut c_void,\r\n) -> EvRInitError {\r\n    let ok = openvr_runtime::initialize(\r\n        driver_context,\r\n        (&DEVICE as *const TrackedDeviceServerDriver).cast_mut().cast(),\r\n    );\r\n    if !ok {\r\n        return VR_INIT_ERROR_DRIVER_FAILED;\r\n    }\r\n\r\n    VR_INIT_ERROR_NONE\r\n}\r\n\r\nextern \"C\" fn provider_cleanup(_this: *mut ServerTrackedDeviceProvider) {\r\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\r\n    openvr_runtime::cleanup();\r\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn provider_get_interface_versions(\r\n    _this: *mut ServerTrackedDeviceProvider,\r\n) -> *const *const c_char {\r\n    openvr_runtime::interface_versions_ptr()\r\n}\r\n\r\nextern \"C\" fn provider_run_frame(_this: *mut ServerTrackedDeviceProvider) {\r\n    if DEVICE_ACTIVE.load(Ordering::SeqCst) {\r\n        let object_id = DEVICE_INDEX.load(Ordering::SeqCst);\r\n        if object_id != u32::MAX {\r\n            let pose = connected_controller_pose();\r\n            openvr_runtime::push_pose_update(\r\n                object_id,\r\n                (&pose as *const DriverPose).cast(),\r\n                core::mem::size_of::<DriverPose>() as u32,\r\n            );\r\n            openvr_runtime::update_pose_components();\r\n        }\r\n    }\r\n\r\n    openvr_runtime::route_haptic_events();\r\n    haptics::on_frame();\r\n}\r\n\r\nextern \"C\" fn provider_should_block_standby_mode(_this: *mut ServerTrackedDeviceProvider) -> bool {\r\n    false\r\n}\r\n\r\nextern \"C\" fn provider_enter_standby(_this: *mut ServerTrackedDeviceProvider) {}\r\n\r\nextern \"C\" fn provider_leave_standby(_this: *mut ServerTrackedDeviceProvider) {}\r\n\r\nextern \"C\" fn device_activate(_this: *mut TrackedDeviceServerDriver, object_id: u32) -> EvRInitError {\r\n    DEVICE_INDEX.store(object_id, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(true, Ordering::SeqCst);\r\n\r\n    if !openvr_runtime::activate_device(object_id) {\r\n        return VR_INIT_ERROR_DRIVER_FAILED;\r\n    }\r\n\r\n    let pose = connected_controller_pose();\r\n    openvr_runtime::push_pose_update(\r\n        object_id,\r\n        (&pose as *const DriverPose).cast(),\r\n        core::mem::size_of::<DriverPose>() as u32,\r\n    );\r\n\r\n    VR_INIT_ERROR_NONE\r\n}\r\n\r\nextern \"C\" fn device_deactivate(_this: *mut TrackedDeviceServerDriver) {\r\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\r\n    openvr_runtime::deactivate_device();\r\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn device_enter_standby(_this: *mut TrackedDeviceServerDriver) {\r\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn device_get_component(\r\n    _this: *mut TrackedDeviceServerDriver,\r\n    _component_name: *const c_char,\r\n) -> *mut c_void {\r\n    core::ptr::null_mut()\r\n}\r\n\r\nextern \"C\" fn device_debug_request(\r\n    _this: *mut TrackedDeviceServerDriver,\r\n    _request: *const c_char,\r\n    response: *mut c_char,\r\n    response_size: u32,\r\n) {\r\n    if response.is_null() || response_size == 0 {\r\n        return;\r\n    }\r\n    unsafe {\r\n        *response = 0;\r\n    }\r\n}\r\n\r\nextern \"C\" fn device_get_pose(_this: *mut TrackedDeviceServerDriver) -> DriverPose {\r\n    connected_controller_pose()\r\n}\r\n\r\nstatic PROVIDER_VTABLE: ServerTrackedDeviceProviderVTable = ServerTrackedDeviceProviderVTable {\r\n    init: provider_init,\r\n    cleanup: provider_cleanup,\r\n    get_interface_versions: provider_get_interface_versions,\r\n    run_frame: provider_run_frame,\r\n    should_block_standby_mode: provider_should_block_standby_mode,\r\n    enter_standby: provider_enter_standby,\r\n    leave_standby: provider_leave_standby,\r\n};\r\n\r\nstatic DEVICE_VTABLE: TrackedDeviceServerDriverVTable = TrackedDeviceServerDriverVTable {\r\n    activate: device_activate,\r\n    deactivate: device_deactivate,\r\n    enter_standby: device_enter_standby,\r\n    get_component: device_get_component,\r\n    debug_request: device_debug_request,\r\n    get_pose: device_get_pose,\r\n};\r\n\r\nstatic PROVIDER: ServerTrackedDeviceProvider = ServerTrackedDeviceProvider {\r\n    vtable: &PROVIDER_VTABLE,\r\n};\r\n\r\nstatic DEVICE: TrackedDeviceServerDriver = TrackedDeviceServerDriver {\r\n    vtable: &DEVICE_VTABLE,\r\n};\r\n\r\nfn set_return_code(out: *mut i32, code: DriverReturnCode) {\r\n    if !out.is_null() {\r\n        unsafe {\r\n            *out = code as i32;\r\n        }\r\n    }\r\n}\r\n\r\nfn read_interface_name(ptr: *const i8) -> Option<&'static str> {\r\n    if ptr.is_null() {\r\n        return None;\r\n    }\r\n\r\n    let cstr = unsafe { CStr::from_ptr(ptr) };\r\n    let Ok(name) = cstr.to_str() else {\r\n        return None;\r\n    };\r\n\r\n    if name == SERVER_PROVIDER_INTERFACE {\r\n        return Some(SERVER_PROVIDER_INTERFACE);\r\n    }\r\n    if name == SERVER_PROVIDER_INTERFACE_ALT {\r\n        return Some(SERVER_PROVIDER_INTERFACE);\r\n    }\r\n    if name == TRACKED_DEVICE_INTERFACE {\r\n        return Some(TRACKED_DEVICE_INTERFACE);\r\n    }\r\n\r\n    None\r\n}\r\n\r\n/// Resolves OpenVR interface requests to static provider/device instances.\r\n///\r\n/// On match, writes `DriverReturnCode::None` to `p_return_code` and returns a\r\n/// stable interface pointer. On failure, writes an error code and returns null.\r\npub fn hmd_driver_factory(\r\n    p_interface_name: *const c_char,\r\n    p_return_code: *mut i32,\r\n) -> *mut c_void {\r\n    let Some(interface_name) = read_interface_name(p_interface_name) else {\r\n        set_return_code(p_return_code, DriverReturnCode::InitInterfaceNotFound);\r\n        return core::ptr::null_mut();\r\n    };\r\n\r\n    if interface_name == SERVER_PROVIDER_INTERFACE {\r\n        set_return_code(p_return_code, DriverReturnCode::None);\r\n        return (&PROVIDER as *const ServerTrackedDeviceProvider)\r\n            .cast_mut()\r\n            .cast();\r\n    }\r\n\r\n    if interface_name == TRACKED_DEVICE_INTERFACE {\r\n        set_return_code(p_return_code, DriverReturnCode::None);\r\n        return (&DEVICE as *const TrackedDeviceServerDriver)\r\n            .cast_mut()\r\n            .cast();\r\n    }\r\n\r\n    set_return_code(p_return_code, DriverReturnCode::Unknown);\r\n    core::ptr::null_mut()\r\n}\r\n","epoch":1704,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/big_haptic_profile.json::request_253d5ff6-6f1d-4b2e-83a2-f98328aa01b9",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\input\\big_haptic_profile.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/big_haptic_profile.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/big_haptic_profile.json","scheme":"file"},"requestId":"request_253d5ff6-6f1d-4b2e-83a2-f98328aa01b9","content":"{\r\n  \"jsonid\": \"input_profile\",\r\n  \"controller_type\": \"big_haptic_driver\",\r\n  \"device_class\": \"TrackedDeviceClass_Controller\",\r\n  \"input_bindingui_mode\": \"controller_handed\",\r\n  \"legacy_binding\": \"{system}/legacy_bindings_generic.json\",\r\n  \"input_source\": {\r\n    \"/output/haptic\": {\r\n      \"type\": \"vibration\",\r\n      \"binding_image_point\": [0.5, 0.5]\r\n    },\r\n    \"/pose/raw\": {\r\n      \"type\": \"pose\",\r\n      \"binding_image_point\": [0.5, 0.2]\r\n    },\r\n    \"/pose/tip\": {\r\n      \"type\": \"pose\",\r\n      \"binding_image_point\": [0.5, 0.1]\r\n    }\r\n  },\r\n  \"default_bindings\": [\r\n    {\r\n      \"app_key\": \"openvr.component.vrcompositor\",\r\n      \"binding_url\": \"vrcompositor_bindings_big_haptic_driver.json\"\r\n    }\r\n  ]\r\n}\r\n","epoch":1707,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json::request_253d5ff6-6f1d-4b2e-83a2-f98328aa01b9",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\input\\vrcompositor_bindings_big_haptic_driver.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","scheme":"file"},"requestId":"request_253d5ff6-6f1d-4b2e-83a2-f98328aa01b9","content":"{\r\n  \"action_manifest_version\": 0,\r\n  \"app_key\": \"openvr.component.vrcompositor\",\r\n  \"bindings\": {\r\n    \"/actions/lasermouse\": {\r\n      \"haptics\": [\r\n        {\r\n          \"output\": \"/actions/lasermouse/out/haptic\",\r\n          \"path\": \"/user/hand/left/output/haptic\"\r\n        },\r\n        {\r\n          \"output\": \"/actions/lasermouse/out/haptic\",\r\n          \"path\": \"/user/hand/right/output/haptic\"\r\n        }\r\n      ],\r\n      \"poses\": [\r\n        {\r\n          \"output\": \"/actions/lasermouse/in/Pointer\",\r\n          \"path\": \"/user/hand/left/pose/tip\"\r\n        },\r\n        {\r\n          \"output\": \"/actions/lasermouse/in/Pointer\",\r\n          \"path\": \"/user/hand/right/pose/tip\"\r\n        }\r\n      ]\r\n    }\r\n  }\r\n}\r\n","epoch":1710,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs::request_52caa11c-9347-4b92-bae5-d4d2c73cbf1f",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_52caa11c-9347-4b92-bae5-d4d2c73cbf1f","content":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse core::sync::atomic::{AtomicU64, Ordering};\r\nuse std::sync::{Mutex, OnceLock};\r\nuse std::time::Instant;\r\nuse std::time::Duration;\r\nuse std::thread;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic STOP_DEADLINE_MS: AtomicU64 = AtomicU64::new(0);\r\nstatic STOP_TIMER_TOKEN: AtomicU64 = AtomicU64::new(0);\r\nstatic START_INSTANT: OnceLock<Instant> = OnceLock::new();\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\nstatic HID_SEND_LOCK: Mutex<()> = Mutex::new(());\r\nstatic IDENTIFY_BURST_DEADLINE_MS: AtomicU64 = AtomicU64::new(0);\r\n\r\nconst OPENVR_MIN_FREQUENCY_HZ: f32 = 1_000_000.0 / 65_535.0;\r\nconst OPENVR_MAX_FREQUENCY_HZ: f32 = 1_000_000.0 / 300.0;\r\nconst OPENVR_MAX_DURATION_SECONDS: f32 = 10.0;\r\nconst HARDWARE_MIN_PULSE_SECONDS: f32 = 0.02;\r\nconst IDENTIFY_SIGNATURE_FREQUENCY_HZ: f32 = 200.0;\r\nconst IDENTIFY_SIGNATURE_FREQUENCY_TOLERANCE_HZ: f32 = 2.0;\r\nconst IDENTIFY_SIGNATURE_MAX_AMPLITUDE: f32 = 0.13;\r\nconst IDENTIFY_MIN_AMPLITUDE: f32 = 0.30;\r\nconst IDENTIFY_PULSE_SECONDS: f32 = 0.50;\r\nconst IDENTIFY_BURST_WINDOW_MS: u64 = 1_500;\r\nconst HID_SEND_ATTEMPTS: usize = 3;\r\nconst HID_SEND_RETRY_DELAY_MS: u64 = 2;\r\n\r\nfn log_haptics(message: &str) {\r\n    crate::openvr_runtime::log_driver_message(message);\r\n}\r\n\r\nfn send_packet(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    let _hid_guard = HID_SEND_LOCK.lock().ok();\r\n\r\n    let mut last_error = None;\r\n\r\n    for attempt in 1..=HID_SEND_ATTEMPTS {\r\n        match hid_bridge::HidBridge::new()\r\n            .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n        {\r\n            Ok(()) => return Ok(()),\r\n            Err(err) => {\r\n                last_error = Some(err);\r\n                if attempt < HID_SEND_ATTEMPTS {\r\n                    thread::sleep(Duration::from_millis(HID_SEND_RETRY_DELAY_MS));\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    let err = last_error.expect(\"HID send attempts exhausted without captured error\");\r\n    log_haptics(&format!(\r\n        \"haptics: HID send failed after {} attempt(s); cmd=0x{:02X}, intensity={}, arg_lo={}, arg_hi={}, err={}\",\r\n        HID_SEND_ATTEMPTS,\r\n        packet.command_id,\r\n        packet.intensity,\r\n        packet.arg_lo,\r\n        packet.arg_hi,\r\n        err\r\n    ));\r\n    Err(err)\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    send_packet(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn now_ms() -> u64 {\r\n    let start = START_INSTANT.get_or_init(Instant::now);\r\n    let ms = Instant::now().duration_since(*start).as_millis();\r\n    ms.min(u128::from(u64::MAX)) as u64\r\n}\r\n\r\nfn schedule_stop_after(duration_seconds: f32) {\r\n    if duration_seconds <= 0.0 {\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let duration_ms = (duration_seconds * 1000.0).round().max(1.0) as u64;\r\n    let deadline = now_ms().saturating_add(duration_ms);\r\n    STOP_DEADLINE_MS.store(deadline, Ordering::SeqCst);\r\n\r\n    let token = STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst).saturating_add(1);\r\n    std::thread::spawn(move || {\r\n        std::thread::sleep(Duration::from_millis(duration_ms));\r\n\r\n        if STOP_TIMER_TOKEN.load(Ordering::SeqCst) != token {\r\n            return;\r\n        }\r\n\r\n        if let Err(err) = send_stop() {\r\n            log_haptics(&format!(\"haptics: delayed stop failed: {}\", err));\r\n        }\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    });\r\n}\r\n\r\nfn resolve_effective_duration_seconds(duration_seconds: f32, frequency_hz: f32) -> f32 {\r\n    let clamped_duration = duration_seconds.clamp(0.0, OPENVR_MAX_DURATION_SECONDS);\r\n    if clamped_duration > 0.0 {\r\n        return clamped_duration;\r\n    }\r\n\r\n    // OpenVR guidance: when duration is 0, trigger exactly one pulse.\r\n    // We approximate one pulse as half a period at the requested frequency,\r\n    // but enforce a hardware minimum so pulses are actually perceptible.\r\n    (0.5 / frequency_hz).max(HARDWARE_MIN_PULSE_SECONDS)\r\n}\r\n\r\nfn looks_like_identify_campaign(req: HapticVibrationRequest) -> bool {\r\n    req.duration_seconds <= 0.0\r\n        && (req.frequency - IDENTIFY_SIGNATURE_FREQUENCY_HZ).abs()\r\n            <= IDENTIFY_SIGNATURE_FREQUENCY_TOLERANCE_HZ\r\n        && req.amplitude > 0.0\r\n        && req.amplitude <= IDENTIFY_SIGNATURE_MAX_AMPLITUDE\r\n}\r\n\r\nfn process_pending_stop() {\r\n    let deadline = STOP_DEADLINE_MS.load(Ordering::SeqCst);\r\n    if deadline == 0 {\r\n        return;\r\n    }\r\n\r\n    if now_ms() >= deadline {\r\n        if let Err(err) = send_stop() {\r\n            log_haptics(&format!(\"haptics: frame stop failed: {}\", err));\r\n        }\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        if let Err(err) = send_stop() {\r\n            log_haptics(&format!(\"haptics: stop-on-zero-amplitude failed: {}\", err));\r\n        }\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    if req.frequency <= 0.0 {\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        if let Err(err) = send_stop() {\r\n            log_haptics(&format!(\"haptics: stop-on-nonpositive-frequency failed: {}\", err));\r\n        }\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let amplitude = req.amplitude.clamp(0.0, 1.0);\r\n    let frequency_hz = req\r\n        .frequency\r\n        .clamp(OPENVR_MIN_FREQUENCY_HZ, OPENVR_MAX_FREQUENCY_HZ);\r\n\r\n    let is_identify_campaign = looks_like_identify_campaign(req);\r\n\r\n    if is_identify_campaign {\r\n        let now = now_ms();\r\n        let active_until = IDENTIFY_BURST_DEADLINE_MS.load(Ordering::SeqCst);\r\n        if now < active_until {\r\n            return;\r\n        }\r\n\r\n        IDENTIFY_BURST_DEADLINE_MS\r\n            .store(now.saturating_add(IDENTIFY_BURST_WINDOW_MS), Ordering::SeqCst);\r\n    }\r\n\r\n    let effective_amplitude = if is_identify_campaign {\r\n        amplitude.max(IDENTIFY_MIN_AMPLITUDE)\r\n    } else {\r\n        amplitude\r\n    };\r\n\r\n    if let Err(err) = send_amplitude(effective_amplitude) {\r\n        log_haptics(&format!(\r\n            \"haptics: send_amplitude failed; amp={:.6}, freq={:.3}, duration_s={:.6}, err={}\",\r\n            effective_amplitude,\r\n            frequency_hz,\r\n            req.duration_seconds,\r\n            err\r\n        ));\r\n        return;\r\n    }\r\n\r\n    let mut effective_duration =\r\n        resolve_effective_duration_seconds(req.duration_seconds, frequency_hz);\r\n    if is_identify_campaign {\r\n        effective_duration = IDENTIFY_PULSE_SECONDS;\r\n    }\r\n\r\n    schedule_stop_after(effective_duration);\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n    process_pending_stop();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","epoch":1714,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs::request_f1f75ee3-7832-415f-ba36-db253bd15323",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_f1f75ee3-7832-415f-ba36-db253bd15323","content":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse core::sync::atomic::{AtomicU64, Ordering};\r\nuse std::sync::{mpsc, Mutex, OnceLock};\r\nuse std::time::Instant;\r\nuse std::time::Duration;\r\nuse std::thread;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic STOP_DEADLINE_MS: AtomicU64 = AtomicU64::new(0);\r\nstatic START_INSTANT: OnceLock<Instant> = OnceLock::new();\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\nstatic HID_TX: OnceLock<mpsc::Sender<HapticPacket>> = OnceLock::new();\r\n\r\nconst OPENVR_MIN_FREQUENCY_HZ: f32 = 1_000_000.0 / 65_535.0;\r\nconst OPENVR_MAX_FREQUENCY_HZ: f32 = 1_000_000.0 / 300.0;\r\nconst OPENVR_MAX_DURATION_SECONDS: f32 = 10.0;\r\nconst HARDWARE_MIN_PULSE_SECONDS: f32 = 0.02;\r\nconst HID_SEND_ATTEMPTS: usize = 3;\r\nconst HID_SEND_RETRY_DELAY_MS: u64 = 2;\r\n\r\nfn log_haptics(message: &str) {\r\n    crate::openvr_runtime::log_driver_message(message);\r\n}\r\n\r\nfn send_packet_blocking(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    let mut last_error = None;\r\n\r\n    for attempt in 1..=HID_SEND_ATTEMPTS {\r\n        match hid_bridge::HidBridge::new()\r\n            .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n        {\r\n            Ok(()) => return Ok(()),\r\n            Err(err) => {\r\n                last_error = Some(err);\r\n                if attempt < HID_SEND_ATTEMPTS {\r\n                    thread::sleep(Duration::from_millis(HID_SEND_RETRY_DELAY_MS));\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    let err = last_error.expect(\"HID send attempts exhausted without captured error\");\r\n    log_haptics(&format!(\r\n        \"haptics: HID send failed after {} attempt(s); cmd=0x{:02X}, intensity={}, arg_lo={}, arg_hi={}, err={}\",\r\n        HID_SEND_ATTEMPTS,\r\n        packet.command_id,\r\n        packet.intensity,\r\n        packet.arg_lo,\r\n        packet.arg_hi,\r\n        err\r\n    ));\r\n    Err(err)\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet_blocking(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    send_packet_blocking(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn hid_tx() -> &'static mpsc::Sender<HapticPacket> {\r\n    HID_TX.get_or_init(|| {\r\n        let (tx, rx) = mpsc::channel::<HapticPacket>();\r\n\r\n        std::thread::spawn(move || {\r\n            while let Ok(packet) = rx.recv() {\r\n                let _ = send_packet_blocking(packet);\r\n            }\r\n        });\r\n\r\n        tx\r\n    })\r\n}\r\n\r\nfn queue_packet(packet: HapticPacket) {\r\n    if let Err(err) = hid_tx().send(packet) {\r\n        log_haptics(&format!(\"haptics: queue send failed: {}\", err));\r\n    }\r\n}\r\n\r\nfn queue_stop() {\r\n    queue_packet(HapticPacket::new(command::STOP, 0, 0, 0));\r\n}\r\n\r\nfn queue_amplitude(amplitude: f32) {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    queue_packet(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0));\r\n}\r\n\r\nfn now_ms() -> u64 {\r\n    let start = START_INSTANT.get_or_init(Instant::now);\r\n    let ms = Instant::now().duration_since(*start).as_millis();\r\n    ms.min(u128::from(u64::MAX)) as u64\r\n}\r\n\r\nfn schedule_stop_after(duration_seconds: f32) {\r\n    if duration_seconds <= 0.0 {\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let duration_ms = (duration_seconds * 1000.0).round().max(1.0) as u64;\r\n    let deadline = now_ms().saturating_add(duration_ms);\r\n    STOP_DEADLINE_MS.store(deadline, Ordering::SeqCst);\r\n}\r\n\r\nfn resolve_effective_duration_seconds(duration_seconds: f32, frequency_hz: f32) -> f32 {\r\n    let clamped_duration = duration_seconds.clamp(0.0, OPENVR_MAX_DURATION_SECONDS);\r\n    if clamped_duration > 0.0 {\r\n        return clamped_duration;\r\n    }\r\n\r\n    // OpenVR guidance: when duration is 0, trigger exactly one pulse.\r\n    // We approximate one pulse as half a period at the requested frequency,\r\n    // but enforce a hardware minimum so pulses are actually perceptible.\r\n    (0.5 / frequency_hz).max(HARDWARE_MIN_PULSE_SECONDS)\r\n}\r\n\r\nfn process_pending_stop() {\r\n    let deadline = STOP_DEADLINE_MS.load(Ordering::SeqCst);\r\n    if deadline == 0 {\r\n        return;\r\n    }\r\n\r\n    if now_ms() >= deadline {\r\n        queue_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        queue_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    if req.frequency <= 0.0 {\r\n        queue_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let amplitude = req.amplitude.clamp(0.0, 1.0);\r\n    let frequency_hz = req\r\n        .frequency\r\n        .clamp(OPENVR_MIN_FREQUENCY_HZ, OPENVR_MAX_FREQUENCY_HZ);\r\n\r\n    queue_amplitude(amplitude);\r\n\r\n    let effective_duration =\r\n        resolve_effective_duration_seconds(req.duration_seconds, frequency_hz);\r\n\r\n    schedule_stop_after(effective_duration);\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n    process_pending_stop();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","epoch":1726,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs::request_19ae42fe-2a8e-4927-9a95-27d34479b063",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_19ae42fe-2a8e-4927-9a95-27d34479b063","content":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse core::sync::atomic::{AtomicU64, Ordering};\r\nuse std::sync::{mpsc, Mutex, OnceLock};\r\nuse std::time::Instant;\r\nuse std::time::Duration;\r\nuse std::thread;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic STOP_DEADLINE_MS: AtomicU64 = AtomicU64::new(0);\r\nstatic START_INSTANT: OnceLock<Instant> = OnceLock::new();\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\nstatic HID_TX: OnceLock<mpsc::Sender<HapticPacket>> = OnceLock::new();\r\n\r\nconst OPENVR_MIN_FREQUENCY_HZ: f32 = 1_000_000.0 / 65_535.0;\r\nconst OPENVR_MAX_FREQUENCY_HZ: f32 = 1_000_000.0 / 300.0;\r\nconst OPENVR_MAX_DURATION_SECONDS: f32 = 10.0;\r\nconst HARDWARE_MIN_PULSE_SECONDS: f32 = 0.02;\r\nconst ERM_AMPLITUDE_GAIN: f32 = 1.8;\r\nconst ERM_MIN_NONZERO_AMPLITUDE: f32 = 0.25;\r\nconst HID_SEND_ATTEMPTS: usize = 3;\r\nconst HID_SEND_RETRY_DELAY_MS: u64 = 2;\r\n\r\nfn log_haptics(message: &str) {\r\n    crate::openvr_runtime::log_driver_message(message);\r\n}\r\n\r\nfn send_packet_blocking(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    let mut last_error = None;\r\n\r\n    for attempt in 1..=HID_SEND_ATTEMPTS {\r\n        match hid_bridge::HidBridge::new()\r\n            .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n        {\r\n            Ok(()) => return Ok(()),\r\n            Err(err) => {\r\n                last_error = Some(err);\r\n                if attempt < HID_SEND_ATTEMPTS {\r\n                    thread::sleep(Duration::from_millis(HID_SEND_RETRY_DELAY_MS));\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    let err = last_error.expect(\"HID send attempts exhausted without captured error\");\r\n    log_haptics(&format!(\r\n        \"haptics: HID send failed after {} attempt(s); cmd=0x{:02X}, intensity={}, arg_lo={}, arg_hi={}, err={}\",\r\n        HID_SEND_ATTEMPTS,\r\n        packet.command_id,\r\n        packet.intensity,\r\n        packet.arg_lo,\r\n        packet.arg_hi,\r\n        err\r\n    ));\r\n    Err(err)\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet_blocking(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    send_packet_blocking(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn hid_tx() -> &'static mpsc::Sender<HapticPacket> {\r\n    HID_TX.get_or_init(|| {\r\n        let (tx, rx) = mpsc::channel::<HapticPacket>();\r\n\r\n        std::thread::spawn(move || {\r\n            while let Ok(packet) = rx.recv() {\r\n                let _ = send_packet_blocking(packet);\r\n            }\r\n        });\r\n\r\n        tx\r\n    })\r\n}\r\n\r\nfn queue_packet(packet: HapticPacket) {\r\n    if let Err(err) = hid_tx().send(packet) {\r\n        log_haptics(&format!(\"haptics: queue send failed: {}\", err));\r\n    }\r\n}\r\n\r\nfn queue_stop() {\r\n    queue_packet(HapticPacket::new(command::STOP, 0, 0, 0));\r\n}\r\n\r\nfn queue_amplitude(amplitude: f32) {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    queue_packet(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0));\r\n}\r\n\r\nfn shape_amplitude_for_erm(amplitude: f32) -> f32 {\r\n    if amplitude <= 0.0 {\r\n        return 0.0;\r\n    }\r\n\r\n    let boosted = (amplitude * ERM_AMPLITUDE_GAIN).clamp(0.0, 1.0);\r\n    boosted.max(ERM_MIN_NONZERO_AMPLITUDE)\r\n}\r\n\r\nfn now_ms() -> u64 {\r\n    let start = START_INSTANT.get_or_init(Instant::now);\r\n    let ms = Instant::now().duration_since(*start).as_millis();\r\n    ms.min(u128::from(u64::MAX)) as u64\r\n}\r\n\r\nfn schedule_stop_after(duration_seconds: f32) {\r\n    if duration_seconds <= 0.0 {\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let duration_ms = (duration_seconds * 1000.0).round().max(1.0) as u64;\r\n    let deadline = now_ms().saturating_add(duration_ms);\r\n    STOP_DEADLINE_MS.store(deadline, Ordering::SeqCst);\r\n}\r\n\r\nfn resolve_effective_duration_seconds(duration_seconds: f32, frequency_hz: f32) -> f32 {\r\n    let clamped_duration = duration_seconds.clamp(0.0, OPENVR_MAX_DURATION_SECONDS);\r\n    if clamped_duration > 0.0 {\r\n        return clamped_duration;\r\n    }\r\n\r\n    // OpenVR guidance: when duration is 0, trigger exactly one pulse.\r\n    // We approximate one pulse as half a period at the requested frequency,\r\n    // but enforce a hardware minimum so pulses are actually perceptible.\r\n    (0.5 / frequency_hz).max(HARDWARE_MIN_PULSE_SECONDS)\r\n}\r\n\r\nfn process_pending_stop() {\r\n    let deadline = STOP_DEADLINE_MS.load(Ordering::SeqCst);\r\n    if deadline == 0 {\r\n        return;\r\n    }\r\n\r\n    if now_ms() >= deadline {\r\n        queue_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        queue_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    if req.frequency <= 0.0 {\r\n        queue_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let amplitude = shape_amplitude_for_erm(req.amplitude.clamp(0.0, 1.0));\r\n    let frequency_hz = req\r\n        .frequency\r\n        .clamp(OPENVR_MIN_FREQUENCY_HZ, OPENVR_MAX_FREQUENCY_HZ);\r\n\r\n    queue_amplitude(amplitude);\r\n\r\n    let effective_duration =\r\n        resolve_effective_duration_seconds(req.duration_seconds, frequency_hz);\r\n\r\n    schedule_stop_after(effective_duration);\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n    process_pending_stop();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","epoch":1730,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs::request_ea2c63b4-ffe0-48a8-aaf1-044afe929342",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_ea2c63b4-ffe0-48a8-aaf1-044afe929342","content":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse core::sync::atomic::{AtomicU64, Ordering};\r\nuse std::sync::{Condvar, Mutex, OnceLock};\r\nuse std::time::Instant;\r\nuse std::time::Duration;\r\nuse std::thread;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic STOP_DEADLINE_MS: AtomicU64 = AtomicU64::new(0);\r\nstatic START_INSTANT: OnceLock<Instant> = OnceLock::new();\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\n\r\nstruct HidMailbox {\r\n    packet: Mutex<Option<HapticPacket>>,\r\n    signal: Condvar,\r\n}\r\n\r\nstatic HID_MAILBOX: OnceLock<HidMailbox> = OnceLock::new();\r\n\r\nconst OPENVR_MIN_FREQUENCY_HZ: f32 = 1_000_000.0 / 65_535.0;\r\nconst OPENVR_MAX_FREQUENCY_HZ: f32 = 1_000_000.0 / 300.0;\r\nconst OPENVR_MAX_DURATION_SECONDS: f32 = 10.0;\r\nconst HARDWARE_MIN_PULSE_SECONDS: f32 = 0.02;\r\nconst ERM_AMPLITUDE_GAIN: f32 = 1.8;\r\nconst ERM_MIN_NONZERO_AMPLITUDE: f32 = 0.25;\r\nconst HID_SEND_ATTEMPTS: usize = 3;\r\nconst HID_SEND_RETRY_DELAY_MS: u64 = 2;\r\n\r\nfn log_haptics(message: &str) {\r\n    crate::openvr_runtime::log_driver_message(message);\r\n}\r\n\r\nfn send_packet_blocking(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    let mut last_error = None;\r\n\r\n    for attempt in 1..=HID_SEND_ATTEMPTS {\r\n        match hid_bridge::HidBridge::new()\r\n            .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n        {\r\n            Ok(()) => return Ok(()),\r\n            Err(err) => {\r\n                last_error = Some(err);\r\n                if attempt < HID_SEND_ATTEMPTS {\r\n                    thread::sleep(Duration::from_millis(HID_SEND_RETRY_DELAY_MS));\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    let err = last_error.expect(\"HID send attempts exhausted without captured error\");\r\n    log_haptics(&format!(\r\n        \"haptics: HID send failed after {} attempt(s); cmd=0x{:02X}, intensity={}, arg_lo={}, arg_hi={}, err={}\",\r\n        HID_SEND_ATTEMPTS,\r\n        packet.command_id,\r\n        packet.intensity,\r\n        packet.arg_lo,\r\n        packet.arg_hi,\r\n        err\r\n    ));\r\n    Err(err)\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet_blocking(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    send_packet_blocking(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn hid_mailbox() -> &'static HidMailbox {\r\n    HID_MAILBOX.get_or_init(|| {\r\n        let mailbox = HidMailbox {\r\n            packet: Mutex::new(None),\r\n            signal: Condvar::new(),\r\n        };\r\n\r\n        std::thread::spawn(|| {\r\n            let mailbox = hid_mailbox();\r\n            'worker: loop {\r\n                let packet = {\r\n                    let mut guard = match mailbox.packet.lock() {\r\n                        Ok(g) => g,\r\n                        Err(_) => continue 'worker,\r\n                    };\r\n\r\n                    while guard.is_none() {\r\n                        let waited = mailbox.signal.wait(guard);\r\n                        guard = match waited {\r\n                            Ok(g) => g,\r\n                            Err(_) => continue 'worker,\r\n                        };\r\n                    }\r\n\r\n                    guard.take().expect(\"mailbox packet should exist\")\r\n                };\r\n\r\n                let _ = send_packet_blocking(packet);\r\n            }\r\n        });\r\n\r\n        mailbox\r\n    })\r\n}\r\n\r\nfn queue_packet(packet: HapticPacket) {\r\n    let mailbox = hid_mailbox();\r\n    if let Ok(mut guard) = mailbox.packet.lock() {\r\n        *guard = Some(packet);\r\n        mailbox.signal.notify_one();\r\n    } else {\r\n        log_haptics(\"haptics: queue send failed: mailbox lock poisoned\");\r\n    }\r\n}\r\n\r\nfn queue_stop() {\r\n    queue_packet(HapticPacket::new(command::STOP, 0, 0, 0));\r\n}\r\n\r\nfn queue_amplitude(amplitude: f32) {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    queue_packet(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0));\r\n}\r\n\r\nfn shape_amplitude_for_erm(amplitude: f32) -> f32 {\r\n    if amplitude <= 0.0 {\r\n        return 0.0;\r\n    }\r\n\r\n    let boosted = (amplitude * ERM_AMPLITUDE_GAIN).clamp(0.0, 1.0);\r\n    boosted.max(ERM_MIN_NONZERO_AMPLITUDE)\r\n}\r\n\r\nfn now_ms() -> u64 {\r\n    let start = START_INSTANT.get_or_init(Instant::now);\r\n    let ms = Instant::now().duration_since(*start).as_millis();\r\n    ms.min(u128::from(u64::MAX)) as u64\r\n}\r\n\r\nfn schedule_stop_after(duration_seconds: f32) {\r\n    if duration_seconds <= 0.0 {\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let duration_ms = (duration_seconds * 1000.0).round().max(1.0) as u64;\r\n    let deadline = now_ms().saturating_add(duration_ms);\r\n    STOP_DEADLINE_MS.store(deadline, Ordering::SeqCst);\r\n}\r\n\r\nfn resolve_effective_duration_seconds(duration_seconds: f32, frequency_hz: f32) -> f32 {\r\n    let clamped_duration = duration_seconds.clamp(0.0, OPENVR_MAX_DURATION_SECONDS);\r\n    if clamped_duration > 0.0 {\r\n        return clamped_duration;\r\n    }\r\n\r\n    // OpenVR guidance: when duration is 0, trigger exactly one pulse.\r\n    // We approximate one pulse as half a period at the requested frequency,\r\n    // but enforce a hardware minimum so pulses are actually perceptible.\r\n    (0.5 / frequency_hz).max(HARDWARE_MIN_PULSE_SECONDS)\r\n}\r\n\r\nfn process_pending_stop() {\r\n    let deadline = STOP_DEADLINE_MS.load(Ordering::SeqCst);\r\n    if deadline == 0 {\r\n        return;\r\n    }\r\n\r\n    if now_ms() >= deadline {\r\n        queue_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        queue_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    if req.frequency <= 0.0 {\r\n        queue_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let amplitude = shape_amplitude_for_erm(req.amplitude.clamp(0.0, 1.0));\r\n    let frequency_hz = req\r\n        .frequency\r\n        .clamp(OPENVR_MIN_FREQUENCY_HZ, OPENVR_MAX_FREQUENCY_HZ);\r\n\r\n    queue_amplitude(amplitude);\r\n\r\n    let effective_duration =\r\n        resolve_effective_duration_seconds(req.duration_seconds, frequency_hz);\r\n\r\n    schedule_stop_after(effective_duration);\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n    process_pending_stop();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","epoch":1736,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs::request_9a5be07b-e7a2-40f4-9005-8520832f2361",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_9a5be07b-e7a2-40f4-9005-8520832f2361","content":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse std::sync::{mpsc, Mutex, OnceLock};\r\nuse std::time::Duration;\r\nuse std::time::Instant;\r\nuse std::thread;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\n\r\nenum WorkerCommand {\r\n    Pulse {\r\n        intensity: u8,\r\n        duration_ms: u64,\r\n    },\r\n    Stop,\r\n}\r\n\r\nstatic HID_TX: OnceLock<mpsc::Sender<WorkerCommand>> = OnceLock::new();\r\n\r\nconst OPENVR_MIN_FREQUENCY_HZ: f32 = 1_000_000.0 / 65_535.0;\r\nconst OPENVR_MAX_FREQUENCY_HZ: f32 = 1_000_000.0 / 300.0;\r\nconst OPENVR_MAX_DURATION_SECONDS: f32 = 10.0;\r\nconst HARDWARE_MIN_PULSE_SECONDS: f32 = 0.05;\r\nconst ERM_AMPLITUDE_GAIN: f32 = 1.8;\r\nconst ERM_MIN_NONZERO_AMPLITUDE: f32 = 0.25;\r\nconst HID_SEND_ATTEMPTS: usize = 3;\r\nconst HID_SEND_RETRY_DELAY_MS: u64 = 2;\r\n\r\nfn log_haptics(message: &str) {\r\n    crate::openvr_runtime::log_driver_message(message);\r\n}\r\n\r\nfn send_packet_blocking(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    let mut last_error = None;\r\n\r\n    for attempt in 1..=HID_SEND_ATTEMPTS {\r\n        match hid_bridge::HidBridge::new()\r\n            .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n        {\r\n            Ok(()) => return Ok(()),\r\n            Err(err) => {\r\n                last_error = Some(err);\r\n                if attempt < HID_SEND_ATTEMPTS {\r\n                    thread::sleep(Duration::from_millis(HID_SEND_RETRY_DELAY_MS));\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    let err = last_error.expect(\"HID send attempts exhausted without captured error\");\r\n    log_haptics(&format!(\r\n        \"haptics: HID send failed after {} attempt(s); cmd=0x{:02X}, intensity={}, arg_lo={}, arg_hi={}, err={}\",\r\n        HID_SEND_ATTEMPTS,\r\n        packet.command_id,\r\n        packet.intensity,\r\n        packet.arg_lo,\r\n        packet.arg_hi,\r\n        err\r\n    ));\r\n    Err(err)\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet_blocking(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = amplitude_to_intensity(amp);\r\n    send_packet_blocking(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn amplitude_to_intensity(amplitude: f32) -> u8 {\r\n    (amplitude.clamp(0.0, 1.0) * 255.0).round() as u8\r\n}\r\n\r\nfn run_hid_worker(rx: mpsc::Receiver<WorkerCommand>) {\r\n    let mut active_until: Option<Instant> = None;\r\n\r\n    loop {\r\n        let incoming = if let Some(deadline) = active_until {\r\n            let now = Instant::now();\r\n            if now >= deadline {\r\n                let _ = send_stop();\r\n                active_until = None;\r\n                continue;\r\n            }\r\n\r\n            match rx.recv_timeout(deadline.saturating_duration_since(now)) {\r\n                Ok(cmd) => Some(cmd),\r\n                Err(mpsc::RecvTimeoutError::Timeout) => {\r\n                    let _ = send_stop();\r\n                    active_until = None;\r\n                    continue;\r\n                }\r\n                Err(mpsc::RecvTimeoutError::Disconnected) => None,\r\n            }\r\n        } else {\r\n            match rx.recv() {\r\n                Ok(cmd) => Some(cmd),\r\n                Err(_) => None,\r\n            }\r\n        };\r\n\r\n        let Some(cmd) = incoming else {\r\n            break;\r\n        };\r\n\r\n        match cmd {\r\n            WorkerCommand::Stop => {\r\n                let _ = send_stop();\r\n                active_until = None;\r\n            }\r\n            WorkerCommand::Pulse {\r\n                intensity,\r\n                duration_ms,\r\n            } => {\r\n                // Force a fresh motor edge so each pulse interrupts immediately.\r\n                let _ = send_stop();\r\n                let _ = send_packet_blocking(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0));\r\n                active_until = Some(Instant::now() + Duration::from_millis(duration_ms.max(1)));\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\nfn hid_tx() -> &'static mpsc::Sender<WorkerCommand> {\r\n    HID_TX.get_or_init(|| {\r\n        let (tx, rx) = mpsc::channel::<WorkerCommand>();\r\n        std::thread::spawn(move || run_hid_worker(rx));\r\n        tx\r\n    })\r\n}\r\n\r\nfn queue_worker_command(cmd: WorkerCommand) {\r\n    if let Err(err) = hid_tx().send(cmd) {\r\n        log_haptics(&format!(\"haptics: worker queue send failed: {}\", err));\r\n    }\r\n}\r\n\r\nfn shape_amplitude_for_erm(amplitude: f32) -> f32 {\r\n    if amplitude <= 0.0 {\r\n        return 0.0;\r\n    }\r\n\r\n    let boosted = (amplitude * ERM_AMPLITUDE_GAIN).clamp(0.0, 1.0);\r\n    boosted.max(ERM_MIN_NONZERO_AMPLITUDE)\r\n}\r\n\r\nfn resolve_effective_duration_seconds(duration_seconds: f32, frequency_hz: f32) -> f32 {\r\n    let clamped_duration = duration_seconds.clamp(0.0, OPENVR_MAX_DURATION_SECONDS);\r\n    if clamped_duration > 0.0 {\r\n        return clamped_duration;\r\n    }\r\n\r\n    // OpenVR guidance: when duration is 0, trigger exactly one pulse.\r\n    // One pulse ~= one period at requested frequency; apply ERM minimum floor.\r\n    (1.0 / frequency_hz).max(HARDWARE_MIN_PULSE_SECONDS)\r\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        queue_worker_command(WorkerCommand::Stop);\r\n        return;\r\n    }\r\n\r\n    if req.frequency <= 0.0 {\r\n        queue_worker_command(WorkerCommand::Stop);\r\n        return;\r\n    }\r\n\r\n    let amplitude = shape_amplitude_for_erm(req.amplitude.clamp(0.0, 1.0));\r\n    let frequency_hz = req\r\n        .frequency\r\n        .clamp(OPENVR_MIN_FREQUENCY_HZ, OPENVR_MAX_FREQUENCY_HZ);\r\n\r\n    let effective_duration =\r\n        resolve_effective_duration_seconds(req.duration_seconds, frequency_hz);\r\n    let duration_ms = (effective_duration * 1000.0).round().max(1.0) as u64;\r\n\r\n    queue_worker_command(WorkerCommand::Pulse {\r\n        intensity: amplitude_to_intensity(amplitude),\r\n        duration_ms,\r\n    });\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","epoch":1740,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs::request_44941874-eadc-4ec9-81a4-51cfe2a1ad4e",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_44941874-eadc-4ec9-81a4-51cfe2a1ad4e","content":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse std::sync::{Condvar, Mutex, OnceLock};\r\nuse std::time::Duration;\r\nuse std::time::Instant;\r\nuse std::thread;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\n\r\nenum WorkerCommand {\r\n    Pulse {\r\n        intensity: u8,\r\n        duration_ms: u64,\r\n    },\r\n    Stop,\r\n}\r\n\r\n#[derive(Default)]\r\nstruct WorkerState {\r\n    pending: Option<WorkerCommand>,\r\n    active_until: Option<Instant>,\r\n}\r\n\r\nstruct WorkerMailbox {\r\n    state: Mutex<WorkerState>,\r\n    signal: Condvar,\r\n}\r\n\r\nstatic HID_MAILBOX: OnceLock<WorkerMailbox> = OnceLock::new();\r\n\r\nconst OPENVR_MIN_FREQUENCY_HZ: f32 = 1_000_000.0 / 65_535.0;\r\nconst OPENVR_MAX_FREQUENCY_HZ: f32 = 1_000_000.0 / 300.0;\r\nconst OPENVR_MAX_DURATION_SECONDS: f32 = 10.0;\r\nconst HARDWARE_MIN_PULSE_SECONDS: f32 = 0.05;\r\nconst ERM_MAX_PULSE_SECONDS: f32 = 0.10;\r\nconst ERM_AMPLITUDE_GAIN: f32 = 1.8;\r\nconst ERM_MIN_NONZERO_AMPLITUDE: f32 = 0.25;\r\nconst HID_SEND_ATTEMPTS: usize = 3;\r\nconst HID_SEND_RETRY_DELAY_MS: u64 = 2;\r\n\r\nfn log_haptics(message: &str) {\r\n    crate::openvr_runtime::log_driver_message(message);\r\n}\r\n\r\nfn send_packet_blocking(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    let mut last_error = None;\r\n\r\n    for attempt in 1..=HID_SEND_ATTEMPTS {\r\n        match hid_bridge::HidBridge::new()\r\n            .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n        {\r\n            Ok(()) => return Ok(()),\r\n            Err(err) => {\r\n                last_error = Some(err);\r\n                if attempt < HID_SEND_ATTEMPTS {\r\n                    thread::sleep(Duration::from_millis(HID_SEND_RETRY_DELAY_MS));\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    let err = last_error.expect(\"HID send attempts exhausted without captured error\");\r\n    log_haptics(&format!(\r\n        \"haptics: HID send failed after {} attempt(s); cmd=0x{:02X}, intensity={}, arg_lo={}, arg_hi={}, err={}\",\r\n        HID_SEND_ATTEMPTS,\r\n        packet.command_id,\r\n        packet.intensity,\r\n        packet.arg_lo,\r\n        packet.arg_hi,\r\n        err\r\n    ));\r\n    Err(err)\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet_blocking(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = amplitude_to_intensity(amp);\r\n    send_packet_blocking(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn amplitude_to_intensity(amplitude: f32) -> u8 {\r\n    (amplitude.clamp(0.0, 1.0) * 255.0).round() as u8\r\n}\r\n\r\nfn run_hid_worker(mailbox: &'static WorkerMailbox) {\r\n    loop {\r\n        let cmd = {\r\n            let mut state = match mailbox.state.lock() {\r\n                Ok(s) => s,\r\n                Err(_) => continue,\r\n            };\r\n\r\n            while state.pending.is_none() {\r\n                if let Some(deadline) = state.active_until {\r\n                    let now = Instant::now();\r\n                    if now >= deadline {\r\n                        drop(state);\r\n                        let _ = send_stop();\r\n                        state = match mailbox.state.lock() {\r\n                            Ok(s) => s,\r\n                            Err(_) => continue,\r\n                        };\r\n                        state.active_until = None;\r\n                        continue;\r\n                    }\r\n\r\n                    let wait_dur = deadline.saturating_duration_since(now);\r\n                    let waited = mailbox.signal.wait_timeout(state, wait_dur);\r\n                    let (new_state, timeout_res) = match waited {\r\n                        Ok(r) => r,\r\n                        Err(_) => continue,\r\n                    };\r\n                    state = new_state;\r\n\r\n                    if timeout_res.timed_out() && state.pending.is_none() {\r\n                        drop(state);\r\n                        let _ = send_stop();\r\n                        state = match mailbox.state.lock() {\r\n                            Ok(s) => s,\r\n                            Err(_) => continue,\r\n                        };\r\n                        state.active_until = None;\r\n                    }\r\n                } else {\r\n                    state = match mailbox.signal.wait(state) {\r\n                        Ok(s) => s,\r\n                        Err(_) => continue,\r\n                    };\r\n                }\r\n            }\r\n\r\n            state.pending.take().expect(\"pending command expected\")\r\n        };\r\n\r\n        match cmd {\r\n            WorkerCommand::Stop => {\r\n                let _ = send_stop();\r\n                if let Ok(mut state) = mailbox.state.lock() {\r\n                    state.active_until = None;\r\n                }\r\n            }\r\n            WorkerCommand::Pulse {\r\n                intensity,\r\n                duration_ms,\r\n            } => {\r\n                // Force a fresh motor edge so each new pulse preempts immediately.\r\n                let _ = send_stop();\r\n                let _ = send_packet_blocking(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0));\r\n                if let Ok(mut state) = mailbox.state.lock() {\r\n                    state.active_until = Some(Instant::now() + Duration::from_millis(duration_ms.max(1)));\r\n                }\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\nfn hid_mailbox() -> &'static WorkerMailbox {\r\n    HID_MAILBOX.get_or_init(|| {\r\n        let mailbox = WorkerMailbox {\r\n            state: Mutex::new(WorkerState::default()),\r\n            signal: Condvar::new(),\r\n        };\r\n        std::thread::spawn(|| run_hid_worker(hid_mailbox()));\r\n        mailbox\r\n    })\r\n}\r\n\r\nfn queue_worker_command(cmd: WorkerCommand) {\r\n    let mailbox = hid_mailbox();\r\n    if let Ok(mut state) = mailbox.state.lock() {\r\n        // Latest command wins: overwrite anything pending.\r\n        state.pending = Some(cmd);\r\n        mailbox.signal.notify_one();\r\n    } else {\r\n        log_haptics(\"haptics: worker queue send failed: mailbox lock poisoned\");\r\n    }\r\n}\r\n\r\nfn shape_amplitude_for_erm(amplitude: f32) -> f32 {\r\n    if amplitude <= 0.0 {\r\n        return 0.0;\r\n    }\r\n\r\n    let boosted = (amplitude * ERM_AMPLITUDE_GAIN).clamp(0.0, 1.0);\r\n    boosted.max(ERM_MIN_NONZERO_AMPLITUDE)\r\n}\r\n\r\nfn resolve_effective_duration_seconds(duration_seconds: f32, frequency_hz: f32) -> f32 {\r\n    let clamped_duration = duration_seconds.clamp(0.0, OPENVR_MAX_DURATION_SECONDS);\r\n    if clamped_duration > 0.0 {\r\n        return clamped_duration.min(ERM_MAX_PULSE_SECONDS);\r\n    }\r\n\r\n    // OpenVR guidance: when duration is 0, trigger exactly one pulse.\r\n    // One pulse ~= one period at requested frequency; apply ERM minimum floor.\r\n    (1.0 / frequency_hz)\r\n        .max(HARDWARE_MIN_PULSE_SECONDS)\r\n        .min(ERM_MAX_PULSE_SECONDS)\r\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        queue_worker_command(WorkerCommand::Stop);\r\n        return;\r\n    }\r\n\r\n    if req.frequency <= 0.0 {\r\n        queue_worker_command(WorkerCommand::Stop);\r\n        return;\r\n    }\r\n\r\n    let amplitude = shape_amplitude_for_erm(req.amplitude.clamp(0.0, 1.0));\r\n    let frequency_hz = req\r\n        .frequency\r\n        .clamp(OPENVR_MIN_FREQUENCY_HZ, OPENVR_MAX_FREQUENCY_HZ);\r\n\r\n    let effective_duration =\r\n        resolve_effective_duration_seconds(req.duration_seconds, frequency_hz);\r\n    let duration_ms = (effective_duration * 1000.0).round().max(1.0) as u64;\r\n\r\n    queue_worker_command(WorkerCommand::Pulse {\r\n        intensity: amplitude_to_intensity(amplitude),\r\n        duration_ms,\r\n    });\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","epoch":1748,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs::request_9257a937-1bc9-4027-ad1c-0dd52ffd875f",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_9257a937-1bc9-4027-ad1c-0dd52ffd875f","content":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse std::sync::{Condvar, Mutex, OnceLock};\r\nuse std::time::Duration;\r\nuse std::time::Instant;\r\nuse std::thread;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\n\r\nenum WorkerCommand {\r\n    Pulse {\r\n        intensity: u8,\r\n        duration_ms: u64,\r\n    },\r\n    Stop,\r\n}\r\n\r\n#[derive(Default)]\r\nstruct WorkerState {\r\n    pending: Option<WorkerCommand>,\r\n    active_until: Option<Instant>,\r\n}\r\n\r\nstruct WorkerMailbox {\r\n    state: Mutex<WorkerState>,\r\n    signal: Condvar,\r\n}\r\n\r\nstatic HID_MAILBOX: OnceLock<WorkerMailbox> = OnceLock::new();\r\n\r\nconst OPENVR_MIN_FREQUENCY_HZ: f32 = 1_000_000.0 / 65_535.0;\r\nconst OPENVR_MAX_FREQUENCY_HZ: f32 = 1_000_000.0 / 300.0;\r\nconst OPENVR_MAX_DURATION_SECONDS: f32 = 10.0;\r\nconst HARDWARE_MIN_PULSE_SECONDS: f32 = 0.05;\r\nconst ERM_AMPLITUDE_GAIN: f32 = 1.8;\r\nconst ERM_MIN_NONZERO_AMPLITUDE: f32 = 0.25;\r\nconst HID_SEND_ATTEMPTS: usize = 3;\r\nconst HID_SEND_RETRY_DELAY_MS: u64 = 2;\r\n\r\nfn log_haptics(message: &str) {\r\n    crate::openvr_runtime::log_driver_message(message);\r\n}\r\n\r\nfn send_packet_blocking(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    let mut last_error = None;\r\n\r\n    for attempt in 1..=HID_SEND_ATTEMPTS {\r\n        match hid_bridge::HidBridge::new()\r\n            .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n        {\r\n            Ok(()) => return Ok(()),\r\n            Err(err) => {\r\n                last_error = Some(err);\r\n                if attempt < HID_SEND_ATTEMPTS {\r\n                    thread::sleep(Duration::from_millis(HID_SEND_RETRY_DELAY_MS));\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    let err = last_error.expect(\"HID send attempts exhausted without captured error\");\r\n    log_haptics(&format!(\r\n        \"haptics: HID send failed after {} attempt(s); cmd=0x{:02X}, intensity={}, arg_lo={}, arg_hi={}, err={}\",\r\n        HID_SEND_ATTEMPTS,\r\n        packet.command_id,\r\n        packet.intensity,\r\n        packet.arg_lo,\r\n        packet.arg_hi,\r\n        err\r\n    ));\r\n    Err(err)\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet_blocking(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = amplitude_to_intensity(amp);\r\n    send_packet_blocking(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn amplitude_to_intensity(amplitude: f32) -> u8 {\r\n    (amplitude.clamp(0.0, 1.0) * 255.0).round() as u8\r\n}\r\n\r\nfn run_hid_worker(mailbox: &'static WorkerMailbox) {\r\n    'worker: loop {\r\n        let cmd = {\r\n            let mut state = match mailbox.state.lock() {\r\n                Ok(s) => s,\r\n                Err(_) => continue 'worker,\r\n            };\r\n\r\n            loop {\r\n                if let Some(cmd) = state.pending.take() {\r\n                    break cmd;\r\n                }\r\n\r\n                if let Some(deadline) = state.active_until {\r\n                    let now = Instant::now();\r\n                    if now >= deadline {\r\n                        state.active_until = None;\r\n                        drop(state);\r\n                        let _ = send_stop();\r\n                        state = match mailbox.state.lock() {\r\n                            Ok(s) => s,\r\n                            Err(_) => continue 'worker,\r\n                        };\r\n                        continue;\r\n                    }\r\n\r\n                    let wait_dur = deadline.saturating_duration_since(now);\r\n                    let waited = match mailbox.signal.wait_timeout(state, wait_dur) {\r\n                        Ok(w) => w,\r\n                        Err(_) => continue 'worker,\r\n                    };\r\n\r\n                    let (new_state, timeout_res) = match waited {\r\n                        r => r,\r\n                    };\r\n                    state = new_state;\r\n\r\n                    if timeout_res.timed_out() && state.pending.is_none() {\r\n                        state.active_until = None;\r\n                        drop(state);\r\n                        let _ = send_stop();\r\n                        state = match mailbox.state.lock() {\r\n                            Ok(s) => s,\r\n                            Err(_) => continue 'worker,\r\n                        };\r\n                    }\r\n                } else {\r\n                    state = match mailbox.signal.wait(state) {\r\n                        Ok(s) => s,\r\n                        Err(_) => continue 'worker,\r\n                    };\r\n                }\r\n            }\r\n        };\r\n\r\n        match cmd {\r\n            WorkerCommand::Stop => {\r\n                let _ = send_stop();\r\n                if let Ok(mut state) = mailbox.state.lock() {\r\n                    state.active_until = None;\r\n                }\r\n            }\r\n            WorkerCommand::Pulse {\r\n                intensity,\r\n                duration_ms,\r\n            } => {\r\n                // Force a fresh motor edge so each new pulse preempts immediately.\r\n                let _ = send_stop();\r\n                let _ = send_packet_blocking(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0));\r\n                if let Ok(mut state) = mailbox.state.lock() {\r\n                    state.active_until = Some(Instant::now() + Duration::from_millis(duration_ms.max(1)));\r\n                }\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\nfn hid_mailbox() -> &'static WorkerMailbox {\r\n    HID_MAILBOX.get_or_init(|| {\r\n        let mailbox = WorkerMailbox {\r\n            state: Mutex::new(WorkerState::default()),\r\n            signal: Condvar::new(),\r\n        };\r\n        std::thread::spawn(|| run_hid_worker(hid_mailbox()));\r\n        mailbox\r\n    })\r\n}\r\n\r\nfn queue_worker_command(cmd: WorkerCommand) {\r\n    let mailbox = hid_mailbox();\r\n    if let Ok(mut state) = mailbox.state.lock() {\r\n        // Latest command wins: overwrite anything pending.\r\n        state.pending = Some(cmd);\r\n        mailbox.signal.notify_one();\r\n    } else {\r\n        log_haptics(\"haptics: worker queue send failed: mailbox lock poisoned\");\r\n    }\r\n}\r\n\r\nfn shape_amplitude_for_erm(amplitude: f32) -> f32 {\r\n    if amplitude <= 0.0 {\r\n        return 0.0;\r\n    }\r\n\r\n    let boosted = (amplitude * ERM_AMPLITUDE_GAIN).clamp(0.0, 1.0);\r\n    boosted.max(ERM_MIN_NONZERO_AMPLITUDE)\r\n}\r\n\r\nfn resolve_effective_duration_seconds(duration_seconds: f32, frequency_hz: f32) -> f32 {\r\n    let clamped_duration = duration_seconds.clamp(0.0, OPENVR_MAX_DURATION_SECONDS);\r\n    if clamped_duration > 0.0 {\r\n        return clamped_duration;\r\n    }\r\n\r\n    // OpenVR guidance: when duration is 0, trigger exactly one pulse.\r\n    // One pulse ~= one period at requested frequency; apply ERM minimum floor.\r\n    (1.0 / frequency_hz).max(HARDWARE_MIN_PULSE_SECONDS)\r\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        queue_worker_command(WorkerCommand::Stop);\r\n        return;\r\n    }\r\n\r\n    if req.frequency <= 0.0 {\r\n        queue_worker_command(WorkerCommand::Stop);\r\n        return;\r\n    }\r\n\r\n    let amplitude = shape_amplitude_for_erm(req.amplitude.clamp(0.0, 1.0));\r\n    let frequency_hz = req\r\n        .frequency\r\n        .clamp(OPENVR_MIN_FREQUENCY_HZ, OPENVR_MAX_FREQUENCY_HZ);\r\n\r\n    let effective_duration =\r\n        resolve_effective_duration_seconds(req.duration_seconds, frequency_hz);\r\n    let duration_ms = (effective_duration * 1000.0).round().max(1.0) as u64;\r\n\r\n    queue_worker_command(WorkerCommand::Pulse {\r\n        intensity: amplitude_to_intensity(amplitude),\r\n        duration_ms,\r\n    });\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","epoch":1754,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs::request_cdd53ada-76bc-4a42-a740-f974d081fa95",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_cdd53ada-76bc-4a42-a740-f974d081fa95","content":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse std::sync::{Condvar, Mutex, OnceLock};\r\nuse std::time::Duration;\r\nuse std::time::Instant;\r\nuse std::thread;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\n\r\nenum WorkerCommand {\r\n    Pulse {\r\n        intensity: u8,\r\n        duration_ms: u64,\r\n    },\r\n    Stop,\r\n}\r\n\r\n#[derive(Default)]\r\nstruct WorkerState {\r\n    pending: Option<WorkerCommand>,\r\n    active_until: Option<Instant>,\r\n}\r\n\r\nstruct WorkerMailbox {\r\n    state: Mutex<WorkerState>,\r\n    signal: Condvar,\r\n}\r\n\r\nstatic HID_MAILBOX: OnceLock<WorkerMailbox> = OnceLock::new();\r\n\r\nconst OPENVR_MIN_FREQUENCY_HZ: f32 = 1_000_000.0 / 65_535.0;\r\nconst OPENVR_MAX_FREQUENCY_HZ: f32 = 1_000_000.0 / 300.0;\r\nconst OPENVR_MAX_DURATION_SECONDS: f32 = 10.0;\r\nconst HARDWARE_MIN_PULSE_SECONDS: f32 = 0.05;\r\nconst ERM_AMPLITUDE_GAIN: f32 = 1.8;\r\nconst ERM_MIN_NONZERO_AMPLITUDE: f32 = 0.25;\r\nconst HID_SEND_ATTEMPTS: usize = 3;\r\nconst HID_SEND_RETRY_DELAY_MS: u64 = 2;\r\n\r\nfn log_haptics(message: &str) {\r\n    crate::openvr_runtime::log_driver_message(message);\r\n}\r\n\r\nfn send_packet_blocking(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    let mut last_error = None;\r\n\r\n    for attempt in 1..=HID_SEND_ATTEMPTS {\r\n        match hid_bridge::HidBridge::new()\r\n            .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n        {\r\n            Ok(()) => return Ok(()),\r\n            Err(err) => {\r\n                last_error = Some(err);\r\n                if attempt < HID_SEND_ATTEMPTS {\r\n                    thread::sleep(Duration::from_millis(HID_SEND_RETRY_DELAY_MS));\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    let err = last_error.expect(\"HID send attempts exhausted without captured error\");\r\n    log_haptics(&format!(\r\n        \"haptics: HID send failed after {} attempt(s); cmd=0x{:02X}, intensity={}, arg_lo={}, arg_hi={}, err={}\",\r\n        HID_SEND_ATTEMPTS,\r\n        packet.command_id,\r\n        packet.intensity,\r\n        packet.arg_lo,\r\n        packet.arg_hi,\r\n        err\r\n    ));\r\n    Err(err)\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet_blocking(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = amplitude_to_intensity(amp);\r\n    send_packet_blocking(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn amplitude_to_intensity(amplitude: f32) -> u8 {\r\n    (amplitude.clamp(0.0, 1.0) * 255.0).round() as u8\r\n}\r\n\r\nfn run_hid_worker(mailbox: &'static WorkerMailbox) {\r\n    'worker: loop {\r\n        let cmd = {\r\n            let mut state = match mailbox.state.lock() {\r\n                Ok(s) => s,\r\n                Err(_) => continue 'worker,\r\n            };\r\n\r\n            loop {\r\n                if let Some(cmd) = state.pending.take() {\r\n                    break cmd;\r\n                }\r\n\r\n                if let Some(deadline) = state.active_until {\r\n                    let now = Instant::now();\r\n                    if now >= deadline {\r\n                        state.active_until = None;\r\n                        drop(state);\r\n                        let _ = send_stop();\r\n                        state = match mailbox.state.lock() {\r\n                            Ok(s) => s,\r\n                            Err(_) => continue 'worker,\r\n                        };\r\n                        continue;\r\n                    }\r\n\r\n                    let wait_dur = deadline.saturating_duration_since(now);\r\n                    let waited = match mailbox.signal.wait_timeout(state, wait_dur) {\r\n                        Ok(w) => w,\r\n                        Err(_) => continue 'worker,\r\n                    };\r\n\r\n                    let (new_state, timeout_res) = match waited {\r\n                        r => r,\r\n                    };\r\n                    state = new_state;\r\n\r\n                    if timeout_res.timed_out() && state.pending.is_none() {\r\n                        state.active_until = None;\r\n                        drop(state);\r\n                        let _ = send_stop();\r\n                        state = match mailbox.state.lock() {\r\n                            Ok(s) => s,\r\n                            Err(_) => continue 'worker,\r\n                        };\r\n                    }\r\n                } else {\r\n                    state = match mailbox.signal.wait(state) {\r\n                        Ok(s) => s,\r\n                        Err(_) => continue 'worker,\r\n                    };\r\n                }\r\n            }\r\n        };\r\n\r\n        match cmd {\r\n            WorkerCommand::Stop => {\r\n                let _ = send_stop();\r\n                if let Ok(mut state) = mailbox.state.lock() {\r\n                    state.active_until = None;\r\n                }\r\n            }\r\n            WorkerCommand::Pulse {\r\n                intensity,\r\n                duration_ms,\r\n            } => {\r\n                // Force a fresh motor edge so each new pulse preempts immediately.\r\n                let _ = send_stop();\r\n                let _ = send_packet_blocking(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0));\r\n                if let Ok(mut state) = mailbox.state.lock() {\r\n                    state.active_until = Some(Instant::now() + Duration::from_millis(duration_ms.max(1)));\r\n                }\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\nfn hid_mailbox() -> &'static WorkerMailbox {\r\n    HID_MAILBOX.get_or_init(|| {\r\n        let mailbox = WorkerMailbox {\r\n            state: Mutex::new(WorkerState::default()),\r\n            signal: Condvar::new(),\r\n        };\r\n        std::thread::spawn(|| run_hid_worker(hid_mailbox()));\r\n        mailbox\r\n    })\r\n}\r\n\r\nfn queue_worker_command(cmd: WorkerCommand) {\r\n    let mailbox = hid_mailbox();\r\n    if let Ok(mut state) = mailbox.state.lock() {\r\n        // Latest command wins: overwrite anything pending.\r\n        state.pending = Some(cmd);\r\n        mailbox.signal.notify_one();\r\n    } else {\r\n        log_haptics(\"haptics: worker queue send failed: mailbox lock poisoned\");\r\n    }\r\n}\r\n\r\nfn shape_amplitude_for_erm(amplitude: f32) -> f32 {\r\n    if amplitude <= 0.0 {\r\n        return 0.0;\r\n    }\r\n\r\n    let boosted = (amplitude * ERM_AMPLITUDE_GAIN).clamp(0.0, 1.0);\r\n    boosted.max(ERM_MIN_NONZERO_AMPLITUDE)\r\n}\r\n\r\nfn resolve_effective_duration_seconds(duration_seconds: f32, frequency_hz: f32) -> f32 {\r\n    let clamped_duration = duration_seconds.clamp(0.0, OPENVR_MAX_DURATION_SECONDS);\r\n    if clamped_duration > 0.0 {\r\n        return clamped_duration;\r\n    }\r\n\r\n    // OpenVR guidance: when duration is 0, trigger exactly one pulse.\r\n    // One pulse ~= one period at requested frequency; apply ERM minimum floor.\r\n    (1.0 / frequency_hz).max(HARDWARE_MIN_PULSE_SECONDS)\r\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        queue_worker_command(WorkerCommand::Stop);\r\n        return;\r\n    }\r\n\r\n    if req.frequency <= 0.0 {\r\n        queue_worker_command(WorkerCommand::Stop);\r\n        return;\r\n    }\r\n\r\n    let frequency_hz = req\r\n        .frequency\r\n        .clamp(OPENVR_MIN_FREQUENCY_HZ, OPENVR_MAX_FREQUENCY_HZ);\r\n\r\n    let effective_duration =\r\n        resolve_effective_duration_seconds(req.duration_seconds, frequency_hz);\r\n    let duration_ms = (effective_duration * 1000.0).round().max(1.0) as u64;\r\n\r\n    let amplitude = shape_amplitude_for_erm(req.amplitude.clamp(0.0, 1.0));\r\n\r\n    queue_worker_command(WorkerCommand::Pulse {\r\n        intensity: amplitude_to_intensity(amplitude),\r\n        duration_ms,\r\n    });\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","epoch":1758,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs::request_ae3b7ebe-5e42-40b0-acca-788a8aa0cfec",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-bridge\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","scheme":"file"},"requestId":"request_ae3b7ebe-5e42-40b0-acca-788a8aa0cfec","content":"use haptics_protocol::HapticPacket;\r\nuse thiserror::Error;\r\n\r\n#[derive(Debug, Error)]\r\npub enum BridgeError {\r\n    #[error(\"HID error: {0}\")]\r\n    Hid(#[from] hidapi::HidError),\r\n    #[error(\"short HID write: expected {expected} bytes, wrote {actual}\")]\r\n    ShortWrite { expected: usize, actual: usize },\r\n}\r\n\r\npub struct HidBridge {\r\n    api: hidapi::HidApi,\r\n}\r\n\r\nimpl HidBridge {\r\n    pub fn new() -> Result<Self, BridgeError> {\r\n        let api = hidapi::HidApi::new()?;\r\n        Ok(Self { api })\r\n    }\r\n\r\n    pub fn send_packet(&self, vid: u16, pid: u16, packet: HapticPacket) -> Result<(), BridgeError> {\r\n        let device = self.api.open(vid, pid)?;\r\n\r\n        // hidapi expects report ID in byte 0 for `write`.\r\n        // For single-report devices, report ID is usually 0.\r\n        let mut report = [0u8; 5];\r\n        report[1..].copy_from_slice(&packet.to_bytes());\r\n\r\n        let written = device.write(&report)?;\r\n        if written != report.len() {\r\n            return Err(BridgeError::ShortWrite {\r\n                expected: report.len(),\r\n                actual: written,\r\n            });\r\n        }\r\n\r\n        Ok(())\r\n    }\r\n}\r\n","epoch":1770,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs::request_ae3b7ebe-5e42-40b0-acca-788a8aa0cfec",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_ae3b7ebe-5e42-40b0-acca-788a8aa0cfec","content":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse std::sync::{Condvar, Mutex, OnceLock};\r\nuse std::time::Duration;\r\nuse std::thread;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\n\r\nenum WorkerCommand {\r\n    PulseTrain {\r\n        intensity: u8,\r\n        pulse_on_ms: u64,\r\n        pulse_off_ms: u64,\r\n        pulse_count: u32,\r\n    },\r\n    Stop,\r\n}\r\n\r\n#[derive(Default)]\r\nstruct WorkerState {\r\n    pending: Option<WorkerCommand>,\r\n}\r\n\r\nstruct WorkerMailbox {\r\n    state: Mutex<WorkerState>,\r\n    signal: Condvar,\r\n}\r\n\r\nstatic HID_MAILBOX: OnceLock<WorkerMailbox> = OnceLock::new();\r\n\r\nconst OPENVR_MIN_FREQUENCY_HZ: f32 = 1_000_000.0 / 65_535.0;\r\nconst OPENVR_MAX_FREQUENCY_HZ: f32 = 1_000_000.0 / 300.0;\r\nconst OPENVR_MAX_DURATION_SECONDS: f32 = 10.0;\r\nconst HARDWARE_MIN_PULSE_SECONDS: f32 = 0.05;\r\nconst MAX_SINGLE_PULSE_SECONDS: f32 = 0.10;\r\nconst ERM_AMPLITUDE_GAIN: f32 = 1.8;\r\nconst ERM_MIN_NONZERO_AMPLITUDE: f32 = 0.25;\r\nconst HID_SEND_ATTEMPTS: usize = 3;\r\nconst HID_SEND_RETRY_DELAY_MS: u64 = 2;\r\n\r\nfn log_haptics(message: &str) {\r\n    crate::openvr_runtime::log_driver_message(message);\r\n}\r\n\r\nfn send_packet_blocking(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    let mut last_error = None;\r\n\r\n    for attempt in 1..=HID_SEND_ATTEMPTS {\r\n        match hid_bridge::HidBridge::new()\r\n            .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n        {\r\n            Ok(()) => return Ok(()),\r\n            Err(err) => {\r\n                last_error = Some(err);\r\n                if attempt < HID_SEND_ATTEMPTS {\r\n                    thread::sleep(Duration::from_millis(HID_SEND_RETRY_DELAY_MS));\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    let err = last_error.expect(\"HID send attempts exhausted without captured error\");\r\n    log_haptics(&format!(\r\n        \"haptics: HID send failed after {} attempt(s); cmd=0x{:02X}, intensity={}, arg_lo={}, arg_hi={}, err={}\",\r\n        HID_SEND_ATTEMPTS,\r\n        packet.command_id,\r\n        packet.intensity,\r\n        packet.arg_lo,\r\n        packet.arg_hi,\r\n        err\r\n    ));\r\n    Err(err)\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet_blocking(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = amplitude_to_intensity(amp);\r\n    send_packet_blocking(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn amplitude_to_intensity(amplitude: f32) -> u8 {\r\n    (amplitude.clamp(0.0, 1.0) * 255.0).round() as u8\r\n}\r\n\r\nfn wait_for_preempt(mailbox: &'static WorkerMailbox, timeout: Duration) -> Option<WorkerCommand> {\r\n    let mut state = mailbox.state.lock().ok()?;\r\n    if let Some(cmd) = state.pending.take() {\r\n        return Some(cmd);\r\n    }\r\n\r\n    let (mut state, _timeout_res) = mailbox.signal.wait_timeout(state, timeout).ok()?;\r\n    state.pending.take()\r\n}\r\n\r\nfn wait_for_next_command(mailbox: &'static WorkerMailbox) -> Option<WorkerCommand> {\r\n    let mut state = mailbox.state.lock().ok()?;\r\n    loop {\r\n        if let Some(cmd) = state.pending.take() {\r\n            return Some(cmd);\r\n        }\r\n\r\n        state = mailbox.signal.wait(state).ok()?;\r\n    }\r\n}\r\n\r\nfn run_hid_worker(mailbox: &'static WorkerMailbox) {\r\n    let mut cmd = match wait_for_next_command(mailbox) {\r\n        Some(c) => c,\r\n        None => return,\r\n    };\r\n\r\n    loop {\r\n        match cmd {\r\n            WorkerCommand::Stop => {\r\n                let _ = send_stop();\r\n                cmd = match wait_for_next_command(mailbox) {\r\n                    Some(c) => c,\r\n                    None => return,\r\n                };\r\n            }\r\n            WorkerCommand::PulseTrain {\r\n                intensity,\r\n                pulse_on_ms,\r\n                pulse_off_ms,\r\n                pulse_count,\r\n            } => {\r\n                let mut preempted = None;\r\n\r\n                for index in 0..pulse_count {\r\n                    let _ = send_stop();\r\n                    let _ = send_packet_blocking(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0));\r\n\r\n                    if let Some(next_cmd) = wait_for_preempt(mailbox, Duration::from_millis(pulse_on_ms.max(1))) {\r\n                        preempted = Some(next_cmd);\r\n                        break;\r\n                    }\r\n\r\n                    let _ = send_stop();\r\n\r\n                    if index + 1 < pulse_count && pulse_off_ms > 0 {\r\n                        if let Some(next_cmd) = wait_for_preempt(mailbox, Duration::from_millis(pulse_off_ms)) {\r\n                            preempted = Some(next_cmd);\r\n                            break;\r\n                        }\r\n                    }\r\n                }\r\n\r\n                cmd = if let Some(next_cmd) = preempted {\r\n                    next_cmd\r\n                } else {\r\n                    match wait_for_next_command(mailbox) {\r\n                        Some(c) => c,\r\n                        None => return,\r\n                    }\r\n                };\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\nfn hid_mailbox() -> &'static WorkerMailbox {\r\n    HID_MAILBOX.get_or_init(|| {\r\n        let mailbox = WorkerMailbox {\r\n            state: Mutex::new(WorkerState::default()),\r\n            signal: Condvar::new(),\r\n        };\r\n        std::thread::spawn(|| run_hid_worker(hid_mailbox()));\r\n        mailbox\r\n    })\r\n}\r\n\r\nfn queue_worker_command(cmd: WorkerCommand) {\r\n    let mailbox = hid_mailbox();\r\n    if let Ok(mut state) = mailbox.state.lock() {\r\n        // Latest command wins: overwrite anything pending.\r\n        state.pending = Some(cmd);\r\n        mailbox.signal.notify_one();\r\n    } else {\r\n        log_haptics(\"haptics: worker queue send failed: mailbox lock poisoned\");\r\n    }\r\n}\r\n\r\nfn shape_amplitude_for_erm(amplitude: f32) -> f32 {\r\n    if amplitude <= 0.0 {\r\n        return 0.0;\r\n    }\r\n\r\n    let boosted = (amplitude * ERM_AMPLITUDE_GAIN).clamp(0.0, 1.0);\r\n    boosted.max(ERM_MIN_NONZERO_AMPLITUDE)\r\n}\r\n\r\nfn resolve_effective_duration_seconds(duration_seconds: f32, frequency_hz: f32) -> f32 {\r\n    let clamped_duration = duration_seconds.clamp(0.0, OPENVR_MAX_DURATION_SECONDS);\r\n    if clamped_duration > 0.0 {\r\n        return clamped_duration;\r\n    }\r\n\r\n    // OpenVR guidance: when duration is 0, trigger exactly one pulse.\r\n    // One pulse ~= one period at requested frequency; apply ERM minimum floor.\r\n    (1.0 / frequency_hz).max(HARDWARE_MIN_PULSE_SECONDS)\r\n}\r\n\r\nfn resolve_pulse_train_parameters(\r\n    amplitude_for_timing: f32,\r\n    effective_duration_seconds: f32,\r\n    frequency_hz: f32,\r\n) -> (u64, u64, u32) {\r\n    let period_s = 1.0 / frequency_hz;\r\n    let max_pulse_s = (0.5 * period_s).min(MAX_SINGLE_PULSE_SECONDS);\r\n    let min_pulse_s = HARDWARE_MIN_PULSE_SECONDS.min(max_pulse_s);\r\n\r\n    let pulse_on_s = min_pulse_s + amplitude_for_timing * (max_pulse_s - min_pulse_s);\r\n    let pulse_off_s = (period_s - pulse_on_s).max(0.0);\r\n\r\n    let pulse_count = if effective_duration_seconds <= 0.0 {\r\n        1\r\n    } else {\r\n        (effective_duration_seconds * frequency_hz).round().max(1.0) as u32\r\n    };\r\n\r\n    let pulse_on_ms = (pulse_on_s * 1000.0).round().max(1.0) as u64;\r\n    let pulse_off_ms = (pulse_off_s * 1000.0).round().max(0.0) as u64;\r\n\r\n    (pulse_on_ms, pulse_off_ms, pulse_count)\r\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        queue_worker_command(WorkerCommand::Stop);\r\n        return;\r\n    }\r\n\r\n    if req.frequency <= 0.0 {\r\n        queue_worker_command(WorkerCommand::Stop);\r\n        return;\r\n    }\r\n\r\n    let amplitude_for_timing = req.amplitude.clamp(0.0, 1.0);\r\n    let frequency_hz = req\r\n        .frequency\r\n        .clamp(OPENVR_MIN_FREQUENCY_HZ, OPENVR_MAX_FREQUENCY_HZ);\r\n\r\n    let effective_duration =\r\n        resolve_effective_duration_seconds(req.duration_seconds, frequency_hz);\r\n    let (pulse_on_ms, pulse_off_ms, pulse_count) = resolve_pulse_train_parameters(\r\n        amplitude_for_timing,\r\n        effective_duration,\r\n        frequency_hz,\r\n    );\r\n\r\n    let amplitude = shape_amplitude_for_erm(amplitude_for_timing);\r\n\r\n    queue_worker_command(WorkerCommand::PulseTrain {\r\n        intensity: amplitude_to_intensity(amplitude),\r\n        pulse_on_ms,\r\n        pulse_off_ms,\r\n        pulse_count,\r\n    });\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","epoch":1773,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs::request_ae3b7ebe-5e42-40b0-acca-788a8aa0cfec",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_ae3b7ebe-5e42-40b0-acca-788a8aa0cfec","content":"use embedded_hal::i2c::I2c;\r\n\r\nuse crate::drv2605l_parser::Drv2605lCommand;\r\n\r\npub const DRV2605L_I2C_ADDR: u8 = 0x5a;\r\n\r\nconst REG_MODE: u8 = 0x01;\r\nconst REG_RTP_INPUT: u8 = 0x02;\r\nconst REG_LIBRARY_SELECTION: u8 = 0x03;\r\nconst REG_WAVEFORM_SEQ1: u8 = 0x04;\r\nconst REG_WAVEFORM_SEQ2: u8 = 0x05;\r\nconst REG_GO: u8 = 0x0c;\r\n\r\nconst MODE_INTERNAL_TRIGGER: u8 = 0x00;\r\nconst MODE_REALTIME_PLAYBACK: u8 = 0x05;\r\n\r\n#[derive(Clone, Copy, Debug, Eq, PartialEq)]\r\npub enum DispatchError<E> {\r\n    Parse(crate::drv2605l_parser::ParseError),\r\n    I2c(E),\r\n}\r\n\r\npub struct Drv2605l<I2C> {\r\n    i2c: I2C,\r\n    address: u8,\r\n}\r\n\r\nimpl<I2C> Drv2605l<I2C>\r\nwhere\r\n    I2C: I2c,\r\n{\r\n    pub fn new(i2c: I2C) -> Self {\r\n        Self {\r\n            i2c,\r\n            address: DRV2605L_I2C_ADDR,\r\n        }\r\n    }\r\n\r\n    pub fn with_address(i2c: I2C, address: u8) -> Self {\r\n        Self { i2c, address }\r\n    }\r\n\r\n    pub fn release(self) -> I2C {\r\n        self.i2c\r\n    }\r\n\r\n    pub fn write_register(&mut self, register: u8, value: u8) -> Result<(), I2C::Error> {\r\n        self.i2c.write(self.address, &[register, value])\r\n    }\r\n\r\n    pub fn set_rtp_mode(&mut self) -> Result<(), I2C::Error> {\r\n        self.write_register(REG_MODE, MODE_REALTIME_PLAYBACK)\r\n    }\r\n\r\n    pub fn set_rtp_input(&mut self, intensity: u8) -> Result<(), I2C::Error> {\r\n        self.write_register(REG_RTP_INPUT, intensity)\r\n    }\r\n\r\n    pub fn trigger_rom_effect(&mut self, effect_id: u8) -> Result<(), I2C::Error> {\r\n        self.write_register(REG_MODE, MODE_INTERNAL_TRIGGER)?;\r\n        self.write_register(REG_LIBRARY_SELECTION, 1)?;\r\n        self.write_register(REG_WAVEFORM_SEQ1, effect_id)?;\r\n        self.write_register(REG_WAVEFORM_SEQ2, 0)?;\r\n        self.write_register(REG_GO, 1)\r\n    }\r\n\r\n    pub fn stop(&mut self) -> Result<(), I2C::Error> {\r\n        self.write_register(REG_GO, 0)?;\r\n        self.set_rtp_mode()?;\r\n        self.set_rtp_input(0)\r\n    }\r\n\r\n    pub fn apply_command(&mut self, cmd: Drv2605lCommand) -> Result<(), I2C::Error> {\r\n        match cmd {\r\n            Drv2605lCommand::SetIntensity { intensity } => {\r\n                self.set_rtp_mode()?;\r\n                self.set_rtp_input(intensity)\r\n            }\r\n            Drv2605lCommand::TriggerRomEffect { effect_id, intensity: _ } => {\r\n                self.trigger_rom_effect(effect_id)\r\n            }\r\n            Drv2605lCommand::Stop => self.stop(),\r\n            Drv2605lCommand::Unknown { .. } => Ok(()),\r\n        }\r\n    }\r\n}\r\n","epoch":1778,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs::request_61a7ab1c-d8bf-428d-b25c-57a9185ebc48",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_61a7ab1c-d8bf-428d-b25c-57a9185ebc48","content":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse std::sync::{Condvar, Mutex, OnceLock};\r\nuse std::time::Duration;\r\nuse std::thread;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\nstatic HID_BRIDGE: OnceLock<Mutex<Option<hid_bridge::HidBridge>>> = OnceLock::new();\r\n\r\nenum WorkerCommand {\r\n    PulseTrain {\r\n        intensity: u8,\r\n        pulse_on_ms: u64,\r\n        pulse_off_ms: u64,\r\n        pulse_count: u32,\r\n    },\r\n    Stop,\r\n}\r\n\r\n#[derive(Default)]\r\nstruct WorkerState {\r\n    pending: Option<WorkerCommand>,\r\n}\r\n\r\nstruct WorkerMailbox {\r\n    state: Mutex<WorkerState>,\r\n    signal: Condvar,\r\n}\r\n\r\nstatic HID_MAILBOX: OnceLock<WorkerMailbox> = OnceLock::new();\r\n\r\nconst OPENVR_MIN_FREQUENCY_HZ: f32 = 1_000_000.0 / 65_535.0;\r\nconst OPENVR_MAX_FREQUENCY_HZ: f32 = 1_000_000.0 / 300.0;\r\nconst OPENVR_MAX_DURATION_SECONDS: f32 = 10.0;\r\nconst HARDWARE_MIN_PULSE_SECONDS: f32 = 0.05;\r\nconst MAX_SINGLE_PULSE_SECONDS: f32 = 0.10;\r\nconst ERM_AMPLITUDE_GAIN: f32 = 1.8;\r\nconst ERM_MIN_NONZERO_AMPLITUDE: f32 = 0.25;\r\nconst HID_SEND_ATTEMPTS: usize = 3;\r\nconst HID_SEND_RETRY_DELAY_MS: u64 = 2;\r\n\r\nfn log_haptics(message: &str) {\r\n    crate::openvr_runtime::log_driver_message(message);\r\n}\r\n\r\nfn with_hid_bridge<F>(f: F) -> Result<(), hid_bridge::BridgeError>\r\nwhere\r\n    F: FnOnce(&hid_bridge::HidBridge) -> Result<(), hid_bridge::BridgeError>,\r\n{\r\n    let bridge_slot = HID_BRIDGE.get_or_init(|| Mutex::new(None));\r\n    let mut guard = match bridge_slot.lock() {\r\n        Ok(g) => g,\r\n        Err(_) => return hid_bridge::HidBridge::new().and_then(|bridge| f(&bridge)),\r\n    };\r\n\r\n    if guard.is_none() {\r\n        *guard = Some(hid_bridge::HidBridge::new()?);\r\n    }\r\n\r\n    let bridge = guard.as_ref().expect(\"HID bridge should be initialized\");\r\n    f(bridge)\r\n}\r\n\r\nfn send_packet_blocking(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    let mut last_error = None;\r\n\r\n    for attempt in 1..=HID_SEND_ATTEMPTS {\r\n        match with_hid_bridge(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet)) {\r\n            Ok(()) => return Ok(()),\r\n            Err(err) => {\r\n                last_error = Some(err);\r\n                if attempt < HID_SEND_ATTEMPTS {\r\n                    thread::sleep(Duration::from_millis(HID_SEND_RETRY_DELAY_MS));\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    let err = last_error.expect(\"HID send attempts exhausted without captured error\");\r\n    log_haptics(&format!(\r\n        \"haptics: HID send failed after {} attempt(s); cmd=0x{:02X}, intensity={}, arg_lo={}, arg_hi={}, err={}\",\r\n        HID_SEND_ATTEMPTS,\r\n        packet.command_id,\r\n        packet.intensity,\r\n        packet.arg_lo,\r\n        packet.arg_hi,\r\n        err\r\n    ));\r\n    Err(err)\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet_blocking(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = amplitude_to_intensity(amp);\r\n    send_packet_blocking(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn amplitude_to_intensity(amplitude: f32) -> u8 {\r\n    (amplitude.clamp(0.0, 1.0) * 255.0).round() as u8\r\n}\r\n\r\nfn wait_for_preempt(mailbox: &'static WorkerMailbox, timeout: Duration) -> Option<WorkerCommand> {\r\n    let mut state = mailbox.state.lock().ok()?;\r\n    if let Some(cmd) = state.pending.take() {\r\n        return Some(cmd);\r\n    }\r\n\r\n    let (mut state, _timeout_res) = mailbox.signal.wait_timeout(state, timeout).ok()?;\r\n    state.pending.take()\r\n}\r\n\r\nfn wait_for_next_command(mailbox: &'static WorkerMailbox) -> Option<WorkerCommand> {\r\n    let mut state = mailbox.state.lock().ok()?;\r\n    loop {\r\n        if let Some(cmd) = state.pending.take() {\r\n            return Some(cmd);\r\n        }\r\n\r\n        state = mailbox.signal.wait(state).ok()?;\r\n    }\r\n}\r\n\r\nfn run_hid_worker(mailbox: &'static WorkerMailbox) {\r\n    let mut cmd = match wait_for_next_command(mailbox) {\r\n        Some(c) => c,\r\n        None => return,\r\n    };\r\n\r\n    loop {\r\n        match cmd {\r\n            WorkerCommand::Stop => {\r\n                let _ = send_stop();\r\n                cmd = match wait_for_next_command(mailbox) {\r\n                    Some(c) => c,\r\n                    None => return,\r\n                };\r\n            }\r\n            WorkerCommand::PulseTrain {\r\n                intensity,\r\n                pulse_on_ms,\r\n                pulse_off_ms,\r\n                pulse_count,\r\n            } => {\r\n                let mut preempted = None;\r\n\r\n                for index in 0..pulse_count {\r\n                    let _ = send_stop();\r\n                    let _ = send_packet_blocking(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0));\r\n\r\n                    if let Some(next_cmd) = wait_for_preempt(mailbox, Duration::from_millis(pulse_on_ms.max(1))) {\r\n                        preempted = Some(next_cmd);\r\n                        break;\r\n                    }\r\n\r\n                    let _ = send_stop();\r\n\r\n                    if index + 1 < pulse_count && pulse_off_ms > 0 {\r\n                        if let Some(next_cmd) = wait_for_preempt(mailbox, Duration::from_millis(pulse_off_ms)) {\r\n                            preempted = Some(next_cmd);\r\n                            break;\r\n                        }\r\n                    }\r\n                }\r\n\r\n                cmd = if let Some(next_cmd) = preempted {\r\n                    next_cmd\r\n                } else {\r\n                    match wait_for_next_command(mailbox) {\r\n                        Some(c) => c,\r\n                        None => return,\r\n                    }\r\n                };\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\nfn hid_mailbox() -> &'static WorkerMailbox {\r\n    HID_MAILBOX.get_or_init(|| {\r\n        let mailbox = WorkerMailbox {\r\n            state: Mutex::new(WorkerState::default()),\r\n            signal: Condvar::new(),\r\n        };\r\n        std::thread::spawn(|| run_hid_worker(hid_mailbox()));\r\n        mailbox\r\n    })\r\n}\r\n\r\nfn queue_worker_command(cmd: WorkerCommand) {\r\n    let mailbox = hid_mailbox();\r\n    if let Ok(mut state) = mailbox.state.lock() {\r\n        // Latest command wins: overwrite anything pending.\r\n        state.pending = Some(cmd);\r\n        mailbox.signal.notify_one();\r\n    } else {\r\n        log_haptics(\"haptics: worker queue send failed: mailbox lock poisoned\");\r\n    }\r\n}\r\n\r\nfn shape_amplitude_for_erm(amplitude: f32) -> f32 {\r\n    if amplitude <= 0.0 {\r\n        return 0.0;\r\n    }\r\n\r\n    let boosted = (amplitude * ERM_AMPLITUDE_GAIN).clamp(0.0, 1.0);\r\n    boosted.max(ERM_MIN_NONZERO_AMPLITUDE)\r\n}\r\n\r\nfn resolve_effective_duration_seconds(duration_seconds: f32, frequency_hz: f32) -> f32 {\r\n    let clamped_duration = duration_seconds.clamp(0.0, OPENVR_MAX_DURATION_SECONDS);\r\n    if clamped_duration > 0.0 {\r\n        return clamped_duration;\r\n    }\r\n\r\n    // OpenVR guidance: when duration is 0, trigger exactly one pulse.\r\n    // One pulse ~= one period at requested frequency; apply ERM minimum floor.\r\n    (1.0 / frequency_hz).max(HARDWARE_MIN_PULSE_SECONDS)\r\n}\r\n\r\nfn resolve_pulse_train_parameters(\r\n    amplitude_for_timing: f32,\r\n    effective_duration_seconds: f32,\r\n    frequency_hz: f32,\r\n) -> (u64, u64, u32) {\r\n    let period_s = 1.0 / frequency_hz;\r\n    let max_pulse_s = (0.5 * period_s).min(MAX_SINGLE_PULSE_SECONDS);\r\n    let min_pulse_s = HARDWARE_MIN_PULSE_SECONDS.min(max_pulse_s);\r\n\r\n    let pulse_on_s = min_pulse_s + amplitude_for_timing * (max_pulse_s - min_pulse_s);\r\n    let pulse_off_s = (period_s - pulse_on_s).max(0.0);\r\n\r\n    let pulse_count = if effective_duration_seconds <= 0.0 {\r\n        1\r\n    } else {\r\n        (effective_duration_seconds * frequency_hz).round().max(1.0) as u32\r\n    };\r\n\r\n    let pulse_on_ms = (pulse_on_s * 1000.0).round().max(1.0) as u64;\r\n    let pulse_off_ms = (pulse_off_s * 1000.0).round().max(0.0) as u64;\r\n\r\n    (pulse_on_ms, pulse_off_ms, pulse_count)\r\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        queue_worker_command(WorkerCommand::Stop);\r\n        return;\r\n    }\r\n\r\n    if req.frequency <= 0.0 {\r\n        queue_worker_command(WorkerCommand::Stop);\r\n        return;\r\n    }\r\n\r\n    let amplitude_for_timing = req.amplitude.clamp(0.0, 1.0);\r\n    let frequency_hz = req\r\n        .frequency\r\n        .clamp(OPENVR_MIN_FREQUENCY_HZ, OPENVR_MAX_FREQUENCY_HZ);\r\n\r\n    let effective_duration =\r\n        resolve_effective_duration_seconds(req.duration_seconds, frequency_hz);\r\n    let (pulse_on_ms, pulse_off_ms, pulse_count) = resolve_pulse_train_parameters(\r\n        amplitude_for_timing,\r\n        effective_duration,\r\n        frequency_hz,\r\n    );\r\n\r\n    let amplitude = shape_amplitude_for_erm(amplitude_for_timing);\r\n\r\n    queue_worker_command(WorkerCommand::PulseTrain {\r\n        intensity: amplitude_to_intensity(amplitude),\r\n        pulse_on_ms,\r\n        pulse_off_ms,\r\n        pulse_count,\r\n    });\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","epoch":1782,"telemetryInfo":{}}],["file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/host_hid_smoke_test.ps1::request_303b5792-8ec1-4d5c-ab9a-948563589794",{"uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\host_hid_smoke_test.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/host_hid_smoke_test.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/host_hid_smoke_test.ps1","scheme":"file"},"requestId":"request_303b5792-8ec1-4d5c-ab9a-948563589794","content":"param(\r\n    [UInt16]$Vid = 0x35bd,\r\n    [Alias('Pid')]\r\n    [UInt16]$ProductId = 0xfe01,\r\n    [byte]$Command = 0x01,\r\n    [byte]$Intensity = 64,\r\n    [int]$AutoStopMs = 120,\r\n    [switch]$NoAutoStop,\r\n    [switch]$SkipPreflight\r\n)\r\n\r\n$ErrorActionPreference = \"Stop\"\r\n$root = \"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\"\r\nSet-Location $root\r\n\r\nfunction Test-HidPresent {\r\n    param(\r\n        [UInt16]$CheckVid,\r\n        [UInt16]$CheckPid\r\n    )\r\n\r\n    $vidHex = ('{0:X4}' -f $CheckVid)\r\n    $pidHex = ('{0:X4}' -f $CheckPid)\r\n    $pattern = \"VID_$vidHex&PID_$pidHex\"\r\n\r\n    $matches = Get-CimInstance -ClassName Win32_PnPEntity -ErrorAction SilentlyContinue |\r\n        Where-Object { $_.DeviceID -like \"*$pattern*\" }\r\n\r\n    return ($null -ne $matches -and $matches.Count -gt 0)\r\n}\r\n\r\nif (-not $SkipPreflight) {\r\n    if (-not (Test-HidPresent -CheckVid $Vid -CheckPid $ProductId)) {\r\n        throw \"HID device not detected for VID=0x$('{0:X4}' -f $Vid), PID=0x$('{0:X4}' -f $ProductId). Check cable, firmware, and device enumeration.\"\r\n    }\r\n}\r\n\r\ncargo run -p hid-cli -- --vid $Vid --pid $ProductId --cmd $Command --intensity $Intensity\r\n\r\n# Safety: for `SET_INTENSITY` smoke tests, send STOP shortly after start\r\n# unless explicitly disabled.\r\nif (-not $NoAutoStop -and $Command -eq 0x01 -and $Intensity -gt 0) {\r\n    Start-Sleep -Milliseconds $AutoStopMs\r\n    cargo run -p hid-cli -- --vid $Vid --pid $ProductId --cmd 3 --intensity 0\r\n}\r\n","epoch":1989,"telemetryInfo":{}}]],"operations":[{"type":"create","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":2,"initialContent":"","telemetryInfo":{}},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":3,"edits":[{"text":"# qt-py-haptics: Directory Structure Outline","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":1,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":4,"edits":[{"text":"\n","range":{"startLineNumber":2,"startColumn":1,"endLineNumber":2,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":5,"edits":[{"text":"\nThis repository is organized around four major layers from the implementation plan:","range":{"startLineNumber":3,"startColumn":1,"endLineNumber":3,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":6,"edits":[{"text":"\n1) Embedded firmware, 2) Host communication, 3) OpenVR driver, 4) Integration/deployment.","range":{"startLineNumber":4,"startColumn":1,"endLineNumber":4,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":7,"edits":[{"text":"\n","range":{"startLineNumber":5,"startColumn":1,"endLineNumber":5,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":8,"edits":[{"text":"\n## Proposed Hierarchy","range":{"startLineNumber":6,"startColumn":1,"endLineNumber":6,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":9,"edits":[{"text":"\n","range":{"startLineNumber":7,"startColumn":1,"endLineNumber":7,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":10,"edits":[{"text":"\n```text","range":{"startLineNumber":8,"startColumn":1,"endLineNumber":8,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":11,"edits":[{"text":"\nqt-py-haptics/","range":{"startLineNumber":9,"startColumn":1,"endLineNumber":9,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":12,"edits":[{"text":"\n├─ .github/","range":{"startLineNumber":10,"startColumn":1,"endLineNumber":10,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":13,"edits":[{"text":"\n│  └─ workflows/","range":{"startLineNumber":11,"startColumn":1,"endLineNumber":11,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":14,"edits":[{"text":"\n├─ build/","range":{"startLineNumber":12,"startColumn":1,"endLineNumber":12,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":15,"edits":[{"text":"\n├─ docs/","range":{"startLineNumber":13,"startColumn":1,"endLineNumber":13,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":16,"edits":[{"text":"\n│  ├─ Haptic System Implementation Plan.md","range":{"startLineNumber":14,"startColumn":1,"endLineNumber":14,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":17,"edits":[{"text":"\n│  └─ Research TXT.txt","range":{"startLineNumber":15,"startColumn":1,"endLineNumber":15,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":18,"edits":[{"text":"\n├─ firmware/","range":{"startLineNumber":16,"startColumn":1,"endLineNumber":16,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":19,"edits":[{"text":"\n│  └─ qtpy-samd21/","range":{"startLineNumber":17,"startColumn":1,"endLineNumber":17,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":20,"edits":[{"text":"\n│     ├─ .cargo/","range":{"startLineNumber":18,"startColumn":1,"endLineNumber":18,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":21,"edits":[{"text":"\n│     ├─ src/","range":{"startLineNumber":19,"startColumn":1,"endLineNumber":19,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":22,"edits":[{"text":"\n│     ├─ examples/","range":{"startLineNumber":20,"startColumn":1,"endLineNumber":20,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":23,"edits":[{"text":"\n│     └─ tests/","range":{"startLineNumber":21,"startColumn":1,"endLineNumber":21,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":24,"edits":[{"text":"\n├─ hardware/","range":{"startLineNumber":22,"startColumn":1,"endLineNumber":22,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":25,"edits":[{"text":"\n│  ├─ boards/","range":{"startLineNumber":23,"startColumn":1,"endLineNumber":23,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":26,"edits":[{"text":"\n│  │  └─ qtpy-samd21/","range":{"startLineNumber":24,"startColumn":1,"endLineNumber":24,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":27,"edits":[{"text":"\n│  ├─ drivers/","range":{"startLineNumber":25,"startColumn":1,"endLineNumber":25,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":28,"edits":[{"text":"\n│  │  └─ drv2605l/","range":{"startLineNumber":26,"startColumn":1,"endLineNumber":26,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":29,"edits":[{"text":"\n│  └─ wiring/","range":{"startLineNumber":27,"startColumn":1,"endLineNumber":27,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":30,"edits":[{"text":"\n├─ host/","range":{"startLineNumber":28,"startColumn":1,"endLineNumber":28,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":31,"edits":[{"text":"\n│  ├─ hid-cli/","range":{"startLineNumber":29,"startColumn":1,"endLineNumber":29,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":32,"edits":[{"text":"\n│  │  └─ src/","range":{"startLineNumber":30,"startColumn":1,"endLineNumber":30,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":33,"edits":[{"text":"\n│  └─ hid-bridge/","range":{"startLineNumber":31,"startColumn":1,"endLineNumber":31,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":34,"edits":[{"text":"\n│     └─ src/","range":{"startLineNumber":32,"startColumn":1,"endLineNumber":32,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":35,"edits":[{"text":"\n├─ openvr-driver/","range":{"startLineNumber":33,"startColumn":1,"endLineNumber":33,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":36,"edits":[{"text":"\n│  └─ rust-haptic-driver/","range":{"startLineNumber":34,"startColumn":1,"endLineNumber":34,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":37,"edits":[{"text":"\n│     ├─ bin/","range":{"startLineNumber":35,"startColumn":1,"endLineNumber":35,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":38,"edits":[{"text":"\n│     │  └─ win64/","range":{"startLineNumber":36,"startColumn":1,"endLineNumber":36,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":39,"edits":[{"text":"\n│     ├─ resources/","range":{"startLineNumber":37,"startColumn":1,"endLineNumber":37,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":40,"edits":[{"text":"\n│     │  ├─ input/","range":{"startLineNumber":38,"startColumn":1,"endLineNumber":38,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":41,"edits":[{"text":"\n│     │  └─ localization/","range":{"startLineNumber":39,"startColumn":1,"endLineNumber":39,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":42,"edits":[{"text":"\n│     └─ src/","range":{"startLineNumber":40,"startColumn":1,"endLineNumber":40,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":43,"edits":[{"text":"\n├─ shared/","range":{"startLineNumber":41,"startColumn":1,"endLineNumber":41,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":44,"edits":[{"text":"\n│  └─ haptics-protocol/","range":{"startLineNumber":42,"startColumn":1,"endLineNumber":42,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":45,"edits":[{"text":"\n│     └─ src/","range":{"startLineNumber":43,"startColumn":1,"endLineNumber":43,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":46,"edits":[{"text":"\n├─ tests/","range":{"startLineNumber":44,"startColumn":1,"endLineNumber":44,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":47,"edits":[{"text":"\n│  ├─ integration/","range":{"startLineNumber":45,"startColumn":1,"endLineNumber":45,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":48,"edits":[{"text":"\n│  └─ latency/","range":{"startLineNumber":46,"startColumn":1,"endLineNumber":46,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":49,"edits":[{"text":"\n└─ tools/","range":{"startLineNumber":47,"startColumn":1,"endLineNumber":47,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":50,"edits":[{"text":"\n   ├─ scripts/","range":{"startLineNumber":48,"startColumn":1,"endLineNumber":48,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":51,"edits":[{"text":"\n   └─ steamvr/","range":{"startLineNumber":49,"startColumn":1,"endLineNumber":49,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":52,"edits":[{"text":"\n```","range":{"startLineNumber":50,"startColumn":1,"endLineNumber":50,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":53,"edits":[{"text":"\n","range":{"startLineNumber":51,"startColumn":1,"endLineNumber":51,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":54,"edits":[{"text":"\n## Folder Purposes","range":{"startLineNumber":52,"startColumn":1,"endLineNumber":52,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":55,"edits":[{"text":"\n","range":{"startLineNumber":53,"startColumn":1,"endLineNumber":53,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":56,"edits":[{"text":"\n### .github/workflows","range":{"startLineNumber":54,"startColumn":1,"endLineNumber":54,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":57,"edits":[{"text":"\nCI pipelines (firmware checks, host/unit tests, formatting/linting, release packaging).","range":{"startLineNumber":55,"startColumn":1,"endLineNumber":55,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":58,"edits":[{"text":"\n","range":{"startLineNumber":56,"startColumn":1,"endLineNumber":56,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":59,"edits":[{"text":"\n### build","range":{"startLineNumber":57,"startColumn":1,"endLineNumber":57,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":60,"edits":[{"text":"\nGenerated artifacts, temporary outputs, and local packaging/staging files.","range":{"startLineNumber":58,"startColumn":1,"endLineNumber":58,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":61,"edits":[{"text":"\n","range":{"startLineNumber":59,"startColumn":1,"endLineNumber":59,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":62,"edits":[{"text":"\n### docs","range":{"startLineNumber":60,"startColumn":1,"endLineNumber":60,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":63,"edits":[{"text":"\nArchitecture docs, protocol notes, calibration procedures, and implementation plans.","range":{"startLineNumber":61,"startColumn":1,"endLineNumber":61,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":64,"edits":[{"text":"\n","range":{"startLineNumber":62,"startColumn":1,"endLineNumber":62,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":65,"edits":[{"text":"\n### firmware/qtpy-samd21","range":{"startLineNumber":63,"startColumn":1,"endLineNumber":63,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":66,"edits":[{"text":"\nRust `no_std` firmware for ATSAMD21 (USB HID + I2C control of DRV2605L).","range":{"startLineNumber":64,"startColumn":1,"endLineNumber":64,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":67,"edits":[{"text":"\n- `.cargo/`: target config (`thumbv6m-none-eabi`), linker settings.","range":{"startLineNumber":65,"startColumn":1,"endLineNumber":65,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":68,"edits":[{"text":"\n- `src/`: main firmware modules (USB task, I2C task, command parser).","range":{"startLineNumber":66,"startColumn":1,"endLineNumber":66,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":69,"edits":[{"text":"\n- `examples/`: bring-up tools (I2C scan, DRV2605L sanity checks).","range":{"startLineNumber":67,"startColumn":1,"endLineNumber":67,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":70,"edits":[{"text":"\n- `tests/`: embedded-focused test harness patterns and host-driven firmware tests.","range":{"startLineNumber":68,"startColumn":1,"endLineNumber":68,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":71,"edits":[{"text":"\n","range":{"startLineNumber":69,"startColumn":1,"endLineNumber":69,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":72,"edits":[{"text":"\n### hardware","range":{"startLineNumber":70,"startColumn":1,"endLineNumber":70,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":73,"edits":[{"text":"\nHardware-specific assets.","range":{"startLineNumber":71,"startColumn":1,"endLineNumber":71,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":74,"edits":[{"text":"\n- `boards/qtpy-samd21/`: pin maps, power notes, bootloader/flash instructions.","range":{"startLineNumber":72,"startColumn":1,"endLineNumber":72,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":75,"edits":[{"text":"\n- `drivers/drv2605l/`: register map references, effect tuning tables.","range":{"startLineNumber":73,"startColumn":1,"endLineNumber":73,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":76,"edits":[{"text":"\n- `wiring/`: connection diagrams (QT Py ↔ DRV2605L ↔ actuator).","range":{"startLineNumber":74,"startColumn":1,"endLineNumber":74,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":77,"edits":[{"text":"\n","range":{"startLineNumber":75,"startColumn":1,"endLineNumber":75,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":78,"edits":[{"text":"\n### host","range":{"startLineNumber":76,"startColumn":1,"endLineNumber":76,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":79,"edits":[{"text":"\nHost-side utilities and communication libraries.","range":{"startLineNumber":77,"startColumn":1,"endLineNumber":77,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":80,"edits":[{"text":"\n- `hid-cli/`: command-line utility to send and inspect HID packets.","range":{"startLineNumber":78,"startColumn":1,"endLineNumber":78,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":81,"edits":[{"text":"\n- `hid-bridge/`: reusable Rust library for HID discovery, packet tx/rx, retries.","range":{"startLineNumber":79,"startColumn":1,"endLineNumber":79,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":82,"edits":[{"text":"\n","range":{"startLineNumber":80,"startColumn":1,"endLineNumber":80,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":83,"edits":[{"text":"\n### openvr-driver/rust-haptic-driver","range":{"startLineNumber":81,"startColumn":1,"endLineNumber":81,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":84,"edits":[{"text":"\nSteamVR/OpenVR driver DLL project in Rust.","range":{"startLineNumber":82,"startColumn":1,"endLineNumber":82,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":85,"edits":[{"text":"\n- `src/`: provider/device interface implementation and event handling.","range":{"startLineNumber":83,"startColumn":1,"endLineNumber":83,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":86,"edits":[{"text":"\n- `resources/input/`: input profile JSON for haptic component bindings.","range":{"startLineNumber":84,"startColumn":1,"endLineNumber":84,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":87,"edits":[{"text":"\n- `resources/localization/`: localized strings used by SteamVR UI.","range":{"startLineNumber":85,"startColumn":1,"endLineNumber":85,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":88,"edits":[{"text":"\n- `bin/win64/`: built DLL placement and deploy-ready runtime layout.","range":{"startLineNumber":86,"startColumn":1,"endLineNumber":86,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":89,"edits":[{"text":"\n","range":{"startLineNumber":87,"startColumn":1,"endLineNumber":87,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":90,"edits":[{"text":"\n### shared/haptics-protocol","range":{"startLineNumber":88,"startColumn":1,"endLineNumber":88,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":91,"edits":[{"text":"\nSingle source of truth for the HID packet schema, command IDs, and encoding helpers used by firmware + host + driver.","range":{"startLineNumber":89,"startColumn":1,"endLineNumber":89,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":92,"edits":[{"text":"\n","range":{"startLineNumber":90,"startColumn":1,"endLineNumber":90,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":93,"edits":[{"text":"\n### tests","range":{"startLineNumber":91,"startColumn":1,"endLineNumber":91,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":94,"edits":[{"text":"\nCross-layer validation.","range":{"startLineNumber":92,"startColumn":1,"endLineNumber":92,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":95,"edits":[{"text":"\n- `integration/`: end-to-end tests (OpenVR event → HID → firmware behavior).","range":{"startLineNumber":93,"startColumn":1,"endLineNumber":93,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":96,"edits":[{"text":"\n- `latency/`: timing and jitter benchmarks for haptic response.","range":{"startLineNumber":94,"startColumn":1,"endLineNumber":94,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":97,"edits":[{"text":"\n","range":{"startLineNumber":95,"startColumn":1,"endLineNumber":95,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":98,"edits":[{"text":"\n### tools","range":{"startLineNumber":96,"startColumn":1,"endLineNumber":96,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":99,"edits":[{"text":"\nOperational scripts and deployment helpers.","range":{"startLineNumber":97,"startColumn":1,"endLineNumber":97,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":100,"edits":[{"text":"\n- `scripts/`: utility scripts (format, flash, smoke test, packaging).","range":{"startLineNumber":98,"startColumn":1,"endLineNumber":98,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":101,"edits":[{"text":"\n- `steamvr/`: driver registration templates and helper assets for `vrpathreg`.","range":{"startLineNumber":99,"startColumn":1,"endLineNumber":99,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":102,"edits":[{"text":"\n","range":{"startLineNumber":100,"startColumn":1,"endLineNumber":100,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":103,"edits":[{"text":"\n## Notes","range":{"startLineNumber":101,"startColumn":1,"endLineNumber":101,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":104,"edits":[{"text":"\n","range":{"startLineNumber":102,"startColumn":1,"endLineNumber":102,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":105,"edits":[{"text":"\n- Keep protocol definitions centralized in `shared/haptics-protocol` to avoid drift.","range":{"startLineNumber":103,"startColumn":1,"endLineNumber":103,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":106,"edits":[{"text":"\n- Keep deployment-facing assets (`manifest`, input profile, localization) under `openvr-driver/rust-haptic-driver/resources`.","range":{"startLineNumber":104,"startColumn":1,"endLineNumber":104,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":107,"edits":[{"text":"\n- Place generated binaries and temporary build output in `build/` and avoid committing transient files.","range":{"startLineNumber":105,"startColumn":1,"endLineNumber":105,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_706ce3a9-9340-4022-af72-dfadd5a6c579","epoch":108,"edits":[{"text":"\n","range":{"startLineNumber":106,"startColumn":1,"endLineNumber":106,"endColumn":1}}]},{"type":"create","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":110,"initialContent":"","telemetryInfo":{}},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":111,"edits":[{"text":"[workspace]","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":1,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":112,"edits":[{"text":"\nresolver = \"2\"","range":{"startLineNumber":2,"startColumn":1,"endLineNumber":2,"endColumn":1}}]},{"type":"create","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":113,"initialContent":"","telemetryInfo":{}},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":114,"edits":[{"text":"[package]","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":1,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":115,"edits":[{"text":"\nmembers = [","range":{"startLineNumber":3,"startColumn":1,"endLineNumber":3,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":116,"edits":[{"text":"\nname = \"haptics-protocol\"","range":{"startLineNumber":2,"startColumn":1,"endLineNumber":2,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":117,"edits":[{"text":"\n  \"shared/haptics-protocol\",","range":{"startLineNumber":4,"startColumn":1,"endLineNumber":4,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":118,"edits":[{"text":"\nversion = \"0.1.0\"","range":{"startLineNumber":3,"startColumn":1,"endLineNumber":3,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":119,"edits":[{"text":"\n  \"host/hid-bridge\",","range":{"startLineNumber":5,"startColumn":1,"endLineNumber":5,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":120,"edits":[{"text":"\nedition.workspace = true","range":{"startLineNumber":4,"startColumn":1,"endLineNumber":4,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":121,"edits":[{"text":"\n  \"host/hid-cli\",","range":{"startLineNumber":6,"startColumn":1,"endLineNumber":6,"endColumn":1}}]},{"type":"create","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":122,"initialContent":"","telemetryInfo":{}},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":123,"edits":[{"text":"#![cfg_attr(not(feature = \"std\"), no_std)]","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":1,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":124,"edits":[{"text":"\nlicense.workspace = true","range":{"startLineNumber":5,"startColumn":1,"endLineNumber":5,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":125,"edits":[{"text":"\n  \"openvr-driver/rust-haptic-driver\",","range":{"startLineNumber":7,"startColumn":1,"endLineNumber":7,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":126,"edits":[{"text":"\n","range":{"startLineNumber":2,"startColumn":1,"endLineNumber":2,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":127,"edits":[{"text":"\nauthors.workspace = true","range":{"startLineNumber":6,"startColumn":1,"endLineNumber":6,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":128,"edits":[{"text":"\n  \"firmware/qtpy-samd21\",","range":{"startLineNumber":8,"startColumn":1,"endLineNumber":8,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":129,"edits":[{"text":"\npub const HID_REPORT_LEN: usize = 4;","range":{"startLineNumber":3,"startColumn":1,"endLineNumber":3,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":130,"edits":[{"text":"\n","range":{"startLineNumber":7,"startColumn":1,"endLineNumber":7,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":131,"edits":[{"text":"\n]","range":{"startLineNumber":9,"startColumn":1,"endLineNumber":9,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":132,"edits":[{"text":"\n","range":{"startLineNumber":4,"startColumn":1,"endLineNumber":4,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":133,"edits":[{"text":"\n[features]","range":{"startLineNumber":8,"startColumn":1,"endLineNumber":8,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":134,"edits":[{"text":"\ndefault-members = [","range":{"startLineNumber":10,"startColumn":1,"endLineNumber":10,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":135,"edits":[{"text":"\npub mod command {","range":{"startLineNumber":5,"startColumn":1,"endLineNumber":5,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":136,"edits":[{"text":"\ndefault = [\"std\"]","range":{"startLineNumber":9,"startColumn":1,"endLineNumber":9,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":137,"edits":[{"text":"\n  \"shared/haptics-protocol\",","range":{"startLineNumber":11,"startColumn":1,"endLineNumber":11,"endColumn":1}}]},{"type":"create","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-bridge\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":138,"initialContent":"","telemetryInfo":{}},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":139,"edits":[{"text":"\n    pub const SET_INTENSITY: u8 = 0x01;","range":{"startLineNumber":6,"startColumn":1,"endLineNumber":6,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-bridge\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":140,"edits":[{"text":"[package]","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":1,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":141,"edits":[{"text":"\nstd = []","range":{"startLineNumber":10,"startColumn":1,"endLineNumber":10,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":142,"edits":[{"text":"\n  \"host/hid-bridge\",","range":{"startLineNumber":12,"startColumn":1,"endLineNumber":12,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":143,"edits":[{"text":"\n    pub const TRIGGER_ROM_EFFECT: u8 = 0x02;","range":{"startLineNumber":7,"startColumn":1,"endLineNumber":7,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-bridge\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":144,"edits":[{"text":"\nname = \"hid-bridge\"","range":{"startLineNumber":2,"startColumn":1,"endLineNumber":2,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":145,"edits":[{"text":"\n","range":{"startLineNumber":11,"startColumn":1,"endLineNumber":11,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":146,"edits":[{"text":"\n  \"host/hid-cli\",","range":{"startLineNumber":13,"startColumn":1,"endLineNumber":13,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":147,"edits":[{"text":"\n    pub const STOP: u8 = 0x03;","range":{"startLineNumber":8,"startColumn":1,"endLineNumber":8,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-bridge\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":148,"edits":[{"text":"\nversion = \"0.1.0\"","range":{"startLineNumber":3,"startColumn":1,"endLineNumber":3,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":149,"edits":[{"text":"\n[dependencies]","range":{"startLineNumber":12,"startColumn":1,"endLineNumber":12,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":150,"edits":[{"text":"\n  \"openvr-driver/rust-haptic-driver\",","range":{"startLineNumber":14,"startColumn":1,"endLineNumber":14,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":151,"edits":[{"text":"\n}","range":{"startLineNumber":9,"startColumn":1,"endLineNumber":9,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-bridge\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":152,"edits":[{"text":"\nedition.workspace = true","range":{"startLineNumber":4,"startColumn":1,"endLineNumber":4,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":153,"edits":[{"text":"\n","range":{"startLineNumber":13,"startColumn":1,"endLineNumber":13,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":154,"edits":[{"text":"\n]","range":{"startLineNumber":15,"startColumn":1,"endLineNumber":15,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":155,"edits":[{"text":"\n","range":{"startLineNumber":10,"startColumn":1,"endLineNumber":10,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-bridge\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":156,"edits":[{"text":"\nlicense.workspace = true","range":{"startLineNumber":5,"startColumn":1,"endLineNumber":5,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":157,"edits":[{"text":"\n","range":{"startLineNumber":16,"startColumn":1,"endLineNumber":16,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":158,"edits":[{"text":"\n#[derive(Clone, Copy, Debug, Eq, PartialEq)]","range":{"startLineNumber":11,"startColumn":1,"endLineNumber":11,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-bridge\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":159,"edits":[{"text":"\nauthors.workspace = true","range":{"startLineNumber":6,"startColumn":1,"endLineNumber":6,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":160,"edits":[{"text":"\n[workspace.package]","range":{"startLineNumber":17,"startColumn":1,"endLineNumber":17,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":161,"edits":[{"text":"\npub struct HapticPacket {","range":{"startLineNumber":12,"startColumn":1,"endLineNumber":12,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-bridge\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":162,"edits":[{"text":"\n","range":{"startLineNumber":7,"startColumn":1,"endLineNumber":7,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":163,"edits":[{"text":"\nedition = \"2021\"","range":{"startLineNumber":18,"startColumn":1,"endLineNumber":18,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":164,"edits":[{"text":"\n    pub command_id: u8,","range":{"startLineNumber":13,"startColumn":1,"endLineNumber":13,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-bridge\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":165,"edits":[{"text":"\n[dependencies]","range":{"startLineNumber":8,"startColumn":1,"endLineNumber":8,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":166,"edits":[{"text":"\nauthors = [\"qt-py-haptics contributors\"]","range":{"startLineNumber":19,"startColumn":1,"endLineNumber":19,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":167,"edits":[{"text":"\n    pub intensity: u8,","range":{"startLineNumber":14,"startColumn":1,"endLineNumber":14,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-bridge\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":168,"edits":[{"text":"\nhaptics-protocol = { path = \"../../shared/haptics-protocol\" }","range":{"startLineNumber":9,"startColumn":1,"endLineNumber":9,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":169,"edits":[{"text":"\nlicense = \"MIT OR Apache-2.0\"","range":{"startLineNumber":20,"startColumn":1,"endLineNumber":20,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":170,"edits":[{"text":"\n    pub arg_lo: u8,","range":{"startLineNumber":15,"startColumn":1,"endLineNumber":15,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-bridge\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":171,"edits":[{"text":"\nhidapi.workspace = true","range":{"startLineNumber":10,"startColumn":1,"endLineNumber":10,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":172,"edits":[{"text":"\n","range":{"startLineNumber":21,"startColumn":1,"endLineNumber":21,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":173,"edits":[{"text":"\n    pub arg_hi: u8,","range":{"startLineNumber":16,"startColumn":1,"endLineNumber":16,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-bridge\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":174,"edits":[{"text":"\nthiserror.workspace = true","range":{"startLineNumber":11,"startColumn":1,"endLineNumber":11,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":175,"edits":[{"text":"\n[workspace.dependencies]","range":{"startLineNumber":22,"startColumn":1,"endLineNumber":22,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":176,"edits":[{"text":"\n}","range":{"startLineNumber":17,"startColumn":1,"endLineNumber":17,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-bridge\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":177,"edits":[{"text":"\n","range":{"startLineNumber":12,"startColumn":1,"endLineNumber":12,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":178,"edits":[{"text":"\nanyhow = \"1\"","range":{"startLineNumber":23,"startColumn":1,"endLineNumber":23,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":179,"edits":[{"text":"\n","range":{"startLineNumber":18,"startColumn":1,"endLineNumber":18,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":180,"edits":[{"text":"\nclap = { version = \"4\", features = [\"derive\"] }","range":{"startLineNumber":24,"startColumn":1,"endLineNumber":24,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":181,"edits":[{"text":"\nimpl HapticPacket {","range":{"startLineNumber":19,"startColumn":1,"endLineNumber":19,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":182,"edits":[{"text":"\nhidapi = \"2\"","range":{"startLineNumber":25,"startColumn":1,"endLineNumber":25,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":183,"edits":[{"text":"\n    pub const fn new(command_id: u8, intensity: u8, arg_lo: u8, arg_hi: u8) -> Self {","range":{"startLineNumber":20,"startColumn":1,"endLineNumber":20,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":184,"edits":[{"text":"\nlog = \"0.4\"","range":{"startLineNumber":26,"startColumn":1,"endLineNumber":26,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":185,"edits":[{"text":"\n        Self {","range":{"startLineNumber":21,"startColumn":1,"endLineNumber":21,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":186,"edits":[{"text":"\nthiserror = \"2\"","range":{"startLineNumber":27,"startColumn":1,"endLineNumber":27,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":187,"edits":[{"text":"\n            command_id,","range":{"startLineNumber":22,"startColumn":1,"endLineNumber":22,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":188,"edits":[{"text":"\n","range":{"startLineNumber":28,"startColumn":1,"endLineNumber":28,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":189,"edits":[{"text":"\n            intensity,","range":{"startLineNumber":23,"startColumn":1,"endLineNumber":23,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":190,"edits":[{"text":"\n            arg_lo,","range":{"startLineNumber":24,"startColumn":1,"endLineNumber":24,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":191,"edits":[{"text":"\n            arg_hi,","range":{"startLineNumber":25,"startColumn":1,"endLineNumber":25,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":192,"edits":[{"text":"\n        }","range":{"startLineNumber":26,"startColumn":1,"endLineNumber":26,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":193,"edits":[{"text":"\n    }","range":{"startLineNumber":27,"startColumn":1,"endLineNumber":27,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":194,"edits":[{"text":"\n","range":{"startLineNumber":28,"startColumn":1,"endLineNumber":28,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":195,"edits":[{"text":"\n    pub const fn to_bytes(self) -> [u8; HID_REPORT_LEN] {","range":{"startLineNumber":29,"startColumn":1,"endLineNumber":29,"endColumn":1}}]},{"type":"create","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-bridge\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":196,"initialContent":"","telemetryInfo":{}},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-bridge\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":197,"edits":[{"text":"use haptics_protocol::HapticPacket;","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":1,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":198,"edits":[{"text":"\n        [self.command_id, self.intensity, self.arg_lo, self.arg_hi]","range":{"startLineNumber":30,"startColumn":1,"endLineNumber":30,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-bridge\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":199,"edits":[{"text":"\nuse thiserror::Error;","range":{"startLineNumber":2,"startColumn":1,"endLineNumber":2,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":200,"edits":[{"text":"\n    }","range":{"startLineNumber":31,"startColumn":1,"endLineNumber":31,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-bridge\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":201,"edits":[{"text":"\n","range":{"startLineNumber":3,"startColumn":1,"endLineNumber":3,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":202,"edits":[{"text":"\n","range":{"startLineNumber":32,"startColumn":1,"endLineNumber":32,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-bridge\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":203,"edits":[{"text":"\n#[derive(Debug, Error)]","range":{"startLineNumber":4,"startColumn":1,"endLineNumber":4,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":204,"edits":[{"text":"\n    pub const fn from_bytes(bytes: [u8; HID_REPORT_LEN]) -> Self {","range":{"startLineNumber":33,"startColumn":1,"endLineNumber":33,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-bridge\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":205,"edits":[{"text":"\npub enum BridgeError {","range":{"startLineNumber":5,"startColumn":1,"endLineNumber":5,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":206,"edits":[{"text":"\n        Self {","range":{"startLineNumber":34,"startColumn":1,"endLineNumber":34,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-bridge\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":207,"edits":[{"text":"\n    #[error(\"HID error: {0}\")]","range":{"startLineNumber":6,"startColumn":1,"endLineNumber":6,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":208,"edits":[{"text":"\n            command_id: bytes[0],","range":{"startLineNumber":35,"startColumn":1,"endLineNumber":35,"endColumn":1}}]},{"type":"create","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-cli\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":209,"initialContent":"","telemetryInfo":{}},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-cli\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":210,"edits":[{"text":"[package]","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":1,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-bridge\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":211,"edits":[{"text":"\n    Hid(#[from] hidapi::HidError),","range":{"startLineNumber":7,"startColumn":1,"endLineNumber":7,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":212,"edits":[{"text":"\n            intensity: bytes[1],","range":{"startLineNumber":36,"startColumn":1,"endLineNumber":36,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-cli\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":213,"edits":[{"text":"\nname = \"hid-cli\"","range":{"startLineNumber":2,"startColumn":1,"endLineNumber":2,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-bridge\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":214,"edits":[{"text":"\n}","range":{"startLineNumber":8,"startColumn":1,"endLineNumber":8,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":215,"edits":[{"text":"\n            arg_lo: bytes[2],","range":{"startLineNumber":37,"startColumn":1,"endLineNumber":37,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-cli\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":216,"edits":[{"text":"\nversion = \"0.1.0\"","range":{"startLineNumber":3,"startColumn":1,"endLineNumber":3,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-bridge\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":217,"edits":[{"text":"\n","range":{"startLineNumber":9,"startColumn":1,"endLineNumber":9,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":218,"edits":[{"text":"\n            arg_hi: bytes[3],","range":{"startLineNumber":38,"startColumn":1,"endLineNumber":38,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-cli\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":219,"edits":[{"text":"\nedition.workspace = true","range":{"startLineNumber":4,"startColumn":1,"endLineNumber":4,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-bridge\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":220,"edits":[{"text":"\npub struct HidBridge {","range":{"startLineNumber":10,"startColumn":1,"endLineNumber":10,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":221,"edits":[{"text":"\n        }","range":{"startLineNumber":39,"startColumn":1,"endLineNumber":39,"endColumn":1}}]},{"type":"create","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-cli\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":222,"initialContent":"","telemetryInfo":{}},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-cli\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":223,"edits":[{"text":"\nlicense.workspace = true","range":{"startLineNumber":5,"startColumn":1,"endLineNumber":5,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-bridge\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":224,"edits":[{"text":"\n    _api: hidapi::HidApi,","range":{"startLineNumber":11,"startColumn":1,"endLineNumber":11,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":225,"edits":[{"text":"\n    }","range":{"startLineNumber":40,"startColumn":1,"endLineNumber":40,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-cli\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":226,"edits":[{"text":"use anyhow::Result;","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":1,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-cli\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":227,"edits":[{"text":"\nauthors.workspace = true","range":{"startLineNumber":6,"startColumn":1,"endLineNumber":6,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-bridge\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":228,"edits":[{"text":"\n}","range":{"startLineNumber":12,"startColumn":1,"endLineNumber":12,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":229,"edits":[{"text":"\n}","range":{"startLineNumber":41,"startColumn":1,"endLineNumber":41,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-cli\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":230,"edits":[{"text":"\nuse clap::Parser;","range":{"startLineNumber":2,"startColumn":1,"endLineNumber":2,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-cli\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":231,"edits":[{"text":"\n","range":{"startLineNumber":7,"startColumn":1,"endLineNumber":7,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-bridge\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":232,"edits":[{"text":"\n","range":{"startLineNumber":13,"startColumn":1,"endLineNumber":13,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":233,"edits":[{"text":"\n","range":{"startLineNumber":42,"startColumn":1,"endLineNumber":42,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-cli\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":234,"edits":[{"text":"\nuse haptics_protocol::HapticPacket;","range":{"startLineNumber":3,"startColumn":1,"endLineNumber":3,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-cli\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":235,"edits":[{"text":"\n[dependencies]","range":{"startLineNumber":8,"startColumn":1,"endLineNumber":8,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-bridge\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":236,"edits":[{"text":"\nimpl HidBridge {","range":{"startLineNumber":14,"startColumn":1,"endLineNumber":14,"endColumn":1}}]},{"type":"create","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":237,"initialContent":"","telemetryInfo":{}},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-cli\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":238,"edits":[{"text":"\n","range":{"startLineNumber":4,"startColumn":1,"endLineNumber":4,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-cli\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":239,"edits":[{"text":"\nanyhow.workspace = true","range":{"startLineNumber":9,"startColumn":1,"endLineNumber":9,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":240,"edits":[{"text":"[package]","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":1,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-bridge\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":241,"edits":[{"text":"\n    pub fn new() -> Result<Self, BridgeError> {","range":{"startLineNumber":15,"startColumn":1,"endLineNumber":15,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-cli\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":242,"edits":[{"text":"\n#[derive(Debug, Parser)]","range":{"startLineNumber":5,"startColumn":1,"endLineNumber":5,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-cli\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":243,"edits":[{"text":"\nclap.workspace = true","range":{"startLineNumber":10,"startColumn":1,"endLineNumber":10,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":244,"edits":[{"text":"\nname = \"rust-haptic-driver\"","range":{"startLineNumber":2,"startColumn":1,"endLineNumber":2,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-bridge\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":245,"edits":[{"text":"\n        let api = hidapi::HidApi::new()?;","range":{"startLineNumber":16,"startColumn":1,"endLineNumber":16,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-cli\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":246,"edits":[{"text":"\n#[command(name = \"hid-cli\")]","range":{"startLineNumber":6,"startColumn":1,"endLineNumber":6,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-cli\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":247,"edits":[{"text":"\nhid-bridge = { path = \"../hid-bridge\" }","range":{"startLineNumber":11,"startColumn":1,"endLineNumber":11,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":248,"edits":[{"text":"\nversion = \"0.1.0\"","range":{"startLineNumber":3,"startColumn":1,"endLineNumber":3,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-bridge\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":249,"edits":[{"text":"\n        Ok(Self { _api: api })","range":{"startLineNumber":17,"startColumn":1,"endLineNumber":17,"endColumn":1}}]},{"type":"create","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":250,"initialContent":"","telemetryInfo":{}},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":251,"edits":[{"text":"use core::ffi::c_void;","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":1,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-cli\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":252,"edits":[{"text":"\n#[command(about = \"Send starter haptic packets over HID\")]","range":{"startLineNumber":7,"startColumn":1,"endLineNumber":7,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-cli\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":253,"edits":[{"text":"\nhaptics-protocol = { path = \"../../shared/haptics-protocol\" }","range":{"startLineNumber":12,"startColumn":1,"endLineNumber":12,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":254,"edits":[{"text":"\nedition.workspace = true","range":{"startLineNumber":4,"startColumn":1,"endLineNumber":4,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-bridge\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":255,"edits":[{"text":"\n    }","range":{"startLineNumber":18,"startColumn":1,"endLineNumber":18,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":256,"edits":[{"text":"\n","range":{"startLineNumber":2,"startColumn":1,"endLineNumber":2,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-cli\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":257,"edits":[{"text":"\nstruct Cli {","range":{"startLineNumber":8,"startColumn":1,"endLineNumber":8,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-cli\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":258,"edits":[{"text":"\n","range":{"startLineNumber":13,"startColumn":1,"endLineNumber":13,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":259,"edits":[{"text":"\nlicense.workspace = true","range":{"startLineNumber":5,"startColumn":1,"endLineNumber":5,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-bridge\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":260,"edits":[{"text":"\n","range":{"startLineNumber":19,"startColumn":1,"endLineNumber":19,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":261,"edits":[{"text":"\n#[no_mangle]","range":{"startLineNumber":3,"startColumn":1,"endLineNumber":3,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-cli\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":262,"edits":[{"text":"\n    #[arg(long, default_value_t = 0x239A)]","range":{"startLineNumber":9,"startColumn":1,"endLineNumber":9,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":263,"edits":[{"text":"\nauthors.workspace = true","range":{"startLineNumber":6,"startColumn":1,"endLineNumber":6,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-bridge\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":264,"edits":[{"text":"\n    pub fn send_packet(&self, _vid: u16, _pid: u16, _packet: HapticPacket) -> Result<(), BridgeError> {","range":{"startLineNumber":20,"startColumn":1,"endLineNumber":20,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":265,"edits":[{"text":"\npub extern \"C\" fn HmdDriverFactory(","range":{"startLineNumber":4,"startColumn":1,"endLineNumber":4,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-cli\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":266,"edits":[{"text":"\n    vid: u16,","range":{"startLineNumber":10,"startColumn":1,"endLineNumber":10,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":267,"edits":[{"text":"\n","range":{"startLineNumber":7,"startColumn":1,"endLineNumber":7,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-bridge\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":268,"edits":[{"text":"\n        // Starter stub: open by VID/PID and write raw report bytes.","range":{"startLineNumber":21,"startColumn":1,"endLineNumber":21,"endColumn":1}}]},{"type":"create","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":269,"initialContent":"","telemetryInfo":{}},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":270,"edits":[{"text":"[package]","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":1,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":271,"edits":[{"text":"\n    _p_interface_name: *const i8,","range":{"startLineNumber":5,"startColumn":1,"endLineNumber":5,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-cli\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":272,"edits":[{"text":"\n    #[arg(long, default_value_t = 0x80F0)]","range":{"startLineNumber":11,"startColumn":1,"endLineNumber":11,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":273,"edits":[{"text":"\n[lib]","range":{"startLineNumber":8,"startColumn":1,"endLineNumber":8,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-bridge\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":274,"edits":[{"text":"\n        Ok(())","range":{"startLineNumber":22,"startColumn":1,"endLineNumber":22,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":275,"edits":[{"text":"\nname = \"qtpy-samd21-fw\"","range":{"startLineNumber":2,"startColumn":1,"endLineNumber":2,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":276,"edits":[{"text":"\n    _p_return_code: *mut i32,","range":{"startLineNumber":6,"startColumn":1,"endLineNumber":6,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-cli\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":277,"edits":[{"text":"\n    pid: u16,","range":{"startLineNumber":12,"startColumn":1,"endLineNumber":12,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":278,"edits":[{"text":"\ncrate-type = [\"cdylib\"]","range":{"startLineNumber":9,"startColumn":1,"endLineNumber":9,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-bridge\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":279,"edits":[{"text":"\n    }","range":{"startLineNumber":23,"startColumn":1,"endLineNumber":23,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":280,"edits":[{"text":"\nversion = \"0.1.0\"","range":{"startLineNumber":3,"startColumn":1,"endLineNumber":3,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":281,"edits":[{"text":"\n) -> *mut c_void {","range":{"startLineNumber":7,"startColumn":1,"endLineNumber":7,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-cli\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":282,"edits":[{"text":"\n    #[arg(long, default_value_t = 0x01)]","range":{"startLineNumber":13,"startColumn":1,"endLineNumber":13,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":283,"edits":[{"text":"\n","range":{"startLineNumber":10,"startColumn":1,"endLineNumber":10,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-bridge\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":284,"edits":[{"text":"\n}","range":{"startLineNumber":24,"startColumn":1,"endLineNumber":24,"endColumn":1}}]},{"type":"create","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":285,"initialContent":"","telemetryInfo":{}},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":286,"edits":[{"text":"\nedition.workspace = true","range":{"startLineNumber":4,"startColumn":1,"endLineNumber":4,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":287,"edits":[{"text":"\n    core::ptr::null_mut()","range":{"startLineNumber":8,"startColumn":1,"endLineNumber":8,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-cli\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":288,"edits":[{"text":"\n    cmd: u8,","range":{"startLineNumber":14,"startColumn":1,"endLineNumber":14,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":289,"edits":[{"text":"#![no_std]","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":1,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":290,"edits":[{"text":"\n[dependencies]","range":{"startLineNumber":11,"startColumn":1,"endLineNumber":11,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-bridge\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":291,"edits":[{"text":"\n","range":{"startLineNumber":25,"startColumn":1,"endLineNumber":25,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":292,"edits":[{"text":"\nlicense.workspace = true","range":{"startLineNumber":5,"startColumn":1,"endLineNumber":5,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":293,"edits":[{"text":"\n}","range":{"startLineNumber":9,"startColumn":1,"endLineNumber":9,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-cli\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":294,"edits":[{"text":"\n    #[arg(long, default_value_t = 0)]","range":{"startLineNumber":15,"startColumn":1,"endLineNumber":15,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":295,"edits":[{"text":"\n#![no_main]","range":{"startLineNumber":2,"startColumn":1,"endLineNumber":2,"endColumn":1}}]},{"type":"create","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\.cargo\\config.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/.cargo/config.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/.cargo/config.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":296,"initialContent":"","telemetryInfo":{}},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":297,"edits":[{"text":"\nhid-bridge = { path = \"../../host/hid-bridge\" }","range":{"startLineNumber":12,"startColumn":1,"endLineNumber":12,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\.cargo\\config.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/.cargo/config.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/.cargo/config.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":298,"edits":[{"text":"[build]","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":1,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":299,"edits":[{"text":"\nauthors.workspace = true","range":{"startLineNumber":6,"startColumn":1,"endLineNumber":6,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":300,"edits":[{"text":"\n","range":{"startLineNumber":10,"startColumn":1,"endLineNumber":10,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-cli\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":301,"edits":[{"text":"\n    intensity: u8,","range":{"startLineNumber":16,"startColumn":1,"endLineNumber":16,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":302,"edits":[{"text":"\n","range":{"startLineNumber":3,"startColumn":1,"endLineNumber":3,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":303,"edits":[{"text":"\nhaptics-protocol = { path = \"../../shared/haptics-protocol\" }","range":{"startLineNumber":13,"startColumn":1,"endLineNumber":13,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\.cargo\\config.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/.cargo/config.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/.cargo/config.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":304,"edits":[{"text":"\ntarget = \"thumbv6m-none-eabi\"","range":{"startLineNumber":2,"startColumn":1,"endLineNumber":2,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":305,"edits":[{"text":"\n","range":{"startLineNumber":7,"startColumn":1,"endLineNumber":7,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-cli\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":306,"edits":[{"text":"\n}","range":{"startLineNumber":17,"startColumn":1,"endLineNumber":17,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":307,"edits":[{"text":"\nuse cortex_m_rt::entry;","range":{"startLineNumber":4,"startColumn":1,"endLineNumber":4,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":308,"edits":[{"text":"\nthiserror.workspace = true","range":{"startLineNumber":14,"startColumn":1,"endLineNumber":14,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\.cargo\\config.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/.cargo/config.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/.cargo/config.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":309,"edits":[{"text":"\n","range":{"startLineNumber":3,"startColumn":1,"endLineNumber":3,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":310,"edits":[{"text":"\n[dependencies]","range":{"startLineNumber":8,"startColumn":1,"endLineNumber":8,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-cli\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":311,"edits":[{"text":"\n","range":{"startLineNumber":18,"startColumn":1,"endLineNumber":18,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":312,"edits":[{"text":"\nuse panic_halt as _;","range":{"startLineNumber":5,"startColumn":1,"endLineNumber":5,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":313,"edits":[{"text":"\n","range":{"startLineNumber":15,"startColumn":1,"endLineNumber":15,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\.cargo\\config.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/.cargo/config.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/.cargo/config.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":314,"edits":[{"text":"\n[target.thumbv6m-none-eabi]","range":{"startLineNumber":4,"startColumn":1,"endLineNumber":4,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":315,"edits":[{"text":"\natsamd-hal = { version = \"0.21\", default-features = false, features = [\"samd21e18a\", \"usb\"] }","range":{"startLineNumber":9,"startColumn":1,"endLineNumber":9,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-cli\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":316,"edits":[{"text":"\nfn main() -> Result<()> {","range":{"startLineNumber":19,"startColumn":1,"endLineNumber":19,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":317,"edits":[{"text":"\n","range":{"startLineNumber":6,"startColumn":1,"endLineNumber":6,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\.cargo\\config.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/.cargo/config.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/.cargo/config.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":318,"edits":[{"text":"\nrustflags = [","range":{"startLineNumber":5,"startColumn":1,"endLineNumber":5,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":319,"edits":[{"text":"\ncortex-m = \"0.7\"","range":{"startLineNumber":10,"startColumn":1,"endLineNumber":10,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-cli\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":320,"edits":[{"text":"\n    let args = Cli::parse();","range":{"startLineNumber":20,"startColumn":1,"endLineNumber":20,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":321,"edits":[{"text":"\n#[entry]","range":{"startLineNumber":7,"startColumn":1,"endLineNumber":7,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\.cargo\\config.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/.cargo/config.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/.cargo/config.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":322,"edits":[{"text":"\n  \"-C\", \"link-arg=-Tlink.x\",","range":{"startLineNumber":6,"startColumn":1,"endLineNumber":6,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":323,"edits":[{"text":"\ncortex-m-rt = \"0.7\"","range":{"startLineNumber":11,"startColumn":1,"endLineNumber":11,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-cli\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":324,"edits":[{"text":"\n    let bridge = hid_bridge::HidBridge::new()?;","range":{"startLineNumber":21,"startColumn":1,"endLineNumber":21,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":325,"edits":[{"text":"\nfn main() -> ! {","range":{"startLineNumber":8,"startColumn":1,"endLineNumber":8,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\.cargo\\config.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/.cargo/config.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/.cargo/config.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":326,"edits":[{"text":"\n]","range":{"startLineNumber":7,"startColumn":1,"endLineNumber":7,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":327,"edits":[{"text":"\nembedded-hal = \"1\"","range":{"startLineNumber":12,"startColumn":1,"endLineNumber":12,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-cli\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":328,"edits":[{"text":"\n    let packet = HapticPacket::new(args.cmd, args.intensity, 0, 0);","range":{"startLineNumber":22,"startColumn":1,"endLineNumber":22,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":329,"edits":[{"text":"\n    loop {","range":{"startLineNumber":9,"startColumn":1,"endLineNumber":9,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\.cargo\\config.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/.cargo/config.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/.cargo/config.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":330,"edits":[{"text":"\n","range":{"startLineNumber":8,"startColumn":1,"endLineNumber":8,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":331,"edits":[{"text":"\nhaptics-protocol = { path = \"../../shared/haptics-protocol\", default-features = false }","range":{"startLineNumber":13,"startColumn":1,"endLineNumber":13,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-cli\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":332,"edits":[{"text":"\n    bridge.send_packet(args.vid, args.pid, packet)?;","range":{"startLineNumber":23,"startColumn":1,"endLineNumber":23,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":333,"edits":[{"text":"\n        cortex_m::asm::nop();","range":{"startLineNumber":10,"startColumn":1,"endLineNumber":10,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":334,"edits":[{"text":"\npanic-halt = \"0.2\"","range":{"startLineNumber":14,"startColumn":1,"endLineNumber":14,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-cli\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":335,"edits":[{"text":"\n    Ok(())","range":{"startLineNumber":24,"startColumn":1,"endLineNumber":24,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":336,"edits":[{"text":"\n    }","range":{"startLineNumber":11,"startColumn":1,"endLineNumber":11,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":337,"edits":[{"text":"\nusb-device = \"0.3\"","range":{"startLineNumber":15,"startColumn":1,"endLineNumber":15,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-cli\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":338,"edits":[{"text":"\n}","range":{"startLineNumber":25,"startColumn":1,"endLineNumber":25,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":339,"edits":[{"text":"\n}","range":{"startLineNumber":12,"startColumn":1,"endLineNumber":12,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":340,"edits":[{"text":"\nusbd-hid = \"0.8\"","range":{"startLineNumber":16,"startColumn":1,"endLineNumber":16,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-cli\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":341,"edits":[{"text":"\n","range":{"startLineNumber":26,"startColumn":1,"endLineNumber":26,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":342,"edits":[{"text":"\n","range":{"startLineNumber":13,"startColumn":1,"endLineNumber":13,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":343,"edits":[{"text":"\n","range":{"startLineNumber":17,"startColumn":1,"endLineNumber":17,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":344,"edits":[{"text":"\n[profile.release]","range":{"startLineNumber":18,"startColumn":1,"endLineNumber":18,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":345,"edits":[{"text":"\ncodegen-units = 1","range":{"startLineNumber":19,"startColumn":1,"endLineNumber":19,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":346,"edits":[{"text":"\nlto = true","range":{"startLineNumber":20,"startColumn":1,"endLineNumber":20,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":347,"edits":[{"text":"\nopt-level = \"s\"","range":{"startLineNumber":21,"startColumn":1,"endLineNumber":21,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":348,"edits":[{"text":"\npanic = \"abort\"","range":{"startLineNumber":22,"startColumn":1,"endLineNumber":22,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","scheme":"file"},"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","epoch":349,"edits":[{"text":"\n","range":{"startLineNumber":23,"startColumn":1,"endLineNumber":23,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\shared\\haptics-protocol\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","scheme":"file"},"requestId":"request_32e09b23-ccf2-430e-8354-01b3c60ea6ea","epoch":353,"edits":[{"text":"#![cfg_attr(not(feature = \"std\"), no_std)]\r\n\r\npub const HID_REPORT_LEN: usize = 4;\r\npub const DEFAULT_USB_VID: u16 = 0x35bd;\npub const DEFAULT_USB_PID: u16 = 0xfe01;\n\r\npub mod command {\r\n    pub const SET_INTENSITY: u8 = 0x01;\r\n    pub const TRIGGER_ROM_EFFECT: u8 = 0x02;\r\n    pub const STOP: u8 = 0x03;\r\n}\r\n\r\n#[derive(Clone, Copy, Debug, Eq, PartialEq)]\r\npub struct HapticPacket {\r\n    pub command_id: u8,\r\n    pub intensity: u8,\r\n    pub arg_lo: u8,\r\n    pub arg_hi: u8,\r\n}\r\n\r\nimpl HapticPacket {\r\n    pub const fn new(command_id: u8, intensity: u8, arg_lo: u8, arg_hi: u8) -> Self {\r\n        Self {\r\n            command_id,\r\n            intensity,\r\n            arg_lo,\r\n            arg_hi,\r\n        }\r\n    }\r\n\r\n    pub const fn to_bytes(self) -> [u8; HID_REPORT_LEN] {\r\n        [self.command_id, self.intensity, self.arg_lo, self.arg_hi]\r\n    }\r\n\r\n    pub const fn from_bytes(bytes: [u8; HID_REPORT_LEN]) -> Self {\r\n        Self {\r\n            command_id: bytes[0],\r\n            intensity: bytes[1],\r\n            arg_lo: bytes[2],\r\n            arg_hi: bytes[3],\r\n        }\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":45,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-cli\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","scheme":"file"},"requestId":"request_32e09b23-ccf2-430e-8354-01b3c60ea6ea","epoch":356,"edits":[{"text":"use anyhow::Result;\r\nuse clap::Parser;\r\nuse haptics_protocol::{HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\n\r\n#[derive(Debug, Parser)]\r\n#[command(name = \"hid-cli\")]\r\n#[command(about = \"Send starter haptic packets over HID\")]\r\nstruct Cli {\r\n    #[arg(long, default_value_t = DEFAULT_USB_VID)]\n    vid: u16,\r\n    #[arg(long, default_value_t = DEFAULT_USB_PID)]\n    pid: u16,\r\n    #[arg(long, default_value_t = 0x01)]\r\n    cmd: u8,\r\n    #[arg(long, default_value_t = 0)]\r\n    intensity: u8,\r\n}\r\n\r\nfn main() -> Result<()> {\r\n    let args = Cli::parse();\r\n    let bridge = hid_bridge::HidBridge::new()?;\r\n    let packet = HapticPacket::new(args.cmd, args.intensity, 0, 0);\r\n    bridge.send_packet(args.vid, args.pid, packet)?;\r\n    Ok(())\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":27,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-bridge\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":360,"edits":[{"text":"use haptics_protocol::HapticPacket;\r\nuse thiserror::Error;\r\n\r\n#[derive(Debug, Error)]\r\npub enum BridgeError {\r\n    #[error(\"HID error: {0}\")]\r\n    Hid(#[from] hidapi::HidError),\r\n    #[error(\"short HID write: expected {expected} bytes, wrote {actual}\")]\n    ShortWrite { expected: usize, actual: usize },\n}\r\n\r\npub struct HidBridge {\r\n    api: hidapi::HidApi,\n}\r\n\r\nimpl HidBridge {\r\n    pub fn new() -> Result<Self, BridgeError> {\r\n        let api = hidapi::HidApi::new()?;\r\n        Ok(Self { api })\n    }\r\n\r\n    pub fn send_packet(&self, vid: u16, pid: u16, packet: HapticPacket) -> Result<(), BridgeError> {\n        let device = self.api.open(vid, pid)?;\n\n        // hidapi expects report ID in byte 0 for `write`.\n        // For single-report devices, report ID is usually 0.\n        let mut report = [0u8; 5];\n        report[1..].copy_from_slice(&packet.to_bytes());\n\n        let written = device.write(&report)?;\n        if written != report.len() {\n            return Err(BridgeError::ShortWrite {\n                expected: report.len(),\n                actual: written,\n            });\n        }\n\n        Ok(())\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":42,"endColumn":1}}]},{"type":"create","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":361,"initialContent":"","telemetryInfo":{}},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":362,"edits":[{"text":"use haptics_protocol::{command, HapticPacket};","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":1,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":363,"edits":[{"text":"\n","range":{"startLineNumber":2,"startColumn":1,"endLineNumber":2,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":364,"edits":[{"text":"\n#[derive(Clone, Copy, Debug, Eq, PartialEq)]","range":{"startLineNumber":3,"startColumn":1,"endLineNumber":3,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":365,"edits":[{"text":"\npub enum ParseError {","range":{"startLineNumber":4,"startColumn":1,"endLineNumber":4,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":366,"edits":[{"text":"\n    InvalidReportLength,","range":{"startLineNumber":5,"startColumn":1,"endLineNumber":5,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":367,"edits":[{"text":"\n}","range":{"startLineNumber":6,"startColumn":1,"endLineNumber":6,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":368,"edits":[{"text":"\n","range":{"startLineNumber":7,"startColumn":1,"endLineNumber":7,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":369,"edits":[{"text":"\n#[derive(Clone, Copy, Debug, Eq, PartialEq)]","range":{"startLineNumber":8,"startColumn":1,"endLineNumber":8,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":370,"edits":[{"text":"\npub enum Drv2605lCommand {","range":{"startLineNumber":9,"startColumn":1,"endLineNumber":9,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":371,"edits":[{"text":"\n    /// MODE=RTP, write `RTP_INPUT` (0x02) using `intensity`.","range":{"startLineNumber":10,"startColumn":1,"endLineNumber":10,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":372,"edits":[{"text":"\n    SetIntensity { intensity: u8 },","range":{"startLineNumber":11,"startColumn":1,"endLineNumber":11,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":373,"edits":[{"text":"\n    /// Trigger a ROM waveform effect ID from `arg_lo`.","range":{"startLineNumber":12,"startColumn":1,"endLineNumber":12,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":374,"edits":[{"text":"\n    TriggerRomEffect { effect_id: u8, intensity: u8 },","range":{"startLineNumber":13,"startColumn":1,"endLineNumber":13,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":375,"edits":[{"text":"\n    /// Stop/idle haptics.","range":{"startLineNumber":14,"startColumn":1,"endLineNumber":14,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":376,"edits":[{"text":"\n    Stop,","range":{"startLineNumber":15,"startColumn":1,"endLineNumber":15,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":377,"edits":[{"text":"\n    /// Unknown command byte for forward compatibility handling.","range":{"startLineNumber":16,"startColumn":1,"endLineNumber":16,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":378,"edits":[{"text":"\n    Unknown { command_id: u8, intensity: u8, arg_lo: u8, arg_hi: u8 },","range":{"startLineNumber":17,"startColumn":1,"endLineNumber":17,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":379,"edits":[{"text":"\n}","range":{"startLineNumber":18,"startColumn":1,"endLineNumber":18,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":380,"edits":[{"text":"\n","range":{"startLineNumber":19,"startColumn":1,"endLineNumber":19,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":381,"edits":[{"text":"\nimpl Drv2605lCommand {","range":{"startLineNumber":20,"startColumn":1,"endLineNumber":20,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":382,"edits":[{"text":"\n    pub const fn from_packet(packet: HapticPacket) -> Self {","range":{"startLineNumber":21,"startColumn":1,"endLineNumber":21,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":383,"edits":[{"text":"\n        match packet.command_id {","range":{"startLineNumber":22,"startColumn":1,"endLineNumber":22,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":384,"edits":[{"text":"\n            command::SET_INTENSITY => Self::SetIntensity {","range":{"startLineNumber":23,"startColumn":1,"endLineNumber":23,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":385,"edits":[{"text":"\n                intensity: packet.intensity,","range":{"startLineNumber":24,"startColumn":1,"endLineNumber":24,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":386,"edits":[{"text":"\n            },","range":{"startLineNumber":25,"startColumn":1,"endLineNumber":25,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":387,"edits":[{"text":"\n            command::TRIGGER_ROM_EFFECT => Self::TriggerRomEffect {","range":{"startLineNumber":26,"startColumn":1,"endLineNumber":26,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":388,"edits":[{"text":"\n                effect_id: packet.arg_lo,","range":{"startLineNumber":27,"startColumn":1,"endLineNumber":27,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":389,"edits":[{"text":"\n                intensity: packet.intensity,","range":{"startLineNumber":28,"startColumn":1,"endLineNumber":28,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":390,"edits":[{"text":"\n            },","range":{"startLineNumber":29,"startColumn":1,"endLineNumber":29,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":391,"edits":[{"text":"\n            command::STOP => Self::Stop,","range":{"startLineNumber":30,"startColumn":1,"endLineNumber":30,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":392,"edits":[{"text":"\n            _ => Self::Unknown {","range":{"startLineNumber":31,"startColumn":1,"endLineNumber":31,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":393,"edits":[{"text":"\n                command_id: packet.command_id,","range":{"startLineNumber":32,"startColumn":1,"endLineNumber":32,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":394,"edits":[{"text":"\n                intensity: packet.intensity,","range":{"startLineNumber":33,"startColumn":1,"endLineNumber":33,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":395,"edits":[{"text":"\n                arg_lo: packet.arg_lo,","range":{"startLineNumber":34,"startColumn":1,"endLineNumber":34,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":396,"edits":[{"text":"\n                arg_hi: packet.arg_hi,","range":{"startLineNumber":35,"startColumn":1,"endLineNumber":35,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":397,"edits":[{"text":"\n            },","range":{"startLineNumber":36,"startColumn":1,"endLineNumber":36,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":398,"edits":[{"text":"\n        }","range":{"startLineNumber":37,"startColumn":1,"endLineNumber":37,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":399,"edits":[{"text":"\n    }","range":{"startLineNumber":38,"startColumn":1,"endLineNumber":38,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":400,"edits":[{"text":"\n}","range":{"startLineNumber":39,"startColumn":1,"endLineNumber":39,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":401,"edits":[{"text":"\n","range":{"startLineNumber":40,"startColumn":1,"endLineNumber":40,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":402,"edits":[{"text":"\n/// Parse a HID output report into a protocol packet.","range":{"startLineNumber":41,"startColumn":1,"endLineNumber":41,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":403,"edits":[{"text":"\n///","range":{"startLineNumber":42,"startColumn":1,"endLineNumber":42,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":404,"edits":[{"text":"\n/// Accepts either:","range":{"startLineNumber":43,"startColumn":1,"endLineNumber":43,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":405,"edits":[{"text":"\n/// - 4-byte payload reports: [cmd, intensity, arg_lo, arg_hi]","range":{"startLineNumber":44,"startColumn":1,"endLineNumber":44,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":406,"edits":[{"text":"\n/// - 5-byte reports with report ID prefix 0: [0, cmd, intensity, arg_lo, arg_hi]","range":{"startLineNumber":45,"startColumn":1,"endLineNumber":45,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":407,"edits":[{"text":"\npub fn parse_hid_report(report: &[u8]) -> Result<HapticPacket, ParseError> {","range":{"startLineNumber":46,"startColumn":1,"endLineNumber":46,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":408,"edits":[{"text":"\n    match report {","range":{"startLineNumber":47,"startColumn":1,"endLineNumber":47,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":409,"edits":[{"text":"\n        [cmd, intensity, arg_lo, arg_hi] => Ok(HapticPacket::new(*cmd, *intensity, *arg_lo, *arg_hi)),","range":{"startLineNumber":48,"startColumn":1,"endLineNumber":48,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":410,"edits":[{"text":"\n        [0, cmd, intensity, arg_lo, arg_hi] => {","range":{"startLineNumber":49,"startColumn":1,"endLineNumber":49,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":411,"edits":[{"text":"\n            Ok(HapticPacket::new(*cmd, *intensity, *arg_lo, *arg_hi))","range":{"startLineNumber":50,"startColumn":1,"endLineNumber":50,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":412,"edits":[{"text":"\n        }","range":{"startLineNumber":51,"startColumn":1,"endLineNumber":51,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":413,"edits":[{"text":"\n        _ => Err(ParseError::InvalidReportLength),","range":{"startLineNumber":52,"startColumn":1,"endLineNumber":52,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":414,"edits":[{"text":"\n    }","range":{"startLineNumber":53,"startColumn":1,"endLineNumber":53,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":415,"edits":[{"text":"\n}","range":{"startLineNumber":54,"startColumn":1,"endLineNumber":54,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":416,"edits":[{"text":"\n","range":{"startLineNumber":55,"startColumn":1,"endLineNumber":55,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":417,"edits":[{"text":"\npub fn parse_command(report: &[u8]) -> Result<Drv2605lCommand, ParseError> {","range":{"startLineNumber":56,"startColumn":1,"endLineNumber":56,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":418,"edits":[{"text":"\n    let packet = parse_hid_report(report)?;","range":{"startLineNumber":57,"startColumn":1,"endLineNumber":57,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":419,"edits":[{"text":"\n    Ok(Drv2605lCommand::from_packet(packet))","range":{"startLineNumber":58,"startColumn":1,"endLineNumber":58,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":420,"edits":[{"text":"\n}","range":{"startLineNumber":59,"startColumn":1,"endLineNumber":59,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l_parser.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":421,"edits":[{"text":"\n","range":{"startLineNumber":60,"startColumn":1,"endLineNumber":60,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs","scheme":"file"},"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","epoch":424,"edits":[{"text":"#![no_std]\r\n#![no_main]\r\n\r\nuse cortex_m_rt::entry;\r\nuse haptics_protocol::command;\nuse panic_halt as _;\r\n\nmod drv2605l_parser;\n\r\n#[entry]\r\nfn main() -> ! {\r\n    // Starter integration smoke check for the parser module.\n    let sample_report = [command::STOP, 0, 0, 0];\n    let _ = drv2605l_parser::parse_command(&sample_report);\n\n    loop {\r\n        cortex_m::asm::nop();\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":21,"endColumn":1}}]},{"type":"create","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":426,"initialContent":"","telemetryInfo":{}},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":427,"edits":[{"text":"use embedded_hal::i2c::I2c;","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":1,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":428,"edits":[{"text":"\n","range":{"startLineNumber":2,"startColumn":1,"endLineNumber":2,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":429,"edits":[{"text":"\nuse crate::drv2605l_parser::Drv2605lCommand;","range":{"startLineNumber":3,"startColumn":1,"endLineNumber":3,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":430,"edits":[{"text":"\n","range":{"startLineNumber":4,"startColumn":1,"endLineNumber":4,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":431,"edits":[{"text":"\npub const DRV2605L_I2C_ADDR: u8 = 0x5a;","range":{"startLineNumber":5,"startColumn":1,"endLineNumber":5,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":432,"edits":[{"text":"\n","range":{"startLineNumber":6,"startColumn":1,"endLineNumber":6,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":433,"edits":[{"text":"\nconst REG_MODE: u8 = 0x01;","range":{"startLineNumber":7,"startColumn":1,"endLineNumber":7,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":434,"edits":[{"text":"\nconst REG_RTP_INPUT: u8 = 0x02;","range":{"startLineNumber":8,"startColumn":1,"endLineNumber":8,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":435,"edits":[{"text":"\nconst REG_LIBRARY_SELECTION: u8 = 0x03;","range":{"startLineNumber":9,"startColumn":1,"endLineNumber":9,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":436,"edits":[{"text":"\nconst REG_WAVEFORM_SEQ1: u8 = 0x04;","range":{"startLineNumber":10,"startColumn":1,"endLineNumber":10,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":437,"edits":[{"text":"\nconst REG_WAVEFORM_SEQ2: u8 = 0x05;","range":{"startLineNumber":11,"startColumn":1,"endLineNumber":11,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":438,"edits":[{"text":"\nconst REG_GO: u8 = 0x0c;","range":{"startLineNumber":12,"startColumn":1,"endLineNumber":12,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":439,"edits":[{"text":"\n","range":{"startLineNumber":13,"startColumn":1,"endLineNumber":13,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":440,"edits":[{"text":"\nconst MODE_INTERNAL_TRIGGER: u8 = 0x00;","range":{"startLineNumber":14,"startColumn":1,"endLineNumber":14,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":441,"edits":[{"text":"\nconst MODE_REALTIME_PLAYBACK: u8 = 0x05;","range":{"startLineNumber":15,"startColumn":1,"endLineNumber":15,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":442,"edits":[{"text":"\n","range":{"startLineNumber":16,"startColumn":1,"endLineNumber":16,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":443,"edits":[{"text":"\n#[derive(Clone, Copy, Debug, Eq, PartialEq)]","range":{"startLineNumber":17,"startColumn":1,"endLineNumber":17,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":444,"edits":[{"text":"\npub enum DispatchError<E> {","range":{"startLineNumber":18,"startColumn":1,"endLineNumber":18,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":445,"edits":[{"text":"\n    Parse(crate::drv2605l_parser::ParseError),","range":{"startLineNumber":19,"startColumn":1,"endLineNumber":19,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":446,"edits":[{"text":"\n    I2c(E),","range":{"startLineNumber":20,"startColumn":1,"endLineNumber":20,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":447,"edits":[{"text":"\n}","range":{"startLineNumber":21,"startColumn":1,"endLineNumber":21,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":448,"edits":[{"text":"\n","range":{"startLineNumber":22,"startColumn":1,"endLineNumber":22,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":449,"edits":[{"text":"\npub struct Drv2605l<I2C> {","range":{"startLineNumber":23,"startColumn":1,"endLineNumber":23,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":450,"edits":[{"text":"\n    i2c: I2C,","range":{"startLineNumber":24,"startColumn":1,"endLineNumber":24,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":451,"edits":[{"text":"\n    address: u8,","range":{"startLineNumber":25,"startColumn":1,"endLineNumber":25,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":452,"edits":[{"text":"\n}","range":{"startLineNumber":26,"startColumn":1,"endLineNumber":26,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":453,"edits":[{"text":"\n","range":{"startLineNumber":27,"startColumn":1,"endLineNumber":27,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":454,"edits":[{"text":"\nimpl<I2C> Drv2605l<I2C>","range":{"startLineNumber":28,"startColumn":1,"endLineNumber":28,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":455,"edits":[{"text":"\nwhere","range":{"startLineNumber":29,"startColumn":1,"endLineNumber":29,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":456,"edits":[{"text":"\n    I2C: I2c,","range":{"startLineNumber":30,"startColumn":1,"endLineNumber":30,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":457,"edits":[{"text":"\n{","range":{"startLineNumber":31,"startColumn":1,"endLineNumber":31,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":458,"edits":[{"text":"\n    pub fn new(i2c: I2C) -> Self {","range":{"startLineNumber":32,"startColumn":1,"endLineNumber":32,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":459,"edits":[{"text":"\n        Self {","range":{"startLineNumber":33,"startColumn":1,"endLineNumber":33,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":460,"edits":[{"text":"\n            i2c,","range":{"startLineNumber":34,"startColumn":1,"endLineNumber":34,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":461,"edits":[{"text":"\n            address: DRV2605L_I2C_ADDR,","range":{"startLineNumber":35,"startColumn":1,"endLineNumber":35,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":462,"edits":[{"text":"\n        }","range":{"startLineNumber":36,"startColumn":1,"endLineNumber":36,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":463,"edits":[{"text":"\n    }","range":{"startLineNumber":37,"startColumn":1,"endLineNumber":37,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":464,"edits":[{"text":"\n","range":{"startLineNumber":38,"startColumn":1,"endLineNumber":38,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":465,"edits":[{"text":"\n    pub fn with_address(i2c: I2C, address: u8) -> Self {","range":{"startLineNumber":39,"startColumn":1,"endLineNumber":39,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":466,"edits":[{"text":"\n        Self { i2c, address }","range":{"startLineNumber":40,"startColumn":1,"endLineNumber":40,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":467,"edits":[{"text":"\n    }","range":{"startLineNumber":41,"startColumn":1,"endLineNumber":41,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":468,"edits":[{"text":"\n","range":{"startLineNumber":42,"startColumn":1,"endLineNumber":42,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":469,"edits":[{"text":"\n    pub fn release(self) -> I2C {","range":{"startLineNumber":43,"startColumn":1,"endLineNumber":43,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":470,"edits":[{"text":"\n        self.i2c","range":{"startLineNumber":44,"startColumn":1,"endLineNumber":44,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":471,"edits":[{"text":"\n    }","range":{"startLineNumber":45,"startColumn":1,"endLineNumber":45,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":472,"edits":[{"text":"\n","range":{"startLineNumber":46,"startColumn":1,"endLineNumber":46,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":473,"edits":[{"text":"\n    pub fn write_register(&mut self, register: u8, value: u8) -> Result<(), I2C::Error> {","range":{"startLineNumber":47,"startColumn":1,"endLineNumber":47,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":474,"edits":[{"text":"\n        self.i2c.write(self.address, &[register, value])","range":{"startLineNumber":48,"startColumn":1,"endLineNumber":48,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":475,"edits":[{"text":"\n    }","range":{"startLineNumber":49,"startColumn":1,"endLineNumber":49,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":476,"edits":[{"text":"\n","range":{"startLineNumber":50,"startColumn":1,"endLineNumber":50,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":477,"edits":[{"text":"\n    pub fn set_rtp_mode(&mut self) -> Result<(), I2C::Error> {","range":{"startLineNumber":51,"startColumn":1,"endLineNumber":51,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":478,"edits":[{"text":"\n        self.write_register(REG_MODE, MODE_REALTIME_PLAYBACK)","range":{"startLineNumber":52,"startColumn":1,"endLineNumber":52,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":479,"edits":[{"text":"\n    }","range":{"startLineNumber":53,"startColumn":1,"endLineNumber":53,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":480,"edits":[{"text":"\n","range":{"startLineNumber":54,"startColumn":1,"endLineNumber":54,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":481,"edits":[{"text":"\n    pub fn set_rtp_input(&mut self, intensity: u8) -> Result<(), I2C::Error> {","range":{"startLineNumber":55,"startColumn":1,"endLineNumber":55,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":482,"edits":[{"text":"\n        self.write_register(REG_RTP_INPUT, intensity)","range":{"startLineNumber":56,"startColumn":1,"endLineNumber":56,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":483,"edits":[{"text":"\n    }","range":{"startLineNumber":57,"startColumn":1,"endLineNumber":57,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":484,"edits":[{"text":"\n","range":{"startLineNumber":58,"startColumn":1,"endLineNumber":58,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":485,"edits":[{"text":"\n    pub fn trigger_rom_effect(&mut self, effect_id: u8) -> Result<(), I2C::Error> {","range":{"startLineNumber":59,"startColumn":1,"endLineNumber":59,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":486,"edits":[{"text":"\n        self.write_register(REG_MODE, MODE_INTERNAL_TRIGGER)?;","range":{"startLineNumber":60,"startColumn":1,"endLineNumber":60,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":487,"edits":[{"text":"\n        self.write_register(REG_LIBRARY_SELECTION, 1)?;","range":{"startLineNumber":61,"startColumn":1,"endLineNumber":61,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":488,"edits":[{"text":"\n        self.write_register(REG_WAVEFORM_SEQ1, effect_id)?;","range":{"startLineNumber":62,"startColumn":1,"endLineNumber":62,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":489,"edits":[{"text":"\n        self.write_register(REG_WAVEFORM_SEQ2, 0)?;","range":{"startLineNumber":63,"startColumn":1,"endLineNumber":63,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":490,"edits":[{"text":"\n        self.write_register(REG_GO, 1)","range":{"startLineNumber":64,"startColumn":1,"endLineNumber":64,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":491,"edits":[{"text":"\n    }","range":{"startLineNumber":65,"startColumn":1,"endLineNumber":65,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":492,"edits":[{"text":"\n","range":{"startLineNumber":66,"startColumn":1,"endLineNumber":66,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":493,"edits":[{"text":"\n    pub fn stop(&mut self) -> Result<(), I2C::Error> {","range":{"startLineNumber":67,"startColumn":1,"endLineNumber":67,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":494,"edits":[{"text":"\n        self.write_register(REG_GO, 0)?;","range":{"startLineNumber":68,"startColumn":1,"endLineNumber":68,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":495,"edits":[{"text":"\n        self.set_rtp_mode()?;","range":{"startLineNumber":69,"startColumn":1,"endLineNumber":69,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":496,"edits":[{"text":"\n        self.set_rtp_input(0)","range":{"startLineNumber":70,"startColumn":1,"endLineNumber":70,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":497,"edits":[{"text":"\n    }","range":{"startLineNumber":71,"startColumn":1,"endLineNumber":71,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":498,"edits":[{"text":"\n","range":{"startLineNumber":72,"startColumn":1,"endLineNumber":72,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":499,"edits":[{"text":"\n    pub fn apply_command(&mut self, cmd: Drv2605lCommand) -> Result<(), I2C::Error> {","range":{"startLineNumber":73,"startColumn":1,"endLineNumber":73,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":500,"edits":[{"text":"\n        match cmd {","range":{"startLineNumber":74,"startColumn":1,"endLineNumber":74,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":501,"edits":[{"text":"\n            Drv2605lCommand::SetIntensity { intensity } => {","range":{"startLineNumber":75,"startColumn":1,"endLineNumber":75,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":502,"edits":[{"text":"\n                self.set_rtp_mode()?;","range":{"startLineNumber":76,"startColumn":1,"endLineNumber":76,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":503,"edits":[{"text":"\n                self.set_rtp_input(intensity)","range":{"startLineNumber":77,"startColumn":1,"endLineNumber":77,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":504,"edits":[{"text":"\n            }","range":{"startLineNumber":78,"startColumn":1,"endLineNumber":78,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":505,"edits":[{"text":"\n            Drv2605lCommand::TriggerRomEffect { effect_id, intensity: _ } => {","range":{"startLineNumber":79,"startColumn":1,"endLineNumber":79,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":506,"edits":[{"text":"\n                self.trigger_rom_effect(effect_id)","range":{"startLineNumber":80,"startColumn":1,"endLineNumber":80,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":507,"edits":[{"text":"\n            }","range":{"startLineNumber":81,"startColumn":1,"endLineNumber":81,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":508,"edits":[{"text":"\n            Drv2605lCommand::Stop => self.stop(),","range":{"startLineNumber":82,"startColumn":1,"endLineNumber":82,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":509,"edits":[{"text":"\n            Drv2605lCommand::Unknown { .. } => Ok(()),","range":{"startLineNumber":83,"startColumn":1,"endLineNumber":83,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":510,"edits":[{"text":"\n        }","range":{"startLineNumber":84,"startColumn":1,"endLineNumber":84,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":511,"edits":[{"text":"\n    }","range":{"startLineNumber":85,"startColumn":1,"endLineNumber":85,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":512,"edits":[{"text":"\n}","range":{"startLineNumber":86,"startColumn":1,"endLineNumber":86,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":513,"edits":[{"text":"\n","range":{"startLineNumber":87,"startColumn":1,"endLineNumber":87,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs","scheme":"file"},"requestId":"request_1fc3f201-1545-49ac-9ca3-cf7a854c3c0f","epoch":516,"edits":[{"text":"#![no_std]\r\n#![no_main]\r\n\r\nuse cortex_m_rt::entry;\r\nuse embedded_hal::i2c::I2c;\nuse haptics_protocol::command;\r\nuse panic_halt as _;\r\n\r\nmod drv2605l;\nmod drv2605l_parser;\r\n\nfn handle_hid_report<I2C>(\n    drv: &mut drv2605l::Drv2605l<I2C>,\n    report: &[u8],\n) -> Result<(), drv2605l::DispatchError<I2C::Error>>\nwhere\n    I2C: I2c,\n{\n    let cmd = drv2605l_parser::parse_command(report).map_err(drv2605l::DispatchError::Parse)?;\n    drv.apply_command(cmd).map_err(drv2605l::DispatchError::I2c)\n}\n\r\n#[entry]\r\nfn main() -> ! {\r\n    // Starter integration smoke check for the parser module.\n    // In the USB HID output-report callback, call `handle_hid_report(...)`.\n    let sample_report = [command::STOP, 0, 0, 0];\r\n    let _ = drv2605l_parser::parse_command(&sample_report);\r\n\r\n    loop {\r\n        cortex_m::asm::nop();\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":35,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":520,"edits":[{"text":"#![no_std]\r\n#![no_main]\r\n\r\nuse cortex_m_rt::entry;\r\nuse embedded_hal::i2c::I2c;\r\nuse haptics_protocol::command;\r\nuse panic_halt as _;\r\n\r\nmod drv2605l;\r\nmod drv2605l_parser;\r\nmod platform_samd21;\nmod usb_hid;\n\r\npub(crate) fn handle_hid_report<I2C>(\n    drv: &mut drv2605l::Drv2605l<I2C>,\r\n    report: &[u8],\r\n) -> Result<(), drv2605l::DispatchError<I2C::Error>>\r\nwhere\r\n    I2C: I2c,\r\n{\r\n    let cmd = drv2605l_parser::parse_command(report).map_err(drv2605l::DispatchError::Parse)?;\r\n    drv.apply_command(cmd).map_err(drv2605l::DispatchError::I2c)\r\n}\r\n\n/// Call this from the USB HID output-report receive path.\npub(crate) fn on_hid_output_report<I2C>(\n    drv: &mut drv2605l::Drv2605l<I2C>,\n    report: &[u8],\n) -> Result<(), drv2605l::DispatchError<I2C::Error>>\nwhere\n    I2C: I2c,\n{\n    usb_hid::process_output_report(drv, report)\n}\n\r\n#[entry]\r\nfn main() -> ! {\r\n    // Starter integration smoke check for the parser module.\n    // For full hardware bring-up, use helpers in `platform_samd21` and invoke\n    // `on_hid_output_report(...)` when an OUT report arrives.\n    let sample_report = [command::STOP, 0, 0, 0];\r\n    let _ = drv2605l_parser::parse_command(&sample_report);\r\n\r\n    loop {\r\n        cortex_m::asm::nop();\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":49,"endColumn":1}}]},{"type":"create","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\usb_hid.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/usb_hid.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/usb_hid.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":521,"initialContent":"","telemetryInfo":{}},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\usb_hid.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/usb_hid.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/usb_hid.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":522,"edits":[{"text":"use embedded_hal::i2c::I2c;","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":1,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\usb_hid.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/usb_hid.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/usb_hid.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":523,"edits":[{"text":"\n","range":{"startLineNumber":2,"startColumn":1,"endLineNumber":2,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\usb_hid.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/usb_hid.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/usb_hid.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":524,"edits":[{"text":"\nuse crate::drv2605l::DispatchError;","range":{"startLineNumber":3,"startColumn":1,"endLineNumber":3,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\usb_hid.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/usb_hid.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/usb_hid.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":525,"edits":[{"text":"\n","range":{"startLineNumber":4,"startColumn":1,"endLineNumber":4,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\usb_hid.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/usb_hid.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/usb_hid.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":526,"edits":[{"text":"\npub fn process_output_report<I2C>(","range":{"startLineNumber":5,"startColumn":1,"endLineNumber":5,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\usb_hid.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/usb_hid.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/usb_hid.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":527,"edits":[{"text":"\n    drv: &mut crate::drv2605l::Drv2605l<I2C>,","range":{"startLineNumber":6,"startColumn":1,"endLineNumber":6,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\usb_hid.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/usb_hid.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/usb_hid.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":528,"edits":[{"text":"\n    report: &[u8],","range":{"startLineNumber":7,"startColumn":1,"endLineNumber":7,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\usb_hid.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/usb_hid.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/usb_hid.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":529,"edits":[{"text":"\n) -> Result<(), DispatchError<I2C::Error>>","range":{"startLineNumber":8,"startColumn":1,"endLineNumber":8,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\usb_hid.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/usb_hid.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/usb_hid.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":530,"edits":[{"text":"\nwhere","range":{"startLineNumber":9,"startColumn":1,"endLineNumber":9,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\usb_hid.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/usb_hid.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/usb_hid.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":531,"edits":[{"text":"\n    I2C: I2c,","range":{"startLineNumber":10,"startColumn":1,"endLineNumber":10,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\usb_hid.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/usb_hid.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/usb_hid.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":532,"edits":[{"text":"\n{","range":{"startLineNumber":11,"startColumn":1,"endLineNumber":11,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\usb_hid.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/usb_hid.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/usb_hid.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":533,"edits":[{"text":"\n    crate::handle_hid_report(drv, report)","range":{"startLineNumber":12,"startColumn":1,"endLineNumber":12,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\usb_hid.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/usb_hid.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/usb_hid.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":534,"edits":[{"text":"\n}","range":{"startLineNumber":13,"startColumn":1,"endLineNumber":13,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\usb_hid.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/usb_hid.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/usb_hid.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":535,"edits":[{"text":"\n","range":{"startLineNumber":14,"startColumn":1,"endLineNumber":14,"endColumn":1}}]},{"type":"create","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":536,"initialContent":"","telemetryInfo":{}},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":537,"edits":[{"text":"//! SAMD21 platform bring-up scaffold.","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":1,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":538,"edits":[{"text":"\n//!","range":{"startLineNumber":2,"startColumn":1,"endLineNumber":2,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":539,"edits":[{"text":"\n//! This module documents where SERCOM2 I2C and USB HID integration should be","range":{"startLineNumber":3,"startColumn":1,"endLineNumber":3,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":540,"edits":[{"text":"\n//! initialized for the QT Py SAMD21 target.","range":{"startLineNumber":4,"startColumn":1,"endLineNumber":4,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":541,"edits":[{"text":"\n//!","range":{"startLineNumber":5,"startColumn":1,"endLineNumber":5,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":542,"edits":[{"text":"\n//! Intended wiring:","range":{"startLineNumber":6,"startColumn":1,"endLineNumber":6,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":543,"edits":[{"text":"\n//! - I2C SDA: PA08 (SERCOM2/PAD0)","range":{"startLineNumber":7,"startColumn":1,"endLineNumber":7,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":544,"edits":[{"text":"\n//! - I2C SCL: PA09 (SERCOM2/PAD1)","range":{"startLineNumber":8,"startColumn":1,"endLineNumber":8,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":545,"edits":[{"text":"\n//! - DRV2605L address: 0x5A","range":{"startLineNumber":9,"startColumn":1,"endLineNumber":9,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":546,"edits":[{"text":"\n//!","range":{"startLineNumber":10,"startColumn":1,"endLineNumber":10,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":547,"edits":[{"text":"\n//! Integration flow:","range":{"startLineNumber":11,"startColumn":1,"endLineNumber":11,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":548,"edits":[{"text":"\n//! 1) Initialize clocks and pins","range":{"startLineNumber":12,"startColumn":1,"endLineNumber":12,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":549,"edits":[{"text":"\n//! 2) Bring up SERCOM2 as I2C master at 400kHz","range":{"startLineNumber":13,"startColumn":1,"endLineNumber":13,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":550,"edits":[{"text":"\n//! 3) Construct `crate::drv2605l::Drv2605l` from I2C peripheral","range":{"startLineNumber":14,"startColumn":1,"endLineNumber":14,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":551,"edits":[{"text":"\n//! 4) Initialize USB device + HID class","range":{"startLineNumber":15,"startColumn":1,"endLineNumber":15,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":552,"edits":[{"text":"\n//! 5) On each received HID OUT report, call `crate::on_hid_output_report(...)`","range":{"startLineNumber":16,"startColumn":1,"endLineNumber":16,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":553,"edits":[{"text":"\n","range":{"startLineNumber":17,"startColumn":1,"endLineNumber":17,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":554,"edits":[{"text":"\n#[allow(dead_code)]","range":{"startLineNumber":18,"startColumn":1,"endLineNumber":18,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":555,"edits":[{"text":"\npub const I2C_TARGET_HZ: u32 = 400_000;","range":{"startLineNumber":19,"startColumn":1,"endLineNumber":19,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":556,"edits":[{"text":"\n","range":{"startLineNumber":20,"startColumn":1,"endLineNumber":20,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":557,"edits":[{"text":"\n#[allow(dead_code)]","range":{"startLineNumber":21,"startColumn":1,"endLineNumber":21,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":558,"edits":[{"text":"\npub const QT_PY_SDA_PIN: &str = \"PA08\";","range":{"startLineNumber":22,"startColumn":1,"endLineNumber":22,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":559,"edits":[{"text":"\n#[allow(dead_code)]","range":{"startLineNumber":23,"startColumn":1,"endLineNumber":23,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":560,"edits":[{"text":"\npub const QT_PY_SCL_PIN: &str = \"PA09\";","range":{"startLineNumber":24,"startColumn":1,"endLineNumber":24,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":561,"edits":[{"text":"\n","range":{"startLineNumber":25,"startColumn":1,"endLineNumber":25,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":562,"edits":[{"text":"\n#[cfg(any())]","range":{"startLineNumber":26,"startColumn":1,"endLineNumber":26,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":563,"edits":[{"text":"\nmod reference_only_example {","range":{"startLineNumber":27,"startColumn":1,"endLineNumber":27,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":564,"edits":[{"text":"\n    use atsamd_hal as hal;","range":{"startLineNumber":28,"startColumn":1,"endLineNumber":28,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":565,"edits":[{"text":"\n","range":{"startLineNumber":29,"startColumn":1,"endLineNumber":29,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":566,"edits":[{"text":"\n    // This block is intentionally disabled (`cfg(any())`) until the concrete","range":{"startLineNumber":30,"startColumn":1,"endLineNumber":30,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":567,"edits":[{"text":"\n    // board setup is finalized for your exact atsamd-hal version.","range":{"startLineNumber":31,"startColumn":1,"endLineNumber":31,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":568,"edits":[{"text":"\n    //","range":{"startLineNumber":32,"startColumn":1,"endLineNumber":32,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":569,"edits":[{"text":"\n    // Pseudocode shape:","range":{"startLineNumber":33,"startColumn":1,"endLineNumber":33,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":570,"edits":[{"text":"\n    //","range":{"startLineNumber":34,"startColumn":1,"endLineNumber":34,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":571,"edits":[{"text":"\n    // let mut peripherals = hal::pac::Peripherals::take().unwrap();","range":{"startLineNumber":35,"startColumn":1,"endLineNumber":35,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":572,"edits":[{"text":"\n    // let mut core = hal::pac::CorePeripherals::take().unwrap();","range":{"startLineNumber":36,"startColumn":1,"endLineNumber":36,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":573,"edits":[{"text":"\n    // let mut clocks = GenericClockController::with_internal_32kosc(","range":{"startLineNumber":37,"startColumn":1,"endLineNumber":37,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":574,"edits":[{"text":"\n    //     peripherals.GCLK,","range":{"startLineNumber":38,"startColumn":1,"endLineNumber":38,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":575,"edits":[{"text":"\n    //     &mut peripherals.MCLK,","range":{"startLineNumber":39,"startColumn":1,"endLineNumber":39,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":576,"edits":[{"text":"\n    //     &mut peripherals.OSC32KCTRL,","range":{"startLineNumber":40,"startColumn":1,"endLineNumber":40,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":577,"edits":[{"text":"\n    //     &mut peripherals.OSCCTRL,","range":{"startLineNumber":41,"startColumn":1,"endLineNumber":41,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":578,"edits":[{"text":"\n    //     &mut peripherals.NVMCTRL,","range":{"startLineNumber":42,"startColumn":1,"endLineNumber":42,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":579,"edits":[{"text":"\n    // );","range":{"startLineNumber":43,"startColumn":1,"endLineNumber":43,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":580,"edits":[{"text":"\n    //","range":{"startLineNumber":44,"startColumn":1,"endLineNumber":44,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":581,"edits":[{"text":"\n    // let pins = hal::Pins::new(peripherals.PORT);","range":{"startLineNumber":45,"startColumn":1,"endLineNumber":45,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":582,"edits":[{"text":"\n    // let sda = pins.d4.into_mode::<hal::gpio::FunctionC>(); // PA08","range":{"startLineNumber":46,"startColumn":1,"endLineNumber":46,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":583,"edits":[{"text":"\n    // let scl = pins.d5.into_mode::<hal::gpio::FunctionC>(); // PA09","range":{"startLineNumber":47,"startColumn":1,"endLineNumber":47,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":584,"edits":[{"text":"\n    //","range":{"startLineNumber":48,"startColumn":1,"endLineNumber":48,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":585,"edits":[{"text":"\n    // let i2c = hal::sercom::I2CMaster2::new(","range":{"startLineNumber":49,"startColumn":1,"endLineNumber":49,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":586,"edits":[{"text":"\n    //     &clocks.sercom2_core(&gclk0).unwrap(),","range":{"startLineNumber":50,"startColumn":1,"endLineNumber":50,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":587,"edits":[{"text":"\n    //     I2C_TARGET_HZ.hz(),","range":{"startLineNumber":51,"startColumn":1,"endLineNumber":51,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":588,"edits":[{"text":"\n    //     peripherals.SERCOM2,","range":{"startLineNumber":52,"startColumn":1,"endLineNumber":52,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":589,"edits":[{"text":"\n    //     &mut peripherals.MCLK,","range":{"startLineNumber":53,"startColumn":1,"endLineNumber":53,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":590,"edits":[{"text":"\n    //     sda,","range":{"startLineNumber":54,"startColumn":1,"endLineNumber":54,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":591,"edits":[{"text":"\n    //     scl,","range":{"startLineNumber":55,"startColumn":1,"endLineNumber":55,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":592,"edits":[{"text":"\n    // );","range":{"startLineNumber":56,"startColumn":1,"endLineNumber":56,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":593,"edits":[{"text":"\n    //","range":{"startLineNumber":57,"startColumn":1,"endLineNumber":57,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":594,"edits":[{"text":"\n    // let mut drv = crate::drv2605l::Drv2605l::new(i2c);","range":{"startLineNumber":58,"startColumn":1,"endLineNumber":58,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":595,"edits":[{"text":"\n    // drv.set_rtp_mode().ok();","range":{"startLineNumber":59,"startColumn":1,"endLineNumber":59,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":596,"edits":[{"text":"\n    //","range":{"startLineNumber":60,"startColumn":1,"endLineNumber":60,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":597,"edits":[{"text":"\n    // // USB event loop:","range":{"startLineNumber":61,"startColumn":1,"endLineNumber":61,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":598,"edits":[{"text":"\n    // // if hid.pull_raw_output(&mut buf).is_ok() {","range":{"startLineNumber":62,"startColumn":1,"endLineNumber":62,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":599,"edits":[{"text":"\n    // //     let _ = crate::on_hid_output_report(&mut drv, &buf[..len]);","range":{"startLineNumber":63,"startColumn":1,"endLineNumber":63,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":600,"edits":[{"text":"\n    // // }","range":{"startLineNumber":64,"startColumn":1,"endLineNumber":64,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":601,"edits":[{"text":"\n}","range":{"startLineNumber":65,"startColumn":1,"endLineNumber":65,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","epoch":602,"edits":[{"text":"\n","range":{"startLineNumber":66,"startColumn":1,"endLineNumber":66,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_91e0ff5b-3f8e-4584-83d9-e232e969c2aa","epoch":607,"edits":[{"text":"//! ATSAMD21 (Adafruit QT Py) bring-up helpers.\n//!\n//! Built from docs.rs API references:\n//! - atsamd-hal `sercom::i2c` (v0.21)\n//! - atsamd-hal `usb::UsbBus` (v0.21)\n//! - usbd-hid `HIDClass` output report APIs (v0.8)\n//!\n//! QT Py SAMD21 board notes:\n//! - STEMMA/Qwiic I2C pins are PA16/PA17 on this BSP mapping.\n//! - USB D-/D+ are PA24/PA25.\n\nuse atsamd_hal::{\n    clock::GenericClockController,\n    gpio::{PA16, PA17, Pins},\n    pac,\n    prelude::*,\n    sercom::{\n        i2c,\n        Sercom1,\n    },\n    time::Hertz,\n    usb::UsbBus,\n};\nuse usb_device::bus::UsbBusAllocator;\n\npub const I2C_TARGET_HZ: u32 = 400_000;\npub const QT_PY_SDA_PIN: &str = \"PA16\";\npub const QT_PY_SCL_PIN: &str = \"PA17\";\n\npub type QtPyI2cPads = i2c::PadsFromIds<Sercom1, PA16, PA17>;\npub type QtPyI2cConfig = i2c::Config<QtPyI2cPads>;\npub type QtPyI2c = i2c::I2c<QtPyI2cConfig>;\n\n/// Configure SERCOM1 I2C at 400kHz for QT Py's STEMMA/Qwiic pins.\npub fn init_qtpy_i2c(\n    pm: &pac::PM,\n    sercom1: pac::SERCOM1,\n    pins: Pins,\n    sercom_core_freq: Hertz,\n) -> QtPyI2c {\n    let pads = i2c::Pads::<Sercom1>::new(pins.pa16, pins.pa17);\n    i2c::Config::new(pm, sercom1, pads, sercom_core_freq)\n        .baud(I2C_TARGET_HZ.Hz())\n        .enable()\n}\n\n/// Configure USB bus allocator (PA24/PA25) for HID device classes.\npub fn init_usb_bus(\n    clocks: &mut GenericClockController,\n    pm: &mut pac::PM,\n    usb: pac::USB,\n    pins: Pins,\n) -> UsbBusAllocator<UsbBus> {\n    let gclk0 = clocks.gclk0();\n    let usb_clock = clocks.usb(&gclk0).expect(\"USB clock unavailable\");\n    UsbBusAllocator::new(UsbBus::new(&usb_clock, pm, pins.pa24, pins.pa25, usb))\n}\n\n/// One-place reminder for the runtime wiring inside your USB poll loop.\npub fn dispatch_hid_out_report<I2C>(\n    drv: &mut crate::drv2605l::Drv2605l<I2C>,\n    report: &[u8],\n) -> Result<(), crate::drv2605l::DispatchError<I2C::Error>>\nwhere\n    I2C: embedded_hal::i2c::I2c,\n{\n    crate::on_hid_output_report(drv, report)\n}\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":70,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_670e73e8-7055-44d2-a172-e3c97407fa56","epoch":611,"edits":[{"text":"//! ATSAMD21 (Adafruit QT Py) bring-up helpers.\r\n//!\r\n//! Built from docs.rs API references:\r\n//! - atsamd-hal `sercom::i2c` (v0.21)\r\n//! - atsamd-hal `usb::UsbBus` (v0.21)\r\n//! - usbd-hid `HIDClass` output report APIs (v0.8)\r\n//!\r\n//! QT Py SAMD21 board notes:\r\n//! - STEMMA/Qwiic I2C pins are PA16/PA17 on this BSP mapping.\r\n//! - USB D-/D+ are PA24/PA25.\r\n\r\nuse atsamd_hal::{\r\n    clock::GenericClockController,\r\n    gpio::{AnyPin, PA16, PA17, PA24, PA25},\n    pac,\r\n    prelude::*,\r\n    sercom::{\r\n        i2c,\r\n        Sercom1,\r\n    },\r\n    time::Hertz,\r\n    usb::UsbBus,\r\n};\r\nuse usb_device::bus::UsbBusAllocator;\r\n\r\npub const I2C_TARGET_HZ: u32 = 400_000;\r\npub const QT_PY_SDA_PIN: &str = \"PA16\";\r\npub const QT_PY_SCL_PIN: &str = \"PA17\";\r\n\r\npub type QtPyI2cPads = i2c::PadsFromIds<Sercom1, PA16, PA17>;\r\npub type QtPyI2cConfig = i2c::Config<QtPyI2cPads>;\r\npub type QtPyI2c = i2c::I2c<QtPyI2cConfig>;\r\n\npub const RAW_HID_REPORT_DESCRIPTOR: &[u8] = &[\n    0x06, 0x00, 0xff, // Usage Page (Vendor Defined)\n    0x09, 0x01, // Usage (0x01)\n    0xa1, 0x01, // Collection (Application)\n    0x15, 0x00, //   Logical Minimum (0)\n    0x26, 0xff, 0x00, //   Logical Maximum (255)\n    0x75, 0x08, //   Report Size (8)\n    0x95, 0x04, //   Report Count (4 bytes payload)\n    0x09, 0x01, //   Usage (0x01)\n    0x81, 0x02, //   Input (Data,Var,Abs)\n    0x95, 0x04, //   Report Count (4 bytes payload)\n    0x09, 0x01, //   Usage (0x01)\n    0x91, 0x02, //   Output (Data,Var,Abs)\n    0xc0, // End Collection\n];\n\r\n/// Configure SERCOM1 I2C at 400kHz for QT Py's STEMMA/Qwiic pins.\r\npub fn init_qtpy_i2c(\r\n    pm: &pac::PM,\r\n    sercom1: pac::SERCOM1,\r\n    sda: impl AnyPin<Id = PA16>,\n    scl: impl AnyPin<Id = PA17>,\n    sercom_core_freq: Hertz,\r\n) -> QtPyI2c {\r\n    let pads = i2c::Pads::<Sercom1>::new(sda, scl);\n    i2c::Config::new(pm, sercom1, pads, sercom_core_freq)\r\n        .baud(I2C_TARGET_HZ.Hz())\r\n        .enable()\r\n}\r\n\r\n/// Configure USB bus allocator (PA24/PA25) for HID device classes.\r\npub fn init_usb_bus(\r\n    clocks: &mut GenericClockController,\r\n    pm: &mut pac::PM,\r\n    usb: pac::USB,\r\n    dm: impl AnyPin<Id = PA24>,\n    dp: impl AnyPin<Id = PA25>,\n) -> UsbBusAllocator<UsbBus> {\r\n    let gclk0 = clocks.gclk0();\r\n    let usb_clock = clocks.usb(&gclk0).expect(\"USB clock unavailable\");\r\n    UsbBusAllocator::new(UsbBus::new(&usb_clock, pm, dm, dp, usb))\n}\r\n\r\n/// One-place reminder for the runtime wiring inside your USB poll loop.\r\npub fn dispatch_hid_out_report<I2C>(\r\n    drv: &mut crate::drv2605l::Drv2605l<I2C>,\r\n    report: &[u8],\r\n) -> Result<(), crate::drv2605l::DispatchError<I2C::Error>>\r\nwhere\r\n    I2C: embedded_hal::i2c::I2c,\r\n{\r\n    crate::on_hid_output_report(drv, report)\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":88,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs","scheme":"file"},"requestId":"request_670e73e8-7055-44d2-a172-e3c97407fa56","epoch":614,"edits":[{"text":"#![no_std]\r\n#![no_main]\r\n\r\nuse atsamd_hal::{\n    clock::GenericClockController,\n    gpio::Pins,\n    pac,\n};\nuse cortex_m_rt::entry;\r\nuse embedded_hal::i2c::I2c;\r\nuse haptics_protocol::{\n    command,\n    DEFAULT_USB_PID,\n    DEFAULT_USB_VID,\n    HID_REPORT_LEN,\n};\nuse panic_halt as _;\r\nuse usb_device::device::{\n    UsbDeviceBuilder,\n    UsbVidPid,\n};\nuse usbd_hid::hid_class::HIDClass;\n\r\nmod drv2605l;\r\nmod drv2605l_parser;\r\nmod platform_samd21;\r\nmod usb_hid;\r\n\r\npub(crate) fn handle_hid_report<I2C>(\r\n    drv: &mut drv2605l::Drv2605l<I2C>,\r\n    report: &[u8],\r\n) -> Result<(), drv2605l::DispatchError<I2C::Error>>\r\nwhere\r\n    I2C: I2c,\r\n{\r\n    let cmd = drv2605l_parser::parse_command(report).map_err(drv2605l::DispatchError::Parse)?;\r\n    drv.apply_command(cmd).map_err(drv2605l::DispatchError::I2c)\r\n}\r\n\r\n/// Call this from the USB HID output-report receive path.\r\npub(crate) fn on_hid_output_report<I2C>(\r\n    drv: &mut drv2605l::Drv2605l<I2C>,\r\n    report: &[u8],\r\n) -> Result<(), drv2605l::DispatchError<I2C::Error>>\r\nwhere\r\n    I2C: I2c,\r\n{\r\n    usb_hid::process_output_report(drv, report)\r\n}\r\n\r\n#[entry]\r\nfn main() -> ! {\r\n    let mut peripherals = match pac::Peripherals::take() {\n        Some(p) => p,\n        None => loop {\n            cortex_m::asm::nop();\n        },\n    };\n\n    let mut clocks = GenericClockController::with_internal_32kosc(\n        peripherals.GCLK,\n        &mut peripherals.PM,\n        &mut peripherals.SYSCTRL,\n        &mut peripherals.NVMCTRL,\n    );\n\n    let gclk0 = clocks.gclk0();\n    let sercom1_core = clocks\n        .sercom1_core(&gclk0)\n        .expect(\"SERCOM1 core clock unavailable\");\n\n    let pins = Pins::new(peripherals.PORT);\n\n    let i2c = platform_samd21::init_qtpy_i2c(\n        &peripherals.PM,\n        peripherals.SERCOM1,\n        pins.pa16,\n        pins.pa17,\n        sercom1_core.freq(),\n    );\n    let mut drv = drv2605l::Drv2605l::new(i2c);\n    let _ = drv.set_rtp_mode();\n\n    let usb_bus = platform_samd21::init_usb_bus(\n        &mut clocks,\n        &mut peripherals.PM,\n        peripherals.USB,\n        pins.pa24,\n        pins.pa25,\n    );\n\n    let mut hid = HIDClass::new(&usb_bus, platform_samd21::RAW_HID_REPORT_DESCRIPTOR, 1);\n    let mut usb_dev = UsbDeviceBuilder::new(\n        &usb_bus,\n        UsbVidPid(DEFAULT_USB_VID, DEFAULT_USB_PID),\n    )\n    .manufacturer(\"qt-py-haptics\")\n    .product(\"QT Py Haptics\")\n    .serial_number(\"qtpy-samd21\")\n    .device_class(0)\n    .build();\n\n    // Keep a parser smoke-check packet for fast sanity checks.\n    let sample_report = [command::STOP, 0, 0, 0];\r\n    let _ = drv2605l_parser::parse_command(&sample_report);\r\n\r\n    loop {\r\n        if usb_dev.poll(&mut [&mut hid]) {\n            let mut report = [0u8; HID_REPORT_LEN + 1];\n            if let Ok(count) = hid.pull_raw_output(&mut report) {\n                let _ = platform_samd21::dispatch_hid_out_report(&mut drv, &report[..count]);\n            }\n        }\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":117,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","scheme":"file"},"requestId":"request_0d1e6e8d-7dbf-467e-af2c-f0fbd5aa689f","epoch":618,"edits":[{"text":"[package]\r\nname = \"qtpy-samd21-fw\"\r\nversion = \"0.1.0\"\r\nedition.workspace = true\r\nlicense.workspace = true\r\nauthors.workspace = true\r\n\r\n[dependencies]\natsamd-hal = { version = \"0.21\", default-features = false, features = [\"samd21e\", \"usb\"] }\ncortex-m = \"0.7\"\r\ncortex-m-rt = \"0.7\"\r\nembedded-hal = \"1\"\r\nhaptics-protocol = { path = \"../../shared/haptics-protocol\", default-features = false }\r\npanic-halt = \"0.2\"\r\nusb-device = \"0.3\"\r\nusbd-hid = \"0.8\"\r\n\r\n[profile.release]\r\ncodegen-units = 1\r\nlto = true\r\nopt-level = \"s\"\r\npanic = \"abort\"\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":24,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":622,"edits":[{"text":"[workspace]\r\nresolver = \"2\"\r\nmembers = [\r\n  \"shared/haptics-protocol\",\r\n  \"host/hid-bridge\",\r\n  \"host/hid-cli\",\r\n  \"openvr-driver/rust-haptic-driver\",\r\n  \"firmware/qtpy-samd21\",\r\n]\r\ndefault-members = [\r\n  \"shared/haptics-protocol\",\r\n  \"host/hid-bridge\",\r\n  \"host/hid-cli\",\r\n  \"openvr-driver/rust-haptic-driver\",\r\n]\r\n\r\n[workspace.package]\r\nedition = \"2021\"\r\nauthors = [\"qt-py-haptics contributors\"]\r\nlicense = \"MIT OR Apache-2.0\"\r\n\r\n[workspace.dependencies]\r\nanyhow = \"1\"\r\nclap = { version = \"4\", features = [\"derive\"] }\r\nhidapi = \"2\"\r\nlog = \"0.4\"\r\nthiserror = \"2\"\r\n\n[profile.release]\ncodegen-units = 1\nlto = true\nopt-level = \"s\"\npanic = \"abort\"\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":35,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":625,"edits":[{"text":"[package]\r\nname = \"qtpy-samd21-fw\"\r\nversion = \"0.1.0\"\r\nedition.workspace = true\r\nlicense.workspace = true\r\nauthors.workspace = true\r\n\r\n[dependencies]\r\natsamd-hal = { version = \"0.21\", default-features = false, features = [\"samd21e\", \"usb\"] }\r\ncortex-m = \"0.7\"\r\ncortex-m-rt = \"0.7\"\r\nembedded-hal = \"1\"\r\nhaptics-protocol = { path = \"../../shared/haptics-protocol\", default-features = false }\r\npanic-halt = \"0.2\"\r\nusb-device = \"0.3\"\r\nusbd-hid = \"0.8\"\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":18,"endColumn":1}},{"text":"","range":{"startLineNumber":18,"startColumn":1,"endLineNumber":24,"endColumn":1}}]},{"type":"create","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\memory.x","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/memory.x","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/memory.x","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":626,"initialContent":"","telemetryInfo":{}},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\memory.x","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/memory.x","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/memory.x","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":627,"edits":[{"text":"/*","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":1,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\memory.x","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/memory.x","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/memory.x","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":628,"edits":[{"text":"\n * Adafruit QT Py SAMD21 (ATSAMD21E18) memory map for UF2 bootloader flashing.","range":{"startLineNumber":2,"startColumn":1,"endLineNumber":2,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\memory.x","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/memory.x","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/memory.x","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":629,"edits":[{"text":"\n *","range":{"startLineNumber":3,"startColumn":1,"endLineNumber":3,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\memory.x","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/memory.x","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/memory.x","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":630,"edits":[{"text":"\n * Bootloader region: 0x0000_0000 .. 0x0000_1FFF (8 KiB)","range":{"startLineNumber":4,"startColumn":1,"endLineNumber":4,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\memory.x","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/memory.x","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/memory.x","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":631,"edits":[{"text":"\n * Application region starts at 0x0000_2000.","range":{"startLineNumber":5,"startColumn":1,"endLineNumber":5,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\memory.x","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/memory.x","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/memory.x","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":632,"edits":[{"text":"\n */","range":{"startLineNumber":6,"startColumn":1,"endLineNumber":6,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\memory.x","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/memory.x","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/memory.x","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":633,"edits":[{"text":"\nMEMORY","range":{"startLineNumber":7,"startColumn":1,"endLineNumber":7,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\memory.x","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/memory.x","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/memory.x","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":634,"edits":[{"text":"\n{","range":{"startLineNumber":8,"startColumn":1,"endLineNumber":8,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\memory.x","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/memory.x","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/memory.x","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":635,"edits":[{"text":"\n  FLASH : ORIGIN = 0x00002000, LENGTH = 0x0003E000 /* 248 KiB */","range":{"startLineNumber":9,"startColumn":1,"endLineNumber":9,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\memory.x","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/memory.x","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/memory.x","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":636,"edits":[{"text":"\n  RAM   : ORIGIN = 0x20000000, LENGTH = 0x00008000 /* 32 KiB */","range":{"startLineNumber":10,"startColumn":1,"endLineNumber":10,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\memory.x","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/memory.x","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/memory.x","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":637,"edits":[{"text":"\n}","range":{"startLineNumber":11,"startColumn":1,"endLineNumber":11,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\memory.x","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/memory.x","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/memory.x","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":638,"edits":[{"text":"\n","range":{"startLineNumber":12,"startColumn":1,"endLineNumber":12,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\platform_samd21.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/platform_samd21.rs","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":641,"edits":[{"text":"//! ATSAMD21 (Adafruit QT Py) bring-up helpers.\r\n//!\r\n//! Built from docs.rs API references:\r\n//! - atsamd-hal `sercom::i2c` (v0.21)\r\n//! - atsamd-hal `usb::UsbBus` (v0.21)\r\n//! - usbd-hid `HIDClass` output report APIs (v0.8)\r\n//!\r\n//! QT Py SAMD21 board notes:\r\n//! - STEMMA/Qwiic I2C pins are PA16/PA17 on this BSP mapping.\r\n//! - USB D-/D+ are PA24/PA25.\r\n\r\nuse atsamd_hal::{\r\n    clock::GenericClockController,\r\n    gpio::{AnyPin, PA16, PA17, PA24, PA25},\r\n    pac,\r\n    prelude::*,\r\n    sercom::{\r\n        i2c,\r\n        Sercom1,\r\n    },\r\n    time::Hertz,\r\n    usb::UsbBus,\r\n};\r\nuse usb_device::bus::UsbBusAllocator;\r\n\r\npub const I2C_TARGET_HZ: u32 = 400_000;\r\npub const QT_PY_SDA_PIN: &str = \"PA16\";\r\npub const QT_PY_SCL_PIN: &str = \"PA17\";\r\n\r\npub type QtPyI2cPads = i2c::PadsFromIds<Sercom1, PA16, PA17>;\r\npub type QtPyI2cConfig = i2c::Config<QtPyI2cPads>;\r\npub type QtPyI2c = i2c::I2c<QtPyI2cConfig>;\r\n\r\npub const RAW_HID_REPORT_DESCRIPTOR: &[u8] = &[\r\n    0x06, 0x00, 0xff, // Usage Page (Vendor Defined)\r\n    0x09, 0x01, // Usage (0x01)\r\n    0xa1, 0x01, // Collection (Application)\r\n    0x15, 0x00, //   Logical Minimum (0)\r\n    0x26, 0xff, 0x00, //   Logical Maximum (255)\r\n    0x75, 0x08, //   Report Size (8)\r\n    0x95, 0x04, //   Report Count (4 bytes payload)\r\n    0x09, 0x01, //   Usage (0x01)\r\n    0x81, 0x02, //   Input (Data,Var,Abs)\r\n    0x95, 0x04, //   Report Count (4 bytes payload)\r\n    0x09, 0x01, //   Usage (0x01)\r\n    0x91, 0x02, //   Output (Data,Var,Abs)\r\n    0xc0, // End Collection\r\n];\r\n\r\n/// Configure SERCOM1 I2C at 400kHz for QT Py's STEMMA/Qwiic pins.\r\npub fn init_qtpy_i2c(\r\n    pm: &pac::Pm,\n    sercom1: pac::Sercom1,\n    sda: impl AnyPin<Id = PA16>,\r\n    scl: impl AnyPin<Id = PA17>,\r\n    sercom_core_freq: Hertz,\r\n) -> QtPyI2c {\r\n    let pads: QtPyI2cPads = i2c::Pads::new(sda, scl);\n    i2c::Config::new(pm, sercom1, pads, sercom_core_freq)\r\n        .baud(I2C_TARGET_HZ.Hz())\r\n        .enable()\r\n}\r\n\r\n/// Configure USB bus allocator (PA24/PA25) for HID device classes.\r\npub fn init_usb_bus(\r\n    clocks: &mut GenericClockController,\r\n    pm: &mut pac::Pm,\n    usb: pac::Usb,\n    dm: impl AnyPin<Id = PA24>,\r\n    dp: impl AnyPin<Id = PA25>,\r\n) -> UsbBusAllocator<UsbBus> {\r\n    let gclk0 = clocks.gclk0();\r\n    let usb_clock = clocks.usb(&gclk0).expect(\"USB clock unavailable\");\r\n    UsbBusAllocator::new(UsbBus::new(&usb_clock, pm, dm, dp, usb))\r\n}\r\n\r\n/// One-place reminder for the runtime wiring inside your USB poll loop.\r\npub fn dispatch_hid_out_report<I2C>(\r\n    drv: &mut crate::drv2605l::Drv2605l<I2C>,\r\n    report: &[u8],\r\n) -> Result<(), crate::drv2605l::DispatchError<I2C::Error>>\r\nwhere\r\n    I2C: embedded_hal::i2c::I2c,\r\n{\r\n    crate::on_hid_output_report(drv, report)\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":88,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":644,"edits":[{"text":"#![no_std]\r\n#![no_main]\r\n\r\nuse atsamd_hal::{\r\n    clock::GenericClockController,\r\n    gpio::Pins,\r\n    pac,\r\n};\r\nuse cortex_m_rt::entry;\r\nuse embedded_hal::i2c::I2c;\r\nuse haptics_protocol::{\r\n    command,\r\n    DEFAULT_USB_PID,\r\n    DEFAULT_USB_VID,\r\n    HID_REPORT_LEN,\r\n};\r\nuse panic_halt as _;\r\nuse usb_device::device::{\r\n    UsbDeviceBuilder,\r\n    UsbVidPid,\r\n};\r\nuse usbd_hid::hid_class::HIDClass;\r\n\r\nmod drv2605l;\r\nmod drv2605l_parser;\r\nmod platform_samd21;\r\nmod usb_hid;\r\n\r\npub(crate) fn handle_hid_report<I2C>(\r\n    drv: &mut drv2605l::Drv2605l<I2C>,\r\n    report: &[u8],\r\n) -> Result<(), drv2605l::DispatchError<I2C::Error>>\r\nwhere\r\n    I2C: I2c,\r\n{\r\n    let cmd = drv2605l_parser::parse_command(report).map_err(drv2605l::DispatchError::Parse)?;\r\n    drv.apply_command(cmd).map_err(drv2605l::DispatchError::I2c)\r\n}\r\n\r\n/// Call this from the USB HID output-report receive path.\r\npub(crate) fn on_hid_output_report<I2C>(\r\n    drv: &mut drv2605l::Drv2605l<I2C>,\r\n    report: &[u8],\r\n) -> Result<(), drv2605l::DispatchError<I2C::Error>>\r\nwhere\r\n    I2C: I2c,\r\n{\r\n    usb_hid::process_output_report(drv, report)\r\n}\r\n\r\n#[entry]\r\nfn main() -> ! {\r\n    let mut peripherals = match pac::Peripherals::take() {\r\n        Some(p) => p,\r\n        None => loop {\r\n            cortex_m::asm::nop();\r\n        },\r\n    };\r\n\r\n    let mut clocks = GenericClockController::with_internal_32kosc(\r\n        peripherals.gclk,\n        &mut peripherals.pm,\n        &mut peripherals.sysctrl,\n        &mut peripherals.nvmctrl,\n    );\r\n\r\n    let gclk0 = clocks.gclk0();\r\n    let sercom1_core = clocks\r\n        .sercom1_core(&gclk0)\r\n        .expect(\"SERCOM1 core clock unavailable\");\r\n\r\n    let pins = Pins::new(peripherals.port);\n\r\n    let i2c = platform_samd21::init_qtpy_i2c(\r\n        &peripherals.PM,\r\n        peripherals.sercom1,\n        pins.pa16,\r\n        pins.pa17,\r\n        sercom1_core.freq(),\r\n    );\r\n    let mut drv = drv2605l::Drv2605l::new(i2c);\r\n    let _ = drv.set_rtp_mode();\r\n\r\n    let usb_bus = platform_samd21::init_usb_bus(\r\n        &mut clocks,\r\n        &mut peripherals.pm,\n        peripherals.usb,\n        pins.pa24,\r\n        pins.pa25,\r\n    );\r\n\r\n    let mut hid = HIDClass::new(&usb_bus, platform_samd21::RAW_HID_REPORT_DESCRIPTOR, 1);\r\n    let mut usb_dev = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(DEFAULT_USB_VID, DEFAULT_USB_PID))\n        .device_class(0)\n        .build();\n\r\n    // Keep a parser smoke-check packet for fast sanity checks.\r\n    let sample_report = [command::STOP, 0, 0, 0];\r\n    let _ = drv2605l_parser::parse_command(&sample_report);\r\n\r\n    loop {\r\n        if usb_dev.poll(&mut [&mut hid]) {\r\n            let mut report = [0u8; HID_REPORT_LEN + 1];\r\n            if let Ok(count) = hid.pull_raw_output(&mut report) {\r\n                let _ = platform_samd21::dispatch_hid_out_report(&mut drv, &report[..count]);\r\n            }\r\n        }\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":111,"endColumn":1}},{"text":"","range":{"startLineNumber":111,"startColumn":1,"endLineNumber":117,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\main.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/main.rs","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":646,"edits":[{"text":"#![no_std]\r\n#![no_main]\r\n\r\nuse atsamd_hal::{\r\n    clock::GenericClockController,\r\n    gpio::Pins,\r\n    pac,\r\n};\r\nuse cortex_m_rt::entry;\r\nuse embedded_hal::i2c::I2c;\r\nuse haptics_protocol::{\r\n    command,\r\n    DEFAULT_USB_PID,\r\n    DEFAULT_USB_VID,\r\n    HID_REPORT_LEN,\r\n};\r\nuse panic_halt as _;\r\nuse usb_device::device::{\r\n    UsbDeviceBuilder,\r\n    UsbVidPid,\r\n};\r\nuse usbd_hid::hid_class::HIDClass;\r\n\r\nmod drv2605l;\r\nmod drv2605l_parser;\r\nmod platform_samd21;\r\nmod usb_hid;\r\n\r\npub(crate) fn handle_hid_report<I2C>(\r\n    drv: &mut drv2605l::Drv2605l<I2C>,\r\n    report: &[u8],\r\n) -> Result<(), drv2605l::DispatchError<I2C::Error>>\r\nwhere\r\n    I2C: I2c,\r\n{\r\n    let cmd = drv2605l_parser::parse_command(report).map_err(drv2605l::DispatchError::Parse)?;\r\n    drv.apply_command(cmd).map_err(drv2605l::DispatchError::I2c)\r\n}\r\n\r\n/// Call this from the USB HID output-report receive path.\r\npub(crate) fn on_hid_output_report<I2C>(\r\n    drv: &mut drv2605l::Drv2605l<I2C>,\r\n    report: &[u8],\r\n) -> Result<(), drv2605l::DispatchError<I2C::Error>>\r\nwhere\r\n    I2C: I2c,\r\n{\r\n    usb_hid::process_output_report(drv, report)\r\n}\r\n\r\n#[entry]\r\nfn main() -> ! {\r\n    let mut peripherals = match pac::Peripherals::take() {\r\n        Some(p) => p,\r\n        None => loop {\r\n            cortex_m::asm::nop();\r\n        },\r\n    };\r\n\r\n    let mut clocks = GenericClockController::with_internal_32kosc(\r\n        peripherals.gclk,\r\n        &mut peripherals.pm,\r\n        &mut peripherals.sysctrl,\r\n        &mut peripherals.nvmctrl,\r\n    );\r\n\r\n    let gclk0 = clocks.gclk0();\r\n    let sercom1_core = clocks\r\n        .sercom1_core(&gclk0)\r\n        .expect(\"SERCOM1 core clock unavailable\");\r\n\r\n    let pins = Pins::new(peripherals.port);\r\n\r\n    let i2c = platform_samd21::init_qtpy_i2c(\r\n        &peripherals.pm,\n        peripherals.sercom1,\r\n        pins.pa16,\r\n        pins.pa17,\r\n        sercom1_core.freq(),\r\n    );\r\n    let mut drv = drv2605l::Drv2605l::new(i2c);\r\n    let _ = drv.set_rtp_mode();\r\n\r\n    let usb_bus = platform_samd21::init_usb_bus(\r\n        &mut clocks,\r\n        &mut peripherals.pm,\r\n        peripherals.usb,\r\n        pins.pa24,\r\n        pins.pa25,\r\n    );\r\n\r\n    let mut hid = HIDClass::new(&usb_bus, platform_samd21::RAW_HID_REPORT_DESCRIPTOR, 1);\r\n    let mut usb_dev = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(DEFAULT_USB_VID, DEFAULT_USB_PID))\r\n        .device_class(0)\r\n        .build();\r\n\r\n    // Keep a parser smoke-check packet for fast sanity checks.\r\n    let sample_report = [command::STOP, 0, 0, 0];\r\n    let _ = drv2605l_parser::parse_command(&sample_report);\r\n\r\n    loop {\r\n        if usb_dev.poll(&mut [&mut hid]) {\r\n            let mut report = [0u8; HID_REPORT_LEN + 1];\r\n            if let Ok(count) = hid.pull_raw_output(&mut report) {\r\n                let _ = platform_samd21::dispatch_hid_out_report(&mut drv, &report[..count]);\r\n            }\r\n        }\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":111,"endColumn":1}}]},{"type":"create","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_firmware.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_firmware.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_firmware.ps1","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":647,"initialContent":"","telemetryInfo":{}},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_firmware.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_firmware.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_firmware.ps1","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":648,"edits":[{"text":"param(","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":1,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_firmware.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_firmware.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_firmware.ps1","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":649,"edits":[{"text":"\n    [ValidateSet(\"dev\", \"release\")]","range":{"startLineNumber":2,"startColumn":1,"endLineNumber":2,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_firmware.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_firmware.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_firmware.ps1","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":650,"edits":[{"text":"\n    [string]$Profile = \"release\"","range":{"startLineNumber":3,"startColumn":1,"endLineNumber":3,"endColumn":1}}]},{"type":"create","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\host_hid_smoke_test.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/host_hid_smoke_test.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/host_hid_smoke_test.ps1","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":651,"initialContent":"","telemetryInfo":{}},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\host_hid_smoke_test.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/host_hid_smoke_test.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/host_hid_smoke_test.ps1","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":652,"edits":[{"text":"param(","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":1,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_firmware.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_firmware.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_firmware.ps1","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":653,"edits":[{"text":"\n)","range":{"startLineNumber":4,"startColumn":1,"endLineNumber":4,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\host_hid_smoke_test.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/host_hid_smoke_test.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/host_hid_smoke_test.ps1","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":654,"edits":[{"text":"\n    [UInt16]$Vid = 0x35bd,","range":{"startLineNumber":2,"startColumn":1,"endLineNumber":2,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_firmware.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_firmware.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_firmware.ps1","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":655,"edits":[{"text":"\n","range":{"startLineNumber":5,"startColumn":1,"endLineNumber":5,"endColumn":1}}]},{"type":"create","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\FLASH_AND_TEST.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":656,"initialContent":"","telemetryInfo":{}},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\FLASH_AND_TEST.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":657,"edits":[{"text":"# QT Py SAMD21 Firmware: Flash + Smoke Test","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":1,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\host_hid_smoke_test.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/host_hid_smoke_test.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/host_hid_smoke_test.ps1","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":658,"edits":[{"text":"\n    [UInt16]$Pid = 0xfe01,","range":{"startLineNumber":3,"startColumn":1,"endLineNumber":3,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_firmware.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_firmware.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_firmware.ps1","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":659,"edits":[{"text":"\n$ErrorActionPreference = \"Stop\"","range":{"startLineNumber":6,"startColumn":1,"endLineNumber":6,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\FLASH_AND_TEST.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":660,"edits":[{"text":"\n","range":{"startLineNumber":2,"startColumn":1,"endLineNumber":2,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\host_hid_smoke_test.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/host_hid_smoke_test.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/host_hid_smoke_test.ps1","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":661,"edits":[{"text":"\n    [byte]$Command = 0x01,","range":{"startLineNumber":4,"startColumn":1,"endLineNumber":4,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_firmware.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_firmware.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_firmware.ps1","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":662,"edits":[{"text":"\n$root = \"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\"","range":{"startLineNumber":7,"startColumn":1,"endLineNumber":7,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\FLASH_AND_TEST.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":663,"edits":[{"text":"\n## Current status","range":{"startLineNumber":3,"startColumn":1,"endLineNumber":3,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\host_hid_smoke_test.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/host_hid_smoke_test.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/host_hid_smoke_test.ps1","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":664,"edits":[{"text":"\n    [byte]$Intensity = 64","range":{"startLineNumber":5,"startColumn":1,"endLineNumber":5,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_firmware.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_firmware.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_firmware.ps1","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":665,"edits":[{"text":"\nSet-Location $root","range":{"startLineNumber":8,"startColumn":1,"endLineNumber":8,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\FLASH_AND_TEST.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":666,"edits":[{"text":"\n","range":{"startLineNumber":4,"startColumn":1,"endLineNumber":4,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\host_hid_smoke_test.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/host_hid_smoke_test.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/host_hid_smoke_test.ps1","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":667,"edits":[{"text":"\n)","range":{"startLineNumber":6,"startColumn":1,"endLineNumber":6,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_firmware.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_firmware.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_firmware.ps1","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":668,"edits":[{"text":"\n","range":{"startLineNumber":9,"startColumn":1,"endLineNumber":9,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\FLASH_AND_TEST.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":669,"edits":[{"text":"\nThe firmware now builds for `thumbv6m-none-eabi` and includes:","range":{"startLineNumber":5,"startColumn":1,"endLineNumber":5,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\host_hid_smoke_test.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/host_hid_smoke_test.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/host_hid_smoke_test.ps1","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":670,"edits":[{"text":"\n","range":{"startLineNumber":7,"startColumn":1,"endLineNumber":7,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_firmware.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_firmware.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_firmware.ps1","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":671,"edits":[{"text":"\n$modeArgs = @()","range":{"startLineNumber":10,"startColumn":1,"endLineNumber":10,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\FLASH_AND_TEST.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":672,"edits":[{"text":"\n- USB HID OUT report polling","range":{"startLineNumber":6,"startColumn":1,"endLineNumber":6,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\host_hid_smoke_test.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/host_hid_smoke_test.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/host_hid_smoke_test.ps1","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":673,"edits":[{"text":"\n$ErrorActionPreference = \"Stop\"","range":{"startLineNumber":8,"startColumn":1,"endLineNumber":8,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_firmware.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_firmware.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_firmware.ps1","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":674,"edits":[{"text":"\nif ($Profile -eq \"release\") {","range":{"startLineNumber":11,"startColumn":1,"endLineNumber":11,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\FLASH_AND_TEST.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":675,"edits":[{"text":"\n- HID packet parser","range":{"startLineNumber":7,"startColumn":1,"endLineNumber":7,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\host_hid_smoke_test.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/host_hid_smoke_test.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/host_hid_smoke_test.ps1","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":676,"edits":[{"text":"\n$root = \"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\"","range":{"startLineNumber":9,"startColumn":1,"endLineNumber":9,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_firmware.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_firmware.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_firmware.ps1","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":677,"edits":[{"text":"\n    $modeArgs += \"--release\"","range":{"startLineNumber":12,"startColumn":1,"endLineNumber":12,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\FLASH_AND_TEST.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":678,"edits":[{"text":"\n- DRV2605L command dispatch over I2C","range":{"startLineNumber":8,"startColumn":1,"endLineNumber":8,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\host_hid_smoke_test.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/host_hid_smoke_test.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/host_hid_smoke_test.ps1","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":679,"edits":[{"text":"\nSet-Location $root","range":{"startLineNumber":10,"startColumn":1,"endLineNumber":10,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_firmware.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_firmware.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_firmware.ps1","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":680,"edits":[{"text":"\n}","range":{"startLineNumber":13,"startColumn":1,"endLineNumber":13,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\FLASH_AND_TEST.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":681,"edits":[{"text":"\n","range":{"startLineNumber":9,"startColumn":1,"endLineNumber":9,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\host_hid_smoke_test.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/host_hid_smoke_test.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/host_hid_smoke_test.ps1","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":682,"edits":[{"text":"\n","range":{"startLineNumber":11,"startColumn":1,"endLineNumber":11,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_firmware.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_firmware.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_firmware.ps1","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":683,"edits":[{"text":"\n","range":{"startLineNumber":14,"startColumn":1,"endLineNumber":14,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\FLASH_AND_TEST.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":684,"edits":[{"text":"\n## Build","range":{"startLineNumber":10,"startColumn":1,"endLineNumber":10,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\host_hid_smoke_test.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/host_hid_smoke_test.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/host_hid_smoke_test.ps1","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":685,"edits":[{"text":"\ncargo run -p hid-cli -- --vid $Vid --pid $Pid --cmd $Command --intensity $Intensity","range":{"startLineNumber":12,"startColumn":1,"endLineNumber":12,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_firmware.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_firmware.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_firmware.ps1","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":686,"edits":[{"text":"\ncargo build -p qtpy-samd21-fw --target thumbv6m-none-eabi @modeArgs","range":{"startLineNumber":15,"startColumn":1,"endLineNumber":15,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\FLASH_AND_TEST.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":687,"edits":[{"text":"\n","range":{"startLineNumber":11,"startColumn":1,"endLineNumber":11,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\host_hid_smoke_test.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/host_hid_smoke_test.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/host_hid_smoke_test.ps1","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":688,"edits":[{"text":"\n","range":{"startLineNumber":13,"startColumn":1,"endLineNumber":13,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_firmware.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_firmware.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_firmware.ps1","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":689,"edits":[{"text":"\nWrite-Host \"Firmware build complete ($Profile).\"","range":{"startLineNumber":16,"startColumn":1,"endLineNumber":16,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\FLASH_AND_TEST.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":690,"edits":[{"text":"\nUse:","range":{"startLineNumber":12,"startColumn":1,"endLineNumber":12,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_firmware.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_firmware.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_firmware.ps1","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":691,"edits":[{"text":"\n","range":{"startLineNumber":17,"startColumn":1,"endLineNumber":17,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\FLASH_AND_TEST.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":692,"edits":[{"text":"\n- `tools/scripts/build_firmware.ps1`","range":{"startLineNumber":13,"startColumn":1,"endLineNumber":13,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\FLASH_AND_TEST.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":693,"edits":[{"text":"\n","range":{"startLineNumber":14,"startColumn":1,"endLineNumber":14,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\FLASH_AND_TEST.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":694,"edits":[{"text":"\n## Flashing options","range":{"startLineNumber":15,"startColumn":1,"endLineNumber":15,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\FLASH_AND_TEST.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":695,"edits":[{"text":"\n","range":{"startLineNumber":16,"startColumn":1,"endLineNumber":16,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\FLASH_AND_TEST.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":696,"edits":[{"text":"\n### Option A (recommended for convenience): UF2 bootloader drag-and-drop","range":{"startLineNumber":17,"startColumn":1,"endLineNumber":17,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\FLASH_AND_TEST.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":697,"edits":[{"text":"\n","range":{"startLineNumber":18,"startColumn":1,"endLineNumber":18,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\FLASH_AND_TEST.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":698,"edits":[{"text":"\n1. Build release firmware.","range":{"startLineNumber":19,"startColumn":1,"endLineNumber":19,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\FLASH_AND_TEST.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":699,"edits":[{"text":"\n2. Convert ELF to UF2 (requires `cargo-binutils` + `llvm-tools` + `uf2conv.py` or equivalent).","range":{"startLineNumber":20,"startColumn":1,"endLineNumber":20,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\FLASH_AND_TEST.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":700,"edits":[{"text":"\n3. Double-tap reset on QT Py to mount `QTPYBOOT`.","range":{"startLineNumber":21,"startColumn":1,"endLineNumber":21,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\FLASH_AND_TEST.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":701,"edits":[{"text":"\n4. Copy UF2 file to the mounted drive.","range":{"startLineNumber":22,"startColumn":1,"endLineNumber":22,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\FLASH_AND_TEST.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":702,"edits":[{"text":"\n","range":{"startLineNumber":23,"startColumn":1,"endLineNumber":23,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\FLASH_AND_TEST.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":703,"edits":[{"text":"\n> Note: This repository does not yet include a pinned UF2 conversion tool script.","range":{"startLineNumber":24,"startColumn":1,"endLineNumber":24,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\FLASH_AND_TEST.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":704,"edits":[{"text":"\n","range":{"startLineNumber":25,"startColumn":1,"endLineNumber":25,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\FLASH_AND_TEST.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":705,"edits":[{"text":"\n### Option B: SWD + probe-rs","range":{"startLineNumber":26,"startColumn":1,"endLineNumber":26,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\FLASH_AND_TEST.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":706,"edits":[{"text":"\n","range":{"startLineNumber":27,"startColumn":1,"endLineNumber":27,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\FLASH_AND_TEST.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":707,"edits":[{"text":"\nIf you have SWD access, flash directly with probe tooling.","range":{"startLineNumber":28,"startColumn":1,"endLineNumber":28,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\FLASH_AND_TEST.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":708,"edits":[{"text":"\n","range":{"startLineNumber":29,"startColumn":1,"endLineNumber":29,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\FLASH_AND_TEST.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":709,"edits":[{"text":"\n## Host smoke test","range":{"startLineNumber":30,"startColumn":1,"endLineNumber":30,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\FLASH_AND_TEST.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":710,"edits":[{"text":"\n","range":{"startLineNumber":31,"startColumn":1,"endLineNumber":31,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\FLASH_AND_TEST.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":711,"edits":[{"text":"\nAfter board reconnects as USB HID, run:","range":{"startLineNumber":32,"startColumn":1,"endLineNumber":32,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\FLASH_AND_TEST.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":712,"edits":[{"text":"\n- `tools/scripts/host_hid_smoke_test.ps1`","range":{"startLineNumber":33,"startColumn":1,"endLineNumber":33,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\FLASH_AND_TEST.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":713,"edits":[{"text":"\n","range":{"startLineNumber":34,"startColumn":1,"endLineNumber":34,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\FLASH_AND_TEST.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":714,"edits":[{"text":"\nDefault smoke packet:","range":{"startLineNumber":35,"startColumn":1,"endLineNumber":35,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\FLASH_AND_TEST.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":715,"edits":[{"text":"\n- Command `0x01` (`SET_INTENSITY`)","range":{"startLineNumber":36,"startColumn":1,"endLineNumber":36,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\FLASH_AND_TEST.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":716,"edits":[{"text":"\n- Intensity `64`","range":{"startLineNumber":37,"startColumn":1,"endLineNumber":37,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\FLASH_AND_TEST.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":717,"edits":[{"text":"\n- VID/PID `0x35bd` / `0xfe01`","range":{"startLineNumber":38,"startColumn":1,"endLineNumber":38,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\FLASH_AND_TEST.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":718,"edits":[{"text":"\n","range":{"startLineNumber":39,"startColumn":1,"endLineNumber":39,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\FLASH_AND_TEST.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":719,"edits":[{"text":"\n## Next recommended firmware increments","range":{"startLineNumber":40,"startColumn":1,"endLineNumber":40,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\FLASH_AND_TEST.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":720,"edits":[{"text":"\n","range":{"startLineNumber":41,"startColumn":1,"endLineNumber":41,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\FLASH_AND_TEST.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":721,"edits":[{"text":"\n1. DRV2605L init sequence (mode exit standby, optional calibration)","range":{"startLineNumber":42,"startColumn":1,"endLineNumber":42,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\FLASH_AND_TEST.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":722,"edits":[{"text":"\n2. Better error handling/recovery for I2C bus faults","range":{"startLineNumber":43,"startColumn":1,"endLineNumber":43,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\FLASH_AND_TEST.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":723,"edits":[{"text":"\n3. Optional HID IN status report path (ack/error counters)","range":{"startLineNumber":44,"startColumn":1,"endLineNumber":44,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\FLASH_AND_TEST.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":724,"edits":[{"text":"\n4. Real waveform/time semantics for ROM effect + duration handling","range":{"startLineNumber":45,"startColumn":1,"endLineNumber":45,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\FLASH_AND_TEST.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","scheme":"file"},"requestId":"request_7d935091-4101-48a6-9ae5-52e365038840","epoch":725,"edits":[{"text":"\n","range":{"startLineNumber":46,"startColumn":1,"endLineNumber":46,"endColumn":1}}]},{"type":"create","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":727,"initialContent":"","telemetryInfo":{}},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":728,"edits":[{"text":"param(","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":1,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":729,"edits":[{"text":"\n    [ValidateSet(\"dev\", \"release\")]","range":{"startLineNumber":2,"startColumn":1,"endLineNumber":2,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":730,"edits":[{"text":"\n    [string]$Profile = \"release\",","range":{"startLineNumber":3,"startColumn":1,"endLineNumber":3,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":731,"edits":[{"text":"\n    [string]$FamilyId = \"0x68ed2b88\",","range":{"startLineNumber":4,"startColumn":1,"endLineNumber":4,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":732,"edits":[{"text":"\n    [string]$BaseAddress = \"0x2000\",","range":{"startLineNumber":5,"startColumn":1,"endLineNumber":5,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":733,"edits":[{"text":"\n    [switch]$InstallTooling,","range":{"startLineNumber":6,"startColumn":1,"endLineNumber":6,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":734,"edits":[{"text":"\n    [switch]$Deploy,","range":{"startLineNumber":7,"startColumn":1,"endLineNumber":7,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":735,"edits":[{"text":"\n    [string]$DriveRoot","range":{"startLineNumber":8,"startColumn":1,"endLineNumber":8,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":736,"edits":[{"text":"\n)","range":{"startLineNumber":9,"startColumn":1,"endLineNumber":9,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":737,"edits":[{"text":"\n","range":{"startLineNumber":10,"startColumn":1,"endLineNumber":10,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":738,"edits":[{"text":"\n$ErrorActionPreference = \"Stop\"","range":{"startLineNumber":11,"startColumn":1,"endLineNumber":11,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":739,"edits":[{"text":"\n$root = \"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\"","range":{"startLineNumber":12,"startColumn":1,"endLineNumber":12,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":740,"edits":[{"text":"\nSet-Location $root","range":{"startLineNumber":13,"startColumn":1,"endLineNumber":13,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":741,"edits":[{"text":"\n","range":{"startLineNumber":14,"startColumn":1,"endLineNumber":14,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":742,"edits":[{"text":"\nif ($InstallTooling) {","range":{"startLineNumber":15,"startColumn":1,"endLineNumber":15,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":743,"edits":[{"text":"\n    rustup component add llvm-tools","range":{"startLineNumber":16,"startColumn":1,"endLineNumber":16,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":744,"edits":[{"text":"\n    cargo install cargo-binutils","range":{"startLineNumber":17,"startColumn":1,"endLineNumber":17,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":745,"edits":[{"text":"\n}","range":{"startLineNumber":18,"startColumn":1,"endLineNumber":18,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":746,"edits":[{"text":"\n","range":{"startLineNumber":19,"startColumn":1,"endLineNumber":19,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":747,"edits":[{"text":"\nif (-not (Get-Command cargo-objcopy -ErrorAction SilentlyContinue)) {","range":{"startLineNumber":20,"startColumn":1,"endLineNumber":20,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":748,"edits":[{"text":"\n    throw \"cargo-objcopy is not installed. Run this script with -InstallTooling once.\"","range":{"startLineNumber":21,"startColumn":1,"endLineNumber":21,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":749,"edits":[{"text":"\n}","range":{"startLineNumber":22,"startColumn":1,"endLineNumber":22,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":750,"edits":[{"text":"\n","range":{"startLineNumber":23,"startColumn":1,"endLineNumber":23,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":751,"edits":[{"text":"\nif (-not (Get-Command python -ErrorAction SilentlyContinue)) {","range":{"startLineNumber":24,"startColumn":1,"endLineNumber":24,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":752,"edits":[{"text":"\n    throw \"python is required for uf2conv.py\"","range":{"startLineNumber":25,"startColumn":1,"endLineNumber":25,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":753,"edits":[{"text":"\n}","range":{"startLineNumber":26,"startColumn":1,"endLineNumber":26,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":754,"edits":[{"text":"\n","range":{"startLineNumber":27,"startColumn":1,"endLineNumber":27,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":755,"edits":[{"text":"\n$uf2ConvPath = Join-Path $root \"tools/uf2/uf2conv.py\"","range":{"startLineNumber":28,"startColumn":1,"endLineNumber":28,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":756,"edits":[{"text":"\nif (-not (Test-Path $uf2ConvPath)) {","range":{"startLineNumber":29,"startColumn":1,"endLineNumber":29,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":757,"edits":[{"text":"\n    $url = \"https://raw.githubusercontent.com/microsoft/uf2/master/utils/uf2conv.py\"","range":{"startLineNumber":30,"startColumn":1,"endLineNumber":30,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":758,"edits":[{"text":"\n    Invoke-WebRequest -Uri $url -OutFile $uf2ConvPath","range":{"startLineNumber":31,"startColumn":1,"endLineNumber":31,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":759,"edits":[{"text":"\n}","range":{"startLineNumber":32,"startColumn":1,"endLineNumber":32,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":760,"edits":[{"text":"\n","range":{"startLineNumber":33,"startColumn":1,"endLineNumber":33,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":761,"edits":[{"text":"\n$cargoArgs = @(\"build\", \"-p\", \"qtpy-samd21-fw\", \"--target\", \"thumbv6m-none-eabi\")","range":{"startLineNumber":34,"startColumn":1,"endLineNumber":34,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":762,"edits":[{"text":"\nif ($Profile -eq \"release\") {","range":{"startLineNumber":35,"startColumn":1,"endLineNumber":35,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":763,"edits":[{"text":"\n    $cargoArgs += \"--release\"","range":{"startLineNumber":36,"startColumn":1,"endLineNumber":36,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":764,"edits":[{"text":"\n}","range":{"startLineNumber":37,"startColumn":1,"endLineNumber":37,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":765,"edits":[{"text":"\n","range":{"startLineNumber":38,"startColumn":1,"endLineNumber":38,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":766,"edits":[{"text":"\n& cargo @cargoArgs","range":{"startLineNumber":39,"startColumn":1,"endLineNumber":39,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":767,"edits":[{"text":"\n","range":{"startLineNumber":40,"startColumn":1,"endLineNumber":40,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":768,"edits":[{"text":"\n$targetDir = Join-Path $root (\"target/thumbv6m-none-eabi/{0}\" -f $Profile)","range":{"startLineNumber":41,"startColumn":1,"endLineNumber":41,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":769,"edits":[{"text":"\n$elf = Join-Path $targetDir \"qtpy-samd21-fw\"","range":{"startLineNumber":42,"startColumn":1,"endLineNumber":42,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":770,"edits":[{"text":"\nif (-not (Test-Path $elf)) {","range":{"startLineNumber":43,"startColumn":1,"endLineNumber":43,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":771,"edits":[{"text":"\n    throw \"Expected ELF not found: $elf\"","range":{"startLineNumber":44,"startColumn":1,"endLineNumber":44,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":772,"edits":[{"text":"\n}","range":{"startLineNumber":45,"startColumn":1,"endLineNumber":45,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":773,"edits":[{"text":"\n","range":{"startLineNumber":46,"startColumn":1,"endLineNumber":46,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":774,"edits":[{"text":"\n$outDir = Join-Path $root \"build/firmware\"","range":{"startLineNumber":47,"startColumn":1,"endLineNumber":47,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":775,"edits":[{"text":"\nNew-Item -ItemType Directory -Path $outDir -Force | Out-Null","range":{"startLineNumber":48,"startColumn":1,"endLineNumber":48,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":776,"edits":[{"text":"\n$binPath = Join-Path $outDir \"qtpy-samd21-fw.bin\"","range":{"startLineNumber":49,"startColumn":1,"endLineNumber":49,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":777,"edits":[{"text":"\n$uf2Path = Join-Path $outDir \"qtpy-samd21-fw.uf2\"","range":{"startLineNumber":50,"startColumn":1,"endLineNumber":50,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":778,"edits":[{"text":"\n","range":{"startLineNumber":51,"startColumn":1,"endLineNumber":51,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":779,"edits":[{"text":"\n$modeArgs = @()","range":{"startLineNumber":52,"startColumn":1,"endLineNumber":52,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":780,"edits":[{"text":"\nif ($Profile -eq \"release\") {","range":{"startLineNumber":53,"startColumn":1,"endLineNumber":53,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":781,"edits":[{"text":"\n    $modeArgs += \"--release\"","range":{"startLineNumber":54,"startColumn":1,"endLineNumber":54,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":782,"edits":[{"text":"\n}","range":{"startLineNumber":55,"startColumn":1,"endLineNumber":55,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":783,"edits":[{"text":"\n","range":{"startLineNumber":56,"startColumn":1,"endLineNumber":56,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":784,"edits":[{"text":"\n& cargo objcopy -p qtpy-samd21-fw --target thumbv6m-none-eabi @modeArgs -- -O binary $binPath","range":{"startLineNumber":57,"startColumn":1,"endLineNumber":57,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":785,"edits":[{"text":"\n","range":{"startLineNumber":58,"startColumn":1,"endLineNumber":58,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":786,"edits":[{"text":"\n& python $uf2ConvPath $binPath --base $BaseAddress --family $FamilyId --convert --output $uf2Path","range":{"startLineNumber":59,"startColumn":1,"endLineNumber":59,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":787,"edits":[{"text":"\n","range":{"startLineNumber":60,"startColumn":1,"endLineNumber":60,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":788,"edits":[{"text":"\nWrite-Host \"BIN: $binPath\"","range":{"startLineNumber":61,"startColumn":1,"endLineNumber":61,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":789,"edits":[{"text":"\nWrite-Host \"UF2: $uf2Path\"","range":{"startLineNumber":62,"startColumn":1,"endLineNumber":62,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":790,"edits":[{"text":"\n","range":{"startLineNumber":63,"startColumn":1,"endLineNumber":63,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":791,"edits":[{"text":"\nif ($Deploy) {","range":{"startLineNumber":64,"startColumn":1,"endLineNumber":64,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":792,"edits":[{"text":"\n    $targetDrive = $DriveRoot","range":{"startLineNumber":65,"startColumn":1,"endLineNumber":65,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":793,"edits":[{"text":"\n","range":{"startLineNumber":66,"startColumn":1,"endLineNumber":66,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":794,"edits":[{"text":"\n    if (-not $targetDrive) {","range":{"startLineNumber":67,"startColumn":1,"endLineNumber":67,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":795,"edits":[{"text":"\n        $candidates = Get-PSDrive -PSProvider FileSystem |","range":{"startLineNumber":68,"startColumn":1,"endLineNumber":68,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":796,"edits":[{"text":"\n            Where-Object { Test-Path (Join-Path $_.Root \"INFO_UF2.TXT\") } |","range":{"startLineNumber":69,"startColumn":1,"endLineNumber":69,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":797,"edits":[{"text":"\n            Select-Object -ExpandProperty Root","range":{"startLineNumber":70,"startColumn":1,"endLineNumber":70,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":798,"edits":[{"text":"\n","range":{"startLineNumber":71,"startColumn":1,"endLineNumber":71,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":799,"edits":[{"text":"\n        if ($candidates.Count -eq 1) {","range":{"startLineNumber":72,"startColumn":1,"endLineNumber":72,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":800,"edits":[{"text":"\n            $targetDrive = $candidates[0]","range":{"startLineNumber":73,"startColumn":1,"endLineNumber":73,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":801,"edits":[{"text":"\n        }","range":{"startLineNumber":74,"startColumn":1,"endLineNumber":74,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":802,"edits":[{"text":"\n        elseif ($candidates.Count -gt 1) {","range":{"startLineNumber":75,"startColumn":1,"endLineNumber":75,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":803,"edits":[{"text":"\n            throw \"Multiple UF2 drives found. Pass -DriveRoot explicitly.\"","range":{"startLineNumber":76,"startColumn":1,"endLineNumber":76,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":804,"edits":[{"text":"\n        }","range":{"startLineNumber":77,"startColumn":1,"endLineNumber":77,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":805,"edits":[{"text":"\n        else {","range":{"startLineNumber":78,"startColumn":1,"endLineNumber":78,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":806,"edits":[{"text":"\n            throw \"No UF2 drive found. Put QT Py into bootloader mode and retry.\"","range":{"startLineNumber":79,"startColumn":1,"endLineNumber":79,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":807,"edits":[{"text":"\n        }","range":{"startLineNumber":80,"startColumn":1,"endLineNumber":80,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":808,"edits":[{"text":"\n    }","range":{"startLineNumber":81,"startColumn":1,"endLineNumber":81,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":809,"edits":[{"text":"\n","range":{"startLineNumber":82,"startColumn":1,"endLineNumber":82,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":810,"edits":[{"text":"\n    Copy-Item -Path $uf2Path -Destination (Join-Path $targetDrive \"qtpy-samd21-fw.uf2\") -Force","range":{"startLineNumber":83,"startColumn":1,"endLineNumber":83,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":811,"edits":[{"text":"\n    Write-Host \"Deployed UF2 to $targetDrive\"","range":{"startLineNumber":84,"startColumn":1,"endLineNumber":84,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":812,"edits":[{"text":"\n}","range":{"startLineNumber":85,"startColumn":1,"endLineNumber":85,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":813,"edits":[{"text":"\n","range":{"startLineNumber":86,"startColumn":1,"endLineNumber":86,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\FLASH_AND_TEST.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":816,"edits":[{"text":"# QT Py SAMD21 Firmware: Flash + Smoke Test\r\n\r\n## Current status\r\n\r\nThe firmware now builds for `thumbv6m-none-eabi` and includes:\r\n- USB HID OUT report polling\r\n- HID packet parser\r\n- DRV2605L command dispatch over I2C\r\n\r\n## Build\r\n\r\nUse:\r\n- `tools/scripts/build_firmware.ps1`\r\n\n## Integrated UF2 workflow (recommended)\n\nUse:\n- `tools/scripts/build_and_make_uf2.ps1`\n\nWhat it does:\n1. Builds firmware for `thumbv6m-none-eabi`\n2. Produces `build/firmware/qtpy-samd21-fw.bin`\n3. Converts BIN to UF2 with `uf2conv.py` into `build/firmware/qtpy-samd21-fw.uf2`\n\nOptional first-run tooling setup:\n- add `-InstallTooling`\n\nOptional auto-copy to mounted bootloader drive:\n- add `-Deploy`\n\r\n## Flashing options\r\n\r\n### Option A (recommended for convenience): UF2 bootloader drag-and-drop\r\n\r\n1. Double-tap reset on QT Py to mount `QTPYBOOT`.\n2. Run `tools/scripts/build_and_make_uf2.ps1 -Deploy`.\n3. Board reboots into application firmware automatically after copy.\n\r\n### Option B: SWD + probe-rs\r\n\r\nIf you have SWD access, flash directly with probe tooling.\r\n\r\n## Host smoke test\r\n\r\nAfter board reconnects as USB HID, run:\r\n- `tools/scripts/host_hid_smoke_test.ps1`\r\n\r\nDefault smoke packet:\r\n- Command `0x01` (`SET_INTENSITY`)\r\n- Intensity `64`\r\n- VID/PID `0x35bd` / `0xfe01`\r\n\r\n## Next recommended firmware increments\r\n\r\n1. DRV2605L init sequence (mode exit standby, optional calibration)\r\n2. Better error handling/recovery for I2C bus faults\r\n3. Optional HID IN status report path (ack/error counters)\r\n4. Real waveform/time semantics for ROM effect + duration handling\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":60,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\.gitignore","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/.gitignore","path":"/C:/Users/decid/Documents/projects/qt-py-haptics/.gitignore","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":819,"edits":[{"text":"# Rust / Cargo\r\n/target/\r\n/build/\n**/*.rs.bk\r\nCargo.lock\r\nstderr\r\nstdout\r\n\r\n# IDEs\r\n.idea/\r\n.vscode/\r\n*.swp\r\n*.swo\r\n\r\n# OS\r\n.DS_Store\r\nThumbs.db","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":18,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":821,"edits":[{"text":"param(\r\n    [ValidateSet(\"dev\", \"release\")]\r\n    [string]$Profile = \"release\",\r\n    [string]$FamilyId = \"0x68ed2b88\",\r\n    [string]$BaseAddress = \"0x2000\",\r\n    [switch]$InstallTooling,\r\n    [switch]$Deploy,\r\n    [string]$DriveRoot\r\n)\r\n\r\n$ErrorActionPreference = \"Stop\"\r\n$root = \"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\"\r\nSet-Location $root\r\n\r\nif ($InstallTooling) {\r\n    rustup component add llvm-tools\r\n    cargo install cargo-binutils\r\n}\r\n\r\nif (-not (Get-Command cargo-objcopy -ErrorAction SilentlyContinue)) {\r\n    throw \"cargo-objcopy is not installed. Run this script with -InstallTooling once.\"\r\n}\r\n\r\nif (-not (Get-Command python -ErrorAction SilentlyContinue)) {\r\n    throw \"python is required for uf2conv.py\"\r\n}\r\n\r\n$uf2ConvPath = Join-Path $root \"tools/uf2/uf2conv.py\"\r\nif (-not (Test-Path $uf2ConvPath)) {\r\n    $url = \"https://raw.githubusercontent.com/microsoft/uf2/master/utils/uf2conv.py\"\r\n    Invoke-WebRequest -Uri $url -OutFile $uf2ConvPath\r\n}\r\n\r\n$cargoArgs = @(\"build\", \"-p\", \"qtpy-samd21-fw\", \"--target\", \"thumbv6m-none-eabi\")\r\nif ($Profile -eq \"release\") {\r\n    $cargoArgs += \"--release\"\r\n}\r\n\r\n& cargo @cargoArgs\r\n\r\n$targetDir = Join-Path $root (\"target/thumbv6m-none-eabi/{0}\" -f $Profile)\r\n$elf = Join-Path $targetDir \"qtpy-samd21-fw\"\r\nif (-not (Test-Path $elf)) {\r\n    throw \"Expected ELF not found: $elf\"\r\n}\r\n\r\n$outDir = Join-Path $root \"build/firmware\"\r\nNew-Item -ItemType Directory -Path $outDir -Force | Out-Null\r\n$binPath = Join-Path $outDir \"qtpy-samd21-fw.bin\"\r\n$uf2Path = Join-Path $outDir \"qtpy-samd21-fw.uf2\"\r\n\r\n$modeArgs = @()\r\nif ($Profile -eq \"release\") {\r\n    $modeArgs += \"--release\"\r\n}\r\n\r\n& cargo objcopy -p qtpy-samd21-fw --target thumbv6m-none-eabi @modeArgs -- -O binary $binPath\r\n\r\n& python $uf2ConvPath $binPath --base $BaseAddress --family $FamilyId --convert --output $uf2Path\r\n\r\nWrite-Host \"BIN: $binPath\"\r\nWrite-Host \"UF2: $uf2Path\"\r\n\r\nif ($Deploy) {\r\n    $targetDrive = $DriveRoot\r\n\r\n    if (-not $targetDrive) {\r\n        $candidates = @(Get-PSDrive -PSProvider FileSystem |\n            Where-Object { Test-Path (Join-Path $_.Root \"INFO_UF2.TXT\") } |\r\n            Select-Object -ExpandProperty Root)\n\r\n        if ($candidates.Count -eq 1) {\r\n            $targetDrive = $candidates[0]\r\n        }\r\n        elseif ($candidates.Count -gt 1) {\r\n            throw \"Multiple UF2 drives found. Pass -DriveRoot explicitly.\"\r\n        }\r\n        else {\r\n            throw \"No UF2 drive found. Put QT Py into bootloader mode and retry.\"\r\n        }\r\n    }\r\n\r\n    Copy-Item -Path $uf2Path -Destination (Join-Path $targetDrive \"qtpy-samd21-fw.uf2\") -Force\r\n    Write-Host \"Deployed UF2 to $targetDrive\"\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":87,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":823,"edits":[{"text":"param(\r\n    [ValidateSet(\"dev\", \"release\")]\r\n    [string]$Profile = \"release\",\r\n    [string]$FamilyId = \"0x68ed2b88\",\r\n    [string]$BaseAddress = \"0x2000\",\r\n    [switch]$InstallTooling,\r\n    [switch]$Deploy,\r\n    [string]$DriveRoot\r\n)\r\n\r\n$ErrorActionPreference = \"Stop\"\r\n$root = \"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\"\r\nSet-Location $root\r\n\r\nif ($InstallTooling) {\r\n    rustup component add llvm-tools\r\n    if ($LASTEXITCODE -ne 0) { throw \"Failed to install llvm-tools\" }\n    cargo install cargo-binutils\r\n    if ($LASTEXITCODE -ne 0) { throw \"Failed to install cargo-binutils\" }\n}\r\n\r\nif (-not (Get-Command cargo-objcopy -ErrorAction SilentlyContinue)) {\r\n    throw \"cargo-objcopy is not installed. Run this script with -InstallTooling once.\"\r\n}\r\n\r\nif (-not (Get-Command python -ErrorAction SilentlyContinue)) {\r\n    throw \"python is required for uf2conv.py\"\r\n}\r\n\r\n$uf2ConvPath = Join-Path $root \"tools/uf2/uf2conv.py\"\r\nif (-not (Test-Path $uf2ConvPath)) {\r\n    $url = \"https://raw.githubusercontent.com/microsoft/uf2/master/utils/uf2conv.py\"\r\n    Invoke-WebRequest -Uri $url -OutFile $uf2ConvPath\r\n}\r\n\n$uf2FamiliesPath = Join-Path $root \"tools/uf2/uf2families.json\"\nif (-not (Test-Path $uf2FamiliesPath)) {\n    $url = \"https://raw.githubusercontent.com/microsoft/uf2/master/utils/uf2families.json\"\n    Invoke-WebRequest -Uri $url -OutFile $uf2FamiliesPath\n}\n\r\n$cargoArgs = @(\"build\", \"-p\", \"qtpy-samd21-fw\", \"--target\", \"thumbv6m-none-eabi\")\r\nif ($Profile -eq \"release\") {\r\n    $cargoArgs += \"--release\"\r\n}\r\n\r\n& cargo @cargoArgs\r\nif ($LASTEXITCODE -ne 0) { throw \"Firmware build failed\" }\n\r\n$targetDir = Join-Path $root (\"target/thumbv6m-none-eabi/{0}\" -f $Profile)\r\n$elf = Join-Path $targetDir \"qtpy-samd21-fw\"\r\nif (-not (Test-Path $elf)) {\r\n    throw \"Expected ELF not found: $elf\"\r\n}\r\n\r\n$outDir = Join-Path $root \"build/firmware\"\r\nNew-Item -ItemType Directory -Path $outDir -Force | Out-Null\r\n$binPath = Join-Path $outDir \"qtpy-samd21-fw.bin\"\r\n$uf2Path = Join-Path $outDir \"qtpy-samd21-fw.uf2\"\r\n\r\n$modeArgs = @()\r\nif ($Profile -eq \"release\") {\r\n    $modeArgs += \"--release\"\r\n}\r\n\r\n& cargo objcopy -p qtpy-samd21-fw --target thumbv6m-none-eabi @modeArgs -- -O binary $binPath\r\nif ($LASTEXITCODE -ne 0) { throw \"ELF to BIN conversion failed\" }\n\r\n& python $uf2ConvPath $binPath --base $BaseAddress --family $FamilyId --convert --output $uf2Path\r\nif ($LASTEXITCODE -ne 0) { throw \"BIN to UF2 conversion failed\" }\n\r\nWrite-Host \"BIN: $binPath\"\r\nWrite-Host \"UF2: $uf2Path\"\r\n\r\nif ($Deploy) {\r\n    $targetDrive = $DriveRoot\r\n\r\n    if (-not $targetDrive) {\r\n        $candidates = @(Get-PSDrive -PSProvider FileSystem |\r\n            Where-Object { Test-Path (Join-Path $_.Root \"INFO_UF2.TXT\") } |\r\n            Select-Object -ExpandProperty Root)\r\n\r\n        if ($candidates.Count -eq 1) {\r\n            $targetDrive = $candidates[0]\r\n        }\r\n        elseif ($candidates.Count -gt 1) {\r\n            throw \"Multiple UF2 drives found. Pass -DriveRoot explicitly.\"\r\n        }\r\n        else {\r\n            throw \"No UF2 drive found. Put QT Py into bootloader mode and retry.\"\r\n        }\r\n    }\r\n\r\n    Copy-Item -Path $uf2Path -Destination (Join-Path $targetDrive \"qtpy-samd21-fw.uf2\") -Force\r\n    Write-Host \"Deployed UF2 to $targetDrive\"\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":98,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":825,"edits":[{"text":"param(\r\n    [ValidateSet(\"dev\", \"release\")]\r\n    [string]$Profile = \"release\",\r\n    [string]$FamilyId = \"0x68ed2b88\",\r\n    [string]$BaseAddress = \"0x2000\",\r\n    [switch]$InstallTooling,\r\n    [switch]$Deploy,\r\n    [string]$DriveRoot\r\n)\r\n\r\n$ErrorActionPreference = \"Stop\"\r\n$root = \"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\"\r\nSet-Location $root\r\n\r\nif ($InstallTooling) {\r\n    rustup component add llvm-tools\r\n    if ($LASTEXITCODE -ne 0) { throw \"Failed to install llvm-tools\" }\r\n    cargo install cargo-binutils\r\n    if ($LASTEXITCODE -ne 0) { throw \"Failed to install cargo-binutils\" }\r\n}\r\n\r\nif (-not (Get-Command cargo-objcopy -ErrorAction SilentlyContinue)) {\r\n    throw \"cargo-objcopy is not installed. Run this script with -InstallTooling once.\"\r\n}\r\n\r\nif (-not (Get-Command python -ErrorAction SilentlyContinue)) {\r\n    throw \"python is required for uf2conv.py\"\r\n}\r\n\r\n$uf2ConvPath = Join-Path $root \"tools/uf2/uf2conv.py\"\r\nif (-not (Test-Path $uf2ConvPath)) {\r\n    $url = \"https://raw.githubusercontent.com/microsoft/uf2/master/utils/uf2conv.py\"\r\n    Invoke-WebRequest -Uri $url -OutFile $uf2ConvPath\r\n}\r\n\r\n$uf2FamiliesPath = Join-Path $root \"tools/uf2/uf2families.json\"\r\nif (-not (Test-Path $uf2FamiliesPath)) {\r\n    $url = \"https://raw.githubusercontent.com/microsoft/uf2/master/utils/uf2families.json\"\r\n    Invoke-WebRequest -Uri $url -OutFile $uf2FamiliesPath\r\n}\r\n\r\n$cargoArgs = @(\"build\", \"-p\", \"qtpy-samd21-fw\", \"--target\", \"thumbv6m-none-eabi\")\r\nif ($Profile -eq \"release\") {\r\n    $cargoArgs += \"--release\"\r\n}\r\n\r\n& cargo @cargoArgs\r\nif ($LASTEXITCODE -ne 0) { throw \"Firmware build failed\" }\r\n\r\n$targetDir = Join-Path $root (\"target/thumbv6m-none-eabi/{0}\" -f $Profile)\r\n$elf = Join-Path $targetDir \"qtpy-samd21-fw\"\r\nif (-not (Test-Path $elf)) {\r\n    throw \"Expected ELF not found: $elf\"\r\n}\r\n\r\n$outDir = Join-Path $root \"build/firmware\"\r\nNew-Item -ItemType Directory -Path $outDir -Force | Out-Null\r\n$binPath = Join-Path $outDir \"qtpy-samd21-fw.bin\"\r\n$uf2Path = Join-Path $outDir \"qtpy-samd21-fw.uf2\"\r\n\r\n$modeArgs = @()\r\nif ($Profile -eq \"release\") {\r\n    $modeArgs += \"--release\"\r\n}\r\n\r\n& cargo objcopy --bin qtpy-samd21-fw --target thumbv6m-none-eabi @modeArgs -- -O binary $binPath\nif ($LASTEXITCODE -ne 0) { throw \"ELF to BIN conversion failed\" }\r\nif (-not (Test-Path $binPath)) { throw \"BIN was not generated\" }\nif ((Get-Item $binPath).Length -lt 8) { throw \"BIN output is unexpectedly small\" }\n\r\n& python $uf2ConvPath $binPath --base $BaseAddress --family $FamilyId --convert --output $uf2Path\r\nif ($LASTEXITCODE -ne 0) { throw \"BIN to UF2 conversion failed\" }\r\n\r\nWrite-Host \"BIN: $binPath\"\r\nWrite-Host \"UF2: $uf2Path\"\r\n\r\nif ($Deploy) {\r\n    $targetDrive = $DriveRoot\r\n\r\n    if (-not $targetDrive) {\r\n        $candidates = @(Get-PSDrive -PSProvider FileSystem |\r\n            Where-Object { Test-Path (Join-Path $_.Root \"INFO_UF2.TXT\") } |\r\n            Select-Object -ExpandProperty Root)\r\n\r\n        if ($candidates.Count -eq 1) {\r\n            $targetDrive = $candidates[0]\r\n        }\r\n        elseif ($candidates.Count -gt 1) {\r\n            throw \"Multiple UF2 drives found. Pass -DriveRoot explicitly.\"\r\n        }\r\n        else {\r\n            throw \"No UF2 drive found. Put QT Py into bootloader mode and retry.\"\r\n        }\r\n    }\r\n\r\n    Copy-Item -Path $uf2Path -Destination (Join-Path $targetDrive \"qtpy-samd21-fw.uf2\") -Force\r\n    Write-Host \"Deployed UF2 to $targetDrive\"\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":100,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":827,"edits":[{"text":"param(\r\n    [ValidateSet(\"dev\", \"release\")]\r\n    [string]$Profile = \"release\",\r\n    [string]$FamilyId = \"0x68ed2b88\",\r\n    [string]$BaseAddress = \"0x2000\",\r\n    [switch]$InstallTooling,\r\n    [switch]$Deploy,\r\n    [string]$DriveRoot\r\n)\r\n\r\n$ErrorActionPreference = \"Stop\"\r\n$root = \"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\"\r\nSet-Location $root\r\n\r\nif ($InstallTooling) {\r\n    rustup component add llvm-tools\r\n    if ($LASTEXITCODE -ne 0) { throw \"Failed to install llvm-tools\" }\r\n    cargo install cargo-binutils\r\n    if ($LASTEXITCODE -ne 0) { throw \"Failed to install cargo-binutils\" }\r\n}\r\n\r\nif (-not (Get-Command cargo-objcopy -ErrorAction SilentlyContinue)) {\r\n    throw \"cargo-objcopy is not installed. Run this script with -InstallTooling once.\"\r\n}\r\n\r\nif (-not (Get-Command python -ErrorAction SilentlyContinue)) {\r\n    throw \"python is required for uf2conv.py\"\r\n}\r\n\r\n$uf2ConvPath = Join-Path $root \"tools/uf2/uf2conv.py\"\r\nif (-not (Test-Path $uf2ConvPath)) {\r\n    $url = \"https://raw.githubusercontent.com/microsoft/uf2/master/utils/uf2conv.py\"\r\n    Invoke-WebRequest -Uri $url -OutFile $uf2ConvPath\r\n}\r\n\r\n$uf2FamiliesPath = Join-Path $root \"tools/uf2/uf2families.json\"\r\nif (-not (Test-Path $uf2FamiliesPath)) {\r\n    $url = \"https://raw.githubusercontent.com/microsoft/uf2/master/utils/uf2families.json\"\r\n    Invoke-WebRequest -Uri $url -OutFile $uf2FamiliesPath\r\n}\r\n\r\n$cargoArgs = @(\"build\", \"-p\", \"qtpy-samd21-fw\", \"--target\", \"thumbv6m-none-eabi\")\r\nif ($Profile -eq \"release\") {\r\n    $cargoArgs += \"--release\"\r\n}\r\n\r\n& cargo @cargoArgs\r\nif ($LASTEXITCODE -ne 0) { throw \"Firmware build failed\" }\r\n\r\n$targetDir = Join-Path $root (\"target/thumbv6m-none-eabi/{0}\" -f $Profile)\r\n$elf = Join-Path $targetDir \"qtpy-samd21-fw\"\r\nif (-not (Test-Path $elf)) {\r\n    throw \"Expected ELF not found: $elf\"\r\n}\r\n\r\n$outDir = Join-Path $root \"build/firmware\"\r\nNew-Item -ItemType Directory -Path $outDir -Force | Out-Null\r\n$binPath = Join-Path $outDir \"qtpy-samd21-fw.bin\"\r\n$uf2Path = Join-Path $outDir \"qtpy-samd21-fw.uf2\"\r\n\r\n$modeArgs = @()\r\nif ($Profile -eq \"release\") {\r\n    $modeArgs += \"--release\"\r\n}\r\n\r\n& cargo objcopy --package qtpy-samd21-fw --bin qtpy-samd21-fw --target thumbv6m-none-eabi @modeArgs -- -O binary $binPath\nif ($LASTEXITCODE -ne 0) { throw \"ELF to BIN conversion failed\" }\r\nif (-not (Test-Path $binPath)) { throw \"BIN was not generated\" }\r\nif ((Get-Item $binPath).Length -lt 8) { throw \"BIN output is unexpectedly small\" }\r\n\r\n& python $uf2ConvPath $binPath --base $BaseAddress --family $FamilyId --convert --output $uf2Path\r\nif ($LASTEXITCODE -ne 0) { throw \"BIN to UF2 conversion failed\" }\r\n\r\nWrite-Host \"BIN: $binPath\"\r\nWrite-Host \"UF2: $uf2Path\"\r\n\r\nif ($Deploy) {\r\n    $targetDrive = $DriveRoot\r\n\r\n    if (-not $targetDrive) {\r\n        $candidates = @(Get-PSDrive -PSProvider FileSystem |\r\n            Where-Object { Test-Path (Join-Path $_.Root \"INFO_UF2.TXT\") } |\r\n            Select-Object -ExpandProperty Root)\r\n\r\n        if ($candidates.Count -eq 1) {\r\n            $targetDrive = $candidates[0]\r\n        }\r\n        elseif ($candidates.Count -gt 1) {\r\n            throw \"Multiple UF2 drives found. Pass -DriveRoot explicitly.\"\r\n        }\r\n        else {\r\n            throw \"No UF2 drive found. Put QT Py into bootloader mode and retry.\"\r\n        }\r\n    }\r\n\r\n    Copy-Item -Path $uf2Path -Destination (Join-Path $targetDrive \"qtpy-samd21-fw.uf2\") -Force\r\n    Write-Host \"Deployed UF2 to $targetDrive\"\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":100,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_make_uf2.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_make_uf2.ps1","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":829,"edits":[{"text":"param(\r\n    [ValidateSet(\"dev\", \"release\")]\r\n    [string]$Profile = \"release\",\r\n    [string]$FamilyId = \"0x68ed2b88\",\r\n    [string]$BaseAddress = \"0x2000\",\r\n    [switch]$InstallTooling,\r\n    [switch]$Deploy,\r\n    [string]$DriveRoot\r\n)\r\n\r\n$ErrorActionPreference = \"Stop\"\r\n$root = \"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\"\r\nSet-Location $root\r\n\r\nif ($InstallTooling) {\r\n    rustup component add llvm-tools\r\n    if ($LASTEXITCODE -ne 0) { throw \"Failed to install llvm-tools\" }\r\n    cargo install cargo-binutils\r\n    if ($LASTEXITCODE -ne 0) { throw \"Failed to install cargo-binutils\" }\r\n}\r\n\r\nif (-not (Get-Command rust-objcopy -ErrorAction SilentlyContinue)) {\n    throw \"rust-objcopy is not installed. Run this script with -InstallTooling once.\"\n}\r\n\r\nif (-not (Get-Command python -ErrorAction SilentlyContinue)) {\r\n    throw \"python is required for uf2conv.py\"\r\n}\r\n\r\n$uf2ConvPath = Join-Path $root \"tools/uf2/uf2conv.py\"\r\nif (-not (Test-Path $uf2ConvPath)) {\r\n    $url = \"https://raw.githubusercontent.com/microsoft/uf2/master/utils/uf2conv.py\"\r\n    Invoke-WebRequest -Uri $url -OutFile $uf2ConvPath\r\n}\r\n\r\n$uf2FamiliesPath = Join-Path $root \"tools/uf2/uf2families.json\"\r\nif (-not (Test-Path $uf2FamiliesPath)) {\r\n    $url = \"https://raw.githubusercontent.com/microsoft/uf2/master/utils/uf2families.json\"\r\n    Invoke-WebRequest -Uri $url -OutFile $uf2FamiliesPath\r\n}\r\n\r\n$cargoArgs = @(\"build\", \"-p\", \"qtpy-samd21-fw\", \"--target\", \"thumbv6m-none-eabi\")\r\nif ($Profile -eq \"release\") {\r\n    $cargoArgs += \"--release\"\r\n}\r\n\r\n& cargo @cargoArgs\r\nif ($LASTEXITCODE -ne 0) { throw \"Firmware build failed\" }\r\n\r\n$targetDir = Join-Path $root (\"target/thumbv6m-none-eabi/{0}\" -f $Profile)\r\n$elf = Join-Path $targetDir \"qtpy-samd21-fw\"\r\nif (-not (Test-Path $elf)) {\r\n    throw \"Expected ELF not found: $elf\"\r\n}\r\n\r\n$outDir = Join-Path $root \"build/firmware\"\r\nNew-Item -ItemType Directory -Path $outDir -Force | Out-Null\r\n$binPath = Join-Path $outDir \"qtpy-samd21-fw.bin\"\r\n$uf2Path = Join-Path $outDir \"qtpy-samd21-fw.uf2\"\r\n\r\n& rust-objcopy $elf -O binary $binPath\nif ($LASTEXITCODE -ne 0) { throw \"ELF to BIN conversion failed\" }\r\nif (-not (Test-Path $binPath)) { throw \"BIN was not generated\" }\r\nif ((Get-Item $binPath).Length -lt 8) { throw \"BIN output is unexpectedly small\" }\r\n\r\n& python $uf2ConvPath $binPath --base $BaseAddress --family $FamilyId --convert --output $uf2Path\r\nif ($LASTEXITCODE -ne 0) { throw \"BIN to UF2 conversion failed\" }\r\n\r\nWrite-Host \"BIN: $binPath\"\r\nWrite-Host \"UF2: $uf2Path\"\r\n\r\nif ($Deploy) {\r\n    $targetDrive = $DriveRoot\r\n\r\n    if (-not $targetDrive) {\r\n        $candidates = @(Get-PSDrive -PSProvider FileSystem |\r\n            Where-Object { Test-Path (Join-Path $_.Root \"INFO_UF2.TXT\") } |\r\n            Select-Object -ExpandProperty Root)\r\n\r\n        if ($candidates.Count -eq 1) {\r\n            $targetDrive = $candidates[0]\r\n        }\r\n        elseif ($candidates.Count -gt 1) {\r\n            throw \"Multiple UF2 drives found. Pass -DriveRoot explicitly.\"\r\n        }\r\n        else {\r\n            throw \"No UF2 drive found. Put QT Py into bootloader mode and retry.\"\r\n        }\r\n    }\r\n\r\n    Copy-Item -Path $uf2Path -Destination (Join-Path $targetDrive \"qtpy-samd21-fw.uf2\") -Force\r\n    Write-Host \"Deployed UF2 to $targetDrive\"\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":95,"endColumn":1}},{"text":"","range":{"startLineNumber":95,"startColumn":1,"endLineNumber":100,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","scheme":"file"},"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","epoch":832,"edits":[{"text":"[package]\r\nname = \"qtpy-samd21-fw\"\r\nversion = \"0.1.0\"\r\nedition.workspace = true\r\nlicense.workspace = true\r\nauthors.workspace = true\r\n\r\n[dependencies]\r\natsamd-hal = { version = \"0.21\", default-features = false, features = [\"samd21e\", \"samd21e-rt\", \"usb\"] }\ncortex-m = \"0.7\"\r\ncortex-m-rt = \"0.7\"\r\nembedded-hal = \"1\"\r\nhaptics-protocol = { path = \"../../shared/haptics-protocol\", default-features = false }\r\npanic-halt = \"0.2\"\r\nusb-device = \"0.3\"\r\nusbd-hid = \"0.8\"\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":18,"endColumn":1}}]},{"type":"create","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\.cargo\\config.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/.cargo/config.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/.cargo/config.toml","scheme":"file"},"requestId":"request_5ffebef5-f6de-4fca-a46a-31d52f96616e","epoch":834,"initialContent":"","telemetryInfo":{}},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\.cargo\\config.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/.cargo/config.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/.cargo/config.toml","scheme":"file"},"requestId":"request_5ffebef5-f6de-4fca-a46a-31d52f96616e","epoch":835,"edits":[{"text":"[target.thumbv6m-none-eabi]","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":1,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\.cargo\\config.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/.cargo/config.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/.cargo/config.toml","scheme":"file"},"requestId":"request_5ffebef5-f6de-4fca-a46a-31d52f96616e","epoch":836,"edits":[{"text":"\nrustflags = [","range":{"startLineNumber":2,"startColumn":1,"endLineNumber":2,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\.cargo\\config.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/.cargo/config.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/.cargo/config.toml","scheme":"file"},"requestId":"request_5ffebef5-f6de-4fca-a46a-31d52f96616e","epoch":837,"edits":[{"text":"\n  \"-C\", \"link-arg=-Tlink.x\",","range":{"startLineNumber":3,"startColumn":1,"endLineNumber":3,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\.cargo\\config.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/.cargo/config.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/.cargo/config.toml","scheme":"file"},"requestId":"request_5ffebef5-f6de-4fca-a46a-31d52f96616e","epoch":838,"edits":[{"text":"\n]","range":{"startLineNumber":4,"startColumn":1,"endLineNumber":4,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\.cargo\\config.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/.cargo/config.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/.cargo/config.toml","scheme":"file"},"requestId":"request_5ffebef5-f6de-4fca-a46a-31d52f96616e","epoch":839,"edits":[{"text":"\n","range":{"startLineNumber":5,"startColumn":1,"endLineNumber":5,"endColumn":1}}]},{"type":"create","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\build.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/build.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/build.rs","scheme":"file"},"requestId":"request_5ffebef5-f6de-4fca-a46a-31d52f96616e","epoch":840,"initialContent":"","telemetryInfo":{}},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\build.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/build.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/build.rs","scheme":"file"},"requestId":"request_5ffebef5-f6de-4fca-a46a-31d52f96616e","epoch":841,"edits":[{"text":"fn main() {","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":1,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\build.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/build.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/build.rs","scheme":"file"},"requestId":"request_5ffebef5-f6de-4fca-a46a-31d52f96616e","epoch":842,"edits":[{"text":"\n    println!(\"cargo:rerun-if-changed=memory.x\");","range":{"startLineNumber":2,"startColumn":1,"endLineNumber":2,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\build.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/build.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/build.rs","scheme":"file"},"requestId":"request_5ffebef5-f6de-4fca-a46a-31d52f96616e","epoch":843,"edits":[{"text":"\n    println!(\"cargo:rustc-link-search={}\", env!(\"CARGO_MANIFEST_DIR\"));","range":{"startLineNumber":3,"startColumn":1,"endLineNumber":3,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\build.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/build.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/build.rs","scheme":"file"},"requestId":"request_5ffebef5-f6de-4fca-a46a-31d52f96616e","epoch":844,"edits":[{"text":"\n}","range":{"startLineNumber":4,"startColumn":1,"endLineNumber":4,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\build.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/build.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/build.rs","scheme":"file"},"requestId":"request_5ffebef5-f6de-4fca-a46a-31d52f96616e","epoch":845,"edits":[{"text":"\n","range":{"startLineNumber":5,"startColumn":1,"endLineNumber":5,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","scheme":"file"},"requestId":"request_5ffebef5-f6de-4fca-a46a-31d52f96616e","epoch":848,"edits":[{"text":"[package]\r\nname = \"qtpy-samd21-fw\"\r\nversion = \"0.1.0\"\r\nedition.workspace = true\r\nlicense.workspace = true\r\nauthors.workspace = true\r\n\r\n[dependencies]\r\natsamd-hal = { version = \"0.21\", default-features = false, features = [\"samd21e\", \"samd21e-rt\", \"usb\"] }\r\ncortex-m = { version = \"0.7\", features = [\"critical-section-single-core\"] }\ncortex-m-rt = \"0.7\"\r\nembedded-hal = \"1\"\r\nhaptics-protocol = { path = \"../../shared/haptics-protocol\", default-features = false }\r\npanic-halt = \"0.2\"\r\nusb-device = \"0.3\"\r\nusbd-hid = \"0.8\"\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":18,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\host_hid_smoke_test.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/host_hid_smoke_test.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/host_hid_smoke_test.ps1","scheme":"file"},"requestId":"request_128f3af0-ca12-4644-bf4a-f57b14bf85bb","epoch":852,"edits":[{"text":"param(\r\n    [UInt16]$Vid = 0x35bd,\r\n    [Alias('Pid')]\n    [UInt16]$ProductId = 0xfe01,\n    [byte]$Command = 0x01,\r\n    [byte]$Intensity = 64\r\n)\r\n\r\n$ErrorActionPreference = \"Stop\"\r\n$root = \"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\"\r\nSet-Location $root\r\n\r\ncargo run -p hid-cli -- --vid $Vid --pid $ProductId --cmd $Command --intensity $Intensity\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":15,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\host_hid_smoke_test.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/host_hid_smoke_test.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/host_hid_smoke_test.ps1","scheme":"file"},"requestId":"request_92d7c45c-4795-4c66-b59e-3d209f7a5944","epoch":856,"edits":[{"text":"param(\r\n    [UInt16]$Vid = 0x35bd,\r\n    [Alias('Pid')]\r\n    [UInt16]$ProductId = 0xfe01,\r\n    [byte]$Command = 0x01,\r\n    [byte]$Intensity = 64,\n    [int]$AutoStopMs = 120,\n    [switch]$NoAutoStop,\n    [switch]$SkipPreflight\n)\r\n\r\n$ErrorActionPreference = \"Stop\"\r\n$root = \"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\"\r\nSet-Location $root\r\n\r\nfunction Test-HidPresent {\n    param(\n        [UInt16]$CheckVid,\n        [UInt16]$CheckPid\n    )\n\n    $vidHex = ('{0:X4}' -f $CheckVid)\n    $pidHex = ('{0:X4}' - f $CheckPid)\n    $pattern = \"VID_$vidHex&PID_$pidHex\"\n\n    $matches = Get-CimInstance -ClassName Win32_PnPEntity -ErrorAction SilentlyContinue |\n        Where-Object { $_.DeviceID -like \"*$pattern*\" }\n\n    return ($null -ne $matches -and $matches.Count -gt 0)\n}\n\nif (-not $SkipPreflight) {\n    if (-not (Test-HidPresent -CheckVid $Vid -CheckPid $ProductId)) {\n        throw \"HID device not detected for VID=0x$('{0:X4}' -f $Vid), PID=0x$('{0:X4}' -f $ProductId). Check cable, firmware, and device enumeration.\"\n    }\n}\n\ncargo run -p hid-cli -- --vid $Vid --pid $ProductId --cmd $Command --intensity $Intensity\n\n# Safety: for `SET_INTENSITY` smoke tests, send STOP shortly after start\n# unless explicitly disabled.\nif (-not $NoAutoStop -and $Command -eq 0x01 -and $Intensity -gt 0) {\n    Start-Sleep -Milliseconds $AutoStopMs\n    cargo run -p hid-cli -- --vid $Vid --pid $ProductId --cmd 0x03 --intensity 0\n}\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":47,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\host_hid_smoke_test.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/host_hid_smoke_test.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/host_hid_smoke_test.ps1","scheme":"file"},"requestId":"request_92d7c45c-4795-4c66-b59e-3d209f7a5944","epoch":858,"edits":[{"text":"param(\r\n    [UInt16]$Vid = 0x35bd,\r\n    [Alias('Pid')]\r\n    [UInt16]$ProductId = 0xfe01,\r\n    [byte]$Command = 0x01,\r\n    [byte]$Intensity = 64,\r\n    [int]$AutoStopMs = 120,\r\n    [switch]$NoAutoStop,\r\n    [switch]$SkipPreflight\r\n)\r\n\r\n$ErrorActionPreference = \"Stop\"\r\n$root = \"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\"\r\nSet-Location $root\r\n\r\nfunction Test-HidPresent {\r\n    param(\r\n        [UInt16]$CheckVid,\r\n        [UInt16]$CheckPid\r\n    )\r\n\r\n    $vidHex = ('{0:X4}' -f $CheckVid)\r\n    $pidHex = ('{0:X4}' -f $CheckPid)\n    $pattern = \"VID_$vidHex&PID_$pidHex\"\r\n\r\n    $matches = Get-CimInstance -ClassName Win32_PnPEntity -ErrorAction SilentlyContinue |\r\n        Where-Object { $_.DeviceID -like \"*$pattern*\" }\r\n\r\n    return ($null -ne $matches -and $matches.Count -gt 0)\r\n}\r\n\r\nif (-not $SkipPreflight) {\r\n    if (-not (Test-HidPresent -CheckVid $Vid -CheckPid $ProductId)) {\r\n        throw \"HID device not detected for VID=0x$('{0:X4}' -f $Vid), PID=0x$('{0:X4}' -f $ProductId). Check cable, firmware, and device enumeration.\"\r\n    }\r\n}\r\n\r\ncargo run -p hid-cli -- --vid $Vid --pid $ProductId --cmd $Command --intensity $Intensity\r\n\r\n# Safety: for `SET_INTENSITY` smoke tests, send STOP shortly after start\r\n# unless explicitly disabled.\r\nif (-not $NoAutoStop -and $Command -eq 0x01 -and $Intensity -gt 0) {\r\n    Start-Sleep -Milliseconds $AutoStopMs\r\n    cargo run -p hid-cli -- --vid $Vid --pid $ProductId --cmd 0x03 --intensity 0\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":47,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\host_hid_smoke_test.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/host_hid_smoke_test.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/host_hid_smoke_test.ps1","scheme":"file"},"requestId":"request_92d7c45c-4795-4c66-b59e-3d209f7a5944","epoch":860,"edits":[{"text":"param(\r\n    [UInt16]$Vid = 0x35bd,\r\n    [Alias('Pid')]\r\n    [UInt16]$ProductId = 0xfe01,\r\n    [byte]$Command = 0x01,\r\n    [byte]$Intensity = 64,\r\n    [int]$AutoStopMs = 120,\r\n    [switch]$NoAutoStop,\r\n    [switch]$SkipPreflight\r\n)\r\n\r\n$ErrorActionPreference = \"Stop\"\r\n$root = \"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\"\r\nSet-Location $root\r\n\r\nfunction Test-HidPresent {\r\n    param(\r\n        [UInt16]$CheckVid,\r\n        [UInt16]$CheckPid\r\n    )\r\n\r\n    $vidHex = ('{0:X4}' -f $CheckVid)\r\n    $pidHex = ('{0:X4}' -f $CheckPid)\r\n    $pattern = \"VID_$vidHex&PID_$pidHex\"\r\n\r\n    $matches = Get-CimInstance -ClassName Win32_PnPEntity -ErrorAction SilentlyContinue |\r\n        Where-Object { $_.DeviceID -like \"*$pattern*\" }\r\n\r\n    return ($null -ne $matches -and $matches.Count -gt 0)\r\n}\r\n\r\nif (-not $SkipPreflight) {\r\n    if (-not (Test-HidPresent -CheckVid $Vid -CheckPid $ProductId)) {\r\n        throw \"HID device not detected for VID=0x$('{0:X4}' -f $Vid), PID=0x$('{0:X4}' -f $ProductId). Check cable, firmware, and device enumeration.\"\r\n    }\r\n}\r\n\r\ncargo run -p hid-cli -- --vid $Vid --pid $ProductId --cmd $Command --intensity $Intensity\r\n\r\n# Safety: for `SET_INTENSITY` smoke tests, send STOP shortly after start\r\n# unless explicitly disabled.\r\nif (-not $NoAutoStop -and $Command -eq 0x01 -and $Intensity -gt 0) {\r\n    Start-Sleep -Milliseconds $AutoStopMs\r\n    cargo run -p hid-cli -- --vid $Vid --pid $ProductId --cmd 3 --intensity 0\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":47,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":864,"edits":[{"text":"use core::ffi::c_void;\nuse core::ffi::CStr;\nuse std::os::raw::c_char;\n\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\n\nconst SERVER_PROVIDER_INTERFACE: &str = \"IServerTrackedDeviceProvider_005\";\n\n#[repr(i32)]\nenum DriverReturnCode {\n    None = 0,\n    Unknown = 1,\n    InitInterfaceNotFound = 105,\n}\n\n#[repr(C)]\nstruct StubProvider {\n    // Placeholder for future OpenVR vtable pointer.\n    _vtable: *const c_void,\n}\n\nstatic STUB_PROVIDER: StubProvider = StubProvider {\n    _vtable: core::ptr::null(),\n};\n\nfn set_return_code(out: *mut i32, code: DriverReturnCode) {\n    if !out.is_null() {\n        // SAFETY: caller owns output pointer according to OpenVR factory ABI.\n        unsafe {\n            *out = code as i32;\n        }\n    }\n}\n\nfn read_interface_name(ptr: *const i8) -> Option<&'static str> {\n    if ptr.is_null() {\n        return None;\n    }\n\n    // SAFETY: OpenVR passes a NUL-terminated C string.\n    let cstr = unsafe { CStr::from_ptr(ptr) };\n    let Ok(name) = cstr.to_str() else {\n        return None;\n    };\n\n    // Keep only known literals in static storage path.\n    if name == SERVER_PROVIDER_INTERFACE {\n        Some(SERVER_PROVIDER_INTERFACE)\n    } else {\n        None\n    }\n}\n\n#[unsafe(no_mangle)]\npub extern \"C\" fn HmdDriverFactory(\n    p_interface_name: *const c_char,\n    p_return_code: *mut i32,\n) -> *mut c_void {\n    let Some(interface_name) = read_interface_name(p_interface_name) else {\n        set_return_code(p_return_code, DriverReturnCode::InitInterfaceNotFound);\n        return core::ptr::null_mut();\n    };\n\n    if interface_name == SERVER_PROVIDER_INTERFACE {\n        set_return_code(p_return_code, DriverReturnCode::None);\n        return (&STUB_PROVIDER as *const StubProvider).cast_mut().cast();\n    }\n\n    set_return_code(p_return_code, DriverReturnCode::Unknown);\n    core::ptr::null_mut()\n}\n\n/// Test-only export for early integration while full OpenVR vtable glue is in progress.\n/// Returns 0 on success, non-zero on failure.\n#[unsafe(no_mangle)]\npub extern \"C\" fn RustHapticDriver_SendAmplitude(amplitude: f32) -> i32 {\n    let amp = amplitude.clamp(0.0, 1.0);\n    let intensity = (amp * 255.0).round() as u8;\n    let packet = HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0);\n\n    match hid_bridge::HidBridge::new().and_then(|bridge| {\n        bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet)\n    }) {\n        Ok(()) => 0,\n        Err(_) => 1,\n    }\n}\n\n/// Explicit stop command helper for testing and recovery.\n#[unsafe(no_mangle)]\npub extern \"C\" fn RustHapticDriver_Stop() -> i32 {\n    let packet = HapticPacket::new(command::STOP, 0, 0, 0);\n\n    match hid_bridge::HidBridge::new().and_then(|bridge| {\n        bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet)\n    }) {\n        Ok(()) => 0,\n        Err(_) => 1,\n    }\n}\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":102,"endColumn":1}}]},{"type":"create","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\driver.vrdrivermanifest","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/driver.vrdrivermanifest","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/driver.vrdrivermanifest","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":865,"initialContent":"","telemetryInfo":{}},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\driver.vrdrivermanifest","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/driver.vrdrivermanifest","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/driver.vrdrivermanifest","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":866,"edits":[{"text":"{","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":1,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\driver.vrdrivermanifest","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/driver.vrdrivermanifest","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/driver.vrdrivermanifest","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":867,"edits":[{"text":"\n  \"always_activate\": true,","range":{"startLineNumber":2,"startColumn":1,"endLineNumber":2,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\driver.vrdrivermanifest","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/driver.vrdrivermanifest","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/driver.vrdrivermanifest","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":868,"edits":[{"text":"\n  \"name\": \"rust_haptic_driver\",","range":{"startLineNumber":3,"startColumn":1,"endLineNumber":3,"endColumn":1}}]},{"type":"create","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\resources\\input\\rust_haptic_profile.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":869,"initialContent":"","telemetryInfo":{}},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\driver.vrdrivermanifest","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/driver.vrdrivermanifest","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/driver.vrdrivermanifest","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":870,"edits":[{"text":"\n  \"resource_only\": false,","range":{"startLineNumber":4,"startColumn":1,"endLineNumber":4,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\resources\\input\\rust_haptic_profile.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":871,"edits":[{"text":"{","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":1,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\driver.vrdrivermanifest","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/driver.vrdrivermanifest","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/driver.vrdrivermanifest","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":872,"edits":[{"text":"\n  \"resource_directory\": \"resources\"","range":{"startLineNumber":5,"startColumn":1,"endLineNumber":5,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\resources\\input\\rust_haptic_profile.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":873,"edits":[{"text":"\n  \"jsonid\": \"input_profile\",","range":{"startLineNumber":2,"startColumn":1,"endLineNumber":2,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\driver.vrdrivermanifest","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/driver.vrdrivermanifest","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/driver.vrdrivermanifest","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":874,"edits":[{"text":"\n}","range":{"startLineNumber":6,"startColumn":1,"endLineNumber":6,"endColumn":1}}]},{"type":"create","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\resources\\localization\\en_us.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/localization/en_us.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/localization/en_us.json","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":875,"initialContent":"","telemetryInfo":{}},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\resources\\input\\rust_haptic_profile.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":876,"edits":[{"text":"\n  \"controller_type\": \"rust_haptic_driver\",","range":{"startLineNumber":3,"startColumn":1,"endLineNumber":3,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\driver.vrdrivermanifest","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/driver.vrdrivermanifest","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/driver.vrdrivermanifest","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":877,"edits":[{"text":"\n","range":{"startLineNumber":7,"startColumn":1,"endLineNumber":7,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\resources\\localization\\en_us.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/localization/en_us.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/localization/en_us.json","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":878,"edits":[{"text":"{","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":1,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\resources\\input\\rust_haptic_profile.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":879,"edits":[{"text":"\n  \"input_bindingui_mode\": \"controller_handed\",","range":{"startLineNumber":4,"startColumn":1,"endLineNumber":4,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\resources\\localization\\en_us.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/localization/en_us.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/localization/en_us.json","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":880,"edits":[{"text":"\n  \"language_tag\": \"en_US\",","range":{"startLineNumber":2,"startColumn":1,"endLineNumber":2,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\resources\\input\\rust_haptic_profile.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":881,"edits":[{"text":"\n  \"input_source\": {","range":{"startLineNumber":5,"startColumn":1,"endLineNumber":5,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\resources\\localization\\en_us.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/localization/en_us.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/localization/en_us.json","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":882,"edits":[{"text":"\n  \"rust_haptic_driver\": \"Rust Haptic Driver\",","range":{"startLineNumber":3,"startColumn":1,"endLineNumber":3,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\resources\\input\\rust_haptic_profile.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":883,"edits":[{"text":"\n    \"haptic\": {","range":{"startLineNumber":6,"startColumn":1,"endLineNumber":6,"endColumn":1}}]},{"type":"create","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":884,"initialContent":"","telemetryInfo":{}},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":885,"edits":[{"text":"# rust-haptic-driver","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":1,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\resources\\localization\\en_us.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/localization/en_us.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/localization/en_us.json","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":886,"edits":[{"text":"\n  \"rust_haptic_driver_input_profile\": \"Rust Haptic Driver Input\",","range":{"startLineNumber":4,"startColumn":1,"endLineNumber":4,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\resources\\input\\rust_haptic_profile.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":887,"edits":[{"text":"\n      \"type\": \"vibration\",","range":{"startLineNumber":7,"startColumn":1,"endLineNumber":7,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":888,"edits":[{"text":"\n","range":{"startLineNumber":2,"startColumn":1,"endLineNumber":2,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\resources\\localization\\en_us.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/localization/en_us.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/localization/en_us.json","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":889,"edits":[{"text":"\n  \"/devices/rust_haptic_driver/prop/modelnumber_string\": \"QT Py SAMD21 Haptic\",","range":{"startLineNumber":5,"startColumn":1,"endLineNumber":5,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\resources\\input\\rust_haptic_profile.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":890,"edits":[{"text":"\n      \"binding_image_point\": [0.5, 0.5]","range":{"startLineNumber":8,"startColumn":1,"endLineNumber":8,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":891,"edits":[{"text":"\nInitial OpenVR driver scaffold in Rust.","range":{"startLineNumber":3,"startColumn":1,"endLineNumber":3,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\resources\\localization\\en_us.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/localization/en_us.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/localization/en_us.json","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":892,"edits":[{"text":"\n  \"/devices/rust_haptic_driver/prop/serialnumber_string\": \"qtpy-samd21-haptic\"","range":{"startLineNumber":6,"startColumn":1,"endLineNumber":6,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\resources\\input\\rust_haptic_profile.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":893,"edits":[{"text":"\n    }","range":{"startLineNumber":9,"startColumn":1,"endLineNumber":9,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":894,"edits":[{"text":"\n","range":{"startLineNumber":4,"startColumn":1,"endLineNumber":4,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\resources\\localization\\en_us.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/localization/en_us.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/localization/en_us.json","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":895,"edits":[{"text":"\n}","range":{"startLineNumber":7,"startColumn":1,"endLineNumber":7,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\resources\\input\\rust_haptic_profile.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":896,"edits":[{"text":"\n  },","range":{"startLineNumber":10,"startColumn":1,"endLineNumber":10,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":897,"edits":[{"text":"\n## Current capabilities","range":{"startLineNumber":5,"startColumn":1,"endLineNumber":5,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\resources\\localization\\en_us.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/localization/en_us.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/localization/en_us.json","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":898,"edits":[{"text":"\n","range":{"startLineNumber":8,"startColumn":1,"endLineNumber":8,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\resources\\input\\rust_haptic_profile.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":899,"edits":[{"text":"\n  \"input_bindingui_left\": {","range":{"startLineNumber":11,"startColumn":1,"endLineNumber":11,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":900,"edits":[{"text":"\n","range":{"startLineNumber":6,"startColumn":1,"endLineNumber":6,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\resources\\input\\rust_haptic_profile.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":901,"edits":[{"text":"\n    \"image\": \"{rust_haptic_driver}/icons/haptic_controller_left.svg\",","range":{"startLineNumber":12,"startColumn":1,"endLineNumber":12,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":902,"edits":[{"text":"\n- Exports `HmdDriverFactory` with minimal interface-name validation","range":{"startLineNumber":7,"startColumn":1,"endLineNumber":7,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\resources\\input\\rust_haptic_profile.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":903,"edits":[{"text":"\n    \"transform\": {","range":{"startLineNumber":13,"startColumn":1,"endLineNumber":13,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":904,"edits":[{"text":"\n- Exposes DLL-callable haptics test exports:","range":{"startLineNumber":8,"startColumn":1,"endLineNumber":8,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\resources\\input\\rust_haptic_profile.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":905,"edits":[{"text":"\n      \"scale\": 1.0,","range":{"startLineNumber":14,"startColumn":1,"endLineNumber":14,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":906,"edits":[{"text":"\n  - `RustHapticDriver_SendAmplitude(float)`","range":{"startLineNumber":9,"startColumn":1,"endLineNumber":9,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\resources\\input\\rust_haptic_profile.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":907,"edits":[{"text":"\n      \"x\": 0,","range":{"startLineNumber":15,"startColumn":1,"endLineNumber":15,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":908,"edits":[{"text":"\n  - `RustHapticDriver_Stop()`","range":{"startLineNumber":10,"startColumn":1,"endLineNumber":10,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\resources\\input\\rust_haptic_profile.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":909,"edits":[{"text":"\n      \"y\": 0","range":{"startLineNumber":16,"startColumn":1,"endLineNumber":16,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":910,"edits":[{"text":"\n- Sends packets to firmware through shared `hid-bridge`","range":{"startLineNumber":11,"startColumn":1,"endLineNumber":11,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\resources\\input\\rust_haptic_profile.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":911,"edits":[{"text":"\n    }","range":{"startLineNumber":17,"startColumn":1,"endLineNumber":17,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":912,"edits":[{"text":"\n","range":{"startLineNumber":12,"startColumn":1,"endLineNumber":12,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\resources\\input\\rust_haptic_profile.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":913,"edits":[{"text":"\n  },","range":{"startLineNumber":18,"startColumn":1,"endLineNumber":18,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":914,"edits":[{"text":"\n## Build","range":{"startLineNumber":13,"startColumn":1,"endLineNumber":13,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\resources\\input\\rust_haptic_profile.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":915,"edits":[{"text":"\n  \"input_bindingui_right\": {","range":{"startLineNumber":19,"startColumn":1,"endLineNumber":19,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":916,"edits":[{"text":"\n","range":{"startLineNumber":14,"startColumn":1,"endLineNumber":14,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\resources\\input\\rust_haptic_profile.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":917,"edits":[{"text":"\n    \"image\": \"{rust_haptic_driver}/icons/haptic_controller_right.svg\",","range":{"startLineNumber":20,"startColumn":1,"endLineNumber":20,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":918,"edits":[{"text":"\nFrom workspace root:","range":{"startLineNumber":15,"startColumn":1,"endLineNumber":15,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\resources\\input\\rust_haptic_profile.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":919,"edits":[{"text":"\n    \"transform\": {","range":{"startLineNumber":21,"startColumn":1,"endLineNumber":21,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":920,"edits":[{"text":"\n","range":{"startLineNumber":16,"startColumn":1,"endLineNumber":16,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\resources\\input\\rust_haptic_profile.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":921,"edits":[{"text":"\n      \"scale\": 1.0,","range":{"startLineNumber":22,"startColumn":1,"endLineNumber":22,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":922,"edits":[{"text":"\n- `cargo build -p rust-haptic-driver --release`","range":{"startLineNumber":17,"startColumn":1,"endLineNumber":17,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\resources\\input\\rust_haptic_profile.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":923,"edits":[{"text":"\n      \"x\": 0,","range":{"startLineNumber":23,"startColumn":1,"endLineNumber":23,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":924,"edits":[{"text":"\n","range":{"startLineNumber":18,"startColumn":1,"endLineNumber":18,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\resources\\input\\rust_haptic_profile.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":925,"edits":[{"text":"\n      \"y\": 0","range":{"startLineNumber":24,"startColumn":1,"endLineNumber":24,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":926,"edits":[{"text":"\nOutput DLL path:","range":{"startLineNumber":19,"startColumn":1,"endLineNumber":19,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\resources\\input\\rust_haptic_profile.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":927,"edits":[{"text":"\n    }","range":{"startLineNumber":25,"startColumn":1,"endLineNumber":25,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":928,"edits":[{"text":"\n","range":{"startLineNumber":20,"startColumn":1,"endLineNumber":20,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\resources\\input\\rust_haptic_profile.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":929,"edits":[{"text":"\n  }","range":{"startLineNumber":26,"startColumn":1,"endLineNumber":26,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":930,"edits":[{"text":"\n- `target/release/rust_haptic_driver.dll`","range":{"startLineNumber":21,"startColumn":1,"endLineNumber":21,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\resources\\input\\rust_haptic_profile.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":931,"edits":[{"text":"\n}","range":{"startLineNumber":27,"startColumn":1,"endLineNumber":27,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":932,"edits":[{"text":"\n","range":{"startLineNumber":22,"startColumn":1,"endLineNumber":22,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\resources\\input\\rust_haptic_profile.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":933,"edits":[{"text":"\n","range":{"startLineNumber":28,"startColumn":1,"endLineNumber":28,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":934,"edits":[{"text":"\n## SteamVR layout","range":{"startLineNumber":23,"startColumn":1,"endLineNumber":23,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":935,"edits":[{"text":"\n","range":{"startLineNumber":24,"startColumn":1,"endLineNumber":24,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":936,"edits":[{"text":"\nDriver folder should include:","range":{"startLineNumber":25,"startColumn":1,"endLineNumber":25,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":937,"edits":[{"text":"\n","range":{"startLineNumber":26,"startColumn":1,"endLineNumber":26,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":938,"edits":[{"text":"\n- `driver.vrdrivermanifest`","range":{"startLineNumber":27,"startColumn":1,"endLineNumber":27,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":939,"edits":[{"text":"\n- `resources/`","range":{"startLineNumber":28,"startColumn":1,"endLineNumber":28,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":940,"edits":[{"text":"\n- `bin/win64/driver_rust_haptic_driver.dll`","range":{"startLineNumber":29,"startColumn":1,"endLineNumber":29,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":941,"edits":[{"text":"\n","range":{"startLineNumber":30,"startColumn":1,"endLineNumber":30,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":942,"edits":[{"text":"\n## Register with SteamVR","range":{"startLineNumber":31,"startColumn":1,"endLineNumber":31,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":943,"edits":[{"text":"\n","range":{"startLineNumber":32,"startColumn":1,"endLineNumber":32,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":944,"edits":[{"text":"\nTypical command:","range":{"startLineNumber":33,"startColumn":1,"endLineNumber":33,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":945,"edits":[{"text":"\n","range":{"startLineNumber":34,"startColumn":1,"endLineNumber":34,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":946,"edits":[{"text":"\n- `vrpathreg.exe adddriver <path-to-openvr-driver/rust-haptic-driver>`","range":{"startLineNumber":35,"startColumn":1,"endLineNumber":35,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":947,"edits":[{"text":"\n","range":{"startLineNumber":36,"startColumn":1,"endLineNumber":36,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":948,"edits":[{"text":"\n## Next implementation step","range":{"startLineNumber":37,"startColumn":1,"endLineNumber":37,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":949,"edits":[{"text":"\n","range":{"startLineNumber":38,"startColumn":1,"endLineNumber":38,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":950,"edits":[{"text":"\nImplement concrete C++ ABI-compatible vtables for:","range":{"startLineNumber":39,"startColumn":1,"endLineNumber":39,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":951,"edits":[{"text":"\n","range":{"startLineNumber":40,"startColumn":1,"endLineNumber":40,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":952,"edits":[{"text":"\n- `IServerTrackedDeviceProvider`","range":{"startLineNumber":41,"startColumn":1,"endLineNumber":41,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":953,"edits":[{"text":"\n- `ITrackedDeviceServerDriver`","range":{"startLineNumber":42,"startColumn":1,"endLineNumber":42,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":954,"edits":[{"text":"\n","range":{"startLineNumber":43,"startColumn":1,"endLineNumber":43,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":955,"edits":[{"text":"\nand route `TriggerHapticVibration` events to `RustHapticDriver_SendAmplitude` logic.","range":{"startLineNumber":44,"startColumn":1,"endLineNumber":44,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":956,"edits":[{"text":"\n","range":{"startLineNumber":45,"startColumn":1,"endLineNumber":45,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\resources\\input\\rust_haptic_profile.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":958,"edits":[{"text":"{\r\n  \"jsonid\": \"input_profile\",\r\n  \"controller_type\": \"rust_haptic_driver\",\r\n  \"input_bindingui_mode\": \"controller_handed\",\n  \"input_source\": {\r\n    \"haptic\": {\r\n      \"type\": \"vibration\",\r\n      \"binding_image_point\": [0.5, 0.5]\r\n    }\r\n  }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":13,"endColumn":1}},{"text":"","range":{"startLineNumber":13,"startColumn":1,"endLineNumber":29,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","scheme":"file"},"requestId":"request_c21a4968-1030-4d03-9ff1-b0cf37dc305a","epoch":960,"edits":[{"text":"use core::ffi::c_void;\r\nuse core::ffi::CStr;\r\nuse std::os::raw::c_char;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\nconst SERVER_PROVIDER_INTERFACE: &str = \"IServerTrackedDeviceProvider_005\";\r\n\r\n#[repr(i32)]\r\nenum DriverReturnCode {\r\n    None = 0,\r\n    Unknown = 1,\r\n    InitInterfaceNotFound = 105,\r\n}\r\n\r\n#[repr(C)]\r\nstruct StubProvider;\n\nstatic STUB_PROVIDER: StubProvider = StubProvider;\n\r\nfn set_return_code(out: *mut i32, code: DriverReturnCode) {\r\n    if !out.is_null() {\r\n        // SAFETY: caller owns output pointer according to OpenVR factory ABI.\r\n        unsafe {\r\n            *out = code as i32;\r\n        }\r\n    }\r\n}\r\n\r\nfn read_interface_name(ptr: *const i8) -> Option<&'static str> {\r\n    if ptr.is_null() {\r\n        return None;\r\n    }\r\n\r\n    // SAFETY: OpenVR passes a NUL-terminated C string.\r\n    let cstr = unsafe { CStr::from_ptr(ptr) };\r\n    let Ok(name) = cstr.to_str() else {\r\n        return None;\r\n    };\r\n\r\n    // Keep only known literals in static storage path.\r\n    if name == SERVER_PROVIDER_INTERFACE {\r\n        Some(SERVER_PROVIDER_INTERFACE)\r\n    } else {\r\n        None\r\n    }\r\n}\r\n\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn HmdDriverFactory(\r\n    p_interface_name: *const c_char,\r\n    p_return_code: *mut i32,\r\n) -> *mut c_void {\r\n    let Some(interface_name) = read_interface_name(p_interface_name) else {\r\n        set_return_code(p_return_code, DriverReturnCode::InitInterfaceNotFound);\r\n        return core::ptr::null_mut();\r\n    };\r\n\r\n    if interface_name == SERVER_PROVIDER_INTERFACE {\r\n        set_return_code(p_return_code, DriverReturnCode::None);\r\n        return (&STUB_PROVIDER as *const StubProvider).cast_mut().cast();\r\n    }\r\n\r\n    set_return_code(p_return_code, DriverReturnCode::Unknown);\r\n    core::ptr::null_mut()\r\n}\r\n\r\n/// Test-only export for early integration while full OpenVR vtable glue is in progress.\r\n/// Returns 0 on success, non-zero on failure.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn RustHapticDriver_SendAmplitude(amplitude: f32) -> i32 {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    let packet = HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0);\r\n\r\n    match hid_bridge::HidBridge::new().and_then(|bridge| {\r\n        bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet)\r\n    }) {\r\n        Ok(()) => 0,\r\n        Err(_) => 1,\r\n    }\r\n}\r\n\r\n/// Explicit stop command helper for testing and recovery.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn RustHapticDriver_Stop() -> i32 {\r\n    let packet = HapticPacket::new(command::STOP, 0, 0, 0);\r\n\r\n    match hid_bridge::HidBridge::new().and_then(|bridge| {\r\n        bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet)\r\n    }) {\r\n        Ok(()) => 0,\r\n        Err(_) => 1,\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":97,"endColumn":1}},{"text":"","range":{"startLineNumber":97,"startColumn":1,"endLineNumber":102,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\driver.vrdrivermanifest","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/driver.vrdrivermanifest","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/driver.vrdrivermanifest","scheme":"file"},"requestId":"request_8b8bee65-e67e-436f-8cf9-9e7a5e2a4cbf","epoch":964,"edits":[{"text":"{\r\n  \"always_activate\": true,\r\n  \"name\": \"big_haptic_driver\",\n  \"resource_only\": false,\r\n  \"resource_directory\": \"resources\"\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":8,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\resources\\input\\rust_haptic_profile.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/input/rust_haptic_profile.json","scheme":"file"},"requestId":"request_8b8bee65-e67e-436f-8cf9-9e7a5e2a4cbf","epoch":967,"edits":[{"text":"{\r\n  \"jsonid\": \"input_profile\",\r\n  \"controller_type\": \"big_haptic_driver\",\n  \"input_bindingui_mode\": \"controller_handed\",\r\n  \"input_source\": {\r\n    \"haptic\": {\r\n      \"type\": \"vibration\",\r\n      \"binding_image_point\": [0.5, 0.5]\r\n    }\r\n  }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":13,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\resources\\localization\\en_us.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/localization/en_us.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/resources/localization/en_us.json","scheme":"file"},"requestId":"request_8b8bee65-e67e-436f-8cf9-9e7a5e2a4cbf","epoch":970,"edits":[{"text":"{\r\n  \"language_tag\": \"en_US\",\r\n  \"big_haptic_driver\": \"Big Haptic Driver\",\n  \"big_haptic_driver_input_profile\": \"Big Haptic Driver Input\",\n  \"/devices/big_haptic_driver/prop/modelnumber_string\": \"QT Py SAMD21 Haptic\",\n  \"/devices/big_haptic_driver/prop/serialnumber_string\": \"qtpy-samd21-haptic\"\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":9,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/Cargo.toml","scheme":"file"},"requestId":"request_c2dbf0bc-9220-41ac-8de0-ebcec63de2a8","epoch":974,"edits":[{"text":"[package]\r\nname = \"big-haptic-driver\"\nversion = \"0.1.0\"\r\nedition.workspace = true\r\nlicense.workspace = true\r\nauthors.workspace = true\r\n\r\n[lib]\r\ncrate-type = [\"cdylib\"]\r\nname = \"big_haptic_driver\"\n\r\n[dependencies]\r\nhid-bridge = { path = \"../../host/hid-bridge\" }\r\nhaptics-protocol = { path = \"../../shared/haptics-protocol\" }\r\nthiserror.workspace = true\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":17,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","scheme":"file"},"requestId":"request_c2dbf0bc-9220-41ac-8de0-ebcec63de2a8","epoch":977,"edits":[{"text":"use core::ffi::c_void;\r\nuse core::ffi::CStr;\r\nuse std::os::raw::c_char;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\nconst SERVER_PROVIDER_INTERFACE: &str = \"IServerTrackedDeviceProvider_005\";\r\n\r\n#[repr(i32)]\r\nenum DriverReturnCode {\r\n    None = 0,\r\n    Unknown = 1,\r\n    InitInterfaceNotFound = 105,\r\n}\r\n\r\n#[repr(C)]\r\nstruct StubProvider;\r\n\r\nstatic STUB_PROVIDER: StubProvider = StubProvider;\r\n\r\nfn set_return_code(out: *mut i32, code: DriverReturnCode) {\r\n    if !out.is_null() {\r\n        // SAFETY: caller owns output pointer according to OpenVR factory ABI.\r\n        unsafe {\r\n            *out = code as i32;\r\n        }\r\n    }\r\n}\r\n\r\nfn read_interface_name(ptr: *const i8) -> Option<&'static str> {\r\n    if ptr.is_null() {\r\n        return None;\r\n    }\r\n\r\n    // SAFETY: OpenVR passes a NUL-terminated C string.\r\n    let cstr = unsafe { CStr::from_ptr(ptr) };\r\n    let Ok(name) = cstr.to_str() else {\r\n        return None;\r\n    };\r\n\r\n    // Keep only known literals in static storage path.\r\n    if name == SERVER_PROVIDER_INTERFACE {\r\n        Some(SERVER_PROVIDER_INTERFACE)\r\n    } else {\r\n        None\r\n    }\r\n}\r\n\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn HmdDriverFactory(\r\n    p_interface_name: *const c_char,\r\n    p_return_code: *mut i32,\r\n) -> *mut c_void {\r\n    let Some(interface_name) = read_interface_name(p_interface_name) else {\r\n        set_return_code(p_return_code, DriverReturnCode::InitInterfaceNotFound);\r\n        return core::ptr::null_mut();\r\n    };\r\n\r\n    if interface_name == SERVER_PROVIDER_INTERFACE {\r\n        set_return_code(p_return_code, DriverReturnCode::None);\r\n        return (&STUB_PROVIDER as *const StubProvider).cast_mut().cast();\r\n    }\r\n\r\n    set_return_code(p_return_code, DriverReturnCode::Unknown);\r\n    core::ptr::null_mut()\r\n}\r\n\r\n/// Test-only export for early integration while full OpenVR vtable glue is in progress.\r\n/// Returns 0 on success, non-zero on failure.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_SendAmplitude(amplitude: f32) -> i32 {\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    let packet = HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0);\r\n\r\n    match hid_bridge::HidBridge::new().and_then(|bridge| {\r\n        bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet)\r\n    }) {\r\n        Ok(()) => 0,\r\n        Err(_) => 1,\r\n    }\r\n}\r\n\r\n/// Explicit stop command helper for testing and recovery.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_Stop() -> i32 {\n    let packet = HapticPacket::new(command::STOP, 0, 0, 0);\r\n\r\n    match hid_bridge::HidBridge::new().and_then(|bridge| {\r\n        bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet)\r\n    }) {\r\n        Ok(()) => 0,\r\n        Err(_) => 1,\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":97,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","scheme":"file"},"requestId":"request_c2dbf0bc-9220-41ac-8de0-ebcec63de2a8","epoch":980,"edits":[{"text":"# rust-haptic-driver\r\n\r\nInitial OpenVR driver scaffold in Rust.\r\n\r\n## Current capabilities\r\n\r\n- Exports `HmdDriverFactory` with minimal interface-name validation\r\n- Exposes DLL-callable haptics test exports:\r\n  - `BigHapticDriver_SendAmplitude(float)`\n  - `BigHapticDriver_Stop()`\n- Sends packets to firmware through shared `hid-bridge`\r\n\r\n## Build\r\n\r\nFrom workspace root:\r\n\r\n- `cargo build -p big-haptic-driver --release`\n\r\nOutput DLL path:\r\n\r\n- `target/release/big_haptic_driver.dll`\n\r\n## SteamVR layout\r\n\r\nDriver folder should include:\r\n\r\n- `driver.vrdrivermanifest`\r\n- `resources/`\r\n- `bin/win64/driver_big_haptic_driver.dll`\n\r\n## Register with SteamVR\r\n\r\nTypical command:\r\n\r\n- `vrpathreg.exe adddriver <path-to-openvr-driver/rust-haptic-driver>`\r\n\r\n## Next implementation step\r\n\r\nImplement concrete C++ ABI-compatible vtables for:\r\n\r\n- `IServerTrackedDeviceProvider`\r\n- `ITrackedDeviceServerDriver`\r\n\r\nand route `TriggerHapticVibration` events to `RustHapticDriver_SendAmplitude` logic.\r\nand route `TriggerHapticVibration` events to `BigHapticDriver_SendAmplitude` logic.\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":47,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","scheme":"file"},"requestId":"request_c2dbf0bc-9220-41ac-8de0-ebcec63de2a8","epoch":982,"edits":[{"text":"# big-haptic-driver\n\r\nInitial OpenVR driver scaffold in Rust.\r\n\r\n## Current capabilities\r\n\r\n- Exports `HmdDriverFactory` with minimal interface-name validation\r\n- Exposes DLL-callable haptics test exports:\r\n  - `BigHapticDriver_SendAmplitude(float)`\r\n  - `BigHapticDriver_Stop()`\r\n- Sends packets to firmware through shared `hid-bridge`\r\n\r\n## Build\r\n\r\nFrom workspace root:\r\n\r\n- `cargo build -p big-haptic-driver --release`\r\n\r\nOutput DLL path:\r\n\r\n- `target/release/big_haptic_driver.dll`\r\n\r\n## SteamVR layout\r\n\r\nDriver folder should include:\r\n\r\n- `driver.vrdrivermanifest`\r\n- `resources/`\r\n- `bin/win64/driver_big_haptic_driver.dll`\r\n\r\n## Register with SteamVR\r\n\r\nTypical command:\r\n\r\n- `vrpathreg.exe adddriver <path-to-openvr-driver/rust-haptic-driver>`\r\n\r\n## Next implementation step\r\n\r\nImplement concrete C++ ABI-compatible vtables for:\r\n\r\n- `IServerTrackedDeviceProvider`\r\n- `ITrackedDeviceServerDriver`\r\n\r\nand route `TriggerHapticVibration` events to `BigHapticDriver_SendAmplitude` logic.\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":46,"endColumn":1}},{"text":"","range":{"startLineNumber":46,"startColumn":1,"endLineNumber":47,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","scheme":"file"},"requestId":"request_05ad5ed7-be85-45fb-af40-007422c0606f","epoch":986,"edits":[{"text":"use core::ffi::c_void;\r\nuse core::ffi::CStr;\r\nuse core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU32, Ordering};\nuse std::os::raw::c_char;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\nconst SERVER_PROVIDER_INTERFACE: &str = \"IServerTrackedDeviceProvider_005\";\r\nconst TRACKED_DEVICE_INTERFACE: &str = \"ITrackedDeviceServerDriver_005\";\n\r\n#[repr(i32)]\r\nenum DriverReturnCode {\r\n    None = 0,\r\n    Unknown = 1,\r\n    InitInterfaceNotFound = 105,\r\n}\r\n\r\ntype EvRInitError = i32;\nconst VR_INIT_ERROR_NONE: EvRInitError = 0;\n\n#[repr(C)]\n#[derive(Clone, Copy)]\nstruct DriverPose {\n    // Placeholder for OpenVR DriverPose_t. Will be replaced with exact layout.\n    _reserved: [u8; 128],\n}\n\n#[repr(C)]\r\nstruct ServerTrackedDeviceProviderVTable {\n    init: extern \"C\" fn(*mut ServerTrackedDeviceProvider, *mut c_void) -> EvRInitError,\n    cleanup: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\n    get_interface_versions:\n        extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> *const *const c_char,\n    run_frame: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\n    should_block_standby_mode: extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> bool,\n    enter_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\n    leave_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\n}\n\r\n#[repr(C)]\nstruct TrackedDeviceServerDriverVTable {\n    activate: extern \"C\" fn(*mut TrackedDeviceServerDriver, u32) -> EvRInitError,\n    deactivate: extern \"C\" fn(*mut TrackedDeviceServerDriver),\n    enter_standby: extern \"C\" fn(*mut TrackedDeviceServerDriver),\n    get_component:\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char) -> *mut c_void,\n    debug_request:\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char, *mut c_char, u32),\n    get_pose: extern \"C\" fn(*mut TrackedDeviceServerDriver) -> DriverPose,\n}\n\n#[repr(C)]\nstruct ServerTrackedDeviceProvider {\n    vtable: *const ServerTrackedDeviceProviderVTable,\n}\n\n#[repr(C)]\nstruct TrackedDeviceServerDriver {\n    vtable: *const TrackedDeviceServerDriverVTable,\n}\n\nstatic HOST_CONTEXT: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\nstatic DEVICE_INDEX: AtomicU32 = AtomicU32::new(u32::MAX);\nstatic DEVICE_ACTIVE: AtomicBool = AtomicBool::new(false);\n\nstatic PROVIDER_INTERFACE_VERSIONS: [*const c_char; 3] = [\n    c\"IServerTrackedDeviceProvider_005\".as_ptr(),\n    c\"ITrackedDeviceServerDriver_005\".as_ptr(),\n    core::ptr::null(),\n];\n\nextern \"C\" fn provider_init(\n    _this: *mut ServerTrackedDeviceProvider,\n    driver_context: *mut c_void,\n) -> EvRInitError {\n    HOST_CONTEXT.store(driver_context, Ordering::SeqCst);\n    VR_INIT_ERROR_NONE\n}\n\nextern \"C\" fn provider_cleanup(_this: *mut ServerTrackedDeviceProvider) {\n    HOST_CONTEXT.store(core::ptr::null_mut(), Ordering::SeqCst);\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\n    let _ = send_stop();\n}\n\nextern \"C\" fn provider_get_interface_versions(\n    _this: *mut ServerTrackedDeviceProvider,\n) -> *const *const c_char {\n    PROVIDER_INTERFACE_VERSIONS.as_ptr()\n}\n\nextern \"C\" fn provider_run_frame(_this: *mut ServerTrackedDeviceProvider) {\n    // TODO: Pump driver events from vrserver and handle haptic vibration events.\n}\n\nextern \"C\" fn provider_should_block_standby_mode(_this: *mut ServerTrackedDeviceProvider) -> bool {\n    false\n}\n\nextern \"C\" fn provider_enter_standby(_this: *mut ServerTrackedDeviceProvider) {}\n\nextern \"C\" fn provider_leave_standby(_this: *mut ServerTrackedDeviceProvider) {}\n\nextern \"C\" fn device_activate(_this: *mut TrackedDeviceServerDriver, object_id: u32) -> EvRInitError {\n    DEVICE_INDEX.store(object_id, Ordering::SeqCst);\n    DEVICE_ACTIVE.store(true, Ordering::SeqCst);\n    VR_INIT_ERROR_NONE\n}\n\nextern \"C\" fn device_deactivate(_this: *mut TrackedDeviceServerDriver) {\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\n    let _ = send_stop();\n}\n\nextern \"C\" fn device_enter_standby(_this: *mut TrackedDeviceServerDriver) {\n    let _ = send_stop();\n}\n\nextern \"C\" fn device_get_component(\n    _this: *mut TrackedDeviceServerDriver,\n    _component_name: *const c_char,\n) -> *mut c_void {\n    core::ptr::null_mut()\n}\n\nextern \"C\" fn device_debug_request(\n    _this: *mut TrackedDeviceServerDriver,\n    _request: *const c_char,\n    response: *mut c_char,\n    response_size: u32,\n) {\n    if response.is_null() || response_size == 0 {\n        return;\n    }\n\n    // Always return an empty C-string for now.\n    // SAFETY: `response` points to caller-provided writable buffer.\n    unsafe {\n        *response = 0;\n    }\n}\n\nextern \"C\" fn device_get_pose(_this: *mut TrackedDeviceServerDriver) -> DriverPose {\n    DriverPose {\n        _reserved: [0; 128],\n    }\n}\n\nstatic PROVIDER_VTABLE: ServerTrackedDeviceProviderVTable = ServerTrackedDeviceProviderVTable {\n    init: provider_init,\n    cleanup: provider_cleanup,\n    get_interface_versions: provider_get_interface_versions,\n    run_frame: provider_run_frame,\n    should_block_standby_mode: provider_should_block_standby_mode,\n    enter_standby: provider_enter_standby,\n    leave_standby: provider_leave_standby,\n};\n\nstatic DEVICE_VTABLE: TrackedDeviceServerDriverVTable = TrackedDeviceServerDriverVTable {\n    activate: device_activate,\n    deactivate: device_deactivate,\n    enter_standby: device_enter_standby,\n    get_component: device_get_component,\n    debug_request: device_debug_request,\n    get_pose: device_get_pose,\n};\n\nstatic PROVIDER: ServerTrackedDeviceProvider = ServerTrackedDeviceProvider {\n    vtable: &PROVIDER_VTABLE,\n};\n\n#[allow(dead_code)]\nstatic DEVICE: TrackedDeviceServerDriver = TrackedDeviceServerDriver {\n    vtable: &DEVICE_VTABLE,\n};\n\nfn send_packet(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\n    hid_bridge::HidBridge::new()\n        .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\n}\n\nfn send_stop() -> Result<(), hid_bridge::BridgeError> {\n    send_packet(HapticPacket::new(command::STOP, 0, 0, 0))\n}\n\nfn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\n    let amp = amplitude.clamp(0.0, 1.0);\n    let intensity = (amp * 255.0).round() as u8;\n    send_packet(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\n}\n\r\nfn set_return_code(out: *mut i32, code: DriverReturnCode) {\r\n    if !out.is_null() {\r\n        // SAFETY: caller owns output pointer according to OpenVR factory ABI.\r\n        unsafe {\r\n            *out = code as i32;\r\n        }\r\n    }\r\n}\r\n\r\nfn read_interface_name(ptr: *const i8) -> Option<&'static str> {\r\n    if ptr.is_null() {\r\n        return None;\r\n    }\r\n\r\n    // SAFETY: OpenVR passes a NUL-terminated C string.\r\n    let cstr = unsafe { CStr::from_ptr(ptr) };\r\n    let Ok(name) = cstr.to_str() else {\r\n        return None;\r\n    };\r\n\r\n    // Keep only known literals in static storage path.\r\n    if name == SERVER_PROVIDER_INTERFACE {\r\n        Some(SERVER_PROVIDER_INTERFACE)\r\n    } else {\r\n        None\r\n    }\r\n}\r\n\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn HmdDriverFactory(\r\n    p_interface_name: *const c_char,\r\n    p_return_code: *mut i32,\r\n) -> *mut c_void {\r\n    let Some(interface_name) = read_interface_name(p_interface_name) else {\r\n        set_return_code(p_return_code, DriverReturnCode::InitInterfaceNotFound);\r\n        return core::ptr::null_mut();\r\n    };\r\n\r\n    if interface_name == SERVER_PROVIDER_INTERFACE {\n        set_return_code(p_return_code, DriverReturnCode::None);\r\n        return (&PROVIDER as *const ServerTrackedDeviceProvider)\n            .cast_mut()\n            .cast();\n    }\r\n\r\n    set_return_code(p_return_code, DriverReturnCode::Unknown);\r\n    core::ptr::null_mut()\r\n}\r\n\r\n/// Test-only export for early integration while full OpenVR vtable glue is in progress.\r\n/// Returns 0 on success, non-zero on failure.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_SendAmplitude(amplitude: f32) -> i32 {\r\n    match send_amplitude(amplitude) {\n        Ok(()) => 0,\r\n        Err(_) => 1,\r\n    }\r\n}\r\n\r\n/// Explicit stop command helper for testing and recovery.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_Stop() -> i32 {\r\n    match send_stop() {\n        Ok(()) => 0,\r\n        Err(_) => 1,\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":262,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","scheme":"file"},"requestId":"request_05ad5ed7-be85-45fb-af40-007422c0606f","epoch":988,"edits":[{"text":"use core::ffi::c_void;\r\nuse core::ffi::CStr;\r\nuse core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU32, Ordering};\r\nuse std::os::raw::c_char;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\nconst SERVER_PROVIDER_INTERFACE: &str = \"IServerTrackedDeviceProvider_005\";\r\nconst TRACKED_DEVICE_INTERFACE: &str = \"ITrackedDeviceServerDriver_005\";\r\n\r\n#[repr(i32)]\r\nenum DriverReturnCode {\r\n    None = 0,\r\n    Unknown = 1,\r\n    InitInterfaceNotFound = 105,\r\n}\r\n\r\ntype EvRInitError = i32;\r\nconst VR_INIT_ERROR_NONE: EvRInitError = 0;\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nstruct DriverPose {\r\n    // Placeholder for OpenVR DriverPose_t. Will be replaced with exact layout.\r\n    _reserved: [u8; 128],\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerTrackedDeviceProviderVTable {\r\n    init: extern \"C\" fn(*mut ServerTrackedDeviceProvider, *mut c_void) -> EvRInitError,\r\n    cleanup: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    get_interface_versions:\r\n        extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> *const *const c_char,\r\n    run_frame: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    should_block_standby_mode: extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> bool,\r\n    enter_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    leave_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n}\r\n\r\n#[repr(C)]\r\nstruct TrackedDeviceServerDriverVTable {\r\n    activate: extern \"C\" fn(*mut TrackedDeviceServerDriver, u32) -> EvRInitError,\r\n    deactivate: extern \"C\" fn(*mut TrackedDeviceServerDriver),\r\n    enter_standby: extern \"C\" fn(*mut TrackedDeviceServerDriver),\r\n    get_component:\r\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char) -> *mut c_void,\r\n    debug_request:\r\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char, *mut c_char, u32),\r\n    get_pose: extern \"C\" fn(*mut TrackedDeviceServerDriver) -> DriverPose,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerTrackedDeviceProvider {\r\n    vtable: *const ServerTrackedDeviceProviderVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct TrackedDeviceServerDriver {\r\n    vtable: *const TrackedDeviceServerDriverVTable,\r\n}\r\n\r\nstatic HOST_CONTEXT: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DEVICE_INDEX: AtomicU32 = AtomicU32::new(u32::MAX);\r\nstatic DEVICE_ACTIVE: AtomicBool = AtomicBool::new(false);\r\n\nconst PROVIDER_IFACE_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_005\\0\";\nconst DEVICE_IFACE_CSTR: &[u8] = b\"ITrackedDeviceServerDriver_005\\0\";\n\r\nstatic PROVIDER_INTERFACE_VERSIONS: [*const c_char; 3] = [\r\n    PROVIDER_IFACE_CSTR.as_ptr().cast(),\n    DEVICE_IFACE_CSTR.as_ptr().cast(),\n    core::ptr::null(),\r\n];\r\n\r\nextern \"C\" fn provider_init(\r\n    _this: *mut ServerTrackedDeviceProvider,\r\n    driver_context: *mut c_void,\r\n) -> EvRInitError {\r\n    HOST_CONTEXT.store(driver_context, Ordering::SeqCst);\r\n    VR_INIT_ERROR_NONE\r\n}\r\n\r\nextern \"C\" fn provider_cleanup(_this: *mut ServerTrackedDeviceProvider) {\r\n    HOST_CONTEXT.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\r\n    let _ = send_stop();\r\n}\r\n\r\nextern \"C\" fn provider_get_interface_versions(\r\n    _this: *mut ServerTrackedDeviceProvider,\r\n) -> *const *const c_char {\r\n    PROVIDER_INTERFACE_VERSIONS.as_ptr()\r\n}\r\n\r\nextern \"C\" fn provider_run_frame(_this: *mut ServerTrackedDeviceProvider) {\r\n    // TODO: Pump driver events from vrserver and handle haptic vibration events.\r\n}\r\n\r\nextern \"C\" fn provider_should_block_standby_mode(_this: *mut ServerTrackedDeviceProvider) -> bool {\r\n    false\r\n}\r\n\r\nextern \"C\" fn provider_enter_standby(_this: *mut ServerTrackedDeviceProvider) {}\r\n\r\nextern \"C\" fn provider_leave_standby(_this: *mut ServerTrackedDeviceProvider) {}\r\n\r\nextern \"C\" fn device_activate(_this: *mut TrackedDeviceServerDriver, object_id: u32) -> EvRInitError {\r\n    DEVICE_INDEX.store(object_id, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(true, Ordering::SeqCst);\r\n    VR_INIT_ERROR_NONE\r\n}\r\n\r\nextern \"C\" fn device_deactivate(_this: *mut TrackedDeviceServerDriver) {\r\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\r\n    let _ = send_stop();\r\n}\r\n\r\nextern \"C\" fn device_enter_standby(_this: *mut TrackedDeviceServerDriver) {\r\n    let _ = send_stop();\r\n}\r\n\r\nextern \"C\" fn device_get_component(\r\n    _this: *mut TrackedDeviceServerDriver,\r\n    _component_name: *const c_char,\r\n) -> *mut c_void {\r\n    core::ptr::null_mut()\r\n}\r\n\r\nextern \"C\" fn device_debug_request(\r\n    _this: *mut TrackedDeviceServerDriver,\r\n    _request: *const c_char,\r\n    response: *mut c_char,\r\n    response_size: u32,\r\n) {\r\n    if response.is_null() || response_size == 0 {\r\n        return;\r\n    }\r\n\r\n    // Always return an empty C-string for now.\r\n    // SAFETY: `response` points to caller-provided writable buffer.\r\n    unsafe {\r\n        *response = 0;\r\n    }\r\n}\r\n\r\nextern \"C\" fn device_get_pose(_this: *mut TrackedDeviceServerDriver) -> DriverPose {\r\n    DriverPose {\r\n        _reserved: [0; 128],\r\n    }\r\n}\r\n\r\nstatic PROVIDER_VTABLE: ServerTrackedDeviceProviderVTable = ServerTrackedDeviceProviderVTable {\r\n    init: provider_init,\r\n    cleanup: provider_cleanup,\r\n    get_interface_versions: provider_get_interface_versions,\r\n    run_frame: provider_run_frame,\r\n    should_block_standby_mode: provider_should_block_standby_mode,\r\n    enter_standby: provider_enter_standby,\r\n    leave_standby: provider_leave_standby,\r\n};\r\n\r\nstatic DEVICE_VTABLE: TrackedDeviceServerDriverVTable = TrackedDeviceServerDriverVTable {\r\n    activate: device_activate,\r\n    deactivate: device_deactivate,\r\n    enter_standby: device_enter_standby,\r\n    get_component: device_get_component,\r\n    debug_request: device_debug_request,\r\n    get_pose: device_get_pose,\r\n};\r\n\r\nstatic PROVIDER: ServerTrackedDeviceProvider = ServerTrackedDeviceProvider {\r\n    vtable: &PROVIDER_VTABLE,\r\n};\r\n\r\n#[allow(dead_code)]\r\nstatic DEVICE: TrackedDeviceServerDriver = TrackedDeviceServerDriver {\r\n    vtable: &DEVICE_VTABLE,\r\n};\r\n\r\nfn send_packet(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    hid_bridge::HidBridge::new()\r\n        .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n}\r\n\r\nfn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\nfn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    send_packet(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn set_return_code(out: *mut i32, code: DriverReturnCode) {\r\n    if !out.is_null() {\r\n        // SAFETY: caller owns output pointer according to OpenVR factory ABI.\r\n        unsafe {\r\n            *out = code as i32;\r\n        }\r\n    }\r\n}\r\n\r\nfn read_interface_name(ptr: *const i8) -> Option<&'static str> {\r\n    if ptr.is_null() {\r\n        return None;\r\n    }\r\n\r\n    // SAFETY: OpenVR passes a NUL-terminated C string.\r\n    let cstr = unsafe { CStr::from_ptr(ptr) };\r\n    let Ok(name) = cstr.to_str() else {\r\n        return None;\r\n    };\r\n\r\n    // Keep only known literals in static storage path.\r\n    if name == SERVER_PROVIDER_INTERFACE {\r\n        Some(SERVER_PROVIDER_INTERFACE)\r\n    } else {\r\n        None\r\n    }\r\n}\r\n\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn HmdDriverFactory(\r\n    p_interface_name: *const c_char,\r\n    p_return_code: *mut i32,\r\n) -> *mut c_void {\r\n    let Some(interface_name) = read_interface_name(p_interface_name) else {\r\n        set_return_code(p_return_code, DriverReturnCode::InitInterfaceNotFound);\r\n        return core::ptr::null_mut();\r\n    };\r\n\r\n    if interface_name == SERVER_PROVIDER_INTERFACE {\r\n        set_return_code(p_return_code, DriverReturnCode::None);\r\n        return (&PROVIDER as *const ServerTrackedDeviceProvider)\r\n            .cast_mut()\r\n            .cast();\r\n    }\r\n\r\n    set_return_code(p_return_code, DriverReturnCode::Unknown);\r\n    core::ptr::null_mut()\r\n}\r\n\r\n/// Test-only export for early integration while full OpenVR vtable glue is in progress.\r\n/// Returns 0 on success, non-zero on failure.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_SendAmplitude(amplitude: f32) -> i32 {\r\n    match send_amplitude(amplitude) {\r\n        Ok(()) => 0,\r\n        Err(_) => 1,\r\n    }\r\n}\r\n\r\n/// Explicit stop command helper for testing and recovery.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_Stop() -> i32 {\r\n    match send_stop() {\r\n        Ok(()) => 0,\r\n        Err(_) => 1,\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":265,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","scheme":"file"},"requestId":"request_05ad5ed7-be85-45fb-af40-007422c0606f","epoch":990,"edits":[{"text":"use core::ffi::c_void;\r\nuse core::ffi::CStr;\r\nuse core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU32, Ordering};\r\nuse std::os::raw::c_char;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\nconst SERVER_PROVIDER_INTERFACE: &str = \"IServerTrackedDeviceProvider_005\";\r\nconst TRACKED_DEVICE_INTERFACE: &str = \"ITrackedDeviceServerDriver_005\";\r\n\r\n#[repr(i32)]\r\nenum DriverReturnCode {\r\n    None = 0,\r\n    Unknown = 1,\r\n    InitInterfaceNotFound = 105,\r\n}\r\n\r\ntype EvRInitError = i32;\r\nconst VR_INIT_ERROR_NONE: EvRInitError = 0;\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nstruct DriverPose {\r\n    // Placeholder for OpenVR DriverPose_t. Will be replaced with exact layout.\r\n    _reserved: [u8; 128],\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerTrackedDeviceProviderVTable {\r\n    init: extern \"C\" fn(*mut ServerTrackedDeviceProvider, *mut c_void) -> EvRInitError,\r\n    cleanup: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    get_interface_versions:\r\n        extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> *const *const c_char,\r\n    run_frame: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    should_block_standby_mode: extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> bool,\r\n    enter_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    leave_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n}\r\n\r\n#[repr(C)]\r\nstruct TrackedDeviceServerDriverVTable {\r\n    activate: extern \"C\" fn(*mut TrackedDeviceServerDriver, u32) -> EvRInitError,\r\n    deactivate: extern \"C\" fn(*mut TrackedDeviceServerDriver),\r\n    enter_standby: extern \"C\" fn(*mut TrackedDeviceServerDriver),\r\n    get_component:\r\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char) -> *mut c_void,\r\n    debug_request:\r\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char, *mut c_char, u32),\r\n    get_pose: extern \"C\" fn(*mut TrackedDeviceServerDriver) -> DriverPose,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerTrackedDeviceProvider {\r\n    vtable: usize,\n}\r\n\r\n#[repr(C)]\r\nstruct TrackedDeviceServerDriver {\r\n    vtable: usize,\n}\r\n\r\nstatic HOST_CONTEXT: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DEVICE_INDEX: AtomicU32 = AtomicU32::new(u32::MAX);\r\nstatic DEVICE_ACTIVE: AtomicBool = AtomicBool::new(false);\r\n\r\nconst PROVIDER_IFACE_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_005\\0\";\r\nconst DEVICE_IFACE_CSTR: &[u8] = b\"ITrackedDeviceServerDriver_005\\0\";\r\n\r\nstatic PROVIDER_INTERFACE_VERSIONS: [usize; 3] = [\n    PROVIDER_IFACE_CSTR.as_ptr() as usize,\n    DEVICE_IFACE_CSTR.as_ptr() as usize,\n    0,\n];\r\n\r\nextern \"C\" fn provider_init(\r\n    _this: *mut ServerTrackedDeviceProvider,\r\n    driver_context: *mut c_void,\r\n) -> EvRInitError {\r\n    HOST_CONTEXT.store(driver_context, Ordering::SeqCst);\r\n    VR_INIT_ERROR_NONE\r\n}\r\n\r\nextern \"C\" fn provider_cleanup(_this: *mut ServerTrackedDeviceProvider) {\r\n    HOST_CONTEXT.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\r\n    let _ = send_stop();\r\n}\r\n\r\nextern \"C\" fn provider_get_interface_versions(\r\n    _this: *mut ServerTrackedDeviceProvider,\r\n) -> *const *const c_char {\r\n    PROVIDER_INTERFACE_VERSIONS.as_ptr().cast()\n}\r\n\r\nextern \"C\" fn provider_run_frame(_this: *mut ServerTrackedDeviceProvider) {\r\n    // TODO: Pump driver events from vrserver and handle haptic vibration events.\r\n}\r\n\r\nextern \"C\" fn provider_should_block_standby_mode(_this: *mut ServerTrackedDeviceProvider) -> bool {\r\n    false\r\n}\r\n\r\nextern \"C\" fn provider_enter_standby(_this: *mut ServerTrackedDeviceProvider) {}\r\n\r\nextern \"C\" fn provider_leave_standby(_this: *mut ServerTrackedDeviceProvider) {}\r\n\r\nextern \"C\" fn device_activate(_this: *mut TrackedDeviceServerDriver, object_id: u32) -> EvRInitError {\r\n    DEVICE_INDEX.store(object_id, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(true, Ordering::SeqCst);\r\n    VR_INIT_ERROR_NONE\r\n}\r\n\r\nextern \"C\" fn device_deactivate(_this: *mut TrackedDeviceServerDriver) {\r\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\r\n    let _ = send_stop();\r\n}\r\n\r\nextern \"C\" fn device_enter_standby(_this: *mut TrackedDeviceServerDriver) {\r\n    let _ = send_stop();\r\n}\r\n\r\nextern \"C\" fn device_get_component(\r\n    _this: *mut TrackedDeviceServerDriver,\r\n    _component_name: *const c_char,\r\n) -> *mut c_void {\r\n    core::ptr::null_mut()\r\n}\r\n\r\nextern \"C\" fn device_debug_request(\r\n    _this: *mut TrackedDeviceServerDriver,\r\n    _request: *const c_char,\r\n    response: *mut c_char,\r\n    response_size: u32,\r\n) {\r\n    if response.is_null() || response_size == 0 {\r\n        return;\r\n    }\r\n\r\n    // Always return an empty C-string for now.\r\n    // SAFETY: `response` points to caller-provided writable buffer.\r\n    unsafe {\r\n        *response = 0;\r\n    }\r\n}\r\n\r\nextern \"C\" fn device_get_pose(_this: *mut TrackedDeviceServerDriver) -> DriverPose {\r\n    DriverPose {\r\n        _reserved: [0; 128],\r\n    }\r\n}\r\n\r\nstatic PROVIDER_VTABLE: ServerTrackedDeviceProviderVTable = ServerTrackedDeviceProviderVTable {\r\n    init: provider_init,\r\n    cleanup: provider_cleanup,\r\n    get_interface_versions: provider_get_interface_versions,\r\n    run_frame: provider_run_frame,\r\n    should_block_standby_mode: provider_should_block_standby_mode,\r\n    enter_standby: provider_enter_standby,\r\n    leave_standby: provider_leave_standby,\r\n};\r\n\r\nstatic DEVICE_VTABLE: TrackedDeviceServerDriverVTable = TrackedDeviceServerDriverVTable {\r\n    activate: device_activate,\r\n    deactivate: device_deactivate,\r\n    enter_standby: device_enter_standby,\r\n    get_component: device_get_component,\r\n    debug_request: device_debug_request,\r\n    get_pose: device_get_pose,\r\n};\r\n\r\nstatic PROVIDER: ServerTrackedDeviceProvider = ServerTrackedDeviceProvider {\r\n    vtable: (&PROVIDER_VTABLE as *const ServerTrackedDeviceProviderVTable) as usize,\n};\r\n\r\n#[allow(dead_code)]\r\nstatic DEVICE: TrackedDeviceServerDriver = TrackedDeviceServerDriver {\r\n    vtable: (&DEVICE_VTABLE as *const TrackedDeviceServerDriverVTable) as usize,\n};\r\n\r\nfn send_packet(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    hid_bridge::HidBridge::new()\r\n        .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n}\r\n\r\nfn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\nfn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    send_packet(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn set_return_code(out: *mut i32, code: DriverReturnCode) {\r\n    if !out.is_null() {\r\n        // SAFETY: caller owns output pointer according to OpenVR factory ABI.\r\n        unsafe {\r\n            *out = code as i32;\r\n        }\r\n    }\r\n}\r\n\r\nfn read_interface_name(ptr: *const i8) -> Option<&'static str> {\r\n    if ptr.is_null() {\r\n        return None;\r\n    }\r\n\r\n    // SAFETY: OpenVR passes a NUL-terminated C string.\r\n    let cstr = unsafe { CStr::from_ptr(ptr) };\r\n    let Ok(name) = cstr.to_str() else {\r\n        return None;\r\n    };\r\n\r\n    // Keep only known literals in static storage path.\r\n    if name == SERVER_PROVIDER_INTERFACE {\r\n        Some(SERVER_PROVIDER_INTERFACE)\r\n    } else {\r\n        None\r\n    }\r\n}\r\n\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn HmdDriverFactory(\r\n    p_interface_name: *const c_char,\r\n    p_return_code: *mut i32,\r\n) -> *mut c_void {\r\n    let Some(interface_name) = read_interface_name(p_interface_name) else {\r\n        set_return_code(p_return_code, DriverReturnCode::InitInterfaceNotFound);\r\n        return core::ptr::null_mut();\r\n    };\r\n\r\n    if interface_name == SERVER_PROVIDER_INTERFACE {\r\n        set_return_code(p_return_code, DriverReturnCode::None);\r\n        return (&PROVIDER as *const ServerTrackedDeviceProvider)\r\n            .cast_mut()\r\n            .cast();\r\n    }\r\n\r\n    set_return_code(p_return_code, DriverReturnCode::Unknown);\r\n    core::ptr::null_mut()\r\n}\r\n\r\n/// Test-only export for early integration while full OpenVR vtable glue is in progress.\r\n/// Returns 0 on success, non-zero on failure.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_SendAmplitude(amplitude: f32) -> i32 {\r\n    match send_amplitude(amplitude) {\r\n        Ok(()) => 0,\r\n        Err(_) => 1,\r\n    }\r\n}\r\n\r\n/// Explicit stop command helper for testing and recovery.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_Stop() -> i32 {\r\n    match send_stop() {\r\n        Ok(()) => 0,\r\n        Err(_) => 1,\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":265,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","scheme":"file"},"requestId":"request_05ad5ed7-be85-45fb-af40-007422c0606f","epoch":992,"edits":[{"text":"use core::ffi::c_void;\r\nuse core::ffi::CStr;\r\nuse core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU32, Ordering};\r\nuse std::os::raw::c_char;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\nconst SERVER_PROVIDER_INTERFACE: &str = \"IServerTrackedDeviceProvider_005\";\r\nconst TRACKED_DEVICE_INTERFACE: &str = \"ITrackedDeviceServerDriver_005\";\r\n\r\n#[repr(i32)]\r\nenum DriverReturnCode {\r\n    None = 0,\r\n    Unknown = 1,\r\n    InitInterfaceNotFound = 105,\r\n}\r\n\r\ntype EvRInitError = i32;\r\nconst VR_INIT_ERROR_NONE: EvRInitError = 0;\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nstruct DriverPose {\r\n    // Placeholder for OpenVR DriverPose_t. Will be replaced with exact layout.\r\n    _reserved: [u8; 128],\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerTrackedDeviceProviderVTable {\r\n    init: extern \"C\" fn(*mut ServerTrackedDeviceProvider, *mut c_void) -> EvRInitError,\r\n    cleanup: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    get_interface_versions:\r\n        extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> *const *const c_char,\r\n    run_frame: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    should_block_standby_mode: extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> bool,\r\n    enter_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    leave_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n}\r\n\r\n#[repr(C)]\r\nstruct TrackedDeviceServerDriverVTable {\r\n    activate: extern \"C\" fn(*mut TrackedDeviceServerDriver, u32) -> EvRInitError,\r\n    deactivate: extern \"C\" fn(*mut TrackedDeviceServerDriver),\r\n    enter_standby: extern \"C\" fn(*mut TrackedDeviceServerDriver),\r\n    get_component:\r\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char) -> *mut c_void,\r\n    debug_request:\r\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char, *mut c_char, u32),\r\n    get_pose: extern \"C\" fn(*mut TrackedDeviceServerDriver) -> DriverPose,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerTrackedDeviceProvider {\r\n    vtable: *const ServerTrackedDeviceProviderVTable,\n}\r\nunsafe impl Sync for ServerTrackedDeviceProvider {}\n\r\n#[repr(C)]\r\nstruct TrackedDeviceServerDriver {\r\n    vtable: *const TrackedDeviceServerDriverVTable,\n}\r\nunsafe impl Sync for TrackedDeviceServerDriver {}\n\n#[repr(C)]\nstruct InterfaceVersions([*const c_char; 3]);\nunsafe impl Sync for InterfaceVersions {}\n\r\nstatic HOST_CONTEXT: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DEVICE_INDEX: AtomicU32 = AtomicU32::new(u32::MAX);\r\nstatic DEVICE_ACTIVE: AtomicBool = AtomicBool::new(false);\r\n\r\nconst PROVIDER_IFACE_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_005\\0\";\r\nconst DEVICE_IFACE_CSTR: &[u8] = b\"ITrackedDeviceServerDriver_005\\0\";\r\n\r\nstatic PROVIDER_INTERFACE_VERSIONS: InterfaceVersions = InterfaceVersions([\n    PROVIDER_IFACE_CSTR.as_ptr().cast(),\n    DEVICE_IFACE_CSTR.as_ptr().cast(),\n    core::ptr::null(),\n]);\n\r\nextern \"C\" fn provider_init(\r\n    _this: *mut ServerTrackedDeviceProvider,\r\n    driver_context: *mut c_void,\r\n) -> EvRInitError {\r\n    HOST_CONTEXT.store(driver_context, Ordering::SeqCst);\r\n    VR_INIT_ERROR_NONE\r\n}\r\n\r\nextern \"C\" fn provider_cleanup(_this: *mut ServerTrackedDeviceProvider) {\r\n    HOST_CONTEXT.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\r\n    let _ = send_stop();\r\n}\r\n\r\nextern \"C\" fn provider_get_interface_versions(\r\n    _this: *mut ServerTrackedDeviceProvider,\r\n) -> *const *const c_char {\r\n    PROVIDER_INTERFACE_VERSIONS.0.as_ptr()\n}\r\n\r\nextern \"C\" fn provider_run_frame(_this: *mut ServerTrackedDeviceProvider) {\r\n    // TODO: Pump driver events from vrserver and handle haptic vibration events.\r\n}\r\n\r\nextern \"C\" fn provider_should_block_standby_mode(_this: *mut ServerTrackedDeviceProvider) -> bool {\r\n    false\r\n}\r\n\r\nextern \"C\" fn provider_enter_standby(_this: *mut ServerTrackedDeviceProvider) {}\r\n\r\nextern \"C\" fn provider_leave_standby(_this: *mut ServerTrackedDeviceProvider) {}\r\n\r\nextern \"C\" fn device_activate(_this: *mut TrackedDeviceServerDriver, object_id: u32) -> EvRInitError {\r\n    DEVICE_INDEX.store(object_id, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(true, Ordering::SeqCst);\r\n    VR_INIT_ERROR_NONE\r\n}\r\n\r\nextern \"C\" fn device_deactivate(_this: *mut TrackedDeviceServerDriver) {\r\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\r\n    let _ = send_stop();\r\n}\r\n\r\nextern \"C\" fn device_enter_standby(_this: *mut TrackedDeviceServerDriver) {\r\n    let _ = send_stop();\r\n}\r\n\r\nextern \"C\" fn device_get_component(\r\n    _this: *mut TrackedDeviceServerDriver,\r\n    _component_name: *const c_char,\r\n) -> *mut c_void {\r\n    core::ptr::null_mut()\r\n}\r\n\r\nextern \"C\" fn device_debug_request(\r\n    _this: *mut TrackedDeviceServerDriver,\r\n    _request: *const c_char,\r\n    response: *mut c_char,\r\n    response_size: u32,\r\n) {\r\n    if response.is_null() || response_size == 0 {\r\n        return;\r\n    }\r\n\r\n    // Always return an empty C-string for now.\r\n    // SAFETY: `response` points to caller-provided writable buffer.\r\n    unsafe {\r\n        *response = 0;\r\n    }\r\n}\r\n\r\nextern \"C\" fn device_get_pose(_this: *mut TrackedDeviceServerDriver) -> DriverPose {\r\n    DriverPose {\r\n        _reserved: [0; 128],\r\n    }\r\n}\r\n\r\nstatic PROVIDER_VTABLE: ServerTrackedDeviceProviderVTable = ServerTrackedDeviceProviderVTable {\r\n    init: provider_init,\r\n    cleanup: provider_cleanup,\r\n    get_interface_versions: provider_get_interface_versions,\r\n    run_frame: provider_run_frame,\r\n    should_block_standby_mode: provider_should_block_standby_mode,\r\n    enter_standby: provider_enter_standby,\r\n    leave_standby: provider_leave_standby,\r\n};\r\n\r\nstatic DEVICE_VTABLE: TrackedDeviceServerDriverVTable = TrackedDeviceServerDriverVTable {\r\n    activate: device_activate,\r\n    deactivate: device_deactivate,\r\n    enter_standby: device_enter_standby,\r\n    get_component: device_get_component,\r\n    debug_request: device_debug_request,\r\n    get_pose: device_get_pose,\r\n};\r\n\r\nstatic PROVIDER: ServerTrackedDeviceProvider = ServerTrackedDeviceProvider {\r\n    vtable: &PROVIDER_VTABLE,\n};\r\n\r\n#[allow(dead_code)]\r\nstatic DEVICE: TrackedDeviceServerDriver = TrackedDeviceServerDriver {\r\n    vtable: &DEVICE_VTABLE,\n};\r\n\r\nfn send_packet(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    hid_bridge::HidBridge::new()\r\n        .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n}\r\n\r\nfn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\nfn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    send_packet(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn set_return_code(out: *mut i32, code: DriverReturnCode) {\r\n    if !out.is_null() {\r\n        // SAFETY: caller owns output pointer according to OpenVR factory ABI.\r\n        unsafe {\r\n            *out = code as i32;\r\n        }\r\n    }\r\n}\r\n\r\nfn read_interface_name(ptr: *const i8) -> Option<&'static str> {\r\n    if ptr.is_null() {\r\n        return None;\r\n    }\r\n\r\n    // SAFETY: OpenVR passes a NUL-terminated C string.\r\n    let cstr = unsafe { CStr::from_ptr(ptr) };\r\n    let Ok(name) = cstr.to_str() else {\r\n        return None;\r\n    };\r\n\r\n    // Keep only known literals in static storage path.\r\n    if name == SERVER_PROVIDER_INTERFACE {\r\n        Some(SERVER_PROVIDER_INTERFACE)\r\n    } else {\r\n        None\r\n    }\r\n}\r\n\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn HmdDriverFactory(\r\n    p_interface_name: *const c_char,\r\n    p_return_code: *mut i32,\r\n) -> *mut c_void {\r\n    let Some(interface_name) = read_interface_name(p_interface_name) else {\r\n        set_return_code(p_return_code, DriverReturnCode::InitInterfaceNotFound);\r\n        return core::ptr::null_mut();\r\n    };\r\n\r\n    if interface_name == SERVER_PROVIDER_INTERFACE {\r\n        set_return_code(p_return_code, DriverReturnCode::None);\r\n        return (&PROVIDER as *const ServerTrackedDeviceProvider)\r\n            .cast_mut()\r\n            .cast();\r\n    }\r\n\r\n    set_return_code(p_return_code, DriverReturnCode::Unknown);\r\n    core::ptr::null_mut()\r\n}\r\n\r\n/// Test-only export for early integration while full OpenVR vtable glue is in progress.\r\n/// Returns 0 on success, non-zero on failure.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_SendAmplitude(amplitude: f32) -> i32 {\r\n    match send_amplitude(amplitude) {\r\n        Ok(()) => 0,\r\n        Err(_) => 1,\r\n    }\r\n}\r\n\r\n/// Explicit stop command helper for testing and recovery.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_Stop() -> i32 {\r\n    match send_stop() {\r\n        Ok(()) => 0,\r\n        Err(_) => 1,\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":271,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","scheme":"file"},"requestId":"request_05ad5ed7-be85-45fb-af40-007422c0606f","epoch":994,"edits":[{"text":"use core::ffi::c_void;\r\nuse core::ffi::CStr;\r\nuse core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU32, Ordering};\r\nuse std::os::raw::c_char;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\nconst SERVER_PROVIDER_INTERFACE: &str = \"IServerTrackedDeviceProvider_005\";\r\nconst TRACKED_DEVICE_INTERFACE: &str = \"ITrackedDeviceServerDriver_005\";\r\n\r\n#[repr(i32)]\r\nenum DriverReturnCode {\r\n    None = 0,\r\n    Unknown = 1,\r\n    InitInterfaceNotFound = 105,\r\n}\r\n\r\ntype EvRInitError = i32;\r\nconst VR_INIT_ERROR_NONE: EvRInitError = 0;\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nstruct DriverPose {\r\n    // Placeholder for OpenVR DriverPose_t. Will be replaced with exact layout.\r\n    _reserved: [u8; 128],\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerTrackedDeviceProviderVTable {\r\n    init: extern \"C\" fn(*mut ServerTrackedDeviceProvider, *mut c_void) -> EvRInitError,\r\n    cleanup: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    get_interface_versions:\r\n        extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> *const *const c_char,\r\n    run_frame: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    should_block_standby_mode: extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> bool,\r\n    enter_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    leave_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n}\r\n\r\n#[repr(C)]\r\nstruct TrackedDeviceServerDriverVTable {\r\n    activate: extern \"C\" fn(*mut TrackedDeviceServerDriver, u32) -> EvRInitError,\r\n    deactivate: extern \"C\" fn(*mut TrackedDeviceServerDriver),\r\n    enter_standby: extern \"C\" fn(*mut TrackedDeviceServerDriver),\r\n    get_component:\r\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char) -> *mut c_void,\r\n    debug_request:\r\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char, *mut c_char, u32),\r\n    get_pose: extern \"C\" fn(*mut TrackedDeviceServerDriver) -> DriverPose,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerTrackedDeviceProvider {\r\n    vtable: *const ServerTrackedDeviceProviderVTable,\r\n}\r\nunsafe impl Sync for ServerTrackedDeviceProvider {}\r\n\r\n#[repr(C)]\r\nstruct TrackedDeviceServerDriver {\r\n    vtable: *const TrackedDeviceServerDriverVTable,\r\n}\r\nunsafe impl Sync for TrackedDeviceServerDriver {}\r\n\r\n#[repr(C)]\r\nstruct InterfaceVersions([*const c_char; 3]);\r\nunsafe impl Sync for InterfaceVersions {}\r\n\r\nstatic HOST_CONTEXT: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DEVICE_INDEX: AtomicU32 = AtomicU32::new(u32::MAX);\r\nstatic DEVICE_ACTIVE: AtomicBool = AtomicBool::new(false);\r\n\r\nconst PROVIDER_IFACE_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_005\\0\";\r\nconst DEVICE_IFACE_CSTR: &[u8] = b\"ITrackedDeviceServerDriver_005\\0\";\r\n\r\nstatic PROVIDER_INTERFACE_VERSIONS: InterfaceVersions = InterfaceVersions([\r\n    PROVIDER_IFACE_CSTR.as_ptr().cast(),\r\n    DEVICE_IFACE_CSTR.as_ptr().cast(),\r\n    core::ptr::null(),\r\n]);\r\n\r\nextern \"C\" fn provider_init(\r\n    _this: *mut ServerTrackedDeviceProvider,\r\n    driver_context: *mut c_void,\r\n) -> EvRInitError {\r\n    HOST_CONTEXT.store(driver_context, Ordering::SeqCst);\r\n    VR_INIT_ERROR_NONE\r\n}\r\n\r\nextern \"C\" fn provider_cleanup(_this: *mut ServerTrackedDeviceProvider) {\r\n    HOST_CONTEXT.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\r\n    let _ = send_stop();\r\n}\r\n\r\nextern \"C\" fn provider_get_interface_versions(\r\n    _this: *mut ServerTrackedDeviceProvider,\r\n) -> *const *const c_char {\r\n    PROVIDER_INTERFACE_VERSIONS.0.as_ptr()\r\n}\r\n\r\nextern \"C\" fn provider_run_frame(_this: *mut ServerTrackedDeviceProvider) {\r\n    // TODO: Pump driver events from vrserver and handle haptic vibration events.\r\n}\r\n\r\nextern \"C\" fn provider_should_block_standby_mode(_this: *mut ServerTrackedDeviceProvider) -> bool {\r\n    false\r\n}\r\n\r\nextern \"C\" fn provider_enter_standby(_this: *mut ServerTrackedDeviceProvider) {}\r\n\r\nextern \"C\" fn provider_leave_standby(_this: *mut ServerTrackedDeviceProvider) {}\r\n\r\nextern \"C\" fn device_activate(_this: *mut TrackedDeviceServerDriver, object_id: u32) -> EvRInitError {\r\n    DEVICE_INDEX.store(object_id, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(true, Ordering::SeqCst);\r\n    VR_INIT_ERROR_NONE\r\n}\r\n\r\nextern \"C\" fn device_deactivate(_this: *mut TrackedDeviceServerDriver) {\r\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\r\n    let _ = send_stop();\r\n}\r\n\r\nextern \"C\" fn device_enter_standby(_this: *mut TrackedDeviceServerDriver) {\r\n    let _ = send_stop();\r\n}\r\n\r\nextern \"C\" fn device_get_component(\r\n    _this: *mut TrackedDeviceServerDriver,\r\n    _component_name: *const c_char,\r\n) -> *mut c_void {\r\n    core::ptr::null_mut()\r\n}\r\n\r\nextern \"C\" fn device_debug_request(\r\n    _this: *mut TrackedDeviceServerDriver,\r\n    _request: *const c_char,\r\n    response: *mut c_char,\r\n    response_size: u32,\r\n) {\r\n    if response.is_null() || response_size == 0 {\r\n        return;\r\n    }\r\n\r\n    // Always return an empty C-string for now.\r\n    // SAFETY: `response` points to caller-provided writable buffer.\r\n    unsafe {\r\n        *response = 0;\r\n    }\r\n}\r\n\r\nextern \"C\" fn device_get_pose(_this: *mut TrackedDeviceServerDriver) -> DriverPose {\r\n    DriverPose {\r\n        _reserved: [0; 128],\r\n    }\r\n}\r\n\r\nstatic PROVIDER_VTABLE: ServerTrackedDeviceProviderVTable = ServerTrackedDeviceProviderVTable {\r\n    init: provider_init,\r\n    cleanup: provider_cleanup,\r\n    get_interface_versions: provider_get_interface_versions,\r\n    run_frame: provider_run_frame,\r\n    should_block_standby_mode: provider_should_block_standby_mode,\r\n    enter_standby: provider_enter_standby,\r\n    leave_standby: provider_leave_standby,\r\n};\r\n\r\nstatic DEVICE_VTABLE: TrackedDeviceServerDriverVTable = TrackedDeviceServerDriverVTable {\r\n    activate: device_activate,\r\n    deactivate: device_deactivate,\r\n    enter_standby: device_enter_standby,\r\n    get_component: device_get_component,\r\n    debug_request: device_debug_request,\r\n    get_pose: device_get_pose,\r\n};\r\n\r\nstatic PROVIDER: ServerTrackedDeviceProvider = ServerTrackedDeviceProvider {\r\n    vtable: &PROVIDER_VTABLE,\r\n};\r\n\r\n#[allow(dead_code)]\r\nstatic DEVICE: TrackedDeviceServerDriver = TrackedDeviceServerDriver {\r\n    vtable: &DEVICE_VTABLE,\r\n};\r\n\r\nfn send_packet(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    hid_bridge::HidBridge::new()\r\n        .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n}\r\n\r\nfn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\nfn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    send_packet(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn set_return_code(out: *mut i32, code: DriverReturnCode) {\r\n    if !out.is_null() {\r\n        // SAFETY: caller owns output pointer according to OpenVR factory ABI.\r\n        unsafe {\r\n            *out = code as i32;\r\n        }\r\n    }\r\n}\r\n\r\nfn read_interface_name(ptr: *const i8) -> Option<&'static str> {\r\n    if ptr.is_null() {\r\n        return None;\r\n    }\r\n\r\n    // SAFETY: OpenVR passes a NUL-terminated C string.\r\n    let cstr = unsafe { CStr::from_ptr(ptr) };\r\n    let Ok(name) = cstr.to_str() else {\r\n        return None;\r\n    };\r\n\r\n    // Keep only known literals in static storage path.\n    if name == SERVER_PROVIDER_INTERFACE {\n        return Some(SERVER_PROVIDER_INTERFACE);\n    }\n    if name == TRACKED_DEVICE_INTERFACE {\n        return Some(TRACKED_DEVICE_INTERFACE);\n    }\n\n    None\n}\r\n\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn HmdDriverFactory(\r\n    p_interface_name: *const c_char,\r\n    p_return_code: *mut i32,\r\n) -> *mut c_void {\r\n    let Some(interface_name) = read_interface_name(p_interface_name) else {\r\n        set_return_code(p_return_code, DriverReturnCode::InitInterfaceNotFound);\r\n        return core::ptr::null_mut();\r\n    };\r\n\r\n    if interface_name == SERVER_PROVIDER_INTERFACE {\r\n        set_return_code(p_return_code, DriverReturnCode::None);\r\n        return (&PROVIDER as *const ServerTrackedDeviceProvider)\r\n            .cast_mut()\r\n            .cast();\r\n    }\r\n\n    if interface_name == TRACKED_DEVICE_INTERFACE {\n        set_return_code(p_return_code, DriverReturnCode::None);\n        return (&DEVICE as *const TrackedDeviceServerDriver)\n            .cast_mut()\n            .cast();\n    }\n\r\n    set_return_code(p_return_code, DriverReturnCode::Unknown);\r\n    core::ptr::null_mut()\r\n}\r\n\r\n/// Test-only export for early integration while full OpenVR vtable glue is in progress.\r\n/// Returns 0 on success, non-zero on failure.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_SendAmplitude(amplitude: f32) -> i32 {\r\n    match send_amplitude(amplitude) {\r\n        Ok(()) => 0,\r\n        Err(_) => 1,\r\n    }\r\n}\r\n\r\n/// Explicit stop command helper for testing and recovery.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_Stop() -> i32 {\r\n    match send_stop() {\r\n        Ok(()) => 0,\r\n        Err(_) => 1,\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":281,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","scheme":"file"},"requestId":"request_814a4084-5479-45cb-b1bb-93faa91ce807","epoch":998,"edits":[{"text":"use core::ffi::c_void;\r\nuse core::ffi::CStr;\r\nuse core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU32, AtomicU64, Ordering};\nuse std::os::raw::c_char;\r\nuse std::sync::{Mutex, OnceLock};\nuse std::time::Instant;\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\nconst SERVER_PROVIDER_INTERFACE: &str = \"IServerTrackedDeviceProvider_005\";\r\nconst TRACKED_DEVICE_INTERFACE: &str = \"ITrackedDeviceServerDriver_005\";\r\n\r\n#[repr(i32)]\r\nenum DriverReturnCode {\r\n    None = 0,\r\n    Unknown = 1,\r\n    InitInterfaceNotFound = 105,\r\n}\r\n\r\ntype EvRInitError = i32;\r\nconst VR_INIT_ERROR_NONE: EvRInitError = 0;\r\n\n#[repr(C)]\n#[derive(Clone, Copy, Default)]\npub struct HapticVibrationRequest {\n    pub amplitude: f32,\n    pub duration_seconds: f32,\n    pub frequency: f32,\n}\n\ntype PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nstruct DriverPose {\r\n    // Placeholder for OpenVR DriverPose_t. Will be replaced with exact layout.\r\n    _reserved: [u8; 128],\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerTrackedDeviceProviderVTable {\r\n    init: extern \"C\" fn(*mut ServerTrackedDeviceProvider, *mut c_void) -> EvRInitError,\r\n    cleanup: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    get_interface_versions:\r\n        extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> *const *const c_char,\r\n    run_frame: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    should_block_standby_mode: extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> bool,\r\n    enter_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    leave_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n}\r\n\r\n#[repr(C)]\r\nstruct TrackedDeviceServerDriverVTable {\r\n    activate: extern \"C\" fn(*mut TrackedDeviceServerDriver, u32) -> EvRInitError,\r\n    deactivate: extern \"C\" fn(*mut TrackedDeviceServerDriver),\r\n    enter_standby: extern \"C\" fn(*mut TrackedDeviceServerDriver),\r\n    get_component:\r\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char) -> *mut c_void,\r\n    debug_request:\r\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char, *mut c_char, u32),\r\n    get_pose: extern \"C\" fn(*mut TrackedDeviceServerDriver) -> DriverPose,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerTrackedDeviceProvider {\r\n    vtable: *const ServerTrackedDeviceProviderVTable,\r\n}\r\nunsafe impl Sync for ServerTrackedDeviceProvider {}\r\n\r\n#[repr(C)]\r\nstruct TrackedDeviceServerDriver {\r\n    vtable: *const TrackedDeviceServerDriverVTable,\r\n}\r\nunsafe impl Sync for TrackedDeviceServerDriver {}\r\n\r\n#[repr(C)]\r\nstruct InterfaceVersions([*const c_char; 3]);\r\nunsafe impl Sync for InterfaceVersions {}\r\n\r\nstatic HOST_CONTEXT: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DEVICE_INDEX: AtomicU32 = AtomicU32::new(u32::MAX);\r\nstatic DEVICE_ACTIVE: AtomicBool = AtomicBool::new(false);\r\nstatic STOP_DEADLINE_MS: AtomicU64 = AtomicU64::new(0);\nstatic START_INSTANT: OnceLock<Instant> = OnceLock::new();\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\n\r\nconst PROVIDER_IFACE_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_005\\0\";\r\nconst DEVICE_IFACE_CSTR: &[u8] = b\"ITrackedDeviceServerDriver_005\\0\";\r\n\r\nstatic PROVIDER_INTERFACE_VERSIONS: InterfaceVersions = InterfaceVersions([\r\n    PROVIDER_IFACE_CSTR.as_ptr().cast(),\r\n    DEVICE_IFACE_CSTR.as_ptr().cast(),\r\n    core::ptr::null(),\r\n]);\r\n\r\nextern \"C\" fn provider_init(\r\n    _this: *mut ServerTrackedDeviceProvider,\r\n    driver_context: *mut c_void,\r\n) -> EvRInitError {\r\n    HOST_CONTEXT.store(driver_context, Ordering::SeqCst);\r\n    VR_INIT_ERROR_NONE\r\n}\r\n\r\nextern \"C\" fn provider_cleanup(_this: *mut ServerTrackedDeviceProvider) {\r\n    HOST_CONTEXT.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\r\n    let _ = send_stop();\r\n}\r\n\r\nextern \"C\" fn provider_get_interface_versions(\r\n    _this: *mut ServerTrackedDeviceProvider,\r\n) -> *const *const c_char {\r\n    PROVIDER_INTERFACE_VERSIONS.0.as_ptr()\r\n}\r\n\r\nextern \"C\" fn provider_run_frame(_this: *mut ServerTrackedDeviceProvider) {\r\n    process_haptic_callback();\n    process_pending_stop();\n}\r\n\r\nextern \"C\" fn provider_should_block_standby_mode(_this: *mut ServerTrackedDeviceProvider) -> bool {\r\n    false\r\n}\r\n\r\nextern \"C\" fn provider_enter_standby(_this: *mut ServerTrackedDeviceProvider) {}\r\n\r\nextern \"C\" fn provider_leave_standby(_this: *mut ServerTrackedDeviceProvider) {}\r\n\r\nextern \"C\" fn device_activate(_this: *mut TrackedDeviceServerDriver, object_id: u32) -> EvRInitError {\r\n    DEVICE_INDEX.store(object_id, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(true, Ordering::SeqCst);\r\n    VR_INIT_ERROR_NONE\r\n}\r\n\r\nextern \"C\" fn device_deactivate(_this: *mut TrackedDeviceServerDriver) {\r\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\r\n    let _ = send_stop();\r\n}\r\n\r\nextern \"C\" fn device_enter_standby(_this: *mut TrackedDeviceServerDriver) {\r\n    let _ = send_stop();\r\n}\r\n\r\nextern \"C\" fn device_get_component(\r\n    _this: *mut TrackedDeviceServerDriver,\r\n    _component_name: *const c_char,\r\n) -> *mut c_void {\r\n    core::ptr::null_mut()\r\n}\r\n\r\nextern \"C\" fn device_debug_request(\r\n    _this: *mut TrackedDeviceServerDriver,\r\n    _request: *const c_char,\r\n    response: *mut c_char,\r\n    response_size: u32,\r\n) {\r\n    if response.is_null() || response_size == 0 {\r\n        return;\r\n    }\r\n\r\n    // Always return an empty C-string for now.\r\n    // SAFETY: `response` points to caller-provided writable buffer.\r\n    unsafe {\r\n        *response = 0;\r\n    }\r\n}\r\n\r\nextern \"C\" fn device_get_pose(_this: *mut TrackedDeviceServerDriver) -> DriverPose {\r\n    DriverPose {\r\n        _reserved: [0; 128],\r\n    }\r\n}\r\n\r\nstatic PROVIDER_VTABLE: ServerTrackedDeviceProviderVTable = ServerTrackedDeviceProviderVTable {\r\n    init: provider_init,\r\n    cleanup: provider_cleanup,\r\n    get_interface_versions: provider_get_interface_versions,\r\n    run_frame: provider_run_frame,\r\n    should_block_standby_mode: provider_should_block_standby_mode,\r\n    enter_standby: provider_enter_standby,\r\n    leave_standby: provider_leave_standby,\r\n};\r\n\r\nstatic DEVICE_VTABLE: TrackedDeviceServerDriverVTable = TrackedDeviceServerDriverVTable {\r\n    activate: device_activate,\r\n    deactivate: device_deactivate,\r\n    enter_standby: device_enter_standby,\r\n    get_component: device_get_component,\r\n    debug_request: device_debug_request,\r\n    get_pose: device_get_pose,\r\n};\r\n\r\nstatic PROVIDER: ServerTrackedDeviceProvider = ServerTrackedDeviceProvider {\r\n    vtable: &PROVIDER_VTABLE,\r\n};\r\n\r\n#[allow(dead_code)]\r\nstatic DEVICE: TrackedDeviceServerDriver = TrackedDeviceServerDriver {\r\n    vtable: &DEVICE_VTABLE,\r\n};\r\n\r\nfn send_packet(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    hid_bridge::HidBridge::new()\r\n        .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n}\r\n\r\nfn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\nfn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    send_packet(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\nfn now_ms() -> u64 {\n    let start = START_INSTANT.get_or_init(Instant::now);\n    let ms = Instant::now().duration_since(*start).as_millis();\n    ms.min(u128::from(u64::MAX)) as u64\n}\n\nfn schedule_stop_after(duration_seconds: f32) {\n    if duration_seconds <= 0.0 {\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\n        return;\n    }\n\n    let duration_ms = (duration_seconds * 1000.0).round().max(1.0) as u64;\n    let deadline = now_ms().saturating_add(duration_ms);\n    STOP_DEADLINE_MS.store(deadline, Ordering::SeqCst);\n}\n\nfn process_pending_stop() {\n    let deadline = STOP_DEADLINE_MS.load(Ordering::SeqCst);\n    if deadline == 0 {\n        return;\n    }\n\n    if now_ms() >= deadline {\n        let _ = send_stop();\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\n    }\n}\n\nfn handle_haptic_request(req: HapticVibrationRequest) {\n    if req.amplitude <= 0.0 {\n        let _ = send_stop();\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\n        return;\n    }\n\n    let _ = send_amplitude(req.amplitude);\n    schedule_stop_after(req.duration_seconds);\n}\n\nfn process_haptic_callback() {\n    let cb = {\n        let guard = POLL_HAPTIC_CB.lock().ok();\n        guard.and_then(|g| *g)\n    };\n\n    let Some(callback) = cb else {\n        return;\n    };\n\n    // Process a bounded number of queued events per frame.\n    for _ in 0..8 {\n        let mut req = HapticVibrationRequest::default();\n        if !callback(&mut req as *mut HapticVibrationRequest) {\n            break;\n        }\n        handle_haptic_request(req);\n    }\n}\n\r\nfn set_return_code(out: *mut i32, code: DriverReturnCode) {\r\n    if !out.is_null() {\r\n        // SAFETY: caller owns output pointer according to OpenVR factory ABI.\r\n        unsafe {\r\n            *out = code as i32;\r\n        }\r\n    }\r\n}\r\n\r\nfn read_interface_name(ptr: *const i8) -> Option<&'static str> {\r\n    if ptr.is_null() {\r\n        return None;\r\n    }\r\n\r\n    // SAFETY: OpenVR passes a NUL-terminated C string.\r\n    let cstr = unsafe { CStr::from_ptr(ptr) };\r\n    let Ok(name) = cstr.to_str() else {\r\n        return None;\r\n    };\r\n\r\n    // Keep only known literals in static storage path.\r\n    if name == SERVER_PROVIDER_INTERFACE {\r\n        return Some(SERVER_PROVIDER_INTERFACE);\r\n    }\r\n    if name == TRACKED_DEVICE_INTERFACE {\r\n        return Some(TRACKED_DEVICE_INTERFACE);\r\n    }\r\n\r\n    None\r\n}\r\n\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn HmdDriverFactory(\r\n    p_interface_name: *const c_char,\r\n    p_return_code: *mut i32,\r\n) -> *mut c_void {\r\n    let Some(interface_name) = read_interface_name(p_interface_name) else {\r\n        set_return_code(p_return_code, DriverReturnCode::InitInterfaceNotFound);\r\n        return core::ptr::null_mut();\r\n    };\r\n\r\n    if interface_name == SERVER_PROVIDER_INTERFACE {\r\n        set_return_code(p_return_code, DriverReturnCode::None);\r\n        return (&PROVIDER as *const ServerTrackedDeviceProvider)\r\n            .cast_mut()\r\n            .cast();\r\n    }\r\n\r\n    if interface_name == TRACKED_DEVICE_INTERFACE {\r\n        set_return_code(p_return_code, DriverReturnCode::None);\r\n        return (&DEVICE as *const TrackedDeviceServerDriver)\r\n            .cast_mut()\r\n            .cast();\r\n    }\r\n\r\n    set_return_code(p_return_code, DriverReturnCode::Unknown);\r\n    core::ptr::null_mut()\r\n}\r\n\r\n/// Test-only export for early integration while full OpenVR vtable glue is in progress.\r\n/// Returns 0 on success, non-zero on failure.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_SendAmplitude(amplitude: f32) -> i32 {\r\n    match send_amplitude(amplitude) {\r\n        Ok(()) => 0,\r\n        Err(_) => 1,\r\n    }\r\n}\r\n\r\n/// Explicit stop command helper for testing and recovery.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_Stop() -> i32 {\r\n    match send_stop() {\r\n        Ok(()) => 0,\r\n        Err(_) => 1,\r\n    }\r\n}\r\n\n/// Temporary bridge: host/OpenVR shim can register a callback polled each frame.\n#[unsafe(no_mangle)]\npub extern \"C\" fn BigHapticDriver_SetPollHapticCallback(cb: Option<PollHapticCallback>) {\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\n        *guard = cb;\n    }\n}\n\n/// Temporary direct injection helper while event wiring is being built.\n#[unsafe(no_mangle)]\npub extern \"C\" fn BigHapticDriver_InjectHaptic(\n    amplitude: f32,\n    duration_seconds: f32,\n    frequency: f32,\n) {\n    handle_haptic_request(HapticVibrationRequest {\n        amplitude,\n        duration_seconds,\n        frequency,\n    });\n}\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":379,"endColumn":1}}]},{"type":"create","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1000,"initialContent":"","telemetryInfo":{}},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1001,"edits":[{"text":"use core::sync::atomic::{AtomicU64, Ordering};","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":1,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1002,"edits":[{"text":"\nuse std::sync::{Mutex, OnceLock};","range":{"startLineNumber":2,"startColumn":1,"endLineNumber":2,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1003,"edits":[{"text":"\nuse std::time::Instant;","range":{"startLineNumber":3,"startColumn":1,"endLineNumber":3,"endColumn":1}}]},{"type":"create","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1004,"initialContent":"","telemetryInfo":{}},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1005,"edits":[{"text":"use core::ffi::{c_void, CStr};","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":1,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1006,"edits":[{"text":"\n","range":{"startLineNumber":4,"startColumn":1,"endLineNumber":4,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1007,"edits":[{"text":"\nuse core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU32, Ordering};","range":{"startLineNumber":2,"startColumn":1,"endLineNumber":2,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1008,"edits":[{"text":"\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};","range":{"startLineNumber":5,"startColumn":1,"endLineNumber":5,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1009,"edits":[{"text":"\nuse std::os::raw::c_char;","range":{"startLineNumber":3,"startColumn":1,"endLineNumber":3,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1010,"edits":[{"text":"\n","range":{"startLineNumber":6,"startColumn":1,"endLineNumber":6,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1011,"edits":[{"text":"\n","range":{"startLineNumber":4,"startColumn":1,"endLineNumber":4,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1012,"edits":[{"text":"\n#[repr(C)]","range":{"startLineNumber":7,"startColumn":1,"endLineNumber":7,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1013,"edits":[{"text":"\nuse crate::haptics;","range":{"startLineNumber":5,"startColumn":1,"endLineNumber":5,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1014,"edits":[{"text":"\n#[derive(Clone, Copy, Default)]","range":{"startLineNumber":8,"startColumn":1,"endLineNumber":8,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1015,"edits":[{"text":"\n","range":{"startLineNumber":6,"startColumn":1,"endLineNumber":6,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1016,"edits":[{"text":"\npub struct HapticVibrationRequest {","range":{"startLineNumber":9,"startColumn":1,"endLineNumber":9,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1017,"edits":[{"text":"\nconst SERVER_PROVIDER_INTERFACE: &str = \"IServerTrackedDeviceProvider_005\";","range":{"startLineNumber":7,"startColumn":1,"endLineNumber":7,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1018,"edits":[{"text":"\n    pub amplitude: f32,","range":{"startLineNumber":10,"startColumn":1,"endLineNumber":10,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1019,"edits":[{"text":"\nconst TRACKED_DEVICE_INTERFACE: &str = \"ITrackedDeviceServerDriver_005\";","range":{"startLineNumber":8,"startColumn":1,"endLineNumber":8,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1020,"edits":[{"text":"\n    pub duration_seconds: f32,","range":{"startLineNumber":11,"startColumn":1,"endLineNumber":11,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1021,"edits":[{"text":"\n","range":{"startLineNumber":9,"startColumn":1,"endLineNumber":9,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1022,"edits":[{"text":"\n    pub frequency: f32,","range":{"startLineNumber":12,"startColumn":1,"endLineNumber":12,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1023,"edits":[{"text":"\n#[repr(i32)]","range":{"startLineNumber":10,"startColumn":1,"endLineNumber":10,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1024,"edits":[{"text":"\n}","range":{"startLineNumber":13,"startColumn":1,"endLineNumber":13,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1025,"edits":[{"text":"\nenum DriverReturnCode {","range":{"startLineNumber":11,"startColumn":1,"endLineNumber":11,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1026,"edits":[{"text":"\n","range":{"startLineNumber":14,"startColumn":1,"endLineNumber":14,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1027,"edits":[{"text":"\n    None = 0,","range":{"startLineNumber":12,"startColumn":1,"endLineNumber":12,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1028,"edits":[{"text":"\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;","range":{"startLineNumber":15,"startColumn":1,"endLineNumber":15,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1029,"edits":[{"text":"\n    Unknown = 1,","range":{"startLineNumber":13,"startColumn":1,"endLineNumber":13,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1030,"edits":[{"text":"\n","range":{"startLineNumber":16,"startColumn":1,"endLineNumber":16,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1031,"edits":[{"text":"\n    InitInterfaceNotFound = 105,","range":{"startLineNumber":14,"startColumn":1,"endLineNumber":14,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1032,"edits":[{"text":"\nstatic STOP_DEADLINE_MS: AtomicU64 = AtomicU64::new(0);","range":{"startLineNumber":17,"startColumn":1,"endLineNumber":17,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1033,"edits":[{"text":"\n}","range":{"startLineNumber":15,"startColumn":1,"endLineNumber":15,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1034,"edits":[{"text":"\nstatic START_INSTANT: OnceLock<Instant> = OnceLock::new();","range":{"startLineNumber":18,"startColumn":1,"endLineNumber":18,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1035,"edits":[{"text":"\n","range":{"startLineNumber":16,"startColumn":1,"endLineNumber":16,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1036,"edits":[{"text":"\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);","range":{"startLineNumber":19,"startColumn":1,"endLineNumber":19,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1037,"edits":[{"text":"\ntype EvRInitError = i32;","range":{"startLineNumber":17,"startColumn":1,"endLineNumber":17,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1038,"edits":[{"text":"\n","range":{"startLineNumber":20,"startColumn":1,"endLineNumber":20,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1039,"edits":[{"text":"\nconst VR_INIT_ERROR_NONE: EvRInitError = 0;","range":{"startLineNumber":18,"startColumn":1,"endLineNumber":18,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1040,"edits":[{"text":"\nfn send_packet(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {","range":{"startLineNumber":21,"startColumn":1,"endLineNumber":21,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1041,"edits":[{"text":"\n","range":{"startLineNumber":19,"startColumn":1,"endLineNumber":19,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1042,"edits":[{"text":"\n    hid_bridge::HidBridge::new()","range":{"startLineNumber":22,"startColumn":1,"endLineNumber":22,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1043,"edits":[{"text":"\n#[repr(C)]","range":{"startLineNumber":20,"startColumn":1,"endLineNumber":20,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1044,"edits":[{"text":"\n        .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))","range":{"startLineNumber":23,"startColumn":1,"endLineNumber":23,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1045,"edits":[{"text":"\n#[derive(Clone, Copy)]","range":{"startLineNumber":21,"startColumn":1,"endLineNumber":21,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1046,"edits":[{"text":"\n}","range":{"startLineNumber":24,"startColumn":1,"endLineNumber":24,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1047,"edits":[{"text":"\nstruct DriverPose {","range":{"startLineNumber":22,"startColumn":1,"endLineNumber":22,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1048,"edits":[{"text":"\n","range":{"startLineNumber":25,"startColumn":1,"endLineNumber":25,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1049,"edits":[{"text":"\n    _reserved: [u8; 128],","range":{"startLineNumber":23,"startColumn":1,"endLineNumber":23,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1050,"edits":[{"text":"\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {","range":{"startLineNumber":26,"startColumn":1,"endLineNumber":26,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1051,"edits":[{"text":"\n}","range":{"startLineNumber":24,"startColumn":1,"endLineNumber":24,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1052,"edits":[{"text":"\n    send_packet(HapticPacket::new(command::STOP, 0, 0, 0))","range":{"startLineNumber":27,"startColumn":1,"endLineNumber":27,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1053,"edits":[{"text":"\n","range":{"startLineNumber":25,"startColumn":1,"endLineNumber":25,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1054,"edits":[{"text":"\n}","range":{"startLineNumber":28,"startColumn":1,"endLineNumber":28,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1055,"edits":[{"text":"\n#[repr(C)]","range":{"startLineNumber":26,"startColumn":1,"endLineNumber":26,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1056,"edits":[{"text":"\n","range":{"startLineNumber":29,"startColumn":1,"endLineNumber":29,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1057,"edits":[{"text":"\nstruct ServerTrackedDeviceProviderVTable {","range":{"startLineNumber":27,"startColumn":1,"endLineNumber":27,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1058,"edits":[{"text":"\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {","range":{"startLineNumber":30,"startColumn":1,"endLineNumber":30,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1059,"edits":[{"text":"\n    init: extern \"C\" fn(*mut ServerTrackedDeviceProvider, *mut c_void) -> EvRInitError,","range":{"startLineNumber":28,"startColumn":1,"endLineNumber":28,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1060,"edits":[{"text":"\n    let amp = amplitude.clamp(0.0, 1.0);","range":{"startLineNumber":31,"startColumn":1,"endLineNumber":31,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1061,"edits":[{"text":"\n    cleanup: extern \"C\" fn(*mut ServerTrackedDeviceProvider),","range":{"startLineNumber":29,"startColumn":1,"endLineNumber":29,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1062,"edits":[{"text":"\n    let intensity = (amp * 255.0).round() as u8;","range":{"startLineNumber":32,"startColumn":1,"endLineNumber":32,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1063,"edits":[{"text":"\n    get_interface_versions:","range":{"startLineNumber":30,"startColumn":1,"endLineNumber":30,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1064,"edits":[{"text":"\n    send_packet(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))","range":{"startLineNumber":33,"startColumn":1,"endLineNumber":33,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1065,"edits":[{"text":"\n        extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> *const *const c_char,","range":{"startLineNumber":31,"startColumn":1,"endLineNumber":31,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1066,"edits":[{"text":"\n}","range":{"startLineNumber":34,"startColumn":1,"endLineNumber":34,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1067,"edits":[{"text":"\n    run_frame: extern \"C\" fn(*mut ServerTrackedDeviceProvider),","range":{"startLineNumber":32,"startColumn":1,"endLineNumber":32,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1068,"edits":[{"text":"\n","range":{"startLineNumber":35,"startColumn":1,"endLineNumber":35,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1069,"edits":[{"text":"\n    should_block_standby_mode: extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> bool,","range":{"startLineNumber":33,"startColumn":1,"endLineNumber":33,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1070,"edits":[{"text":"\nfn now_ms() -> u64 {","range":{"startLineNumber":36,"startColumn":1,"endLineNumber":36,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1071,"edits":[{"text":"\n    enter_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),","range":{"startLineNumber":34,"startColumn":1,"endLineNumber":34,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1072,"edits":[{"text":"\n    let start = START_INSTANT.get_or_init(Instant::now);","range":{"startLineNumber":37,"startColumn":1,"endLineNumber":37,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1073,"edits":[{"text":"\n    leave_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),","range":{"startLineNumber":35,"startColumn":1,"endLineNumber":35,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1074,"edits":[{"text":"\n    let ms = Instant::now().duration_since(*start).as_millis();","range":{"startLineNumber":38,"startColumn":1,"endLineNumber":38,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1075,"edits":[{"text":"\n}","range":{"startLineNumber":36,"startColumn":1,"endLineNumber":36,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1076,"edits":[{"text":"\n    ms.min(u128::from(u64::MAX)) as u64","range":{"startLineNumber":39,"startColumn":1,"endLineNumber":39,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1077,"edits":[{"text":"\n","range":{"startLineNumber":37,"startColumn":1,"endLineNumber":37,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1078,"edits":[{"text":"\n}","range":{"startLineNumber":40,"startColumn":1,"endLineNumber":40,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1079,"edits":[{"text":"\n#[repr(C)]","range":{"startLineNumber":38,"startColumn":1,"endLineNumber":38,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1080,"edits":[{"text":"\n","range":{"startLineNumber":41,"startColumn":1,"endLineNumber":41,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1081,"edits":[{"text":"\nstruct TrackedDeviceServerDriverVTable {","range":{"startLineNumber":39,"startColumn":1,"endLineNumber":39,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1082,"edits":[{"text":"\nfn schedule_stop_after(duration_seconds: f32) {","range":{"startLineNumber":42,"startColumn":1,"endLineNumber":42,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1083,"edits":[{"text":"\n    activate: extern \"C\" fn(*mut TrackedDeviceServerDriver, u32) -> EvRInitError,","range":{"startLineNumber":40,"startColumn":1,"endLineNumber":40,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1084,"edits":[{"text":"\n    if duration_seconds <= 0.0 {","range":{"startLineNumber":43,"startColumn":1,"endLineNumber":43,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1085,"edits":[{"text":"\n    deactivate: extern \"C\" fn(*mut TrackedDeviceServerDriver),","range":{"startLineNumber":41,"startColumn":1,"endLineNumber":41,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1086,"edits":[{"text":"\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);","range":{"startLineNumber":44,"startColumn":1,"endLineNumber":44,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1087,"edits":[{"text":"\n    enter_standby: extern \"C\" fn(*mut TrackedDeviceServerDriver),","range":{"startLineNumber":42,"startColumn":1,"endLineNumber":42,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1088,"edits":[{"text":"\n        return;","range":{"startLineNumber":45,"startColumn":1,"endLineNumber":45,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1089,"edits":[{"text":"\n    get_component:","range":{"startLineNumber":43,"startColumn":1,"endLineNumber":43,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1090,"edits":[{"text":"\n    }","range":{"startLineNumber":46,"startColumn":1,"endLineNumber":46,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1091,"edits":[{"text":"\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char) -> *mut c_void,","range":{"startLineNumber":44,"startColumn":1,"endLineNumber":44,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1092,"edits":[{"text":"\n","range":{"startLineNumber":47,"startColumn":1,"endLineNumber":47,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1093,"edits":[{"text":"\n    debug_request:","range":{"startLineNumber":45,"startColumn":1,"endLineNumber":45,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1094,"edits":[{"text":"\n    let duration_ms = (duration_seconds * 1000.0).round().max(1.0) as u64;","range":{"startLineNumber":48,"startColumn":1,"endLineNumber":48,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1095,"edits":[{"text":"\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char, *mut c_char, u32),","range":{"startLineNumber":46,"startColumn":1,"endLineNumber":46,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1096,"edits":[{"text":"\n    let deadline = now_ms().saturating_add(duration_ms);","range":{"startLineNumber":49,"startColumn":1,"endLineNumber":49,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1097,"edits":[{"text":"\n    get_pose: extern \"C\" fn(*mut TrackedDeviceServerDriver) -> DriverPose,","range":{"startLineNumber":47,"startColumn":1,"endLineNumber":47,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1098,"edits":[{"text":"\n    STOP_DEADLINE_MS.store(deadline, Ordering::SeqCst);","range":{"startLineNumber":50,"startColumn":1,"endLineNumber":50,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1099,"edits":[{"text":"\n}","range":{"startLineNumber":48,"startColumn":1,"endLineNumber":48,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1100,"edits":[{"text":"\n}","range":{"startLineNumber":51,"startColumn":1,"endLineNumber":51,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1101,"edits":[{"text":"\n","range":{"startLineNumber":49,"startColumn":1,"endLineNumber":49,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1102,"edits":[{"text":"\n","range":{"startLineNumber":52,"startColumn":1,"endLineNumber":52,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1103,"edits":[{"text":"\n#[repr(C)]","range":{"startLineNumber":50,"startColumn":1,"endLineNumber":50,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1104,"edits":[{"text":"\nfn process_pending_stop() {","range":{"startLineNumber":53,"startColumn":1,"endLineNumber":53,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1105,"edits":[{"text":"\nstruct ServerTrackedDeviceProvider {","range":{"startLineNumber":51,"startColumn":1,"endLineNumber":51,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1106,"edits":[{"text":"\n    let deadline = STOP_DEADLINE_MS.load(Ordering::SeqCst);","range":{"startLineNumber":54,"startColumn":1,"endLineNumber":54,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1107,"edits":[{"text":"\n    vtable: *const ServerTrackedDeviceProviderVTable,","range":{"startLineNumber":52,"startColumn":1,"endLineNumber":52,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1108,"edits":[{"text":"\n    if deadline == 0 {","range":{"startLineNumber":55,"startColumn":1,"endLineNumber":55,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1109,"edits":[{"text":"\n}","range":{"startLineNumber":53,"startColumn":1,"endLineNumber":53,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1110,"edits":[{"text":"\n        return;","range":{"startLineNumber":56,"startColumn":1,"endLineNumber":56,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1111,"edits":[{"text":"\nunsafe impl Sync for ServerTrackedDeviceProvider {}","range":{"startLineNumber":54,"startColumn":1,"endLineNumber":54,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1112,"edits":[{"text":"\n    }","range":{"startLineNumber":57,"startColumn":1,"endLineNumber":57,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1113,"edits":[{"text":"\n","range":{"startLineNumber":55,"startColumn":1,"endLineNumber":55,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1114,"edits":[{"text":"\n","range":{"startLineNumber":58,"startColumn":1,"endLineNumber":58,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1115,"edits":[{"text":"\n#[repr(C)]","range":{"startLineNumber":56,"startColumn":1,"endLineNumber":56,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1116,"edits":[{"text":"\n    if now_ms() >= deadline {","range":{"startLineNumber":59,"startColumn":1,"endLineNumber":59,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1117,"edits":[{"text":"\nstruct TrackedDeviceServerDriver {","range":{"startLineNumber":57,"startColumn":1,"endLineNumber":57,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1118,"edits":[{"text":"\n        let _ = send_stop();","range":{"startLineNumber":60,"startColumn":1,"endLineNumber":60,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1119,"edits":[{"text":"\n    vtable: *const TrackedDeviceServerDriverVTable,","range":{"startLineNumber":58,"startColumn":1,"endLineNumber":58,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1120,"edits":[{"text":"\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);","range":{"startLineNumber":61,"startColumn":1,"endLineNumber":61,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1121,"edits":[{"text":"\n}","range":{"startLineNumber":59,"startColumn":1,"endLineNumber":59,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1122,"edits":[{"text":"\n    }","range":{"startLineNumber":62,"startColumn":1,"endLineNumber":62,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1123,"edits":[{"text":"\nunsafe impl Sync for TrackedDeviceServerDriver {}","range":{"startLineNumber":60,"startColumn":1,"endLineNumber":60,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1124,"edits":[{"text":"\n}","range":{"startLineNumber":63,"startColumn":1,"endLineNumber":63,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1125,"edits":[{"text":"\n","range":{"startLineNumber":61,"startColumn":1,"endLineNumber":61,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1126,"edits":[{"text":"\n","range":{"startLineNumber":64,"startColumn":1,"endLineNumber":64,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1127,"edits":[{"text":"\n#[repr(C)]","range":{"startLineNumber":62,"startColumn":1,"endLineNumber":62,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1128,"edits":[{"text":"\npub fn handle_haptic_request(req: HapticVibrationRequest) {","range":{"startLineNumber":65,"startColumn":1,"endLineNumber":65,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1129,"edits":[{"text":"\nstruct InterfaceVersions([*const c_char; 3]);","range":{"startLineNumber":63,"startColumn":1,"endLineNumber":63,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1130,"edits":[{"text":"\n    if req.amplitude <= 0.0 {","range":{"startLineNumber":66,"startColumn":1,"endLineNumber":66,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1131,"edits":[{"text":"\n        let _ = send_stop();","range":{"startLineNumber":67,"startColumn":1,"endLineNumber":67,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1132,"edits":[{"text":"\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);","range":{"startLineNumber":68,"startColumn":1,"endLineNumber":68,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1133,"edits":[{"text":"\n        return;","range":{"startLineNumber":69,"startColumn":1,"endLineNumber":69,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1134,"edits":[{"text":"\n    }","range":{"startLineNumber":70,"startColumn":1,"endLineNumber":70,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1135,"edits":[{"text":"\n","range":{"startLineNumber":71,"startColumn":1,"endLineNumber":71,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1136,"edits":[{"text":"\n    let _ = send_amplitude(req.amplitude);","range":{"startLineNumber":72,"startColumn":1,"endLineNumber":72,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1137,"edits":[{"text":"\n    schedule_stop_after(req.duration_seconds);","range":{"startLineNumber":73,"startColumn":1,"endLineNumber":73,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1138,"edits":[{"text":"\n}","range":{"startLineNumber":74,"startColumn":1,"endLineNumber":74,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1139,"edits":[{"text":"\n","range":{"startLineNumber":75,"startColumn":1,"endLineNumber":75,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1140,"edits":[{"text":"\nfn process_haptic_callback() {","range":{"startLineNumber":76,"startColumn":1,"endLineNumber":76,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1141,"edits":[{"text":"\n    let cb = {","range":{"startLineNumber":77,"startColumn":1,"endLineNumber":77,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1142,"edits":[{"text":"\n        let guard = POLL_HAPTIC_CB.lock().ok();","range":{"startLineNumber":78,"startColumn":1,"endLineNumber":78,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1143,"edits":[{"text":"\n        guard.and_then(|g| *g)","range":{"startLineNumber":79,"startColumn":1,"endLineNumber":79,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1144,"edits":[{"text":"\n    };","range":{"startLineNumber":80,"startColumn":1,"endLineNumber":80,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1145,"edits":[{"text":"\n","range":{"startLineNumber":81,"startColumn":1,"endLineNumber":81,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1146,"edits":[{"text":"\n    let Some(callback) = cb else {","range":{"startLineNumber":82,"startColumn":1,"endLineNumber":82,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1147,"edits":[{"text":"\n        return;","range":{"startLineNumber":83,"startColumn":1,"endLineNumber":83,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1148,"edits":[{"text":"\n    };","range":{"startLineNumber":84,"startColumn":1,"endLineNumber":84,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1149,"edits":[{"text":"\n","range":{"startLineNumber":85,"startColumn":1,"endLineNumber":85,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1150,"edits":[{"text":"\n    for _ in 0..8 {","range":{"startLineNumber":86,"startColumn":1,"endLineNumber":86,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1151,"edits":[{"text":"\n        let mut req = HapticVibrationRequest::default();","range":{"startLineNumber":87,"startColumn":1,"endLineNumber":87,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1152,"edits":[{"text":"\n        if !callback(&mut req as *mut HapticVibrationRequest) {","range":{"startLineNumber":88,"startColumn":1,"endLineNumber":88,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1153,"edits":[{"text":"\n            break;","range":{"startLineNumber":89,"startColumn":1,"endLineNumber":89,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1154,"edits":[{"text":"\n        }","range":{"startLineNumber":90,"startColumn":1,"endLineNumber":90,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1155,"edits":[{"text":"\n        handle_haptic_request(req);","range":{"startLineNumber":91,"startColumn":1,"endLineNumber":91,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1156,"edits":[{"text":"\n    }","range":{"startLineNumber":92,"startColumn":1,"endLineNumber":92,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1157,"edits":[{"text":"\n}","range":{"startLineNumber":93,"startColumn":1,"endLineNumber":93,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1158,"edits":[{"text":"\n","range":{"startLineNumber":94,"startColumn":1,"endLineNumber":94,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1159,"edits":[{"text":"\npub fn on_frame() {","range":{"startLineNumber":95,"startColumn":1,"endLineNumber":95,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1160,"edits":[{"text":"\n    process_haptic_callback();","range":{"startLineNumber":96,"startColumn":1,"endLineNumber":96,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1161,"edits":[{"text":"\n    process_pending_stop();","range":{"startLineNumber":97,"startColumn":1,"endLineNumber":97,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1162,"edits":[{"text":"\n}","range":{"startLineNumber":98,"startColumn":1,"endLineNumber":98,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1163,"edits":[{"text":"\n","range":{"startLineNumber":99,"startColumn":1,"endLineNumber":99,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1164,"edits":[{"text":"\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {","range":{"startLineNumber":100,"startColumn":1,"endLineNumber":100,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1165,"edits":[{"text":"\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {","range":{"startLineNumber":101,"startColumn":1,"endLineNumber":101,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1166,"edits":[{"text":"\n        *guard = cb;","range":{"startLineNumber":102,"startColumn":1,"endLineNumber":102,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1167,"edits":[{"text":"\n    }","range":{"startLineNumber":103,"startColumn":1,"endLineNumber":103,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1168,"edits":[{"text":"\n}","range":{"startLineNumber":104,"startColumn":1,"endLineNumber":104,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1169,"edits":[{"text":"\n","range":{"startLineNumber":105,"startColumn":1,"endLineNumber":105,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1170,"edits":[{"text":"\nunsafe impl Sync for InterfaceVersions {}","range":{"startLineNumber":64,"startColumn":1,"endLineNumber":64,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1171,"edits":[{"text":"\n","range":{"startLineNumber":65,"startColumn":1,"endLineNumber":65,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1172,"edits":[{"text":"\nstatic HOST_CONTEXT: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());","range":{"startLineNumber":66,"startColumn":1,"endLineNumber":66,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1173,"edits":[{"text":"\nstatic DEVICE_INDEX: AtomicU32 = AtomicU32::new(u32::MAX);","range":{"startLineNumber":67,"startColumn":1,"endLineNumber":67,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1174,"edits":[{"text":"\nstatic DEVICE_ACTIVE: AtomicBool = AtomicBool::new(false);","range":{"startLineNumber":68,"startColumn":1,"endLineNumber":68,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1175,"edits":[{"text":"\n","range":{"startLineNumber":69,"startColumn":1,"endLineNumber":69,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1176,"edits":[{"text":"\nconst PROVIDER_IFACE_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_005\\0\";","range":{"startLineNumber":70,"startColumn":1,"endLineNumber":70,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1177,"edits":[{"text":"\nconst DEVICE_IFACE_CSTR: &[u8] = b\"ITrackedDeviceServerDriver_005\\0\";","range":{"startLineNumber":71,"startColumn":1,"endLineNumber":71,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1178,"edits":[{"text":"\n","range":{"startLineNumber":72,"startColumn":1,"endLineNumber":72,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1179,"edits":[{"text":"\nstatic PROVIDER_INTERFACE_VERSIONS: InterfaceVersions = InterfaceVersions([","range":{"startLineNumber":73,"startColumn":1,"endLineNumber":73,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1180,"edits":[{"text":"\n    PROVIDER_IFACE_CSTR.as_ptr().cast(),","range":{"startLineNumber":74,"startColumn":1,"endLineNumber":74,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1181,"edits":[{"text":"\n    DEVICE_IFACE_CSTR.as_ptr().cast(),","range":{"startLineNumber":75,"startColumn":1,"endLineNumber":75,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1182,"edits":[{"text":"\n    core::ptr::null(),","range":{"startLineNumber":76,"startColumn":1,"endLineNumber":76,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1183,"edits":[{"text":"\n]);","range":{"startLineNumber":77,"startColumn":1,"endLineNumber":77,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1184,"edits":[{"text":"\n","range":{"startLineNumber":78,"startColumn":1,"endLineNumber":78,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1185,"edits":[{"text":"\nextern \"C\" fn provider_init(","range":{"startLineNumber":79,"startColumn":1,"endLineNumber":79,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1186,"edits":[{"text":"\n    _this: *mut ServerTrackedDeviceProvider,","range":{"startLineNumber":80,"startColumn":1,"endLineNumber":80,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1187,"edits":[{"text":"\n    driver_context: *mut c_void,","range":{"startLineNumber":81,"startColumn":1,"endLineNumber":81,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1188,"edits":[{"text":"\n) -> EvRInitError {","range":{"startLineNumber":82,"startColumn":1,"endLineNumber":82,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1189,"edits":[{"text":"\n    HOST_CONTEXT.store(driver_context, Ordering::SeqCst);","range":{"startLineNumber":83,"startColumn":1,"endLineNumber":83,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1190,"edits":[{"text":"\n    VR_INIT_ERROR_NONE","range":{"startLineNumber":84,"startColumn":1,"endLineNumber":84,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1191,"edits":[{"text":"\n}","range":{"startLineNumber":85,"startColumn":1,"endLineNumber":85,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1192,"edits":[{"text":"\n","range":{"startLineNumber":86,"startColumn":1,"endLineNumber":86,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1193,"edits":[{"text":"\nextern \"C\" fn provider_cleanup(_this: *mut ServerTrackedDeviceProvider) {","range":{"startLineNumber":87,"startColumn":1,"endLineNumber":87,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1194,"edits":[{"text":"\n    HOST_CONTEXT.store(core::ptr::null_mut(), Ordering::SeqCst);","range":{"startLineNumber":88,"startColumn":1,"endLineNumber":88,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1195,"edits":[{"text":"\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);","range":{"startLineNumber":89,"startColumn":1,"endLineNumber":89,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1196,"edits":[{"text":"\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);","range":{"startLineNumber":90,"startColumn":1,"endLineNumber":90,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1197,"edits":[{"text":"\n    let _ = haptics::send_stop();","range":{"startLineNumber":91,"startColumn":1,"endLineNumber":91,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1198,"edits":[{"text":"\n}","range":{"startLineNumber":92,"startColumn":1,"endLineNumber":92,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1199,"edits":[{"text":"\n","range":{"startLineNumber":93,"startColumn":1,"endLineNumber":93,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1200,"edits":[{"text":"\nextern \"C\" fn provider_get_interface_versions(","range":{"startLineNumber":94,"startColumn":1,"endLineNumber":94,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1201,"edits":[{"text":"\n    _this: *mut ServerTrackedDeviceProvider,","range":{"startLineNumber":95,"startColumn":1,"endLineNumber":95,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1202,"edits":[{"text":"\n) -> *const *const c_char {","range":{"startLineNumber":96,"startColumn":1,"endLineNumber":96,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1203,"edits":[{"text":"\n    PROVIDER_INTERFACE_VERSIONS.0.as_ptr()","range":{"startLineNumber":97,"startColumn":1,"endLineNumber":97,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1204,"edits":[{"text":"\n}","range":{"startLineNumber":98,"startColumn":1,"endLineNumber":98,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1205,"edits":[{"text":"\n","range":{"startLineNumber":99,"startColumn":1,"endLineNumber":99,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1206,"edits":[{"text":"\nextern \"C\" fn provider_run_frame(_this: *mut ServerTrackedDeviceProvider) {","range":{"startLineNumber":100,"startColumn":1,"endLineNumber":100,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1207,"edits":[{"text":"\n    haptics::on_frame();","range":{"startLineNumber":101,"startColumn":1,"endLineNumber":101,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1208,"edits":[{"text":"\n}","range":{"startLineNumber":102,"startColumn":1,"endLineNumber":102,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1209,"edits":[{"text":"\n","range":{"startLineNumber":103,"startColumn":1,"endLineNumber":103,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1210,"edits":[{"text":"\nextern \"C\" fn provider_should_block_standby_mode(_this: *mut ServerTrackedDeviceProvider) -> bool {","range":{"startLineNumber":104,"startColumn":1,"endLineNumber":104,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1211,"edits":[{"text":"\n    false","range":{"startLineNumber":105,"startColumn":1,"endLineNumber":105,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1212,"edits":[{"text":"\n}","range":{"startLineNumber":106,"startColumn":1,"endLineNumber":106,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1213,"edits":[{"text":"\n","range":{"startLineNumber":107,"startColumn":1,"endLineNumber":107,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1214,"edits":[{"text":"\nextern \"C\" fn provider_enter_standby(_this: *mut ServerTrackedDeviceProvider) {}","range":{"startLineNumber":108,"startColumn":1,"endLineNumber":108,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1215,"edits":[{"text":"\n","range":{"startLineNumber":109,"startColumn":1,"endLineNumber":109,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1216,"edits":[{"text":"\nextern \"C\" fn provider_leave_standby(_this: *mut ServerTrackedDeviceProvider) {}","range":{"startLineNumber":110,"startColumn":1,"endLineNumber":110,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1217,"edits":[{"text":"\n","range":{"startLineNumber":111,"startColumn":1,"endLineNumber":111,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1218,"edits":[{"text":"\nextern \"C\" fn device_activate(_this: *mut TrackedDeviceServerDriver, object_id: u32) -> EvRInitError {","range":{"startLineNumber":112,"startColumn":1,"endLineNumber":112,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1219,"edits":[{"text":"\n    DEVICE_INDEX.store(object_id, Ordering::SeqCst);","range":{"startLineNumber":113,"startColumn":1,"endLineNumber":113,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1220,"edits":[{"text":"\n    DEVICE_ACTIVE.store(true, Ordering::SeqCst);","range":{"startLineNumber":114,"startColumn":1,"endLineNumber":114,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1221,"edits":[{"text":"\n    VR_INIT_ERROR_NONE","range":{"startLineNumber":115,"startColumn":1,"endLineNumber":115,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1222,"edits":[{"text":"\n}","range":{"startLineNumber":116,"startColumn":1,"endLineNumber":116,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1223,"edits":[{"text":"\n","range":{"startLineNumber":117,"startColumn":1,"endLineNumber":117,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1224,"edits":[{"text":"\nextern \"C\" fn device_deactivate(_this: *mut TrackedDeviceServerDriver) {","range":{"startLineNumber":118,"startColumn":1,"endLineNumber":118,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1225,"edits":[{"text":"\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);","range":{"startLineNumber":119,"startColumn":1,"endLineNumber":119,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1226,"edits":[{"text":"\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);","range":{"startLineNumber":120,"startColumn":1,"endLineNumber":120,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1227,"edits":[{"text":"\n    let _ = haptics::send_stop();","range":{"startLineNumber":121,"startColumn":1,"endLineNumber":121,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1228,"edits":[{"text":"\n}","range":{"startLineNumber":122,"startColumn":1,"endLineNumber":122,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1229,"edits":[{"text":"\n","range":{"startLineNumber":123,"startColumn":1,"endLineNumber":123,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1230,"edits":[{"text":"\nextern \"C\" fn device_enter_standby(_this: *mut TrackedDeviceServerDriver) {","range":{"startLineNumber":124,"startColumn":1,"endLineNumber":124,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1231,"edits":[{"text":"\n    let _ = haptics::send_stop();","range":{"startLineNumber":125,"startColumn":1,"endLineNumber":125,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1232,"edits":[{"text":"\n}","range":{"startLineNumber":126,"startColumn":1,"endLineNumber":126,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1233,"edits":[{"text":"\n","range":{"startLineNumber":127,"startColumn":1,"endLineNumber":127,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1234,"edits":[{"text":"\nextern \"C\" fn device_get_component(","range":{"startLineNumber":128,"startColumn":1,"endLineNumber":128,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1235,"edits":[{"text":"\n    _this: *mut TrackedDeviceServerDriver,","range":{"startLineNumber":129,"startColumn":1,"endLineNumber":129,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1236,"edits":[{"text":"\n    _component_name: *const c_char,","range":{"startLineNumber":130,"startColumn":1,"endLineNumber":130,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1237,"edits":[{"text":"\n) -> *mut c_void {","range":{"startLineNumber":131,"startColumn":1,"endLineNumber":131,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1238,"edits":[{"text":"\n    core::ptr::null_mut()","range":{"startLineNumber":132,"startColumn":1,"endLineNumber":132,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1239,"edits":[{"text":"\n}","range":{"startLineNumber":133,"startColumn":1,"endLineNumber":133,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1240,"edits":[{"text":"\n","range":{"startLineNumber":134,"startColumn":1,"endLineNumber":134,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1241,"edits":[{"text":"\nextern \"C\" fn device_debug_request(","range":{"startLineNumber":135,"startColumn":1,"endLineNumber":135,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1242,"edits":[{"text":"\n    _this: *mut TrackedDeviceServerDriver,","range":{"startLineNumber":136,"startColumn":1,"endLineNumber":136,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1243,"edits":[{"text":"\n    _request: *const c_char,","range":{"startLineNumber":137,"startColumn":1,"endLineNumber":137,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1244,"edits":[{"text":"\n    response: *mut c_char,","range":{"startLineNumber":138,"startColumn":1,"endLineNumber":138,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1245,"edits":[{"text":"\n    response_size: u32,","range":{"startLineNumber":139,"startColumn":1,"endLineNumber":139,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1246,"edits":[{"text":"\n) {","range":{"startLineNumber":140,"startColumn":1,"endLineNumber":140,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1247,"edits":[{"text":"\n    if response.is_null() || response_size == 0 {","range":{"startLineNumber":141,"startColumn":1,"endLineNumber":141,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1248,"edits":[{"text":"\n        return;","range":{"startLineNumber":142,"startColumn":1,"endLineNumber":142,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1249,"edits":[{"text":"\n    }","range":{"startLineNumber":143,"startColumn":1,"endLineNumber":143,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1250,"edits":[{"text":"\n    unsafe {","range":{"startLineNumber":144,"startColumn":1,"endLineNumber":144,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1251,"edits":[{"text":"\n        *response = 0;","range":{"startLineNumber":145,"startColumn":1,"endLineNumber":145,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1252,"edits":[{"text":"\n    }","range":{"startLineNumber":146,"startColumn":1,"endLineNumber":146,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1253,"edits":[{"text":"\n}","range":{"startLineNumber":147,"startColumn":1,"endLineNumber":147,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1254,"edits":[{"text":"\n","range":{"startLineNumber":148,"startColumn":1,"endLineNumber":148,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1255,"edits":[{"text":"\nextern \"C\" fn device_get_pose(_this: *mut TrackedDeviceServerDriver) -> DriverPose {","range":{"startLineNumber":149,"startColumn":1,"endLineNumber":149,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1256,"edits":[{"text":"\n    DriverPose { _reserved: [0; 128] }","range":{"startLineNumber":150,"startColumn":1,"endLineNumber":150,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1257,"edits":[{"text":"\n}","range":{"startLineNumber":151,"startColumn":1,"endLineNumber":151,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1258,"edits":[{"text":"\n","range":{"startLineNumber":152,"startColumn":1,"endLineNumber":152,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1259,"edits":[{"text":"\nstatic PROVIDER_VTABLE: ServerTrackedDeviceProviderVTable = ServerTrackedDeviceProviderVTable {","range":{"startLineNumber":153,"startColumn":1,"endLineNumber":153,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1260,"edits":[{"text":"\n    init: provider_init,","range":{"startLineNumber":154,"startColumn":1,"endLineNumber":154,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1261,"edits":[{"text":"\n    cleanup: provider_cleanup,","range":{"startLineNumber":155,"startColumn":1,"endLineNumber":155,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1262,"edits":[{"text":"\n    get_interface_versions: provider_get_interface_versions,","range":{"startLineNumber":156,"startColumn":1,"endLineNumber":156,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1263,"edits":[{"text":"\n    run_frame: provider_run_frame,","range":{"startLineNumber":157,"startColumn":1,"endLineNumber":157,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1264,"edits":[{"text":"\n    should_block_standby_mode: provider_should_block_standby_mode,","range":{"startLineNumber":158,"startColumn":1,"endLineNumber":158,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1265,"edits":[{"text":"\n    enter_standby: provider_enter_standby,","range":{"startLineNumber":159,"startColumn":1,"endLineNumber":159,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1266,"edits":[{"text":"\n    leave_standby: provider_leave_standby,","range":{"startLineNumber":160,"startColumn":1,"endLineNumber":160,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1267,"edits":[{"text":"\n};","range":{"startLineNumber":161,"startColumn":1,"endLineNumber":161,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1268,"edits":[{"text":"\n","range":{"startLineNumber":162,"startColumn":1,"endLineNumber":162,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1269,"edits":[{"text":"\nstatic DEVICE_VTABLE: TrackedDeviceServerDriverVTable = TrackedDeviceServerDriverVTable {","range":{"startLineNumber":163,"startColumn":1,"endLineNumber":163,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1270,"edits":[{"text":"\n    activate: device_activate,","range":{"startLineNumber":164,"startColumn":1,"endLineNumber":164,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1271,"edits":[{"text":"\n    deactivate: device_deactivate,","range":{"startLineNumber":165,"startColumn":1,"endLineNumber":165,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1272,"edits":[{"text":"\n    enter_standby: device_enter_standby,","range":{"startLineNumber":166,"startColumn":1,"endLineNumber":166,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1273,"edits":[{"text":"\n    get_component: device_get_component,","range":{"startLineNumber":167,"startColumn":1,"endLineNumber":167,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1274,"edits":[{"text":"\n    debug_request: device_debug_request,","range":{"startLineNumber":168,"startColumn":1,"endLineNumber":168,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1275,"edits":[{"text":"\n    get_pose: device_get_pose,","range":{"startLineNumber":169,"startColumn":1,"endLineNumber":169,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1276,"edits":[{"text":"\n};","range":{"startLineNumber":170,"startColumn":1,"endLineNumber":170,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1277,"edits":[{"text":"\n","range":{"startLineNumber":171,"startColumn":1,"endLineNumber":171,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1278,"edits":[{"text":"\nstatic PROVIDER: ServerTrackedDeviceProvider = ServerTrackedDeviceProvider {","range":{"startLineNumber":172,"startColumn":1,"endLineNumber":172,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1279,"edits":[{"text":"\n    vtable: &PROVIDER_VTABLE,","range":{"startLineNumber":173,"startColumn":1,"endLineNumber":173,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1280,"edits":[{"text":"\n};","range":{"startLineNumber":174,"startColumn":1,"endLineNumber":174,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1281,"edits":[{"text":"\n","range":{"startLineNumber":175,"startColumn":1,"endLineNumber":175,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1282,"edits":[{"text":"\nstatic DEVICE: TrackedDeviceServerDriver = TrackedDeviceServerDriver {","range":{"startLineNumber":176,"startColumn":1,"endLineNumber":176,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1283,"edits":[{"text":"\n    vtable: &DEVICE_VTABLE,","range":{"startLineNumber":177,"startColumn":1,"endLineNumber":177,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1284,"edits":[{"text":"\n};","range":{"startLineNumber":178,"startColumn":1,"endLineNumber":178,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1285,"edits":[{"text":"\n","range":{"startLineNumber":179,"startColumn":1,"endLineNumber":179,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1286,"edits":[{"text":"\nfn set_return_code(out: *mut i32, code: DriverReturnCode) {","range":{"startLineNumber":180,"startColumn":1,"endLineNumber":180,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1287,"edits":[{"text":"\n    if !out.is_null() {","range":{"startLineNumber":181,"startColumn":1,"endLineNumber":181,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1288,"edits":[{"text":"\n        unsafe {","range":{"startLineNumber":182,"startColumn":1,"endLineNumber":182,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1289,"edits":[{"text":"\n            *out = code as i32;","range":{"startLineNumber":183,"startColumn":1,"endLineNumber":183,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1290,"edits":[{"text":"\n        }","range":{"startLineNumber":184,"startColumn":1,"endLineNumber":184,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1291,"edits":[{"text":"\n    }","range":{"startLineNumber":185,"startColumn":1,"endLineNumber":185,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1292,"edits":[{"text":"\n}","range":{"startLineNumber":186,"startColumn":1,"endLineNumber":186,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1293,"edits":[{"text":"\n","range":{"startLineNumber":187,"startColumn":1,"endLineNumber":187,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1294,"edits":[{"text":"\nfn read_interface_name(ptr: *const i8) -> Option<&'static str> {","range":{"startLineNumber":188,"startColumn":1,"endLineNumber":188,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1295,"edits":[{"text":"\n    if ptr.is_null() {","range":{"startLineNumber":189,"startColumn":1,"endLineNumber":189,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1296,"edits":[{"text":"\n        return None;","range":{"startLineNumber":190,"startColumn":1,"endLineNumber":190,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1297,"edits":[{"text":"\n    }","range":{"startLineNumber":191,"startColumn":1,"endLineNumber":191,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1298,"edits":[{"text":"\n","range":{"startLineNumber":192,"startColumn":1,"endLineNumber":192,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1299,"edits":[{"text":"\n    let cstr = unsafe { CStr::from_ptr(ptr) };","range":{"startLineNumber":193,"startColumn":1,"endLineNumber":193,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1300,"edits":[{"text":"\n    let Ok(name) = cstr.to_str() else {","range":{"startLineNumber":194,"startColumn":1,"endLineNumber":194,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1301,"edits":[{"text":"\n        return None;","range":{"startLineNumber":195,"startColumn":1,"endLineNumber":195,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1302,"edits":[{"text":"\n    };","range":{"startLineNumber":196,"startColumn":1,"endLineNumber":196,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1303,"edits":[{"text":"\n","range":{"startLineNumber":197,"startColumn":1,"endLineNumber":197,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1304,"edits":[{"text":"\n    if name == SERVER_PROVIDER_INTERFACE {","range":{"startLineNumber":198,"startColumn":1,"endLineNumber":198,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1305,"edits":[{"text":"\n        return Some(SERVER_PROVIDER_INTERFACE);","range":{"startLineNumber":199,"startColumn":1,"endLineNumber":199,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1306,"edits":[{"text":"\n    }","range":{"startLineNumber":200,"startColumn":1,"endLineNumber":200,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1307,"edits":[{"text":"\n    if name == TRACKED_DEVICE_INTERFACE {","range":{"startLineNumber":201,"startColumn":1,"endLineNumber":201,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1308,"edits":[{"text":"\n        return Some(TRACKED_DEVICE_INTERFACE);","range":{"startLineNumber":202,"startColumn":1,"endLineNumber":202,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1309,"edits":[{"text":"\n    }","range":{"startLineNumber":203,"startColumn":1,"endLineNumber":203,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1310,"edits":[{"text":"\n","range":{"startLineNumber":204,"startColumn":1,"endLineNumber":204,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1311,"edits":[{"text":"\n    None","range":{"startLineNumber":205,"startColumn":1,"endLineNumber":205,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1312,"edits":[{"text":"\n}","range":{"startLineNumber":206,"startColumn":1,"endLineNumber":206,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1313,"edits":[{"text":"\n","range":{"startLineNumber":207,"startColumn":1,"endLineNumber":207,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1314,"edits":[{"text":"\npub fn hmd_driver_factory(","range":{"startLineNumber":208,"startColumn":1,"endLineNumber":208,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1315,"edits":[{"text":"\n    p_interface_name: *const c_char,","range":{"startLineNumber":209,"startColumn":1,"endLineNumber":209,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1316,"edits":[{"text":"\n    p_return_code: *mut i32,","range":{"startLineNumber":210,"startColumn":1,"endLineNumber":210,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1317,"edits":[{"text":"\n) -> *mut c_void {","range":{"startLineNumber":211,"startColumn":1,"endLineNumber":211,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1318,"edits":[{"text":"\n    let Some(interface_name) = read_interface_name(p_interface_name) else {","range":{"startLineNumber":212,"startColumn":1,"endLineNumber":212,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1319,"edits":[{"text":"\n        set_return_code(p_return_code, DriverReturnCode::InitInterfaceNotFound);","range":{"startLineNumber":213,"startColumn":1,"endLineNumber":213,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1320,"edits":[{"text":"\n        return core::ptr::null_mut();","range":{"startLineNumber":214,"startColumn":1,"endLineNumber":214,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1321,"edits":[{"text":"\n    };","range":{"startLineNumber":215,"startColumn":1,"endLineNumber":215,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1322,"edits":[{"text":"\n","range":{"startLineNumber":216,"startColumn":1,"endLineNumber":216,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1323,"edits":[{"text":"\n    if interface_name == SERVER_PROVIDER_INTERFACE {","range":{"startLineNumber":217,"startColumn":1,"endLineNumber":217,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1324,"edits":[{"text":"\n        set_return_code(p_return_code, DriverReturnCode::None);","range":{"startLineNumber":218,"startColumn":1,"endLineNumber":218,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1325,"edits":[{"text":"\n        return (&PROVIDER as *const ServerTrackedDeviceProvider)","range":{"startLineNumber":219,"startColumn":1,"endLineNumber":219,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1326,"edits":[{"text":"\n            .cast_mut()","range":{"startLineNumber":220,"startColumn":1,"endLineNumber":220,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1327,"edits":[{"text":"\n            .cast();","range":{"startLineNumber":221,"startColumn":1,"endLineNumber":221,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1328,"edits":[{"text":"\n    }","range":{"startLineNumber":222,"startColumn":1,"endLineNumber":222,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1329,"edits":[{"text":"\n","range":{"startLineNumber":223,"startColumn":1,"endLineNumber":223,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1330,"edits":[{"text":"\n    if interface_name == TRACKED_DEVICE_INTERFACE {","range":{"startLineNumber":224,"startColumn":1,"endLineNumber":224,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1331,"edits":[{"text":"\n        set_return_code(p_return_code, DriverReturnCode::None);","range":{"startLineNumber":225,"startColumn":1,"endLineNumber":225,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1332,"edits":[{"text":"\n        return (&DEVICE as *const TrackedDeviceServerDriver)","range":{"startLineNumber":226,"startColumn":1,"endLineNumber":226,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1333,"edits":[{"text":"\n            .cast_mut()","range":{"startLineNumber":227,"startColumn":1,"endLineNumber":227,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1334,"edits":[{"text":"\n            .cast();","range":{"startLineNumber":228,"startColumn":1,"endLineNumber":228,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1335,"edits":[{"text":"\n    }","range":{"startLineNumber":229,"startColumn":1,"endLineNumber":229,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1336,"edits":[{"text":"\n","range":{"startLineNumber":230,"startColumn":1,"endLineNumber":230,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1337,"edits":[{"text":"\n    set_return_code(p_return_code, DriverReturnCode::Unknown);","range":{"startLineNumber":231,"startColumn":1,"endLineNumber":231,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1338,"edits":[{"text":"\n    core::ptr::null_mut()","range":{"startLineNumber":232,"startColumn":1,"endLineNumber":232,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1339,"edits":[{"text":"\n}","range":{"startLineNumber":233,"startColumn":1,"endLineNumber":233,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1340,"edits":[{"text":"\n","range":{"startLineNumber":234,"startColumn":1,"endLineNumber":234,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1343,"edits":[{"text":"use core::ffi::c_void;\r\nuse core::ffi::CStr;\r\nuse core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU32, AtomicU64, Ordering};\r\nuse core::ffi::c_void;\nuse std::os::raw::c_char;\n\nmod haptics;\nmod openvr_scaffold;\n\npub use haptics::{HapticVibrationRequest, PollHapticCallback};\npub extern \"C\" fn HmdDriverFactory(\r\n    p_interface_name: *const c_char,\r\n    p_return_code: *mut i32,\r\n) -> *mut c_void {\r\n    openvr_scaffold::hmd_driver_factory(p_interface_name, p_return_code)\n}\r\n\r\n/// Test-only export for early integration while full OpenVR vtable glue is in progress.\r\n/// Returns 0 on success, non-zero on failure.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_SendAmplitude(amplitude: f32) -> i32 {\r\n    match haptics::send_amplitude(amplitude) {\n        Ok(()) => 0,\r\n        Err(_) => 1,\r\n    }\r\n}\r\n\r\n/// Explicit stop command helper for testing and recovery.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_Stop() -> i32 {\r\n    match haptics::send_stop() {\n        Ok(()) => 0,\r\n        Err(_) => 1,\r\n    }\r\n}\r\n\r\n/// Temporary bridge: host/OpenVR shim can register a callback polled each frame.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_SetPollHapticCallback(cb: Option<PollHapticCallback>) {\r\n    haptics::set_poll_haptic_callback(cb);\n}\r\n\r\n/// Temporary direct injection helper while event wiring is being built.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_InjectHaptic(\r\n    amplitude: f32,\r\n    duration_seconds: f32,\r\n    frequency: f32,\r\n) {\r\n    haptics::handle_haptic_request(HapticVibrationRequest {\n        amplitude,\r\n        duration_seconds,\r\n        frequency,\r\n    });\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":57,"endColumn":1}},{"text":"","range":{"startLineNumber":57,"startColumn":1,"endLineNumber":379,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","scheme":"file"},"requestId":"request_3cb8f730-b84c-454e-9cfe-afd5a70a9203","epoch":1345,"edits":[{"text":"use core::ffi::c_void;\r\nuse std::os::raw::c_char;\r\n\r\nmod haptics;\r\nmod openvr_scaffold;\r\n\r\npub use haptics::{HapticVibrationRequest, PollHapticCallback};\r\n\n#[unsafe(no_mangle)]\npub extern \"C\" fn HmdDriverFactory(\r\n    p_interface_name: *const c_char,\r\n    p_return_code: *mut i32,\r\n) -> *mut c_void {\r\n    openvr_scaffold::hmd_driver_factory(p_interface_name, p_return_code)\r\n}\r\n\r\n/// Test-only export for early integration while full OpenVR vtable glue is in progress.\r\n/// Returns 0 on success, non-zero on failure.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_SendAmplitude(amplitude: f32) -> i32 {\r\n    match haptics::send_amplitude(amplitude) {\r\n        Ok(()) => 0,\r\n        Err(_) => 1,\r\n    }\r\n}\r\n\r\n/// Explicit stop command helper for testing and recovery.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_Stop() -> i32 {\r\n    match haptics::send_stop() {\r\n        Ok(()) => 0,\r\n        Err(_) => 1,\r\n    }\r\n}\r\n\r\n/// Temporary bridge: host/OpenVR shim can register a callback polled each frame.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_SetPollHapticCallback(cb: Option<PollHapticCallback>) {\r\n    haptics::set_poll_haptic_callback(cb);\r\n}\r\n\r\n/// Temporary direct injection helper while event wiring is being built.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_InjectHaptic(\r\n    amplitude: f32,\r\n    duration_seconds: f32,\r\n    frequency: f32,\r\n) {\r\n    haptics::handle_haptic_request(HapticVibrationRequest {\r\n        amplitude,\r\n        duration_seconds,\r\n        frequency,\r\n    });\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":56,"endColumn":1}},{"text":"","range":{"startLineNumber":56,"startColumn":1,"endLineNumber":57,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_d489ba23-ce50-4bec-9632-8fe576c78ee8","epoch":1349,"edits":[{"text":"//! Haptics domain logic for the Big Haptic Driver.\n//!\n//! This module owns:\n//! - mapping vibration requests to HID packets,\n//! - scheduling delayed stop behavior,\n//! - polling an optional host-provided callback each frame.\n\nuse core::sync::atomic::{AtomicU64, Ordering};\r\nuse std::sync::{Mutex, OnceLock};\r\nuse std::time::Instant;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\npub struct HapticVibrationRequest {\r\n    pub amplitude: f32,\r\n    pub duration_seconds: f32,\r\n    pub frequency: f32,\r\n}\r\n\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic STOP_DEADLINE_MS: AtomicU64 = AtomicU64::new(0);\r\nstatic START_INSTANT: OnceLock<Instant> = OnceLock::new();\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\n\r\nfn send_packet(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    hid_bridge::HidBridge::new()\r\n        .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n}\r\n\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    send_packet(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn now_ms() -> u64 {\r\n    let start = START_INSTANT.get_or_init(Instant::now);\r\n    let ms = Instant::now().duration_since(*start).as_millis();\r\n    ms.min(u128::from(u64::MAX)) as u64\r\n}\r\n\r\nfn schedule_stop_after(duration_seconds: f32) {\r\n    if duration_seconds <= 0.0 {\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let duration_ms = (duration_seconds * 1000.0).round().max(1.0) as u64;\r\n    let deadline = now_ms().saturating_add(duration_ms);\r\n    STOP_DEADLINE_MS.store(deadline, Ordering::SeqCst);\r\n}\r\n\r\nfn process_pending_stop() {\r\n    let deadline = STOP_DEADLINE_MS.load(Ordering::SeqCst);\r\n    if deadline == 0 {\r\n        return;\r\n    }\r\n\r\n    if now_ms() >= deadline {\r\n        let _ = send_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        let _ = send_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let _ = send_amplitude(req.amplitude);\r\n    schedule_stop_after(req.duration_seconds);\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n    process_pending_stop();\r\n}\r\n\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":113,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_d489ba23-ce50-4bec-9632-8fe576c78ee8","epoch":1352,"edits":[{"text":"//! OpenVR ABI/vtable scaffolding for the Big Haptic Driver.\n//!\n//! This module owns C ABI exports and interface routing for SteamVR-facing\n//! provider/device entry points, while delegating haptics behavior to\n//! the `haptics` module.\n\nuse core::ffi::{c_void, CStr};\r\nuse core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU32, Ordering};\r\nuse std::os::raw::c_char;\r\n\r\nuse crate::haptics;\r\n\r\nconst SERVER_PROVIDER_INTERFACE: &str = \"IServerTrackedDeviceProvider_005\";\r\nconst TRACKED_DEVICE_INTERFACE: &str = \"ITrackedDeviceServerDriver_005\";\r\n\r\n#[repr(i32)]\r\nenum DriverReturnCode {\r\n    None = 0,\r\n    Unknown = 1,\r\n    InitInterfaceNotFound = 105,\r\n}\r\n\r\ntype EvRInitError = i32;\r\nconst VR_INIT_ERROR_NONE: EvRInitError = 0;\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nstruct DriverPose {\r\n    _reserved: [u8; 128],\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerTrackedDeviceProviderVTable {\r\n    init: extern \"C\" fn(*mut ServerTrackedDeviceProvider, *mut c_void) -> EvRInitError,\r\n    cleanup: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    get_interface_versions:\r\n        extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> *const *const c_char,\r\n    run_frame: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    should_block_standby_mode: extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> bool,\r\n    enter_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    leave_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n}\r\n\r\n#[repr(C)]\r\nstruct TrackedDeviceServerDriverVTable {\r\n    activate: extern \"C\" fn(*mut TrackedDeviceServerDriver, u32) -> EvRInitError,\r\n    deactivate: extern \"C\" fn(*mut TrackedDeviceServerDriver),\r\n    enter_standby: extern \"C\" fn(*mut TrackedDeviceServerDriver),\r\n    get_component:\r\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char) -> *mut c_void,\r\n    debug_request:\r\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char, *mut c_char, u32),\r\n    get_pose: extern \"C\" fn(*mut TrackedDeviceServerDriver) -> DriverPose,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerTrackedDeviceProvider {\r\n    vtable: *const ServerTrackedDeviceProviderVTable,\r\n}\r\nunsafe impl Sync for ServerTrackedDeviceProvider {}\r\n\r\n#[repr(C)]\r\nstruct TrackedDeviceServerDriver {\r\n    vtable: *const TrackedDeviceServerDriverVTable,\r\n}\r\nunsafe impl Sync for TrackedDeviceServerDriver {}\r\n\r\n#[repr(C)]\r\nstruct InterfaceVersions([*const c_char; 3]);\r\nunsafe impl Sync for InterfaceVersions {}\r\n\r\nstatic HOST_CONTEXT: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DEVICE_INDEX: AtomicU32 = AtomicU32::new(u32::MAX);\r\nstatic DEVICE_ACTIVE: AtomicBool = AtomicBool::new(false);\r\n\r\nconst PROVIDER_IFACE_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_005\\0\";\r\nconst DEVICE_IFACE_CSTR: &[u8] = b\"ITrackedDeviceServerDriver_005\\0\";\r\n\r\nstatic PROVIDER_INTERFACE_VERSIONS: InterfaceVersions = InterfaceVersions([\r\n    PROVIDER_IFACE_CSTR.as_ptr().cast(),\r\n    DEVICE_IFACE_CSTR.as_ptr().cast(),\r\n    core::ptr::null(),\r\n]);\r\n\r\nextern \"C\" fn provider_init(\r\n    _this: *mut ServerTrackedDeviceProvider,\r\n    driver_context: *mut c_void,\r\n) -> EvRInitError {\r\n    HOST_CONTEXT.store(driver_context, Ordering::SeqCst);\r\n    VR_INIT_ERROR_NONE\r\n}\r\n\r\nextern \"C\" fn provider_cleanup(_this: *mut ServerTrackedDeviceProvider) {\r\n    HOST_CONTEXT.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\r\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn provider_get_interface_versions(\r\n    _this: *mut ServerTrackedDeviceProvider,\r\n) -> *const *const c_char {\r\n    PROVIDER_INTERFACE_VERSIONS.0.as_ptr()\r\n}\r\n\r\nextern \"C\" fn provider_run_frame(_this: *mut ServerTrackedDeviceProvider) {\r\n    haptics::on_frame();\r\n}\r\n\r\nextern \"C\" fn provider_should_block_standby_mode(_this: *mut ServerTrackedDeviceProvider) -> bool {\r\n    false\r\n}\r\n\r\nextern \"C\" fn provider_enter_standby(_this: *mut ServerTrackedDeviceProvider) {}\r\n\r\nextern \"C\" fn provider_leave_standby(_this: *mut ServerTrackedDeviceProvider) {}\r\n\r\nextern \"C\" fn device_activate(_this: *mut TrackedDeviceServerDriver, object_id: u32) -> EvRInitError {\r\n    DEVICE_INDEX.store(object_id, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(true, Ordering::SeqCst);\r\n    VR_INIT_ERROR_NONE\r\n}\r\n\r\nextern \"C\" fn device_deactivate(_this: *mut TrackedDeviceServerDriver) {\r\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\r\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn device_enter_standby(_this: *mut TrackedDeviceServerDriver) {\r\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn device_get_component(\r\n    _this: *mut TrackedDeviceServerDriver,\r\n    _component_name: *const c_char,\r\n) -> *mut c_void {\r\n    core::ptr::null_mut()\r\n}\r\n\r\nextern \"C\" fn device_debug_request(\r\n    _this: *mut TrackedDeviceServerDriver,\r\n    _request: *const c_char,\r\n    response: *mut c_char,\r\n    response_size: u32,\r\n) {\r\n    if response.is_null() || response_size == 0 {\r\n        return;\r\n    }\r\n    unsafe {\r\n        *response = 0;\r\n    }\r\n}\r\n\r\nextern \"C\" fn device_get_pose(_this: *mut TrackedDeviceServerDriver) -> DriverPose {\r\n    DriverPose { _reserved: [0; 128] }\r\n}\r\n\r\nstatic PROVIDER_VTABLE: ServerTrackedDeviceProviderVTable = ServerTrackedDeviceProviderVTable {\r\n    init: provider_init,\r\n    cleanup: provider_cleanup,\r\n    get_interface_versions: provider_get_interface_versions,\r\n    run_frame: provider_run_frame,\r\n    should_block_standby_mode: provider_should_block_standby_mode,\r\n    enter_standby: provider_enter_standby,\r\n    leave_standby: provider_leave_standby,\r\n};\r\n\r\nstatic DEVICE_VTABLE: TrackedDeviceServerDriverVTable = TrackedDeviceServerDriverVTable {\r\n    activate: device_activate,\r\n    deactivate: device_deactivate,\r\n    enter_standby: device_enter_standby,\r\n    get_component: device_get_component,\r\n    debug_request: device_debug_request,\r\n    get_pose: device_get_pose,\r\n};\r\n\r\nstatic PROVIDER: ServerTrackedDeviceProvider = ServerTrackedDeviceProvider {\r\n    vtable: &PROVIDER_VTABLE,\r\n};\r\n\r\nstatic DEVICE: TrackedDeviceServerDriver = TrackedDeviceServerDriver {\r\n    vtable: &DEVICE_VTABLE,\r\n};\r\n\r\nfn set_return_code(out: *mut i32, code: DriverReturnCode) {\r\n    if !out.is_null() {\r\n        unsafe {\r\n            *out = code as i32;\r\n        }\r\n    }\r\n}\r\n\r\nfn read_interface_name(ptr: *const i8) -> Option<&'static str> {\r\n    if ptr.is_null() {\r\n        return None;\r\n    }\r\n\r\n    let cstr = unsafe { CStr::from_ptr(ptr) };\r\n    let Ok(name) = cstr.to_str() else {\r\n        return None;\r\n    };\r\n\r\n    if name == SERVER_PROVIDER_INTERFACE {\r\n        return Some(SERVER_PROVIDER_INTERFACE);\r\n    }\r\n    if name == TRACKED_DEVICE_INTERFACE {\r\n        return Some(TRACKED_DEVICE_INTERFACE);\r\n    }\r\n\r\n    None\r\n}\r\n\r\npub fn hmd_driver_factory(\r\n    p_interface_name: *const c_char,\r\n    p_return_code: *mut i32,\r\n) -> *mut c_void {\r\n    let Some(interface_name) = read_interface_name(p_interface_name) else {\r\n        set_return_code(p_return_code, DriverReturnCode::InitInterfaceNotFound);\r\n        return core::ptr::null_mut();\r\n    };\r\n\r\n    if interface_name == SERVER_PROVIDER_INTERFACE {\r\n        set_return_code(p_return_code, DriverReturnCode::None);\r\n        return (&PROVIDER as *const ServerTrackedDeviceProvider)\r\n            .cast_mut()\r\n            .cast();\r\n    }\r\n\r\n    if interface_name == TRACKED_DEVICE_INTERFACE {\r\n        set_return_code(p_return_code, DriverReturnCode::None);\r\n        return (&DEVICE as *const TrackedDeviceServerDriver)\r\n            .cast_mut()\r\n            .cast();\r\n    }\r\n\r\n    set_return_code(p_return_code, DriverReturnCode::Unknown);\r\n    core::ptr::null_mut()\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":241,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","scheme":"file"},"requestId":"request_d489ba23-ce50-4bec-9632-8fe576c78ee8","epoch":1355,"edits":[{"text":"//! Big Haptic Driver crate entrypoints.\n//!\n//! `lib.rs` stays intentionally thin: it exposes C ABI exports and delegates\n//! implementation details to focused modules.\n\nuse core::ffi::c_void;\r\nuse std::os::raw::c_char;\r\n\r\nmod haptics;\r\nmod openvr_scaffold;\r\n\r\npub use haptics::{HapticVibrationRequest, PollHapticCallback};\r\n\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn HmdDriverFactory(\r\n    p_interface_name: *const c_char,\r\n    p_return_code: *mut i32,\r\n) -> *mut c_void {\r\n    openvr_scaffold::hmd_driver_factory(p_interface_name, p_return_code)\r\n}\r\n\r\n/// Test-only export for early integration while full OpenVR vtable glue is in progress.\r\n/// Returns 0 on success, non-zero on failure.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_SendAmplitude(amplitude: f32) -> i32 {\r\n    match haptics::send_amplitude(amplitude) {\r\n        Ok(()) => 0,\r\n        Err(_) => 1,\r\n    }\r\n}\r\n\r\n/// Explicit stop command helper for testing and recovery.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_Stop() -> i32 {\r\n    match haptics::send_stop() {\r\n        Ok(()) => 0,\r\n        Err(_) => 1,\r\n    }\r\n}\r\n\r\n/// Temporary bridge: host/OpenVR shim can register a callback polled each frame.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_SetPollHapticCallback(cb: Option<PollHapticCallback>) {\r\n    haptics::set_poll_haptic_callback(cb);\r\n}\r\n\r\n/// Temporary direct injection helper while event wiring is being built.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_InjectHaptic(\r\n    amplitude: f32,\r\n    duration_seconds: f32,\r\n    frequency: f32,\r\n) {\r\n    haptics::handle_haptic_request(HapticVibrationRequest {\r\n        amplitude,\r\n        duration_seconds,\r\n        frequency,\r\n    });\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":61,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/lib.rs","scheme":"file"},"requestId":"request_a50ae47b-0ab6-4f4c-9668-6b3916eef14c","epoch":1359,"edits":[{"text":"//! Big Haptic Driver crate entrypoints.\r\n//!\r\n//! `lib.rs` stays intentionally thin: it exposes C ABI exports and delegates\r\n//! implementation details to focused modules.\r\n\r\nuse core::ffi::c_void;\r\nuse std::os::raw::c_char;\r\n\r\nmod haptics;\r\nmod openvr_scaffold;\r\n\r\npub use haptics::{HapticVibrationRequest, PollHapticCallback};\r\n\r\n/// OpenVR factory entrypoint expected by SteamVR.\n///\n/// Routes interface-name requests to the internal provider/device singletons\n/// and writes an OpenVR-style init return code to `p_return_code`.\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn HmdDriverFactory(\r\n    p_interface_name: *const c_char,\r\n    p_return_code: *mut i32,\r\n) -> *mut c_void {\r\n    openvr_scaffold::hmd_driver_factory(p_interface_name, p_return_code)\r\n}\r\n\r\n/// Test-only export for early integration while full OpenVR vtable glue is in progress.\r\n/// Returns 0 on success, non-zero on failure.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_SendAmplitude(amplitude: f32) -> i32 {\r\n    match haptics::send_amplitude(amplitude) {\r\n        Ok(()) => 0,\r\n        Err(_) => 1,\r\n    }\r\n}\r\n\r\n/// Explicit stop command helper for testing and recovery.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_Stop() -> i32 {\r\n    match haptics::send_stop() {\r\n        Ok(()) => 0,\r\n        Err(_) => 1,\r\n    }\r\n}\r\n\r\n/// Temporary bridge: host/OpenVR shim can register a callback polled each frame.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_SetPollHapticCallback(cb: Option<PollHapticCallback>) {\r\n    haptics::set_poll_haptic_callback(cb);\r\n}\r\n\r\n/// Temporary direct injection helper while event wiring is being built.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_InjectHaptic(\r\n    amplitude: f32,\r\n    duration_seconds: f32,\r\n    frequency: f32,\r\n) {\r\n    haptics::handle_haptic_request(HapticVibrationRequest {\r\n        amplitude,\r\n        duration_seconds,\r\n        frequency,\r\n    });\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":65,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_a50ae47b-0ab6-4f4c-9668-6b3916eef14c","epoch":1362,"edits":[{"text":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse core::sync::atomic::{AtomicU64, Ordering};\r\nuse std::sync::{Mutex, OnceLock};\r\nuse std::time::Instant;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\n///\n/// Return `true` when a request was written to the output pointer, otherwise\n/// return `false` to indicate no more requests for this frame.\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic STOP_DEADLINE_MS: AtomicU64 = AtomicU64::new(0);\r\nstatic START_INSTANT: OnceLock<Instant> = OnceLock::new();\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\n\r\nfn send_packet(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    hid_bridge::HidBridge::new()\r\n        .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n}\r\n\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\n///\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    send_packet(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn now_ms() -> u64 {\r\n    let start = START_INSTANT.get_or_init(Instant::now);\r\n    let ms = Instant::now().duration_since(*start).as_millis();\r\n    ms.min(u128::from(u64::MAX)) as u64\r\n}\r\n\r\nfn schedule_stop_after(duration_seconds: f32) {\r\n    if duration_seconds <= 0.0 {\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let duration_ms = (duration_seconds * 1000.0).round().max(1.0) as u64;\r\n    let deadline = now_ms().saturating_add(duration_ms);\r\n    STOP_DEADLINE_MS.store(deadline, Ordering::SeqCst);\r\n}\r\n\r\nfn process_pending_stop() {\r\n    let deadline = STOP_DEADLINE_MS.load(Ordering::SeqCst);\r\n    if deadline == 0 {\r\n        return;\r\n    }\r\n\r\n    if now_ms() >= deadline {\r\n        let _ = send_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        let _ = send_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let _ = send_amplitude(req.amplitude);\r\n    schedule_stop_after(req.duration_seconds);\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n    process_pending_stop();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\n///\n/// The callback is invoked from `on_frame()` and may provide multiple requests\n/// per frame.\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":128,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_a50ae47b-0ab6-4f4c-9668-6b3916eef14c","epoch":1364,"edits":[{"text":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse core::sync::atomic::{AtomicU64, Ordering};\r\nuse std::sync::{Mutex, OnceLock};\r\nuse std::time::Instant;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic STOP_DEADLINE_MS: AtomicU64 = AtomicU64::new(0);\r\nstatic START_INSTANT: OnceLock<Instant> = OnceLock::new();\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\n\r\nfn send_packet(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    hid_bridge::HidBridge::new()\r\n        .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    send_packet(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn now_ms() -> u64 {\r\n    let start = START_INSTANT.get_or_init(Instant::now);\r\n    let ms = Instant::now().duration_since(*start).as_millis();\r\n    ms.min(u128::from(u64::MAX)) as u64\r\n}\r\n\r\nfn schedule_stop_after(duration_seconds: f32) {\r\n    if duration_seconds <= 0.0 {\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let duration_ms = (duration_seconds * 1000.0).round().max(1.0) as u64;\r\n    let deadline = now_ms().saturating_add(duration_ms);\r\n    STOP_DEADLINE_MS.store(deadline, Ordering::SeqCst);\r\n}\r\n\r\nfn process_pending_stop() {\r\n    let deadline = STOP_DEADLINE_MS.load(Ordering::SeqCst);\r\n    if deadline == 0 {\r\n        return;\r\n    }\r\n\r\n    if now_ms() >= deadline {\r\n        let _ = send_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\n///\n/// A non-positive amplitude is treated as a stop request.\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        let _ = send_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let _ = send_amplitude(req.amplitude);\r\n    schedule_stop_after(req.duration_seconds);\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\npub fn on_frame() {\r\n    process_haptic_callback();\r\n    process_pending_stop();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":133,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_a50ae47b-0ab6-4f4c-9668-6b3916eef14c","epoch":1367,"edits":[{"text":"//! OpenVR ABI/vtable scaffolding for the Big Haptic Driver.\r\n//!\r\n//! This module owns C ABI exports and interface routing for SteamVR-facing\r\n//! provider/device entry points, while delegating haptics behavior to\r\n//! the `haptics` module.\r\n\r\nuse core::ffi::{c_void, CStr};\r\nuse core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU32, Ordering};\r\nuse std::os::raw::c_char;\r\n\r\nuse crate::haptics;\r\n\r\nconst SERVER_PROVIDER_INTERFACE: &str = \"IServerTrackedDeviceProvider_005\";\r\nconst TRACKED_DEVICE_INTERFACE: &str = \"ITrackedDeviceServerDriver_005\";\r\n\r\n#[repr(i32)]\r\nenum DriverReturnCode {\r\n    None = 0,\r\n    Unknown = 1,\r\n    InitInterfaceNotFound = 105,\r\n}\r\n\r\ntype EvRInitError = i32;\r\nconst VR_INIT_ERROR_NONE: EvRInitError = 0;\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nstruct DriverPose {\r\n    _reserved: [u8; 128],\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerTrackedDeviceProviderVTable {\r\n    init: extern \"C\" fn(*mut ServerTrackedDeviceProvider, *mut c_void) -> EvRInitError,\r\n    cleanup: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    get_interface_versions:\r\n        extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> *const *const c_char,\r\n    run_frame: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    should_block_standby_mode: extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> bool,\r\n    enter_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    leave_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n}\r\n\r\n#[repr(C)]\r\nstruct TrackedDeviceServerDriverVTable {\r\n    activate: extern \"C\" fn(*mut TrackedDeviceServerDriver, u32) -> EvRInitError,\r\n    deactivate: extern \"C\" fn(*mut TrackedDeviceServerDriver),\r\n    enter_standby: extern \"C\" fn(*mut TrackedDeviceServerDriver),\r\n    get_component:\r\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char) -> *mut c_void,\r\n    debug_request:\r\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char, *mut c_char, u32),\r\n    get_pose: extern \"C\" fn(*mut TrackedDeviceServerDriver) -> DriverPose,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerTrackedDeviceProvider {\r\n    vtable: *const ServerTrackedDeviceProviderVTable,\r\n}\r\nunsafe impl Sync for ServerTrackedDeviceProvider {}\r\n\r\n#[repr(C)]\r\nstruct TrackedDeviceServerDriver {\r\n    vtable: *const TrackedDeviceServerDriverVTable,\r\n}\r\nunsafe impl Sync for TrackedDeviceServerDriver {}\r\n\r\n#[repr(C)]\r\nstruct InterfaceVersions([*const c_char; 3]);\r\nunsafe impl Sync for InterfaceVersions {}\r\n\r\nstatic HOST_CONTEXT: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DEVICE_INDEX: AtomicU32 = AtomicU32::new(u32::MAX);\r\nstatic DEVICE_ACTIVE: AtomicBool = AtomicBool::new(false);\r\n\r\nconst PROVIDER_IFACE_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_005\\0\";\r\nconst DEVICE_IFACE_CSTR: &[u8] = b\"ITrackedDeviceServerDriver_005\\0\";\r\n\r\nstatic PROVIDER_INTERFACE_VERSIONS: InterfaceVersions = InterfaceVersions([\r\n    PROVIDER_IFACE_CSTR.as_ptr().cast(),\r\n    DEVICE_IFACE_CSTR.as_ptr().cast(),\r\n    core::ptr::null(),\r\n]);\r\n\r\nextern \"C\" fn provider_init(\r\n    _this: *mut ServerTrackedDeviceProvider,\r\n    driver_context: *mut c_void,\r\n) -> EvRInitError {\r\n    HOST_CONTEXT.store(driver_context, Ordering::SeqCst);\r\n    VR_INIT_ERROR_NONE\r\n}\r\n\r\nextern \"C\" fn provider_cleanup(_this: *mut ServerTrackedDeviceProvider) {\r\n    HOST_CONTEXT.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\r\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn provider_get_interface_versions(\r\n    _this: *mut ServerTrackedDeviceProvider,\r\n) -> *const *const c_char {\r\n    PROVIDER_INTERFACE_VERSIONS.0.as_ptr()\r\n}\r\n\r\nextern \"C\" fn provider_run_frame(_this: *mut ServerTrackedDeviceProvider) {\r\n    haptics::on_frame();\r\n}\r\n\r\nextern \"C\" fn provider_should_block_standby_mode(_this: *mut ServerTrackedDeviceProvider) -> bool {\r\n    false\r\n}\r\n\r\nextern \"C\" fn provider_enter_standby(_this: *mut ServerTrackedDeviceProvider) {}\r\n\r\nextern \"C\" fn provider_leave_standby(_this: *mut ServerTrackedDeviceProvider) {}\r\n\r\nextern \"C\" fn device_activate(_this: *mut TrackedDeviceServerDriver, object_id: u32) -> EvRInitError {\r\n    DEVICE_INDEX.store(object_id, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(true, Ordering::SeqCst);\r\n    VR_INIT_ERROR_NONE\r\n}\r\n\r\nextern \"C\" fn device_deactivate(_this: *mut TrackedDeviceServerDriver) {\r\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\r\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn device_enter_standby(_this: *mut TrackedDeviceServerDriver) {\r\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn device_get_component(\r\n    _this: *mut TrackedDeviceServerDriver,\r\n    _component_name: *const c_char,\r\n) -> *mut c_void {\r\n    core::ptr::null_mut()\r\n}\r\n\r\nextern \"C\" fn device_debug_request(\r\n    _this: *mut TrackedDeviceServerDriver,\r\n    _request: *const c_char,\r\n    response: *mut c_char,\r\n    response_size: u32,\r\n) {\r\n    if response.is_null() || response_size == 0 {\r\n        return;\r\n    }\r\n    unsafe {\r\n        *response = 0;\r\n    }\r\n}\r\n\r\nextern \"C\" fn device_get_pose(_this: *mut TrackedDeviceServerDriver) -> DriverPose {\r\n    DriverPose { _reserved: [0; 128] }\r\n}\r\n\r\nstatic PROVIDER_VTABLE: ServerTrackedDeviceProviderVTable = ServerTrackedDeviceProviderVTable {\r\n    init: provider_init,\r\n    cleanup: provider_cleanup,\r\n    get_interface_versions: provider_get_interface_versions,\r\n    run_frame: provider_run_frame,\r\n    should_block_standby_mode: provider_should_block_standby_mode,\r\n    enter_standby: provider_enter_standby,\r\n    leave_standby: provider_leave_standby,\r\n};\r\n\r\nstatic DEVICE_VTABLE: TrackedDeviceServerDriverVTable = TrackedDeviceServerDriverVTable {\r\n    activate: device_activate,\r\n    deactivate: device_deactivate,\r\n    enter_standby: device_enter_standby,\r\n    get_component: device_get_component,\r\n    debug_request: device_debug_request,\r\n    get_pose: device_get_pose,\r\n};\r\n\r\nstatic PROVIDER: ServerTrackedDeviceProvider = ServerTrackedDeviceProvider {\r\n    vtable: &PROVIDER_VTABLE,\r\n};\r\n\r\nstatic DEVICE: TrackedDeviceServerDriver = TrackedDeviceServerDriver {\r\n    vtable: &DEVICE_VTABLE,\r\n};\r\n\r\nfn set_return_code(out: *mut i32, code: DriverReturnCode) {\r\n    if !out.is_null() {\r\n        unsafe {\r\n            *out = code as i32;\r\n        }\r\n    }\r\n}\r\n\r\nfn read_interface_name(ptr: *const i8) -> Option<&'static str> {\r\n    if ptr.is_null() {\r\n        return None;\r\n    }\r\n\r\n    let cstr = unsafe { CStr::from_ptr(ptr) };\r\n    let Ok(name) = cstr.to_str() else {\r\n        return None;\r\n    };\r\n\r\n    if name == SERVER_PROVIDER_INTERFACE {\r\n        return Some(SERVER_PROVIDER_INTERFACE);\r\n    }\r\n    if name == TRACKED_DEVICE_INTERFACE {\r\n        return Some(TRACKED_DEVICE_INTERFACE);\r\n    }\r\n\r\n    None\r\n}\r\n\r\npub fn hmd_driver_factory(\r\n    p_interface_name: *const c_char,\r\n    p_return_code: *mut i32,\r\n) -> *mut c_void {\r\n    let Some(interface_name) = read_interface_name(p_interface_name) else {\r\n        set_return_code(p_return_code, DriverReturnCode::InitInterfaceNotFound);\r\n        return core::ptr::null_mut();\r\n    };\r\n\r\n    if interface_name == SERVER_PROVIDER_INTERFACE {\r\n        set_return_code(p_return_code, DriverReturnCode::None);\r\n        return (&PROVIDER as *const ServerTrackedDeviceProvider)\r\n            .cast_mut()\r\n            .cast();\r\n    }\r\n\r\n    if interface_name == TRACKED_DEVICE_INTERFACE {\r\n        set_return_code(p_return_code, DriverReturnCode::None);\r\n        return (&DEVICE as *const TrackedDeviceServerDriver)\r\n            .cast_mut()\r\n            .cast();\r\n    }\r\n\r\n    set_return_code(p_return_code, DriverReturnCode::Unknown);\r\n    core::ptr::null_mut()\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":241,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_a50ae47b-0ab6-4f4c-9668-6b3916eef14c","epoch":1369,"edits":[{"text":"//! OpenVR ABI/vtable scaffolding for the Big Haptic Driver.\r\n//!\r\n//! This module owns C ABI exports and interface routing for SteamVR-facing\r\n//! provider/device entry points, while delegating haptics behavior to\r\n//! the `haptics` module.\r\n\r\nuse core::ffi::{c_void, CStr};\r\nuse core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU32, Ordering};\r\nuse std::os::raw::c_char;\r\n\r\nuse crate::haptics;\r\n\r\nconst SERVER_PROVIDER_INTERFACE: &str = \"IServerTrackedDeviceProvider_005\";\r\nconst TRACKED_DEVICE_INTERFACE: &str = \"ITrackedDeviceServerDriver_005\";\r\n\r\n#[repr(i32)]\r\nenum DriverReturnCode {\r\n    None = 0,\r\n    Unknown = 1,\r\n    InitInterfaceNotFound = 105,\r\n}\r\n\r\ntype EvRInitError = i32;\r\nconst VR_INIT_ERROR_NONE: EvRInitError = 0;\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nstruct DriverPose {\r\n    _reserved: [u8; 128],\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerTrackedDeviceProviderVTable {\r\n    init: extern \"C\" fn(*mut ServerTrackedDeviceProvider, *mut c_void) -> EvRInitError,\r\n    cleanup: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    get_interface_versions:\r\n        extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> *const *const c_char,\r\n    run_frame: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    should_block_standby_mode: extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> bool,\r\n    enter_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    leave_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n}\r\n\r\n#[repr(C)]\r\nstruct TrackedDeviceServerDriverVTable {\r\n    activate: extern \"C\" fn(*mut TrackedDeviceServerDriver, u32) -> EvRInitError,\r\n    deactivate: extern \"C\" fn(*mut TrackedDeviceServerDriver),\r\n    enter_standby: extern \"C\" fn(*mut TrackedDeviceServerDriver),\r\n    get_component:\r\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char) -> *mut c_void,\r\n    debug_request:\r\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char, *mut c_char, u32),\r\n    get_pose: extern \"C\" fn(*mut TrackedDeviceServerDriver) -> DriverPose,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerTrackedDeviceProvider {\r\n    vtable: *const ServerTrackedDeviceProviderVTable,\r\n}\r\nunsafe impl Sync for ServerTrackedDeviceProvider {}\r\n\r\n#[repr(C)]\r\nstruct TrackedDeviceServerDriver {\r\n    vtable: *const TrackedDeviceServerDriverVTable,\r\n}\r\nunsafe impl Sync for TrackedDeviceServerDriver {}\r\n\r\n#[repr(C)]\r\nstruct InterfaceVersions([*const c_char; 3]);\r\nunsafe impl Sync for InterfaceVersions {}\r\n\r\nstatic HOST_CONTEXT: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DEVICE_INDEX: AtomicU32 = AtomicU32::new(u32::MAX);\r\nstatic DEVICE_ACTIVE: AtomicBool = AtomicBool::new(false);\r\n\r\nconst PROVIDER_IFACE_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_005\\0\";\r\nconst DEVICE_IFACE_CSTR: &[u8] = b\"ITrackedDeviceServerDriver_005\\0\";\r\n\r\nstatic PROVIDER_INTERFACE_VERSIONS: InterfaceVersions = InterfaceVersions([\r\n    PROVIDER_IFACE_CSTR.as_ptr().cast(),\r\n    DEVICE_IFACE_CSTR.as_ptr().cast(),\r\n    core::ptr::null(),\r\n]);\r\n\r\nextern \"C\" fn provider_init(\r\n    _this: *mut ServerTrackedDeviceProvider,\r\n    driver_context: *mut c_void,\r\n) -> EvRInitError {\r\n    HOST_CONTEXT.store(driver_context, Ordering::SeqCst);\r\n    VR_INIT_ERROR_NONE\r\n}\r\n\r\nextern \"C\" fn provider_cleanup(_this: *mut ServerTrackedDeviceProvider) {\r\n    HOST_CONTEXT.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\r\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn provider_get_interface_versions(\r\n    _this: *mut ServerTrackedDeviceProvider,\r\n) -> *const *const c_char {\r\n    PROVIDER_INTERFACE_VERSIONS.0.as_ptr()\r\n}\r\n\r\nextern \"C\" fn provider_run_frame(_this: *mut ServerTrackedDeviceProvider) {\r\n    haptics::on_frame();\r\n}\r\n\r\nextern \"C\" fn provider_should_block_standby_mode(_this: *mut ServerTrackedDeviceProvider) -> bool {\r\n    false\r\n}\r\n\r\nextern \"C\" fn provider_enter_standby(_this: *mut ServerTrackedDeviceProvider) {}\r\n\r\nextern \"C\" fn provider_leave_standby(_this: *mut ServerTrackedDeviceProvider) {}\r\n\r\nextern \"C\" fn device_activate(_this: *mut TrackedDeviceServerDriver, object_id: u32) -> EvRInitError {\r\n    DEVICE_INDEX.store(object_id, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(true, Ordering::SeqCst);\r\n    VR_INIT_ERROR_NONE\r\n}\r\n\r\nextern \"C\" fn device_deactivate(_this: *mut TrackedDeviceServerDriver) {\r\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\r\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn device_enter_standby(_this: *mut TrackedDeviceServerDriver) {\r\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn device_get_component(\r\n    _this: *mut TrackedDeviceServerDriver,\r\n    _component_name: *const c_char,\r\n) -> *mut c_void {\r\n    core::ptr::null_mut()\r\n}\r\n\r\nextern \"C\" fn device_debug_request(\r\n    _this: *mut TrackedDeviceServerDriver,\r\n    _request: *const c_char,\r\n    response: *mut c_char,\r\n    response_size: u32,\r\n) {\r\n    if response.is_null() || response_size == 0 {\r\n        return;\r\n    }\r\n    unsafe {\r\n        *response = 0;\r\n    }\r\n}\r\n\r\nextern \"C\" fn device_get_pose(_this: *mut TrackedDeviceServerDriver) -> DriverPose {\r\n    DriverPose { _reserved: [0; 128] }\r\n}\r\n\r\nstatic PROVIDER_VTABLE: ServerTrackedDeviceProviderVTable = ServerTrackedDeviceProviderVTable {\r\n    init: provider_init,\r\n    cleanup: provider_cleanup,\r\n    get_interface_versions: provider_get_interface_versions,\r\n    run_frame: provider_run_frame,\r\n    should_block_standby_mode: provider_should_block_standby_mode,\r\n    enter_standby: provider_enter_standby,\r\n    leave_standby: provider_leave_standby,\r\n};\r\n\r\nstatic DEVICE_VTABLE: TrackedDeviceServerDriverVTable = TrackedDeviceServerDriverVTable {\r\n    activate: device_activate,\r\n    deactivate: device_deactivate,\r\n    enter_standby: device_enter_standby,\r\n    get_component: device_get_component,\r\n    debug_request: device_debug_request,\r\n    get_pose: device_get_pose,\r\n};\r\n\r\nstatic PROVIDER: ServerTrackedDeviceProvider = ServerTrackedDeviceProvider {\r\n    vtable: &PROVIDER_VTABLE,\r\n};\r\n\r\nstatic DEVICE: TrackedDeviceServerDriver = TrackedDeviceServerDriver {\r\n    vtable: &DEVICE_VTABLE,\r\n};\r\n\r\nfn set_return_code(out: *mut i32, code: DriverReturnCode) {\r\n    if !out.is_null() {\r\n        unsafe {\r\n            *out = code as i32;\r\n        }\r\n    }\r\n}\r\n\r\nfn read_interface_name(ptr: *const i8) -> Option<&'static str> {\r\n    if ptr.is_null() {\r\n        return None;\r\n    }\r\n\r\n    let cstr = unsafe { CStr::from_ptr(ptr) };\r\n    let Ok(name) = cstr.to_str() else {\r\n        return None;\r\n    };\r\n\r\n    if name == SERVER_PROVIDER_INTERFACE {\r\n        return Some(SERVER_PROVIDER_INTERFACE);\r\n    }\r\n    if name == TRACKED_DEVICE_INTERFACE {\r\n        return Some(TRACKED_DEVICE_INTERFACE);\r\n    }\r\n\r\n    None\r\n}\r\n\r\n/// Resolves OpenVR interface requests to static provider/device instances.\n///\n/// On match, writes `DriverReturnCode::None` to `p_return_code` and returns a\n/// stable interface pointer. On failure, writes an error code and returns null.\npub fn hmd_driver_factory(\r\n    p_interface_name: *const c_char,\r\n    p_return_code: *mut i32,\r\n) -> *mut c_void {\r\n    let Some(interface_name) = read_interface_name(p_interface_name) else {\r\n        set_return_code(p_return_code, DriverReturnCode::InitInterfaceNotFound);\r\n        return core::ptr::null_mut();\r\n    };\r\n\r\n    if interface_name == SERVER_PROVIDER_INTERFACE {\r\n        set_return_code(p_return_code, DriverReturnCode::None);\r\n        return (&PROVIDER as *const ServerTrackedDeviceProvider)\r\n            .cast_mut()\r\n            .cast();\r\n    }\r\n\r\n    if interface_name == TRACKED_DEVICE_INTERFACE {\r\n        set_return_code(p_return_code, DriverReturnCode::None);\r\n        return (&DEVICE as *const TrackedDeviceServerDriver)\r\n            .cast_mut()\r\n            .cast();\r\n    }\r\n\r\n    set_return_code(p_return_code, DriverReturnCode::Unknown);\r\n    core::ptr::null_mut()\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":245,"endColumn":1}}]},{"type":"create","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_deploy_openvr_driver.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","scheme":"file"},"requestId":"request_35127224-e6a7-4c31-8952-d8bd8bf5d5fe","epoch":1372,"initialContent":"","telemetryInfo":{}},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_deploy_openvr_driver.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","scheme":"file"},"requestId":"request_35127224-e6a7-4c31-8952-d8bd8bf5d5fe","epoch":1373,"edits":[{"text":"[CmdletBinding()]","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":1,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_deploy_openvr_driver.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","scheme":"file"},"requestId":"request_35127224-e6a7-4c31-8952-d8bd8bf5d5fe","epoch":1374,"edits":[{"text":"\nparam(","range":{"startLineNumber":2,"startColumn":1,"endLineNumber":2,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_deploy_openvr_driver.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","scheme":"file"},"requestId":"request_35127224-e6a7-4c31-8952-d8bd8bf5d5fe","epoch":1375,"edits":[{"text":"\n    [ValidateSet(\"debug\", \"release\")]","range":{"startLineNumber":3,"startColumn":1,"endLineNumber":3,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_deploy_openvr_driver.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","scheme":"file"},"requestId":"request_35127224-e6a7-4c31-8952-d8bd8bf5d5fe","epoch":1376,"edits":[{"text":"\n    [string]$Profile = \"release\"","range":{"startLineNumber":4,"startColumn":1,"endLineNumber":4,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_deploy_openvr_driver.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","scheme":"file"},"requestId":"request_35127224-e6a7-4c31-8952-d8bd8bf5d5fe","epoch":1377,"edits":[{"text":"\n)","range":{"startLineNumber":5,"startColumn":1,"endLineNumber":5,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_deploy_openvr_driver.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","scheme":"file"},"requestId":"request_35127224-e6a7-4c31-8952-d8bd8bf5d5fe","epoch":1378,"edits":[{"text":"\n","range":{"startLineNumber":6,"startColumn":1,"endLineNumber":6,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_deploy_openvr_driver.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","scheme":"file"},"requestId":"request_35127224-e6a7-4c31-8952-d8bd8bf5d5fe","epoch":1379,"edits":[{"text":"\n$ErrorActionPreference = \"Stop\"","range":{"startLineNumber":7,"startColumn":1,"endLineNumber":7,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_deploy_openvr_driver.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","scheme":"file"},"requestId":"request_35127224-e6a7-4c31-8952-d8bd8bf5d5fe","epoch":1380,"edits":[{"text":"\n","range":{"startLineNumber":8,"startColumn":1,"endLineNumber":8,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_deploy_openvr_driver.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","scheme":"file"},"requestId":"request_35127224-e6a7-4c31-8952-d8bd8bf5d5fe","epoch":1381,"edits":[{"text":"\n$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot \"..\\..\" )).Path","range":{"startLineNumber":9,"startColumn":1,"endLineNumber":9,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_deploy_openvr_driver.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","scheme":"file"},"requestId":"request_35127224-e6a7-4c31-8952-d8bd8bf5d5fe","epoch":1382,"edits":[{"text":"\n$DriverRoot = Join-Path $RepoRoot \"openvr-driver\\rust-haptic-driver\"","range":{"startLineNumber":10,"startColumn":1,"endLineNumber":10,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_deploy_openvr_driver.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","scheme":"file"},"requestId":"request_35127224-e6a7-4c31-8952-d8bd8bf5d5fe","epoch":1383,"edits":[{"text":"\n$DeployDir = Join-Path $DriverRoot \"bin\\win64\"","range":{"startLineNumber":11,"startColumn":1,"endLineNumber":11,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_deploy_openvr_driver.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","scheme":"file"},"requestId":"request_35127224-e6a7-4c31-8952-d8bd8bf5d5fe","epoch":1384,"edits":[{"text":"\n$BuiltDll = Join-Path $RepoRoot \"target\\$Profile\\big_haptic_driver.dll\"","range":{"startLineNumber":12,"startColumn":1,"endLineNumber":12,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_deploy_openvr_driver.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","scheme":"file"},"requestId":"request_35127224-e6a7-4c31-8952-d8bd8bf5d5fe","epoch":1385,"edits":[{"text":"\n$DeployDll = Join-Path $DeployDir \"driver_big_haptic_driver.dll\"","range":{"startLineNumber":13,"startColumn":1,"endLineNumber":13,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_deploy_openvr_driver.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","scheme":"file"},"requestId":"request_35127224-e6a7-4c31-8952-d8bd8bf5d5fe","epoch":1386,"edits":[{"text":"\n","range":{"startLineNumber":14,"startColumn":1,"endLineNumber":14,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_deploy_openvr_driver.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","scheme":"file"},"requestId":"request_35127224-e6a7-4c31-8952-d8bd8bf5d5fe","epoch":1387,"edits":[{"text":"\nif (-not (Test-Path $DeployDir)) {","range":{"startLineNumber":15,"startColumn":1,"endLineNumber":15,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_deploy_openvr_driver.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","scheme":"file"},"requestId":"request_35127224-e6a7-4c31-8952-d8bd8bf5d5fe","epoch":1388,"edits":[{"text":"\n    New-Item -ItemType Directory -Path $DeployDir -Force | Out-Null","range":{"startLineNumber":16,"startColumn":1,"endLineNumber":16,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_deploy_openvr_driver.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","scheme":"file"},"requestId":"request_35127224-e6a7-4c31-8952-d8bd8bf5d5fe","epoch":1389,"edits":[{"text":"\n}","range":{"startLineNumber":17,"startColumn":1,"endLineNumber":17,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_deploy_openvr_driver.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","scheme":"file"},"requestId":"request_35127224-e6a7-4c31-8952-d8bd8bf5d5fe","epoch":1390,"edits":[{"text":"\n","range":{"startLineNumber":18,"startColumn":1,"endLineNumber":18,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_deploy_openvr_driver.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","scheme":"file"},"requestId":"request_35127224-e6a7-4c31-8952-d8bd8bf5d5fe","epoch":1391,"edits":[{"text":"\nPush-Location $RepoRoot","range":{"startLineNumber":19,"startColumn":1,"endLineNumber":19,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_deploy_openvr_driver.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","scheme":"file"},"requestId":"request_35127224-e6a7-4c31-8952-d8bd8bf5d5fe","epoch":1392,"edits":[{"text":"\ntry {","range":{"startLineNumber":20,"startColumn":1,"endLineNumber":20,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_deploy_openvr_driver.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","scheme":"file"},"requestId":"request_35127224-e6a7-4c31-8952-d8bd8bf5d5fe","epoch":1393,"edits":[{"text":"\n    Write-Host \"Building big-haptic-driver ($Profile)...\"","range":{"startLineNumber":21,"startColumn":1,"endLineNumber":21,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_deploy_openvr_driver.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","scheme":"file"},"requestId":"request_35127224-e6a7-4c31-8952-d8bd8bf5d5fe","epoch":1394,"edits":[{"text":"\n","range":{"startLineNumber":22,"startColumn":1,"endLineNumber":22,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_deploy_openvr_driver.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","scheme":"file"},"requestId":"request_35127224-e6a7-4c31-8952-d8bd8bf5d5fe","epoch":1395,"edits":[{"text":"\n    $cargoArgs = @(\"build\", \"-p\", \"big-haptic-driver\")","range":{"startLineNumber":23,"startColumn":1,"endLineNumber":23,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_deploy_openvr_driver.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","scheme":"file"},"requestId":"request_35127224-e6a7-4c31-8952-d8bd8bf5d5fe","epoch":1396,"edits":[{"text":"\n    if ($Profile -eq \"release\") {","range":{"startLineNumber":24,"startColumn":1,"endLineNumber":24,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_deploy_openvr_driver.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","scheme":"file"},"requestId":"request_35127224-e6a7-4c31-8952-d8bd8bf5d5fe","epoch":1397,"edits":[{"text":"\n        $cargoArgs += \"--release\"","range":{"startLineNumber":25,"startColumn":1,"endLineNumber":25,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_deploy_openvr_driver.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","scheme":"file"},"requestId":"request_35127224-e6a7-4c31-8952-d8bd8bf5d5fe","epoch":1398,"edits":[{"text":"\n    }","range":{"startLineNumber":26,"startColumn":1,"endLineNumber":26,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_deploy_openvr_driver.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","scheme":"file"},"requestId":"request_35127224-e6a7-4c31-8952-d8bd8bf5d5fe","epoch":1399,"edits":[{"text":"\n","range":{"startLineNumber":27,"startColumn":1,"endLineNumber":27,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_deploy_openvr_driver.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","scheme":"file"},"requestId":"request_35127224-e6a7-4c31-8952-d8bd8bf5d5fe","epoch":1400,"edits":[{"text":"\n    & cargo @cargoArgs","range":{"startLineNumber":28,"startColumn":1,"endLineNumber":28,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_deploy_openvr_driver.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","scheme":"file"},"requestId":"request_35127224-e6a7-4c31-8952-d8bd8bf5d5fe","epoch":1401,"edits":[{"text":"\n    if ($LASTEXITCODE -ne 0) {","range":{"startLineNumber":29,"startColumn":1,"endLineNumber":29,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_deploy_openvr_driver.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","scheme":"file"},"requestId":"request_35127224-e6a7-4c31-8952-d8bd8bf5d5fe","epoch":1402,"edits":[{"text":"\n        throw \"Cargo build failed with exit code $LASTEXITCODE\"","range":{"startLineNumber":30,"startColumn":1,"endLineNumber":30,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_deploy_openvr_driver.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","scheme":"file"},"requestId":"request_35127224-e6a7-4c31-8952-d8bd8bf5d5fe","epoch":1403,"edits":[{"text":"\n    }","range":{"startLineNumber":31,"startColumn":1,"endLineNumber":31,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_deploy_openvr_driver.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","scheme":"file"},"requestId":"request_35127224-e6a7-4c31-8952-d8bd8bf5d5fe","epoch":1404,"edits":[{"text":"\n","range":{"startLineNumber":32,"startColumn":1,"endLineNumber":32,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_deploy_openvr_driver.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","scheme":"file"},"requestId":"request_35127224-e6a7-4c31-8952-d8bd8bf5d5fe","epoch":1405,"edits":[{"text":"\n    if (-not (Test-Path $BuiltDll)) {","range":{"startLineNumber":33,"startColumn":1,"endLineNumber":33,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_deploy_openvr_driver.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","scheme":"file"},"requestId":"request_35127224-e6a7-4c31-8952-d8bd8bf5d5fe","epoch":1406,"edits":[{"text":"\n        throw \"Built DLL not found: $BuiltDll\"","range":{"startLineNumber":34,"startColumn":1,"endLineNumber":34,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_deploy_openvr_driver.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","scheme":"file"},"requestId":"request_35127224-e6a7-4c31-8952-d8bd8bf5d5fe","epoch":1407,"edits":[{"text":"\n    }","range":{"startLineNumber":35,"startColumn":1,"endLineNumber":35,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_deploy_openvr_driver.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","scheme":"file"},"requestId":"request_35127224-e6a7-4c31-8952-d8bd8bf5d5fe","epoch":1408,"edits":[{"text":"\n","range":{"startLineNumber":36,"startColumn":1,"endLineNumber":36,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_deploy_openvr_driver.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","scheme":"file"},"requestId":"request_35127224-e6a7-4c31-8952-d8bd8bf5d5fe","epoch":1409,"edits":[{"text":"\n    Copy-Item -Path $BuiltDll -Destination $DeployDll -Force","range":{"startLineNumber":37,"startColumn":1,"endLineNumber":37,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_deploy_openvr_driver.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","scheme":"file"},"requestId":"request_35127224-e6a7-4c31-8952-d8bd8bf5d5fe","epoch":1410,"edits":[{"text":"\n    Write-Host \"Deployed: $DeployDll\"","range":{"startLineNumber":38,"startColumn":1,"endLineNumber":38,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_deploy_openvr_driver.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","scheme":"file"},"requestId":"request_35127224-e6a7-4c31-8952-d8bd8bf5d5fe","epoch":1411,"edits":[{"text":"\n}","range":{"startLineNumber":39,"startColumn":1,"endLineNumber":39,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_deploy_openvr_driver.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","scheme":"file"},"requestId":"request_35127224-e6a7-4c31-8952-d8bd8bf5d5fe","epoch":1412,"edits":[{"text":"\nfinally {","range":{"startLineNumber":40,"startColumn":1,"endLineNumber":40,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_deploy_openvr_driver.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","scheme":"file"},"requestId":"request_35127224-e6a7-4c31-8952-d8bd8bf5d5fe","epoch":1413,"edits":[{"text":"\n    Pop-Location","range":{"startLineNumber":41,"startColumn":1,"endLineNumber":41,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_deploy_openvr_driver.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","scheme":"file"},"requestId":"request_35127224-e6a7-4c31-8952-d8bd8bf5d5fe","epoch":1414,"edits":[{"text":"\n}","range":{"startLineNumber":42,"startColumn":1,"endLineNumber":42,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_deploy_openvr_driver.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","scheme":"file"},"requestId":"request_35127224-e6a7-4c31-8952-d8bd8bf5d5fe","epoch":1415,"edits":[{"text":"\n","range":{"startLineNumber":43,"startColumn":1,"endLineNumber":43,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\rust-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/rust-haptic-driver/README.md","scheme":"file"},"requestId":"request_35127224-e6a7-4c31-8952-d8bd8bf5d5fe","epoch":1418,"edits":[{"text":"# big-haptic-driver\r\n\r\nInitial OpenVR driver scaffold in Rust.\r\n\r\n## Current capabilities\r\n\r\n- Exports `HmdDriverFactory` with minimal interface-name validation\r\n- Exposes DLL-callable haptics test exports:\r\n  - `BigHapticDriver_SendAmplitude(float)`\r\n  - `BigHapticDriver_Stop()`\r\n- Sends packets to firmware through shared `hid-bridge`\r\n\r\n## Build\r\n\r\nFrom workspace root:\r\n\r\n- `cargo build -p big-haptic-driver --release`\r\n\r\nOutput DLL path:\r\n\r\n- `target/release/big_haptic_driver.dll`\r\n\n## Build + deploy (one command)\n\nFrom workspace root:\n\n- `./tools/scripts/build_and_deploy_openvr_driver.ps1`\n\nThis builds the crate and copies the DLL to:\n\n- `openvr-driver/rust-haptic-driver/bin/win64/driver_big_haptic_driver.dll`\n\r\n## SteamVR layout\r\n\r\nDriver folder should include:\r\n\r\n- `driver.vrdrivermanifest`\r\n- `resources/`\r\n- `bin/win64/driver_big_haptic_driver.dll`\r\n\r\n## Register with SteamVR\r\n\r\nTypical command:\r\n\r\n- `vrpathreg.exe adddriver <path-to-openvr-driver/rust-haptic-driver>`\r\n\r\n## Next implementation step\r\n\r\nImplement concrete C++ ABI-compatible vtables for:\r\n\r\n- `IServerTrackedDeviceProvider`\r\n- `ITrackedDeviceServerDriver`\r\n\r\nand route `TriggerHapticVibration` events to `BigHapticDriver_SendAmplitude` logic.\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":56,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\Cargo.toml","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","scheme":"file"},"requestId":"request_dc7aedcd-b1dc-443b-8063-d3b5527768d7","epoch":1423,"edits":[{"text":"[workspace]\r\nresolver = \"2\"\r\nmembers = [\r\n  \"shared/haptics-protocol\",\r\n  \"host/hid-bridge\",\r\n  \"host/hid-cli\",\r\n  \"openvr-driver/big-haptic-driver\",\n  \"firmware/qtpy-samd21\",\r\n]\r\ndefault-members = [\r\n  \"shared/haptics-protocol\",\r\n  \"host/hid-bridge\",\r\n  \"host/hid-cli\",\r\n  \"openvr-driver/big-haptic-driver\",\n]\r\n\r\n[workspace.package]\r\nedition = \"2021\"\r\nauthors = [\"qt-py-haptics contributors\"]\r\nlicense = \"MIT OR Apache-2.0\"\r\n\r\n[workspace.dependencies]\r\nanyhow = \"1\"\r\nclap = { version = \"4\", features = [\"derive\"] }\r\nhidapi = \"2\"\r\nlog = \"0.4\"\r\nthiserror = \"2\"\r\n\r\n[profile.release]\r\ncodegen-units = 1\r\nlto = true\r\nopt-level = \"s\"\r\npanic = \"abort\"\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":35,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\.gitignore","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/.gitignore","path":"/C:/Users/decid/Documents/projects/qt-py-haptics/.gitignore","scheme":"file"},"requestId":"request_dc7aedcd-b1dc-443b-8063-d3b5527768d7","epoch":1426,"edits":[{"text":"# Rust / Cargo\r\n/target/\r\n/build/\r\n**/*.rs.bk\r\nCargo.lock\r\nstderr\r\nstdout\r\nopenvr-driver/big-haptic-driver/bin/win64/driver_big_haptic_driver.dll\n\r\n# IDEs\r\n.idea/\r\n.vscode/\r\n*.swp\r\n*.swo\r\n\r\n# OS\r\n.DS_Store\r\nThumbs.db\r\n\r\n# Tools\r\ntools/uf2/uf2conv.py\r\ntools/uf2/uf2families.json\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":24,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\build_and_deploy_openvr_driver.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","scheme":"file"},"requestId":"request_dc7aedcd-b1dc-443b-8063-d3b5527768d7","epoch":1429,"edits":[{"text":"[CmdletBinding()]\r\nparam(\r\n    [ValidateSet(\"debug\", \"release\")]\r\n    [string]$Profile = \"release\"\r\n)\r\n\r\n$ErrorActionPreference = \"Stop\"\r\n\r\n$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot \"..\\..\" )).Path\r\n$DriverRoot = Join-Path $RepoRoot \"openvr-driver\\big-haptic-driver\"\n$DeployDir = Join-Path $DriverRoot \"bin\\win64\"\r\n$BuiltDll = Join-Path $RepoRoot \"target\\$Profile\\big_haptic_driver.dll\"\r\n$DeployDll = Join-Path $DeployDir \"driver_big_haptic_driver.dll\"\r\n\r\nif (-not (Test-Path $DeployDir)) {\r\n    New-Item -ItemType Directory -Path $DeployDir -Force | Out-Null\r\n}\r\n\r\nPush-Location $RepoRoot\r\ntry {\r\n    Write-Host \"Building big-haptic-driver ($Profile)...\"\r\n\r\n    $cargoArgs = @(\"build\", \"-p\", \"big-haptic-driver\")\r\n    if ($Profile -eq \"release\") {\r\n        $cargoArgs += \"--release\"\r\n    }\r\n\r\n    & cargo @cargoArgs\r\n    if ($LASTEXITCODE -ne 0) {\r\n        throw \"Cargo build failed with exit code $LASTEXITCODE\"\r\n    }\r\n\r\n    if (-not (Test-Path $BuiltDll)) {\r\n        throw \"Built DLL not found: $BuiltDll\"\r\n    }\r\n\r\n    Copy-Item -Path $BuiltDll -Destination $DeployDll -Force\r\n    Write-Host \"Deployed: $DeployDll\"\r\n}\r\nfinally {\r\n    Pop-Location\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":44,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/README.md","scheme":"file"},"requestId":"request_dc7aedcd-b1dc-443b-8063-d3b5527768d7","epoch":1432,"edits":[{"text":"# big-haptic-driver\r\n\r\nInitial OpenVR driver scaffold in Rust.\r\n\r\n## Current capabilities\r\n\r\n- Exports `HmdDriverFactory` with minimal interface-name validation\r\n- Exposes DLL-callable haptics test exports:\r\n  - `BigHapticDriver_SendAmplitude(float)`\r\n  - `BigHapticDriver_Stop()`\r\n- Sends packets to firmware through shared `hid-bridge`\r\n\r\n## Build\r\n\r\nFrom workspace root:\r\n\r\n- `cargo build -p big-haptic-driver --release`\r\n\r\nOutput DLL path:\r\n\r\n- `target/release/big_haptic_driver.dll`\r\n\r\n## Build + deploy (one command)\r\n\r\nFrom workspace root:\r\n\r\n- `./tools/scripts/build_and_deploy_openvr_driver.ps1`\r\n\r\nThis builds the crate and copies the DLL to:\n\r\n- `openvr-driver/big-haptic-driver/bin/win64/driver_big_haptic_driver.dll`\n\r\n## SteamVR layout\r\n\r\nDriver folder should include:\r\n\r\n- `driver.vrdrivermanifest`\r\n- `resources/`\r\n- `bin/win64/driver_big_haptic_driver.dll`\r\n\r\n## Register with SteamVR\r\n\r\nTypical command:\r\n\r\n- `vrpathreg.exe adddriver <path-to-openvr-driver/big-haptic-driver>`\n\r\n## Next implementation step\r\n\r\nImplement concrete C++ ABI-compatible vtables for:\r\n\r\n- `IServerTrackedDeviceProvider`\r\n- `ITrackedDeviceServerDriver`\r\n\r\nand route `TriggerHapticVibration` events to `BigHapticDriver_SendAmplitude` logic.\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":56,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\PROJECT_STRUCTURE_OUTLINE.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","scheme":"file"},"requestId":"request_dc7aedcd-b1dc-443b-8063-d3b5527768d7","epoch":1435,"edits":[{"text":"# qt-py-haptics: Directory Structure Outline\r\n\r\nThis repository is organized around four major layers from the implementation plan:\r\n1) Embedded firmware, 2) Host communication, 3) OpenVR driver, 4) Integration/deployment.\r\n\r\n## Proposed Hierarchy\r\n\r\n```text\r\nqt-py-haptics/\r\n├─ .github/\r\n│  └─ workflows/\r\n├─ build/\r\n├─ docs/\r\n│  ├─ Haptic System Implementation Plan.md\r\n│  └─ Research TXT.txt\r\n├─ firmware/\r\n│  └─ qtpy-samd21/\r\n│     ├─ .cargo/\r\n│     ├─ src/\r\n│     ├─ examples/\r\n│     └─ tests/\r\n├─ hardware/\r\n│  ├─ boards/\r\n│  │  └─ qtpy-samd21/\r\n│  ├─ drivers/\r\n│  │  └─ drv2605l/\r\n│  └─ wiring/\r\n├─ host/\r\n│  ├─ hid-cli/\r\n│  │  └─ src/\r\n│  └─ hid-bridge/\r\n│     └─ src/\r\n├─ openvr-driver/\r\n│  └─ big-haptic-driver/\n│     ├─ bin/\r\n│     │  └─ win64/\r\n│     ├─ resources/\r\n│     │  ├─ input/\r\n│     │  └─ localization/\r\n│     └─ src/\r\n├─ shared/\r\n│  └─ haptics-protocol/\r\n│     └─ src/\r\n├─ tests/\r\n│  ├─ integration/\r\n│  └─ latency/\r\n└─ tools/\r\n   ├─ scripts/\r\n   └─ steamvr/\r\n```\r\n\r\n## Folder Purposes\r\n\r\n### .github/workflows\r\nCI pipelines (firmware checks, host/unit tests, formatting/linting, release packaging).\r\n\r\n### build\r\nGenerated artifacts, temporary outputs, and local packaging/staging files.\r\n\r\n### docs\r\nArchitecture docs, protocol notes, calibration procedures, and implementation plans.\r\n\r\n### firmware/qtpy-samd21\r\nRust `no_std` firmware for ATSAMD21 (USB HID + I2C control of DRV2605L).\r\n- `.cargo/`: target config (`thumbv6m-none-eabi`), linker settings.\r\n- `src/`: main firmware modules (USB task, I2C task, command parser).\r\n- `examples/`: bring-up tools (I2C scan, DRV2605L sanity checks).\r\n- `tests/`: embedded-focused test harness patterns and host-driven firmware tests.\r\n\r\n### hardware\r\nHardware-specific assets.\r\n- `boards/qtpy-samd21/`: pin maps, power notes, bootloader/flash instructions.\r\n- `drivers/drv2605l/`: register map references, effect tuning tables.\r\n- `wiring/`: connection diagrams (QT Py ↔ DRV2605L ↔ actuator).\r\n\r\n### host\r\nHost-side utilities and communication libraries.\r\n- `hid-cli/`: command-line utility to send and inspect HID packets.\r\n- `hid-bridge/`: reusable Rust library for HID discovery, packet tx/rx, retries.\r\n\r\n### openvr-driver/big-haptic-driver\nSteamVR/OpenVR driver DLL project in Rust.\r\n- `src/`: provider/device interface implementation and event handling.\r\n- `resources/input/`: input profile JSON for haptic component bindings.\r\n- `resources/localization/`: localized strings used by SteamVR UI.\r\n- `bin/win64/`: built DLL placement and deploy-ready runtime layout.\r\n\r\n### shared/haptics-protocol\r\nSingle source of truth for the HID packet schema, command IDs, and encoding helpers used by firmware + host + driver.\r\n\r\n### tests\r\nCross-layer validation.\r\n- `integration/`: end-to-end tests (OpenVR event → HID → firmware behavior).\r\n- `latency/`: timing and jitter benchmarks for haptic response.\r\n\r\n### tools\r\nOperational scripts and deployment helpers.\r\n- `scripts/`: utility scripts (format, flash, smoke test, packaging).\r\n- `steamvr/`: driver registration templates and helper assets for `vrpathreg`.\r\n\r\n## Notes\r\n\r\n- Keep protocol definitions centralized in `shared/haptics-protocol` to avoid drift.\r\n- Keep deployment-facing assets (`manifest`, input profile, localization) under `openvr-driver/big-haptic-driver/resources`.\n- Place generated binaries and temporary build output in `build/` and avoid committing transient files.\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":107,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/README.md","scheme":"file"},"requestId":"request_72a7d041-925f-4bbf-a717-feff5a61422d","epoch":1439,"edits":[{"text":"# big-haptic-driver\r\n\r\nInitial OpenVR driver scaffold in Rust.\r\n\r\n## Current capabilities\r\n\r\n- Exports `HmdDriverFactory` with minimal interface-name validation\r\n- Exposes DLL-callable haptics test exports:\r\n  - `BigHapticDriver_SendAmplitude(float)`\r\n  - `BigHapticDriver_Stop()`\r\n- Sends packets to firmware through shared `hid-bridge`\r\n\r\n## Build\r\n\r\nFrom workspace root:\r\n\r\n- `cargo build -p big-haptic-driver --release`\r\n\r\nOutput DLL path:\r\n\r\n- `target/release/big_haptic_driver.dll`\r\n\r\n## Build + deploy (one command)\r\n\r\nFrom workspace root:\r\n\r\n- `./tools/scripts/build_and_deploy_openvr_driver.ps1`\r\n\r\nThis builds the crate and copies the DLL to:\r\n\r\n- `openvr-driver/big-haptic-driver/bin/win64/driver_big_haptic_driver.dll`\r\n\r\n## SteamVR layout\r\n\r\nDriver folder should include:\r\n\r\n- `driver.vrdrivermanifest`\r\n- `resources/`\r\n- `resources/input/big_haptic_profile.json`\n- `resources/localization/en_us.json`\n- `bin/win64/driver_big_haptic_driver.dll`\r\n\r\n## Register with SteamVR\r\n\r\nTypical command:\r\n\r\n- `vrpathreg.exe adddriver <path-to-openvr-driver/big-haptic-driver>`\r\n\r\n## Next implementation step\r\n\r\nImplement concrete C++ ABI-compatible vtables for:\r\n\r\n- `IServerTrackedDeviceProvider`\r\n- `ITrackedDeviceServerDriver`\r\n\r\nand route `TriggerHapticVibration` events to `BigHapticDriver_SendAmplitude` logic.\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":58,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/README.md","scheme":"file"},"requestId":"request_7c1fcecb-8a9a-46b2-9072-3ea1069fbf7d","epoch":1443,"edits":[{"text":"# big-haptic-driver\r\n\r\nInitial OpenVR driver scaffold in Rust.\r\n\r\n## Current capabilities\r\n\r\n- Exports `HmdDriverFactory` with minimal interface-name validation\r\n- Exposes DLL-callable haptics test exports:\r\n  - `BigHapticDriver_SendAmplitude(float)`\r\n  - `BigHapticDriver_Stop()`\r\n- Sends packets to firmware through shared `hid-bridge`\r\n\r\n## Build\r\n\r\nFrom workspace root:\r\n\r\n- `cargo build -p big-haptic-driver --release`\r\n\r\nOutput DLL path:\r\n\r\n- `target/release/big_haptic_driver.dll`\r\n\r\n## Build + deploy (one command)\r\n\r\nFrom workspace root:\r\n\r\n- `./tools/scripts/build_and_deploy_openvr_driver.ps1`\r\n\r\nThis builds the crate and copies the DLL to:\r\n\r\n- `openvr-driver/big-haptic-driver/bin/win64/driver_big_haptic_driver.dll`\r\n\r\n## SteamVR layout\r\n\r\nDriver folder should include:\r\n\r\n- `driver.vrdrivermanifest`\r\n- `resources/`\r\n- `resources/input/big_haptic_profile.json`\r\n- `resources/localization/en_us.json`\r\n- `bin/win64/driver_big_haptic_driver.dll`\r\n\r\n## Register with SteamVR\r\n\r\nPowerShell command (note the `&` call operator before the quoted exe path):\n\n- `& \"C:\\Program Files (x86)\\Steam\\steamapps\\common\\SteamVR\\bin\\win64\\vrpathreg.exe\" adddriver \"C:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\"`\n\nTo verify registration:\n\n- `& \"C:\\Program Files (x86)\\Steam\\steamapps\\common\\SteamVR\\bin\\win64\\vrpathreg.exe\"`\n\nTo remove registration:\n\n- `& \"C:\\Program Files (x86)\\Steam\\steamapps\\common\\SteamVR\\bin\\win64\\vrpathreg.exe\" removedriver \"C:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\"`\n\r\n## Next implementation step\r\n\r\nImplement concrete C++ ABI-compatible vtables for:\r\n\r\n- `IServerTrackedDeviceProvider`\r\n- `ITrackedDeviceServerDriver`\r\n\r\nand route `TriggerHapticVibration` events to `BigHapticDriver_SendAmplitude` logic.\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":66,"endColumn":1}}]},{"type":"create","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1445,"initialContent":"","telemetryInfo":{}},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1446,"edits":[{"text":"[CmdletBinding()]","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":1,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1447,"edits":[{"text":"\nparam(","range":{"startLineNumber":2,"startColumn":1,"endLineNumber":2,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1448,"edits":[{"text":"\n    [float]$Amplitude = 0.35,","range":{"startLineNumber":3,"startColumn":1,"endLineNumber":3,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1449,"edits":[{"text":"\n    [float]$DurationSeconds = 0.20,","range":{"startLineNumber":4,"startColumn":1,"endLineNumber":4,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1450,"edits":[{"text":"\n    [float]$Frequency = 160.0,","range":{"startLineNumber":5,"startColumn":1,"endLineNumber":5,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1451,"edits":[{"text":"\n    [switch]$Stop","range":{"startLineNumber":6,"startColumn":1,"endLineNumber":6,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1452,"edits":[{"text":"\n)","range":{"startLineNumber":7,"startColumn":1,"endLineNumber":7,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1453,"edits":[{"text":"\n","range":{"startLineNumber":8,"startColumn":1,"endLineNumber":8,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1454,"edits":[{"text":"\n$ErrorActionPreference = \"Stop\"","range":{"startLineNumber":9,"startColumn":1,"endLineNumber":9,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1455,"edits":[{"text":"\n","range":{"startLineNumber":10,"startColumn":1,"endLineNumber":10,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1456,"edits":[{"text":"\n$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot \"..\\..\" )).Path","range":{"startLineNumber":11,"startColumn":1,"endLineNumber":11,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1457,"edits":[{"text":"\n$DriverBin = Join-Path $RepoRoot \"openvr-driver\\big-haptic-driver\\bin\\win64\"","range":{"startLineNumber":12,"startColumn":1,"endLineNumber":12,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1458,"edits":[{"text":"\n$DriverDll = Join-Path $DriverBin \"driver_big_haptic_driver.dll\"","range":{"startLineNumber":13,"startColumn":1,"endLineNumber":13,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1459,"edits":[{"text":"\n","range":{"startLineNumber":14,"startColumn":1,"endLineNumber":14,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1460,"edits":[{"text":"\nif (-not (Test-Path $DriverDll)) {","range":{"startLineNumber":15,"startColumn":1,"endLineNumber":15,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1461,"edits":[{"text":"\n    throw \"Driver DLL not found: $DriverDll`nRun .\\tools\\scripts\\build_and_deploy_openvr_driver.ps1 first.\"","range":{"startLineNumber":16,"startColumn":1,"endLineNumber":16,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1462,"edits":[{"text":"\n}","range":{"startLineNumber":17,"startColumn":1,"endLineNumber":17,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1463,"edits":[{"text":"\n","range":{"startLineNumber":18,"startColumn":1,"endLineNumber":18,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1464,"edits":[{"text":"\n$originalPath = $env:PATH","range":{"startLineNumber":19,"startColumn":1,"endLineNumber":19,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1465,"edits":[{"text":"\ntry {","range":{"startLineNumber":20,"startColumn":1,"endLineNumber":20,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1466,"edits":[{"text":"\n    $env:PATH = \"$DriverBin;$env:PATH\"","range":{"startLineNumber":21,"startColumn":1,"endLineNumber":21,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1467,"edits":[{"text":"\n    Set-Location $DriverBin","range":{"startLineNumber":22,"startColumn":1,"endLineNumber":22,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1468,"edits":[{"text":"\n","range":{"startLineNumber":23,"startColumn":1,"endLineNumber":23,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1469,"edits":[{"text":"\n    Add-Type -TypeDefinition @\"","range":{"startLineNumber":24,"startColumn":1,"endLineNumber":24,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1470,"edits":[{"text":"\nusing System;","range":{"startLineNumber":25,"startColumn":1,"endLineNumber":25,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1471,"edits":[{"text":"\nusing System.Runtime.InteropServices;","range":{"startLineNumber":26,"startColumn":1,"endLineNumber":26,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1472,"edits":[{"text":"\n","range":{"startLineNumber":27,"startColumn":1,"endLineNumber":27,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1473,"edits":[{"text":"\npublic static class BigHapticNative {","range":{"startLineNumber":28,"startColumn":1,"endLineNumber":28,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1474,"edits":[{"text":"\n    [DllImport(\"driver_big_haptic_driver.dll\", CallingConvention = CallingConvention.Cdecl)]","range":{"startLineNumber":29,"startColumn":1,"endLineNumber":29,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1475,"edits":[{"text":"\n    public static extern int BigHapticDriver_SendAmplitude(float amplitude);","range":{"startLineNumber":30,"startColumn":1,"endLineNumber":30,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1476,"edits":[{"text":"\n","range":{"startLineNumber":31,"startColumn":1,"endLineNumber":31,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1477,"edits":[{"text":"\n    [DllImport(\"driver_big_haptic_driver.dll\", CallingConvention = CallingConvention.Cdecl)]","range":{"startLineNumber":32,"startColumn":1,"endLineNumber":32,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1478,"edits":[{"text":"\n    public static extern int BigHapticDriver_Stop();","range":{"startLineNumber":33,"startColumn":1,"endLineNumber":33,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1479,"edits":[{"text":"\n","range":{"startLineNumber":34,"startColumn":1,"endLineNumber":34,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1480,"edits":[{"text":"\n    [DllImport(\"driver_big_haptic_driver.dll\", CallingConvention = CallingConvention.Cdecl)]","range":{"startLineNumber":35,"startColumn":1,"endLineNumber":35,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1481,"edits":[{"text":"\n    public static extern void BigHapticDriver_InjectHaptic(float amplitude, float duration_seconds, float frequency);","range":{"startLineNumber":36,"startColumn":1,"endLineNumber":36,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1482,"edits":[{"text":"\n}","range":{"startLineNumber":37,"startColumn":1,"endLineNumber":37,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1483,"edits":[{"text":"\n\"@","range":{"startLineNumber":38,"startColumn":1,"endLineNumber":38,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1484,"edits":[{"text":"\n","range":{"startLineNumber":39,"startColumn":1,"endLineNumber":39,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1485,"edits":[{"text":"\n    if ($Stop) {","range":{"startLineNumber":40,"startColumn":1,"endLineNumber":40,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1486,"edits":[{"text":"\n        $rc = [BigHapticNative]::BigHapticDriver_Stop()","range":{"startLineNumber":41,"startColumn":1,"endLineNumber":41,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1487,"edits":[{"text":"\n        Write-Host \"Stop rc: $rc\"","range":{"startLineNumber":42,"startColumn":1,"endLineNumber":42,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1488,"edits":[{"text":"\n        return","range":{"startLineNumber":43,"startColumn":1,"endLineNumber":43,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1489,"edits":[{"text":"\n    }","range":{"startLineNumber":44,"startColumn":1,"endLineNumber":44,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1490,"edits":[{"text":"\n","range":{"startLineNumber":45,"startColumn":1,"endLineNumber":45,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1491,"edits":[{"text":"\n    [BigHapticNative]::BigHapticDriver_InjectHaptic($Amplitude, $DurationSeconds, $Frequency)","range":{"startLineNumber":46,"startColumn":1,"endLineNumber":46,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1492,"edits":[{"text":"\n    Write-Host \"Injected haptic amplitude=$Amplitude duration_s=$DurationSeconds freq_hz=$Frequency\"","range":{"startLineNumber":47,"startColumn":1,"endLineNumber":47,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1493,"edits":[{"text":"\n}","range":{"startLineNumber":48,"startColumn":1,"endLineNumber":48,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1494,"edits":[{"text":"\nfinally {","range":{"startLineNumber":49,"startColumn":1,"endLineNumber":49,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1495,"edits":[{"text":"\n    $env:PATH = $originalPath","range":{"startLineNumber":50,"startColumn":1,"endLineNumber":50,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1496,"edits":[{"text":"\n}","range":{"startLineNumber":51,"startColumn":1,"endLineNumber":51,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1497,"edits":[{"text":"\n","range":{"startLineNumber":52,"startColumn":1,"endLineNumber":52,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/README.md","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1500,"edits":[{"text":"# big-haptic-driver\r\n\r\nInitial OpenVR driver scaffold in Rust.\r\n\r\n## Current capabilities\r\n\r\n- Exports `HmdDriverFactory` with minimal interface-name validation\r\n- Exposes DLL-callable haptics test exports:\r\n  - `BigHapticDriver_SendAmplitude(float)`\r\n  - `BigHapticDriver_Stop()`\r\n- Sends packets to firmware through shared `hid-bridge`\r\n\r\n## Build\r\n\r\nFrom workspace root:\r\n\r\n- `cargo build -p big-haptic-driver --release`\r\n\r\nOutput DLL path:\r\n\r\n- `target/release/big_haptic_driver.dll`\r\n\r\n## Build + deploy (one command)\r\n\r\nFrom workspace root:\r\n\r\n- `./tools/scripts/build_and_deploy_openvr_driver.ps1`\r\n\r\nThis builds the crate and copies the DLL to:\r\n\r\n- `openvr-driver/big-haptic-driver/bin/win64/driver_big_haptic_driver.dll`\r\n\r\n## SteamVR layout\r\n\r\nDriver folder should include:\r\n\r\n- `driver.vrdrivermanifest`\r\n- `resources/`\r\n- `resources/input/big_haptic_profile.json`\r\n- `resources/localization/en_us.json`\r\n- `bin/win64/driver_big_haptic_driver.dll`\r\n\r\n## Register with SteamVR\r\n\r\nPowerShell command (note the `&` call operator before the quoted exe path):\r\n\r\n- `& \"C:\\Program Files (x86)\\Steam\\steamapps\\common\\SteamVR\\bin\\win64\\vrpathreg.exe\" adddriver \"C:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\"`\r\n\r\nTo verify registration:\r\n\r\n- `& \"C:\\Program Files (x86)\\Steam\\steamapps\\common\\SteamVR\\bin\\win64\\vrpathreg.exe\"`\r\n\r\nTo remove registration:\r\n\r\n- `& \"C:\\Program Files (x86)\\Steam\\steamapps\\common\\SteamVR\\bin\\win64\\vrpathreg.exe\" removedriver \"C:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\"`\r\n\r\n## Next implementation step\r\n\r\nImplement concrete C++ ABI-compatible vtables for:\r\n\r\n- `IServerTrackedDeviceProvider`\r\n- `ITrackedDeviceServerDriver`\r\n\r\nand route `TriggerHapticVibration` events to `BigHapticDriver_SendAmplitude` logic.\r\n\n## Testing status (important)\n\nThe driver currently registers with SteamVR, but it does **not** yet create a fully usable tracked controller/input device in SteamVR bindings UI.\n\nSo, end-to-end OpenVR binding-triggered haptics is not available yet.\n\nTemporary direct test (bypasses SteamVR input bindings):\n\n- `./tools/scripts/test_big_haptic_driver_exports.ps1 -Amplitude 0.35 -DurationSeconds 0.20 -Frequency 160`\n\nStop command:\n\n- `./tools/scripts/test_big_haptic_driver_exports.ps1 -Stop`\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":80,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_1ad8789a-3373-4fdf-be29-2d5df5f5cfb8","epoch":1502,"edits":[{"text":"[CmdletBinding()]\r\nparam(\r\n    [float]$Amplitude = 0.35,\r\n    [float]$DurationSeconds = 0.20,\r\n    [float]$Frequency = 160.0,\r\n    [switch]$Stop\r\n)\r\n\r\n$ErrorActionPreference = \"Stop\"\r\n\r\n$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot \"..\\..\" )).Path\r\n$DriverBin = Join-Path $RepoRoot \"openvr-driver\\big-haptic-driver\\bin\\win64\"\r\n$DriverDll = Join-Path $DriverBin \"driver_big_haptic_driver.dll\"\r\n\r\nif (-not (Test-Path $DriverDll)) {\r\n    throw \"Driver DLL not found: $DriverDll`nRun .\\tools\\scripts\\build_and_deploy_openvr_driver.ps1 first.\"\r\n}\r\n\r\n$originalPath = $env:PATH\r\nPush-Location\ntry {\r\n    $env:PATH = \"$DriverBin;$env:PATH\"\r\n    Set-Location $DriverBin\r\n\r\n    Add-Type -TypeDefinition @\"\r\nusing System;\r\nusing System.Runtime.InteropServices;\r\n\r\npublic static class BigHapticNative {\r\n    [DllImport(\"driver_big_haptic_driver.dll\", CallingConvention = CallingConvention.Cdecl)]\r\n    public static extern int BigHapticDriver_SendAmplitude(float amplitude);\r\n\r\n    [DllImport(\"driver_big_haptic_driver.dll\", CallingConvention = CallingConvention.Cdecl)]\r\n    public static extern int BigHapticDriver_Stop();\r\n\r\n    [DllImport(\"driver_big_haptic_driver.dll\", CallingConvention = CallingConvention.Cdecl)]\r\n    public static extern void BigHapticDriver_InjectHaptic(float amplitude, float duration_seconds, float frequency);\r\n}\r\n\"@\r\n\r\n    if ($Stop) {\r\n        $rc = [BigHapticNative]::BigHapticDriver_Stop()\r\n        Write-Host \"Stop rc: $rc\"\r\n        return\r\n    }\r\n\r\n    [BigHapticNative]::BigHapticDriver_InjectHaptic($Amplitude, $DurationSeconds, $Frequency)\r\n    Write-Host \"Injected haptic amplitude=$Amplitude duration_s=$DurationSeconds freq_hz=$Frequency\"\r\n}\r\nfinally {\r\n    $env:PATH = $originalPath\r\n    Pop-Location\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":55,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\test_big_haptic_driver_exports.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","scheme":"file"},"requestId":"request_0837dd8d-03e4-419a-ab9a-44ad661c4a6c","epoch":1506,"edits":[{"text":"[CmdletBinding()]\r\nparam(\r\n    [float]$Amplitude = 0.35,\r\n    [float]$DurationSeconds = 0.20,\r\n    [float]$Frequency = 160.0,\r\n    [switch]$NoAutoStop,\n    [switch]$Stop\r\n)\r\n\r\n$ErrorActionPreference = \"Stop\"\r\n\r\n$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot \"..\\..\" )).Path\r\n$DriverBin = Join-Path $RepoRoot \"openvr-driver\\big-haptic-driver\\bin\\win64\"\r\n$DriverDll = Join-Path $DriverBin \"driver_big_haptic_driver.dll\"\r\n\r\nif (-not (Test-Path $DriverDll)) {\r\n    throw \"Driver DLL not found: $DriverDll`nRun .\\tools\\scripts\\build_and_deploy_openvr_driver.ps1 first.\"\r\n}\r\n\r\n$originalPath = $env:PATH\r\nPush-Location\r\ntry {\r\n    $env:PATH = \"$DriverBin;$env:PATH\"\r\n    Set-Location $DriverBin\r\n\r\n    Add-Type -TypeDefinition @\"\r\nusing System;\r\nusing System.Runtime.InteropServices;\r\n\r\npublic static class BigHapticNative {\r\n    [DllImport(\"driver_big_haptic_driver.dll\", CallingConvention = CallingConvention.Cdecl)]\r\n    public static extern int BigHapticDriver_SendAmplitude(float amplitude);\r\n\r\n    [DllImport(\"driver_big_haptic_driver.dll\", CallingConvention = CallingConvention.Cdecl)]\r\n    public static extern int BigHapticDriver_Stop();\r\n\r\n    [DllImport(\"driver_big_haptic_driver.dll\", CallingConvention = CallingConvention.Cdecl)]\r\n    public static extern void BigHapticDriver_InjectHaptic(float amplitude, float duration_seconds, float frequency);\r\n}\r\n\"@\r\n\r\n    if ($Stop) {\r\n        $rc = [BigHapticNative]::BigHapticDriver_Stop()\r\n        Write-Host \"Stop rc: $rc\"\r\n        return\r\n    }\r\n\r\n    [BigHapticNative]::BigHapticDriver_InjectHaptic($Amplitude, $DurationSeconds, $Frequency)\r\n    Write-Host \"Injected haptic amplitude=$Amplitude duration_s=$DurationSeconds freq_hz=$Frequency\"\r\n\n    if (-not $NoAutoStop -and $DurationSeconds -gt 0) {\n        Start-Sleep -Milliseconds ([Math]::Max(1, [int]([Math]::Round($DurationSeconds * 1000.0))))\n        $stopRc = [BigHapticNative]::BigHapticDriver_Stop()\n        Write-Host \"Auto-stop rc: $stopRc\"\n    }\n}\r\nfinally {\r\n    $env:PATH = $originalPath\r\n    Pop-Location\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":62,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/README.md","scheme":"file"},"requestId":"request_0837dd8d-03e4-419a-ab9a-44ad661c4a6c","epoch":1509,"edits":[{"text":"# big-haptic-driver\r\n\r\nInitial OpenVR driver scaffold in Rust.\r\n\r\n## Current capabilities\r\n\r\n- Exports `HmdDriverFactory` with minimal interface-name validation\r\n- Exposes DLL-callable haptics test exports:\r\n  - `BigHapticDriver_SendAmplitude(float)`\r\n  - `BigHapticDriver_Stop()`\r\n- Sends packets to firmware through shared `hid-bridge`\r\n\r\n## Build\r\n\r\nFrom workspace root:\r\n\r\n- `cargo build -p big-haptic-driver --release`\r\n\r\nOutput DLL path:\r\n\r\n- `target/release/big_haptic_driver.dll`\r\n\r\n## Build + deploy (one command)\r\n\r\nFrom workspace root:\r\n\r\n- `./tools/scripts/build_and_deploy_openvr_driver.ps1`\r\n\r\nThis builds the crate and copies the DLL to:\r\n\r\n- `openvr-driver/big-haptic-driver/bin/win64/driver_big_haptic_driver.dll`\r\n\r\n## SteamVR layout\r\n\r\nDriver folder should include:\r\n\r\n- `driver.vrdrivermanifest`\r\n- `resources/`\r\n- `resources/input/big_haptic_profile.json`\r\n- `resources/localization/en_us.json`\r\n- `bin/win64/driver_big_haptic_driver.dll`\r\n\r\n## Register with SteamVR\r\n\r\nPowerShell command (note the `&` call operator before the quoted exe path):\r\n\r\n- `& \"C:\\Program Files (x86)\\Steam\\steamapps\\common\\SteamVR\\bin\\win64\\vrpathreg.exe\" adddriver \"C:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\"`\r\n\r\nTo verify registration:\r\n\r\n- `& \"C:\\Program Files (x86)\\Steam\\steamapps\\common\\SteamVR\\bin\\win64\\vrpathreg.exe\"`\r\n\r\nTo remove registration:\r\n\r\n- `& \"C:\\Program Files (x86)\\Steam\\steamapps\\common\\SteamVR\\bin\\win64\\vrpathreg.exe\" removedriver \"C:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\"`\r\n\r\n## Next implementation step\r\n\r\nImplement concrete C++ ABI-compatible vtables for:\r\n\r\n- `IServerTrackedDeviceProvider`\r\n- `ITrackedDeviceServerDriver`\r\n\r\nand route `TriggerHapticVibration` events to `BigHapticDriver_SendAmplitude` logic.\r\n\r\n## Testing status (important)\r\n\r\nThe driver currently registers with SteamVR, but it does **not** yet create a fully usable tracked controller/input device in SteamVR bindings UI.\r\n\r\nSo, end-to-end OpenVR binding-triggered haptics is not available yet.\r\n\r\nTemporary direct test (bypasses SteamVR input bindings):\r\n\r\n- `./tools/scripts/test_big_haptic_driver_exports.ps1 -Amplitude 0.35 -DurationSeconds 0.20 -Frequency 160`\r\n\nNote: in this direct-export mode, SteamVR frame pumping is bypassed, so the script performs an explicit delayed stop for `-DurationSeconds` unless `-NoAutoStop` is supplied.\n\r\nStop command:\r\n\r\n- `./tools/scripts/test_big_haptic_driver_exports.ps1 -Stop`\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":82,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_244f6538-b0e5-4f3c-84eb-c094cc01ec2c","epoch":1513,"edits":[{"text":"//! OpenVR ABI/vtable scaffolding for the Big Haptic Driver.\r\n//!\r\n//! This module owns C ABI exports and interface routing for SteamVR-facing\r\n//! provider/device entry points, while delegating haptics behavior to\r\n//! the `haptics` module.\r\n\r\nuse core::ffi::{c_void, CStr};\r\nuse core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU32, AtomicU64, Ordering};\nuse std::os::raw::c_char;\r\n\r\nuse crate::haptics;\r\n\r\nconst SERVER_PROVIDER_INTERFACE: &str = \"IServerTrackedDeviceProvider_004\";\nconst SERVER_PROVIDER_INTERFACE_ALT: &str = \"IServerTrackedDeviceProvider_005\";\nconst TRACKED_DEVICE_INTERFACE: &str = \"ITrackedDeviceServerDriver_005\";\r\n\nconst IVR_SERVER_DRIVER_HOST_VERSION: &[u8] = b\"IVRServerDriverHost_006\\0\";\nconst IVR_DRIVER_INPUT_VERSION: &[u8] = b\"IVRDriverInput_004\\0\";\nconst IVR_PROPERTIES_VERSION: &[u8] = b\"IVRProperties_001\\0\";\n\nconst TRACKED_DEVICE_CLASS_CONTROLLER: i32 = 2;\nconst VREVENT_INPUT_HAPTIC_VIBRATION: u32 = 1700;\nconst VR_INIT_ERROR_DRIVER_FAILED: EvRInitError = 200;\n\nconst PROP_MODEL_NUMBER_STRING: u32 = 1001;\nconst PROP_SERIAL_NUMBER_STRING: u32 = 1002;\nconst PROP_INPUT_PROFILE_PATH_STRING: u32 = 1037;\nconst PROP_CONTROLLER_TYPE_STRING: u32 = 7000;\n\nconst K_UN_STRING_PROPERTY_TAG: u32 = 5;\nconst PROPERTY_WRITE_SET: i32 = 0;\n\nconst DEVICE_SERIAL_CSTR: &[u8] = b\"qtpy-samd21-haptic-001\\0\";\nconst DEVICE_MODEL_CSTR: &[u8] = b\"QT Py SAMD21 Haptic\\0\";\nconst DEVICE_CONTROLLER_TYPE_CSTR: &[u8] = b\"big_haptic_driver\\0\";\nconst DEVICE_INPUT_PROFILE_CSTR: &[u8] = b\"{big_haptic_driver}/input/big_haptic_profile.json\\0\";\nconst DEVICE_HAPTIC_PATH_CSTR: &[u8] = b\"/output/haptic\\0\";\n\r\n#[repr(i32)]\r\nenum DriverReturnCode {\r\n    None = 0,\r\n    Unknown = 1,\r\n    InitInterfaceNotFound = 105,\r\n}\r\n\r\ntype EvRInitError = i32;\r\nconst VR_INIT_ERROR_NONE: EvRInitError = 0;\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nstruct DriverPose {\r\n    _reserved: [u8; 128],\r\n}\r\n\n#[repr(C)]\n#[derive(Clone, Copy, Default)]\nstruct VREventHapticVibration {\n    container_handle: u64,\n    component_handle: u64,\n    duration_seconds: f32,\n    frequency: f32,\n    amplitude: f32,\n}\n\n#[repr(C)]\n#[derive(Clone, Copy)]\nunion VREventData {\n    haptic_vibration: VREventHapticVibration,\n    reserved: [u8; 48],\n}\n\nimpl Default for VREventData {\n    fn default() -> Self {\n        Self { reserved: [0; 48] }\n    }\n}\n\n#[repr(C)]\n#[derive(Clone, Copy, Default)]\nstruct VREvent {\n    event_type: u32,\n    tracked_device_index: u32,\n    event_age_seconds: f32,\n    data: VREventData,\n}\n\n#[repr(C)]\nstruct DriverContextVTable {\n    get_generic_interface:\n        extern \"C\" fn(*mut DriverContext, *const c_char, *mut EvRInitError) -> *mut c_void,\n    get_driver_handle: extern \"C\" fn(*mut DriverContext) -> u64,\n}\n\n#[repr(C)]\nstruct DriverContext {\n    vtable: *const DriverContextVTable,\n}\n\n#[repr(C)]\nstruct ServerDriverHostVTable {\n    tracked_device_added:\n        extern \"C\" fn(*mut ServerDriverHost, *const c_char, i32, *mut TrackedDeviceServerDriver) -> bool,\n    tracked_device_pose_updated: extern \"C\" fn(*mut ServerDriverHost, u32, *const DriverPose, u32),\n    vsync_event: extern \"C\" fn(*mut ServerDriverHost, f64),\n    vendor_specific_event: extern \"C\" fn(*mut ServerDriverHost, u32, u32, *const VREventData, f64),\n    is_exiting: extern \"C\" fn(*mut ServerDriverHost) -> bool,\n    poll_next_event: extern \"C\" fn(*mut ServerDriverHost, *mut VREvent, u32) -> bool,\n}\n\n#[repr(C)]\nstruct ServerDriverHost {\n    vtable: *const ServerDriverHostVTable,\n}\n\n#[repr(C)]\nstruct DriverInputVTable {\n    create_boolean_component:\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\n    update_boolean_component: extern \"C\" fn(*mut DriverInput, u64, bool, f64) -> i32,\n    create_scalar_component:\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64, i32, i32) -> i32,\n    update_scalar_component: extern \"C\" fn(*mut DriverInput, u64, f32, f64) -> i32,\n    create_haptic_component:\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\n}\n\n#[repr(C)]\nstruct DriverInput {\n    vtable: *const DriverInputVTable,\n}\n\n#[repr(C)]\nstruct PropertiesVTable {\n    read_property_batch: extern \"C\" fn(*mut Properties, u64, *mut c_void, u32) -> i32,\n    write_property_batch: extern \"C\" fn(*mut Properties, u64, *mut PropertyWrite, u32) -> i32,\n    get_prop_error_name_from_enum: extern \"C\" fn(*mut Properties, i32) -> *const c_char,\n    tracked_device_to_property_container: extern \"C\" fn(*mut Properties, u32) -> u64,\n}\n\n#[repr(C)]\nstruct Properties {\n    vtable: *const PropertiesVTable,\n}\n\n#[repr(C)]\nstruct PropertyWrite {\n    prop: u32,\n    write_type: i32,\n    set_error: i32,\n    pv_buffer: *mut c_void,\n    un_buffer_size: u32,\n    un_tag: u32,\n    e_error: i32,\n}\n\r\n#[repr(C)]\r\nstruct ServerTrackedDeviceProviderVTable {\r\n    init: extern \"C\" fn(*mut ServerTrackedDeviceProvider, *mut c_void) -> EvRInitError,\r\n    cleanup: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    get_interface_versions:\r\n        extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> *const *const c_char,\r\n    run_frame: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    should_block_standby_mode: extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> bool,\r\n    enter_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    leave_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n}\r\n\r\n#[repr(C)]\r\nstruct TrackedDeviceServerDriverVTable {\r\n    activate: extern \"C\" fn(*mut TrackedDeviceServerDriver, u32) -> EvRInitError,\r\n    deactivate: extern \"C\" fn(*mut TrackedDeviceServerDriver),\r\n    enter_standby: extern \"C\" fn(*mut TrackedDeviceServerDriver),\r\n    get_component:\r\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char) -> *mut c_void,\r\n    debug_request:\r\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char, *mut c_char, u32),\r\n    get_pose: extern \"C\" fn(*mut TrackedDeviceServerDriver) -> DriverPose,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerTrackedDeviceProvider {\r\n    vtable: *const ServerTrackedDeviceProviderVTable,\r\n}\r\nunsafe impl Sync for ServerTrackedDeviceProvider {}\r\n\r\n#[repr(C)]\r\nstruct TrackedDeviceServerDriver {\r\n    vtable: *const TrackedDeviceServerDriverVTable,\r\n}\r\nunsafe impl Sync for TrackedDeviceServerDriver {}\r\n\r\n#[repr(C)]\r\nstruct InterfaceVersions([*const c_char; 3]);\r\nunsafe impl Sync for InterfaceVersions {}\r\n\r\nstatic HOST_CONTEXT: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DEVICE_INDEX: AtomicU32 = AtomicU32::new(u32::MAX);\r\nstatic DEVICE_ACTIVE: AtomicBool = AtomicBool::new(false);\r\nstatic DEVICE_REGISTERED: AtomicBool = AtomicBool::new(false);\n\nstatic SERVER_HOST_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\nstatic DRIVER_INPUT_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\nstatic PROPERTIES_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\n\nstatic DEVICE_CONTAINER: AtomicU64 = AtomicU64::new(0);\nstatic HAPTIC_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\n\r\nconst PROVIDER_IFACE_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_004\\0\";\nconst PROVIDER_IFACE_ALT_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_005\\0\";\nconst DEVICE_IFACE_CSTR: &[u8] = b\"ITrackedDeviceServerDriver_005\\0\";\r\n\nconst IVR_PROPERTIES_CSTR: &[u8] = b\"IVRProperties_001\\0\";\nconst IVR_DRIVER_INPUT_CSTR: &[u8] = b\"IVRDriverInput_004\\0\";\nconst IVR_SERVER_DRIVER_HOST_CSTR: &[u8] = b\"IVRServerDriverHost_006\\0\";\n\nconst IVR_SETTINGS_CSTR: &[u8] = b\"IVRSettings_003\\0\";\nconst IVR_DRIVER_MANAGER_CSTR: &[u8] = b\"IVRDriverManager_001\\0\";\nconst IVR_RESOURCES_CSTR: &[u8] = b\"IVRResources_001\\0\";\n\r\nstatic INTERFACE_VERSIONS: InterfaceVersions = InterfaceVersions([\n    IVR_SETTINGS_CSTR.as_ptr().cast(),\n    DEVICE_IFACE_CSTR.as_ptr().cast(),\n    PROVIDER_IFACE_CSTR.as_ptr().cast(),\n    IVR_PROPERTIES_CSTR.as_ptr().cast(),\n    IVR_SERVER_DRIVER_HOST_CSTR.as_ptr().cast(),\n    IVR_DRIVER_MANAGER_CSTR.as_ptr().cast(),\n    IVR_RESOURCES_CSTR.as_ptr().cast(),\n    IVR_DRIVER_INPUT_CSTR.as_ptr().cast(),\n    PROVIDER_IFACE_ALT_CSTR.as_ptr().cast(),\n    core::ptr::null(),\n]);\n\nfn get_server_host() -> Option<*mut ServerDriverHost> {\n    let ptr = SERVER_HOST_IFACE.load(Ordering::SeqCst);\n    if ptr.is_null() {\n        None\n    } else {\n        Some(ptr.cast())\n    }\n}\n\nfn get_driver_input() -> Option<*mut DriverInput> {\n    let ptr = DRIVER_INPUT_IFACE.load(Ordering::SeqCst);\n    if ptr.is_null() {\n        None\n    } else {\n        Some(ptr.cast())\n    }\n}\n\nfn get_properties() -> Option<*mut Properties> {\n    let ptr = PROPERTIES_IFACE.load(Ordering::SeqCst);\n    if ptr.is_null() {\n        None\n    } else {\n        Some(ptr.cast())\n    }\n}\n\nfn write_string_property(props: *mut Properties, container: u64, prop: u32, value_nul: &'static [u8]) {\n    if props.is_null() || value_nul.is_empty() {\n        return;\n    }\n\n    let mut write = PropertyWrite {\n        prop,\n        write_type: PROPERTY_WRITE_SET,\n        set_error: 0,\n        pv_buffer: value_nul.as_ptr().cast_mut().cast(),\n        un_buffer_size: value_nul.len() as u32,\n        un_tag: K_UN_STRING_PROPERTY_TAG,\n        e_error: 0,\n    };\n\n    unsafe {\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\n    }\n}\n\nfn register_tracked_device_if_needed() {\n    if DEVICE_REGISTERED.load(Ordering::SeqCst) {\n        return;\n    }\n\n    let Some(host) = get_server_host() else {\n        return;\n    };\n\n    let added = unsafe {\n        ((*(*host).vtable).tracked_device_added)(\n            host,\n            DEVICE_SERIAL_CSTR.as_ptr().cast(),\n            TRACKED_DEVICE_CLASS_CONTROLLER,\n            (&DEVICE as *const TrackedDeviceServerDriver).cast_mut(),\n        )\n    };\n\n    if added {\n        DEVICE_REGISTERED.store(true, Ordering::SeqCst);\n    }\n}\n\nfn resolve_driver_interfaces(driver_context: *mut c_void) -> bool {\n    if driver_context.is_null() {\n        return false;\n    }\n\n    let ctx = driver_context.cast::<DriverContext>();\n    let mut err = VR_INIT_ERROR_NONE;\n\n    let get_iface = unsafe { (*(*ctx).vtable).get_generic_interface };\n\n    let host = get_iface(\n        ctx,\n        IVR_SERVER_DRIVER_HOST_VERSION.as_ptr().cast(),\n        &mut err as *mut EvRInitError,\n    );\n\n    let driver_input = get_iface(\n        ctx,\n        IVR_DRIVER_INPUT_VERSION.as_ptr().cast(),\n        &mut err as *mut EvRInitError,\n    );\n\n    let properties = get_iface(\n        ctx,\n        IVR_PROPERTIES_VERSION.as_ptr().cast(),\n        &mut err as *mut EvRInitError,\n    );\n\n    SERVER_HOST_IFACE.store(host, Ordering::SeqCst);\n    DRIVER_INPUT_IFACE.store(driver_input, Ordering::SeqCst);\n    PROPERTIES_IFACE.store(properties, Ordering::SeqCst);\n\n    !host.is_null() && !driver_input.is_null() && !properties.is_null()\n}\n\nfn route_haptic_events() {\n    let Some(host) = get_server_host() else {\n        return;\n    };\n\n    loop {\n        let mut event = VREvent::default();\n        let ok = unsafe {\n            ((*(*host).vtable).poll_next_event)(\n                host,\n                &mut event as *mut VREvent,\n                core::mem::size_of::<VREvent>() as u32,\n            )\n        };\n\n        if !ok {\n            break;\n        }\n\n        if event.event_type != VREVENT_INPUT_HAPTIC_VIBRATION {\n            continue;\n        }\n\n        let hv = unsafe { event.data.haptic_vibration };\n\n        let container = DEVICE_CONTAINER.load(Ordering::SeqCst);\n        if container != 0 && hv.container_handle != 0 && hv.container_handle != container {\n            continue;\n        }\n\n        let haptic_component = HAPTIC_COMPONENT_HANDLE.load(Ordering::SeqCst);\n        if haptic_component != 0 && hv.component_handle != 0 && hv.component_handle != haptic_component {\n            continue;\n        }\n\n        haptics::handle_haptic_request(haptics::HapticVibrationRequest {\n            amplitude: hv.amplitude,\n            duration_seconds: hv.duration_seconds,\n            frequency: hv.frequency,\n        });\n    }\n}\n    PROVIDER_IFACE_CSTR.as_ptr().cast(),\r\n    DEVICE_IFACE_CSTR.as_ptr().cast(),\r\n    core::ptr::null(),\r\n]);\r\n\r\nextern \"C\" fn provider_init(\r\n    _this: *mut ServerTrackedDeviceProvider,\r\n    driver_context: *mut c_void,\r\n) -> EvRInitError {\r\n    HOST_CONTEXT.store(driver_context, Ordering::SeqCst);\r\n\n    if !resolve_driver_interfaces(driver_context) {\n        return VR_INIT_ERROR_DRIVER_FAILED;\n    }\n\n    register_tracked_device_if_needed();\n\n    VR_INIT_ERROR_NONE\r\n}\r\n\r\nextern \"C\" fn provider_cleanup(_this: *mut ServerTrackedDeviceProvider) {\r\n    HOST_CONTEXT.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\r\n    DEVICE_REGISTERED.store(false, Ordering::SeqCst);\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\n    SERVER_HOST_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\n    DRIVER_INPUT_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\n    PROPERTIES_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn provider_get_interface_versions(\r\n    _this: *mut ServerTrackedDeviceProvider,\r\n) -> *const *const c_char {\r\n    INTERFACE_VERSIONS.0.as_ptr()\n}\r\n\r\nextern \"C\" fn provider_run_frame(_this: *mut ServerTrackedDeviceProvider) {\r\n    route_haptic_events();\n    haptics::on_frame();\r\n}\r\n\r\nextern \"C\" fn provider_should_block_standby_mode(_this: *mut ServerTrackedDeviceProvider) -> bool {\r\n    false\r\n}\r\n\r\nextern \"C\" fn provider_enter_standby(_this: *mut ServerTrackedDeviceProvider) {}\r\n\r\nextern \"C\" fn provider_leave_standby(_this: *mut ServerTrackedDeviceProvider) {}\r\n\r\nextern \"C\" fn device_activate(_this: *mut TrackedDeviceServerDriver, object_id: u32) -> EvRInitError {\r\n    DEVICE_INDEX.store(object_id, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(true, Ordering::SeqCst);\r\n\n    let Some(props) = get_properties() else {\n        return VR_INIT_ERROR_DRIVER_FAILED;\n    };\n\n    let container = unsafe { ((*(*props).vtable).tracked_device_to_property_container)(props, object_id) };\n    DEVICE_CONTAINER.store(container, Ordering::SeqCst);\n\n    write_string_property(props, container, PROP_MODEL_NUMBER_STRING, DEVICE_MODEL_CSTR);\n    write_string_property(props, container, PROP_SERIAL_NUMBER_STRING, DEVICE_SERIAL_CSTR);\n    write_string_property(\n        props,\n        container,\n        PROP_INPUT_PROFILE_PATH_STRING,\n        DEVICE_INPUT_PROFILE_CSTR,\n    );\n    write_string_property(\n        props,\n        container,\n        PROP_CONTROLLER_TYPE_STRING,\n        DEVICE_CONTROLLER_TYPE_CSTR,\n    );\n\n    if let Some(input) = get_driver_input() {\n        let mut handle = 0_u64;\n        let rc = unsafe {\n            ((*(*input).vtable).create_haptic_component)(\n                input,\n                container,\n                DEVICE_HAPTIC_PATH_CSTR.as_ptr().cast(),\n                &mut handle as *mut u64,\n            )\n        };\n\n        if rc == 0 {\n            HAPTIC_COMPONENT_HANDLE.store(handle, Ordering::SeqCst);\n        }\n    }\n\n    VR_INIT_ERROR_NONE\r\n}\r\n\r\nextern \"C\" fn device_deactivate(_this: *mut TrackedDeviceServerDriver) {\r\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn device_enter_standby(_this: *mut TrackedDeviceServerDriver) {\r\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn device_get_component(\r\n    _this: *mut TrackedDeviceServerDriver,\r\n    _component_name: *const c_char,\r\n) -> *mut c_void {\r\n    core::ptr::null_mut()\r\n}\r\n\r\nextern \"C\" fn device_debug_request(\r\n    _this: *mut TrackedDeviceServerDriver,\r\n    _request: *const c_char,\r\n    response: *mut c_char,\r\n    response_size: u32,\r\n) {\r\n    if response.is_null() || response_size == 0 {\r\n        return;\r\n    }\r\n    unsafe {\r\n        *response = 0;\r\n    }\r\n}\r\n\r\nextern \"C\" fn device_get_pose(_this: *mut TrackedDeviceServerDriver) -> DriverPose {\r\n    DriverPose { _reserved: [0; 128] }\r\n}\r\n\r\nstatic PROVIDER_VTABLE: ServerTrackedDeviceProviderVTable = ServerTrackedDeviceProviderVTable {\r\n    init: provider_init,\r\n    cleanup: provider_cleanup,\r\n    get_interface_versions: provider_get_interface_versions,\r\n    run_frame: provider_run_frame,\r\n    should_block_standby_mode: provider_should_block_standby_mode,\r\n    enter_standby: provider_enter_standby,\r\n    leave_standby: provider_leave_standby,\r\n};\r\n\r\nstatic DEVICE_VTABLE: TrackedDeviceServerDriverVTable = TrackedDeviceServerDriverVTable {\r\n    activate: device_activate,\r\n    deactivate: device_deactivate,\r\n    enter_standby: device_enter_standby,\r\n    get_component: device_get_component,\r\n    debug_request: device_debug_request,\r\n    get_pose: device_get_pose,\r\n};\r\n\r\nstatic PROVIDER: ServerTrackedDeviceProvider = ServerTrackedDeviceProvider {\r\n    vtable: &PROVIDER_VTABLE,\r\n};\r\n\r\nstatic DEVICE: TrackedDeviceServerDriver = TrackedDeviceServerDriver {\r\n    vtable: &DEVICE_VTABLE,\r\n};\r\n\r\nfn set_return_code(out: *mut i32, code: DriverReturnCode) {\r\n    if !out.is_null() {\r\n        unsafe {\r\n            *out = code as i32;\r\n        }\r\n    }\r\n}\r\n\r\nfn read_interface_name(ptr: *const i8) -> Option<&'static str> {\r\n    if ptr.is_null() {\r\n        return None;\r\n    }\r\n\r\n    let cstr = unsafe { CStr::from_ptr(ptr) };\r\n    let Ok(name) = cstr.to_str() else {\r\n        return None;\r\n    };\r\n\r\n    if name == SERVER_PROVIDER_INTERFACE {\r\n        return Some(SERVER_PROVIDER_INTERFACE);\r\n    }\r\n    if name == SERVER_PROVIDER_INTERFACE_ALT {\n        return Some(SERVER_PROVIDER_INTERFACE);\n    }\n    if name == TRACKED_DEVICE_INTERFACE {\r\n        return Some(TRACKED_DEVICE_INTERFACE);\r\n    }\r\n\r\n    None\r\n}\r\n\r\n/// Resolves OpenVR interface requests to static provider/device instances.\r\n///\r\n/// On match, writes `DriverReturnCode::None` to `p_return_code` and returns a\r\n/// stable interface pointer. On failure, writes an error code and returns null.\r\npub fn hmd_driver_factory(\r\n    p_interface_name: *const c_char,\r\n    p_return_code: *mut i32,\r\n) -> *mut c_void {\r\n    let Some(interface_name) = read_interface_name(p_interface_name) else {\r\n        set_return_code(p_return_code, DriverReturnCode::InitInterfaceNotFound);\r\n        return core::ptr::null_mut();\r\n    };\r\n\r\n    if interface_name == SERVER_PROVIDER_INTERFACE {\r\n        set_return_code(p_return_code, DriverReturnCode::None);\r\n        return (&PROVIDER as *const ServerTrackedDeviceProvider)\r\n            .cast_mut()\r\n            .cast();\r\n    }\r\n\r\n    if interface_name == TRACKED_DEVICE_INTERFACE {\r\n        set_return_code(p_return_code, DriverReturnCode::None);\r\n        return (&DEVICE as *const TrackedDeviceServerDriver)\r\n            .cast_mut()\r\n            .cast();\r\n    }\r\n\r\n    set_return_code(p_return_code, DriverReturnCode::Unknown);\r\n    core::ptr::null_mut()\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":603,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_244f6538-b0e5-4f3c-84eb-c094cc01ec2c","epoch":1515,"edits":[{"text":"//! OpenVR ABI/vtable scaffolding for the Big Haptic Driver.\r\n//!\r\n//! This module owns C ABI exports and interface routing for SteamVR-facing\r\n//! provider/device entry points, while delegating haptics behavior to\r\n//! the `haptics` module.\r\n\r\nuse core::ffi::{c_void, CStr};\r\nuse core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU32, AtomicU64, Ordering};\r\nuse std::os::raw::c_char;\r\n\r\nuse crate::haptics;\r\n\r\nconst SERVER_PROVIDER_INTERFACE: &str = \"IServerTrackedDeviceProvider_004\";\r\nconst SERVER_PROVIDER_INTERFACE_ALT: &str = \"IServerTrackedDeviceProvider_005\";\r\nconst TRACKED_DEVICE_INTERFACE: &str = \"ITrackedDeviceServerDriver_005\";\r\n\r\nconst IVR_SERVER_DRIVER_HOST_VERSION: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\nconst IVR_DRIVER_INPUT_VERSION: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_PROPERTIES_VERSION: &[u8] = b\"IVRProperties_001\\0\";\r\n\r\nconst TRACKED_DEVICE_CLASS_CONTROLLER: i32 = 2;\r\nconst VREVENT_INPUT_HAPTIC_VIBRATION: u32 = 1700;\r\nconst VR_INIT_ERROR_DRIVER_FAILED: EvRInitError = 200;\r\n\r\nconst PROP_MODEL_NUMBER_STRING: u32 = 1001;\r\nconst PROP_SERIAL_NUMBER_STRING: u32 = 1002;\r\nconst PROP_INPUT_PROFILE_PATH_STRING: u32 = 1037;\r\nconst PROP_CONTROLLER_TYPE_STRING: u32 = 7000;\r\n\r\nconst K_UN_STRING_PROPERTY_TAG: u32 = 5;\r\nconst PROPERTY_WRITE_SET: i32 = 0;\r\n\r\nconst DEVICE_SERIAL_CSTR: &[u8] = b\"qtpy-samd21-haptic-001\\0\";\r\nconst DEVICE_MODEL_CSTR: &[u8] = b\"QT Py SAMD21 Haptic\\0\";\r\nconst DEVICE_CONTROLLER_TYPE_CSTR: &[u8] = b\"big_haptic_driver\\0\";\r\nconst DEVICE_INPUT_PROFILE_CSTR: &[u8] = b\"{big_haptic_driver}/input/big_haptic_profile.json\\0\";\r\nconst DEVICE_HAPTIC_PATH_CSTR: &[u8] = b\"/output/haptic\\0\";\r\n\r\n#[repr(i32)]\r\nenum DriverReturnCode {\r\n    None = 0,\r\n    Unknown = 1,\r\n    InitInterfaceNotFound = 105,\r\n}\r\n\r\ntype EvRInitError = i32;\r\nconst VR_INIT_ERROR_NONE: EvRInitError = 0;\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nstruct DriverPose {\r\n    _reserved: [u8; 128],\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREventHapticVibration {\r\n    container_handle: u64,\r\n    component_handle: u64,\r\n    duration_seconds: f32,\r\n    frequency: f32,\r\n    amplitude: f32,\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nunion VREventData {\r\n    haptic_vibration: VREventHapticVibration,\r\n    reserved: [u8; 48],\r\n}\r\n\r\nimpl Default for VREventData {\r\n    fn default() -> Self {\r\n        Self { reserved: [0; 48] }\r\n    }\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREvent {\r\n    event_type: u32,\r\n    tracked_device_index: u32,\r\n    event_age_seconds: f32,\r\n    data: VREventData,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContextVTable {\r\n    get_generic_interface:\r\n        extern \"C\" fn(*mut DriverContext, *const c_char, *mut EvRInitError) -> *mut c_void,\r\n    get_driver_handle: extern \"C\" fn(*mut DriverContext) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContext {\r\n    vtable: *const DriverContextVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHostVTable {\r\n    tracked_device_added:\r\n        extern \"C\" fn(*mut ServerDriverHost, *const c_char, i32, *mut TrackedDeviceServerDriver) -> bool,\r\n    tracked_device_pose_updated: extern \"C\" fn(*mut ServerDriverHost, u32, *const DriverPose, u32),\r\n    vsync_event: extern \"C\" fn(*mut ServerDriverHost, f64),\r\n    vendor_specific_event: extern \"C\" fn(*mut ServerDriverHost, u32, u32, *const VREventData, f64),\r\n    is_exiting: extern \"C\" fn(*mut ServerDriverHost) -> bool,\r\n    poll_next_event: extern \"C\" fn(*mut ServerDriverHost, *mut VREvent, u32) -> bool,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHost {\r\n    vtable: *const ServerDriverHostVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverInputVTable {\r\n    create_boolean_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n    update_boolean_component: extern \"C\" fn(*mut DriverInput, u64, bool, f64) -> i32,\r\n    create_scalar_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64, i32, i32) -> i32,\r\n    update_scalar_component: extern \"C\" fn(*mut DriverInput, u64, f32, f64) -> i32,\r\n    create_haptic_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverInput {\r\n    vtable: *const DriverInputVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct PropertiesVTable {\r\n    read_property_batch: extern \"C\" fn(*mut Properties, u64, *mut c_void, u32) -> i32,\r\n    write_property_batch: extern \"C\" fn(*mut Properties, u64, *mut PropertyWrite, u32) -> i32,\r\n    get_prop_error_name_from_enum: extern \"C\" fn(*mut Properties, i32) -> *const c_char,\r\n    tracked_device_to_property_container: extern \"C\" fn(*mut Properties, u32) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct Properties {\r\n    vtable: *const PropertiesVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct PropertyWrite {\r\n    prop: u32,\r\n    write_type: i32,\r\n    set_error: i32,\r\n    pv_buffer: *mut c_void,\r\n    un_buffer_size: u32,\r\n    un_tag: u32,\r\n    e_error: i32,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerTrackedDeviceProviderVTable {\r\n    init: extern \"C\" fn(*mut ServerTrackedDeviceProvider, *mut c_void) -> EvRInitError,\r\n    cleanup: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    get_interface_versions:\r\n        extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> *const *const c_char,\r\n    run_frame: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    should_block_standby_mode: extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> bool,\r\n    enter_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    leave_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n}\r\n\r\n#[repr(C)]\r\nstruct TrackedDeviceServerDriverVTable {\r\n    activate: extern \"C\" fn(*mut TrackedDeviceServerDriver, u32) -> EvRInitError,\r\n    deactivate: extern \"C\" fn(*mut TrackedDeviceServerDriver),\r\n    enter_standby: extern \"C\" fn(*mut TrackedDeviceServerDriver),\r\n    get_component:\r\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char) -> *mut c_void,\r\n    debug_request:\r\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char, *mut c_char, u32),\r\n    get_pose: extern \"C\" fn(*mut TrackedDeviceServerDriver) -> DriverPose,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerTrackedDeviceProvider {\r\n    vtable: *const ServerTrackedDeviceProviderVTable,\r\n}\r\nunsafe impl Sync for ServerTrackedDeviceProvider {}\r\n\r\n#[repr(C)]\r\nstruct TrackedDeviceServerDriver {\r\n    vtable: *const TrackedDeviceServerDriverVTable,\r\n}\r\nunsafe impl Sync for TrackedDeviceServerDriver {}\r\n\r\n#[repr(C)]\r\nstruct InterfaceVersions([*const c_char; 10]);\nunsafe impl Sync for InterfaceVersions {}\r\n\r\nstatic HOST_CONTEXT: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DEVICE_INDEX: AtomicU32 = AtomicU32::new(u32::MAX);\r\nstatic DEVICE_ACTIVE: AtomicBool = AtomicBool::new(false);\r\nstatic DEVICE_REGISTERED: AtomicBool = AtomicBool::new(false);\r\n\r\nstatic SERVER_HOST_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DRIVER_INPUT_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic PROPERTIES_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\n\r\nstatic DEVICE_CONTAINER: AtomicU64 = AtomicU64::new(0);\r\nstatic HAPTIC_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\n\r\nconst PROVIDER_IFACE_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_004\\0\";\r\nconst PROVIDER_IFACE_ALT_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_005\\0\";\r\nconst DEVICE_IFACE_CSTR: &[u8] = b\"ITrackedDeviceServerDriver_005\\0\";\r\n\r\nconst IVR_PROPERTIES_CSTR: &[u8] = b\"IVRProperties_001\\0\";\r\nconst IVR_DRIVER_INPUT_CSTR: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_SERVER_DRIVER_HOST_CSTR: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\n\r\nconst IVR_SETTINGS_CSTR: &[u8] = b\"IVRSettings_003\\0\";\r\nconst IVR_DRIVER_MANAGER_CSTR: &[u8] = b\"IVRDriverManager_001\\0\";\r\nconst IVR_RESOURCES_CSTR: &[u8] = b\"IVRResources_001\\0\";\r\n\r\nstatic INTERFACE_VERSIONS: InterfaceVersions = InterfaceVersions([\r\n    IVR_SETTINGS_CSTR.as_ptr().cast(),\r\n    DEVICE_IFACE_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_CSTR.as_ptr().cast(),\r\n    IVR_PROPERTIES_CSTR.as_ptr().cast(),\r\n    IVR_SERVER_DRIVER_HOST_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_MANAGER_CSTR.as_ptr().cast(),\r\n    IVR_RESOURCES_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_INPUT_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_ALT_CSTR.as_ptr().cast(),\r\n    core::ptr::null(),\r\n]);\r\n\r\nfn get_server_host() -> Option<*mut ServerDriverHost> {\r\n    let ptr = SERVER_HOST_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_driver_input() -> Option<*mut DriverInput> {\r\n    let ptr = DRIVER_INPUT_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_properties() -> Option<*mut Properties> {\r\n    let ptr = PROPERTIES_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn write_string_property(props: *mut Properties, container: u64, prop: u32, value_nul: &'static [u8]) {\r\n    if props.is_null() || value_nul.is_empty() {\r\n        return;\r\n    }\r\n\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: value_nul.as_ptr().cast_mut().cast(),\r\n        un_buffer_size: value_nul.len() as u32,\r\n        un_tag: K_UN_STRING_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn register_tracked_device_if_needed() {\r\n    if DEVICE_REGISTERED.load(Ordering::SeqCst) {\r\n        return;\r\n    }\r\n\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    let added = unsafe {\r\n        ((*(*host).vtable).tracked_device_added)(\r\n            host,\r\n            DEVICE_SERIAL_CSTR.as_ptr().cast(),\r\n            TRACKED_DEVICE_CLASS_CONTROLLER,\r\n            (&DEVICE as *const TrackedDeviceServerDriver).cast_mut(),\r\n        )\r\n    };\r\n\r\n    if added {\r\n        DEVICE_REGISTERED.store(true, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\nfn resolve_driver_interfaces(driver_context: *mut c_void) -> bool {\r\n    if driver_context.is_null() {\r\n        return false;\r\n    }\r\n\r\n    let ctx = driver_context.cast::<DriverContext>();\r\n    let mut err = VR_INIT_ERROR_NONE;\r\n\r\n    let get_iface = unsafe { (*(*ctx).vtable).get_generic_interface };\r\n\r\n    let host = get_iface(\r\n        ctx,\r\n        IVR_SERVER_DRIVER_HOST_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let driver_input = get_iface(\r\n        ctx,\r\n        IVR_DRIVER_INPUT_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let properties = get_iface(\r\n        ctx,\r\n        IVR_PROPERTIES_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    SERVER_HOST_IFACE.store(host, Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(driver_input, Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(properties, Ordering::SeqCst);\r\n\r\n    !host.is_null() && !driver_input.is_null() && !properties.is_null()\r\n}\r\n\r\nfn route_haptic_events() {\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    loop {\r\n        let mut event = VREvent::default();\r\n        let ok = unsafe {\r\n            ((*(*host).vtable).poll_next_event)(\r\n                host,\r\n                &mut event as *mut VREvent,\r\n                core::mem::size_of::<VREvent>() as u32,\r\n            )\r\n        };\r\n\r\n        if !ok {\r\n            break;\r\n        }\r\n\r\n        if event.event_type != VREVENT_INPUT_HAPTIC_VIBRATION {\r\n            continue;\r\n        }\r\n\r\n        let hv = unsafe { event.data.haptic_vibration };\r\n\r\n        let container = DEVICE_CONTAINER.load(Ordering::SeqCst);\r\n        if container != 0 && hv.container_handle != 0 && hv.container_handle != container {\r\n            continue;\r\n        }\r\n\r\n        let haptic_component = HAPTIC_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n        if haptic_component != 0 && hv.component_handle != 0 && hv.component_handle != haptic_component {\r\n            continue;\r\n        }\r\n\r\n        haptics::handle_haptic_request(haptics::HapticVibrationRequest {\r\n            amplitude: hv.amplitude,\r\n            duration_seconds: hv.duration_seconds,\r\n            frequency: hv.frequency,\r\n        });\r\n    }\r\n}\r\n    PROVIDER_IFACE_CSTR.as_ptr().cast(),\r\n    DEVICE_IFACE_CSTR.as_ptr().cast(),\r\n    core::ptr::null(),\r\n]);\r\n\r\nextern \"C\" fn provider_init(\r\n    _this: *mut ServerTrackedDeviceProvider,\r\n    driver_context: *mut c_void,\r\n) -> EvRInitError {\r\n    HOST_CONTEXT.store(driver_context, Ordering::SeqCst);\r\n\r\n    if !resolve_driver_interfaces(driver_context) {\r\n        return VR_INIT_ERROR_DRIVER_FAILED;\r\n    }\r\n\r\n    register_tracked_device_if_needed();\r\n\r\n    VR_INIT_ERROR_NONE\r\n}\r\n\r\nextern \"C\" fn provider_cleanup(_this: *mut ServerTrackedDeviceProvider) {\r\n    HOST_CONTEXT.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\r\n    DEVICE_REGISTERED.store(false, Ordering::SeqCst);\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    SERVER_HOST_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn provider_get_interface_versions(\r\n    _this: *mut ServerTrackedDeviceProvider,\r\n) -> *const *const c_char {\r\n    INTERFACE_VERSIONS.0.as_ptr()\r\n}\r\n\r\nextern \"C\" fn provider_run_frame(_this: *mut ServerTrackedDeviceProvider) {\r\n    route_haptic_events();\r\n    haptics::on_frame();\r\n}\r\n\r\nextern \"C\" fn provider_should_block_standby_mode(_this: *mut ServerTrackedDeviceProvider) -> bool {\r\n    false\r\n}\r\n\r\nextern \"C\" fn provider_enter_standby(_this: *mut ServerTrackedDeviceProvider) {}\r\n\r\nextern \"C\" fn provider_leave_standby(_this: *mut ServerTrackedDeviceProvider) {}\r\n\r\nextern \"C\" fn device_activate(_this: *mut TrackedDeviceServerDriver, object_id: u32) -> EvRInitError {\r\n    DEVICE_INDEX.store(object_id, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(true, Ordering::SeqCst);\r\n\r\n    let Some(props) = get_properties() else {\r\n        return VR_INIT_ERROR_DRIVER_FAILED;\r\n    };\r\n\r\n    let container = unsafe { ((*(*props).vtable).tracked_device_to_property_container)(props, object_id) };\r\n    DEVICE_CONTAINER.store(container, Ordering::SeqCst);\r\n\r\n    write_string_property(props, container, PROP_MODEL_NUMBER_STRING, DEVICE_MODEL_CSTR);\r\n    write_string_property(props, container, PROP_SERIAL_NUMBER_STRING, DEVICE_SERIAL_CSTR);\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_INPUT_PROFILE_PATH_STRING,\r\n        DEVICE_INPUT_PROFILE_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_CONTROLLER_TYPE_STRING,\r\n        DEVICE_CONTROLLER_TYPE_CSTR,\r\n    );\r\n\r\n    if let Some(input) = get_driver_input() {\r\n        let mut handle = 0_u64;\r\n        let rc = unsafe {\r\n            ((*(*input).vtable).create_haptic_component)(\r\n                input,\r\n                container,\r\n                DEVICE_HAPTIC_PATH_CSTR.as_ptr().cast(),\r\n                &mut handle as *mut u64,\r\n            )\r\n        };\r\n\r\n        if rc == 0 {\r\n            HAPTIC_COMPONENT_HANDLE.store(handle, Ordering::SeqCst);\r\n        }\r\n    }\r\n\r\n    VR_INIT_ERROR_NONE\r\n}\r\n\r\nextern \"C\" fn device_deactivate(_this: *mut TrackedDeviceServerDriver) {\r\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn device_enter_standby(_this: *mut TrackedDeviceServerDriver) {\r\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn device_get_component(\r\n    _this: *mut TrackedDeviceServerDriver,\r\n    _component_name: *const c_char,\r\n) -> *mut c_void {\r\n    core::ptr::null_mut()\r\n}\r\n\r\nextern \"C\" fn device_debug_request(\r\n    _this: *mut TrackedDeviceServerDriver,\r\n    _request: *const c_char,\r\n    response: *mut c_char,\r\n    response_size: u32,\r\n) {\r\n    if response.is_null() || response_size == 0 {\r\n        return;\r\n    }\r\n    unsafe {\r\n        *response = 0;\r\n    }\r\n}\r\n\r\nextern \"C\" fn device_get_pose(_this: *mut TrackedDeviceServerDriver) -> DriverPose {\r\n    DriverPose { _reserved: [0; 128] }\r\n}\r\n\r\nstatic PROVIDER_VTABLE: ServerTrackedDeviceProviderVTable = ServerTrackedDeviceProviderVTable {\r\n    init: provider_init,\r\n    cleanup: provider_cleanup,\r\n    get_interface_versions: provider_get_interface_versions,\r\n    run_frame: provider_run_frame,\r\n    should_block_standby_mode: provider_should_block_standby_mode,\r\n    enter_standby: provider_enter_standby,\r\n    leave_standby: provider_leave_standby,\r\n};\r\n\r\nstatic DEVICE_VTABLE: TrackedDeviceServerDriverVTable = TrackedDeviceServerDriverVTable {\r\n    activate: device_activate,\r\n    deactivate: device_deactivate,\r\n    enter_standby: device_enter_standby,\r\n    get_component: device_get_component,\r\n    debug_request: device_debug_request,\r\n    get_pose: device_get_pose,\r\n};\r\n\r\nstatic PROVIDER: ServerTrackedDeviceProvider = ServerTrackedDeviceProvider {\r\n    vtable: &PROVIDER_VTABLE,\r\n};\r\n\r\nstatic DEVICE: TrackedDeviceServerDriver = TrackedDeviceServerDriver {\r\n    vtable: &DEVICE_VTABLE,\r\n};\r\n\r\nfn set_return_code(out: *mut i32, code: DriverReturnCode) {\r\n    if !out.is_null() {\r\n        unsafe {\r\n            *out = code as i32;\r\n        }\r\n    }\r\n}\r\n\r\nfn read_interface_name(ptr: *const i8) -> Option<&'static str> {\r\n    if ptr.is_null() {\r\n        return None;\r\n    }\r\n\r\n    let cstr = unsafe { CStr::from_ptr(ptr) };\r\n    let Ok(name) = cstr.to_str() else {\r\n        return None;\r\n    };\r\n\r\n    if name == SERVER_PROVIDER_INTERFACE {\r\n        return Some(SERVER_PROVIDER_INTERFACE);\r\n    }\r\n    if name == SERVER_PROVIDER_INTERFACE_ALT {\r\n        return Some(SERVER_PROVIDER_INTERFACE);\r\n    }\r\n    if name == TRACKED_DEVICE_INTERFACE {\r\n        return Some(TRACKED_DEVICE_INTERFACE);\r\n    }\r\n\r\n    None\r\n}\r\n\r\n/// Resolves OpenVR interface requests to static provider/device instances.\r\n///\r\n/// On match, writes `DriverReturnCode::None` to `p_return_code` and returns a\r\n/// stable interface pointer. On failure, writes an error code and returns null.\r\npub fn hmd_driver_factory(\r\n    p_interface_name: *const c_char,\r\n    p_return_code: *mut i32,\r\n) -> *mut c_void {\r\n    let Some(interface_name) = read_interface_name(p_interface_name) else {\r\n        set_return_code(p_return_code, DriverReturnCode::InitInterfaceNotFound);\r\n        return core::ptr::null_mut();\r\n    };\r\n\r\n    if interface_name == SERVER_PROVIDER_INTERFACE {\r\n        set_return_code(p_return_code, DriverReturnCode::None);\r\n        return (&PROVIDER as *const ServerTrackedDeviceProvider)\r\n            .cast_mut()\r\n            .cast();\r\n    }\r\n\r\n    if interface_name == TRACKED_DEVICE_INTERFACE {\r\n        set_return_code(p_return_code, DriverReturnCode::None);\r\n        return (&DEVICE as *const TrackedDeviceServerDriver)\r\n            .cast_mut()\r\n            .cast();\r\n    }\r\n\r\n    set_return_code(p_return_code, DriverReturnCode::Unknown);\r\n    core::ptr::null_mut()\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":603,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_244f6538-b0e5-4f3c-84eb-c094cc01ec2c","epoch":1517,"edits":[{"text":"//! OpenVR ABI/vtable scaffolding for the Big Haptic Driver.\r\n//!\r\n//! This module owns C ABI exports and interface routing for SteamVR-facing\r\n//! provider/device entry points, while delegating haptics behavior to\r\n//! the `haptics` module.\r\n\r\nuse core::ffi::{c_void, CStr};\r\nuse core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU32, AtomicU64, Ordering};\r\nuse std::os::raw::c_char;\r\n\r\nuse crate::haptics;\r\n\r\nconst SERVER_PROVIDER_INTERFACE: &str = \"IServerTrackedDeviceProvider_004\";\r\nconst SERVER_PROVIDER_INTERFACE_ALT: &str = \"IServerTrackedDeviceProvider_005\";\r\nconst TRACKED_DEVICE_INTERFACE: &str = \"ITrackedDeviceServerDriver_005\";\r\n\r\nconst IVR_SERVER_DRIVER_HOST_VERSION: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\nconst IVR_DRIVER_INPUT_VERSION: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_PROPERTIES_VERSION: &[u8] = b\"IVRProperties_001\\0\";\r\n\r\nconst TRACKED_DEVICE_CLASS_CONTROLLER: i32 = 2;\r\nconst VREVENT_INPUT_HAPTIC_VIBRATION: u32 = 1700;\r\nconst VR_INIT_ERROR_DRIVER_FAILED: EvRInitError = 200;\r\n\r\nconst PROP_MODEL_NUMBER_STRING: u32 = 1001;\r\nconst PROP_SERIAL_NUMBER_STRING: u32 = 1002;\r\nconst PROP_INPUT_PROFILE_PATH_STRING: u32 = 1037;\r\nconst PROP_CONTROLLER_TYPE_STRING: u32 = 7000;\r\n\r\nconst K_UN_STRING_PROPERTY_TAG: u32 = 5;\r\nconst PROPERTY_WRITE_SET: i32 = 0;\r\n\r\nconst DEVICE_SERIAL_CSTR: &[u8] = b\"qtpy-samd21-haptic-001\\0\";\r\nconst DEVICE_MODEL_CSTR: &[u8] = b\"QT Py SAMD21 Haptic\\0\";\r\nconst DEVICE_CONTROLLER_TYPE_CSTR: &[u8] = b\"big_haptic_driver\\0\";\r\nconst DEVICE_INPUT_PROFILE_CSTR: &[u8] = b\"{big_haptic_driver}/input/big_haptic_profile.json\\0\";\r\nconst DEVICE_HAPTIC_PATH_CSTR: &[u8] = b\"/output/haptic\\0\";\r\n\r\n#[repr(i32)]\r\nenum DriverReturnCode {\r\n    None = 0,\r\n    Unknown = 1,\r\n    InitInterfaceNotFound = 105,\r\n}\r\n\r\ntype EvRInitError = i32;\r\nconst VR_INIT_ERROR_NONE: EvRInitError = 0;\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nstruct DriverPose {\r\n    _reserved: [u8; 128],\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREventHapticVibration {\r\n    container_handle: u64,\r\n    component_handle: u64,\r\n    duration_seconds: f32,\r\n    frequency: f32,\r\n    amplitude: f32,\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nunion VREventData {\r\n    haptic_vibration: VREventHapticVibration,\r\n    reserved: [u8; 48],\r\n}\r\n\r\nimpl Default for VREventData {\r\n    fn default() -> Self {\r\n        Self { reserved: [0; 48] }\r\n    }\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREvent {\r\n    event_type: u32,\r\n    tracked_device_index: u32,\r\n    event_age_seconds: f32,\r\n    data: VREventData,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContextVTable {\r\n    get_generic_interface:\r\n        extern \"C\" fn(*mut DriverContext, *const c_char, *mut EvRInitError) -> *mut c_void,\r\n    get_driver_handle: extern \"C\" fn(*mut DriverContext) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContext {\r\n    vtable: *const DriverContextVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHostVTable {\r\n    tracked_device_added:\r\n        extern \"C\" fn(*mut ServerDriverHost, *const c_char, i32, *mut TrackedDeviceServerDriver) -> bool,\r\n    tracked_device_pose_updated: extern \"C\" fn(*mut ServerDriverHost, u32, *const DriverPose, u32),\r\n    vsync_event: extern \"C\" fn(*mut ServerDriverHost, f64),\r\n    vendor_specific_event: extern \"C\" fn(*mut ServerDriverHost, u32, u32, *const VREventData, f64),\r\n    is_exiting: extern \"C\" fn(*mut ServerDriverHost) -> bool,\r\n    poll_next_event: extern \"C\" fn(*mut ServerDriverHost, *mut VREvent, u32) -> bool,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHost {\r\n    vtable: *const ServerDriverHostVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverInputVTable {\r\n    create_boolean_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n    update_boolean_component: extern \"C\" fn(*mut DriverInput, u64, bool, f64) -> i32,\r\n    create_scalar_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64, i32, i32) -> i32,\r\n    update_scalar_component: extern \"C\" fn(*mut DriverInput, u64, f32, f64) -> i32,\r\n    create_haptic_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverInput {\r\n    vtable: *const DriverInputVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct PropertiesVTable {\r\n    read_property_batch: extern \"C\" fn(*mut Properties, u64, *mut c_void, u32) -> i32,\r\n    write_property_batch: extern \"C\" fn(*mut Properties, u64, *mut PropertyWrite, u32) -> i32,\r\n    get_prop_error_name_from_enum: extern \"C\" fn(*mut Properties, i32) -> *const c_char,\r\n    tracked_device_to_property_container: extern \"C\" fn(*mut Properties, u32) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct Properties {\r\n    vtable: *const PropertiesVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct PropertyWrite {\r\n    prop: u32,\r\n    write_type: i32,\r\n    set_error: i32,\r\n    pv_buffer: *mut c_void,\r\n    un_buffer_size: u32,\r\n    un_tag: u32,\r\n    e_error: i32,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerTrackedDeviceProviderVTable {\r\n    init: extern \"C\" fn(*mut ServerTrackedDeviceProvider, *mut c_void) -> EvRInitError,\r\n    cleanup: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    get_interface_versions:\r\n        extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> *const *const c_char,\r\n    run_frame: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    should_block_standby_mode: extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> bool,\r\n    enter_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    leave_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n}\r\n\r\n#[repr(C)]\r\nstruct TrackedDeviceServerDriverVTable {\r\n    activate: extern \"C\" fn(*mut TrackedDeviceServerDriver, u32) -> EvRInitError,\r\n    deactivate: extern \"C\" fn(*mut TrackedDeviceServerDriver),\r\n    enter_standby: extern \"C\" fn(*mut TrackedDeviceServerDriver),\r\n    get_component:\r\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char) -> *mut c_void,\r\n    debug_request:\r\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char, *mut c_char, u32),\r\n    get_pose: extern \"C\" fn(*mut TrackedDeviceServerDriver) -> DriverPose,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerTrackedDeviceProvider {\r\n    vtable: *const ServerTrackedDeviceProviderVTable,\r\n}\r\nunsafe impl Sync for ServerTrackedDeviceProvider {}\r\n\r\n#[repr(C)]\r\nstruct TrackedDeviceServerDriver {\r\n    vtable: *const TrackedDeviceServerDriverVTable,\r\n}\r\nunsafe impl Sync for TrackedDeviceServerDriver {}\r\n\r\n#[repr(C)]\r\nstruct InterfaceVersions([*const c_char; 10]);\r\nunsafe impl Sync for InterfaceVersions {}\r\n\r\nstatic HOST_CONTEXT: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DEVICE_INDEX: AtomicU32 = AtomicU32::new(u32::MAX);\r\nstatic DEVICE_ACTIVE: AtomicBool = AtomicBool::new(false);\r\nstatic DEVICE_REGISTERED: AtomicBool = AtomicBool::new(false);\r\n\r\nstatic SERVER_HOST_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DRIVER_INPUT_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic PROPERTIES_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\n\r\nstatic DEVICE_CONTAINER: AtomicU64 = AtomicU64::new(0);\r\nstatic HAPTIC_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\n\r\nconst PROVIDER_IFACE_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_004\\0\";\r\nconst PROVIDER_IFACE_ALT_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_005\\0\";\r\nconst DEVICE_IFACE_CSTR: &[u8] = b\"ITrackedDeviceServerDriver_005\\0\";\r\n\r\nconst IVR_PROPERTIES_CSTR: &[u8] = b\"IVRProperties_001\\0\";\r\nconst IVR_DRIVER_INPUT_CSTR: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_SERVER_DRIVER_HOST_CSTR: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\n\r\nconst IVR_SETTINGS_CSTR: &[u8] = b\"IVRSettings_003\\0\";\r\nconst IVR_DRIVER_MANAGER_CSTR: &[u8] = b\"IVRDriverManager_001\\0\";\r\nconst IVR_RESOURCES_CSTR: &[u8] = b\"IVRResources_001\\0\";\r\n\r\nstatic INTERFACE_VERSIONS: InterfaceVersions = InterfaceVersions([\r\n    IVR_SETTINGS_CSTR.as_ptr().cast(),\r\n    DEVICE_IFACE_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_CSTR.as_ptr().cast(),\r\n    IVR_PROPERTIES_CSTR.as_ptr().cast(),\r\n    IVR_SERVER_DRIVER_HOST_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_MANAGER_CSTR.as_ptr().cast(),\r\n    IVR_RESOURCES_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_INPUT_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_ALT_CSTR.as_ptr().cast(),\r\n    core::ptr::null(),\r\n]);\r\n\r\nfn get_server_host() -> Option<*mut ServerDriverHost> {\r\n    let ptr = SERVER_HOST_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_driver_input() -> Option<*mut DriverInput> {\r\n    let ptr = DRIVER_INPUT_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_properties() -> Option<*mut Properties> {\r\n    let ptr = PROPERTIES_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn write_string_property(props: *mut Properties, container: u64, prop: u32, value_nul: &'static [u8]) {\r\n    if props.is_null() || value_nul.is_empty() {\r\n        return;\r\n    }\r\n\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: value_nul.as_ptr().cast_mut().cast(),\r\n        un_buffer_size: value_nul.len() as u32,\r\n        un_tag: K_UN_STRING_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn register_tracked_device_if_needed() {\r\n    if DEVICE_REGISTERED.load(Ordering::SeqCst) {\r\n        return;\r\n    }\r\n\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    let added = unsafe {\r\n        ((*(*host).vtable).tracked_device_added)(\r\n            host,\r\n            DEVICE_SERIAL_CSTR.as_ptr().cast(),\r\n            TRACKED_DEVICE_CLASS_CONTROLLER,\r\n            (&DEVICE as *const TrackedDeviceServerDriver).cast_mut(),\r\n        )\r\n    };\r\n\r\n    if added {\r\n        DEVICE_REGISTERED.store(true, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\nfn resolve_driver_interfaces(driver_context: *mut c_void) -> bool {\r\n    if driver_context.is_null() {\r\n        return false;\r\n    }\r\n\r\n    let ctx = driver_context.cast::<DriverContext>();\r\n    let mut err = VR_INIT_ERROR_NONE;\r\n\r\n    let get_iface = unsafe { (*(*ctx).vtable).get_generic_interface };\r\n\r\n    let host = get_iface(\r\n        ctx,\r\n        IVR_SERVER_DRIVER_HOST_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let driver_input = get_iface(\r\n        ctx,\r\n        IVR_DRIVER_INPUT_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let properties = get_iface(\r\n        ctx,\r\n        IVR_PROPERTIES_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    SERVER_HOST_IFACE.store(host, Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(driver_input, Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(properties, Ordering::SeqCst);\r\n\r\n    !host.is_null() && !driver_input.is_null() && !properties.is_null()\r\n}\r\n\r\nfn route_haptic_events() {\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    loop {\r\n        let mut event = VREvent::default();\r\n        let ok = unsafe {\r\n            ((*(*host).vtable).poll_next_event)(\r\n                host,\r\n                &mut event as *mut VREvent,\r\n                core::mem::size_of::<VREvent>() as u32,\r\n            )\r\n        };\r\n\r\n        if !ok {\r\n            break;\r\n        }\r\n\r\n        if event.event_type != VREVENT_INPUT_HAPTIC_VIBRATION {\r\n            continue;\r\n        }\r\n\r\n        let hv = unsafe { event.data.haptic_vibration };\r\n\r\n        let container = DEVICE_CONTAINER.load(Ordering::SeqCst);\r\n        if container != 0 && hv.container_handle != 0 && hv.container_handle != container {\r\n            continue;\r\n        }\r\n\r\n        let haptic_component = HAPTIC_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n        if haptic_component != 0 && hv.component_handle != 0 && hv.component_handle != haptic_component {\r\n            continue;\r\n        }\r\n\r\n        haptics::handle_haptic_request(haptics::HapticVibrationRequest {\r\n            amplitude: hv.amplitude,\r\n            duration_seconds: hv.duration_seconds,\r\n            frequency: hv.frequency,\r\n        });\r\n    }\r\n}\r\n\r\nextern \"C\" fn provider_init(\r\n    _this: *mut ServerTrackedDeviceProvider,\r\n    driver_context: *mut c_void,\r\n) -> EvRInitError {\r\n    HOST_CONTEXT.store(driver_context, Ordering::SeqCst);\r\n\r\n    if !resolve_driver_interfaces(driver_context) {\r\n        return VR_INIT_ERROR_DRIVER_FAILED;\r\n    }\r\n\r\n    register_tracked_device_if_needed();\r\n\r\n    VR_INIT_ERROR_NONE\r\n}\r\n\r\nextern \"C\" fn provider_cleanup(_this: *mut ServerTrackedDeviceProvider) {\r\n    HOST_CONTEXT.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\r\n    DEVICE_REGISTERED.store(false, Ordering::SeqCst);\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    SERVER_HOST_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn provider_get_interface_versions(\r\n    _this: *mut ServerTrackedDeviceProvider,\r\n) -> *const *const c_char {\r\n    INTERFACE_VERSIONS.0.as_ptr()\r\n}\r\n\r\nextern \"C\" fn provider_run_frame(_this: *mut ServerTrackedDeviceProvider) {\r\n    route_haptic_events();\r\n    haptics::on_frame();\r\n}\r\n\r\nextern \"C\" fn provider_should_block_standby_mode(_this: *mut ServerTrackedDeviceProvider) -> bool {\r\n    false\r\n}\r\n\r\nextern \"C\" fn provider_enter_standby(_this: *mut ServerTrackedDeviceProvider) {}\r\n\r\nextern \"C\" fn provider_leave_standby(_this: *mut ServerTrackedDeviceProvider) {}\r\n\r\nextern \"C\" fn device_activate(_this: *mut TrackedDeviceServerDriver, object_id: u32) -> EvRInitError {\r\n    DEVICE_INDEX.store(object_id, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(true, Ordering::SeqCst);\r\n\r\n    let Some(props) = get_properties() else {\r\n        return VR_INIT_ERROR_DRIVER_FAILED;\r\n    };\r\n\r\n    let container = unsafe { ((*(*props).vtable).tracked_device_to_property_container)(props, object_id) };\r\n    DEVICE_CONTAINER.store(container, Ordering::SeqCst);\r\n\r\n    write_string_property(props, container, PROP_MODEL_NUMBER_STRING, DEVICE_MODEL_CSTR);\r\n    write_string_property(props, container, PROP_SERIAL_NUMBER_STRING, DEVICE_SERIAL_CSTR);\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_INPUT_PROFILE_PATH_STRING,\r\n        DEVICE_INPUT_PROFILE_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_CONTROLLER_TYPE_STRING,\r\n        DEVICE_CONTROLLER_TYPE_CSTR,\r\n    );\r\n\r\n    if let Some(input) = get_driver_input() {\r\n        let mut handle = 0_u64;\r\n        let rc = unsafe {\r\n            ((*(*input).vtable).create_haptic_component)(\r\n                input,\r\n                container,\r\n                DEVICE_HAPTIC_PATH_CSTR.as_ptr().cast(),\r\n                &mut handle as *mut u64,\r\n            )\r\n        };\r\n\r\n        if rc == 0 {\r\n            HAPTIC_COMPONENT_HANDLE.store(handle, Ordering::SeqCst);\r\n        }\r\n    }\r\n\r\n    VR_INIT_ERROR_NONE\r\n}\r\n\r\nextern \"C\" fn device_deactivate(_this: *mut TrackedDeviceServerDriver) {\r\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn device_enter_standby(_this: *mut TrackedDeviceServerDriver) {\r\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn device_get_component(\r\n    _this: *mut TrackedDeviceServerDriver,\r\n    _component_name: *const c_char,\r\n) -> *mut c_void {\r\n    core::ptr::null_mut()\r\n}\r\n\r\nextern \"C\" fn device_debug_request(\r\n    _this: *mut TrackedDeviceServerDriver,\r\n    _request: *const c_char,\r\n    response: *mut c_char,\r\n    response_size: u32,\r\n) {\r\n    if response.is_null() || response_size == 0 {\r\n        return;\r\n    }\r\n    unsafe {\r\n        *response = 0;\r\n    }\r\n}\r\n\r\nextern \"C\" fn device_get_pose(_this: *mut TrackedDeviceServerDriver) -> DriverPose {\r\n    DriverPose { _reserved: [0; 128] }\r\n}\r\n\r\nstatic PROVIDER_VTABLE: ServerTrackedDeviceProviderVTable = ServerTrackedDeviceProviderVTable {\r\n    init: provider_init,\r\n    cleanup: provider_cleanup,\r\n    get_interface_versions: provider_get_interface_versions,\r\n    run_frame: provider_run_frame,\r\n    should_block_standby_mode: provider_should_block_standby_mode,\r\n    enter_standby: provider_enter_standby,\r\n    leave_standby: provider_leave_standby,\r\n};\r\n\r\nstatic DEVICE_VTABLE: TrackedDeviceServerDriverVTable = TrackedDeviceServerDriverVTable {\r\n    activate: device_activate,\r\n    deactivate: device_deactivate,\r\n    enter_standby: device_enter_standby,\r\n    get_component: device_get_component,\r\n    debug_request: device_debug_request,\r\n    get_pose: device_get_pose,\r\n};\r\n\r\nstatic PROVIDER: ServerTrackedDeviceProvider = ServerTrackedDeviceProvider {\r\n    vtable: &PROVIDER_VTABLE,\r\n};\r\n\r\nstatic DEVICE: TrackedDeviceServerDriver = TrackedDeviceServerDriver {\r\n    vtable: &DEVICE_VTABLE,\r\n};\r\n\r\nfn set_return_code(out: *mut i32, code: DriverReturnCode) {\r\n    if !out.is_null() {\r\n        unsafe {\r\n            *out = code as i32;\r\n        }\r\n    }\r\n}\r\n\r\nfn read_interface_name(ptr: *const i8) -> Option<&'static str> {\r\n    if ptr.is_null() {\r\n        return None;\r\n    }\r\n\r\n    let cstr = unsafe { CStr::from_ptr(ptr) };\r\n    let Ok(name) = cstr.to_str() else {\r\n        return None;\r\n    };\r\n\r\n    if name == SERVER_PROVIDER_INTERFACE {\r\n        return Some(SERVER_PROVIDER_INTERFACE);\r\n    }\r\n    if name == SERVER_PROVIDER_INTERFACE_ALT {\r\n        return Some(SERVER_PROVIDER_INTERFACE);\r\n    }\r\n    if name == TRACKED_DEVICE_INTERFACE {\r\n        return Some(TRACKED_DEVICE_INTERFACE);\r\n    }\r\n\r\n    None\r\n}\r\n\r\n/// Resolves OpenVR interface requests to static provider/device instances.\r\n///\r\n/// On match, writes `DriverReturnCode::None` to `p_return_code` and returns a\r\n/// stable interface pointer. On failure, writes an error code and returns null.\r\npub fn hmd_driver_factory(\r\n    p_interface_name: *const c_char,\r\n    p_return_code: *mut i32,\r\n) -> *mut c_void {\r\n    let Some(interface_name) = read_interface_name(p_interface_name) else {\r\n        set_return_code(p_return_code, DriverReturnCode::InitInterfaceNotFound);\r\n        return core::ptr::null_mut();\r\n    };\r\n\r\n    if interface_name == SERVER_PROVIDER_INTERFACE {\r\n        set_return_code(p_return_code, DriverReturnCode::None);\r\n        return (&PROVIDER as *const ServerTrackedDeviceProvider)\r\n            .cast_mut()\r\n            .cast();\r\n    }\r\n\r\n    if interface_name == TRACKED_DEVICE_INTERFACE {\r\n        set_return_code(p_return_code, DriverReturnCode::None);\r\n        return (&DEVICE as *const TrackedDeviceServerDriver)\r\n            .cast_mut()\r\n            .cast();\r\n    }\r\n\r\n    set_return_code(p_return_code, DriverReturnCode::Unknown);\r\n    core::ptr::null_mut()\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":599,"endColumn":1}},{"text":"","range":{"startLineNumber":599,"startColumn":1,"endLineNumber":603,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\README.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/README.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/README.md","scheme":"file"},"requestId":"request_244f6538-b0e5-4f3c-84eb-c094cc01ec2c","epoch":1520,"edits":[{"text":"# big-haptic-driver\r\n\r\nInitial OpenVR driver scaffold in Rust.\r\n\r\n## Current capabilities\r\n\r\n- Exports `HmdDriverFactory` with minimal interface-name validation\r\n- Registers a tracked controller device with `IVRServerDriverHost::TrackedDeviceAdded`\n- Creates a haptic input component (`/output/haptic`) via `IVRDriverInput`\n- Routes `VREvent_Input_HapticVibration` events to firmware haptics (`SET_INTENSITY` + timed stop)\n- Exposes DLL-callable haptics test exports:\r\n  - `BigHapticDriver_SendAmplitude(float)`\r\n  - `BigHapticDriver_Stop()`\r\n- Sends packets to firmware through shared `hid-bridge`\r\n\r\n## Build\r\n\r\nFrom workspace root:\r\n\r\n- `cargo build -p big-haptic-driver --release`\r\n\r\nOutput DLL path:\r\n\r\n- `target/release/big_haptic_driver.dll`\r\n\r\n## Build + deploy (one command)\r\n\r\nFrom workspace root:\r\n\r\n- `./tools/scripts/build_and_deploy_openvr_driver.ps1`\r\n\r\nThis builds the crate and copies the DLL to:\r\n\r\n- `openvr-driver/big-haptic-driver/bin/win64/driver_big_haptic_driver.dll`\r\n\r\n## SteamVR layout\r\n\r\nDriver folder should include:\r\n\r\n- `driver.vrdrivermanifest`\r\n- `resources/`\r\n- `resources/input/big_haptic_profile.json`\r\n- `resources/localization/en_us.json`\r\n- `bin/win64/driver_big_haptic_driver.dll`\r\n\r\n## Register with SteamVR\r\n\r\nPowerShell command (note the `&` call operator before the quoted exe path):\r\n\r\n- `& \"C:\\Program Files (x86)\\Steam\\steamapps\\common\\SteamVR\\bin\\win64\\vrpathreg.exe\" adddriver \"C:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\"`\r\n\r\nTo verify registration:\r\n\r\n- `& \"C:\\Program Files (x86)\\Steam\\steamapps\\common\\SteamVR\\bin\\win64\\vrpathreg.exe\"`\r\n\r\nTo remove registration:\r\n\r\n- `& \"C:\\Program Files (x86)\\Steam\\steamapps\\common\\SteamVR\\bin\\win64\\vrpathreg.exe\" removedriver \"C:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\"`\r\n\r\n## Testing status (important)\r\n\nThe driver now performs real device/input registration and listens for haptic vibration events from SteamVR.\n\nRecommended validation flow:\n\n1. Ensure firmware is flashed and responding to HID.\n2. Restart SteamVR after deploying the driver DLL.\n3. Open SteamVR input bindings and look for controller type `big_haptic_driver`.\n4. Trigger haptics from an app/binding and verify actuator response.\n\nIf DLL deploy fails with \"file in use\", close SteamVR (and any PowerShell process that loaded the DLL) and redeploy.\n\r\nTemporary direct test (bypasses SteamVR input bindings):\r\n\r\n- `./tools/scripts/test_big_haptic_driver_exports.ps1 -Amplitude 0.35 -DurationSeconds 0.20 -Frequency 160`\r\n\r\nNote: in this direct-export mode, SteamVR frame pumping is bypassed, so the script performs an explicit delayed stop for `-DurationSeconds` unless `-NoAutoStop` is supplied.\r\n\r\nStop command:\r\n\r\n- `./tools/scripts/test_big_haptic_driver_exports.ps1 -Stop`\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":83,"endColumn":1}}]},{"type":"create","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\openvr_runtime.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","scheme":"file"},"requestId":"request_cd9235f7-4b6a-4823-ab9f-74e85fd0e64c","epoch":1523,"initialContent":"","telemetryInfo":{}},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\openvr_runtime.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","scheme":"file"},"requestId":"request_cd9235f7-4b6a-4823-ab9f-74e85fd0e64c","epoch":1524,"edits":[{"text":"//! OpenVR runtime integration helpers.\n//!\n//! This module owns driver-context interface resolution, tracked-device\n//! registration, component/property setup, and OpenVR haptic event routing.\n\nuse core::ffi::c_void;\nuse core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU64, Ordering};\nuse std::os::raw::c_char;\n\nuse crate::haptics;\n\ntype EvRInitError = i32;\n\nconst IVR_SERVER_DRIVER_HOST_VERSION: &[u8] = b\"IVRServerDriverHost_006\\0\";\nconst IVR_DRIVER_INPUT_VERSION: &[u8] = b\"IVRDriverInput_004\\0\";\nconst IVR_PROPERTIES_VERSION: &[u8] = b\"IVRProperties_001\\0\";\n\nconst TRACKED_DEVICE_CLASS_CONTROLLER: i32 = 2;\nconst VREVENT_INPUT_HAPTIC_VIBRATION: u32 = 1700;\n\nconst PROP_MODEL_NUMBER_STRING: u32 = 1001;\nconst PROP_SERIAL_NUMBER_STRING: u32 = 1002;\nconst PROP_INPUT_PROFILE_PATH_STRING: u32 = 1037;\nconst PROP_CONTROLLER_TYPE_STRING: u32 = 7000;\n\nconst K_UN_STRING_PROPERTY_TAG: u32 = 5;\nconst PROPERTY_WRITE_SET: i32 = 0;\n\nconst DEVICE_SERIAL_CSTR: &[u8] = b\"qtpy-samd21-haptic-001\\0\";\nconst DEVICE_MODEL_CSTR: &[u8] = b\"QT Py SAMD21 Haptic\\0\";\nconst DEVICE_CONTROLLER_TYPE_CSTR: &[u8] = b\"big_haptic_driver\\0\";\nconst DEVICE_INPUT_PROFILE_CSTR: &[u8] = b\"{big_haptic_driver}/input/big_haptic_profile.json\\0\";\nconst DEVICE_HAPTIC_PATH_CSTR: &[u8] = b\"/output/haptic\\0\";\n\nconst PROVIDER_IFACE_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_004\\0\";\nconst PROVIDER_IFACE_ALT_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_005\\0\";\nconst DEVICE_IFACE_CSTR: &[u8] = b\"ITrackedDeviceServerDriver_005\\0\";\n\nconst IVR_PROPERTIES_CSTR: &[u8] = b\"IVRProperties_001\\0\";\nconst IVR_DRIVER_INPUT_CSTR: &[u8] = b\"IVRDriverInput_004\\0\";\nconst IVR_SERVER_DRIVER_HOST_CSTR: &[u8] = b\"IVRServerDriverHost_006\\0\";\n\nconst IVR_SETTINGS_CSTR: &[u8] = b\"IVRSettings_003\\0\";\nconst IVR_DRIVER_MANAGER_CSTR: &[u8] = b\"IVRDriverManager_001\\0\";\nconst IVR_RESOURCES_CSTR: &[u8] = b\"IVRResources_001\\0\";\n\n#[repr(C)]\nstruct InterfaceVersions([*const c_char; 10]);\nunsafe impl Sync for InterfaceVersions {}\n\nstatic INTERFACE_VERSIONS: InterfaceVersions = InterfaceVersions([\n    IVR_SETTINGS_CSTR.as_ptr().cast(),\n    DEVICE_IFACE_CSTR.as_ptr().cast(),\n    PROVIDER_IFACE_CSTR.as_ptr().cast(),\n    IVR_PROPERTIES_CSTR.as_ptr().cast(),\n    IVR_SERVER_DRIVER_HOST_CSTR.as_ptr().cast(),\n    IVR_DRIVER_MANAGER_CSTR.as_ptr().cast(),\n    IVR_RESOURCES_CSTR.as_ptr().cast(),\n    IVR_DRIVER_INPUT_CSTR.as_ptr().cast(),\n    PROVIDER_IFACE_ALT_CSTR.as_ptr().cast(),\n    core::ptr::null(),\n]);\n\n#[repr(C)]\n#[derive(Clone, Copy, Default)]\nstruct VREventHapticVibration {\n    container_handle: u64,\n    component_handle: u64,\n    duration_seconds: f32,\n    frequency: f32,\n    amplitude: f32,\n}\n\n#[repr(C)]\n#[derive(Clone, Copy)]\nunion VREventData {\n    haptic_vibration: VREventHapticVibration,\n    reserved: [u8; 48],\n}\n\nimpl Default for VREventData {\n    fn default() -> Self {\n        Self { reserved: [0; 48] }\n    }\n}\n\n#[repr(C)]\n#[derive(Clone, Copy, Default)]\nstruct VREvent {\n    event_type: u32,\n    tracked_device_index: u32,\n    event_age_seconds: f32,\n    data: VREventData,\n}\n\n#[repr(C)]\nstruct DriverContextVTable {\n    get_generic_interface:\n        extern \"C\" fn(*mut DriverContext, *const c_char, *mut EvRInitError) -> *mut c_void,\n    get_driver_handle: extern \"C\" fn(*mut DriverContext) -> u64,\n}\n\n#[repr(C)]\nstruct DriverContext {\n    vtable: *const DriverContextVTable,\n}\n\n#[repr(C)]\nstruct ServerDriverHostVTable {\n    tracked_device_added:\n        extern \"C\" fn(*mut ServerDriverHost, *const c_char, i32, *mut c_void) -> bool,\n    tracked_device_pose_updated: extern \"C\" fn(*mut ServerDriverHost, u32, *const c_void, u32),\n    vsync_event: extern \"C\" fn(*mut ServerDriverHost, f64),\n    vendor_specific_event: extern \"C\" fn(*mut ServerDriverHost, u32, u32, *const VREventData, f64),\n    is_exiting: extern \"C\" fn(*mut ServerDriverHost) -> bool,\n    poll_next_event: extern \"C\" fn(*mut ServerDriverHost, *mut VREvent, u32) -> bool,\n}\n\n#[repr(C)]\nstruct ServerDriverHost {\n    vtable: *const ServerDriverHostVTable,\n}\n\n#[repr(C)]\nstruct DriverInputVTable {\n    create_boolean_component:\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\n    update_boolean_component: extern \"C\" fn(*mut DriverInput, u64, bool, f64) -> i32,\n    create_scalar_component:\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64, i32, i32) -> i32,\n    update_scalar_component: extern \"C\" fn(*mut DriverInput, u64, f32, f64) -> i32,\n    create_haptic_component:\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\n}\n\n#[repr(C)]\nstruct DriverInput {\n    vtable: *const DriverInputVTable,\n}\n\n#[repr(C)]\nstruct PropertiesVTable {\n    read_property_batch: extern \"C\" fn(*mut Properties, u64, *mut c_void, u32) -> i32,\n    write_property_batch: extern \"C\" fn(*mut Properties, u64, *mut PropertyWrite, u32) -> i32,\n    get_prop_error_name_from_enum: extern \"C\" fn(*mut Properties, i32) -> *const c_char,\n    tracked_device_to_property_container: extern \"C\" fn(*mut Properties, u32) -> u64,\n}\n\n#[repr(C)]\nstruct Properties {\n    vtable: *const PropertiesVTable,\n}\n\n#[repr(C)]\nstruct PropertyWrite {\n    prop: u32,\n    write_type: i32,\n    set_error: i32,\n    pv_buffer: *mut c_void,\n    un_buffer_size: u32,\n    un_tag: u32,\n    e_error: i32,\n}\n\nstatic DEVICE_REGISTERED: AtomicBool = AtomicBool::new(false);\n\nstatic SERVER_HOST_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\nstatic DRIVER_INPUT_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\nstatic PROPERTIES_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\n\nstatic DEVICE_CONTAINER: AtomicU64 = AtomicU64::new(0);\nstatic HAPTIC_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\n\npub fn interface_versions_ptr() -> *const *const c_char {\n    INTERFACE_VERSIONS.0.as_ptr()\n}\n\nfn get_server_host() -> Option<*mut ServerDriverHost> {\n    let ptr = SERVER_HOST_IFACE.load(Ordering::SeqCst);\n    if ptr.is_null() {\n        None\n    } else {\n        Some(ptr.cast())\n    }\n}\n\nfn get_driver_input() -> Option<*mut DriverInput> {\n    let ptr = DRIVER_INPUT_IFACE.load(Ordering::SeqCst);\n    if ptr.is_null() {\n        None\n    } else {\n        Some(ptr.cast())\n    }\n}\n\nfn get_properties() -> Option<*mut Properties> {\n    let ptr = PROPERTIES_IFACE.load(Ordering::SeqCst);\n    if ptr.is_null() {\n        None\n    } else {\n        Some(ptr.cast())\n    }\n}\n\nfn write_string_property(props: *mut Properties, container: u64, prop: u32, value_nul: &'static [u8]) {\n    if props.is_null() || value_nul.is_empty() {\n        return;\n    }\n\n    let mut write = PropertyWrite {\n        prop,\n        write_type: PROPERTY_WRITE_SET,\n        set_error: 0,\n        pv_buffer: value_nul.as_ptr().cast_mut().cast(),\n        un_buffer_size: value_nul.len() as u32,\n        un_tag: K_UN_STRING_PROPERTY_TAG,\n        e_error: 0,\n    };\n\n    unsafe {\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\n    }\n}\n\nfn register_tracked_device_if_needed(device_ptr: *mut c_void) {\n    if DEVICE_REGISTERED.load(Ordering::SeqCst) {\n        return;\n    }\n\n    let Some(host) = get_server_host() else {\n        return;\n    };\n\n    let added = unsafe {\n        ((*(*host).vtable).tracked_device_added)(\n            host,\n            DEVICE_SERIAL_CSTR.as_ptr().cast(),\n            TRACKED_DEVICE_CLASS_CONTROLLER,\n            device_ptr,\n        )\n    };\n\n    if added {\n        DEVICE_REGISTERED.store(true, Ordering::SeqCst);\n    }\n}\n\npub fn initialize(driver_context: *mut c_void, device_ptr: *mut c_void) -> bool {\n    if driver_context.is_null() {\n        return false;\n    }\n\n    let ctx = driver_context.cast::<DriverContext>();\n    let mut err = 0;\n\n    let get_iface = unsafe { (*(*ctx).vtable).get_generic_interface };\n\n    let host = get_iface(\n        ctx,\n        IVR_SERVER_DRIVER_HOST_VERSION.as_ptr().cast(),\n        &mut err as *mut EvRInitError,\n    );\n\n    let driver_input = get_iface(\n        ctx,\n        IVR_DRIVER_INPUT_VERSION.as_ptr().cast(),\n        &mut err as *mut EvRInitError,\n    );\n\n    let properties = get_iface(\n        ctx,\n        IVR_PROPERTIES_VERSION.as_ptr().cast(),\n        &mut err as *mut EvRInitError,\n    );\n\n    SERVER_HOST_IFACE.store(host, Ordering::SeqCst);\n    DRIVER_INPUT_IFACE.store(driver_input, Ordering::SeqCst);\n    PROPERTIES_IFACE.store(properties, Ordering::SeqCst);\n\n    let ok = !host.is_null() && !driver_input.is_null() && !properties.is_null();\n    if !ok {\n        return false;\n    }\n\n    register_tracked_device_if_needed(device_ptr);\n    true\n}\n\npub fn cleanup() {\n    DEVICE_REGISTERED.store(false, Ordering::SeqCst);\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\n    SERVER_HOST_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\n    DRIVER_INPUT_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\n    PROPERTIES_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\n}\n\npub fn route_haptic_events() {\n    let Some(host) = get_server_host() else {\n        return;\n    };\n\n    loop {\n        let mut event = VREvent::default();\n        let ok = unsafe {\n            ((*(*host).vtable).poll_next_event)(\n                host,\n                &mut event as *mut VREvent,\n                core::mem::size_of::<VREvent>() as u32,\n            )\n        };\n\n        if !ok {\n            break;\n        }\n\n        if event.event_type != VREVENT_INPUT_HAPTIC_VIBRATION {\n            continue;\n        }\n\n        let hv = unsafe { event.data.haptic_vibration };\n\n        let container = DEVICE_CONTAINER.load(Ordering::SeqCst);\n        if container != 0 && hv.container_handle != 0 && hv.container_handle != container {\n            continue;\n        }\n\n        let haptic_component = HAPTIC_COMPONENT_HANDLE.load(Ordering::SeqCst);\n        if haptic_component != 0 && hv.component_handle != 0 && hv.component_handle != haptic_component {\n            continue;\n        }\n\n        haptics::handle_haptic_request(haptics::HapticVibrationRequest {\n            amplitude: hv.amplitude,\n            duration_seconds: hv.duration_seconds,\n            frequency: hv.frequency,\n        });\n    }\n}\n\npub fn activate_device(object_id: u32) -> bool {\n    let Some(props) = get_properties() else {\n        return false;\n    };\n\n    let container = unsafe { ((*(*props).vtable).tracked_device_to_property_container)(props, object_id) };\n    DEVICE_CONTAINER.store(container, Ordering::SeqCst);\n\n    write_string_property(props, container, PROP_MODEL_NUMBER_STRING, DEVICE_MODEL_CSTR);\n    write_string_property(props, container, PROP_SERIAL_NUMBER_STRING, DEVICE_SERIAL_CSTR);\n    write_string_property(\n        props,\n        container,\n        PROP_INPUT_PROFILE_PATH_STRING,\n        DEVICE_INPUT_PROFILE_CSTR,\n    );\n    write_string_property(\n        props,\n        container,\n        PROP_CONTROLLER_TYPE_STRING,\n        DEVICE_CONTROLLER_TYPE_CSTR,\n    );\n\n    if let Some(input) = get_driver_input() {\n        let mut handle = 0_u64;\n        let rc = unsafe {\n            ((*(*input).vtable).create_haptic_component)(\n                input,\n                container,\n                DEVICE_HAPTIC_PATH_CSTR.as_ptr().cast(),\n                &mut handle as *mut u64,\n            )\n        };\n\n        if rc == 0 {\n            HAPTIC_COMPONENT_HANDLE.store(handle, Ordering::SeqCst);\n        }\n    }\n\n    true\n}\n\npub fn deactivate_device() {\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\n}\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":1,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_cd9235f7-4b6a-4823-ab9f-74e85fd0e64c","epoch":1527,"edits":[{"text":"//! OpenVR ABI/vtable scaffolding for the Big Haptic Driver.\r\n//!\r\n//! This module owns C ABI exports and interface routing for SteamVR-facing\r\n//! provider/device entry points, while delegating runtime integration\n//! (device registration, component setup, and event routing) to\n//! `openvr_runtime`.\n\r\nuse core::ffi::{c_void, CStr};\r\nuse core::sync::atomic::{AtomicBool, AtomicU32, Ordering};\nuse std::os::raw::c_char;\r\n\r\nuse crate::haptics;\r\nuse crate::openvr_runtime;\n\r\nconst SERVER_PROVIDER_INTERFACE: &str = \"IServerTrackedDeviceProvider_004\";\r\nconst SERVER_PROVIDER_INTERFACE_ALT: &str = \"IServerTrackedDeviceProvider_005\";\r\nconst TRACKED_DEVICE_INTERFACE: &str = \"ITrackedDeviceServerDriver_005\";\r\nconst VR_INIT_ERROR_DRIVER_FAILED: EvRInitError = 200;\r\n\r\n#[repr(i32)]\r\nenum DriverReturnCode {\r\n    None = 0,\r\n    Unknown = 1,\r\n    InitInterfaceNotFound = 105,\r\n}\r\n\r\ntype EvRInitError = i32;\r\nconst VR_INIT_ERROR_NONE: EvRInitError = 0;\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nstruct DriverPose {\r\n    _reserved: [u8; 128],\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerTrackedDeviceProviderVTable {\r\n    init: extern \"C\" fn(*mut ServerTrackedDeviceProvider, *mut c_void) -> EvRInitError,\r\n    cleanup: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    get_interface_versions:\r\n        extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> *const *const c_char,\r\n    run_frame: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    should_block_standby_mode: extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> bool,\r\n    enter_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    leave_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n}\r\n\r\n#[repr(C)]\r\nstruct TrackedDeviceServerDriverVTable {\r\n    activate: extern \"C\" fn(*mut TrackedDeviceServerDriver, u32) -> EvRInitError,\r\n    deactivate: extern \"C\" fn(*mut TrackedDeviceServerDriver),\r\n    enter_standby: extern \"C\" fn(*mut TrackedDeviceServerDriver),\r\n    get_component:\r\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char) -> *mut c_void,\r\n    debug_request:\r\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char, *mut c_char, u32),\r\n    get_pose: extern \"C\" fn(*mut TrackedDeviceServerDriver) -> DriverPose,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerTrackedDeviceProvider {\r\n    vtable: *const ServerTrackedDeviceProviderVTable,\r\n}\r\nunsafe impl Sync for ServerTrackedDeviceProvider {}\r\n\r\n#[repr(C)]\r\nstruct TrackedDeviceServerDriver {\r\n    vtable: *const TrackedDeviceServerDriverVTable,\r\n}\r\nunsafe impl Sync for TrackedDeviceServerDriver {}\r\n\r\nstatic DEVICE_INDEX: AtomicU32 = AtomicU32::new(u32::MAX);\r\nstatic DEVICE_ACTIVE: AtomicBool = AtomicBool::new(false);\r\n\r\nextern \"C\" fn provider_init(\r\n    _this: *mut ServerTrackedDeviceProvider,\r\n    driver_context: *mut c_void,\r\n) -> EvRInitError {\r\n    let ok = openvr_runtime::initialize(\n        driver_context,\n        (&DEVICE as *const TrackedDeviceServerDriver).cast_mut().cast(),\n    );\n    if !ok {\n        return VR_INIT_ERROR_DRIVER_FAILED;\r\n    }\r\n\r\n    VR_INIT_ERROR_NONE\r\n}\r\n\r\nextern \"C\" fn provider_cleanup(_this: *mut ServerTrackedDeviceProvider) {\r\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\r\n    openvr_runtime::cleanup();\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn provider_get_interface_versions(\r\n    _this: *mut ServerTrackedDeviceProvider,\r\n) -> *const *const c_char {\r\n    openvr_runtime::interface_versions_ptr()\n}\r\n\r\nextern \"C\" fn provider_run_frame(_this: *mut ServerTrackedDeviceProvider) {\r\n    openvr_runtime::route_haptic_events();\n    haptics::on_frame();\r\n}\r\n\r\nextern \"C\" fn provider_should_block_standby_mode(_this: *mut ServerTrackedDeviceProvider) -> bool {\r\n    false\r\n}\r\n\r\nextern \"C\" fn provider_enter_standby(_this: *mut ServerTrackedDeviceProvider) {}\r\n\r\nextern \"C\" fn provider_leave_standby(_this: *mut ServerTrackedDeviceProvider) {}\r\n\r\nextern \"C\" fn device_activate(_this: *mut TrackedDeviceServerDriver, object_id: u32) -> EvRInitError {\r\n    DEVICE_INDEX.store(object_id, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(true, Ordering::SeqCst);\r\n\r\n    if !openvr_runtime::activate_device(object_id) {\n        return VR_INIT_ERROR_DRIVER_FAILED;\r\n    }\r\n\r\n    VR_INIT_ERROR_NONE\r\n}\r\n\r\nextern \"C\" fn device_deactivate(_this: *mut TrackedDeviceServerDriver) {\r\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\r\n    openvr_runtime::deactivate_device();\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn device_enter_standby(_this: *mut TrackedDeviceServerDriver) {\r\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn device_get_component(\r\n    _this: *mut TrackedDeviceServerDriver,\r\n    _component_name: *const c_char,\r\n) -> *mut c_void {\r\n    core::ptr::null_mut()\r\n}\r\n\r\nextern \"C\" fn device_debug_request(\r\n    _this: *mut TrackedDeviceServerDriver,\r\n    _request: *const c_char,\r\n    response: *mut c_char,\r\n    response_size: u32,\r\n) {\r\n    if response.is_null() || response_size == 0 {\r\n        return;\r\n    }\r\n    unsafe {\r\n        *response = 0;\r\n    }\r\n}\r\n\r\nextern \"C\" fn device_get_pose(_this: *mut TrackedDeviceServerDriver) -> DriverPose {\r\n    DriverPose { _reserved: [0; 128] }\r\n}\r\n\r\nstatic PROVIDER_VTABLE: ServerTrackedDeviceProviderVTable = ServerTrackedDeviceProviderVTable {\r\n    init: provider_init,\r\n    cleanup: provider_cleanup,\r\n    get_interface_versions: provider_get_interface_versions,\r\n    run_frame: provider_run_frame,\r\n    should_block_standby_mode: provider_should_block_standby_mode,\r\n    enter_standby: provider_enter_standby,\r\n    leave_standby: provider_leave_standby,\r\n};\r\n\r\nstatic DEVICE_VTABLE: TrackedDeviceServerDriverVTable = TrackedDeviceServerDriverVTable {\r\n    activate: device_activate,\r\n    deactivate: device_deactivate,\r\n    enter_standby: device_enter_standby,\r\n    get_component: device_get_component,\r\n    debug_request: device_debug_request,\r\n    get_pose: device_get_pose,\r\n};\r\n\r\nstatic PROVIDER: ServerTrackedDeviceProvider = ServerTrackedDeviceProvider {\r\n    vtable: &PROVIDER_VTABLE,\r\n};\r\n\r\nstatic DEVICE: TrackedDeviceServerDriver = TrackedDeviceServerDriver {\r\n    vtable: &DEVICE_VTABLE,\r\n};\r\n\r\nfn set_return_code(out: *mut i32, code: DriverReturnCode) {\r\n    if !out.is_null() {\r\n        unsafe {\r\n            *out = code as i32;\r\n        }\r\n    }\r\n}\r\n\r\nfn read_interface_name(ptr: *const i8) -> Option<&'static str> {\r\n    if ptr.is_null() {\r\n        return None;\r\n    }\r\n\r\n    let cstr = unsafe { CStr::from_ptr(ptr) };\r\n    let Ok(name) = cstr.to_str() else {\r\n        return None;\r\n    };\r\n\r\n    if name == SERVER_PROVIDER_INTERFACE {\r\n        return Some(SERVER_PROVIDER_INTERFACE);\r\n    }\r\n    if name == SERVER_PROVIDER_INTERFACE_ALT {\r\n        return Some(SERVER_PROVIDER_INTERFACE);\r\n    }\r\n    if name == TRACKED_DEVICE_INTERFACE {\r\n        return Some(TRACKED_DEVICE_INTERFACE);\r\n    }\r\n\r\n    None\r\n}\r\n\r\n/// Resolves OpenVR interface requests to static provider/device instances.\r\n///\r\n/// On match, writes `DriverReturnCode::None` to `p_return_code` and returns a\r\n/// stable interface pointer. On failure, writes an error code and returns null.\r\npub fn hmd_driver_factory(\r\n    p_interface_name: *const c_char,\r\n    p_return_code: *mut i32,\r\n) -> *mut c_void {\r\n    let Some(interface_name) = read_interface_name(p_interface_name) else {\r\n        set_return_code(p_return_code, DriverReturnCode::InitInterfaceNotFound);\r\n        return core::ptr::null_mut();\r\n    };\r\n\r\n    if interface_name == SERVER_PROVIDER_INTERFACE {\r\n        set_return_code(p_return_code, DriverReturnCode::None);\r\n        return (&PROVIDER as *const ServerTrackedDeviceProvider)\r\n            .cast_mut()\r\n            .cast();\r\n    }\r\n\r\n    if interface_name == TRACKED_DEVICE_INTERFACE {\r\n        set_return_code(p_return_code, DriverReturnCode::None);\r\n        return (&DEVICE as *const TrackedDeviceServerDriver)\r\n            .cast_mut()\r\n            .cast();\r\n    }\r\n\r\n    set_return_code(p_return_code, DriverReturnCode::Unknown);\r\n    core::ptr::null_mut()\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":252,"endColumn":1}},{"text":"","range":{"startLineNumber":252,"startColumn":1,"endLineNumber":599,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/lib.rs","scheme":"file"},"requestId":"request_cd9235f7-4b6a-4823-ab9f-74e85fd0e64c","epoch":1530,"edits":[{"text":"//! Big Haptic Driver crate entrypoints.\r\n//!\r\n//! `lib.rs` stays intentionally thin: it exposes C ABI exports and delegates\r\n//! implementation details to focused modules.\r\n\r\nuse core::ffi::c_void;\r\nuse std::os::raw::c_char;\r\n\r\nmod haptics;\r\nmod openvr_runtime;\nmod openvr_scaffold;\r\n\r\npub use haptics::{HapticVibrationRequest, PollHapticCallback};\r\n\r\n/// OpenVR factory entrypoint expected by SteamVR.\r\n///\r\n/// Routes interface-name requests to the internal provider/device singletons\r\n/// and writes an OpenVR-style init return code to `p_return_code`.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn HmdDriverFactory(\r\n    p_interface_name: *const c_char,\r\n    p_return_code: *mut i32,\r\n) -> *mut c_void {\r\n    openvr_scaffold::hmd_driver_factory(p_interface_name, p_return_code)\r\n}\r\n\r\n/// Test-only export for early integration while full OpenVR vtable glue is in progress.\r\n/// Returns 0 on success, non-zero on failure.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_SendAmplitude(amplitude: f32) -> i32 {\r\n    match haptics::send_amplitude(amplitude) {\r\n        Ok(()) => 0,\r\n        Err(_) => 1,\r\n    }\r\n}\r\n\r\n/// Explicit stop command helper for testing and recovery.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_Stop() -> i32 {\r\n    match haptics::send_stop() {\r\n        Ok(()) => 0,\r\n        Err(_) => 1,\r\n    }\r\n}\r\n\r\n/// Temporary bridge: host/OpenVR shim can register a callback polled each frame.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_SetPollHapticCallback(cb: Option<PollHapticCallback>) {\r\n    haptics::set_poll_haptic_callback(cb);\r\n}\r\n\r\n/// Temporary direct injection helper while event wiring is being built.\r\n#[unsafe(no_mangle)]\r\npub extern \"C\" fn BigHapticDriver_InjectHaptic(\r\n    amplitude: f32,\r\n    duration_seconds: f32,\r\n    frequency: f32,\r\n) {\r\n    haptics::handle_haptic_request(HapticVibrationRequest {\r\n        amplitude,\r\n        duration_seconds,\r\n        frequency,\r\n    });\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":66,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_9bcbe4ab-720b-4660-919b-ab4be1cdb158","epoch":1534,"edits":[{"text":"//! OpenVR ABI/vtable scaffolding for the Big Haptic Driver.\r\n//!\r\n//! This module owns C ABI exports and interface routing for SteamVR-facing\r\n//! provider/device entry points, while delegating runtime integration\r\n//! (device registration, component setup, and event routing) to\r\n//! `openvr_runtime`.\r\n\r\nuse core::ffi::{c_void, CStr};\r\nuse core::sync::atomic::{AtomicBool, AtomicU32, Ordering};\r\nuse std::os::raw::c_char;\r\n\r\nuse crate::haptics;\r\nuse crate::openvr_runtime;\r\n\r\nconst SERVER_PROVIDER_INTERFACE: &str = \"IServerTrackedDeviceProvider_004\";\r\nconst SERVER_PROVIDER_INTERFACE_ALT: &str = \"IServerTrackedDeviceProvider_005\";\r\nconst TRACKED_DEVICE_INTERFACE: &str = \"ITrackedDeviceServerDriver_005\";\r\nconst VR_INIT_ERROR_DRIVER_FAILED: EvRInitError = 200;\r\n\r\n#[repr(i32)]\r\nenum DriverReturnCode {\r\n    None = 0,\r\n    Unknown = 1,\r\n    InitInterfaceNotFound = 105,\r\n}\r\n\r\ntype EvRInitError = i32;\r\nconst VR_INIT_ERROR_NONE: EvRInitError = 0;\r\nconst TRACKING_RESULT_RUNNING_OK: i32 = 200;\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nstruct DriverPoseQuaternion {\n    w: f64,\n    x: f64,\n    y: f64,\n    z: f64,\n}\n\n#[repr(C)]\n#[derive(Clone, Copy)]\nstruct DriverPose {\n    pose_time_offset: f64,\n    q_world_from_driver_rotation: DriverPoseQuaternion,\n    vec_world_from_driver_translation: [f64; 3],\n    q_driver_from_head_rotation: DriverPoseQuaternion,\n    vec_driver_from_head_translation: [f64; 3],\n    vec_position: [f64; 3],\n    vec_velocity: [f64; 3],\n    vec_acceleration: [f64; 3],\n    q_rotation: DriverPoseQuaternion,\n    vec_angular_velocity: [f64; 3],\n    vec_angular_acceleration: [f64; 3],\n    result: i32,\n    pose_is_valid: bool,\n    will_drift_in_yaw: bool,\n    should_apply_head_model: bool,\n    device_is_connected: bool,\n}\r\n\nconst IDENTITY_QUATERNION: DriverPoseQuaternion = DriverPoseQuaternion {\n    w: 1.0,\n    x: 0.0,\n    y: 0.0,\n    z: 0.0,\n};\n\nfn connected_controller_pose() -> DriverPose {\n    DriverPose {\n        pose_time_offset: 0.0,\n        q_world_from_driver_rotation: IDENTITY_QUATERNION,\n        vec_world_from_driver_translation: [0.0, 0.0, 0.0],\n        q_driver_from_head_rotation: IDENTITY_QUATERNION,\n        vec_driver_from_head_translation: [0.0, 0.0, 0.0],\n        vec_position: [0.0, 1.2, -0.4],\n        vec_velocity: [0.0, 0.0, 0.0],\n        vec_acceleration: [0.0, 0.0, 0.0],\n        q_rotation: IDENTITY_QUATERNION,\n        vec_angular_velocity: [0.0, 0.0, 0.0],\n        vec_angular_acceleration: [0.0, 0.0, 0.0],\n        result: TRACKING_RESULT_RUNNING_OK,\n        pose_is_valid: true,\n        will_drift_in_yaw: false,\n        should_apply_head_model: false,\n        device_is_connected: true,\n    }\n}\n\r\n#[repr(C)]\r\nstruct ServerTrackedDeviceProviderVTable {\r\n    init: extern \"C\" fn(*mut ServerTrackedDeviceProvider, *mut c_void) -> EvRInitError,\r\n    cleanup: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    get_interface_versions:\r\n        extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> *const *const c_char,\r\n    run_frame: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    should_block_standby_mode: extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> bool,\r\n    enter_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    leave_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n}\r\n\r\n#[repr(C)]\r\nstruct TrackedDeviceServerDriverVTable {\r\n    activate: extern \"C\" fn(*mut TrackedDeviceServerDriver, u32) -> EvRInitError,\r\n    deactivate: extern \"C\" fn(*mut TrackedDeviceServerDriver),\r\n    enter_standby: extern \"C\" fn(*mut TrackedDeviceServerDriver),\r\n    get_component:\r\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char) -> *mut c_void,\r\n    debug_request:\r\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char, *mut c_char, u32),\r\n    get_pose: extern \"C\" fn(*mut TrackedDeviceServerDriver) -> DriverPose,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerTrackedDeviceProvider {\r\n    vtable: *const ServerTrackedDeviceProviderVTable,\r\n}\r\nunsafe impl Sync for ServerTrackedDeviceProvider {}\r\n\r\n#[repr(C)]\r\nstruct TrackedDeviceServerDriver {\r\n    vtable: *const TrackedDeviceServerDriverVTable,\r\n}\r\nunsafe impl Sync for TrackedDeviceServerDriver {}\r\n\r\nstatic DEVICE_INDEX: AtomicU32 = AtomicU32::new(u32::MAX);\r\nstatic DEVICE_ACTIVE: AtomicBool = AtomicBool::new(false);\r\n\r\nextern \"C\" fn provider_init(\r\n    _this: *mut ServerTrackedDeviceProvider,\r\n    driver_context: *mut c_void,\r\n) -> EvRInitError {\r\n    let ok = openvr_runtime::initialize(\r\n        driver_context,\r\n        (&DEVICE as *const TrackedDeviceServerDriver).cast_mut().cast(),\r\n    );\r\n    if !ok {\r\n        return VR_INIT_ERROR_DRIVER_FAILED;\r\n    }\r\n\r\n    VR_INIT_ERROR_NONE\r\n}\r\n\r\nextern \"C\" fn provider_cleanup(_this: *mut ServerTrackedDeviceProvider) {\r\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\r\n    openvr_runtime::cleanup();\r\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn provider_get_interface_versions(\r\n    _this: *mut ServerTrackedDeviceProvider,\r\n) -> *const *const c_char {\r\n    openvr_runtime::interface_versions_ptr()\r\n}\r\n\r\nextern \"C\" fn provider_run_frame(_this: *mut ServerTrackedDeviceProvider) {\r\n    if DEVICE_ACTIVE.load(Ordering::SeqCst) {\n        let object_id = DEVICE_INDEX.load(Ordering::SeqCst);\n        if object_id != u32::MAX {\n            let pose = connected_controller_pose();\n            openvr_runtime::push_pose_update(\n                object_id,\n                (&pose as *const DriverPose).cast(),\n                core::mem::size_of::<DriverPose>() as u32,\n            );\n        }\n    }\n\n    openvr_runtime::route_haptic_events();\r\n    haptics::on_frame();\r\n}\r\n\r\nextern \"C\" fn provider_should_block_standby_mode(_this: *mut ServerTrackedDeviceProvider) -> bool {\r\n    false\r\n}\r\n\r\nextern \"C\" fn provider_enter_standby(_this: *mut ServerTrackedDeviceProvider) {}\r\n\r\nextern \"C\" fn provider_leave_standby(_this: *mut ServerTrackedDeviceProvider) {}\r\n\r\nextern \"C\" fn device_activate(_this: *mut TrackedDeviceServerDriver, object_id: u32) -> EvRInitError {\r\n    DEVICE_INDEX.store(object_id, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(true, Ordering::SeqCst);\r\n\r\n    if !openvr_runtime::activate_device(object_id) {\r\n        return VR_INIT_ERROR_DRIVER_FAILED;\r\n    }\r\n\n    let pose = connected_controller_pose();\n    openvr_runtime::push_pose_update(\n        object_id,\n        (&pose as *const DriverPose).cast(),\n        core::mem::size_of::<DriverPose>() as u32,\n    );\n\r\n    VR_INIT_ERROR_NONE\r\n}\r\n\r\nextern \"C\" fn device_deactivate(_this: *mut TrackedDeviceServerDriver) {\r\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\r\n    openvr_runtime::deactivate_device();\r\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn device_enter_standby(_this: *mut TrackedDeviceServerDriver) {\r\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn device_get_component(\r\n    _this: *mut TrackedDeviceServerDriver,\r\n    _component_name: *const c_char,\r\n) -> *mut c_void {\r\n    core::ptr::null_mut()\r\n}\r\n\r\nextern \"C\" fn device_debug_request(\r\n    _this: *mut TrackedDeviceServerDriver,\r\n    _request: *const c_char,\r\n    response: *mut c_char,\r\n    response_size: u32,\r\n) {\r\n    if response.is_null() || response_size == 0 {\r\n        return;\r\n    }\r\n    unsafe {\r\n        *response = 0;\r\n    }\r\n}\r\n\r\nextern \"C\" fn device_get_pose(_this: *mut TrackedDeviceServerDriver) -> DriverPose {\r\n    connected_controller_pose()\n}\r\n\r\nstatic PROVIDER_VTABLE: ServerTrackedDeviceProviderVTable = ServerTrackedDeviceProviderVTable {\r\n    init: provider_init,\r\n    cleanup: provider_cleanup,\r\n    get_interface_versions: provider_get_interface_versions,\r\n    run_frame: provider_run_frame,\r\n    should_block_standby_mode: provider_should_block_standby_mode,\r\n    enter_standby: provider_enter_standby,\r\n    leave_standby: provider_leave_standby,\r\n};\r\n\r\nstatic DEVICE_VTABLE: TrackedDeviceServerDriverVTable = TrackedDeviceServerDriverVTable {\r\n    activate: device_activate,\r\n    deactivate: device_deactivate,\r\n    enter_standby: device_enter_standby,\r\n    get_component: device_get_component,\r\n    debug_request: device_debug_request,\r\n    get_pose: device_get_pose,\r\n};\r\n\r\nstatic PROVIDER: ServerTrackedDeviceProvider = ServerTrackedDeviceProvider {\r\n    vtable: &PROVIDER_VTABLE,\r\n};\r\n\r\nstatic DEVICE: TrackedDeviceServerDriver = TrackedDeviceServerDriver {\r\n    vtable: &DEVICE_VTABLE,\r\n};\r\n\r\nfn set_return_code(out: *mut i32, code: DriverReturnCode) {\r\n    if !out.is_null() {\r\n        unsafe {\r\n            *out = code as i32;\r\n        }\r\n    }\r\n}\r\n\r\nfn read_interface_name(ptr: *const i8) -> Option<&'static str> {\r\n    if ptr.is_null() {\r\n        return None;\r\n    }\r\n\r\n    let cstr = unsafe { CStr::from_ptr(ptr) };\r\n    let Ok(name) = cstr.to_str() else {\r\n        return None;\r\n    };\r\n\r\n    if name == SERVER_PROVIDER_INTERFACE {\r\n        return Some(SERVER_PROVIDER_INTERFACE);\r\n    }\r\n    if name == SERVER_PROVIDER_INTERFACE_ALT {\r\n        return Some(SERVER_PROVIDER_INTERFACE);\r\n    }\r\n    if name == TRACKED_DEVICE_INTERFACE {\r\n        return Some(TRACKED_DEVICE_INTERFACE);\r\n    }\r\n\r\n    None\r\n}\r\n\r\n/// Resolves OpenVR interface requests to static provider/device instances.\r\n///\r\n/// On match, writes `DriverReturnCode::None` to `p_return_code` and returns a\r\n/// stable interface pointer. On failure, writes an error code and returns null.\r\npub fn hmd_driver_factory(\r\n    p_interface_name: *const c_char,\r\n    p_return_code: *mut i32,\r\n) -> *mut c_void {\r\n    let Some(interface_name) = read_interface_name(p_interface_name) else {\r\n        set_return_code(p_return_code, DriverReturnCode::InitInterfaceNotFound);\r\n        return core::ptr::null_mut();\r\n    };\r\n\r\n    if interface_name == SERVER_PROVIDER_INTERFACE {\r\n        set_return_code(p_return_code, DriverReturnCode::None);\r\n        return (&PROVIDER as *const ServerTrackedDeviceProvider)\r\n            .cast_mut()\r\n            .cast();\r\n    }\r\n\r\n    if interface_name == TRACKED_DEVICE_INTERFACE {\r\n        set_return_code(p_return_code, DriverReturnCode::None);\r\n        return (&DEVICE as *const TrackedDeviceServerDriver)\r\n            .cast_mut()\r\n            .cast();\r\n    }\r\n\r\n    set_return_code(p_return_code, DriverReturnCode::Unknown);\r\n    core::ptr::null_mut()\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":324,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\openvr_runtime.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","scheme":"file"},"requestId":"request_9bcbe4ab-720b-4660-919b-ab4be1cdb158","epoch":1537,"edits":[{"text":"//! OpenVR runtime integration helpers.\r\n//!\r\n//! This module owns driver-context interface resolution, tracked-device\r\n//! registration, component/property setup, and OpenVR haptic event routing.\r\n\r\nuse core::ffi::c_void;\r\nuse core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU64, Ordering};\r\nuse std::os::raw::c_char;\r\n\r\nuse crate::haptics;\r\n\r\ntype EvRInitError = i32;\r\n\r\nconst IVR_SERVER_DRIVER_HOST_VERSION: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\nconst IVR_DRIVER_INPUT_VERSION: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_PROPERTIES_VERSION: &[u8] = b\"IVRProperties_001\\0\";\r\n\r\nconst TRACKED_DEVICE_CLASS_CONTROLLER: i32 = 2;\r\nconst VREVENT_INPUT_HAPTIC_VIBRATION: u32 = 1700;\r\n\r\nconst PROP_MODEL_NUMBER_STRING: u32 = 1001;\r\nconst PROP_SERIAL_NUMBER_STRING: u32 = 1002;\r\nconst PROP_INPUT_PROFILE_PATH_STRING: u32 = 1037;\r\nconst PROP_CONTROLLER_TYPE_STRING: u32 = 7000;\r\n\r\nconst K_UN_STRING_PROPERTY_TAG: u32 = 5;\r\nconst PROPERTY_WRITE_SET: i32 = 0;\r\n\r\nconst DEVICE_SERIAL_CSTR: &[u8] = b\"qtpy-samd21-haptic-001\\0\";\r\nconst DEVICE_MODEL_CSTR: &[u8] = b\"QT Py SAMD21 Haptic\\0\";\r\nconst DEVICE_CONTROLLER_TYPE_CSTR: &[u8] = b\"big_haptic_driver\\0\";\r\nconst DEVICE_INPUT_PROFILE_CSTR: &[u8] = b\"{big_haptic_driver}/input/big_haptic_profile.json\\0\";\r\nconst DEVICE_HAPTIC_PATH_CSTR: &[u8] = b\"/output/haptic\\0\";\r\n\r\nconst PROVIDER_IFACE_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_004\\0\";\r\nconst PROVIDER_IFACE_ALT_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_005\\0\";\r\nconst DEVICE_IFACE_CSTR: &[u8] = b\"ITrackedDeviceServerDriver_005\\0\";\r\n\r\nconst IVR_PROPERTIES_CSTR: &[u8] = b\"IVRProperties_001\\0\";\r\nconst IVR_DRIVER_INPUT_CSTR: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_SERVER_DRIVER_HOST_CSTR: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\n\r\nconst IVR_SETTINGS_CSTR: &[u8] = b\"IVRSettings_003\\0\";\r\nconst IVR_DRIVER_MANAGER_CSTR: &[u8] = b\"IVRDriverManager_001\\0\";\r\nconst IVR_RESOURCES_CSTR: &[u8] = b\"IVRResources_001\\0\";\r\n\r\n#[repr(C)]\r\nstruct InterfaceVersions([*const c_char; 10]);\r\nunsafe impl Sync for InterfaceVersions {}\r\n\r\nstatic INTERFACE_VERSIONS: InterfaceVersions = InterfaceVersions([\r\n    IVR_SETTINGS_CSTR.as_ptr().cast(),\r\n    DEVICE_IFACE_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_CSTR.as_ptr().cast(),\r\n    IVR_PROPERTIES_CSTR.as_ptr().cast(),\r\n    IVR_SERVER_DRIVER_HOST_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_MANAGER_CSTR.as_ptr().cast(),\r\n    IVR_RESOURCES_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_INPUT_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_ALT_CSTR.as_ptr().cast(),\r\n    core::ptr::null(),\r\n]);\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREventHapticVibration {\r\n    container_handle: u64,\r\n    component_handle: u64,\r\n    duration_seconds: f32,\r\n    frequency: f32,\r\n    amplitude: f32,\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nunion VREventData {\r\n    haptic_vibration: VREventHapticVibration,\r\n    reserved: [u8; 48],\r\n}\r\n\r\nimpl Default for VREventData {\r\n    fn default() -> Self {\r\n        Self { reserved: [0; 48] }\r\n    }\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREvent {\r\n    event_type: u32,\r\n    tracked_device_index: u32,\r\n    event_age_seconds: f32,\r\n    data: VREventData,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContextVTable {\r\n    get_generic_interface:\r\n        extern \"C\" fn(*mut DriverContext, *const c_char, *mut EvRInitError) -> *mut c_void,\r\n    get_driver_handle: extern \"C\" fn(*mut DriverContext) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContext {\r\n    vtable: *const DriverContextVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHostVTable {\r\n    tracked_device_added:\r\n        extern \"C\" fn(*mut ServerDriverHost, *const c_char, i32, *mut c_void) -> bool,\r\n    tracked_device_pose_updated: extern \"C\" fn(*mut ServerDriverHost, u32, *const c_void, u32),\r\n    vsync_event: extern \"C\" fn(*mut ServerDriverHost, f64),\r\n    vendor_specific_event: extern \"C\" fn(*mut ServerDriverHost, u32, u32, *const VREventData, f64),\r\n    is_exiting: extern \"C\" fn(*mut ServerDriverHost) -> bool,\r\n    poll_next_event: extern \"C\" fn(*mut ServerDriverHost, *mut VREvent, u32) -> bool,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHost {\r\n    vtable: *const ServerDriverHostVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverInputVTable {\r\n    create_boolean_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n    update_boolean_component: extern \"C\" fn(*mut DriverInput, u64, bool, f64) -> i32,\r\n    create_scalar_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64, i32, i32) -> i32,\r\n    update_scalar_component: extern \"C\" fn(*mut DriverInput, u64, f32, f64) -> i32,\r\n    create_haptic_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverInput {\r\n    vtable: *const DriverInputVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct PropertiesVTable {\r\n    read_property_batch: extern \"C\" fn(*mut Properties, u64, *mut c_void, u32) -> i32,\r\n    write_property_batch: extern \"C\" fn(*mut Properties, u64, *mut PropertyWrite, u32) -> i32,\r\n    get_prop_error_name_from_enum: extern \"C\" fn(*mut Properties, i32) -> *const c_char,\r\n    tracked_device_to_property_container: extern \"C\" fn(*mut Properties, u32) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct Properties {\r\n    vtable: *const PropertiesVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct PropertyWrite {\r\n    prop: u32,\r\n    write_type: i32,\r\n    set_error: i32,\r\n    pv_buffer: *mut c_void,\r\n    un_buffer_size: u32,\r\n    un_tag: u32,\r\n    e_error: i32,\r\n}\r\n\r\nstatic DEVICE_REGISTERED: AtomicBool = AtomicBool::new(false);\r\n\r\nstatic SERVER_HOST_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DRIVER_INPUT_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic PROPERTIES_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\n\r\nstatic DEVICE_CONTAINER: AtomicU64 = AtomicU64::new(0);\r\nstatic HAPTIC_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\n\r\npub fn interface_versions_ptr() -> *const *const c_char {\r\n    INTERFACE_VERSIONS.0.as_ptr()\r\n}\r\n\r\nfn get_server_host() -> Option<*mut ServerDriverHost> {\r\n    let ptr = SERVER_HOST_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_driver_input() -> Option<*mut DriverInput> {\r\n    let ptr = DRIVER_INPUT_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_properties() -> Option<*mut Properties> {\r\n    let ptr = PROPERTIES_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn write_string_property(props: *mut Properties, container: u64, prop: u32, value_nul: &'static [u8]) {\r\n    if props.is_null() || value_nul.is_empty() {\r\n        return;\r\n    }\r\n\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: value_nul.as_ptr().cast_mut().cast(),\r\n        un_buffer_size: value_nul.len() as u32,\r\n        un_tag: K_UN_STRING_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn register_tracked_device_if_needed(device_ptr: *mut c_void) {\r\n    if DEVICE_REGISTERED.load(Ordering::SeqCst) {\r\n        return;\r\n    }\r\n\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    let added = unsafe {\r\n        ((*(*host).vtable).tracked_device_added)(\r\n            host,\r\n            DEVICE_SERIAL_CSTR.as_ptr().cast(),\r\n            TRACKED_DEVICE_CLASS_CONTROLLER,\r\n            device_ptr,\r\n        )\r\n    };\r\n\r\n    if added {\r\n        DEVICE_REGISTERED.store(true, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\npub fn initialize(driver_context: *mut c_void, device_ptr: *mut c_void) -> bool {\r\n    if driver_context.is_null() {\r\n        return false;\r\n    }\r\n\r\n    let ctx = driver_context.cast::<DriverContext>();\r\n    let mut err = 0;\r\n\r\n    let get_iface = unsafe { (*(*ctx).vtable).get_generic_interface };\r\n\r\n    let host = get_iface(\r\n        ctx,\r\n        IVR_SERVER_DRIVER_HOST_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let driver_input = get_iface(\r\n        ctx,\r\n        IVR_DRIVER_INPUT_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let properties = get_iface(\r\n        ctx,\r\n        IVR_PROPERTIES_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    SERVER_HOST_IFACE.store(host, Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(driver_input, Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(properties, Ordering::SeqCst);\r\n\r\n    let ok = !host.is_null() && !driver_input.is_null() && !properties.is_null();\r\n    if !ok {\r\n        return false;\r\n    }\r\n\r\n    register_tracked_device_if_needed(device_ptr);\r\n    true\r\n}\r\n\r\npub fn cleanup() {\r\n    DEVICE_REGISTERED.store(false, Ordering::SeqCst);\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    SERVER_HOST_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n}\r\n\r\npub fn route_haptic_events() {\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    loop {\r\n        let mut event = VREvent::default();\r\n        let ok = unsafe {\r\n            ((*(*host).vtable).poll_next_event)(\r\n                host,\r\n                &mut event as *mut VREvent,\r\n                core::mem::size_of::<VREvent>() as u32,\r\n            )\r\n        };\r\n\r\n        if !ok {\r\n            break;\r\n        }\r\n\r\n        if event.event_type != VREVENT_INPUT_HAPTIC_VIBRATION {\r\n            continue;\r\n        }\r\n\r\n        let hv = unsafe { event.data.haptic_vibration };\r\n\r\n        let container = DEVICE_CONTAINER.load(Ordering::SeqCst);\r\n        if container != 0 && hv.container_handle != 0 && hv.container_handle != container {\r\n            continue;\r\n        }\r\n\r\n        let haptic_component = HAPTIC_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n        if haptic_component != 0 && hv.component_handle != 0 && hv.component_handle != haptic_component {\r\n            continue;\r\n        }\r\n\r\n        haptics::handle_haptic_request(haptics::HapticVibrationRequest {\r\n            amplitude: hv.amplitude,\r\n            duration_seconds: hv.duration_seconds,\r\n            frequency: hv.frequency,\r\n        });\r\n    }\r\n}\r\n\npub fn push_pose_update(object_id: u32, pose: *const c_void, pose_size: u32) {\n    let Some(host) = get_server_host() else {\n        return;\n    };\n\n    if pose.is_null() || pose_size == 0 {\n        return;\n    }\n\n    unsafe {\n        ((*(*host).vtable).tracked_device_pose_updated)(host, object_id, pose, pose_size);\n    }\n}\n\r\npub fn activate_device(object_id: u32) -> bool {\r\n    let Some(props) = get_properties() else {\r\n        return false;\r\n    };\r\n\r\n    let container = unsafe { ((*(*props).vtable).tracked_device_to_property_container)(props, object_id) };\r\n    DEVICE_CONTAINER.store(container, Ordering::SeqCst);\r\n\r\n    write_string_property(props, container, PROP_MODEL_NUMBER_STRING, DEVICE_MODEL_CSTR);\r\n    write_string_property(props, container, PROP_SERIAL_NUMBER_STRING, DEVICE_SERIAL_CSTR);\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_INPUT_PROFILE_PATH_STRING,\r\n        DEVICE_INPUT_PROFILE_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_CONTROLLER_TYPE_STRING,\r\n        DEVICE_CONTROLLER_TYPE_CSTR,\r\n    );\r\n\r\n    if let Some(input) = get_driver_input() {\r\n        let mut handle = 0_u64;\r\n        let rc = unsafe {\r\n            ((*(*input).vtable).create_haptic_component)(\r\n                input,\r\n                container,\r\n                DEVICE_HAPTIC_PATH_CSTR.as_ptr().cast(),\r\n                &mut handle as *mut u64,\r\n            )\r\n        };\r\n\r\n        if rc == 0 {\r\n            HAPTIC_COMPONENT_HANDLE.store(handle, Ordering::SeqCst);\r\n        }\r\n    }\r\n\r\n    true\r\n}\r\n\r\npub fn deactivate_device() {\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":402,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\driver.vrdrivermanifest","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/driver.vrdrivermanifest","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/driver.vrdrivermanifest","scheme":"file"},"requestId":"request_2b97d49c-137d-4738-aaf4-66378596ea73","epoch":1541,"edits":[{"text":"{\n  \"alwaysActivate\": true,\n  \"name\": \"big_haptic_driver\",\n  \"directory\": \"\",\n  \"resourceOnly\": false,\n  \"hmd_presence\": []\n}\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":9,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\openvr_runtime.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","scheme":"file"},"requestId":"request_2b97d49c-137d-4738-aaf4-66378596ea73","epoch":1544,"edits":[{"text":"//! OpenVR runtime integration helpers.\r\n//!\r\n//! This module owns driver-context interface resolution, tracked-device\r\n//! registration, component/property setup, and OpenVR haptic event routing.\r\n\r\nuse core::ffi::c_void;\r\nuse core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU64, Ordering};\r\nuse std::os::raw::c_char;\r\n\r\nuse crate::haptics;\r\n\r\ntype EvRInitError = i32;\r\n\r\nconst IVR_SERVER_DRIVER_HOST_VERSION: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\nconst IVR_DRIVER_INPUT_VERSION: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_PROPERTIES_VERSION: &[u8] = b\"IVRProperties_001\\0\";\r\n\r\nconst TRACKED_DEVICE_CLASS_CONTROLLER: i32 = 2;\r\nconst VREVENT_INPUT_HAPTIC_VIBRATION: u32 = 1700;\r\n\r\nconst PROP_MODEL_NUMBER_STRING: u32 = 1001;\r\nconst PROP_SERIAL_NUMBER_STRING: u32 = 1002;\r\nconst PROP_TRACKING_SYSTEM_NAME_STRING: u32 = 1000;\nconst PROP_REGISTERED_DEVICE_TYPE_STRING: u32 = 1036;\nconst PROP_INPUT_PROFILE_PATH_STRING: u32 = 1037;\r\nconst PROP_CONTROLLER_TYPE_STRING: u32 = 7000;\r\nconst PROP_CONTROLLER_ROLE_HINT_INT32: u32 = 3007;\nconst PROP_HAS_CONTROLLER_COMPONENT_BOOL: u32 = 6003;\n\r\nconst K_UN_BOOL_PROPERTY_TAG: u32 = 4;\nconst K_UN_INT32_PROPERTY_TAG: u32 = 2;\nconst K_UN_STRING_PROPERTY_TAG: u32 = 5;\r\nconst PROPERTY_WRITE_SET: i32 = 0;\r\n\r\nconst DEVICE_SERIAL_CSTR: &[u8] = b\"qtpy-samd21-haptic-001\\0\";\r\nconst DEVICE_MODEL_CSTR: &[u8] = b\"QT Py SAMD21 Haptic\\0\";\r\nconst DEVICE_TRACKING_SYSTEM_NAME_CSTR: &[u8] = b\"big_haptic_driver\\0\";\nconst DEVICE_REGISTERED_TYPE_CSTR: &[u8] = b\"big_haptic_driver/controller\\0\";\nconst DEVICE_CONTROLLER_TYPE_CSTR: &[u8] = b\"big_haptic_driver\\0\";\r\nconst DEVICE_INPUT_PROFILE_CSTR: &[u8] = b\"{big_haptic_driver}/input/big_haptic_profile.json\\0\";\r\nconst DEVICE_HAPTIC_PATH_CSTR: &[u8] = b\"/output/haptic\\0\";\r\n\r\nconst PROVIDER_IFACE_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_004\\0\";\r\nconst PROVIDER_IFACE_ALT_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_005\\0\";\r\nconst DEVICE_IFACE_CSTR: &[u8] = b\"ITrackedDeviceServerDriver_005\\0\";\r\n\r\nconst IVR_PROPERTIES_CSTR: &[u8] = b\"IVRProperties_001\\0\";\r\nconst IVR_DRIVER_INPUT_CSTR: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_SERVER_DRIVER_HOST_CSTR: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\n\r\nconst IVR_SETTINGS_CSTR: &[u8] = b\"IVRSettings_003\\0\";\r\nconst IVR_DRIVER_MANAGER_CSTR: &[u8] = b\"IVRDriverManager_001\\0\";\r\nconst IVR_RESOURCES_CSTR: &[u8] = b\"IVRResources_001\\0\";\r\n\r\n#[repr(C)]\r\nstruct InterfaceVersions([*const c_char; 10]);\r\nunsafe impl Sync for InterfaceVersions {}\r\n\r\nstatic INTERFACE_VERSIONS: InterfaceVersions = InterfaceVersions([\r\n    IVR_SETTINGS_CSTR.as_ptr().cast(),\r\n    DEVICE_IFACE_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_CSTR.as_ptr().cast(),\r\n    IVR_PROPERTIES_CSTR.as_ptr().cast(),\r\n    IVR_SERVER_DRIVER_HOST_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_MANAGER_CSTR.as_ptr().cast(),\r\n    IVR_RESOURCES_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_INPUT_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_ALT_CSTR.as_ptr().cast(),\r\n    core::ptr::null(),\r\n]);\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREventHapticVibration {\r\n    container_handle: u64,\r\n    component_handle: u64,\r\n    duration_seconds: f32,\r\n    frequency: f32,\r\n    amplitude: f32,\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nunion VREventData {\r\n    haptic_vibration: VREventHapticVibration,\r\n    reserved: [u8; 48],\r\n}\r\n\r\nimpl Default for VREventData {\r\n    fn default() -> Self {\r\n        Self { reserved: [0; 48] }\r\n    }\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREvent {\r\n    event_type: u32,\r\n    tracked_device_index: u32,\r\n    event_age_seconds: f32,\r\n    data: VREventData,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContextVTable {\r\n    get_generic_interface:\r\n        extern \"C\" fn(*mut DriverContext, *const c_char, *mut EvRInitError) -> *mut c_void,\r\n    get_driver_handle: extern \"C\" fn(*mut DriverContext) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContext {\r\n    vtable: *const DriverContextVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHostVTable {\r\n    tracked_device_added:\r\n        extern \"C\" fn(*mut ServerDriverHost, *const c_char, i32, *mut c_void) -> bool,\r\n    tracked_device_pose_updated: extern \"C\" fn(*mut ServerDriverHost, u32, *const c_void, u32),\r\n    vsync_event: extern \"C\" fn(*mut ServerDriverHost, f64),\r\n    vendor_specific_event: extern \"C\" fn(*mut ServerDriverHost, u32, u32, *const VREventData, f64),\r\n    is_exiting: extern \"C\" fn(*mut ServerDriverHost) -> bool,\r\n    poll_next_event: extern \"C\" fn(*mut ServerDriverHost, *mut VREvent, u32) -> bool,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHost {\r\n    vtable: *const ServerDriverHostVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverInputVTable {\r\n    create_boolean_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n    update_boolean_component: extern \"C\" fn(*mut DriverInput, u64, bool, f64) -> i32,\r\n    create_scalar_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64, i32, i32) -> i32,\r\n    update_scalar_component: extern \"C\" fn(*mut DriverInput, u64, f32, f64) -> i32,\r\n    create_haptic_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverInput {\r\n    vtable: *const DriverInputVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct PropertiesVTable {\r\n    read_property_batch: extern \"C\" fn(*mut Properties, u64, *mut c_void, u32) -> i32,\r\n    write_property_batch: extern \"C\" fn(*mut Properties, u64, *mut PropertyWrite, u32) -> i32,\r\n    get_prop_error_name_from_enum: extern \"C\" fn(*mut Properties, i32) -> *const c_char,\r\n    tracked_device_to_property_container: extern \"C\" fn(*mut Properties, u32) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct Properties {\r\n    vtable: *const PropertiesVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct PropertyWrite {\r\n    prop: u32,\r\n    write_type: i32,\r\n    set_error: i32,\r\n    pv_buffer: *mut c_void,\r\n    un_buffer_size: u32,\r\n    un_tag: u32,\r\n    e_error: i32,\r\n}\r\n\r\nstatic DEVICE_REGISTERED: AtomicBool = AtomicBool::new(false);\r\n\r\nstatic SERVER_HOST_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DRIVER_INPUT_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic PROPERTIES_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\n\r\nstatic DEVICE_CONTAINER: AtomicU64 = AtomicU64::new(0);\r\nstatic HAPTIC_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\n\r\npub fn interface_versions_ptr() -> *const *const c_char {\r\n    INTERFACE_VERSIONS.0.as_ptr()\r\n}\r\n\r\nfn get_server_host() -> Option<*mut ServerDriverHost> {\r\n    let ptr = SERVER_HOST_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_driver_input() -> Option<*mut DriverInput> {\r\n    let ptr = DRIVER_INPUT_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_properties() -> Option<*mut Properties> {\r\n    let ptr = PROPERTIES_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn write_string_property(props: *mut Properties, container: u64, prop: u32, value_nul: &'static [u8]) {\r\n    if props.is_null() || value_nul.is_empty() {\r\n        return;\r\n    }\r\n\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: value_nul.as_ptr().cast_mut().cast(),\r\n        un_buffer_size: value_nul.len() as u32,\r\n        un_tag: K_UN_STRING_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\nfn write_bool_property(props: *mut Properties, container: u64, prop: u32, value: bool) {\n    if props.is_null() {\n        return;\n    }\n\n    let mut bool_value = value;\n    let mut write = PropertyWrite {\n        prop,\n        write_type: PROPERTY_WRITE_SET,\n        set_error: 0,\n        pv_buffer: (&mut bool_value as *mut bool).cast(),\n        un_buffer_size: core::mem::size_of::<bool>() as u32,\n        un_tag: K_UN_BOOL_PROPERTY_TAG,\n        e_error: 0,\n    };\n\n    unsafe {\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\n    }\n}\n\nfn write_int32_property(props: *mut Properties, container: u64, prop: u32, value: i32) {\n    if props.is_null() {\n        return;\n    }\n\n    let mut int_value = value;\n    let mut write = PropertyWrite {\n        prop,\n        write_type: PROPERTY_WRITE_SET,\n        set_error: 0,\n        pv_buffer: (&mut int_value as *mut i32).cast(),\n        un_buffer_size: core::mem::size_of::<i32>() as u32,\n        un_tag: K_UN_INT32_PROPERTY_TAG,\n        e_error: 0,\n    };\n\n    unsafe {\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\n    }\n}\n\r\nfn register_tracked_device_if_needed(device_ptr: *mut c_void) {\r\n    if DEVICE_REGISTERED.load(Ordering::SeqCst) {\r\n        return;\r\n    }\r\n\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    let added = unsafe {\r\n        ((*(*host).vtable).tracked_device_added)(\r\n            host,\r\n            DEVICE_SERIAL_CSTR.as_ptr().cast(),\r\n            TRACKED_DEVICE_CLASS_CONTROLLER,\r\n            device_ptr,\r\n        )\r\n    };\r\n\r\n    if added {\r\n        DEVICE_REGISTERED.store(true, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\npub fn initialize(driver_context: *mut c_void, device_ptr: *mut c_void) -> bool {\r\n    if driver_context.is_null() {\r\n        return false;\r\n    }\r\n\r\n    let ctx = driver_context.cast::<DriverContext>();\r\n    let mut err = 0;\r\n\r\n    let get_iface = unsafe { (*(*ctx).vtable).get_generic_interface };\r\n\r\n    let host = get_iface(\r\n        ctx,\r\n        IVR_SERVER_DRIVER_HOST_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let driver_input = get_iface(\r\n        ctx,\r\n        IVR_DRIVER_INPUT_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let properties = get_iface(\r\n        ctx,\r\n        IVR_PROPERTIES_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    SERVER_HOST_IFACE.store(host, Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(driver_input, Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(properties, Ordering::SeqCst);\r\n\r\n    let ok = !host.is_null() && !driver_input.is_null() && !properties.is_null();\r\n    if !ok {\r\n        return false;\r\n    }\r\n\r\n    register_tracked_device_if_needed(device_ptr);\r\n    true\r\n}\r\n\r\npub fn cleanup() {\r\n    DEVICE_REGISTERED.store(false, Ordering::SeqCst);\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    SERVER_HOST_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n}\r\n\r\npub fn route_haptic_events() {\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    loop {\r\n        let mut event = VREvent::default();\r\n        let ok = unsafe {\r\n            ((*(*host).vtable).poll_next_event)(\r\n                host,\r\n                &mut event as *mut VREvent,\r\n                core::mem::size_of::<VREvent>() as u32,\r\n            )\r\n        };\r\n\r\n        if !ok {\r\n            break;\r\n        }\r\n\r\n        if event.event_type != VREVENT_INPUT_HAPTIC_VIBRATION {\r\n            continue;\r\n        }\r\n\r\n        let hv = unsafe { event.data.haptic_vibration };\r\n\r\n        let container = DEVICE_CONTAINER.load(Ordering::SeqCst);\r\n        if container != 0 && hv.container_handle != 0 && hv.container_handle != container {\r\n            continue;\r\n        }\r\n\r\n        let haptic_component = HAPTIC_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n        if haptic_component != 0 && hv.component_handle != 0 && hv.component_handle != haptic_component {\r\n            continue;\r\n        }\r\n\r\n        haptics::handle_haptic_request(haptics::HapticVibrationRequest {\r\n            amplitude: hv.amplitude,\r\n            duration_seconds: hv.duration_seconds,\r\n            frequency: hv.frequency,\r\n        });\r\n    }\r\n}\r\n\r\npub fn push_pose_update(object_id: u32, pose: *const c_void, pose_size: u32) {\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    if pose.is_null() || pose_size == 0 {\r\n        return;\r\n    }\r\n\r\n    unsafe {\r\n        ((*(*host).vtable).tracked_device_pose_updated)(host, object_id, pose, pose_size);\r\n    }\r\n}\r\n\r\npub fn activate_device(object_id: u32) -> bool {\r\n    let Some(props) = get_properties() else {\r\n        return false;\r\n    };\r\n\r\n    let container = unsafe { ((*(*props).vtable).tracked_device_to_property_container)(props, object_id) };\r\n    DEVICE_CONTAINER.store(container, Ordering::SeqCst);\r\n\r\n    write_string_property(props, container, PROP_MODEL_NUMBER_STRING, DEVICE_MODEL_CSTR);\r\n    write_string_property(props, container, PROP_SERIAL_NUMBER_STRING, DEVICE_SERIAL_CSTR);\r\n    write_string_property(\n        props,\n        container,\n        PROP_TRACKING_SYSTEM_NAME_STRING,\n        DEVICE_TRACKING_SYSTEM_NAME_CSTR,\n    );\n    write_string_property(\n        props,\n        container,\n        PROP_REGISTERED_DEVICE_TYPE_STRING,\n        DEVICE_REGISTERED_TYPE_CSTR,\n    );\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_INPUT_PROFILE_PATH_STRING,\r\n        DEVICE_INPUT_PROFILE_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_CONTROLLER_TYPE_STRING,\r\n        DEVICE_CONTROLLER_TYPE_CSTR,\r\n    );\r\n    write_bool_property(props, container, PROP_HAS_CONTROLLER_COMPONENT_BOOL, true);\n    write_int32_property(props, container, PROP_CONTROLLER_ROLE_HINT_INT32, 1);\n\r\n    if let Some(input) = get_driver_input() {\r\n        let mut handle = 0_u64;\r\n        let rc = unsafe {\r\n            ((*(*input).vtable).create_haptic_component)(\r\n                input,\r\n                container,\r\n                DEVICE_HAPTIC_PATH_CSTR.as_ptr().cast(),\r\n                &mut handle as *mut u64,\r\n            )\r\n        };\r\n\r\n        if rc == 0 {\r\n            HAPTIC_COMPONENT_HANDLE.store(handle, Ordering::SeqCst);\r\n        }\r\n    }\r\n\r\n    true\r\n}\r\n\r\npub fn deactivate_device() {\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":466,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_ed61c233-e170-4e17-8961-1cf0caa56eb3","epoch":1548,"edits":[{"text":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse core::sync::atomic::{AtomicU64, Ordering};\r\nuse std::sync::{Mutex, OnceLock};\r\nuse std::time::Instant;\r\nuse std::time::Duration;\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic STOP_DEADLINE_MS: AtomicU64 = AtomicU64::new(0);\r\nstatic STOP_TIMER_TOKEN: AtomicU64 = AtomicU64::new(0);\nstatic START_INSTANT: OnceLock<Instant> = OnceLock::new();\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\n\r\nfn send_packet(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    hid_bridge::HidBridge::new()\r\n        .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    send_packet(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn now_ms() -> u64 {\r\n    let start = START_INSTANT.get_or_init(Instant::now);\r\n    let ms = Instant::now().duration_since(*start).as_millis();\r\n    ms.min(u128::from(u64::MAX)) as u64\r\n}\r\n\r\nfn schedule_stop_after(duration_seconds: f32) {\r\n    if duration_seconds <= 0.0 {\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\n        return;\r\n    }\r\n\r\n    let duration_ms = (duration_seconds * 1000.0).round().max(1.0) as u64;\r\n    let deadline = now_ms().saturating_add(duration_ms);\r\n    STOP_DEADLINE_MS.store(deadline, Ordering::SeqCst);\r\n\n    let token = STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst).saturating_add(1);\n    std::thread::spawn(move || {\n        std::thread::sleep(Duration::from_millis(duration_ms));\n\n        if STOP_TIMER_TOKEN.load(Ordering::SeqCst) != token {\n            return;\n        }\n\n        let _ = send_stop();\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\n    });\n}\r\n\r\nfn process_pending_stop() {\r\n    let deadline = STOP_DEADLINE_MS.load(Ordering::SeqCst);\r\n    if deadline == 0 {\r\n        return;\r\n    }\r\n\r\n    if now_ms() >= deadline {\r\n        let _ = send_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\n        let _ = send_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let _ = send_amplitude(req.amplitude);\r\n    schedule_stop_after(req.duration_seconds);\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n    process_pending_stop();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":149,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_3718300f-4171-4bf2-8ae7-b39ae522ec11","epoch":1552,"edits":[{"text":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse core::sync::atomic::{AtomicU64, Ordering};\r\nuse std::sync::{Mutex, OnceLock};\r\nuse std::time::Instant;\r\nuse std::time::Duration;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic STOP_DEADLINE_MS: AtomicU64 = AtomicU64::new(0);\r\nstatic STOP_TIMER_TOKEN: AtomicU64 = AtomicU64::new(0);\r\nstatic START_INSTANT: OnceLock<Instant> = OnceLock::new();\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\nstatic ACTIVE_PULSE: Mutex<Option<ActivePulse>> = Mutex::new(None);\n\n#[derive(Clone, Copy)]\nstruct ActivePulse {\n    amplitude: f32,\n    frequency: f32,\n    deadline_ms: u64,\n}\n\r\nfn send_packet(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    hid_bridge::HidBridge::new()\r\n        .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    send_packet(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn now_ms() -> u64 {\r\n    let start = START_INSTANT.get_or_init(Instant::now);\r\n    let ms = Instant::now().duration_since(*start).as_millis();\r\n    ms.min(u128::from(u64::MAX)) as u64\r\n}\r\n\nfn clear_active_pulse() {\n    if let Ok(mut guard) = ACTIVE_PULSE.lock() {\n        *guard = None;\n    }\n}\n\nfn pulse_is_duplicate_while_active(amplitude: f32, frequency: f32, now_ms: u64) -> bool {\n    let Ok(guard) = ACTIVE_PULSE.lock() else {\n        return false;\n    };\n\n    let Some(active) = *guard else {\n        return false;\n    };\n\n    if now_ms >= active.deadline_ms {\n        return false;\n    }\n\n    let amp_close = (active.amplitude - amplitude).abs() <= 0.01;\n    let freq_close = (active.frequency - frequency).abs() <= 1.0;\n    amp_close && freq_close\n}\n\nfn set_active_pulse(amplitude: f32, frequency: f32, deadline_ms: u64) {\n    if let Ok(mut guard) = ACTIVE_PULSE.lock() {\n        *guard = Some(ActivePulse {\n            amplitude,\n            frequency,\n            deadline_ms,\n        });\n    }\n}\n\r\nfn schedule_stop_after(duration_seconds: f32) {\r\n    if duration_seconds <= 0.0 {\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        clear_active_pulse();\n        return;\r\n    }\r\n\r\n    let bounded_duration_seconds = duration_seconds.clamp(0.0, 5.0);\n    let duration_ms = (bounded_duration_seconds * 1000.0).round().max(1.0) as u64;\n    let deadline = now_ms().saturating_add(duration_ms);\r\n    STOP_DEADLINE_MS.store(deadline, Ordering::SeqCst);\r\n\r\n    let token = STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst).saturating_add(1);\r\n    std::thread::spawn(move || {\r\n        std::thread::sleep(Duration::from_millis(duration_ms));\r\n\r\n        if STOP_TIMER_TOKEN.load(Ordering::SeqCst) != token {\r\n            return;\r\n        }\r\n\r\n        let _ = send_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        clear_active_pulse();\n    });\r\n}\r\n\r\nfn process_pending_stop() {\r\n    let deadline = STOP_DEADLINE_MS.load(Ordering::SeqCst);\r\n    if deadline == 0 {\r\n        return;\r\n    }\r\n\r\n    if now_ms() >= deadline {\r\n        let _ = send_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        clear_active_pulse();\n    }\r\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        let _ = send_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        clear_active_pulse();\n        return;\r\n    }\r\n\r\n    let amplitude = req.amplitude.clamp(0.0, 1.0);\n    let frequency = req.frequency;\n    let now = now_ms();\n\n    if pulse_is_duplicate_while_active(amplitude, frequency, now) {\n        return;\n    }\n\n    let _ = send_amplitude(amplitude);\n    schedule_stop_after(req.duration_seconds);\r\n\n    let deadline_ms = STOP_DEADLINE_MS.load(Ordering::SeqCst);\n    if deadline_ms != 0 {\n        set_active_pulse(amplitude, frequency, deadline_ms);\n    }\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n    process_pending_stop();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":209,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_bcc1b2c2-e5c6-4c6f-9444-e7f9f5abb7a4","epoch":1556,"edits":[{"text":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse core::sync::atomic::{AtomicU64, Ordering};\r\nuse std::sync::{Mutex, OnceLock};\r\nuse std::time::Instant;\r\nuse std::time::Duration;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic STOP_DEADLINE_MS: AtomicU64 = AtomicU64::new(0);\r\nstatic STOP_TIMER_TOKEN: AtomicU64 = AtomicU64::new(0);\r\nstatic START_INSTANT: OnceLock<Instant> = OnceLock::new();\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\n\r\nfn send_packet(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    hid_bridge::HidBridge::new()\r\n        .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    send_packet(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn now_ms() -> u64 {\r\n    let start = START_INSTANT.get_or_init(Instant::now);\r\n    let ms = Instant::now().duration_since(*start).as_millis();\r\n    ms.min(u128::from(u64::MAX)) as u64\r\n}\r\n\r\nfn schedule_stop_after(duration_seconds: f32) {\r\n    if duration_seconds <= 0.0 {\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let duration_ms = (duration_seconds * 1000.0).round().max(1.0) as u64;\n    let deadline = now_ms().saturating_add(duration_ms);\r\n    STOP_DEADLINE_MS.store(deadline, Ordering::SeqCst);\r\n\r\n    let token = STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst).saturating_add(1);\r\n    std::thread::spawn(move || {\r\n        std::thread::sleep(Duration::from_millis(duration_ms));\r\n\r\n        if STOP_TIMER_TOKEN.load(Ordering::SeqCst) != token {\r\n            return;\r\n        }\r\n\r\n        let _ = send_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    });\r\n}\r\n\r\nfn process_pending_stop() {\r\n    let deadline = STOP_DEADLINE_MS.load(Ordering::SeqCst);\r\n    if deadline == 0 {\r\n        return;\r\n    }\r\n\r\n    if now_ms() >= deadline {\r\n        let _ = send_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        let _ = send_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let _ = send_amplitude(req.amplitude);\n    schedule_stop_after(req.duration_seconds);\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n    process_pending_stop();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":149,"endColumn":1}},{"text":"","range":{"startLineNumber":149,"startColumn":1,"endLineNumber":209,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\openvr_runtime.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","scheme":"file"},"requestId":"request_bcc1b2c2-e5c6-4c6f-9444-e7f9f5abb7a4","epoch":1559,"edits":[{"text":"//! OpenVR runtime integration helpers.\r\n//!\r\n//! This module owns driver-context interface resolution, tracked-device\r\n//! registration, component/property setup, and OpenVR haptic event routing.\r\n\r\nuse core::ffi::c_void;\r\nuse core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU64, Ordering};\r\nuse std::ffi::CString;\nuse std::os::raw::c_char;\r\n\r\nuse crate::haptics;\r\n\r\ntype EvRInitError = i32;\r\n\r\nconst IVR_SERVER_DRIVER_HOST_VERSION: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\nconst IVR_DRIVER_INPUT_VERSION: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_PROPERTIES_VERSION: &[u8] = b\"IVRProperties_001\\0\";\r\nconst IVR_DRIVER_LOG_VERSION: &[u8] = b\"IVRDriverLog_001\\0\";\n\r\nconst TRACKED_DEVICE_CLASS_CONTROLLER: i32 = 2;\r\nconst VREVENT_INPUT_HAPTIC_VIBRATION: u32 = 1700;\r\n\r\nconst PROP_MODEL_NUMBER_STRING: u32 = 1001;\r\nconst PROP_SERIAL_NUMBER_STRING: u32 = 1002;\r\nconst PROP_TRACKING_SYSTEM_NAME_STRING: u32 = 1000;\r\nconst PROP_REGISTERED_DEVICE_TYPE_STRING: u32 = 1036;\r\nconst PROP_INPUT_PROFILE_PATH_STRING: u32 = 1037;\r\nconst PROP_CONTROLLER_TYPE_STRING: u32 = 7000;\r\nconst PROP_CONTROLLER_ROLE_HINT_INT32: u32 = 3007;\r\nconst PROP_HAS_CONTROLLER_COMPONENT_BOOL: u32 = 6003;\r\n\r\nconst K_UN_BOOL_PROPERTY_TAG: u32 = 4;\r\nconst K_UN_INT32_PROPERTY_TAG: u32 = 2;\r\nconst K_UN_STRING_PROPERTY_TAG: u32 = 5;\r\nconst PROPERTY_WRITE_SET: i32 = 0;\r\n\r\nconst DEVICE_SERIAL_CSTR: &[u8] = b\"qtpy-samd21-haptic-001\\0\";\r\nconst DEVICE_MODEL_CSTR: &[u8] = b\"QT Py SAMD21 Haptic\\0\";\r\nconst DEVICE_TRACKING_SYSTEM_NAME_CSTR: &[u8] = b\"big_haptic_driver\\0\";\r\nconst DEVICE_REGISTERED_TYPE_CSTR: &[u8] = b\"big_haptic_driver/controller\\0\";\r\nconst DEVICE_CONTROLLER_TYPE_CSTR: &[u8] = b\"big_haptic_driver\\0\";\r\nconst DEVICE_INPUT_PROFILE_CSTR: &[u8] = b\"{big_haptic_driver}/input/big_haptic_profile.json\\0\";\r\nconst DEVICE_HAPTIC_PATH_CSTR: &[u8] = b\"/output/haptic\\0\";\r\n\r\nconst PROVIDER_IFACE_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_004\\0\";\r\nconst PROVIDER_IFACE_ALT_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_005\\0\";\r\nconst DEVICE_IFACE_CSTR: &[u8] = b\"ITrackedDeviceServerDriver_005\\0\";\r\n\r\nconst IVR_PROPERTIES_CSTR: &[u8] = b\"IVRProperties_001\\0\";\r\nconst IVR_DRIVER_INPUT_CSTR: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_SERVER_DRIVER_HOST_CSTR: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\nconst IVR_DRIVER_LOG_CSTR: &[u8] = b\"IVRDriverLog_001\\0\";\n\r\nconst IVR_SETTINGS_CSTR: &[u8] = b\"IVRSettings_003\\0\";\r\nconst IVR_DRIVER_MANAGER_CSTR: &[u8] = b\"IVRDriverManager_001\\0\";\r\nconst IVR_RESOURCES_CSTR: &[u8] = b\"IVRResources_001\\0\";\r\n\r\n#[repr(C)]\r\nstruct InterfaceVersions([*const c_char; 10]);\r\nunsafe impl Sync for InterfaceVersions {}\r\n\r\nstatic INTERFACE_VERSIONS: InterfaceVersions = InterfaceVersions([\r\n    IVR_SETTINGS_CSTR.as_ptr().cast(),\r\n    DEVICE_IFACE_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_CSTR.as_ptr().cast(),\r\n    IVR_PROPERTIES_CSTR.as_ptr().cast(),\r\n    IVR_SERVER_DRIVER_HOST_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_LOG_CSTR.as_ptr().cast(),\n    IVR_DRIVER_MANAGER_CSTR.as_ptr().cast(),\r\n    IVR_RESOURCES_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_INPUT_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_ALT_CSTR.as_ptr().cast(),\r\n    core::ptr::null(),\r\n]);\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREventHapticVibration {\r\n    container_handle: u64,\r\n    component_handle: u64,\r\n    duration_seconds: f32,\r\n    frequency: f32,\r\n    amplitude: f32,\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nunion VREventData {\r\n    haptic_vibration: VREventHapticVibration,\r\n    reserved: [u8; 48],\r\n}\r\n\r\nimpl Default for VREventData {\r\n    fn default() -> Self {\r\n        Self { reserved: [0; 48] }\r\n    }\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREvent {\r\n    event_type: u32,\r\n    tracked_device_index: u32,\r\n    event_age_seconds: f32,\r\n    data: VREventData,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContextVTable {\r\n    get_generic_interface:\r\n        extern \"C\" fn(*mut DriverContext, *const c_char, *mut EvRInitError) -> *mut c_void,\r\n    get_driver_handle: extern \"C\" fn(*mut DriverContext) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContext {\r\n    vtable: *const DriverContextVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHostVTable {\r\n    tracked_device_added:\r\n        extern \"C\" fn(*mut ServerDriverHost, *const c_char, i32, *mut c_void) -> bool,\r\n    tracked_device_pose_updated: extern \"C\" fn(*mut ServerDriverHost, u32, *const c_void, u32),\r\n    vsync_event: extern \"C\" fn(*mut ServerDriverHost, f64),\r\n    vendor_specific_event: extern \"C\" fn(*mut ServerDriverHost, u32, u32, *const VREventData, f64),\r\n    is_exiting: extern \"C\" fn(*mut ServerDriverHost) -> bool,\r\n    poll_next_event: extern \"C\" fn(*mut ServerDriverHost, *mut VREvent, u32) -> bool,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHost {\r\n    vtable: *const ServerDriverHostVTable,\r\n}\r\n\n#[repr(C)]\nstruct DriverLogVTable {\n    log: extern \"C\" fn(*mut DriverLog, *const c_char),\n}\n\n#[repr(C)]\nstruct DriverLog {\n    vtable: *const DriverLogVTable,\n}\n\r\n#[repr(C)]\r\nstruct DriverInputVTable {\r\n    create_boolean_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n    update_boolean_component: extern \"C\" fn(*mut DriverInput, u64, bool, f64) -> i32,\r\n    create_scalar_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64, i32, i32) -> i32,\r\n    update_scalar_component: extern \"C\" fn(*mut DriverInput, u64, f32, f64) -> i32,\r\n    create_haptic_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverInput {\r\n    vtable: *const DriverInputVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct PropertiesVTable {\r\n    read_property_batch: extern \"C\" fn(*mut Properties, u64, *mut c_void, u32) -> i32,\r\n    write_property_batch: extern \"C\" fn(*mut Properties, u64, *mut PropertyWrite, u32) -> i32,\r\n    get_prop_error_name_from_enum: extern \"C\" fn(*mut Properties, i32) -> *const c_char,\r\n    tracked_device_to_property_container: extern \"C\" fn(*mut Properties, u32) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct Properties {\r\n    vtable: *const PropertiesVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct PropertyWrite {\r\n    prop: u32,\r\n    write_type: i32,\r\n    set_error: i32,\r\n    pv_buffer: *mut c_void,\r\n    un_buffer_size: u32,\r\n    un_tag: u32,\r\n    e_error: i32,\r\n}\r\n\r\nstatic DEVICE_REGISTERED: AtomicBool = AtomicBool::new(false);\r\n\r\nstatic SERVER_HOST_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DRIVER_INPUT_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic PROPERTIES_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DRIVER_LOG_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\n\r\nstatic DEVICE_CONTAINER: AtomicU64 = AtomicU64::new(0);\r\nstatic HAPTIC_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\n\r\npub fn interface_versions_ptr() -> *const *const c_char {\r\n    INTERFACE_VERSIONS.0.as_ptr()\r\n}\r\n\r\nfn get_server_host() -> Option<*mut ServerDriverHost> {\r\n    let ptr = SERVER_HOST_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_driver_input() -> Option<*mut DriverInput> {\r\n    let ptr = DRIVER_INPUT_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_properties() -> Option<*mut Properties> {\r\n    let ptr = PROPERTIES_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\nfn get_driver_log() -> Option<*mut DriverLog> {\n    let ptr = DRIVER_LOG_IFACE.load(Ordering::SeqCst);\n    if ptr.is_null() {\n        None\n    } else {\n        Some(ptr.cast())\n    }\n}\n\nfn log_driver_message(message: &str) {\n    let Some(driver_log) = get_driver_log() else {\n        return;\n    };\n\n    let Ok(c_message) = CString::new(message) else {\n        return;\n    };\n\n    unsafe {\n        ((*(*driver_log).vtable).log)(driver_log, c_message.as_ptr());\n    }\n}\n\r\nfn write_string_property(props: *mut Properties, container: u64, prop: u32, value_nul: &'static [u8]) {\r\n    if props.is_null() || value_nul.is_empty() {\r\n        return;\r\n    }\r\n\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: value_nul.as_ptr().cast_mut().cast(),\r\n        un_buffer_size: value_nul.len() as u32,\r\n        un_tag: K_UN_STRING_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn write_bool_property(props: *mut Properties, container: u64, prop: u32, value: bool) {\r\n    if props.is_null() {\r\n        return;\r\n    }\r\n\r\n    let mut bool_value = value;\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: (&mut bool_value as *mut bool).cast(),\r\n        un_buffer_size: core::mem::size_of::<bool>() as u32,\r\n        un_tag: K_UN_BOOL_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn write_int32_property(props: *mut Properties, container: u64, prop: u32, value: i32) {\r\n    if props.is_null() {\r\n        return;\r\n    }\r\n\r\n    let mut int_value = value;\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: (&mut int_value as *mut i32).cast(),\r\n        un_buffer_size: core::mem::size_of::<i32>() as u32,\r\n        un_tag: K_UN_INT32_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn register_tracked_device_if_needed(device_ptr: *mut c_void) {\r\n    if DEVICE_REGISTERED.load(Ordering::SeqCst) {\r\n        return;\r\n    }\r\n\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    let added = unsafe {\r\n        ((*(*host).vtable).tracked_device_added)(\r\n            host,\r\n            DEVICE_SERIAL_CSTR.as_ptr().cast(),\r\n            TRACKED_DEVICE_CLASS_CONTROLLER,\r\n            device_ptr,\r\n        )\r\n    };\r\n\r\n    if added {\r\n        DEVICE_REGISTERED.store(true, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\npub fn initialize(driver_context: *mut c_void, device_ptr: *mut c_void) -> bool {\r\n    if driver_context.is_null() {\r\n        return false;\r\n    }\r\n\r\n    let ctx = driver_context.cast::<DriverContext>();\r\n    let mut err = 0;\r\n\r\n    let get_iface = unsafe { (*(*ctx).vtable).get_generic_interface };\r\n\r\n    let host = get_iface(\r\n        ctx,\r\n        IVR_SERVER_DRIVER_HOST_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let driver_input = get_iface(\r\n        ctx,\r\n        IVR_DRIVER_INPUT_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let properties = get_iface(\r\n        ctx,\r\n        IVR_PROPERTIES_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\n    let driver_log = get_iface(\n        ctx,\n        IVR_DRIVER_LOG_VERSION.as_ptr().cast(),\n        &mut err as *mut EvRInitError,\n    );\n\r\n    SERVER_HOST_IFACE.store(host, Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(driver_input, Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(properties, Ordering::SeqCst);\r\n    DRIVER_LOG_IFACE.store(driver_log, Ordering::SeqCst);\n\r\n    let ok = !host.is_null() && !driver_input.is_null() && !properties.is_null();\r\n    if !ok {\r\n        return false;\r\n    }\r\n\n    log_driver_message(&format!(\n        \"[big_haptic_driver] OpenVR init ok; sizeof(VREvent)={}, sizeof(VREventHapticVibration)={}\",\n        core::mem::size_of::<VREvent>(),\n        core::mem::size_of::<VREventHapticVibration>()\n    ));\n\r\n    register_tracked_device_if_needed(device_ptr);\r\n    true\r\n}\r\n\r\npub fn cleanup() {\r\n    DEVICE_REGISTERED.store(false, Ordering::SeqCst);\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    SERVER_HOST_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DRIVER_LOG_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\n}\r\n\r\npub fn route_haptic_events() {\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    loop {\r\n        let mut event = VREvent::default();\r\n        let ok = unsafe {\r\n            ((*(*host).vtable).poll_next_event)(\r\n                host,\r\n                &mut event as *mut VREvent,\r\n                core::mem::size_of::<VREvent>() as u32,\r\n            )\r\n        };\r\n\r\n        if !ok {\r\n            break;\r\n        }\r\n\r\n        if event.event_type != VREVENT_INPUT_HAPTIC_VIBRATION {\r\n            continue;\r\n        }\r\n\r\n        let hv = unsafe { event.data.haptic_vibration };\r\n\n        log_driver_message(&format!(\n            \"[big_haptic_driver] haptic event: dev={} container={} component={} duration_s={:.6} (0x{:08x}) freq={:.3} amp={:.6} (0x{:08x})\",\n            event.tracked_device_index,\n            hv.container_handle,\n            hv.component_handle,\n            hv.duration_seconds,\n            hv.duration_seconds.to_bits(),\n            hv.frequency,\n            hv.amplitude,\n            hv.amplitude.to_bits()\n        ));\n\r\n        let container = DEVICE_CONTAINER.load(Ordering::SeqCst);\r\n        if container != 0 && hv.container_handle != 0 && hv.container_handle != container {\r\n            log_driver_message(&format!(\n                \"[big_haptic_driver] skipped haptic event: container mismatch event={} driver={}\",\n                hv.container_handle, container\n            ));\n            continue;\r\n        }\r\n\r\n        let haptic_component = HAPTIC_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n        if haptic_component != 0 && hv.component_handle != 0 && hv.component_handle != haptic_component {\r\n            log_driver_message(&format!(\n                \"[big_haptic_driver] skipped haptic event: component mismatch event={} driver={}\",\n                hv.component_handle, haptic_component\n            ));\n            continue;\r\n        }\r\n\n        log_driver_message(\"[big_haptic_driver] forwarding haptic request to HID layer\");\n\r\n        haptics::handle_haptic_request(haptics::HapticVibrationRequest {\r\n            amplitude: hv.amplitude,\r\n            duration_seconds: hv.duration_seconds,\r\n            frequency: hv.frequency,\r\n        });\r\n    }\r\n}\r\n\r\npub fn push_pose_update(object_id: u32, pose: *const c_void, pose_size: u32) {\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    if pose.is_null() || pose_size == 0 {\r\n        return;\r\n    }\r\n\r\n    unsafe {\r\n        ((*(*host).vtable).tracked_device_pose_updated)(host, object_id, pose, pose_size);\r\n    }\r\n}\r\n\r\npub fn activate_device(object_id: u32) -> bool {\r\n    let Some(props) = get_properties() else {\r\n        return false;\r\n    };\r\n\r\n    let container = unsafe { ((*(*props).vtable).tracked_device_to_property_container)(props, object_id) };\r\n    DEVICE_CONTAINER.store(container, Ordering::SeqCst);\r\n\r\n    write_string_property(props, container, PROP_MODEL_NUMBER_STRING, DEVICE_MODEL_CSTR);\r\n    write_string_property(props, container, PROP_SERIAL_NUMBER_STRING, DEVICE_SERIAL_CSTR);\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_TRACKING_SYSTEM_NAME_STRING,\r\n        DEVICE_TRACKING_SYSTEM_NAME_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_REGISTERED_DEVICE_TYPE_STRING,\r\n        DEVICE_REGISTERED_TYPE_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_INPUT_PROFILE_PATH_STRING,\r\n        DEVICE_INPUT_PROFILE_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_CONTROLLER_TYPE_STRING,\r\n        DEVICE_CONTROLLER_TYPE_CSTR,\r\n    );\r\n    write_bool_property(props, container, PROP_HAS_CONTROLLER_COMPONENT_BOOL, true);\r\n    write_int32_property(props, container, PROP_CONTROLLER_ROLE_HINT_INT32, 1);\r\n\r\n    if let Some(input) = get_driver_input() {\r\n        let mut handle = 0_u64;\r\n        let rc = unsafe {\r\n            ((*(*input).vtable).create_haptic_component)(\r\n                input,\r\n                container,\r\n                DEVICE_HAPTIC_PATH_CSTR.as_ptr().cast(),\r\n                &mut handle as *mut u64,\r\n            )\r\n        };\r\n\r\n        if rc == 0 {\r\n            HAPTIC_COMPONENT_HANDLE.store(handle, Ordering::SeqCst);\r\n        }\r\n    }\r\n\r\n    true\r\n}\r\n\r\npub fn deactivate_device() {\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":540,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\openvr_runtime.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","scheme":"file"},"requestId":"request_bcc1b2c2-e5c6-4c6f-9444-e7f9f5abb7a4","epoch":1561,"edits":[{"text":"//! OpenVR runtime integration helpers.\r\n//!\r\n//! This module owns driver-context interface resolution, tracked-device\r\n//! registration, component/property setup, and OpenVR haptic event routing.\r\n\r\nuse core::ffi::c_void;\r\nuse core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU64, Ordering};\r\nuse std::ffi::CString;\r\nuse std::os::raw::c_char;\r\n\r\nuse crate::haptics;\r\n\r\ntype EvRInitError = i32;\r\n\r\nconst IVR_SERVER_DRIVER_HOST_VERSION: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\nconst IVR_DRIVER_INPUT_VERSION: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_PROPERTIES_VERSION: &[u8] = b\"IVRProperties_001\\0\";\r\nconst IVR_DRIVER_LOG_VERSION: &[u8] = b\"IVRDriverLog_001\\0\";\r\n\r\nconst TRACKED_DEVICE_CLASS_CONTROLLER: i32 = 2;\r\nconst VREVENT_INPUT_HAPTIC_VIBRATION: u32 = 1700;\r\n\r\nconst PROP_MODEL_NUMBER_STRING: u32 = 1001;\r\nconst PROP_SERIAL_NUMBER_STRING: u32 = 1002;\r\nconst PROP_TRACKING_SYSTEM_NAME_STRING: u32 = 1000;\r\nconst PROP_REGISTERED_DEVICE_TYPE_STRING: u32 = 1036;\r\nconst PROP_INPUT_PROFILE_PATH_STRING: u32 = 1037;\r\nconst PROP_CONTROLLER_TYPE_STRING: u32 = 7000;\r\nconst PROP_CONTROLLER_ROLE_HINT_INT32: u32 = 3007;\r\nconst PROP_HAS_CONTROLLER_COMPONENT_BOOL: u32 = 6003;\r\n\r\nconst K_UN_BOOL_PROPERTY_TAG: u32 = 4;\r\nconst K_UN_INT32_PROPERTY_TAG: u32 = 2;\r\nconst K_UN_STRING_PROPERTY_TAG: u32 = 5;\r\nconst PROPERTY_WRITE_SET: i32 = 0;\r\n\r\nconst DEVICE_SERIAL_CSTR: &[u8] = b\"qtpy-samd21-haptic-001\\0\";\r\nconst DEVICE_MODEL_CSTR: &[u8] = b\"QT Py SAMD21 Haptic\\0\";\r\nconst DEVICE_TRACKING_SYSTEM_NAME_CSTR: &[u8] = b\"big_haptic_driver\\0\";\r\nconst DEVICE_REGISTERED_TYPE_CSTR: &[u8] = b\"big_haptic_driver/controller\\0\";\r\nconst DEVICE_CONTROLLER_TYPE_CSTR: &[u8] = b\"big_haptic_driver\\0\";\r\nconst DEVICE_INPUT_PROFILE_CSTR: &[u8] = b\"{big_haptic_driver}/input/big_haptic_profile.json\\0\";\r\nconst DEVICE_HAPTIC_PATH_CSTR: &[u8] = b\"/output/haptic\\0\";\r\n\r\nconst PROVIDER_IFACE_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_004\\0\";\r\nconst PROVIDER_IFACE_ALT_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_005\\0\";\r\nconst DEVICE_IFACE_CSTR: &[u8] = b\"ITrackedDeviceServerDriver_005\\0\";\r\n\r\nconst IVR_PROPERTIES_CSTR: &[u8] = b\"IVRProperties_001\\0\";\r\nconst IVR_DRIVER_INPUT_CSTR: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_SERVER_DRIVER_HOST_CSTR: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\nconst IVR_DRIVER_LOG_CSTR: &[u8] = b\"IVRDriverLog_001\\0\";\r\n\r\nconst IVR_SETTINGS_CSTR: &[u8] = b\"IVRSettings_003\\0\";\r\nconst IVR_DRIVER_MANAGER_CSTR: &[u8] = b\"IVRDriverManager_001\\0\";\r\nconst IVR_RESOURCES_CSTR: &[u8] = b\"IVRResources_001\\0\";\r\n\r\n#[repr(C)]\r\nstruct InterfaceVersions([*const c_char; 11]);\nunsafe impl Sync for InterfaceVersions {}\r\n\r\nstatic INTERFACE_VERSIONS: InterfaceVersions = InterfaceVersions([\r\n    IVR_SETTINGS_CSTR.as_ptr().cast(),\r\n    DEVICE_IFACE_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_CSTR.as_ptr().cast(),\r\n    IVR_PROPERTIES_CSTR.as_ptr().cast(),\r\n    IVR_SERVER_DRIVER_HOST_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_LOG_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_MANAGER_CSTR.as_ptr().cast(),\r\n    IVR_RESOURCES_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_INPUT_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_ALT_CSTR.as_ptr().cast(),\r\n    core::ptr::null(),\r\n]);\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREventHapticVibration {\r\n    container_handle: u64,\r\n    component_handle: u64,\r\n    duration_seconds: f32,\r\n    frequency: f32,\r\n    amplitude: f32,\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nunion VREventData {\r\n    haptic_vibration: VREventHapticVibration,\r\n    reserved: [u8; 48],\r\n}\r\n\r\nimpl Default for VREventData {\r\n    fn default() -> Self {\r\n        Self { reserved: [0; 48] }\r\n    }\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREvent {\r\n    event_type: u32,\r\n    tracked_device_index: u32,\r\n    event_age_seconds: f32,\r\n    data: VREventData,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContextVTable {\r\n    get_generic_interface:\r\n        extern \"C\" fn(*mut DriverContext, *const c_char, *mut EvRInitError) -> *mut c_void,\r\n    get_driver_handle: extern \"C\" fn(*mut DriverContext) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContext {\r\n    vtable: *const DriverContextVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHostVTable {\r\n    tracked_device_added:\r\n        extern \"C\" fn(*mut ServerDriverHost, *const c_char, i32, *mut c_void) -> bool,\r\n    tracked_device_pose_updated: extern \"C\" fn(*mut ServerDriverHost, u32, *const c_void, u32),\r\n    vsync_event: extern \"C\" fn(*mut ServerDriverHost, f64),\r\n    vendor_specific_event: extern \"C\" fn(*mut ServerDriverHost, u32, u32, *const VREventData, f64),\r\n    is_exiting: extern \"C\" fn(*mut ServerDriverHost) -> bool,\r\n    poll_next_event: extern \"C\" fn(*mut ServerDriverHost, *mut VREvent, u32) -> bool,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHost {\r\n    vtable: *const ServerDriverHostVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverLogVTable {\r\n    log: extern \"C\" fn(*mut DriverLog, *const c_char),\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverLog {\r\n    vtable: *const DriverLogVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverInputVTable {\r\n    create_boolean_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n    update_boolean_component: extern \"C\" fn(*mut DriverInput, u64, bool, f64) -> i32,\r\n    create_scalar_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64, i32, i32) -> i32,\r\n    update_scalar_component: extern \"C\" fn(*mut DriverInput, u64, f32, f64) -> i32,\r\n    create_haptic_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverInput {\r\n    vtable: *const DriverInputVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct PropertiesVTable {\r\n    read_property_batch: extern \"C\" fn(*mut Properties, u64, *mut c_void, u32) -> i32,\r\n    write_property_batch: extern \"C\" fn(*mut Properties, u64, *mut PropertyWrite, u32) -> i32,\r\n    get_prop_error_name_from_enum: extern \"C\" fn(*mut Properties, i32) -> *const c_char,\r\n    tracked_device_to_property_container: extern \"C\" fn(*mut Properties, u32) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct Properties {\r\n    vtable: *const PropertiesVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct PropertyWrite {\r\n    prop: u32,\r\n    write_type: i32,\r\n    set_error: i32,\r\n    pv_buffer: *mut c_void,\r\n    un_buffer_size: u32,\r\n    un_tag: u32,\r\n    e_error: i32,\r\n}\r\n\r\nstatic DEVICE_REGISTERED: AtomicBool = AtomicBool::new(false);\r\n\r\nstatic SERVER_HOST_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DRIVER_INPUT_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic PROPERTIES_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DRIVER_LOG_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\n\r\nstatic DEVICE_CONTAINER: AtomicU64 = AtomicU64::new(0);\r\nstatic HAPTIC_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\n\r\npub fn interface_versions_ptr() -> *const *const c_char {\r\n    INTERFACE_VERSIONS.0.as_ptr()\r\n}\r\n\r\nfn get_server_host() -> Option<*mut ServerDriverHost> {\r\n    let ptr = SERVER_HOST_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_driver_input() -> Option<*mut DriverInput> {\r\n    let ptr = DRIVER_INPUT_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_properties() -> Option<*mut Properties> {\r\n    let ptr = PROPERTIES_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_driver_log() -> Option<*mut DriverLog> {\r\n    let ptr = DRIVER_LOG_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn log_driver_message(message: &str) {\r\n    let Some(driver_log) = get_driver_log() else {\r\n        return;\r\n    };\r\n\r\n    let Ok(c_message) = CString::new(message) else {\r\n        return;\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*driver_log).vtable).log)(driver_log, c_message.as_ptr());\r\n    }\r\n}\r\n\r\nfn write_string_property(props: *mut Properties, container: u64, prop: u32, value_nul: &'static [u8]) {\r\n    if props.is_null() || value_nul.is_empty() {\r\n        return;\r\n    }\r\n\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: value_nul.as_ptr().cast_mut().cast(),\r\n        un_buffer_size: value_nul.len() as u32,\r\n        un_tag: K_UN_STRING_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn write_bool_property(props: *mut Properties, container: u64, prop: u32, value: bool) {\r\n    if props.is_null() {\r\n        return;\r\n    }\r\n\r\n    let mut bool_value = value;\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: (&mut bool_value as *mut bool).cast(),\r\n        un_buffer_size: core::mem::size_of::<bool>() as u32,\r\n        un_tag: K_UN_BOOL_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn write_int32_property(props: *mut Properties, container: u64, prop: u32, value: i32) {\r\n    if props.is_null() {\r\n        return;\r\n    }\r\n\r\n    let mut int_value = value;\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: (&mut int_value as *mut i32).cast(),\r\n        un_buffer_size: core::mem::size_of::<i32>() as u32,\r\n        un_tag: K_UN_INT32_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn register_tracked_device_if_needed(device_ptr: *mut c_void) {\r\n    if DEVICE_REGISTERED.load(Ordering::SeqCst) {\r\n        return;\r\n    }\r\n\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    let added = unsafe {\r\n        ((*(*host).vtable).tracked_device_added)(\r\n            host,\r\n            DEVICE_SERIAL_CSTR.as_ptr().cast(),\r\n            TRACKED_DEVICE_CLASS_CONTROLLER,\r\n            device_ptr,\r\n        )\r\n    };\r\n\r\n    if added {\r\n        DEVICE_REGISTERED.store(true, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\npub fn initialize(driver_context: *mut c_void, device_ptr: *mut c_void) -> bool {\r\n    if driver_context.is_null() {\r\n        return false;\r\n    }\r\n\r\n    let ctx = driver_context.cast::<DriverContext>();\r\n    let mut err = 0;\r\n\r\n    let get_iface = unsafe { (*(*ctx).vtable).get_generic_interface };\r\n\r\n    let host = get_iface(\r\n        ctx,\r\n        IVR_SERVER_DRIVER_HOST_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let driver_input = get_iface(\r\n        ctx,\r\n        IVR_DRIVER_INPUT_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let properties = get_iface(\r\n        ctx,\r\n        IVR_PROPERTIES_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let driver_log = get_iface(\r\n        ctx,\r\n        IVR_DRIVER_LOG_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    SERVER_HOST_IFACE.store(host, Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(driver_input, Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(properties, Ordering::SeqCst);\r\n    DRIVER_LOG_IFACE.store(driver_log, Ordering::SeqCst);\r\n\r\n    let ok = !host.is_null() && !driver_input.is_null() && !properties.is_null();\r\n    if !ok {\r\n        return false;\r\n    }\r\n\r\n    log_driver_message(&format!(\r\n        \"[big_haptic_driver] OpenVR init ok; sizeof(VREvent)={}, sizeof(VREventHapticVibration)={}\",\r\n        core::mem::size_of::<VREvent>(),\r\n        core::mem::size_of::<VREventHapticVibration>()\r\n    ));\r\n\r\n    register_tracked_device_if_needed(device_ptr);\r\n    true\r\n}\r\n\r\npub fn cleanup() {\r\n    DEVICE_REGISTERED.store(false, Ordering::SeqCst);\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    SERVER_HOST_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DRIVER_LOG_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n}\r\n\r\npub fn route_haptic_events() {\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    loop {\r\n        let mut event = VREvent::default();\r\n        let ok = unsafe {\r\n            ((*(*host).vtable).poll_next_event)(\r\n                host,\r\n                &mut event as *mut VREvent,\r\n                core::mem::size_of::<VREvent>() as u32,\r\n            )\r\n        };\r\n\r\n        if !ok {\r\n            break;\r\n        }\r\n\r\n        if event.event_type != VREVENT_INPUT_HAPTIC_VIBRATION {\r\n            continue;\r\n        }\r\n\r\n        let hv = unsafe { event.data.haptic_vibration };\r\n\r\n        log_driver_message(&format!(\r\n            \"[big_haptic_driver] haptic event: dev={} container={} component={} duration_s={:.6} (0x{:08x}) freq={:.3} amp={:.6} (0x{:08x})\",\r\n            event.tracked_device_index,\r\n            hv.container_handle,\r\n            hv.component_handle,\r\n            hv.duration_seconds,\r\n            hv.duration_seconds.to_bits(),\r\n            hv.frequency,\r\n            hv.amplitude,\r\n            hv.amplitude.to_bits()\r\n        ));\r\n\r\n        let container = DEVICE_CONTAINER.load(Ordering::SeqCst);\r\n        if container != 0 && hv.container_handle != 0 && hv.container_handle != container {\r\n            log_driver_message(&format!(\r\n                \"[big_haptic_driver] skipped haptic event: container mismatch event={} driver={}\",\r\n                hv.container_handle, container\r\n            ));\r\n            continue;\r\n        }\r\n\r\n        let haptic_component = HAPTIC_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n        if haptic_component != 0 && hv.component_handle != 0 && hv.component_handle != haptic_component {\r\n            log_driver_message(&format!(\r\n                \"[big_haptic_driver] skipped haptic event: component mismatch event={} driver={}\",\r\n                hv.component_handle, haptic_component\r\n            ));\r\n            continue;\r\n        }\r\n\r\n        log_driver_message(\"[big_haptic_driver] forwarding haptic request to HID layer\");\r\n\r\n        haptics::handle_haptic_request(haptics::HapticVibrationRequest {\r\n            amplitude: hv.amplitude,\r\n            duration_seconds: hv.duration_seconds,\r\n            frequency: hv.frequency,\r\n        });\r\n    }\r\n}\r\n\r\npub fn push_pose_update(object_id: u32, pose: *const c_void, pose_size: u32) {\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    if pose.is_null() || pose_size == 0 {\r\n        return;\r\n    }\r\n\r\n    unsafe {\r\n        ((*(*host).vtable).tracked_device_pose_updated)(host, object_id, pose, pose_size);\r\n    }\r\n}\r\n\r\npub fn activate_device(object_id: u32) -> bool {\r\n    let Some(props) = get_properties() else {\r\n        return false;\r\n    };\r\n\r\n    let container = unsafe { ((*(*props).vtable).tracked_device_to_property_container)(props, object_id) };\r\n    DEVICE_CONTAINER.store(container, Ordering::SeqCst);\r\n\r\n    write_string_property(props, container, PROP_MODEL_NUMBER_STRING, DEVICE_MODEL_CSTR);\r\n    write_string_property(props, container, PROP_SERIAL_NUMBER_STRING, DEVICE_SERIAL_CSTR);\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_TRACKING_SYSTEM_NAME_STRING,\r\n        DEVICE_TRACKING_SYSTEM_NAME_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_REGISTERED_DEVICE_TYPE_STRING,\r\n        DEVICE_REGISTERED_TYPE_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_INPUT_PROFILE_PATH_STRING,\r\n        DEVICE_INPUT_PROFILE_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_CONTROLLER_TYPE_STRING,\r\n        DEVICE_CONTROLLER_TYPE_CSTR,\r\n    );\r\n    write_bool_property(props, container, PROP_HAS_CONTROLLER_COMPONENT_BOOL, true);\r\n    write_int32_property(props, container, PROP_CONTROLLER_ROLE_HINT_INT32, 1);\r\n\r\n    if let Some(input) = get_driver_input() {\r\n        let mut handle = 0_u64;\r\n        let rc = unsafe {\r\n            ((*(*input).vtable).create_haptic_component)(\r\n                input,\r\n                container,\r\n                DEVICE_HAPTIC_PATH_CSTR.as_ptr().cast(),\r\n                &mut handle as *mut u64,\r\n            )\r\n        };\r\n\r\n        if rc == 0 {\r\n            HAPTIC_COMPONENT_HANDLE.store(handle, Ordering::SeqCst);\r\n        }\r\n    }\r\n\r\n    true\r\n}\r\n\r\npub fn deactivate_device() {\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":540,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_66e3538f-7fed-4c58-b7f0-e5edecfabab8","epoch":1565,"edits":[{"text":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse core::sync::atomic::{AtomicU64, Ordering};\r\nuse std::sync::{Mutex, OnceLock};\r\nuse std::time::Instant;\r\nuse std::time::Duration;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic STOP_DEADLINE_MS: AtomicU64 = AtomicU64::new(0);\r\nstatic STOP_TIMER_TOKEN: AtomicU64 = AtomicU64::new(0);\r\nstatic START_INSTANT: OnceLock<Instant> = OnceLock::new();\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\n\nconst DEFAULT_IMPLICIT_PULSE_SECONDS: f32 = 0.03;\nconst MIN_IMPLICIT_PULSE_SECONDS: f32 = 0.005;\nconst MAX_IMPLICIT_PULSE_SECONDS: f32 = 0.03;\n\r\nfn send_packet(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    hid_bridge::HidBridge::new()\r\n        .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    send_packet(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn now_ms() -> u64 {\r\n    let start = START_INSTANT.get_or_init(Instant::now);\r\n    let ms = Instant::now().duration_since(*start).as_millis();\r\n    ms.min(u128::from(u64::MAX)) as u64\r\n}\r\n\r\nfn schedule_stop_after(duration_seconds: f32) {\r\n    if duration_seconds <= 0.0 {\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let duration_ms = (duration_seconds * 1000.0).round().max(1.0) as u64;\r\n    let deadline = now_ms().saturating_add(duration_ms);\r\n    STOP_DEADLINE_MS.store(deadline, Ordering::SeqCst);\r\n\r\n    let token = STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst).saturating_add(1);\r\n    std::thread::spawn(move || {\r\n        std::thread::sleep(Duration::from_millis(duration_ms));\r\n\r\n        if STOP_TIMER_TOKEN.load(Ordering::SeqCst) != token {\r\n            return;\r\n        }\r\n\r\n        let _ = send_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    });\r\n}\r\n\nfn resolve_effective_duration_seconds(req: HapticVibrationRequest) -> f32 {\n    if req.duration_seconds > 0.0 {\n        return req.duration_seconds;\n    }\n\n    if req.frequency > 0.0 {\n        return (1.0 / req.frequency).clamp(MIN_IMPLICIT_PULSE_SECONDS, MAX_IMPLICIT_PULSE_SECONDS);\n    }\n\n    DEFAULT_IMPLICIT_PULSE_SECONDS\n}\n\r\nfn process_pending_stop() {\r\n    let deadline = STOP_DEADLINE_MS.load(Ordering::SeqCst);\r\n    if deadline == 0 {\r\n        return;\r\n    }\r\n\r\n    if now_ms() >= deadline {\r\n        let _ = send_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        let _ = send_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let _ = send_amplitude(req.amplitude);\r\n    let effective_duration = resolve_effective_duration_seconds(req);\n    schedule_stop_after(effective_duration);\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n    process_pending_stop();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":166,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\openvr_runtime.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","scheme":"file"},"requestId":"request_66e3538f-7fed-4c58-b7f0-e5edecfabab8","epoch":1568,"edits":[{"text":"//! OpenVR runtime integration helpers.\r\n//!\r\n//! This module owns driver-context interface resolution, tracked-device\r\n//! registration, component/property setup, and OpenVR haptic event routing.\r\n\r\nuse core::ffi::c_void;\r\nuse core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU64, Ordering};\r\nuse std::ffi::CString;\r\nuse std::os::raw::c_char;\r\n\r\nuse crate::haptics;\r\n\r\ntype EvRInitError = i32;\r\n\r\nconst IVR_SERVER_DRIVER_HOST_VERSION: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\nconst IVR_DRIVER_INPUT_VERSION: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_PROPERTIES_VERSION: &[u8] = b\"IVRProperties_001\\0\";\r\nconst IVR_DRIVER_LOG_VERSION: &[u8] = b\"IVRDriverLog_001\\0\";\r\n\r\nconst TRACKED_DEVICE_CLASS_CONTROLLER: i32 = 2;\r\nconst VREVENT_INPUT_HAPTIC_VIBRATION: u32 = 1700;\r\n\r\nconst PROP_MODEL_NUMBER_STRING: u32 = 1001;\r\nconst PROP_SERIAL_NUMBER_STRING: u32 = 1002;\r\nconst PROP_TRACKING_SYSTEM_NAME_STRING: u32 = 1000;\r\nconst PROP_REGISTERED_DEVICE_TYPE_STRING: u32 = 1036;\r\nconst PROP_INPUT_PROFILE_PATH_STRING: u32 = 1037;\r\nconst PROP_CONTROLLER_TYPE_STRING: u32 = 7000;\r\nconst PROP_CONTROLLER_ROLE_HINT_INT32: u32 = 3007;\r\nconst PROP_HAS_CONTROLLER_COMPONENT_BOOL: u32 = 6003;\r\n\r\nconst K_UN_BOOL_PROPERTY_TAG: u32 = 4;\r\nconst K_UN_INT32_PROPERTY_TAG: u32 = 2;\r\nconst K_UN_STRING_PROPERTY_TAG: u32 = 5;\r\nconst PROPERTY_WRITE_SET: i32 = 0;\r\n\r\nconst DEVICE_SERIAL_CSTR: &[u8] = b\"qtpy-samd21-haptic-001\\0\";\r\nconst DEVICE_MODEL_CSTR: &[u8] = b\"QT Py SAMD21 Haptic\\0\";\r\nconst DEVICE_TRACKING_SYSTEM_NAME_CSTR: &[u8] = b\"big_haptic_driver\\0\";\r\nconst DEVICE_REGISTERED_TYPE_CSTR: &[u8] = b\"big_haptic_driver/controller\\0\";\r\nconst DEVICE_CONTROLLER_TYPE_CSTR: &[u8] = b\"big_haptic_driver\\0\";\r\nconst DEVICE_INPUT_PROFILE_CSTR: &[u8] = b\"{big_haptic_driver}/input/big_haptic_profile.json\\0\";\r\nconst DEVICE_HAPTIC_PATH_CSTR: &[u8] = b\"/output/haptic\\0\";\r\n\r\nconst PROVIDER_IFACE_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_004\\0\";\r\nconst PROVIDER_IFACE_ALT_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_005\\0\";\r\nconst DEVICE_IFACE_CSTR: &[u8] = b\"ITrackedDeviceServerDriver_005\\0\";\r\n\r\nconst IVR_PROPERTIES_CSTR: &[u8] = b\"IVRProperties_001\\0\";\r\nconst IVR_DRIVER_INPUT_CSTR: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_SERVER_DRIVER_HOST_CSTR: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\nconst IVR_DRIVER_LOG_CSTR: &[u8] = b\"IVRDriverLog_001\\0\";\r\n\r\nconst IVR_SETTINGS_CSTR: &[u8] = b\"IVRSettings_003\\0\";\r\nconst IVR_DRIVER_MANAGER_CSTR: &[u8] = b\"IVRDriverManager_001\\0\";\r\nconst IVR_RESOURCES_CSTR: &[u8] = b\"IVRResources_001\\0\";\r\n\r\n#[repr(C)]\r\nstruct InterfaceVersions([*const c_char; 11]);\r\nunsafe impl Sync for InterfaceVersions {}\r\n\r\nstatic INTERFACE_VERSIONS: InterfaceVersions = InterfaceVersions([\r\n    IVR_SETTINGS_CSTR.as_ptr().cast(),\r\n    DEVICE_IFACE_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_CSTR.as_ptr().cast(),\r\n    IVR_PROPERTIES_CSTR.as_ptr().cast(),\r\n    IVR_SERVER_DRIVER_HOST_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_LOG_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_MANAGER_CSTR.as_ptr().cast(),\r\n    IVR_RESOURCES_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_INPUT_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_ALT_CSTR.as_ptr().cast(),\r\n    core::ptr::null(),\r\n]);\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREventHapticVibration {\r\n    container_handle: u64,\r\n    component_handle: u64,\r\n    duration_seconds: f32,\r\n    frequency: f32,\r\n    amplitude: f32,\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nunion VREventData {\r\n    haptic_vibration: VREventHapticVibration,\r\n    reserved: [u8; 48],\r\n}\r\n\r\nimpl Default for VREventData {\r\n    fn default() -> Self {\r\n        Self { reserved: [0; 48] }\r\n    }\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREvent {\r\n    event_type: u32,\r\n    tracked_device_index: u32,\r\n    event_age_seconds: f32,\r\n    data: VREventData,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContextVTable {\r\n    get_generic_interface:\r\n        extern \"C\" fn(*mut DriverContext, *const c_char, *mut EvRInitError) -> *mut c_void,\r\n    get_driver_handle: extern \"C\" fn(*mut DriverContext) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContext {\r\n    vtable: *const DriverContextVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHostVTable {\r\n    tracked_device_added:\r\n        extern \"C\" fn(*mut ServerDriverHost, *const c_char, i32, *mut c_void) -> bool,\r\n    tracked_device_pose_updated: extern \"C\" fn(*mut ServerDriverHost, u32, *const c_void, u32),\r\n    vsync_event: extern \"C\" fn(*mut ServerDriverHost, f64),\r\n    vendor_specific_event: extern \"C\" fn(*mut ServerDriverHost, u32, u32, *const VREventData, f64),\r\n    is_exiting: extern \"C\" fn(*mut ServerDriverHost) -> bool,\r\n    poll_next_event: extern \"C\" fn(*mut ServerDriverHost, *mut VREvent, u32) -> bool,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHost {\r\n    vtable: *const ServerDriverHostVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverLogVTable {\r\n    log: extern \"C\" fn(*mut DriverLog, *const c_char),\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverLog {\r\n    vtable: *const DriverLogVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverInputVTable {\r\n    create_boolean_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n    update_boolean_component: extern \"C\" fn(*mut DriverInput, u64, bool, f64) -> i32,\r\n    create_scalar_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64, i32, i32) -> i32,\r\n    update_scalar_component: extern \"C\" fn(*mut DriverInput, u64, f32, f64) -> i32,\r\n    create_haptic_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverInput {\r\n    vtable: *const DriverInputVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct PropertiesVTable {\r\n    read_property_batch: extern \"C\" fn(*mut Properties, u64, *mut c_void, u32) -> i32,\r\n    write_property_batch: extern \"C\" fn(*mut Properties, u64, *mut PropertyWrite, u32) -> i32,\r\n    get_prop_error_name_from_enum: extern \"C\" fn(*mut Properties, i32) -> *const c_char,\r\n    tracked_device_to_property_container: extern \"C\" fn(*mut Properties, u32) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct Properties {\r\n    vtable: *const PropertiesVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct PropertyWrite {\r\n    prop: u32,\r\n    write_type: i32,\r\n    set_error: i32,\r\n    pv_buffer: *mut c_void,\r\n    un_buffer_size: u32,\r\n    un_tag: u32,\r\n    e_error: i32,\r\n}\r\n\r\nstatic DEVICE_REGISTERED: AtomicBool = AtomicBool::new(false);\r\n\r\nstatic SERVER_HOST_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DRIVER_INPUT_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic PROPERTIES_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DRIVER_LOG_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\n\r\nstatic DEVICE_CONTAINER: AtomicU64 = AtomicU64::new(0);\r\nstatic HAPTIC_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\n\r\npub fn interface_versions_ptr() -> *const *const c_char {\r\n    INTERFACE_VERSIONS.0.as_ptr()\r\n}\r\n\r\nfn get_server_host() -> Option<*mut ServerDriverHost> {\r\n    let ptr = SERVER_HOST_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_driver_input() -> Option<*mut DriverInput> {\r\n    let ptr = DRIVER_INPUT_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_properties() -> Option<*mut Properties> {\r\n    let ptr = PROPERTIES_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_driver_log() -> Option<*mut DriverLog> {\r\n    let ptr = DRIVER_LOG_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn log_driver_message(message: &str) {\r\n    let Some(driver_log) = get_driver_log() else {\r\n        return;\r\n    };\r\n\r\n    let Ok(c_message) = CString::new(message) else {\r\n        return;\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*driver_log).vtable).log)(driver_log, c_message.as_ptr());\r\n    }\r\n}\r\n\r\nfn write_string_property(props: *mut Properties, container: u64, prop: u32, value_nul: &'static [u8]) {\r\n    if props.is_null() || value_nul.is_empty() {\r\n        return;\r\n    }\r\n\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: value_nul.as_ptr().cast_mut().cast(),\r\n        un_buffer_size: value_nul.len() as u32,\r\n        un_tag: K_UN_STRING_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn write_bool_property(props: *mut Properties, container: u64, prop: u32, value: bool) {\r\n    if props.is_null() {\r\n        return;\r\n    }\r\n\r\n    let mut bool_value = value;\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: (&mut bool_value as *mut bool).cast(),\r\n        un_buffer_size: core::mem::size_of::<bool>() as u32,\r\n        un_tag: K_UN_BOOL_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn write_int32_property(props: *mut Properties, container: u64, prop: u32, value: i32) {\r\n    if props.is_null() {\r\n        return;\r\n    }\r\n\r\n    let mut int_value = value;\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: (&mut int_value as *mut i32).cast(),\r\n        un_buffer_size: core::mem::size_of::<i32>() as u32,\r\n        un_tag: K_UN_INT32_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn register_tracked_device_if_needed(device_ptr: *mut c_void) {\r\n    if DEVICE_REGISTERED.load(Ordering::SeqCst) {\r\n        return;\r\n    }\r\n\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    let added = unsafe {\r\n        ((*(*host).vtable).tracked_device_added)(\r\n            host,\r\n            DEVICE_SERIAL_CSTR.as_ptr().cast(),\r\n            TRACKED_DEVICE_CLASS_CONTROLLER,\r\n            device_ptr,\r\n        )\r\n    };\r\n\r\n    if added {\r\n        DEVICE_REGISTERED.store(true, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\npub fn initialize(driver_context: *mut c_void, device_ptr: *mut c_void) -> bool {\r\n    if driver_context.is_null() {\r\n        return false;\r\n    }\r\n\r\n    let ctx = driver_context.cast::<DriverContext>();\r\n    let mut err = 0;\r\n\r\n    let get_iface = unsafe { (*(*ctx).vtable).get_generic_interface };\r\n\r\n    let host = get_iface(\r\n        ctx,\r\n        IVR_SERVER_DRIVER_HOST_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let driver_input = get_iface(\r\n        ctx,\r\n        IVR_DRIVER_INPUT_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let properties = get_iface(\r\n        ctx,\r\n        IVR_PROPERTIES_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let driver_log = get_iface(\r\n        ctx,\r\n        IVR_DRIVER_LOG_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    SERVER_HOST_IFACE.store(host, Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(driver_input, Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(properties, Ordering::SeqCst);\r\n    DRIVER_LOG_IFACE.store(driver_log, Ordering::SeqCst);\r\n\r\n    let ok = !host.is_null() && !driver_input.is_null() && !properties.is_null();\r\n    if !ok {\r\n        return false;\r\n    }\r\n\r\n    log_driver_message(&format!(\r\n        \"[big_haptic_driver] OpenVR init ok; sizeof(VREvent)={}, sizeof(VREventHapticVibration)={}\",\r\n        core::mem::size_of::<VREvent>(),\r\n        core::mem::size_of::<VREventHapticVibration>()\r\n    ));\r\n\r\n    register_tracked_device_if_needed(device_ptr);\r\n    true\r\n}\r\n\r\npub fn cleanup() {\r\n    DEVICE_REGISTERED.store(false, Ordering::SeqCst);\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    SERVER_HOST_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DRIVER_LOG_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n}\r\n\r\npub fn route_haptic_events() {\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    loop {\r\n        let mut event = VREvent::default();\r\n        let ok = unsafe {\r\n            ((*(*host).vtable).poll_next_event)(\r\n                host,\r\n                &mut event as *mut VREvent,\r\n                core::mem::size_of::<VREvent>() as u32,\r\n            )\r\n        };\r\n\r\n        if !ok {\r\n            break;\r\n        }\r\n\r\n        if event.event_type != VREVENT_INPUT_HAPTIC_VIBRATION {\r\n            continue;\r\n        }\r\n\r\n        let hv = unsafe { event.data.haptic_vibration };\r\n\r\n        log_driver_message(&format!(\r\n            \"[big_haptic_driver] haptic event: dev={} container={} component={} duration_s={:.6} (0x{:08x}) freq={:.3} amp={:.6} (0x{:08x})\",\r\n            event.tracked_device_index,\r\n            hv.container_handle,\r\n            hv.component_handle,\r\n            hv.duration_seconds,\r\n            hv.duration_seconds.to_bits(),\r\n            hv.frequency,\r\n            hv.amplitude,\r\n            hv.amplitude.to_bits()\r\n        ));\r\n\n        if hv.amplitude > 0.0 && hv.duration_seconds <= 0.0 {\n            log_driver_message(&format!(\n                \"[big_haptic_driver] note: OpenVR sent non-positive duration; driver will apply implicit finite pulse (freq={:.3})\",\n                hv.frequency\n            ));\n        }\n\r\n        let container = DEVICE_CONTAINER.load(Ordering::SeqCst);\r\n        if container != 0 && hv.container_handle != 0 && hv.container_handle != container {\r\n            log_driver_message(&format!(\r\n                \"[big_haptic_driver] skipped haptic event: container mismatch event={} driver={}\",\r\n                hv.container_handle, container\r\n            ));\r\n            continue;\r\n        }\r\n\r\n        let haptic_component = HAPTIC_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n        if haptic_component != 0 && hv.component_handle != 0 && hv.component_handle != haptic_component {\r\n            log_driver_message(&format!(\r\n                \"[big_haptic_driver] skipped haptic event: component mismatch event={} driver={}\",\r\n                hv.component_handle, haptic_component\r\n            ));\r\n            continue;\r\n        }\r\n\r\n        log_driver_message(\"[big_haptic_driver] forwarding haptic request to HID layer\");\r\n\r\n        haptics::handle_haptic_request(haptics::HapticVibrationRequest {\r\n            amplitude: hv.amplitude,\r\n            duration_seconds: hv.duration_seconds,\r\n            frequency: hv.frequency,\r\n        });\r\n    }\r\n}\r\n\r\npub fn push_pose_update(object_id: u32, pose: *const c_void, pose_size: u32) {\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    if pose.is_null() || pose_size == 0 {\r\n        return;\r\n    }\r\n\r\n    unsafe {\r\n        ((*(*host).vtable).tracked_device_pose_updated)(host, object_id, pose, pose_size);\r\n    }\r\n}\r\n\r\npub fn activate_device(object_id: u32) -> bool {\r\n    let Some(props) = get_properties() else {\r\n        return false;\r\n    };\r\n\r\n    let container = unsafe { ((*(*props).vtable).tracked_device_to_property_container)(props, object_id) };\r\n    DEVICE_CONTAINER.store(container, Ordering::SeqCst);\r\n\r\n    write_string_property(props, container, PROP_MODEL_NUMBER_STRING, DEVICE_MODEL_CSTR);\r\n    write_string_property(props, container, PROP_SERIAL_NUMBER_STRING, DEVICE_SERIAL_CSTR);\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_TRACKING_SYSTEM_NAME_STRING,\r\n        DEVICE_TRACKING_SYSTEM_NAME_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_REGISTERED_DEVICE_TYPE_STRING,\r\n        DEVICE_REGISTERED_TYPE_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_INPUT_PROFILE_PATH_STRING,\r\n        DEVICE_INPUT_PROFILE_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_CONTROLLER_TYPE_STRING,\r\n        DEVICE_CONTROLLER_TYPE_CSTR,\r\n    );\r\n    write_bool_property(props, container, PROP_HAS_CONTROLLER_COMPONENT_BOOL, true);\r\n    write_int32_property(props, container, PROP_CONTROLLER_ROLE_HINT_INT32, 1);\r\n\r\n    if let Some(input) = get_driver_input() {\r\n        let mut handle = 0_u64;\r\n        let rc = unsafe {\r\n            ((*(*input).vtable).create_haptic_component)(\r\n                input,\r\n                container,\r\n                DEVICE_HAPTIC_PATH_CSTR.as_ptr().cast(),\r\n                &mut handle as *mut u64,\r\n            )\r\n        };\r\n\r\n        if rc == 0 {\r\n            HAPTIC_COMPONENT_HANDLE.store(handle, Ordering::SeqCst);\r\n        }\r\n    }\r\n\r\n    true\r\n}\r\n\r\npub fn deactivate_device() {\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":547,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_940afaca-5401-4dbf-a524-3fba59fd2ac5","epoch":1572,"edits":[{"text":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse core::sync::atomic::{AtomicU64, Ordering};\r\nuse std::sync::{Mutex, OnceLock};\r\nuse std::time::Instant;\r\nuse std::time::Duration;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic STOP_DEADLINE_MS: AtomicU64 = AtomicU64::new(0);\r\nstatic STOP_TIMER_TOKEN: AtomicU64 = AtomicU64::new(0);\r\nstatic START_INSTANT: OnceLock<Instant> = OnceLock::new();\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\n\r\nconst OPENVR_MIN_FREQUENCY_HZ: f32 = 1_000_000.0 / 65_535.0;\nconst OPENVR_MAX_FREQUENCY_HZ: f32 = 1_000_000.0 / 300.0;\nconst OPENVR_MAX_DURATION_SECONDS: f32 = 10.0;\n\r\nfn send_packet(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    hid_bridge::HidBridge::new()\r\n        .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    send_packet(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn now_ms() -> u64 {\r\n    let start = START_INSTANT.get_or_init(Instant::now);\r\n    let ms = Instant::now().duration_since(*start).as_millis();\r\n    ms.min(u128::from(u64::MAX)) as u64\r\n}\r\n\r\nfn schedule_stop_after(duration_seconds: f32) {\r\n    if duration_seconds <= 0.0 {\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let duration_ms = (duration_seconds * 1000.0).round().max(1.0) as u64;\r\n    let deadline = now_ms().saturating_add(duration_ms);\r\n    STOP_DEADLINE_MS.store(deadline, Ordering::SeqCst);\r\n\r\n    let token = STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst).saturating_add(1);\r\n    std::thread::spawn(move || {\r\n        std::thread::sleep(Duration::from_millis(duration_ms));\r\n\r\n        if STOP_TIMER_TOKEN.load(Ordering::SeqCst) != token {\r\n            return;\r\n        }\r\n\r\n        let _ = send_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    });\r\n}\r\n\r\nfn resolve_effective_duration_seconds(duration_seconds: f32, frequency_hz: f32) -> f32 {\n    let clamped_duration = duration_seconds.clamp(0.0, OPENVR_MAX_DURATION_SECONDS);\n    if clamped_duration > 0.0 {\n        return clamped_duration;\n    }\n\n    // OpenVR guidance: when duration is 0, trigger exactly one pulse.\n    // We approximate one pulse as half a period at the requested frequency.\n    (0.5 / frequency_hz).max(0.001)\n}\r\n\r\nfn process_pending_stop() {\r\n    let deadline = STOP_DEADLINE_MS.load(Ordering::SeqCst);\r\n    if deadline == 0 {\r\n        return;\r\n    }\r\n\r\n    if now_ms() >= deadline {\r\n        let _ = send_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        let _ = send_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\n    if req.frequency <= 0.0 {\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\n        let _ = send_stop();\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\n        return;\n    }\n\n    let amplitude = req.amplitude.clamp(0.0, 1.0);\n    let frequency_hz = req\n        .frequency\n        .clamp(OPENVR_MIN_FREQUENCY_HZ, OPENVR_MAX_FREQUENCY_HZ);\n\r\n    let _ = send_amplitude(amplitude);\n    let effective_duration = resolve_effective_duration_seconds(req.duration_seconds, frequency_hz);\n    schedule_stop_after(effective_duration);\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n    process_pending_stop();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":177,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\openvr_runtime.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","scheme":"file"},"requestId":"request_940afaca-5401-4dbf-a524-3fba59fd2ac5","epoch":1575,"edits":[{"text":"//! OpenVR runtime integration helpers.\r\n//!\r\n//! This module owns driver-context interface resolution, tracked-device\r\n//! registration, component/property setup, and OpenVR haptic event routing.\r\n\r\nuse core::ffi::c_void;\r\nuse core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU64, Ordering};\r\nuse std::ffi::CString;\r\nuse std::os::raw::c_char;\r\n\r\nuse crate::haptics;\r\n\r\ntype EvRInitError = i32;\r\n\r\nconst IVR_SERVER_DRIVER_HOST_VERSION: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\nconst IVR_DRIVER_INPUT_VERSION: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_PROPERTIES_VERSION: &[u8] = b\"IVRProperties_001\\0\";\r\nconst IVR_DRIVER_LOG_VERSION: &[u8] = b\"IVRDriverLog_001\\0\";\r\n\r\nconst TRACKED_DEVICE_CLASS_CONTROLLER: i32 = 2;\r\nconst VREVENT_INPUT_HAPTIC_VIBRATION: u32 = 1700;\r\n\r\nconst PROP_MODEL_NUMBER_STRING: u32 = 1001;\r\nconst PROP_SERIAL_NUMBER_STRING: u32 = 1002;\r\nconst PROP_TRACKING_SYSTEM_NAME_STRING: u32 = 1000;\r\nconst PROP_REGISTERED_DEVICE_TYPE_STRING: u32 = 1036;\r\nconst PROP_INPUT_PROFILE_PATH_STRING: u32 = 1037;\r\nconst PROP_CONTROLLER_TYPE_STRING: u32 = 7000;\r\nconst PROP_CONTROLLER_ROLE_HINT_INT32: u32 = 3007;\r\nconst PROP_HAS_CONTROLLER_COMPONENT_BOOL: u32 = 6003;\r\n\r\nconst K_UN_BOOL_PROPERTY_TAG: u32 = 4;\r\nconst K_UN_INT32_PROPERTY_TAG: u32 = 2;\r\nconst K_UN_STRING_PROPERTY_TAG: u32 = 5;\r\nconst PROPERTY_WRITE_SET: i32 = 0;\r\n\r\nconst DEVICE_SERIAL_CSTR: &[u8] = b\"qtpy-samd21-haptic-001\\0\";\r\nconst DEVICE_MODEL_CSTR: &[u8] = b\"QT Py SAMD21 Haptic\\0\";\r\nconst DEVICE_TRACKING_SYSTEM_NAME_CSTR: &[u8] = b\"big_haptic_driver\\0\";\r\nconst DEVICE_REGISTERED_TYPE_CSTR: &[u8] = b\"big_haptic_driver/controller\\0\";\r\nconst DEVICE_CONTROLLER_TYPE_CSTR: &[u8] = b\"big_haptic_driver\\0\";\r\nconst DEVICE_INPUT_PROFILE_CSTR: &[u8] = b\"{big_haptic_driver}/input/big_haptic_profile.json\\0\";\r\nconst DEVICE_HAPTIC_PATH_CSTR: &[u8] = b\"/output/haptic\\0\";\r\n\r\nconst PROVIDER_IFACE_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_004\\0\";\r\nconst PROVIDER_IFACE_ALT_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_005\\0\";\r\nconst DEVICE_IFACE_CSTR: &[u8] = b\"ITrackedDeviceServerDriver_005\\0\";\r\n\r\nconst IVR_PROPERTIES_CSTR: &[u8] = b\"IVRProperties_001\\0\";\r\nconst IVR_DRIVER_INPUT_CSTR: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_SERVER_DRIVER_HOST_CSTR: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\nconst IVR_DRIVER_LOG_CSTR: &[u8] = b\"IVRDriverLog_001\\0\";\r\n\r\nconst IVR_SETTINGS_CSTR: &[u8] = b\"IVRSettings_003\\0\";\r\nconst IVR_DRIVER_MANAGER_CSTR: &[u8] = b\"IVRDriverManager_001\\0\";\r\nconst IVR_RESOURCES_CSTR: &[u8] = b\"IVRResources_001\\0\";\r\n\r\n#[repr(C)]\r\nstruct InterfaceVersions([*const c_char; 11]);\r\nunsafe impl Sync for InterfaceVersions {}\r\n\r\nstatic INTERFACE_VERSIONS: InterfaceVersions = InterfaceVersions([\r\n    IVR_SETTINGS_CSTR.as_ptr().cast(),\r\n    DEVICE_IFACE_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_CSTR.as_ptr().cast(),\r\n    IVR_PROPERTIES_CSTR.as_ptr().cast(),\r\n    IVR_SERVER_DRIVER_HOST_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_LOG_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_MANAGER_CSTR.as_ptr().cast(),\r\n    IVR_RESOURCES_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_INPUT_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_ALT_CSTR.as_ptr().cast(),\r\n    core::ptr::null(),\r\n]);\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREventHapticVibration {\r\n    container_handle: u64,\r\n    component_handle: u64,\r\n    duration_seconds: f32,\r\n    frequency: f32,\r\n    amplitude: f32,\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nunion VREventData {\r\n    haptic_vibration: VREventHapticVibration,\r\n    reserved: [u8; 48],\r\n}\r\n\r\nimpl Default for VREventData {\r\n    fn default() -> Self {\r\n        Self { reserved: [0; 48] }\r\n    }\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREvent {\r\n    event_type: u32,\r\n    tracked_device_index: u32,\r\n    event_age_seconds: f32,\r\n    data: VREventData,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContextVTable {\r\n    get_generic_interface:\r\n        extern \"C\" fn(*mut DriverContext, *const c_char, *mut EvRInitError) -> *mut c_void,\r\n    get_driver_handle: extern \"C\" fn(*mut DriverContext) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContext {\r\n    vtable: *const DriverContextVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHostVTable {\r\n    tracked_device_added:\r\n        extern \"C\" fn(*mut ServerDriverHost, *const c_char, i32, *mut c_void) -> bool,\r\n    tracked_device_pose_updated: extern \"C\" fn(*mut ServerDriverHost, u32, *const c_void, u32),\r\n    vsync_event: extern \"C\" fn(*mut ServerDriverHost, f64),\r\n    vendor_specific_event: extern \"C\" fn(*mut ServerDriverHost, u32, u32, *const VREventData, f64),\r\n    is_exiting: extern \"C\" fn(*mut ServerDriverHost) -> bool,\r\n    poll_next_event: extern \"C\" fn(*mut ServerDriverHost, *mut VREvent, u32) -> bool,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHost {\r\n    vtable: *const ServerDriverHostVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverLogVTable {\r\n    log: extern \"C\" fn(*mut DriverLog, *const c_char),\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverLog {\r\n    vtable: *const DriverLogVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverInputVTable {\r\n    create_boolean_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n    update_boolean_component: extern \"C\" fn(*mut DriverInput, u64, bool, f64) -> i32,\r\n    create_scalar_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64, i32, i32) -> i32,\r\n    update_scalar_component: extern \"C\" fn(*mut DriverInput, u64, f32, f64) -> i32,\r\n    create_haptic_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverInput {\r\n    vtable: *const DriverInputVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct PropertiesVTable {\r\n    read_property_batch: extern \"C\" fn(*mut Properties, u64, *mut c_void, u32) -> i32,\r\n    write_property_batch: extern \"C\" fn(*mut Properties, u64, *mut PropertyWrite, u32) -> i32,\r\n    get_prop_error_name_from_enum: extern \"C\" fn(*mut Properties, i32) -> *const c_char,\r\n    tracked_device_to_property_container: extern \"C\" fn(*mut Properties, u32) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct Properties {\r\n    vtable: *const PropertiesVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct PropertyWrite {\r\n    prop: u32,\r\n    write_type: i32,\r\n    set_error: i32,\r\n    pv_buffer: *mut c_void,\r\n    un_buffer_size: u32,\r\n    un_tag: u32,\r\n    e_error: i32,\r\n}\r\n\r\nstatic DEVICE_REGISTERED: AtomicBool = AtomicBool::new(false);\r\n\r\nstatic SERVER_HOST_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DRIVER_INPUT_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic PROPERTIES_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DRIVER_LOG_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\n\r\nstatic DEVICE_CONTAINER: AtomicU64 = AtomicU64::new(0);\r\nstatic HAPTIC_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\nstatic HAPTIC_EVENT_SEQ: AtomicU64 = AtomicU64::new(0);\n\r\npub fn interface_versions_ptr() -> *const *const c_char {\r\n    INTERFACE_VERSIONS.0.as_ptr()\r\n}\r\n\r\nfn get_server_host() -> Option<*mut ServerDriverHost> {\r\n    let ptr = SERVER_HOST_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_driver_input() -> Option<*mut DriverInput> {\r\n    let ptr = DRIVER_INPUT_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_properties() -> Option<*mut Properties> {\r\n    let ptr = PROPERTIES_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_driver_log() -> Option<*mut DriverLog> {\r\n    let ptr = DRIVER_LOG_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn log_driver_message(message: &str) {\r\n    let Some(driver_log) = get_driver_log() else {\r\n        return;\r\n    };\r\n\r\n    let Ok(c_message) = CString::new(message) else {\r\n        return;\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*driver_log).vtable).log)(driver_log, c_message.as_ptr());\r\n    }\r\n}\r\n\r\nfn write_string_property(props: *mut Properties, container: u64, prop: u32, value_nul: &'static [u8]) {\r\n    if props.is_null() || value_nul.is_empty() {\r\n        return;\r\n    }\r\n\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: value_nul.as_ptr().cast_mut().cast(),\r\n        un_buffer_size: value_nul.len() as u32,\r\n        un_tag: K_UN_STRING_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn write_bool_property(props: *mut Properties, container: u64, prop: u32, value: bool) {\r\n    if props.is_null() {\r\n        return;\r\n    }\r\n\r\n    let mut bool_value = value;\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: (&mut bool_value as *mut bool).cast(),\r\n        un_buffer_size: core::mem::size_of::<bool>() as u32,\r\n        un_tag: K_UN_BOOL_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn write_int32_property(props: *mut Properties, container: u64, prop: u32, value: i32) {\r\n    if props.is_null() {\r\n        return;\r\n    }\r\n\r\n    let mut int_value = value;\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: (&mut int_value as *mut i32).cast(),\r\n        un_buffer_size: core::mem::size_of::<i32>() as u32,\r\n        un_tag: K_UN_INT32_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn register_tracked_device_if_needed(device_ptr: *mut c_void) {\r\n    if DEVICE_REGISTERED.load(Ordering::SeqCst) {\r\n        return;\r\n    }\r\n\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    let added = unsafe {\r\n        ((*(*host).vtable).tracked_device_added)(\r\n            host,\r\n            DEVICE_SERIAL_CSTR.as_ptr().cast(),\r\n            TRACKED_DEVICE_CLASS_CONTROLLER,\r\n            device_ptr,\r\n        )\r\n    };\r\n\r\n    if added {\r\n        DEVICE_REGISTERED.store(true, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\npub fn initialize(driver_context: *mut c_void, device_ptr: *mut c_void) -> bool {\r\n    if driver_context.is_null() {\r\n        return false;\r\n    }\r\n\r\n    let ctx = driver_context.cast::<DriverContext>();\r\n    let mut err = 0;\r\n\r\n    let get_iface = unsafe { (*(*ctx).vtable).get_generic_interface };\r\n\r\n    let host = get_iface(\r\n        ctx,\r\n        IVR_SERVER_DRIVER_HOST_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let driver_input = get_iface(\r\n        ctx,\r\n        IVR_DRIVER_INPUT_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let properties = get_iface(\r\n        ctx,\r\n        IVR_PROPERTIES_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let driver_log = get_iface(\r\n        ctx,\r\n        IVR_DRIVER_LOG_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    SERVER_HOST_IFACE.store(host, Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(driver_input, Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(properties, Ordering::SeqCst);\r\n    DRIVER_LOG_IFACE.store(driver_log, Ordering::SeqCst);\r\n\r\n    let ok = !host.is_null() && !driver_input.is_null() && !properties.is_null();\r\n    if !ok {\r\n        return false;\r\n    }\r\n\r\n    log_driver_message(&format!(\r\n        \"[big_haptic_driver] OpenVR init ok; sizeof(VREvent)={}, sizeof(VREventHapticVibration)={}\",\r\n        core::mem::size_of::<VREvent>(),\r\n        core::mem::size_of::<VREventHapticVibration>()\r\n    ));\r\n\r\n    register_tracked_device_if_needed(device_ptr);\r\n    true\r\n}\r\n\r\npub fn cleanup() {\r\n    DEVICE_REGISTERED.store(false, Ordering::SeqCst);\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    SERVER_HOST_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DRIVER_LOG_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n}\r\n\r\npub fn route_haptic_events() {\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    loop {\r\n        let mut event = VREvent::default();\r\n        let ok = unsafe {\r\n            ((*(*host).vtable).poll_next_event)(\r\n                host,\r\n                &mut event as *mut VREvent,\r\n                core::mem::size_of::<VREvent>() as u32,\r\n            )\r\n        };\r\n\r\n        if !ok {\r\n            break;\r\n        }\r\n\r\n        if event.event_type != VREVENT_INPUT_HAPTIC_VIBRATION {\r\n            continue;\r\n        }\r\n\r\n        let hv = unsafe { event.data.haptic_vibration };\r\n        let seq = HAPTIC_EVENT_SEQ.fetch_add(1, Ordering::SeqCst).saturating_add(1);\n\r\n        log_driver_message(&format!(\r\n            \"[big_haptic_driver] haptic event #{}: dev={} age_s={:.6} container={} component={} duration_s={:.6} (0x{:08x}) freq={:.3} amp={:.6} (0x{:08x})\",\n            seq,\n            event.tracked_device_index,\r\n            event.event_age_seconds,\n            hv.container_handle,\r\n            hv.component_handle,\r\n            hv.duration_seconds,\r\n            hv.duration_seconds.to_bits(),\r\n            hv.frequency,\r\n            hv.amplitude,\r\n            hv.amplitude.to_bits()\r\n        ));\r\n\r\n        if hv.amplitude > 0.0 && hv.duration_seconds <= 0.0 {\r\n            log_driver_message(&format!(\r\n                \"[big_haptic_driver] note: OpenVR sent duration<=0; per OpenVR guidance driver will emit one finite pulse (freq={:.3})\",\n                hv.frequency\r\n            ));\r\n        }\r\n\r\n        let container = DEVICE_CONTAINER.load(Ordering::SeqCst);\r\n        if container != 0 && hv.container_handle != 0 && hv.container_handle != container {\r\n            log_driver_message(&format!(\r\n                \"[big_haptic_driver] skipped haptic event: container mismatch event={} driver={}\",\r\n                hv.container_handle, container\r\n            ));\r\n            continue;\r\n        }\r\n\r\n        let haptic_component = HAPTIC_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n        if haptic_component != 0 && hv.component_handle != 0 && hv.component_handle != haptic_component {\r\n            log_driver_message(&format!(\r\n                \"[big_haptic_driver] skipped haptic event: component mismatch event={} driver={}\",\r\n                hv.component_handle, haptic_component\r\n            ));\r\n            continue;\r\n        }\r\n\r\n        log_driver_message(\"[big_haptic_driver] forwarding haptic request to HID layer\");\r\n\r\n        haptics::handle_haptic_request(haptics::HapticVibrationRequest {\r\n            amplitude: hv.amplitude,\r\n            duration_seconds: hv.duration_seconds,\r\n            frequency: hv.frequency,\r\n        });\r\n    }\r\n}\r\n\r\npub fn push_pose_update(object_id: u32, pose: *const c_void, pose_size: u32) {\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    if pose.is_null() || pose_size == 0 {\r\n        return;\r\n    }\r\n\r\n    unsafe {\r\n        ((*(*host).vtable).tracked_device_pose_updated)(host, object_id, pose, pose_size);\r\n    }\r\n}\r\n\r\npub fn activate_device(object_id: u32) -> bool {\r\n    let Some(props) = get_properties() else {\r\n        return false;\r\n    };\r\n\r\n    let container = unsafe { ((*(*props).vtable).tracked_device_to_property_container)(props, object_id) };\r\n    DEVICE_CONTAINER.store(container, Ordering::SeqCst);\r\n\r\n    write_string_property(props, container, PROP_MODEL_NUMBER_STRING, DEVICE_MODEL_CSTR);\r\n    write_string_property(props, container, PROP_SERIAL_NUMBER_STRING, DEVICE_SERIAL_CSTR);\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_TRACKING_SYSTEM_NAME_STRING,\r\n        DEVICE_TRACKING_SYSTEM_NAME_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_REGISTERED_DEVICE_TYPE_STRING,\r\n        DEVICE_REGISTERED_TYPE_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_INPUT_PROFILE_PATH_STRING,\r\n        DEVICE_INPUT_PROFILE_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_CONTROLLER_TYPE_STRING,\r\n        DEVICE_CONTROLLER_TYPE_CSTR,\r\n    );\r\n    write_bool_property(props, container, PROP_HAS_CONTROLLER_COMPONENT_BOOL, true);\r\n    write_int32_property(props, container, PROP_CONTROLLER_ROLE_HINT_INT32, 1);\r\n\r\n    if let Some(input) = get_driver_input() {\r\n        let mut handle = 0_u64;\r\n        let rc = unsafe {\r\n            ((*(*input).vtable).create_haptic_component)(\r\n                input,\r\n                container,\r\n                DEVICE_HAPTIC_PATH_CSTR.as_ptr().cast(),\r\n                &mut handle as *mut u64,\r\n            )\r\n        };\r\n\r\n        if rc == 0 {\r\n            HAPTIC_COMPONENT_HANDLE.store(handle, Ordering::SeqCst);\r\n        }\r\n    }\r\n\r\n    true\r\n}\r\n\r\npub fn deactivate_device() {\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":551,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_1d9fb0bc-867e-467e-86a5-8e6b0b5a1c0d","epoch":1580,"edits":[{"text":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse core::sync::atomic::{AtomicU64, Ordering};\r\nuse std::sync::{Mutex, OnceLock};\r\nuse std::time::Instant;\r\nuse std::time::Duration;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic STOP_DEADLINE_MS: AtomicU64 = AtomicU64::new(0);\r\nstatic STOP_TIMER_TOKEN: AtomicU64 = AtomicU64::new(0);\r\nstatic START_INSTANT: OnceLock<Instant> = OnceLock::new();\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\n\r\nconst OPENVR_MIN_FREQUENCY_HZ: f32 = 1_000_000.0 / 65_535.0;\r\nconst OPENVR_MAX_FREQUENCY_HZ: f32 = 1_000_000.0 / 300.0;\r\nconst OPENVR_MAX_DURATION_SECONDS: f32 = 10.0;\r\nconst HARDWARE_MIN_PULSE_SECONDS: f32 = 0.02;\n\r\nfn send_packet(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    hid_bridge::HidBridge::new()\r\n        .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    send_packet(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn now_ms() -> u64 {\r\n    let start = START_INSTANT.get_or_init(Instant::now);\r\n    let ms = Instant::now().duration_since(*start).as_millis();\r\n    ms.min(u128::from(u64::MAX)) as u64\r\n}\r\n\r\nfn schedule_stop_after(duration_seconds: f32) {\r\n    if duration_seconds <= 0.0 {\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let duration_ms = (duration_seconds * 1000.0).round().max(1.0) as u64;\r\n    let deadline = now_ms().saturating_add(duration_ms);\r\n    STOP_DEADLINE_MS.store(deadline, Ordering::SeqCst);\r\n\r\n    let token = STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst).saturating_add(1);\r\n    std::thread::spawn(move || {\r\n        std::thread::sleep(Duration::from_millis(duration_ms));\r\n\r\n        if STOP_TIMER_TOKEN.load(Ordering::SeqCst) != token {\r\n            return;\r\n        }\r\n\r\n        let _ = send_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    });\r\n}\r\n\r\nfn resolve_effective_duration_seconds(duration_seconds: f32, frequency_hz: f32) -> f32 {\n    let clamped_duration = duration_seconds.clamp(0.0, OPENVR_MAX_DURATION_SECONDS);\r\n    if clamped_duration > 0.0 {\r\n        return clamped_duration;\r\n    }\r\n\r\n    // OpenVR guidance: when duration is 0, trigger exactly one pulse.\r\n    // We approximate one pulse as half a period at the requested frequency,\n    // but enforce a hardware minimum so pulses are actually perceptible.\n    (0.5 / frequency_hz).max(HARDWARE_MIN_PULSE_SECONDS)\n}\r\n\r\nfn process_pending_stop() {\r\n    let deadline = STOP_DEADLINE_MS.load(Ordering::SeqCst);\r\n    if deadline == 0 {\r\n        return;\r\n    }\r\n\r\n    if now_ms() >= deadline {\r\n        let _ = send_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        let _ = send_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    if req.frequency <= 0.0 {\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        let _ = send_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let amplitude = req.amplitude.clamp(0.0, 1.0);\r\n    let frequency_hz = req\r\n        .frequency\r\n        .clamp(OPENVR_MIN_FREQUENCY_HZ, OPENVR_MAX_FREQUENCY_HZ);\r\n\r\n    let _ = send_amplitude(amplitude);\r\n    let effective_duration = resolve_effective_duration_seconds(req.duration_seconds, frequency_hz);\r\n    schedule_stop_after(effective_duration);\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n    process_pending_stop();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":179,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_b0ebfe2c-99b2-4212-9657-77428a9baa8d","epoch":1585,"edits":[{"text":"use embedded_hal::i2c::I2c;\r\n\r\nuse crate::drv2605l_parser::Drv2605lCommand;\r\n\r\npub const DRV2605L_I2C_ADDR: u8 = 0x5a;\r\n\r\nconst REG_MODE: u8 = 0x01;\r\nconst REG_RTP_INPUT: u8 = 0x02;\r\nconst REG_LIBRARY_SELECTION: u8 = 0x03;\r\nconst REG_WAVEFORM_SEQ1: u8 = 0x04;\r\nconst REG_WAVEFORM_SEQ2: u8 = 0x05;\r\nconst REG_GO: u8 = 0x0c;\r\n\r\nconst MODE_INTERNAL_TRIGGER: u8 = 0x00;\r\nconst MODE_REALTIME_PLAYBACK: u8 = 0x05;\r\n\r\n#[derive(Clone, Copy, Debug, Eq, PartialEq)]\r\npub enum DispatchError<E> {\r\n    Parse(crate::drv2605l_parser::ParseError),\r\n    I2c(E),\r\n}\r\n\r\npub struct Drv2605l<I2C> {\r\n    i2c: I2C,\r\n    address: u8,\r\n    last_rtp_intensity: u8,\n}\r\n\r\nimpl<I2C> Drv2605l<I2C>\r\nwhere\r\n    I2C: I2c,\r\n{\r\n    pub fn new(i2c: I2C) -> Self {\r\n        Self {\r\n            i2c,\r\n            address: DRV2605L_I2C_ADDR,\r\n            last_rtp_intensity: 0,\n        }\r\n    }\r\n\r\n    pub fn with_address(i2c: I2C, address: u8) -> Self {\r\n        Self {\n            i2c,\n            address,\n            last_rtp_intensity: 0,\n        }\n    }\r\n\r\n    pub fn release(self) -> I2C {\r\n        self.i2c\r\n    }\r\n\r\n    pub fn write_register(&mut self, register: u8, value: u8) -> Result<(), I2C::Error> {\r\n        self.i2c.write(self.address, &[register, value])\r\n    }\r\n\n    fn write_register_retry(&mut self, register: u8, value: u8) -> Result<(), I2C::Error> {\n        // Transient NACKs can occur during bursty command streams; one retry\n        // improves robustness without significantly increasing command latency.\n        if let Err(_first_err) = self.write_register(register, value) {\n            return self.write_register(register, value);\n        }\n        Ok(())\n    }\n\r\n    pub fn set_rtp_mode(&mut self) -> Result<(), I2C::Error> {\r\n        self.write_register_retry(REG_MODE, MODE_REALTIME_PLAYBACK)\n    }\r\n\r\n    pub fn set_rtp_input(&mut self, intensity: u8) -> Result<(), I2C::Error> {\r\n        self.write_register_retry(REG_RTP_INPUT, intensity)\n    }\r\n\r\n    pub fn trigger_rom_effect(&mut self, effect_id: u8) -> Result<(), I2C::Error> {\r\n        self.write_register_retry(REG_MODE, MODE_INTERNAL_TRIGGER)?;\n        self.write_register_retry(REG_LIBRARY_SELECTION, 1)?;\n        self.write_register_retry(REG_WAVEFORM_SEQ1, effect_id)?;\n        self.write_register_retry(REG_WAVEFORM_SEQ2, 0)?;\n        self.write_register_retry(REG_GO, 1)\n    }\r\n\r\n    pub fn stop(&mut self) -> Result<(), I2C::Error> {\r\n        self.write_register_retry(REG_GO, 0)?;\n        self.set_rtp_mode()?;\r\n        self.set_rtp_input(0)?;\n        self.last_rtp_intensity = 0;\n        Ok(())\n    }\n\n    fn apply_set_intensity(&mut self, intensity: u8) -> Result<(), I2C::Error> {\n        self.set_rtp_mode()?;\n\n        // Force a low->high edge for repeated pulses. Some actuators can feel\n        // like they \"drop out\" when only receiving the same non-zero RTP value.\n        if intensity > 0 && self.last_rtp_intensity > 0 {\n            self.set_rtp_input(0)?;\n            self.last_rtp_intensity = 0;\n        }\n\n        self.set_rtp_input(intensity)?;\n        self.last_rtp_intensity = intensity;\n        Ok(())\n    }\n\n    fn recover_after_error(&mut self) {\n        let _ = self.set_rtp_mode();\n        let _ = self.set_rtp_input(0);\n        self.last_rtp_intensity = 0;\n    }\r\n\r\n    pub fn apply_command(&mut self, cmd: Drv2605lCommand) -> Result<(), I2C::Error> {\r\n        let result = match cmd {\n            Drv2605lCommand::SetIntensity { intensity } => {\n                if intensity == 0 {\n                    self.stop()\n                } else {\n                    self.apply_set_intensity(intensity)\n                }\n            }\n            Drv2605lCommand::TriggerRomEffect { effect_id, intensity: _ } => {\n                self.trigger_rom_effect(effect_id)\n            }\n            Drv2605lCommand::Stop => self.stop(),\n            Drv2605lCommand::Unknown { .. } => Ok(()),\n        };\n\n        if result.is_err() {\n            self.recover_after_error();\n        }\r\n\n        result\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":135,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_f47e2855-d76f-4d49-b524-bdc5e267fde9","epoch":1589,"edits":[{"text":"use embedded_hal::i2c::I2c;\r\n\r\nuse crate::drv2605l_parser::Drv2605lCommand;\r\n\r\npub const DRV2605L_I2C_ADDR: u8 = 0x5a;\r\n\r\nconst REG_MODE: u8 = 0x01;\r\nconst REG_RTP_INPUT: u8 = 0x02;\r\nconst REG_LIBRARY_SELECTION: u8 = 0x03;\r\nconst REG_WAVEFORM_SEQ1: u8 = 0x04;\r\nconst REG_WAVEFORM_SEQ2: u8 = 0x05;\r\nconst REG_GO: u8 = 0x0c;\r\n\r\nconst MODE_INTERNAL_TRIGGER: u8 = 0x00;\r\nconst MODE_REALTIME_PLAYBACK: u8 = 0x05;\r\n\r\n#[derive(Clone, Copy, Debug, Eq, PartialEq)]\r\npub enum DispatchError<E> {\r\n    Parse(crate::drv2605l_parser::ParseError),\r\n    I2c(E),\r\n}\r\n\r\npub struct Drv2605l<I2C> {\r\n    i2c: I2C,\r\n    address: u8,\r\n}\r\n\r\nimpl<I2C> Drv2605l<I2C>\r\nwhere\r\n    I2C: I2c,\r\n{\r\n    pub fn new(i2c: I2C) -> Self {\r\n        Self {\r\n            i2c,\r\n            address: DRV2605L_I2C_ADDR,\r\n        }\r\n    }\r\n\r\n    pub fn with_address(i2c: I2C, address: u8) -> Self {\r\n        Self { i2c, address }\n    }\r\n\r\n    pub fn release(self) -> I2C {\r\n        self.i2c\r\n    }\r\n\r\n    pub fn write_register(&mut self, register: u8, value: u8) -> Result<(), I2C::Error> {\r\n        self.i2c.write(self.address, &[register, value])\r\n    }\r\n\r\n    pub fn set_rtp_mode(&mut self) -> Result<(), I2C::Error> {\r\n        self.write_register(REG_MODE, MODE_REALTIME_PLAYBACK)\n    }\r\n\r\n    pub fn set_rtp_input(&mut self, intensity: u8) -> Result<(), I2C::Error> {\r\n        self.write_register(REG_RTP_INPUT, intensity)\n    }\r\n\r\n    pub fn trigger_rom_effect(&mut self, effect_id: u8) -> Result<(), I2C::Error> {\r\n        self.write_register(REG_MODE, MODE_INTERNAL_TRIGGER)?;\n        self.write_register(REG_LIBRARY_SELECTION, 1)?;\n        self.write_register(REG_WAVEFORM_SEQ1, effect_id)?;\n        self.write_register(REG_WAVEFORM_SEQ2, 0)?;\n        self.write_register(REG_GO, 1)\n    }\r\n\r\n    pub fn stop(&mut self) -> Result<(), I2C::Error> {\r\n        self.write_register(REG_GO, 0)?;\n        self.set_rtp_mode()?;\r\n        self.set_rtp_input(0)\n    }\r\n\r\n    pub fn apply_command(&mut self, cmd: Drv2605lCommand) -> Result<(), I2C::Error> {\r\n        match cmd {\n            Drv2605lCommand::SetIntensity { intensity } => {\r\n                self.set_rtp_mode()?;\n                self.set_rtp_input(intensity)\n            }\r\n            Drv2605lCommand::TriggerRomEffect { effect_id, intensity: _ } => {\r\n                self.trigger_rom_effect(effect_id)\r\n            }\r\n            Drv2605lCommand::Stop => self.stop(),\r\n            Drv2605lCommand::Unknown { .. } => Ok(()),\r\n        }\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":88,"endColumn":1}},{"text":"","range":{"startLineNumber":88,"startColumn":1,"endLineNumber":135,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\openvr_runtime.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","scheme":"file"},"requestId":"request_38f8befb-ed20-48d4-a718-4a697ba92587","epoch":1593,"edits":[{"text":"//! OpenVR runtime integration helpers.\r\n//!\r\n//! This module owns driver-context interface resolution, tracked-device\r\n//! registration, component/property setup, and OpenVR haptic event routing.\r\n\r\nuse core::ffi::c_void;\r\nuse core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU64, Ordering};\r\nuse std::ffi::CString;\r\nuse std::os::raw::c_char;\r\n\r\nuse crate::haptics;\r\n\r\ntype EvRInitError = i32;\r\n\r\nconst IVR_SERVER_DRIVER_HOST_VERSION: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\nconst IVR_DRIVER_INPUT_VERSION: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_PROPERTIES_VERSION: &[u8] = b\"IVRProperties_001\\0\";\r\nconst IVR_DRIVER_LOG_VERSION: &[u8] = b\"IVRDriverLog_001\\0\";\r\n\r\nconst TRACKED_DEVICE_CLASS_CONTROLLER: i32 = 2;\r\nconst VREVENT_INPUT_HAPTIC_VIBRATION: u32 = 1700;\r\n\r\nconst PROP_MODEL_NUMBER_STRING: u32 = 1001;\r\nconst PROP_SERIAL_NUMBER_STRING: u32 = 1002;\r\nconst PROP_TRACKING_SYSTEM_NAME_STRING: u32 = 1000;\r\nconst PROP_REGISTERED_DEVICE_TYPE_STRING: u32 = 1036;\r\nconst PROP_INPUT_PROFILE_PATH_STRING: u32 = 1037;\r\nconst PROP_CONTROLLER_TYPE_STRING: u32 = 7000;\r\nconst PROP_CONTROLLER_ROLE_HINT_INT32: u32 = 3007;\r\nconst PROP_HAS_CONTROLLER_COMPONENT_BOOL: u32 = 6003;\r\n\r\nconst K_UN_BOOL_PROPERTY_TAG: u32 = 4;\r\nconst K_UN_INT32_PROPERTY_TAG: u32 = 2;\r\nconst K_UN_STRING_PROPERTY_TAG: u32 = 5;\r\nconst PROPERTY_WRITE_SET: i32 = 0;\r\n\r\nconst DEVICE_SERIAL_CSTR: &[u8] = b\"qtpy-samd21-haptic-001\\0\";\r\nconst DEVICE_MODEL_CSTR: &[u8] = b\"QT Py SAMD21 Haptic\\0\";\r\nconst DEVICE_TRACKING_SYSTEM_NAME_CSTR: &[u8] = b\"big_haptic_driver\\0\";\r\nconst DEVICE_REGISTERED_TYPE_CSTR: &[u8] = b\"big_haptic_driver/controller\\0\";\r\nconst DEVICE_CONTROLLER_TYPE_CSTR: &[u8] = b\"big_haptic_driver\\0\";\r\nconst DEVICE_INPUT_PROFILE_CSTR: &[u8] = b\"{big_haptic_driver}/input/big_haptic_profile.json\\0\";\r\nconst DEVICE_HAPTIC_PATH_CSTR: &[u8] = b\"/output/haptic\\0\";\r\n\r\nconst PROVIDER_IFACE_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_004\\0\";\r\nconst PROVIDER_IFACE_ALT_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_005\\0\";\r\nconst DEVICE_IFACE_CSTR: &[u8] = b\"ITrackedDeviceServerDriver_005\\0\";\r\n\r\nconst IVR_PROPERTIES_CSTR: &[u8] = b\"IVRProperties_001\\0\";\r\nconst IVR_DRIVER_INPUT_CSTR: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_SERVER_DRIVER_HOST_CSTR: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\nconst IVR_DRIVER_LOG_CSTR: &[u8] = b\"IVRDriverLog_001\\0\";\r\n\r\nconst IVR_SETTINGS_CSTR: &[u8] = b\"IVRSettings_003\\0\";\r\nconst IVR_DRIVER_MANAGER_CSTR: &[u8] = b\"IVRDriverManager_001\\0\";\r\nconst IVR_RESOURCES_CSTR: &[u8] = b\"IVRResources_001\\0\";\r\n\r\n#[repr(C)]\r\nstruct InterfaceVersions([*const c_char; 11]);\r\nunsafe impl Sync for InterfaceVersions {}\r\n\r\nstatic INTERFACE_VERSIONS: InterfaceVersions = InterfaceVersions([\r\n    IVR_SETTINGS_CSTR.as_ptr().cast(),\r\n    DEVICE_IFACE_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_CSTR.as_ptr().cast(),\r\n    IVR_PROPERTIES_CSTR.as_ptr().cast(),\r\n    IVR_SERVER_DRIVER_HOST_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_LOG_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_MANAGER_CSTR.as_ptr().cast(),\r\n    IVR_RESOURCES_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_INPUT_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_ALT_CSTR.as_ptr().cast(),\r\n    core::ptr::null(),\r\n]);\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREventHapticVibration {\r\n    container_handle: u64,\r\n    component_handle: u64,\r\n    duration_seconds: f32,\r\n    frequency: f32,\r\n    amplitude: f32,\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nunion VREventData {\r\n    haptic_vibration: VREventHapticVibration,\r\n    reserved: [u8; 48],\r\n}\r\n\r\nimpl Default for VREventData {\r\n    fn default() -> Self {\r\n        Self { reserved: [0; 48] }\r\n    }\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREvent {\r\n    event_type: u32,\r\n    tracked_device_index: u32,\r\n    event_age_seconds: f32,\r\n    data: VREventData,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContextVTable {\r\n    get_generic_interface:\r\n        extern \"C\" fn(*mut DriverContext, *const c_char, *mut EvRInitError) -> *mut c_void,\r\n    get_driver_handle: extern \"C\" fn(*mut DriverContext) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContext {\r\n    vtable: *const DriverContextVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHostVTable {\r\n    tracked_device_added:\r\n        extern \"C\" fn(*mut ServerDriverHost, *const c_char, i32, *mut c_void) -> bool,\r\n    tracked_device_pose_updated: extern \"C\" fn(*mut ServerDriverHost, u32, *const c_void, u32),\r\n    vsync_event: extern \"C\" fn(*mut ServerDriverHost, f64),\r\n    vendor_specific_event: extern \"C\" fn(*mut ServerDriverHost, u32, u32, *const VREventData, f64),\r\n    is_exiting: extern \"C\" fn(*mut ServerDriverHost) -> bool,\r\n    poll_next_event: extern \"C\" fn(*mut ServerDriverHost, *mut VREvent, u32) -> bool,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHost {\r\n    vtable: *const ServerDriverHostVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverLogVTable {\r\n    log: extern \"C\" fn(*mut DriverLog, *const c_char),\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverLog {\r\n    vtable: *const DriverLogVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverInputVTable {\r\n    create_boolean_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n    update_boolean_component: extern \"C\" fn(*mut DriverInput, u64, bool, f64) -> i32,\r\n    create_scalar_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64, i32, i32) -> i32,\r\n    update_scalar_component: extern \"C\" fn(*mut DriverInput, u64, f32, f64) -> i32,\r\n    create_haptic_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverInput {\r\n    vtable: *const DriverInputVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct PropertiesVTable {\r\n    read_property_batch: extern \"C\" fn(*mut Properties, u64, *mut c_void, u32) -> i32,\r\n    write_property_batch: extern \"C\" fn(*mut Properties, u64, *mut PropertyWrite, u32) -> i32,\r\n    get_prop_error_name_from_enum: extern \"C\" fn(*mut Properties, i32) -> *const c_char,\r\n    tracked_device_to_property_container: extern \"C\" fn(*mut Properties, u32) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct Properties {\r\n    vtable: *const PropertiesVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct PropertyWrite {\r\n    prop: u32,\r\n    write_type: i32,\r\n    set_error: i32,\r\n    pv_buffer: *mut c_void,\r\n    un_buffer_size: u32,\r\n    un_tag: u32,\r\n    e_error: i32,\r\n}\r\n\r\nstatic DEVICE_REGISTERED: AtomicBool = AtomicBool::new(false);\r\n\r\nstatic SERVER_HOST_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DRIVER_INPUT_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic PROPERTIES_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DRIVER_LOG_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\n\r\nstatic DEVICE_CONTAINER: AtomicU64 = AtomicU64::new(0);\r\nstatic HAPTIC_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\nstatic HAPTIC_EVENT_SEQ: AtomicU64 = AtomicU64::new(0);\r\n\r\npub fn interface_versions_ptr() -> *const *const c_char {\r\n    INTERFACE_VERSIONS.0.as_ptr()\r\n}\r\n\r\nfn get_server_host() -> Option<*mut ServerDriverHost> {\r\n    let ptr = SERVER_HOST_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_driver_input() -> Option<*mut DriverInput> {\r\n    let ptr = DRIVER_INPUT_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_properties() -> Option<*mut Properties> {\r\n    let ptr = PROPERTIES_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_driver_log() -> Option<*mut DriverLog> {\r\n    let ptr = DRIVER_LOG_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\npub(crate) fn log_driver_message(message: &str) {\n    let Some(driver_log) = get_driver_log() else {\r\n        return;\r\n    };\r\n\r\n    let Ok(c_message) = CString::new(message) else {\r\n        return;\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*driver_log).vtable).log)(driver_log, c_message.as_ptr());\r\n    }\r\n}\r\n\r\nfn write_string_property(props: *mut Properties, container: u64, prop: u32, value_nul: &'static [u8]) {\r\n    if props.is_null() || value_nul.is_empty() {\r\n        return;\r\n    }\r\n\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: value_nul.as_ptr().cast_mut().cast(),\r\n        un_buffer_size: value_nul.len() as u32,\r\n        un_tag: K_UN_STRING_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn write_bool_property(props: *mut Properties, container: u64, prop: u32, value: bool) {\r\n    if props.is_null() {\r\n        return;\r\n    }\r\n\r\n    let mut bool_value = value;\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: (&mut bool_value as *mut bool).cast(),\r\n        un_buffer_size: core::mem::size_of::<bool>() as u32,\r\n        un_tag: K_UN_BOOL_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn write_int32_property(props: *mut Properties, container: u64, prop: u32, value: i32) {\r\n    if props.is_null() {\r\n        return;\r\n    }\r\n\r\n    let mut int_value = value;\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: (&mut int_value as *mut i32).cast(),\r\n        un_buffer_size: core::mem::size_of::<i32>() as u32,\r\n        un_tag: K_UN_INT32_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn register_tracked_device_if_needed(device_ptr: *mut c_void) {\r\n    if DEVICE_REGISTERED.load(Ordering::SeqCst) {\r\n        return;\r\n    }\r\n\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    let added = unsafe {\r\n        ((*(*host).vtable).tracked_device_added)(\r\n            host,\r\n            DEVICE_SERIAL_CSTR.as_ptr().cast(),\r\n            TRACKED_DEVICE_CLASS_CONTROLLER,\r\n            device_ptr,\r\n        )\r\n    };\r\n\r\n    if added {\r\n        DEVICE_REGISTERED.store(true, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\npub fn initialize(driver_context: *mut c_void, device_ptr: *mut c_void) -> bool {\r\n    if driver_context.is_null() {\r\n        return false;\r\n    }\r\n\r\n    let ctx = driver_context.cast::<DriverContext>();\r\n    let mut err = 0;\r\n\r\n    let get_iface = unsafe { (*(*ctx).vtable).get_generic_interface };\r\n\r\n    let host = get_iface(\r\n        ctx,\r\n        IVR_SERVER_DRIVER_HOST_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let driver_input = get_iface(\r\n        ctx,\r\n        IVR_DRIVER_INPUT_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let properties = get_iface(\r\n        ctx,\r\n        IVR_PROPERTIES_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let driver_log = get_iface(\r\n        ctx,\r\n        IVR_DRIVER_LOG_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    SERVER_HOST_IFACE.store(host, Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(driver_input, Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(properties, Ordering::SeqCst);\r\n    DRIVER_LOG_IFACE.store(driver_log, Ordering::SeqCst);\r\n\r\n    let ok = !host.is_null() && !driver_input.is_null() && !properties.is_null();\r\n    if !ok {\r\n        return false;\r\n    }\r\n\r\n    log_driver_message(&format!(\r\n        \"[big_haptic_driver] OpenVR init ok; sizeof(VREvent)={}, sizeof(VREventHapticVibration)={}\",\r\n        core::mem::size_of::<VREvent>(),\r\n        core::mem::size_of::<VREventHapticVibration>()\r\n    ));\r\n\r\n    register_tracked_device_if_needed(device_ptr);\r\n    true\r\n}\r\n\r\npub fn cleanup() {\r\n    DEVICE_REGISTERED.store(false, Ordering::SeqCst);\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    SERVER_HOST_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DRIVER_LOG_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n}\r\n\r\npub fn route_haptic_events() {\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    loop {\r\n        let mut event = VREvent::default();\r\n        let ok = unsafe {\r\n            ((*(*host).vtable).poll_next_event)(\r\n                host,\r\n                &mut event as *mut VREvent,\r\n                core::mem::size_of::<VREvent>() as u32,\r\n            )\r\n        };\r\n\r\n        if !ok {\r\n            break;\r\n        }\r\n\r\n        if event.event_type != VREVENT_INPUT_HAPTIC_VIBRATION {\r\n            continue;\r\n        }\r\n\r\n        let hv = unsafe { event.data.haptic_vibration };\r\n        let seq = HAPTIC_EVENT_SEQ.fetch_add(1, Ordering::SeqCst).saturating_add(1);\r\n\r\n        log_driver_message(&format!(\r\n            \"[big_haptic_driver] haptic event #{}: dev={} age_s={:.6} container={} component={} duration_s={:.6} (0x{:08x}) freq={:.3} amp={:.6} (0x{:08x})\",\r\n            seq,\r\n            event.tracked_device_index,\r\n            event.event_age_seconds,\r\n            hv.container_handle,\r\n            hv.component_handle,\r\n            hv.duration_seconds,\r\n            hv.duration_seconds.to_bits(),\r\n            hv.frequency,\r\n            hv.amplitude,\r\n            hv.amplitude.to_bits()\r\n        ));\r\n\r\n        if hv.amplitude > 0.0 && hv.duration_seconds <= 0.0 {\r\n            log_driver_message(&format!(\r\n                \"[big_haptic_driver] note: OpenVR sent duration<=0; per OpenVR guidance driver will emit one finite pulse (freq={:.3})\",\r\n                hv.frequency\r\n            ));\r\n        }\r\n\r\n        let container = DEVICE_CONTAINER.load(Ordering::SeqCst);\r\n        if container != 0 && hv.container_handle != 0 && hv.container_handle != container {\r\n            log_driver_message(&format!(\r\n                \"[big_haptic_driver] skipped haptic event: container mismatch event={} driver={}\",\r\n                hv.container_handle, container\r\n            ));\r\n            continue;\r\n        }\r\n\r\n        let haptic_component = HAPTIC_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n        if haptic_component != 0 && hv.component_handle != 0 && hv.component_handle != haptic_component {\r\n            log_driver_message(&format!(\r\n                \"[big_haptic_driver] skipped haptic event: component mismatch event={} driver={}\",\r\n                hv.component_handle, haptic_component\r\n            ));\r\n            continue;\r\n        }\r\n\r\n        log_driver_message(\"[big_haptic_driver] forwarding haptic request to HID layer\");\r\n\r\n        haptics::handle_haptic_request(haptics::HapticVibrationRequest {\r\n            amplitude: hv.amplitude,\r\n            duration_seconds: hv.duration_seconds,\r\n            frequency: hv.frequency,\r\n        });\r\n    }\r\n}\r\n\r\npub fn push_pose_update(object_id: u32, pose: *const c_void, pose_size: u32) {\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    if pose.is_null() || pose_size == 0 {\r\n        return;\r\n    }\r\n\r\n    unsafe {\r\n        ((*(*host).vtable).tracked_device_pose_updated)(host, object_id, pose, pose_size);\r\n    }\r\n}\r\n\r\npub fn activate_device(object_id: u32) -> bool {\r\n    let Some(props) = get_properties() else {\r\n        return false;\r\n    };\r\n\r\n    let container = unsafe { ((*(*props).vtable).tracked_device_to_property_container)(props, object_id) };\r\n    DEVICE_CONTAINER.store(container, Ordering::SeqCst);\r\n\r\n    write_string_property(props, container, PROP_MODEL_NUMBER_STRING, DEVICE_MODEL_CSTR);\r\n    write_string_property(props, container, PROP_SERIAL_NUMBER_STRING, DEVICE_SERIAL_CSTR);\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_TRACKING_SYSTEM_NAME_STRING,\r\n        DEVICE_TRACKING_SYSTEM_NAME_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_REGISTERED_DEVICE_TYPE_STRING,\r\n        DEVICE_REGISTERED_TYPE_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_INPUT_PROFILE_PATH_STRING,\r\n        DEVICE_INPUT_PROFILE_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_CONTROLLER_TYPE_STRING,\r\n        DEVICE_CONTROLLER_TYPE_CSTR,\r\n    );\r\n    write_bool_property(props, container, PROP_HAS_CONTROLLER_COMPONENT_BOOL, true);\r\n    write_int32_property(props, container, PROP_CONTROLLER_ROLE_HINT_INT32, 1);\r\n\r\n    if let Some(input) = get_driver_input() {\r\n        let mut handle = 0_u64;\r\n        let rc = unsafe {\r\n            ((*(*input).vtable).create_haptic_component)(\r\n                input,\r\n                container,\r\n                DEVICE_HAPTIC_PATH_CSTR.as_ptr().cast(),\r\n                &mut handle as *mut u64,\r\n            )\r\n        };\r\n\r\n        if rc == 0 {\r\n            HAPTIC_COMPONENT_HANDLE.store(handle, Ordering::SeqCst);\r\n        }\r\n    }\r\n\r\n    true\r\n}\r\n\r\npub fn deactivate_device() {\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":551,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_38f8befb-ed20-48d4-a718-4a697ba92587","epoch":1596,"edits":[{"text":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse core::sync::atomic::{AtomicU64, Ordering};\r\nuse std::sync::{Mutex, OnceLock};\r\nuse std::time::Instant;\r\nuse std::time::Duration;\r\nuse std::thread;\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic STOP_DEADLINE_MS: AtomicU64 = AtomicU64::new(0);\r\nstatic STOP_TIMER_TOKEN: AtomicU64 = AtomicU64::new(0);\r\nstatic START_INSTANT: OnceLock<Instant> = OnceLock::new();\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\n\r\nconst OPENVR_MIN_FREQUENCY_HZ: f32 = 1_000_000.0 / 65_535.0;\r\nconst OPENVR_MAX_FREQUENCY_HZ: f32 = 1_000_000.0 / 300.0;\r\nconst OPENVR_MAX_DURATION_SECONDS: f32 = 10.0;\r\nconst HARDWARE_MIN_PULSE_SECONDS: f32 = 0.02;\r\nconst HID_SEND_ATTEMPTS: usize = 3;\nconst HID_SEND_RETRY_DELAY_MS: u64 = 2;\n\nfn log_haptics(message: &str) {\n    crate::openvr_runtime::log_driver_message(message);\n}\n\r\nfn send_packet(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    let mut last_error = None;\n\n    for attempt in 1..=HID_SEND_ATTEMPTS {\n        match hid_bridge::HidBridge::new()\n            .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\n        {\n            Ok(()) => return Ok(()),\n            Err(err) => {\n                last_error = Some(err);\n                if attempt < HID_SEND_ATTEMPTS {\n                    thread::sleep(Duration::from_millis(HID_SEND_RETRY_DELAY_MS));\n                }\n            }\n        }\n    }\n\n    let err = last_error.expect(\"HID send attempts exhausted without captured error\");\n    log_haptics(&format!(\n        \"haptics: HID send failed after {} attempt(s); cmd=0x{:02X}, intensity={}, arg_lo={}, arg_hi={}, err={}\",\n        HID_SEND_ATTEMPTS,\n        packet.command_id,\n        packet.intensity,\n        packet.arg_lo,\n        packet.arg_hi,\n        err\n    ));\n    Err(err)\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    send_packet(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn now_ms() -> u64 {\r\n    let start = START_INSTANT.get_or_init(Instant::now);\r\n    let ms = Instant::now().duration_since(*start).as_millis();\r\n    ms.min(u128::from(u64::MAX)) as u64\r\n}\r\n\r\nfn schedule_stop_after(duration_seconds: f32) {\r\n    if duration_seconds <= 0.0 {\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let duration_ms = (duration_seconds * 1000.0).round().max(1.0) as u64;\r\n    let deadline = now_ms().saturating_add(duration_ms);\r\n    STOP_DEADLINE_MS.store(deadline, Ordering::SeqCst);\r\n\r\n    let token = STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst).saturating_add(1);\r\n    std::thread::spawn(move || {\r\n        std::thread::sleep(Duration::from_millis(duration_ms));\r\n\r\n        if STOP_TIMER_TOKEN.load(Ordering::SeqCst) != token {\r\n            return;\r\n        }\r\n\r\n        if let Err(err) = send_stop() {\n            log_haptics(&format!(\"haptics: delayed stop failed: {}\", err));\n        }\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    });\r\n}\r\n\r\nfn resolve_effective_duration_seconds(duration_seconds: f32, frequency_hz: f32) -> f32 {\r\n    let clamped_duration = duration_seconds.clamp(0.0, OPENVR_MAX_DURATION_SECONDS);\r\n    if clamped_duration > 0.0 {\r\n        return clamped_duration;\r\n    }\r\n\r\n    // OpenVR guidance: when duration is 0, trigger exactly one pulse.\r\n    // We approximate one pulse as half a period at the requested frequency,\r\n    // but enforce a hardware minimum so pulses are actually perceptible.\r\n    (0.5 / frequency_hz).max(HARDWARE_MIN_PULSE_SECONDS)\r\n}\r\n\r\nfn process_pending_stop() {\r\n    let deadline = STOP_DEADLINE_MS.load(Ordering::SeqCst);\r\n    if deadline == 0 {\r\n        return;\r\n    }\r\n\r\n    if now_ms() >= deadline {\r\n        if let Err(err) = send_stop() {\n            log_haptics(&format!(\"haptics: frame stop failed: {}\", err));\n        }\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        if let Err(err) = send_stop() {\n            log_haptics(&format!(\"haptics: stop-on-zero-amplitude failed: {}\", err));\n        }\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    if req.frequency <= 0.0 {\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        if let Err(err) = send_stop() {\n            log_haptics(&format!(\"haptics: stop-on-nonpositive-frequency failed: {}\", err));\n        }\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let amplitude = req.amplitude.clamp(0.0, 1.0);\r\n    let frequency_hz = req\r\n        .frequency\r\n        .clamp(OPENVR_MIN_FREQUENCY_HZ, OPENVR_MAX_FREQUENCY_HZ);\r\n\r\n    if let Err(err) = send_amplitude(amplitude) {\n        log_haptics(&format!(\n            \"haptics: send_amplitude failed; amp={:.6}, freq={:.3}, duration_s={:.6}, err={}\",\n            amplitude,\n            frequency_hz,\n            req.duration_seconds,\n            err\n        ));\n        return;\n    }\n    let effective_duration = resolve_effective_duration_seconds(req.duration_seconds, frequency_hz);\r\n    schedule_stop_after(effective_duration);\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n    process_pending_stop();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":228,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_10cae230-7b11-4b8d-94f0-da978bd0fbe4","epoch":1600,"edits":[{"text":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse core::sync::atomic::{AtomicU64, Ordering};\r\nuse std::sync::{Mutex, OnceLock};\r\nuse std::time::Instant;\r\nuse std::time::Duration;\r\nuse std::thread;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic STOP_DEADLINE_MS: AtomicU64 = AtomicU64::new(0);\r\nstatic STOP_TIMER_TOKEN: AtomicU64 = AtomicU64::new(0);\r\nstatic START_INSTANT: OnceLock<Instant> = OnceLock::new();\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\nstatic HID_SEND_LOCK: Mutex<()> = Mutex::new(());\n\r\nconst OPENVR_MIN_FREQUENCY_HZ: f32 = 1_000_000.0 / 65_535.0;\r\nconst OPENVR_MAX_FREQUENCY_HZ: f32 = 1_000_000.0 / 300.0;\r\nconst OPENVR_MAX_DURATION_SECONDS: f32 = 10.0;\r\nconst HARDWARE_MIN_PULSE_SECONDS: f32 = 0.02;\r\nconst HID_SEND_ATTEMPTS: usize = 3;\r\nconst HID_SEND_RETRY_DELAY_MS: u64 = 2;\r\n\r\nfn log_haptics(message: &str) {\r\n    crate::openvr_runtime::log_driver_message(message);\r\n}\r\n\r\nfn send_packet(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    let _hid_guard = HID_SEND_LOCK.lock().ok();\n\n    let mut last_error = None;\r\n\r\n    for attempt in 1..=HID_SEND_ATTEMPTS {\r\n        match hid_bridge::HidBridge::new()\r\n            .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n        {\r\n            Ok(()) => return Ok(()),\r\n            Err(err) => {\r\n                last_error = Some(err);\r\n                if attempt < HID_SEND_ATTEMPTS {\r\n                    thread::sleep(Duration::from_millis(HID_SEND_RETRY_DELAY_MS));\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    let err = last_error.expect(\"HID send attempts exhausted without captured error\");\r\n    log_haptics(&format!(\r\n        \"haptics: HID send failed after {} attempt(s); cmd=0x{:02X}, intensity={}, arg_lo={}, arg_hi={}, err={}\",\r\n        HID_SEND_ATTEMPTS,\r\n        packet.command_id,\r\n        packet.intensity,\r\n        packet.arg_lo,\r\n        packet.arg_hi,\r\n        err\r\n    ));\r\n    Err(err)\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    send_packet(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn now_ms() -> u64 {\r\n    let start = START_INSTANT.get_or_init(Instant::now);\r\n    let ms = Instant::now().duration_since(*start).as_millis();\r\n    ms.min(u128::from(u64::MAX)) as u64\r\n}\r\n\r\nfn schedule_stop_after(duration_seconds: f32) {\r\n    if duration_seconds <= 0.0 {\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let duration_ms = (duration_seconds * 1000.0).round().max(1.0) as u64;\r\n    let deadline = now_ms().saturating_add(duration_ms);\r\n    STOP_DEADLINE_MS.store(deadline, Ordering::SeqCst);\r\n\r\n    let token = STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst).saturating_add(1);\r\n    std::thread::spawn(move || {\r\n        std::thread::sleep(Duration::from_millis(duration_ms));\r\n\r\n        if STOP_TIMER_TOKEN.load(Ordering::SeqCst) != token {\r\n            return;\r\n        }\r\n\r\n        if let Err(err) = send_stop() {\r\n            log_haptics(&format!(\"haptics: delayed stop failed: {}\", err));\r\n        }\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    });\r\n}\r\n\r\nfn resolve_effective_duration_seconds(duration_seconds: f32, frequency_hz: f32) -> f32 {\r\n    let clamped_duration = duration_seconds.clamp(0.0, OPENVR_MAX_DURATION_SECONDS);\r\n    if clamped_duration > 0.0 {\r\n        return clamped_duration;\r\n    }\r\n\r\n    // OpenVR guidance: when duration is 0, trigger exactly one pulse.\r\n    // We approximate one pulse as half a period at the requested frequency,\r\n    // but enforce a hardware minimum so pulses are actually perceptible.\r\n    (0.5 / frequency_hz).max(HARDWARE_MIN_PULSE_SECONDS)\r\n}\r\n\r\nfn process_pending_stop() {\r\n    let deadline = STOP_DEADLINE_MS.load(Ordering::SeqCst);\r\n    if deadline == 0 {\r\n        return;\r\n    }\r\n\r\n    if now_ms() >= deadline {\r\n        if let Err(err) = send_stop() {\r\n            log_haptics(&format!(\"haptics: frame stop failed: {}\", err));\r\n        }\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        if let Err(err) = send_stop() {\r\n            log_haptics(&format!(\"haptics: stop-on-zero-amplitude failed: {}\", err));\r\n        }\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    if req.frequency <= 0.0 {\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        if let Err(err) = send_stop() {\r\n            log_haptics(&format!(\"haptics: stop-on-nonpositive-frequency failed: {}\", err));\r\n        }\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let amplitude = req.amplitude.clamp(0.0, 1.0);\r\n    let frequency_hz = req\r\n        .frequency\r\n        .clamp(OPENVR_MIN_FREQUENCY_HZ, OPENVR_MAX_FREQUENCY_HZ);\r\n\r\n    if let Err(err) = send_amplitude(amplitude) {\r\n        log_haptics(&format!(\r\n            \"haptics: send_amplitude failed; amp={:.6}, freq={:.3}, duration_s={:.6}, err={}\",\r\n            amplitude,\r\n            frequency_hz,\r\n            req.duration_seconds,\r\n            err\r\n        ));\r\n        return;\r\n    }\r\n    let effective_duration = resolve_effective_duration_seconds(req.duration_seconds, frequency_hz);\r\n    schedule_stop_after(effective_duration);\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n    process_pending_stop();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":231,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_bb69abcc-71e3-469c-8c5d-1b8769eea60e","epoch":1604,"edits":[{"text":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse core::sync::atomic::{AtomicU64, Ordering};\r\nuse std::sync::{Mutex, OnceLock};\r\nuse std::time::Instant;\r\nuse std::time::Duration;\r\nuse std::thread;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic STOP_DEADLINE_MS: AtomicU64 = AtomicU64::new(0);\r\nstatic STOP_TIMER_TOKEN: AtomicU64 = AtomicU64::new(0);\r\nstatic START_INSTANT: OnceLock<Instant> = OnceLock::new();\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\nstatic HID_SEND_LOCK: Mutex<()> = Mutex::new(());\r\n\r\nconst OPENVR_MIN_FREQUENCY_HZ: f32 = 1_000_000.0 / 65_535.0;\r\nconst OPENVR_MAX_FREQUENCY_HZ: f32 = 1_000_000.0 / 300.0;\r\nconst OPENVR_MAX_DURATION_SECONDS: f32 = 10.0;\r\nconst HARDWARE_MIN_PULSE_SECONDS: f32 = 0.02;\r\nconst IDENTIFY_SIGNATURE_FREQUENCY_HZ: f32 = 200.0;\nconst IDENTIFY_SIGNATURE_FREQUENCY_TOLERANCE_HZ: f32 = 2.0;\nconst IDENTIFY_SIGNATURE_MAX_AMPLITUDE: f32 = 0.13;\nconst IDENTIFY_MIN_AMPLITUDE: f32 = 0.30;\nconst IDENTIFY_MIN_PULSE_SECONDS: f32 = 0.03;\nconst HID_SEND_ATTEMPTS: usize = 3;\r\nconst HID_SEND_RETRY_DELAY_MS: u64 = 2;\r\n\r\nfn log_haptics(message: &str) {\r\n    crate::openvr_runtime::log_driver_message(message);\r\n}\r\n\r\nfn send_packet(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    let _hid_guard = HID_SEND_LOCK.lock().ok();\r\n\r\n    let mut last_error = None;\r\n\r\n    for attempt in 1..=HID_SEND_ATTEMPTS {\r\n        match hid_bridge::HidBridge::new()\r\n            .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n        {\r\n            Ok(()) => return Ok(()),\r\n            Err(err) => {\r\n                last_error = Some(err);\r\n                if attempt < HID_SEND_ATTEMPTS {\r\n                    thread::sleep(Duration::from_millis(HID_SEND_RETRY_DELAY_MS));\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    let err = last_error.expect(\"HID send attempts exhausted without captured error\");\r\n    log_haptics(&format!(\r\n        \"haptics: HID send failed after {} attempt(s); cmd=0x{:02X}, intensity={}, arg_lo={}, arg_hi={}, err={}\",\r\n        HID_SEND_ATTEMPTS,\r\n        packet.command_id,\r\n        packet.intensity,\r\n        packet.arg_lo,\r\n        packet.arg_hi,\r\n        err\r\n    ));\r\n    Err(err)\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    send_packet(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn now_ms() -> u64 {\r\n    let start = START_INSTANT.get_or_init(Instant::now);\r\n    let ms = Instant::now().duration_since(*start).as_millis();\r\n    ms.min(u128::from(u64::MAX)) as u64\r\n}\r\n\r\nfn schedule_stop_after(duration_seconds: f32) {\r\n    if duration_seconds <= 0.0 {\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let duration_ms = (duration_seconds * 1000.0).round().max(1.0) as u64;\r\n    let deadline = now_ms().saturating_add(duration_ms);\r\n    STOP_DEADLINE_MS.store(deadline, Ordering::SeqCst);\r\n\r\n    let token = STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst).saturating_add(1);\r\n    std::thread::spawn(move || {\r\n        std::thread::sleep(Duration::from_millis(duration_ms));\r\n\r\n        if STOP_TIMER_TOKEN.load(Ordering::SeqCst) != token {\r\n            return;\r\n        }\r\n\r\n        if let Err(err) = send_stop() {\r\n            log_haptics(&format!(\"haptics: delayed stop failed: {}\", err));\r\n        }\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    });\r\n}\r\n\r\nfn resolve_effective_duration_seconds(duration_seconds: f32, frequency_hz: f32) -> f32 {\r\n    let clamped_duration = duration_seconds.clamp(0.0, OPENVR_MAX_DURATION_SECONDS);\r\n    if clamped_duration > 0.0 {\r\n        return clamped_duration;\r\n    }\r\n\r\n    // OpenVR guidance: when duration is 0, trigger exactly one pulse.\r\n    // We approximate one pulse as half a period at the requested frequency,\r\n    // but enforce a hardware minimum so pulses are actually perceptible.\r\n    (0.5 / frequency_hz).max(HARDWARE_MIN_PULSE_SECONDS)\r\n}\r\n\nfn looks_like_identify_campaign(req: HapticVibrationRequest) -> bool {\n    req.duration_seconds <= 0.0\n        && (req.frequency - IDENTIFY_SIGNATURE_FREQUENCY_HZ).abs()\n            <= IDENTIFY_SIGNATURE_FREQUENCY_TOLERANCE_HZ\n        && req.amplitude > 0.0\n        && req.amplitude <= IDENTIFY_SIGNATURE_MAX_AMPLITUDE\n}\n\r\nfn process_pending_stop() {\r\n    let deadline = STOP_DEADLINE_MS.load(Ordering::SeqCst);\r\n    if deadline == 0 {\r\n        return;\r\n    }\r\n\r\n    if now_ms() >= deadline {\r\n        if let Err(err) = send_stop() {\r\n            log_haptics(&format!(\"haptics: frame stop failed: {}\", err));\r\n        }\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        if let Err(err) = send_stop() {\r\n            log_haptics(&format!(\"haptics: stop-on-zero-amplitude failed: {}\", err));\r\n        }\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    if req.frequency <= 0.0 {\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        if let Err(err) = send_stop() {\r\n            log_haptics(&format!(\"haptics: stop-on-nonpositive-frequency failed: {}\", err));\r\n        }\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let amplitude = req.amplitude.clamp(0.0, 1.0);\r\n    let frequency_hz = req\r\n        .frequency\r\n        .clamp(OPENVR_MIN_FREQUENCY_HZ, OPENVR_MAX_FREQUENCY_HZ);\r\n\r\n    let is_identify_campaign = looks_like_identify_campaign(req);\n    let effective_amplitude = if is_identify_campaign {\n        amplitude.max(IDENTIFY_MIN_AMPLITUDE)\n    } else {\n        amplitude\n    };\n\n    if let Err(err) = send_amplitude(effective_amplitude) {\n        log_haptics(&format!(\r\n            \"haptics: send_amplitude failed; amp={:.6}, freq={:.3}, duration_s={:.6}, err={}\",\r\n            effective_amplitude,\n            frequency_hz,\r\n            req.duration_seconds,\r\n            err\r\n        ));\r\n        return;\r\n    }\r\n\n    let mut effective_duration =\n        resolve_effective_duration_seconds(req.duration_seconds, frequency_hz);\n    if is_identify_campaign {\n        effective_duration = effective_duration.max(IDENTIFY_MIN_PULSE_SECONDS);\n    }\n\n    schedule_stop_after(effective_duration);\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n    process_pending_stop();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":257,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_38df11d7-df4d-47e8-996c-ce006e7e6d59","epoch":1608,"edits":[{"text":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse core::sync::atomic::{AtomicU64, Ordering};\r\nuse std::sync::{Mutex, OnceLock};\r\nuse std::time::Instant;\r\nuse std::time::Duration;\r\nuse std::thread;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic STOP_DEADLINE_MS: AtomicU64 = AtomicU64::new(0);\r\nstatic STOP_TIMER_TOKEN: AtomicU64 = AtomicU64::new(0);\r\nstatic START_INSTANT: OnceLock<Instant> = OnceLock::new();\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\nstatic HID_SEND_LOCK: Mutex<()> = Mutex::new(());\r\nstatic IDENTIFY_CAMPAIGN_DEADLINE_MS: AtomicU64 = AtomicU64::new(0);\n\r\nconst OPENVR_MIN_FREQUENCY_HZ: f32 = 1_000_000.0 / 65_535.0;\r\nconst OPENVR_MAX_FREQUENCY_HZ: f32 = 1_000_000.0 / 300.0;\r\nconst OPENVR_MAX_DURATION_SECONDS: f32 = 10.0;\r\nconst HARDWARE_MIN_PULSE_SECONDS: f32 = 0.02;\r\nconst IDENTIFY_SIGNATURE_FREQUENCY_HZ: f32 = 200.0;\r\nconst IDENTIFY_SIGNATURE_FREQUENCY_TOLERANCE_HZ: f32 = 2.0;\r\nconst IDENTIFY_SIGNATURE_MAX_AMPLITUDE: f32 = 0.13;\r\nconst IDENTIFY_MIN_AMPLITUDE: f32 = 0.30;\r\nconst IDENTIFY_PULSE_SECONDS: f32 = 0.50;\nconst IDENTIFY_CAMPAIGN_WINDOW_MS: u64 = 22_000;\nconst HID_SEND_ATTEMPTS: usize = 3;\r\nconst HID_SEND_RETRY_DELAY_MS: u64 = 2;\r\n\r\nfn log_haptics(message: &str) {\r\n    crate::openvr_runtime::log_driver_message(message);\r\n}\r\n\r\nfn send_packet(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    let _hid_guard = HID_SEND_LOCK.lock().ok();\r\n\r\n    let mut last_error = None;\r\n\r\n    for attempt in 1..=HID_SEND_ATTEMPTS {\r\n        match hid_bridge::HidBridge::new()\r\n            .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n        {\r\n            Ok(()) => return Ok(()),\r\n            Err(err) => {\r\n                last_error = Some(err);\r\n                if attempt < HID_SEND_ATTEMPTS {\r\n                    thread::sleep(Duration::from_millis(HID_SEND_RETRY_DELAY_MS));\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    let err = last_error.expect(\"HID send attempts exhausted without captured error\");\r\n    log_haptics(&format!(\r\n        \"haptics: HID send failed after {} attempt(s); cmd=0x{:02X}, intensity={}, arg_lo={}, arg_hi={}, err={}\",\r\n        HID_SEND_ATTEMPTS,\r\n        packet.command_id,\r\n        packet.intensity,\r\n        packet.arg_lo,\r\n        packet.arg_hi,\r\n        err\r\n    ));\r\n    Err(err)\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    send_packet(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn now_ms() -> u64 {\r\n    let start = START_INSTANT.get_or_init(Instant::now);\r\n    let ms = Instant::now().duration_since(*start).as_millis();\r\n    ms.min(u128::from(u64::MAX)) as u64\r\n}\r\n\r\nfn schedule_stop_after(duration_seconds: f32) {\r\n    if duration_seconds <= 0.0 {\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let duration_ms = (duration_seconds * 1000.0).round().max(1.0) as u64;\r\n    let deadline = now_ms().saturating_add(duration_ms);\r\n    STOP_DEADLINE_MS.store(deadline, Ordering::SeqCst);\r\n\r\n    let token = STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst).saturating_add(1);\r\n    std::thread::spawn(move || {\r\n        std::thread::sleep(Duration::from_millis(duration_ms));\r\n\r\n        if STOP_TIMER_TOKEN.load(Ordering::SeqCst) != token {\r\n            return;\r\n        }\r\n\r\n        if let Err(err) = send_stop() {\r\n            log_haptics(&format!(\"haptics: delayed stop failed: {}\", err));\r\n        }\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    });\r\n}\r\n\r\nfn resolve_effective_duration_seconds(duration_seconds: f32, frequency_hz: f32) -> f32 {\r\n    let clamped_duration = duration_seconds.clamp(0.0, OPENVR_MAX_DURATION_SECONDS);\r\n    if clamped_duration > 0.0 {\r\n        return clamped_duration;\r\n    }\r\n\r\n    // OpenVR guidance: when duration is 0, trigger exactly one pulse.\r\n    // We approximate one pulse as half a period at the requested frequency,\r\n    // but enforce a hardware minimum so pulses are actually perceptible.\r\n    (0.5 / frequency_hz).max(HARDWARE_MIN_PULSE_SECONDS)\r\n}\r\n\r\nfn looks_like_identify_campaign(req: HapticVibrationRequest) -> bool {\r\n    req.duration_seconds <= 0.0\r\n        && (req.frequency - IDENTIFY_SIGNATURE_FREQUENCY_HZ).abs()\r\n            <= IDENTIFY_SIGNATURE_FREQUENCY_TOLERANCE_HZ\r\n        && req.amplitude > 0.0\r\n        && req.amplitude <= IDENTIFY_SIGNATURE_MAX_AMPLITUDE\r\n}\r\n\r\nfn process_pending_stop() {\r\n    let deadline = STOP_DEADLINE_MS.load(Ordering::SeqCst);\r\n    if deadline == 0 {\r\n        return;\r\n    }\r\n\r\n    if now_ms() >= deadline {\r\n        if let Err(err) = send_stop() {\r\n            log_haptics(&format!(\"haptics: frame stop failed: {}\", err));\r\n        }\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        if let Err(err) = send_stop() {\r\n            log_haptics(&format!(\"haptics: stop-on-zero-amplitude failed: {}\", err));\r\n        }\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    if req.frequency <= 0.0 {\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        if let Err(err) = send_stop() {\r\n            log_haptics(&format!(\"haptics: stop-on-nonpositive-frequency failed: {}\", err));\r\n        }\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let amplitude = req.amplitude.clamp(0.0, 1.0);\r\n    let frequency_hz = req\r\n        .frequency\r\n        .clamp(OPENVR_MIN_FREQUENCY_HZ, OPENVR_MAX_FREQUENCY_HZ);\r\n\r\n    let is_identify_campaign = looks_like_identify_campaign(req);\r\n\n    if is_identify_campaign {\n        let now = now_ms();\n        let active_until = IDENTIFY_CAMPAIGN_DEADLINE_MS.load(Ordering::SeqCst);\n        if now < active_until {\n            return;\n        }\n\n        IDENTIFY_CAMPAIGN_DEADLINE_MS\n            .store(now.saturating_add(IDENTIFY_CAMPAIGN_WINDOW_MS), Ordering::SeqCst);\n    }\n\n    let effective_amplitude = if is_identify_campaign {\r\n        amplitude.max(IDENTIFY_MIN_AMPLITUDE)\r\n    } else {\r\n        amplitude\r\n    };\r\n\r\n    if let Err(err) = send_amplitude(effective_amplitude) {\r\n        log_haptics(&format!(\r\n            \"haptics: send_amplitude failed; amp={:.6}, freq={:.3}, duration_s={:.6}, err={}\",\r\n            effective_amplitude,\r\n            frequency_hz,\r\n            req.duration_seconds,\r\n            err\r\n        ));\r\n        return;\r\n    }\r\n\r\n    let mut effective_duration =\r\n        resolve_effective_duration_seconds(req.duration_seconds, frequency_hz);\r\n    if is_identify_campaign {\r\n        effective_duration = IDENTIFY_PULSE_SECONDS;\n    }\r\n\r\n    schedule_stop_after(effective_duration);\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n    process_pending_stop();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":271,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_a7dd4e41-c8fa-4e43-bca4-89ec5b632f41","epoch":1612,"edits":[{"text":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse core::sync::atomic::{AtomicU64, Ordering};\r\nuse std::sync::{Mutex, OnceLock};\r\nuse std::time::Instant;\r\nuse std::time::Duration;\r\nuse std::thread;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic STOP_DEADLINE_MS: AtomicU64 = AtomicU64::new(0);\r\nstatic STOP_TIMER_TOKEN: AtomicU64 = AtomicU64::new(0);\r\nstatic START_INSTANT: OnceLock<Instant> = OnceLock::new();\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\nstatic HID_SEND_LOCK: Mutex<()> = Mutex::new(());\r\nstatic IDENTIFY_BURST_DEADLINE_MS: AtomicU64 = AtomicU64::new(0);\n\r\nconst OPENVR_MIN_FREQUENCY_HZ: f32 = 1_000_000.0 / 65_535.0;\r\nconst OPENVR_MAX_FREQUENCY_HZ: f32 = 1_000_000.0 / 300.0;\r\nconst OPENVR_MAX_DURATION_SECONDS: f32 = 10.0;\r\nconst HARDWARE_MIN_PULSE_SECONDS: f32 = 0.02;\r\nconst IDENTIFY_SIGNATURE_FREQUENCY_HZ: f32 = 200.0;\r\nconst IDENTIFY_SIGNATURE_FREQUENCY_TOLERANCE_HZ: f32 = 2.0;\r\nconst IDENTIFY_SIGNATURE_MAX_AMPLITUDE: f32 = 0.13;\r\nconst IDENTIFY_MIN_AMPLITUDE: f32 = 0.30;\r\nconst IDENTIFY_PULSE_SECONDS: f32 = 0.50;\r\nconst IDENTIFY_BURST_WINDOW_MS: u64 = 1_500;\nconst HID_SEND_ATTEMPTS: usize = 3;\r\nconst HID_SEND_RETRY_DELAY_MS: u64 = 2;\r\n\r\nfn log_haptics(message: &str) {\r\n    crate::openvr_runtime::log_driver_message(message);\r\n}\r\n\r\nfn send_packet(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    let _hid_guard = HID_SEND_LOCK.lock().ok();\r\n\r\n    let mut last_error = None;\r\n\r\n    for attempt in 1..=HID_SEND_ATTEMPTS {\r\n        match hid_bridge::HidBridge::new()\r\n            .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n        {\r\n            Ok(()) => return Ok(()),\r\n            Err(err) => {\r\n                last_error = Some(err);\r\n                if attempt < HID_SEND_ATTEMPTS {\r\n                    thread::sleep(Duration::from_millis(HID_SEND_RETRY_DELAY_MS));\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    let err = last_error.expect(\"HID send attempts exhausted without captured error\");\r\n    log_haptics(&format!(\r\n        \"haptics: HID send failed after {} attempt(s); cmd=0x{:02X}, intensity={}, arg_lo={}, arg_hi={}, err={}\",\r\n        HID_SEND_ATTEMPTS,\r\n        packet.command_id,\r\n        packet.intensity,\r\n        packet.arg_lo,\r\n        packet.arg_hi,\r\n        err\r\n    ));\r\n    Err(err)\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    send_packet(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn now_ms() -> u64 {\r\n    let start = START_INSTANT.get_or_init(Instant::now);\r\n    let ms = Instant::now().duration_since(*start).as_millis();\r\n    ms.min(u128::from(u64::MAX)) as u64\r\n}\r\n\r\nfn schedule_stop_after(duration_seconds: f32) {\r\n    if duration_seconds <= 0.0 {\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let duration_ms = (duration_seconds * 1000.0).round().max(1.0) as u64;\r\n    let deadline = now_ms().saturating_add(duration_ms);\r\n    STOP_DEADLINE_MS.store(deadline, Ordering::SeqCst);\r\n\r\n    let token = STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst).saturating_add(1);\r\n    std::thread::spawn(move || {\r\n        std::thread::sleep(Duration::from_millis(duration_ms));\r\n\r\n        if STOP_TIMER_TOKEN.load(Ordering::SeqCst) != token {\r\n            return;\r\n        }\r\n\r\n        if let Err(err) = send_stop() {\r\n            log_haptics(&format!(\"haptics: delayed stop failed: {}\", err));\r\n        }\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    });\r\n}\r\n\r\nfn resolve_effective_duration_seconds(duration_seconds: f32, frequency_hz: f32) -> f32 {\r\n    let clamped_duration = duration_seconds.clamp(0.0, OPENVR_MAX_DURATION_SECONDS);\r\n    if clamped_duration > 0.0 {\r\n        return clamped_duration;\r\n    }\r\n\r\n    // OpenVR guidance: when duration is 0, trigger exactly one pulse.\r\n    // We approximate one pulse as half a period at the requested frequency,\r\n    // but enforce a hardware minimum so pulses are actually perceptible.\r\n    (0.5 / frequency_hz).max(HARDWARE_MIN_PULSE_SECONDS)\r\n}\r\n\r\nfn looks_like_identify_campaign(req: HapticVibrationRequest) -> bool {\r\n    req.duration_seconds <= 0.0\r\n        && (req.frequency - IDENTIFY_SIGNATURE_FREQUENCY_HZ).abs()\r\n            <= IDENTIFY_SIGNATURE_FREQUENCY_TOLERANCE_HZ\r\n        && req.amplitude > 0.0\r\n        && req.amplitude <= IDENTIFY_SIGNATURE_MAX_AMPLITUDE\r\n}\r\n\r\nfn process_pending_stop() {\r\n    let deadline = STOP_DEADLINE_MS.load(Ordering::SeqCst);\r\n    if deadline == 0 {\r\n        return;\r\n    }\r\n\r\n    if now_ms() >= deadline {\r\n        if let Err(err) = send_stop() {\r\n            log_haptics(&format!(\"haptics: frame stop failed: {}\", err));\r\n        }\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        if let Err(err) = send_stop() {\r\n            log_haptics(&format!(\"haptics: stop-on-zero-amplitude failed: {}\", err));\r\n        }\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    if req.frequency <= 0.0 {\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        if let Err(err) = send_stop() {\r\n            log_haptics(&format!(\"haptics: stop-on-nonpositive-frequency failed: {}\", err));\r\n        }\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let amplitude = req.amplitude.clamp(0.0, 1.0);\r\n    let frequency_hz = req\r\n        .frequency\r\n        .clamp(OPENVR_MIN_FREQUENCY_HZ, OPENVR_MAX_FREQUENCY_HZ);\r\n\r\n    let is_identify_campaign = looks_like_identify_campaign(req);\r\n\r\n    if is_identify_campaign {\r\n        let now = now_ms();\r\n        let active_until = IDENTIFY_BURST_DEADLINE_MS.load(Ordering::SeqCst);\n        if now < active_until {\r\n            return;\r\n        }\r\n\r\n        IDENTIFY_BURST_DEADLINE_MS\n            .store(now.saturating_add(IDENTIFY_BURST_WINDOW_MS), Ordering::SeqCst);\n    }\r\n\r\n    let effective_amplitude = if is_identify_campaign {\r\n        amplitude.max(IDENTIFY_MIN_AMPLITUDE)\r\n    } else {\r\n        amplitude\r\n    };\r\n\r\n    if let Err(err) = send_amplitude(effective_amplitude) {\r\n        log_haptics(&format!(\r\n            \"haptics: send_amplitude failed; amp={:.6}, freq={:.3}, duration_s={:.6}, err={}\",\r\n            effective_amplitude,\r\n            frequency_hz,\r\n            req.duration_seconds,\r\n            err\r\n        ));\r\n        return;\r\n    }\r\n\r\n    let mut effective_duration =\r\n        resolve_effective_duration_seconds(req.duration_seconds, frequency_hz);\r\n    if is_identify_campaign {\r\n        effective_duration = IDENTIFY_PULSE_SECONDS;\r\n    }\r\n\r\n    schedule_stop_after(effective_duration);\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n    process_pending_stop();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":271,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\openvr_runtime.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1616,"edits":[{"text":"//! OpenVR runtime integration helpers.\r\n//!\r\n//! This module owns driver-context interface resolution, tracked-device\r\n//! registration, component/property setup, and OpenVR haptic event routing.\r\n\r\nuse core::ffi::c_void;\r\nuse core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU64, Ordering};\r\nuse std::ffi::CString;\r\nuse std::os::raw::c_char;\r\n\r\nuse crate::haptics;\r\n\r\ntype EvRInitError = i32;\r\n\r\nconst IVR_SERVER_DRIVER_HOST_VERSION: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\nconst IVR_DRIVER_INPUT_VERSION: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_PROPERTIES_VERSION: &[u8] = b\"IVRProperties_001\\0\";\r\nconst IVR_DRIVER_LOG_VERSION: &[u8] = b\"IVRDriverLog_001\\0\";\r\n\r\nconst TRACKED_DEVICE_CLASS_CONTROLLER: i32 = 2;\r\nconst VREVENT_INPUT_HAPTIC_VIBRATION: u32 = 1700;\r\n\r\nconst PROP_MODEL_NUMBER_STRING: u32 = 1001;\r\nconst PROP_SERIAL_NUMBER_STRING: u32 = 1002;\r\nconst PROP_TRACKING_SYSTEM_NAME_STRING: u32 = 1000;\r\nconst PROP_REGISTERED_DEVICE_TYPE_STRING: u32 = 1036;\r\nconst PROP_INPUT_PROFILE_PATH_STRING: u32 = 1037;\r\nconst PROP_CONTROLLER_TYPE_STRING: u32 = 7000;\r\nconst PROP_CONTROLLER_ROLE_HINT_INT32: u32 = 3007;\r\nconst PROP_HAS_CONTROLLER_COMPONENT_BOOL: u32 = 6003;\r\nconst TRACKED_DEVICE_INDEX_HMD: u32 = 0;\n\r\nconst K_UN_BOOL_PROPERTY_TAG: u32 = 4;\r\nconst K_UN_INT32_PROPERTY_TAG: u32 = 2;\r\nconst K_UN_STRING_PROPERTY_TAG: u32 = 5;\r\nconst PROPERTY_WRITE_SET: i32 = 0;\r\n\r\nconst DEVICE_SERIAL_CSTR: &[u8] = b\"qtpy-samd21-haptic-001\\0\";\r\nconst DEVICE_MODEL_CSTR: &[u8] = b\"QT Py SAMD21 Haptic\\0\";\r\nconst DEVICE_TRACKING_SYSTEM_NAME_CSTR: &[u8] = b\"big_haptic_driver\\0\";\r\nconst DEVICE_REGISTERED_TYPE_CSTR: &[u8] = b\"big_haptic_driver/controller\\0\";\r\nconst DEVICE_CONTROLLER_TYPE_CSTR: &[u8] = b\"big_haptic_driver\\0\";\r\nconst DEVICE_INPUT_PROFILE_CSTR: &[u8] = b\"{big_haptic_driver}/input/big_haptic_profile.json\\0\";\r\nconst DEVICE_HAPTIC_PATH_CSTR: &[u8] = b\"/output/haptic\\0\";\r\nconst DEVICE_POSE_RAW_PATH_CSTR: &[u8] = b\"/pose/raw\\0\";\nconst DEVICE_POSE_TIP_PATH_CSTR: &[u8] = b\"/pose/tip\\0\";\n\r\nconst PROVIDER_IFACE_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_004\\0\";\r\nconst PROVIDER_IFACE_ALT_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_005\\0\";\r\nconst DEVICE_IFACE_CSTR: &[u8] = b\"ITrackedDeviceServerDriver_005\\0\";\r\n\r\nconst IVR_PROPERTIES_CSTR: &[u8] = b\"IVRProperties_001\\0\";\r\nconst IVR_DRIVER_INPUT_CSTR: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_SERVER_DRIVER_HOST_CSTR: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\nconst IVR_DRIVER_LOG_CSTR: &[u8] = b\"IVRDriverLog_001\\0\";\r\n\r\nconst IVR_SETTINGS_CSTR: &[u8] = b\"IVRSettings_003\\0\";\r\nconst IVR_DRIVER_MANAGER_CSTR: &[u8] = b\"IVRDriverManager_001\\0\";\r\nconst IVR_RESOURCES_CSTR: &[u8] = b\"IVRResources_001\\0\";\r\n\r\n#[repr(C)]\r\nstruct InterfaceVersions([*const c_char; 11]);\r\nunsafe impl Sync for InterfaceVersions {}\r\n\r\nstatic INTERFACE_VERSIONS: InterfaceVersions = InterfaceVersions([\r\n    IVR_SETTINGS_CSTR.as_ptr().cast(),\r\n    DEVICE_IFACE_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_CSTR.as_ptr().cast(),\r\n    IVR_PROPERTIES_CSTR.as_ptr().cast(),\r\n    IVR_SERVER_DRIVER_HOST_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_LOG_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_MANAGER_CSTR.as_ptr().cast(),\r\n    IVR_RESOURCES_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_INPUT_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_ALT_CSTR.as_ptr().cast(),\r\n    core::ptr::null(),\r\n]);\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREventHapticVibration {\r\n    container_handle: u64,\r\n    component_handle: u64,\r\n    duration_seconds: f32,\r\n    frequency: f32,\r\n    amplitude: f32,\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nunion VREventData {\r\n    haptic_vibration: VREventHapticVibration,\r\n    reserved: [u8; 48],\r\n}\r\n\r\nimpl Default for VREventData {\r\n    fn default() -> Self {\r\n        Self { reserved: [0; 48] }\r\n    }\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREvent {\r\n    event_type: u32,\r\n    tracked_device_index: u32,\r\n    event_age_seconds: f32,\r\n    data: VREventData,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContextVTable {\r\n    get_generic_interface:\r\n        extern \"C\" fn(*mut DriverContext, *const c_char, *mut EvRInitError) -> *mut c_void,\r\n    get_driver_handle: extern \"C\" fn(*mut DriverContext) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContext {\r\n    vtable: *const DriverContextVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHostVTable {\r\n    tracked_device_added:\r\n        extern \"C\" fn(*mut ServerDriverHost, *const c_char, i32, *mut c_void) -> bool,\r\n    tracked_device_pose_updated: extern \"C\" fn(*mut ServerDriverHost, u32, *const c_void, u32),\r\n    vsync_event: extern \"C\" fn(*mut ServerDriverHost, f64),\r\n    vendor_specific_event: extern \"C\" fn(*mut ServerDriverHost, u32, u32, *const VREventData, f64),\r\n    is_exiting: extern \"C\" fn(*mut ServerDriverHost) -> bool,\r\n    poll_next_event: extern \"C\" fn(*mut ServerDriverHost, *mut VREvent, u32) -> bool,\r\n    get_raw_tracked_device_poses:\n        extern \"C\" fn(*mut ServerDriverHost, f32, *mut TrackedDevicePose, u32),\n    request_restart:\n        extern \"C\" fn(*mut ServerDriverHost, *const c_char, *const c_char, *const c_char, *const c_char),\n    get_frame_timings: extern \"C\" fn(*mut ServerDriverHost, *mut c_void, u32) -> u32,\n    set_display_eye_to_head:\n        extern \"C\" fn(*mut ServerDriverHost, u32, *const c_void, *const c_void),\n    set_display_projection_raw:\n        extern \"C\" fn(*mut ServerDriverHost, u32, *const c_void, *const c_void),\n    set_recommended_render_target_size: extern \"C\" fn(*mut ServerDriverHost, u32, u32, u32),\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHost {\r\n    vtable: *const ServerDriverHostVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverLogVTable {\r\n    log: extern \"C\" fn(*mut DriverLog, *const c_char),\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverLog {\r\n    vtable: *const DriverLogVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverInputVTable {\r\n    create_boolean_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n    update_boolean_component: extern \"C\" fn(*mut DriverInput, u64, bool, f64) -> i32,\r\n    create_scalar_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64, i32, i32) -> i32,\r\n    update_scalar_component: extern \"C\" fn(*mut DriverInput, u64, f32, f64) -> i32,\r\n    create_haptic_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n    create_skeleton_component: extern \"C\" fn(\n        *mut DriverInput,\n        u64,\n        *const c_char,\n        *const c_char,\n        *const c_char,\n        i32,\n        *const c_void,\n        u32,\n        *mut u64,\n    ) -> i32,\n    update_skeleton_component:\n        extern \"C\" fn(*mut DriverInput, u64, i32, *const c_void, u32) -> i32,\n    create_pose_component:\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\n    update_pose_component:\n        extern \"C\" fn(*mut DriverInput, u64, *const HmdMatrix34, f64) -> i32,\n}\r\n\r\n#[repr(C)]\r\nstruct DriverInput {\r\n    vtable: *const DriverInputVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct PropertiesVTable {\r\n    read_property_batch: extern \"C\" fn(*mut Properties, u64, *mut c_void, u32) -> i32,\r\n    write_property_batch: extern \"C\" fn(*mut Properties, u64, *mut PropertyWrite, u32) -> i32,\r\n    get_prop_error_name_from_enum: extern \"C\" fn(*mut Properties, i32) -> *const c_char,\r\n    tracked_device_to_property_container: extern \"C\" fn(*mut Properties, u32) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct Properties {\r\n    vtable: *const PropertiesVTable,\r\n}\r\n\n#[repr(C)]\n#[derive(Clone, Copy, Default)]\nstruct HmdMatrix34 {\n    m: [[f32; 4]; 3],\n}\n\n#[repr(C)]\n#[derive(Clone, Copy, Default)]\nstruct TrackedDevicePose {\n    m_device_to_absolute_tracking: HmdMatrix34,\n    v_velocity: [f32; 3],\n    v_angular_velocity: [f32; 3],\n    e_tracking_result: i32,\n    b_pose_is_valid: bool,\n    b_device_is_connected: bool,\n}\n\n#[derive(Clone, Copy, Default)]\npub struct RuntimeHmdPose {\n    pub position_m: [f32; 3],\n    pub rotation: [[f32; 3]; 3],\n}\n\r\n#[repr(C)]\r\nstruct PropertyWrite {\r\n    prop: u32,\r\n    write_type: i32,\r\n    set_error: i32,\r\n    pv_buffer: *mut c_void,\r\n    un_buffer_size: u32,\r\n    un_tag: u32,\r\n    e_error: i32,\r\n}\r\n\r\nstatic DEVICE_REGISTERED: AtomicBool = AtomicBool::new(false);\r\n\r\nstatic SERVER_HOST_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DRIVER_INPUT_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic PROPERTIES_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DRIVER_LOG_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\n\r\nstatic DEVICE_CONTAINER: AtomicU64 = AtomicU64::new(0);\r\nstatic HAPTIC_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\nstatic POSE_RAW_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\nstatic POSE_TIP_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\nstatic HAPTIC_EVENT_SEQ: AtomicU64 = AtomicU64::new(0);\r\n\r\npub fn interface_versions_ptr() -> *const *const c_char {\r\n    INTERFACE_VERSIONS.0.as_ptr()\r\n}\r\n\r\nfn get_server_host() -> Option<*mut ServerDriverHost> {\r\n    let ptr = SERVER_HOST_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_driver_input() -> Option<*mut DriverInput> {\r\n    let ptr = DRIVER_INPUT_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_properties() -> Option<*mut Properties> {\r\n    let ptr = PROPERTIES_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_driver_log() -> Option<*mut DriverLog> {\r\n    let ptr = DRIVER_LOG_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\npub(crate) fn log_driver_message(message: &str) {\r\n    let Some(driver_log) = get_driver_log() else {\r\n        return;\r\n    };\r\n\r\n    let Ok(c_message) = CString::new(message) else {\r\n        return;\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*driver_log).vtable).log)(driver_log, c_message.as_ptr());\r\n    }\r\n}\r\n\r\nfn write_string_property(props: *mut Properties, container: u64, prop: u32, value_nul: &'static [u8]) {\r\n    if props.is_null() || value_nul.is_empty() {\r\n        return;\r\n    }\r\n\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: value_nul.as_ptr().cast_mut().cast(),\r\n        un_buffer_size: value_nul.len() as u32,\r\n        un_tag: K_UN_STRING_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn write_bool_property(props: *mut Properties, container: u64, prop: u32, value: bool) {\r\n    if props.is_null() {\r\n        return;\r\n    }\r\n\r\n    let mut bool_value = value;\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: (&mut bool_value as *mut bool).cast(),\r\n        un_buffer_size: core::mem::size_of::<bool>() as u32,\r\n        un_tag: K_UN_BOOL_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn write_int32_property(props: *mut Properties, container: u64, prop: u32, value: i32) {\r\n    if props.is_null() {\r\n        return;\r\n    }\r\n\r\n    let mut int_value = value;\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: (&mut int_value as *mut i32).cast(),\r\n        un_buffer_size: core::mem::size_of::<i32>() as u32,\r\n        un_tag: K_UN_INT32_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn register_tracked_device_if_needed(device_ptr: *mut c_void) {\r\n    if DEVICE_REGISTERED.load(Ordering::SeqCst) {\r\n        return;\r\n    }\r\n\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    let added = unsafe {\r\n        ((*(*host).vtable).tracked_device_added)(\r\n            host,\r\n            DEVICE_SERIAL_CSTR.as_ptr().cast(),\r\n            TRACKED_DEVICE_CLASS_CONTROLLER,\r\n            device_ptr,\r\n        )\r\n    };\r\n\r\n    if added {\r\n        DEVICE_REGISTERED.store(true, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\npub fn initialize(driver_context: *mut c_void, device_ptr: *mut c_void) -> bool {\r\n    if driver_context.is_null() {\r\n        return false;\r\n    }\r\n\r\n    let ctx = driver_context.cast::<DriverContext>();\r\n    let mut err = 0;\r\n\r\n    let get_iface = unsafe { (*(*ctx).vtable).get_generic_interface };\r\n\r\n    let host = get_iface(\r\n        ctx,\r\n        IVR_SERVER_DRIVER_HOST_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let driver_input = get_iface(\r\n        ctx,\r\n        IVR_DRIVER_INPUT_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let properties = get_iface(\r\n        ctx,\r\n        IVR_PROPERTIES_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let driver_log = get_iface(\r\n        ctx,\r\n        IVR_DRIVER_LOG_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    SERVER_HOST_IFACE.store(host, Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(driver_input, Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(properties, Ordering::SeqCst);\r\n    DRIVER_LOG_IFACE.store(driver_log, Ordering::SeqCst);\r\n\r\n    let ok = !host.is_null() && !driver_input.is_null() && !properties.is_null();\r\n    if !ok {\r\n        return false;\r\n    }\r\n\r\n    log_driver_message(&format!(\r\n        \"[big_haptic_driver] OpenVR init ok; sizeof(VREvent)={}, sizeof(VREventHapticVibration)={}\",\r\n        core::mem::size_of::<VREvent>(),\r\n        core::mem::size_of::<VREventHapticVibration>()\r\n    ));\r\n\r\n    register_tracked_device_if_needed(device_ptr);\r\n    true\r\n}\r\n\r\npub fn cleanup() {\r\n    DEVICE_REGISTERED.store(false, Ordering::SeqCst);\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    POSE_RAW_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\n    POSE_TIP_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\n    SERVER_HOST_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DRIVER_LOG_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n}\r\n\r\npub fn route_haptic_events() {\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    loop {\r\n        let mut event = VREvent::default();\r\n        let ok = unsafe {\r\n            ((*(*host).vtable).poll_next_event)(\r\n                host,\r\n                &mut event as *mut VREvent,\r\n                core::mem::size_of::<VREvent>() as u32,\r\n            )\r\n        };\r\n\r\n        if !ok {\r\n            break;\r\n        }\r\n\r\n        if event.event_type != VREVENT_INPUT_HAPTIC_VIBRATION {\r\n            continue;\r\n        }\r\n\r\n        let hv = unsafe { event.data.haptic_vibration };\r\n        let seq = HAPTIC_EVENT_SEQ.fetch_add(1, Ordering::SeqCst).saturating_add(1);\r\n\r\n        log_driver_message(&format!(\r\n            \"[big_haptic_driver] haptic event #{}: dev={} age_s={:.6} container={} component={} duration_s={:.6} (0x{:08x}) freq={:.3} amp={:.6} (0x{:08x})\",\r\n            seq,\r\n            event.tracked_device_index,\r\n            event.event_age_seconds,\r\n            hv.container_handle,\r\n            hv.component_handle,\r\n            hv.duration_seconds,\r\n            hv.duration_seconds.to_bits(),\r\n            hv.frequency,\r\n            hv.amplitude,\r\n            hv.amplitude.to_bits()\r\n        ));\r\n\r\n        if hv.amplitude > 0.0 && hv.duration_seconds <= 0.0 {\r\n            log_driver_message(&format!(\r\n                \"[big_haptic_driver] note: OpenVR sent duration<=0; per OpenVR guidance driver will emit one finite pulse (freq={:.3})\",\r\n                hv.frequency\r\n            ));\r\n        }\r\n\r\n        let container = DEVICE_CONTAINER.load(Ordering::SeqCst);\r\n        if container != 0 && hv.container_handle != 0 && hv.container_handle != container {\r\n            log_driver_message(&format!(\r\n                \"[big_haptic_driver] skipped haptic event: container mismatch event={} driver={}\",\r\n                hv.container_handle, container\r\n            ));\r\n            continue;\r\n        }\r\n\r\n        let haptic_component = HAPTIC_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n        if haptic_component != 0 && hv.component_handle != 0 && hv.component_handle != haptic_component {\r\n            log_driver_message(&format!(\r\n                \"[big_haptic_driver] skipped haptic event: component mismatch event={} driver={}\",\r\n                hv.component_handle, haptic_component\r\n            ));\r\n            continue;\r\n        }\r\n\r\n        log_driver_message(\"[big_haptic_driver] forwarding haptic request to HID layer\");\r\n\r\n        haptics::handle_haptic_request(haptics::HapticVibrationRequest {\r\n            amplitude: hv.amplitude,\r\n            duration_seconds: hv.duration_seconds,\r\n            frequency: hv.frequency,\r\n        });\r\n    }\r\n}\r\n\r\npub fn push_pose_update(object_id: u32, pose: *const c_void, pose_size: u32) {\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    if pose.is_null() || pose_size == 0 {\r\n        return;\r\n    }\r\n\r\n    unsafe {\r\n        ((*(*host).vtable).tracked_device_pose_updated)(host, object_id, pose, pose_size);\r\n    }\r\n}\r\n\npub fn sample_hmd_pose() -> Option<RuntimeHmdPose> {\n    let host = get_server_host()?;\n\n    let mut poses = [TrackedDevicePose::default(); 1];\n    unsafe {\n        ((*(*host).vtable).get_raw_tracked_device_poses)(\n            host,\n            0.0,\n            poses.as_mut_ptr(),\n            poses.len() as u32,\n        );\n    }\n\n    let hmd = poses[TRACKED_DEVICE_INDEX_HMD as usize];\n    if !hmd.b_device_is_connected || !hmd.b_pose_is_valid {\n        return None;\n    }\n\n    Some(RuntimeHmdPose {\n        position_m: [\n            hmd.m_device_to_absolute_tracking.m[0][3],\n            hmd.m_device_to_absolute_tracking.m[1][3],\n            hmd.m_device_to_absolute_tracking.m[2][3],\n        ],\n        rotation: [\n            [\n                hmd.m_device_to_absolute_tracking.m[0][0],\n                hmd.m_device_to_absolute_tracking.m[0][1],\n                hmd.m_device_to_absolute_tracking.m[0][2],\n            ],\n            [\n                hmd.m_device_to_absolute_tracking.m[1][0],\n                hmd.m_device_to_absolute_tracking.m[1][1],\n                hmd.m_device_to_absolute_tracking.m[1][2],\n            ],\n            [\n                hmd.m_device_to_absolute_tracking.m[2][0],\n                hmd.m_device_to_absolute_tracking.m[2][1],\n                hmd.m_device_to_absolute_tracking.m[2][2],\n            ],\n        ],\n    })\n}\n\npub fn update_pose_components() {\n    let Some(input) = get_driver_input() else {\n        return;\n    };\n\n    let raw_handle = POSE_RAW_COMPONENT_HANDLE.load(Ordering::SeqCst);\n    let tip_handle = POSE_TIP_COMPONENT_HANDLE.load(Ordering::SeqCst);\n\n    let raw_offset = HmdMatrix34 {\n        m: [\n            [1.0, 0.0, 0.0, 0.0],\n            [0.0, 1.0, 0.0, 0.0],\n            [0.0, 0.0, 1.0, 0.0],\n        ],\n    };\n    let tip_offset = HmdMatrix34 {\n        m: [\n            [1.0, 0.0, 0.0, 0.0],\n            [0.0, 1.0, 0.0, 0.0],\n            [0.0, 0.0, 1.0, -0.04],\n        ],\n    };\n\n    if raw_handle != 0 {\n        unsafe {\n            ((*(*input).vtable).update_pose_component)(input, raw_handle, &raw_offset, 0.0);\n        }\n    }\n\n    if tip_handle != 0 {\n        unsafe {\n            ((*(*input).vtable).update_pose_component)(input, tip_handle, &tip_offset, 0.0);\n        }\n    }\n}\n\r\npub fn activate_device(object_id: u32) -> bool {\r\n    let Some(props) = get_properties() else {\r\n        return false;\r\n    };\r\n\r\n    let container = unsafe { ((*(*props).vtable).tracked_device_to_property_container)(props, object_id) };\r\n    DEVICE_CONTAINER.store(container, Ordering::SeqCst);\r\n\r\n    write_string_property(props, container, PROP_MODEL_NUMBER_STRING, DEVICE_MODEL_CSTR);\r\n    write_string_property(props, container, PROP_SERIAL_NUMBER_STRING, DEVICE_SERIAL_CSTR);\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_TRACKING_SYSTEM_NAME_STRING,\r\n        DEVICE_TRACKING_SYSTEM_NAME_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_REGISTERED_DEVICE_TYPE_STRING,\r\n        DEVICE_REGISTERED_TYPE_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_INPUT_PROFILE_PATH_STRING,\r\n        DEVICE_INPUT_PROFILE_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_CONTROLLER_TYPE_STRING,\r\n        DEVICE_CONTROLLER_TYPE_CSTR,\r\n    );\r\n    write_bool_property(props, container, PROP_HAS_CONTROLLER_COMPONENT_BOOL, true);\r\n    write_int32_property(props, container, PROP_CONTROLLER_ROLE_HINT_INT32, 1);\r\n\r\n    if let Some(input) = get_driver_input() {\r\n        let mut handle = 0_u64;\r\n        let rc = unsafe {\r\n            ((*(*input).vtable).create_haptic_component)(\r\n                input,\r\n                container,\r\n                DEVICE_HAPTIC_PATH_CSTR.as_ptr().cast(),\r\n                &mut handle as *mut u64,\r\n            )\r\n        };\r\n\r\n        if rc == 0 {\r\n            HAPTIC_COMPONENT_HANDLE.store(handle, Ordering::SeqCst);\r\n        }\r\n\n        let mut pose_raw_handle = 0_u64;\n        let rc_raw = unsafe {\n            ((*(*input).vtable).create_pose_component)(\n                input,\n                container,\n                DEVICE_POSE_RAW_PATH_CSTR.as_ptr().cast(),\n                &mut pose_raw_handle as *mut u64,\n            )\n        };\n        if rc_raw == 0 {\n            POSE_RAW_COMPONENT_HANDLE.store(pose_raw_handle, Ordering::SeqCst);\n        }\n\n        let mut pose_tip_handle = 0_u64;\n        let rc_tip = unsafe {\n            ((*(*input).vtable).create_pose_component)(\n                input,\n                container,\n                DEVICE_POSE_TIP_PATH_CSTR.as_ptr().cast(),\n                &mut pose_tip_handle as *mut u64,\n            )\n        };\n        if rc_tip == 0 {\n            POSE_TIP_COMPONENT_HANDLE.store(pose_tip_handle, Ordering::SeqCst);\n        }\n\n        update_pose_components();\n    }\r\n\r\n    true\r\n}\r\n\r\npub fn deactivate_device() {\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    POSE_RAW_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\n    POSE_TIP_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":718,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1619,"edits":[{"text":"//! OpenVR ABI/vtable scaffolding for the Big Haptic Driver.\r\n//!\r\n//! This module owns C ABI exports and interface routing for SteamVR-facing\r\n//! provider/device entry points, while delegating runtime integration\r\n//! (device registration, component setup, and event routing) to\r\n//! `openvr_runtime`.\r\n\r\nuse core::ffi::{c_void, CStr};\r\nuse core::sync::atomic::{AtomicBool, AtomicU32, Ordering};\r\nuse std::os::raw::c_char;\r\n\r\nuse crate::haptics;\r\nuse crate::openvr_runtime;\r\n\r\nconst SERVER_PROVIDER_INTERFACE: &str = \"IServerTrackedDeviceProvider_004\";\r\nconst SERVER_PROVIDER_INTERFACE_ALT: &str = \"IServerTrackedDeviceProvider_005\";\r\nconst TRACKED_DEVICE_INTERFACE: &str = \"ITrackedDeviceServerDriver_005\";\r\nconst VR_INIT_ERROR_DRIVER_FAILED: EvRInitError = 200;\r\n\r\n#[repr(i32)]\r\nenum DriverReturnCode {\r\n    None = 0,\r\n    Unknown = 1,\r\n    InitInterfaceNotFound = 105,\r\n}\r\n\r\ntype EvRInitError = i32;\r\nconst VR_INIT_ERROR_NONE: EvRInitError = 0;\r\nconst TRACKING_RESULT_RUNNING_OK: i32 = 200;\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nstruct DriverPoseQuaternion {\r\n    w: f64,\r\n    x: f64,\r\n    y: f64,\r\n    z: f64,\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nstruct DriverPose {\r\n    pose_time_offset: f64,\r\n    q_world_from_driver_rotation: DriverPoseQuaternion,\r\n    vec_world_from_driver_translation: [f64; 3],\r\n    q_driver_from_head_rotation: DriverPoseQuaternion,\r\n    vec_driver_from_head_translation: [f64; 3],\r\n    vec_position: [f64; 3],\r\n    vec_velocity: [f64; 3],\r\n    vec_acceleration: [f64; 3],\r\n    q_rotation: DriverPoseQuaternion,\r\n    vec_angular_velocity: [f64; 3],\r\n    vec_angular_acceleration: [f64; 3],\r\n    result: i32,\r\n    pose_is_valid: bool,\r\n    will_drift_in_yaw: bool,\r\n    should_apply_head_model: bool,\r\n    device_is_connected: bool,\r\n}\r\n\r\nconst IDENTITY_QUATERNION: DriverPoseQuaternion = DriverPoseQuaternion {\r\n    w: 1.0,\r\n    x: 0.0,\r\n    y: 0.0,\r\n    z: 0.0,\r\n};\r\n\r\nfn connected_controller_pose() -> DriverPose {\r\n    if let Some(hmd_pose) = openvr_runtime::sample_hmd_pose() {\n        let rotation = hmd_pose.rotation;\n\n        let right = [rotation[0][0], rotation[1][0], rotation[2][0]];\n        let up = [rotation[0][1], rotation[1][1], rotation[2][1]];\n        let forward = [-rotation[0][2], -rotation[1][2], -rotation[2][2]];\n\n        let position = [\n            hmd_pose.position_m[0] + forward[0] * 0.38 + right[0] * -0.18 + up[0] * -0.18,\n            hmd_pose.position_m[1] + forward[1] * 0.38 + right[1] * -0.18 + up[1] * -0.18,\n            hmd_pose.position_m[2] + forward[2] * 0.38 + right[2] * -0.18 + up[2] * -0.18,\n        ];\n\n        let q_rotation = quaternion_from_rotation_matrix(rotation);\n\n        return DriverPose {\n            pose_time_offset: 0.0,\n            q_world_from_driver_rotation: IDENTITY_QUATERNION,\n            vec_world_from_driver_translation: [0.0, 0.0, 0.0],\n            q_driver_from_head_rotation: IDENTITY_QUATERNION,\n            vec_driver_from_head_translation: [0.0, 0.0, 0.0],\n            vec_position: [position[0] as f64, position[1] as f64, position[2] as f64],\n            vec_velocity: [0.0, 0.0, 0.0],\n            vec_acceleration: [0.0, 0.0, 0.0],\n            q_rotation,\n            vec_angular_velocity: [0.0, 0.0, 0.0],\n            vec_angular_acceleration: [0.0, 0.0, 0.0],\n            result: TRACKING_RESULT_RUNNING_OK,\n            pose_is_valid: true,\n            will_drift_in_yaw: false,\n            should_apply_head_model: false,\n            device_is_connected: true,\n        };\n    }\n\n    DriverPose {\r\n        pose_time_offset: 0.0,\r\n        q_world_from_driver_rotation: IDENTITY_QUATERNION,\r\n        vec_world_from_driver_translation: [0.0, 0.0, 0.0],\r\n        q_driver_from_head_rotation: IDENTITY_QUATERNION,\r\n        vec_driver_from_head_translation: [0.0, 0.0, 0.0],\r\n        vec_position: [0.0, 1.2, -0.4],\r\n        vec_velocity: [0.0, 0.0, 0.0],\r\n        vec_acceleration: [0.0, 0.0, 0.0],\r\n        q_rotation: IDENTITY_QUATERNION,\r\n        vec_angular_velocity: [0.0, 0.0, 0.0],\r\n        vec_angular_acceleration: [0.0, 0.0, 0.0],\r\n        result: TRACKING_RESULT_RUNNING_OK,\r\n        pose_is_valid: true,\r\n        will_drift_in_yaw: false,\r\n        should_apply_head_model: false,\r\n        device_is_connected: true,\r\n    }\r\n}\r\n\nfn quaternion_from_rotation_matrix(m: [[f32; 3]; 3]) -> DriverPoseQuaternion {\n    let m00 = m[0][0] as f64;\n    let m01 = m[0][1] as f64;\n    let m02 = m[0][2] as f64;\n    let m10 = m[1][0] as f64;\n    let m11 = m[1][1] as f64;\n    let m12 = m[1][2] as f64;\n    let m20 = m[2][0] as f64;\n    let m21 = m[2][1] as f64;\n    let m22 = m[2][2] as f64;\n\n    let trace = m00 + m11 + m22;\n    if trace > 0.0 {\n        let s = (trace + 1.0).sqrt() * 2.0;\n        return DriverPoseQuaternion {\n            w: 0.25 * s,\n            x: (m21 - m12) / s,\n            y: (m02 - m20) / s,\n            z: (m10 - m01) / s,\n        };\n    }\n\n    if m00 > m11 && m00 > m22 {\n        let s = (1.0 + m00 - m11 - m22).sqrt() * 2.0;\n        return DriverPoseQuaternion {\n            w: (m21 - m12) / s,\n            x: 0.25 * s,\n            y: (m01 + m10) / s,\n            z: (m02 + m20) / s,\n        };\n    }\n\n    if m11 > m22 {\n        let s = (1.0 + m11 - m00 - m22).sqrt() * 2.0;\n        return DriverPoseQuaternion {\n            w: (m02 - m20) / s,\n            x: (m01 + m10) / s,\n            y: 0.25 * s,\n            z: (m12 + m21) / s,\n        };\n    }\n\n    let s = (1.0 + m22 - m00 - m11).sqrt() * 2.0;\n    DriverPoseQuaternion {\n        w: (m10 - m01) / s,\n        x: (m02 + m20) / s,\n        y: (m12 + m21) / s,\n        z: 0.25 * s,\n    }\n}\n\r\n#[repr(C)]\r\nstruct ServerTrackedDeviceProviderVTable {\r\n    init: extern \"C\" fn(*mut ServerTrackedDeviceProvider, *mut c_void) -> EvRInitError,\r\n    cleanup: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    get_interface_versions:\r\n        extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> *const *const c_char,\r\n    run_frame: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    should_block_standby_mode: extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> bool,\r\n    enter_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    leave_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n}\r\n\r\n#[repr(C)]\r\nstruct TrackedDeviceServerDriverVTable {\r\n    activate: extern \"C\" fn(*mut TrackedDeviceServerDriver, u32) -> EvRInitError,\r\n    deactivate: extern \"C\" fn(*mut TrackedDeviceServerDriver),\r\n    enter_standby: extern \"C\" fn(*mut TrackedDeviceServerDriver),\r\n    get_component:\r\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char) -> *mut c_void,\r\n    debug_request:\r\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char, *mut c_char, u32),\r\n    get_pose: extern \"C\" fn(*mut TrackedDeviceServerDriver) -> DriverPose,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerTrackedDeviceProvider {\r\n    vtable: *const ServerTrackedDeviceProviderVTable,\r\n}\r\nunsafe impl Sync for ServerTrackedDeviceProvider {}\r\n\r\n#[repr(C)]\r\nstruct TrackedDeviceServerDriver {\r\n    vtable: *const TrackedDeviceServerDriverVTable,\r\n}\r\nunsafe impl Sync for TrackedDeviceServerDriver {}\r\n\r\nstatic DEVICE_INDEX: AtomicU32 = AtomicU32::new(u32::MAX);\r\nstatic DEVICE_ACTIVE: AtomicBool = AtomicBool::new(false);\r\n\r\nextern \"C\" fn provider_init(\r\n    _this: *mut ServerTrackedDeviceProvider,\r\n    driver_context: *mut c_void,\r\n) -> EvRInitError {\r\n    let ok = openvr_runtime::initialize(\r\n        driver_context,\r\n        (&DEVICE as *const TrackedDeviceServerDriver).cast_mut().cast(),\r\n    );\r\n    if !ok {\r\n        return VR_INIT_ERROR_DRIVER_FAILED;\r\n    }\r\n\r\n    VR_INIT_ERROR_NONE\r\n}\r\n\r\nextern \"C\" fn provider_cleanup(_this: *mut ServerTrackedDeviceProvider) {\r\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\r\n    openvr_runtime::cleanup();\r\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn provider_get_interface_versions(\r\n    _this: *mut ServerTrackedDeviceProvider,\r\n) -> *const *const c_char {\r\n    openvr_runtime::interface_versions_ptr()\r\n}\r\n\r\nextern \"C\" fn provider_run_frame(_this: *mut ServerTrackedDeviceProvider) {\r\n    if DEVICE_ACTIVE.load(Ordering::SeqCst) {\r\n        let object_id = DEVICE_INDEX.load(Ordering::SeqCst);\r\n        if object_id != u32::MAX {\r\n            let pose = connected_controller_pose();\r\n            openvr_runtime::push_pose_update(\r\n                object_id,\r\n                (&pose as *const DriverPose).cast(),\r\n                core::mem::size_of::<DriverPose>() as u32,\r\n            );\r\n            openvr_runtime::update_pose_components();\n        }\r\n    }\r\n\r\n    openvr_runtime::route_haptic_events();\r\n    haptics::on_frame();\r\n}\r\n\r\nextern \"C\" fn provider_should_block_standby_mode(_this: *mut ServerTrackedDeviceProvider) -> bool {\r\n    false\r\n}\r\n\r\nextern \"C\" fn provider_enter_standby(_this: *mut ServerTrackedDeviceProvider) {}\r\n\r\nextern \"C\" fn provider_leave_standby(_this: *mut ServerTrackedDeviceProvider) {}\r\n\r\nextern \"C\" fn device_activate(_this: *mut TrackedDeviceServerDriver, object_id: u32) -> EvRInitError {\r\n    DEVICE_INDEX.store(object_id, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(true, Ordering::SeqCst);\r\n\r\n    if !openvr_runtime::activate_device(object_id) {\r\n        return VR_INIT_ERROR_DRIVER_FAILED;\r\n    }\r\n\r\n    let pose = connected_controller_pose();\r\n    openvr_runtime::push_pose_update(\r\n        object_id,\r\n        (&pose as *const DriverPose).cast(),\r\n        core::mem::size_of::<DriverPose>() as u32,\r\n    );\r\n\r\n    VR_INIT_ERROR_NONE\r\n}\r\n\r\nextern \"C\" fn device_deactivate(_this: *mut TrackedDeviceServerDriver) {\r\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\r\n    openvr_runtime::deactivate_device();\r\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn device_enter_standby(_this: *mut TrackedDeviceServerDriver) {\r\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn device_get_component(\r\n    _this: *mut TrackedDeviceServerDriver,\r\n    _component_name: *const c_char,\r\n) -> *mut c_void {\r\n    core::ptr::null_mut()\r\n}\r\n\r\nextern \"C\" fn device_debug_request(\r\n    _this: *mut TrackedDeviceServerDriver,\r\n    _request: *const c_char,\r\n    response: *mut c_char,\r\n    response_size: u32,\r\n) {\r\n    if response.is_null() || response_size == 0 {\r\n        return;\r\n    }\r\n    unsafe {\r\n        *response = 0;\r\n    }\r\n}\r\n\r\nextern \"C\" fn device_get_pose(_this: *mut TrackedDeviceServerDriver) -> DriverPose {\r\n    connected_controller_pose()\r\n}\r\n\r\nstatic PROVIDER_VTABLE: ServerTrackedDeviceProviderVTable = ServerTrackedDeviceProviderVTable {\r\n    init: provider_init,\r\n    cleanup: provider_cleanup,\r\n    get_interface_versions: provider_get_interface_versions,\r\n    run_frame: provider_run_frame,\r\n    should_block_standby_mode: provider_should_block_standby_mode,\r\n    enter_standby: provider_enter_standby,\r\n    leave_standby: provider_leave_standby,\r\n};\r\n\r\nstatic DEVICE_VTABLE: TrackedDeviceServerDriverVTable = TrackedDeviceServerDriverVTable {\r\n    activate: device_activate,\r\n    deactivate: device_deactivate,\r\n    enter_standby: device_enter_standby,\r\n    get_component: device_get_component,\r\n    debug_request: device_debug_request,\r\n    get_pose: device_get_pose,\r\n};\r\n\r\nstatic PROVIDER: ServerTrackedDeviceProvider = ServerTrackedDeviceProvider {\r\n    vtable: &PROVIDER_VTABLE,\r\n};\r\n\r\nstatic DEVICE: TrackedDeviceServerDriver = TrackedDeviceServerDriver {\r\n    vtable: &DEVICE_VTABLE,\r\n};\r\n\r\nfn set_return_code(out: *mut i32, code: DriverReturnCode) {\r\n    if !out.is_null() {\r\n        unsafe {\r\n            *out = code as i32;\r\n        }\r\n    }\r\n}\r\n\r\nfn read_interface_name(ptr: *const i8) -> Option<&'static str> {\r\n    if ptr.is_null() {\r\n        return None;\r\n    }\r\n\r\n    let cstr = unsafe { CStr::from_ptr(ptr) };\r\n    let Ok(name) = cstr.to_str() else {\r\n        return None;\r\n    };\r\n\r\n    if name == SERVER_PROVIDER_INTERFACE {\r\n        return Some(SERVER_PROVIDER_INTERFACE);\r\n    }\r\n    if name == SERVER_PROVIDER_INTERFACE_ALT {\r\n        return Some(SERVER_PROVIDER_INTERFACE);\r\n    }\r\n    if name == TRACKED_DEVICE_INTERFACE {\r\n        return Some(TRACKED_DEVICE_INTERFACE);\r\n    }\r\n\r\n    None\r\n}\r\n\r\n/// Resolves OpenVR interface requests to static provider/device instances.\r\n///\r\n/// On match, writes `DriverReturnCode::None` to `p_return_code` and returns a\r\n/// stable interface pointer. On failure, writes an error code and returns null.\r\npub fn hmd_driver_factory(\r\n    p_interface_name: *const c_char,\r\n    p_return_code: *mut i32,\r\n) -> *mut c_void {\r\n    let Some(interface_name) = read_interface_name(p_interface_name) else {\r\n        set_return_code(p_return_code, DriverReturnCode::InitInterfaceNotFound);\r\n        return core::ptr::null_mut();\r\n    };\r\n\r\n    if interface_name == SERVER_PROVIDER_INTERFACE {\r\n        set_return_code(p_return_code, DriverReturnCode::None);\r\n        return (&PROVIDER as *const ServerTrackedDeviceProvider)\r\n            .cast_mut()\r\n            .cast();\r\n    }\r\n\r\n    if interface_name == TRACKED_DEVICE_INTERFACE {\r\n        set_return_code(p_return_code, DriverReturnCode::None);\r\n        return (&DEVICE as *const TrackedDeviceServerDriver)\r\n            .cast_mut()\r\n            .cast();\r\n    }\r\n\r\n    set_return_code(p_return_code, DriverReturnCode::Unknown);\r\n    core::ptr::null_mut()\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":411,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\input\\big_haptic_profile.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/big_haptic_profile.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/big_haptic_profile.json","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1622,"edits":[{"text":"{\r\n  \"jsonid\": \"input_profile\",\r\n  \"controller_type\": \"big_haptic_driver\",\r\n  \"device_class\": \"TrackedDeviceClass_Controller\",\n  \"input_bindingui_mode\": \"controller_handed\",\r\n  \"legacy_binding\": \"{system}/legacy_bindings_generic.json\",\n  \"input_source\": {\r\n    \"/output/haptic\": {\n      \"type\": \"vibration\",\r\n      \"binding_image_point\": [0.5, 0.5]\r\n    },\n    \"/pose/raw\": {\n      \"type\": \"pose\",\n      \"binding_image_point\": [0.5, 0.2]\n    },\n    \"/pose/tip\": {\n      \"type\": \"pose\",\n      \"binding_image_point\": [0.5, 0.1]\n    }\r\n  },\n  \"default_bindings\": [\n    {\n      \"app_key\": \"openvr.component.vrcompositor\",\n      \"binding_url\": \"vrcompositor_bindings_big_haptic_driver.json\"\n    }\n  ]\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":29,"endColumn":1}}]},{"type":"create","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\input\\vrcompositor_bindings_big_haptic_driver.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1623,"initialContent":"","telemetryInfo":{}},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\input\\vrcompositor_bindings_big_haptic_driver.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1624,"edits":[{"text":"{","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":1,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\input\\vrcompositor_bindings_big_haptic_driver.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1625,"edits":[{"text":"\n  \"action_manifest_version\": 0,","range":{"startLineNumber":2,"startColumn":1,"endLineNumber":2,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\input\\vrcompositor_bindings_big_haptic_driver.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1626,"edits":[{"text":"\n  \"app_key\": \"openvr.component.vrcompositor\",","range":{"startLineNumber":3,"startColumn":1,"endLineNumber":3,"endColumn":1}}]},{"type":"create","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\driver.vrresources","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/driver.vrresources","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/driver.vrresources","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1627,"initialContent":"","telemetryInfo":{}},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\driver.vrresources","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/driver.vrresources","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/driver.vrresources","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1628,"edits":[{"text":"{","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":1,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\input\\vrcompositor_bindings_big_haptic_driver.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1629,"edits":[{"text":"\n  \"bindings\": {","range":{"startLineNumber":4,"startColumn":1,"endLineNumber":4,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\driver.vrresources","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/driver.vrresources","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/driver.vrresources","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1630,"edits":[{"text":"\n  \"jsonid\": \"vrresources\",","range":{"startLineNumber":2,"startColumn":1,"endLineNumber":2,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\input\\vrcompositor_bindings_big_haptic_driver.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1631,"edits":[{"text":"\n    \"/actions/lasermouse\": {","range":{"startLineNumber":5,"startColumn":1,"endLineNumber":5,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\driver.vrresources","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/driver.vrresources","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/driver.vrresources","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1632,"edits":[{"text":"\n  \"statusicons\": {","range":{"startLineNumber":3,"startColumn":1,"endLineNumber":3,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\input\\vrcompositor_bindings_big_haptic_driver.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1633,"edits":[{"text":"\n      \"haptics\": [","range":{"startLineNumber":6,"startColumn":1,"endLineNumber":6,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\driver.vrresources","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/driver.vrresources","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/driver.vrresources","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1634,"edits":[{"text":"\n    \"LeftController\": {","range":{"startLineNumber":4,"startColumn":1,"endLineNumber":4,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\input\\vrcompositor_bindings_big_haptic_driver.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1635,"edits":[{"text":"\n        {","range":{"startLineNumber":7,"startColumn":1,"endLineNumber":7,"endColumn":1}}]},{"type":"create","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\settings\\default.vrsettings","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/settings/default.vrsettings","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/settings/default.vrsettings","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1636,"initialContent":"","telemetryInfo":{}},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\settings\\default.vrsettings","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/settings/default.vrsettings","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/settings/default.vrsettings","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1637,"edits":[{"text":"{","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":1,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\driver.vrresources","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/driver.vrresources","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/driver.vrresources","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1638,"edits":[{"text":"\n      \"Prop_NamedIconPathDeviceOff_String\": \"{system}/icons/controller_status_off.png\",","range":{"startLineNumber":5,"startColumn":1,"endLineNumber":5,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\input\\vrcompositor_bindings_big_haptic_driver.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1639,"edits":[{"text":"\n          \"output\": \"/actions/lasermouse/out/haptic\",","range":{"startLineNumber":8,"startColumn":1,"endLineNumber":8,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\settings\\default.vrsettings","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/settings/default.vrsettings","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/settings/default.vrsettings","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1640,"edits":[{"text":"\n  \"driver_big_haptic_driver\": {","range":{"startLineNumber":2,"startColumn":1,"endLineNumber":2,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\driver.vrresources","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/driver.vrresources","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/driver.vrresources","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1641,"edits":[{"text":"\n      \"Prop_NamedIconPathDeviceSearching_String\": \"{system}/icons/controller_status_searching.gif\",","range":{"startLineNumber":6,"startColumn":1,"endLineNumber":6,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\input\\vrcompositor_bindings_big_haptic_driver.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1642,"edits":[{"text":"\n          \"path\": \"/user/hand/left/output/haptic\"","range":{"startLineNumber":9,"startColumn":1,"endLineNumber":9,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\settings\\default.vrsettings","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/settings/default.vrsettings","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/settings/default.vrsettings","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1643,"edits":[{"text":"\n    \"enable\": true,","range":{"startLineNumber":3,"startColumn":1,"endLineNumber":3,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\driver.vrresources","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/driver.vrresources","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/driver.vrresources","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1644,"edits":[{"text":"\n      \"Prop_NamedIconPathDeviceSearchingAlert_String\": \"{system}/icons/controller_status_searching_alert.gif\",","range":{"startLineNumber":7,"startColumn":1,"endLineNumber":7,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\input\\vrcompositor_bindings_big_haptic_driver.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1645,"edits":[{"text":"\n        },","range":{"startLineNumber":10,"startColumn":1,"endLineNumber":10,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\settings\\default.vrsettings","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/settings/default.vrsettings","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/settings/default.vrsettings","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1646,"edits":[{"text":"\n    \"loadPriority\": 50,","range":{"startLineNumber":4,"startColumn":1,"endLineNumber":4,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\driver.vrresources","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/driver.vrresources","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/driver.vrresources","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1647,"edits":[{"text":"\n      \"Prop_NamedIconPathDeviceReady_String\": \"{system}/icons/controller_status_ready.png\",","range":{"startLineNumber":8,"startColumn":1,"endLineNumber":8,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\input\\vrcompositor_bindings_big_haptic_driver.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1648,"edits":[{"text":"\n        {","range":{"startLineNumber":11,"startColumn":1,"endLineNumber":11,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\settings\\default.vrsettings","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/settings/default.vrsettings","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/settings/default.vrsettings","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1649,"edits":[{"text":"\n    \"blocked_by_safe_mode\": false","range":{"startLineNumber":5,"startColumn":1,"endLineNumber":5,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\driver.vrresources","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/driver.vrresources","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/driver.vrresources","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1650,"edits":[{"text":"\n      \"Prop_NamedIconPathDeviceReadyAlert_String\": \"{system}/icons/controller_status_ready_alert.png\",","range":{"startLineNumber":9,"startColumn":1,"endLineNumber":9,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\input\\vrcompositor_bindings_big_haptic_driver.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1651,"edits":[{"text":"\n          \"output\": \"/actions/lasermouse/out/haptic\",","range":{"startLineNumber":12,"startColumn":1,"endLineNumber":12,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\settings\\default.vrsettings","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/settings/default.vrsettings","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/settings/default.vrsettings","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1652,"edits":[{"text":"\n  }","range":{"startLineNumber":6,"startColumn":1,"endLineNumber":6,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\driver.vrresources","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/driver.vrresources","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/driver.vrresources","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1653,"edits":[{"text":"\n      \"Prop_NamedIconPathDeviceNotReady_String\": \"{system}/icons/controller_status_error.png\",","range":{"startLineNumber":10,"startColumn":1,"endLineNumber":10,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\input\\vrcompositor_bindings_big_haptic_driver.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1654,"edits":[{"text":"\n          \"path\": \"/user/hand/right/output/haptic\"","range":{"startLineNumber":13,"startColumn":1,"endLineNumber":13,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\settings\\default.vrsettings","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/settings/default.vrsettings","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/settings/default.vrsettings","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1655,"edits":[{"text":"\n}","range":{"startLineNumber":7,"startColumn":1,"endLineNumber":7,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\driver.vrresources","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/driver.vrresources","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/driver.vrresources","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1656,"edits":[{"text":"\n      \"Prop_NamedIconPathDeviceStandby_String\": \"{system}/icons/controller_status_standby.png\",","range":{"startLineNumber":11,"startColumn":1,"endLineNumber":11,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\input\\vrcompositor_bindings_big_haptic_driver.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1657,"edits":[{"text":"\n        }","range":{"startLineNumber":14,"startColumn":1,"endLineNumber":14,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\settings\\default.vrsettings","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/settings/default.vrsettings","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/settings/default.vrsettings","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1658,"edits":[{"text":"\n","range":{"startLineNumber":8,"startColumn":1,"endLineNumber":8,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\driver.vrresources","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/driver.vrresources","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/driver.vrresources","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1659,"edits":[{"text":"\n      \"Prop_NamedIconPathDeviceAlertLow_String\": \"{system}/icons/controller_status_ready_low.png\"","range":{"startLineNumber":12,"startColumn":1,"endLineNumber":12,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\input\\vrcompositor_bindings_big_haptic_driver.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1660,"edits":[{"text":"\n      ],","range":{"startLineNumber":15,"startColumn":1,"endLineNumber":15,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\driver.vrresources","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/driver.vrresources","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/driver.vrresources","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1661,"edits":[{"text":"\n    },","range":{"startLineNumber":13,"startColumn":1,"endLineNumber":13,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\input\\vrcompositor_bindings_big_haptic_driver.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1662,"edits":[{"text":"\n      \"poses\": [","range":{"startLineNumber":16,"startColumn":1,"endLineNumber":16,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\driver.vrresources","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/driver.vrresources","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/driver.vrresources","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1663,"edits":[{"text":"\n    \"RightController\": {","range":{"startLineNumber":14,"startColumn":1,"endLineNumber":14,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\input\\vrcompositor_bindings_big_haptic_driver.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1664,"edits":[{"text":"\n        {","range":{"startLineNumber":17,"startColumn":1,"endLineNumber":17,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\driver.vrresources","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/driver.vrresources","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/driver.vrresources","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1665,"edits":[{"text":"\n      \"Alias\": \"LeftController\"","range":{"startLineNumber":15,"startColumn":1,"endLineNumber":15,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\input\\vrcompositor_bindings_big_haptic_driver.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1666,"edits":[{"text":"\n          \"output\": \"/actions/lasermouse/in/Pointer\",","range":{"startLineNumber":18,"startColumn":1,"endLineNumber":18,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\driver.vrresources","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/driver.vrresources","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/driver.vrresources","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1667,"edits":[{"text":"\n    }","range":{"startLineNumber":16,"startColumn":1,"endLineNumber":16,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\input\\vrcompositor_bindings_big_haptic_driver.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1668,"edits":[{"text":"\n          \"path\": \"/user/hand/left/pose/tip\"","range":{"startLineNumber":19,"startColumn":1,"endLineNumber":19,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\driver.vrresources","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/driver.vrresources","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/driver.vrresources","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1669,"edits":[{"text":"\n  }","range":{"startLineNumber":17,"startColumn":1,"endLineNumber":17,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\input\\vrcompositor_bindings_big_haptic_driver.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1670,"edits":[{"text":"\n        },","range":{"startLineNumber":20,"startColumn":1,"endLineNumber":20,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\driver.vrresources","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/driver.vrresources","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/driver.vrresources","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1671,"edits":[{"text":"\n}","range":{"startLineNumber":18,"startColumn":1,"endLineNumber":18,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\input\\vrcompositor_bindings_big_haptic_driver.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1672,"edits":[{"text":"\n        {","range":{"startLineNumber":21,"startColumn":1,"endLineNumber":21,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\driver.vrresources","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/driver.vrresources","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/driver.vrresources","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1673,"edits":[{"text":"\n","range":{"startLineNumber":19,"startColumn":1,"endLineNumber":19,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\input\\vrcompositor_bindings_big_haptic_driver.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1674,"edits":[{"text":"\n          \"output\": \"/actions/lasermouse/in/Pointer\",","range":{"startLineNumber":22,"startColumn":1,"endLineNumber":22,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\input\\vrcompositor_bindings_big_haptic_driver.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1675,"edits":[{"text":"\n          \"path\": \"/user/hand/right/pose/tip\"","range":{"startLineNumber":23,"startColumn":1,"endLineNumber":23,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\input\\vrcompositor_bindings_big_haptic_driver.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1676,"edits":[{"text":"\n        }","range":{"startLineNumber":24,"startColumn":1,"endLineNumber":24,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\input\\vrcompositor_bindings_big_haptic_driver.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1677,"edits":[{"text":"\n      ]","range":{"startLineNumber":25,"startColumn":1,"endLineNumber":25,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\input\\vrcompositor_bindings_big_haptic_driver.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1678,"edits":[{"text":"\n    }","range":{"startLineNumber":26,"startColumn":1,"endLineNumber":26,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\input\\vrcompositor_bindings_big_haptic_driver.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1679,"edits":[{"text":"\n  }","range":{"startLineNumber":27,"startColumn":1,"endLineNumber":27,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\input\\vrcompositor_bindings_big_haptic_driver.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1680,"edits":[{"text":"\n}","range":{"startLineNumber":28,"startColumn":1,"endLineNumber":28,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\input\\vrcompositor_bindings_big_haptic_driver.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1681,"edits":[{"text":"\n","range":{"startLineNumber":29,"startColumn":1,"endLineNumber":29,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\openvr_runtime.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","scheme":"file"},"requestId":"request_7a4b86e1-4322-4b4e-9754-ee5435807546","epoch":1683,"edits":[{"text":"//! OpenVR runtime integration helpers.\r\n//!\r\n//! This module owns driver-context interface resolution, tracked-device\r\n//! registration, component/property setup, and OpenVR haptic event routing.\r\n\r\nuse core::ffi::c_void;\r\nuse core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU64, Ordering};\r\nuse std::ffi::CString;\r\nuse std::os::raw::c_char;\r\n\r\nuse crate::haptics;\r\n\r\ntype EvRInitError = i32;\r\n\r\nconst IVR_SERVER_DRIVER_HOST_VERSION: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\nconst IVR_DRIVER_INPUT_VERSION: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_PROPERTIES_VERSION: &[u8] = b\"IVRProperties_001\\0\";\r\nconst IVR_DRIVER_LOG_VERSION: &[u8] = b\"IVRDriverLog_001\\0\";\r\n\r\nconst TRACKED_DEVICE_CLASS_CONTROLLER: i32 = 2;\r\nconst VREVENT_INPUT_HAPTIC_VIBRATION: u32 = 1700;\r\n\r\nconst PROP_MODEL_NUMBER_STRING: u32 = 1001;\r\nconst PROP_SERIAL_NUMBER_STRING: u32 = 1002;\r\nconst PROP_TRACKING_SYSTEM_NAME_STRING: u32 = 1000;\r\nconst PROP_REGISTERED_DEVICE_TYPE_STRING: u32 = 1036;\r\nconst PROP_INPUT_PROFILE_PATH_STRING: u32 = 1037;\r\nconst PROP_CONTROLLER_TYPE_STRING: u32 = 7000;\r\nconst PROP_CONTROLLER_ROLE_HINT_INT32: u32 = 3007;\r\nconst PROP_HAS_CONTROLLER_COMPONENT_BOOL: u32 = 6003;\r\nconst TRACKED_DEVICE_INDEX_HMD: u32 = 0;\r\n\r\nconst K_UN_BOOL_PROPERTY_TAG: u32 = 4;\r\nconst K_UN_INT32_PROPERTY_TAG: u32 = 2;\r\nconst K_UN_STRING_PROPERTY_TAG: u32 = 5;\r\nconst PROPERTY_WRITE_SET: i32 = 0;\r\n\r\nconst DEVICE_SERIAL_CSTR: &[u8] = b\"qtpy-samd21-haptic-001\\0\";\r\nconst DEVICE_MODEL_CSTR: &[u8] = b\"QT Py SAMD21 Haptic\\0\";\r\nconst DEVICE_TRACKING_SYSTEM_NAME_CSTR: &[u8] = b\"big_haptic_driver\\0\";\r\nconst DEVICE_REGISTERED_TYPE_CSTR: &[u8] = b\"big_haptic_driver/controller\\0\";\r\nconst DEVICE_CONTROLLER_TYPE_CSTR: &[u8] = b\"big_haptic_driver\\0\";\r\nconst DEVICE_INPUT_PROFILE_CSTR: &[u8] = b\"{big_haptic_driver}/input/big_haptic_profile.json\\0\";\r\nconst DEVICE_HAPTIC_PATH_CSTR: &[u8] = b\"/output/haptic\\0\";\r\nconst DEVICE_POSE_RAW_PATH_CSTR: &[u8] = b\"/pose/raw\\0\";\r\nconst DEVICE_POSE_TIP_PATH_CSTR: &[u8] = b\"/pose/tip\\0\";\r\n\r\nconst PROVIDER_IFACE_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_004\\0\";\r\nconst PROVIDER_IFACE_ALT_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_005\\0\";\r\nconst DEVICE_IFACE_CSTR: &[u8] = b\"ITrackedDeviceServerDriver_005\\0\";\r\n\r\nconst IVR_PROPERTIES_CSTR: &[u8] = b\"IVRProperties_001\\0\";\r\nconst IVR_DRIVER_INPUT_CSTR: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_SERVER_DRIVER_HOST_CSTR: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\nconst IVR_DRIVER_LOG_CSTR: &[u8] = b\"IVRDriverLog_001\\0\";\r\n\r\nconst IVR_SETTINGS_CSTR: &[u8] = b\"IVRSettings_003\\0\";\r\nconst IVR_DRIVER_MANAGER_CSTR: &[u8] = b\"IVRDriverManager_001\\0\";\r\nconst IVR_RESOURCES_CSTR: &[u8] = b\"IVRResources_001\\0\";\r\n\r\n#[repr(C)]\r\nstruct InterfaceVersions([*const c_char; 11]);\r\nunsafe impl Sync for InterfaceVersions {}\r\n\r\nstatic INTERFACE_VERSIONS: InterfaceVersions = InterfaceVersions([\r\n    IVR_SETTINGS_CSTR.as_ptr().cast(),\r\n    DEVICE_IFACE_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_CSTR.as_ptr().cast(),\r\n    IVR_PROPERTIES_CSTR.as_ptr().cast(),\r\n    IVR_SERVER_DRIVER_HOST_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_LOG_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_MANAGER_CSTR.as_ptr().cast(),\r\n    IVR_RESOURCES_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_INPUT_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_ALT_CSTR.as_ptr().cast(),\r\n    core::ptr::null(),\r\n]);\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREventHapticVibration {\r\n    container_handle: u64,\r\n    component_handle: u64,\r\n    duration_seconds: f32,\r\n    frequency: f32,\r\n    amplitude: f32,\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nunion VREventData {\r\n    haptic_vibration: VREventHapticVibration,\r\n    reserved: [u8; 48],\r\n}\r\n\r\nimpl Default for VREventData {\r\n    fn default() -> Self {\r\n        Self { reserved: [0; 48] }\r\n    }\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREvent {\r\n    event_type: u32,\r\n    tracked_device_index: u32,\r\n    event_age_seconds: f32,\r\n    data: VREventData,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContextVTable {\r\n    get_generic_interface:\r\n        extern \"C\" fn(*mut DriverContext, *const c_char, *mut EvRInitError) -> *mut c_void,\r\n    get_driver_handle: extern \"C\" fn(*mut DriverContext) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContext {\r\n    vtable: *const DriverContextVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHostVTable {\r\n    tracked_device_added:\r\n        extern \"C\" fn(*mut ServerDriverHost, *const c_char, i32, *mut c_void) -> bool,\r\n    tracked_device_pose_updated: extern \"C\" fn(*mut ServerDriverHost, u32, *const c_void, u32),\r\n    vsync_event: extern \"C\" fn(*mut ServerDriverHost, f64),\r\n    vendor_specific_event: extern \"C\" fn(*mut ServerDriverHost, u32, u32, *const VREventData, f64),\r\n    is_exiting: extern \"C\" fn(*mut ServerDriverHost) -> bool,\r\n    poll_next_event: extern \"C\" fn(*mut ServerDriverHost, *mut VREvent, u32) -> bool,\r\n    get_raw_tracked_device_poses:\r\n        extern \"C\" fn(*mut ServerDriverHost, f32, *mut TrackedDevicePose, u32),\r\n    request_restart:\r\n        extern \"C\" fn(*mut ServerDriverHost, *const c_char, *const c_char, *const c_char, *const c_char),\r\n    get_frame_timings: extern \"C\" fn(*mut ServerDriverHost, *mut c_void, u32) -> u32,\r\n    set_display_eye_to_head:\r\n        extern \"C\" fn(*mut ServerDriverHost, u32, *const c_void, *const c_void),\r\n    set_display_projection_raw:\r\n        extern \"C\" fn(*mut ServerDriverHost, u32, *const c_void, *const c_void),\r\n    set_recommended_render_target_size: extern \"C\" fn(*mut ServerDriverHost, u32, u32, u32),\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHost {\r\n    vtable: *const ServerDriverHostVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverLogVTable {\r\n    log: extern \"C\" fn(*mut DriverLog, *const c_char),\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverLog {\r\n    vtable: *const DriverLogVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverInputVTable {\r\n    create_boolean_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n    update_boolean_component: extern \"C\" fn(*mut DriverInput, u64, bool, f64) -> i32,\r\n    create_scalar_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64, i32, i32) -> i32,\r\n    update_scalar_component: extern \"C\" fn(*mut DriverInput, u64, f32, f64) -> i32,\r\n    create_haptic_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n    create_skeleton_component: extern \"C\" fn(\r\n        *mut DriverInput,\r\n        u64,\r\n        *const c_char,\r\n        *const c_char,\r\n        *const c_char,\r\n        i32,\r\n        *const c_void,\r\n        u32,\r\n        *mut u64,\r\n    ) -> i32,\r\n    update_skeleton_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, i32, *const c_void, u32) -> i32,\r\n    create_pose_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n    update_pose_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const HmdMatrix34, f64) -> i32,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverInput {\r\n    vtable: *const DriverInputVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct PropertiesVTable {\r\n    read_property_batch: extern \"C\" fn(*mut Properties, u64, *mut c_void, u32) -> i32,\r\n    write_property_batch: extern \"C\" fn(*mut Properties, u64, *mut PropertyWrite, u32) -> i32,\r\n    get_prop_error_name_from_enum: extern \"C\" fn(*mut Properties, i32) -> *const c_char,\r\n    tracked_device_to_property_container: extern \"C\" fn(*mut Properties, u32) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct Properties {\r\n    vtable: *const PropertiesVTable,\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct HmdMatrix34 {\r\n    m: [[f32; 4]; 3],\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct TrackedDevicePose {\r\n    m_device_to_absolute_tracking: HmdMatrix34,\r\n    v_velocity: [f32; 3],\r\n    v_angular_velocity: [f32; 3],\r\n    e_tracking_result: i32,\r\n    b_pose_is_valid: bool,\r\n    b_device_is_connected: bool,\r\n}\r\n\r\n#[derive(Clone, Copy, Default)]\r\npub struct RuntimeHmdPose {\r\n    pub position_m: [f32; 3],\r\n    pub rotation: [[f32; 3]; 3],\r\n}\r\n\r\n#[repr(C)]\r\nstruct PropertyWrite {\r\n    prop: u32,\r\n    write_type: i32,\r\n    set_error: i32,\r\n    pv_buffer: *mut c_void,\r\n    un_buffer_size: u32,\r\n    un_tag: u32,\r\n    e_error: i32,\r\n}\r\n\r\nstatic DEVICE_REGISTERED: AtomicBool = AtomicBool::new(false);\r\n\r\nstatic SERVER_HOST_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DRIVER_INPUT_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic PROPERTIES_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DRIVER_LOG_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\n\r\nstatic DEVICE_CONTAINER: AtomicU64 = AtomicU64::new(0);\r\nstatic HAPTIC_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\nstatic POSE_RAW_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\nstatic POSE_TIP_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\nstatic HAPTIC_EVENT_SEQ: AtomicU64 = AtomicU64::new(0);\r\n\r\npub fn interface_versions_ptr() -> *const *const c_char {\r\n    INTERFACE_VERSIONS.0.as_ptr()\r\n}\r\n\r\nfn get_server_host() -> Option<*mut ServerDriverHost> {\r\n    let ptr = SERVER_HOST_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_driver_input() -> Option<*mut DriverInput> {\r\n    let ptr = DRIVER_INPUT_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_properties() -> Option<*mut Properties> {\r\n    let ptr = PROPERTIES_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_driver_log() -> Option<*mut DriverLog> {\r\n    let ptr = DRIVER_LOG_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\npub(crate) fn log_driver_message(message: &str) {\r\n    let Some(driver_log) = get_driver_log() else {\r\n        return;\r\n    };\r\n\r\n    let Ok(c_message) = CString::new(message) else {\r\n        return;\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*driver_log).vtable).log)(driver_log, c_message.as_ptr());\r\n    }\r\n}\r\n\r\nfn write_string_property(props: *mut Properties, container: u64, prop: u32, value_nul: &'static [u8]) {\r\n    if props.is_null() || value_nul.is_empty() {\r\n        return;\r\n    }\r\n\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: value_nul.as_ptr().cast_mut().cast(),\r\n        un_buffer_size: value_nul.len() as u32,\r\n        un_tag: K_UN_STRING_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn write_bool_property(props: *mut Properties, container: u64, prop: u32, value: bool) {\r\n    if props.is_null() {\r\n        return;\r\n    }\r\n\r\n    let mut bool_value = value;\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: (&mut bool_value as *mut bool).cast(),\r\n        un_buffer_size: core::mem::size_of::<bool>() as u32,\r\n        un_tag: K_UN_BOOL_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn write_int32_property(props: *mut Properties, container: u64, prop: u32, value: i32) {\r\n    if props.is_null() {\r\n        return;\r\n    }\r\n\r\n    let mut int_value = value;\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: (&mut int_value as *mut i32).cast(),\r\n        un_buffer_size: core::mem::size_of::<i32>() as u32,\r\n        un_tag: K_UN_INT32_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn register_tracked_device_if_needed(device_ptr: *mut c_void) {\r\n    if DEVICE_REGISTERED.load(Ordering::SeqCst) {\r\n        return;\r\n    }\r\n\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    let added = unsafe {\r\n        ((*(*host).vtable).tracked_device_added)(\r\n            host,\r\n            DEVICE_SERIAL_CSTR.as_ptr().cast(),\r\n            TRACKED_DEVICE_CLASS_CONTROLLER,\r\n            device_ptr,\r\n        )\r\n    };\r\n\r\n    if added {\r\n        DEVICE_REGISTERED.store(true, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\npub fn initialize(driver_context: *mut c_void, device_ptr: *mut c_void) -> bool {\r\n    if driver_context.is_null() {\r\n        return false;\r\n    }\r\n\r\n    let ctx = driver_context.cast::<DriverContext>();\r\n    let mut err = 0;\r\n\r\n    let get_iface = unsafe { (*(*ctx).vtable).get_generic_interface };\r\n\r\n    let host = get_iface(\r\n        ctx,\r\n        IVR_SERVER_DRIVER_HOST_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let driver_input = get_iface(\r\n        ctx,\r\n        IVR_DRIVER_INPUT_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let properties = get_iface(\r\n        ctx,\r\n        IVR_PROPERTIES_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let driver_log = get_iface(\r\n        ctx,\r\n        IVR_DRIVER_LOG_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    SERVER_HOST_IFACE.store(host, Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(driver_input, Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(properties, Ordering::SeqCst);\r\n    DRIVER_LOG_IFACE.store(driver_log, Ordering::SeqCst);\r\n\r\n    let ok = !host.is_null() && !driver_input.is_null() && !properties.is_null();\r\n    if !ok {\r\n        return false;\r\n    }\r\n\r\n    log_driver_message(&format!(\r\n        \"[big_haptic_driver] OpenVR init ok; sizeof(VREvent)={}, sizeof(VREventHapticVibration)={}\",\r\n        core::mem::size_of::<VREvent>(),\r\n        core::mem::size_of::<VREventHapticVibration>()\r\n    ));\r\n\r\n    register_tracked_device_if_needed(device_ptr);\r\n    true\r\n}\r\n\r\npub fn cleanup() {\r\n    DEVICE_REGISTERED.store(false, Ordering::SeqCst);\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    POSE_RAW_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    POSE_TIP_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    SERVER_HOST_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DRIVER_LOG_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n}\r\n\r\npub fn route_haptic_events() {\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    loop {\r\n        let mut event = VREvent::default();\r\n        let ok = unsafe {\r\n            ((*(*host).vtable).poll_next_event)(\r\n                host,\r\n                &mut event as *mut VREvent,\r\n                core::mem::size_of::<VREvent>() as u32,\r\n            )\r\n        };\r\n\r\n        if !ok {\r\n            break;\r\n        }\r\n\r\n        if event.event_type != VREVENT_INPUT_HAPTIC_VIBRATION {\r\n            continue;\r\n        }\r\n\r\n        let hv = unsafe { event.data.haptic_vibration };\r\n        let seq = HAPTIC_EVENT_SEQ.fetch_add(1, Ordering::SeqCst).saturating_add(1);\r\n\r\n        log_driver_message(&format!(\r\n            \"[big_haptic_driver] haptic event #{}: dev={} age_s={:.6} container={} component={} duration_s={:.6} (0x{:08x}) freq={:.3} amp={:.6} (0x{:08x})\",\r\n            seq,\r\n            event.tracked_device_index,\r\n            event.event_age_seconds,\r\n            hv.container_handle,\r\n            hv.component_handle,\r\n            hv.duration_seconds,\r\n            hv.duration_seconds.to_bits(),\r\n            hv.frequency,\r\n            hv.amplitude,\r\n            hv.amplitude.to_bits()\r\n        ));\r\n\r\n        if hv.amplitude > 0.0 && hv.duration_seconds <= 0.0 {\r\n            log_driver_message(&format!(\r\n                \"[big_haptic_driver] note: OpenVR sent duration<=0; per OpenVR guidance driver will emit one finite pulse (freq={:.3})\",\r\n                hv.frequency\r\n            ));\r\n        }\r\n\r\n        let container = DEVICE_CONTAINER.load(Ordering::SeqCst);\r\n        if container != 0 && hv.container_handle != 0 && hv.container_handle != container {\r\n            log_driver_message(&format!(\r\n                \"[big_haptic_driver] skipped haptic event: container mismatch event={} driver={}\",\r\n                hv.container_handle, container\r\n            ));\r\n            continue;\r\n        }\r\n\r\n        let haptic_component = HAPTIC_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n        if haptic_component != 0 && hv.component_handle != 0 && hv.component_handle != haptic_component {\r\n            log_driver_message(&format!(\r\n                \"[big_haptic_driver] skipped haptic event: component mismatch event={} driver={}\",\r\n                hv.component_handle, haptic_component\r\n            ));\r\n            continue;\r\n        }\r\n\r\n        log_driver_message(\"[big_haptic_driver] forwarding haptic request to HID layer\");\r\n\r\n        haptics::handle_haptic_request(haptics::HapticVibrationRequest {\r\n            amplitude: hv.amplitude,\r\n            duration_seconds: hv.duration_seconds,\r\n            frequency: hv.frequency,\r\n        });\r\n    }\r\n}\r\n\r\npub fn push_pose_update(object_id: u32, pose: *const c_void, pose_size: u32) {\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    if pose.is_null() || pose_size == 0 {\r\n        return;\r\n    }\r\n\r\n    unsafe {\r\n        ((*(*host).vtable).tracked_device_pose_updated)(host, object_id, pose, pose_size);\r\n    }\r\n}\r\n\r\npub fn sample_hmd_pose() -> Option<RuntimeHmdPose> {\r\n    let host = get_server_host()?;\r\n\r\n    let mut poses = [TrackedDevicePose::default(); 1];\r\n    unsafe {\r\n        ((*(*host).vtable).get_raw_tracked_device_poses)(\r\n            host,\r\n            0.0,\r\n            poses.as_mut_ptr(),\r\n            poses.len() as u32,\r\n        );\r\n    }\r\n\r\n    let hmd = poses[TRACKED_DEVICE_INDEX_HMD as usize];\r\n    if !hmd.b_device_is_connected || !hmd.b_pose_is_valid {\r\n        return None;\r\n    }\r\n\r\n    Some(RuntimeHmdPose {\r\n        position_m: [\r\n            hmd.m_device_to_absolute_tracking.m[0][3],\r\n            hmd.m_device_to_absolute_tracking.m[1][3],\r\n            hmd.m_device_to_absolute_tracking.m[2][3],\r\n        ],\r\n        rotation: [\r\n            [\r\n                hmd.m_device_to_absolute_tracking.m[0][0],\r\n                hmd.m_device_to_absolute_tracking.m[0][1],\r\n                hmd.m_device_to_absolute_tracking.m[0][2],\r\n            ],\r\n            [\r\n                hmd.m_device_to_absolute_tracking.m[1][0],\r\n                hmd.m_device_to_absolute_tracking.m[1][1],\r\n                hmd.m_device_to_absolute_tracking.m[1][2],\r\n            ],\r\n            [\r\n                hmd.m_device_to_absolute_tracking.m[2][0],\r\n                hmd.m_device_to_absolute_tracking.m[2][1],\r\n                hmd.m_device_to_absolute_tracking.m[2][2],\r\n            ],\r\n        ],\r\n    })\r\n}\r\n\r\npub fn update_pose_components() {\r\n    let Some(input) = get_driver_input() else {\r\n        return;\r\n    };\r\n\r\n    let raw_handle = POSE_RAW_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n    let tip_handle = POSE_TIP_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n\r\n    let raw_offset = HmdMatrix34 {\r\n        m: [\r\n            [1.0, 0.0, 0.0, 0.0],\r\n            [0.0, 1.0, 0.0, 0.0],\r\n            [0.0, 0.0, 1.0, 0.0],\r\n        ],\r\n    };\r\n    let tip_offset = HmdMatrix34 {\r\n        m: [\r\n            [1.0, 0.0, 0.0, 0.0],\r\n            [0.0, 1.0, 0.0, 0.0],\r\n            [0.0, 0.0, 1.0, -0.04],\r\n        ],\r\n    };\r\n\r\n    if raw_handle != 0 {\r\n        unsafe {\r\n            ((*(*input).vtable).update_pose_component)(input, raw_handle, &raw_offset, 0.0);\r\n        }\r\n    }\r\n\r\n    if tip_handle != 0 {\r\n        unsafe {\r\n            ((*(*input).vtable).update_pose_component)(input, tip_handle, &tip_offset, 0.0);\r\n        }\r\n    }\r\n}\r\n\r\npub fn activate_device(object_id: u32) -> bool {\r\n    let Some(props) = get_properties() else {\r\n        return false;\r\n    };\r\n\r\n    let container = unsafe { ((*(*props).vtable).tracked_device_to_property_container)(props, object_id) };\r\n    DEVICE_CONTAINER.store(container, Ordering::SeqCst);\r\n\r\n    write_string_property(props, container, PROP_MODEL_NUMBER_STRING, DEVICE_MODEL_CSTR);\r\n    write_string_property(props, container, PROP_SERIAL_NUMBER_STRING, DEVICE_SERIAL_CSTR);\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_TRACKING_SYSTEM_NAME_STRING,\r\n        DEVICE_TRACKING_SYSTEM_NAME_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_REGISTERED_DEVICE_TYPE_STRING,\r\n        DEVICE_REGISTERED_TYPE_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_INPUT_PROFILE_PATH_STRING,\r\n        DEVICE_INPUT_PROFILE_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_CONTROLLER_TYPE_STRING,\r\n        DEVICE_CONTROLLER_TYPE_CSTR,\r\n    );\r\n    write_bool_property(props, container, PROP_HAS_CONTROLLER_COMPONENT_BOOL, true);\r\n    write_int32_property(props, container, PROP_CONTROLLER_ROLE_HINT_INT32, 2);\n\r\n    if let Some(input) = get_driver_input() {\r\n        let mut handle = 0_u64;\r\n        let rc = unsafe {\r\n            ((*(*input).vtable).create_haptic_component)(\r\n                input,\r\n                container,\r\n                DEVICE_HAPTIC_PATH_CSTR.as_ptr().cast(),\r\n                &mut handle as *mut u64,\r\n            )\r\n        };\r\n\r\n        if rc == 0 {\r\n            HAPTIC_COMPONENT_HANDLE.store(handle, Ordering::SeqCst);\r\n        }\r\n\r\n        let mut pose_raw_handle = 0_u64;\r\n        let rc_raw = unsafe {\r\n            ((*(*input).vtable).create_pose_component)(\r\n                input,\r\n                container,\r\n                DEVICE_POSE_RAW_PATH_CSTR.as_ptr().cast(),\r\n                &mut pose_raw_handle as *mut u64,\r\n            )\r\n        };\r\n        if rc_raw == 0 {\r\n            POSE_RAW_COMPONENT_HANDLE.store(pose_raw_handle, Ordering::SeqCst);\r\n        }\r\n\r\n        let mut pose_tip_handle = 0_u64;\r\n        let rc_tip = unsafe {\r\n            ((*(*input).vtable).create_pose_component)(\r\n                input,\r\n                container,\r\n                DEVICE_POSE_TIP_PATH_CSTR.as_ptr().cast(),\r\n                &mut pose_tip_handle as *mut u64,\r\n            )\r\n        };\r\n        if rc_tip == 0 {\r\n            POSE_TIP_COMPONENT_HANDLE.store(pose_tip_handle, Ordering::SeqCst);\r\n        }\r\n\r\n        update_pose_components();\r\n    }\r\n\r\n    true\r\n}\r\n\r\npub fn deactivate_device() {\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    POSE_RAW_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    POSE_TIP_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":718,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_6f13d5f3-b3de-4f61-a09f-3c6ee71f5876","epoch":1687,"edits":[{"text":"//! OpenVR ABI/vtable scaffolding for the Big Haptic Driver.\r\n//!\r\n//! This module owns C ABI exports and interface routing for SteamVR-facing\r\n//! provider/device entry points, while delegating runtime integration\r\n//! (device registration, component setup, and event routing) to\r\n//! `openvr_runtime`.\r\n\r\nuse core::ffi::{c_void, CStr};\r\nuse core::sync::atomic::{AtomicBool, AtomicU32, Ordering};\r\nuse std::os::raw::c_char;\r\n\r\nuse crate::haptics;\r\nuse crate::openvr_runtime;\r\n\r\nconst SERVER_PROVIDER_INTERFACE: &str = \"IServerTrackedDeviceProvider_004\";\r\nconst SERVER_PROVIDER_INTERFACE_ALT: &str = \"IServerTrackedDeviceProvider_005\";\r\nconst TRACKED_DEVICE_INTERFACE: &str = \"ITrackedDeviceServerDriver_005\";\r\nconst VR_INIT_ERROR_DRIVER_FAILED: EvRInitError = 200;\r\n\r\n#[repr(i32)]\r\nenum DriverReturnCode {\r\n    None = 0,\r\n    Unknown = 1,\r\n    InitInterfaceNotFound = 105,\r\n}\r\n\r\ntype EvRInitError = i32;\r\nconst VR_INIT_ERROR_NONE: EvRInitError = 0;\r\nconst TRACKING_RESULT_RUNNING_OK: i32 = 200;\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nstruct DriverPoseQuaternion {\r\n    w: f64,\r\n    x: f64,\r\n    y: f64,\r\n    z: f64,\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nstruct DriverPose {\r\n    pose_time_offset: f64,\r\n    q_world_from_driver_rotation: DriverPoseQuaternion,\r\n    vec_world_from_driver_translation: [f64; 3],\r\n    q_driver_from_head_rotation: DriverPoseQuaternion,\r\n    vec_driver_from_head_translation: [f64; 3],\r\n    vec_position: [f64; 3],\r\n    vec_velocity: [f64; 3],\r\n    vec_acceleration: [f64; 3],\r\n    q_rotation: DriverPoseQuaternion,\r\n    vec_angular_velocity: [f64; 3],\r\n    vec_angular_acceleration: [f64; 3],\r\n    result: i32,\r\n    pose_is_valid: bool,\r\n    will_drift_in_yaw: bool,\r\n    should_apply_head_model: bool,\r\n    device_is_connected: bool,\r\n}\r\n\r\nconst IDENTITY_QUATERNION: DriverPoseQuaternion = DriverPoseQuaternion {\r\n    w: 1.0,\r\n    x: 0.0,\r\n    y: 0.0,\r\n    z: 0.0,\r\n};\r\n\r\nfn connected_controller_pose() -> DriverPose {\r\n    if let Some(hmd_pose) = openvr_runtime::sample_hmd_pose() {\r\n        let rotation = hmd_pose.rotation;\r\n        let position = hmd_pose.position_m;\n\r\n        let q_rotation = quaternion_from_rotation_matrix(rotation);\r\n\r\n        return DriverPose {\r\n            pose_time_offset: 0.0,\r\n            q_world_from_driver_rotation: IDENTITY_QUATERNION,\r\n            vec_world_from_driver_translation: [0.0, 0.0, 0.0],\r\n            q_driver_from_head_rotation: IDENTITY_QUATERNION,\r\n            vec_driver_from_head_translation: [0.0, 0.0, 0.0],\r\n            vec_position: [position[0] as f64, position[1] as f64, position[2] as f64],\r\n            vec_velocity: [0.0, 0.0, 0.0],\r\n            vec_acceleration: [0.0, 0.0, 0.0],\r\n            q_rotation,\r\n            vec_angular_velocity: [0.0, 0.0, 0.0],\r\n            vec_angular_acceleration: [0.0, 0.0, 0.0],\r\n            result: TRACKING_RESULT_RUNNING_OK,\r\n            pose_is_valid: true,\r\n            will_drift_in_yaw: false,\r\n            should_apply_head_model: false,\r\n            device_is_connected: true,\r\n        };\r\n    }\r\n\r\n    DriverPose {\r\n        pose_time_offset: 0.0,\r\n        q_world_from_driver_rotation: IDENTITY_QUATERNION,\r\n        vec_world_from_driver_translation: [0.0, 0.0, 0.0],\r\n        q_driver_from_head_rotation: IDENTITY_QUATERNION,\r\n        vec_driver_from_head_translation: [0.0, 0.0, 0.0],\r\n        vec_position: [0.0, 1.2, -0.4],\r\n        vec_velocity: [0.0, 0.0, 0.0],\r\n        vec_acceleration: [0.0, 0.0, 0.0],\r\n        q_rotation: IDENTITY_QUATERNION,\r\n        vec_angular_velocity: [0.0, 0.0, 0.0],\r\n        vec_angular_acceleration: [0.0, 0.0, 0.0],\r\n        result: TRACKING_RESULT_RUNNING_OK,\r\n        pose_is_valid: true,\r\n        will_drift_in_yaw: false,\r\n        should_apply_head_model: false,\r\n        device_is_connected: true,\r\n    }\r\n}\r\n\r\nfn quaternion_from_rotation_matrix(m: [[f32; 3]; 3]) -> DriverPoseQuaternion {\r\n    let m00 = m[0][0] as f64;\r\n    let m01 = m[0][1] as f64;\r\n    let m02 = m[0][2] as f64;\r\n    let m10 = m[1][0] as f64;\r\n    let m11 = m[1][1] as f64;\r\n    let m12 = m[1][2] as f64;\r\n    let m20 = m[2][0] as f64;\r\n    let m21 = m[2][1] as f64;\r\n    let m22 = m[2][2] as f64;\r\n\r\n    let trace = m00 + m11 + m22;\r\n    if trace > 0.0 {\r\n        let s = (trace + 1.0).sqrt() * 2.0;\r\n        return DriverPoseQuaternion {\r\n            w: 0.25 * s,\r\n            x: (m21 - m12) / s,\r\n            y: (m02 - m20) / s,\r\n            z: (m10 - m01) / s,\r\n        };\r\n    }\r\n\r\n    if m00 > m11 && m00 > m22 {\r\n        let s = (1.0 + m00 - m11 - m22).sqrt() * 2.0;\r\n        return DriverPoseQuaternion {\r\n            w: (m21 - m12) / s,\r\n            x: 0.25 * s,\r\n            y: (m01 + m10) / s,\r\n            z: (m02 + m20) / s,\r\n        };\r\n    }\r\n\r\n    if m11 > m22 {\r\n        let s = (1.0 + m11 - m00 - m22).sqrt() * 2.0;\r\n        return DriverPoseQuaternion {\r\n            w: (m02 - m20) / s,\r\n            x: (m01 + m10) / s,\r\n            y: 0.25 * s,\r\n            z: (m12 + m21) / s,\r\n        };\r\n    }\r\n\r\n    let s = (1.0 + m22 - m00 - m11).sqrt() * 2.0;\r\n    DriverPoseQuaternion {\r\n        w: (m10 - m01) / s,\r\n        x: (m02 + m20) / s,\r\n        y: (m12 + m21) / s,\r\n        z: 0.25 * s,\r\n    }\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerTrackedDeviceProviderVTable {\r\n    init: extern \"C\" fn(*mut ServerTrackedDeviceProvider, *mut c_void) -> EvRInitError,\r\n    cleanup: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    get_interface_versions:\r\n        extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> *const *const c_char,\r\n    run_frame: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    should_block_standby_mode: extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> bool,\r\n    enter_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    leave_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n}\r\n\r\n#[repr(C)]\r\nstruct TrackedDeviceServerDriverVTable {\r\n    activate: extern \"C\" fn(*mut TrackedDeviceServerDriver, u32) -> EvRInitError,\r\n    deactivate: extern \"C\" fn(*mut TrackedDeviceServerDriver),\r\n    enter_standby: extern \"C\" fn(*mut TrackedDeviceServerDriver),\r\n    get_component:\r\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char) -> *mut c_void,\r\n    debug_request:\r\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char, *mut c_char, u32),\r\n    get_pose: extern \"C\" fn(*mut TrackedDeviceServerDriver) -> DriverPose,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerTrackedDeviceProvider {\r\n    vtable: *const ServerTrackedDeviceProviderVTable,\r\n}\r\nunsafe impl Sync for ServerTrackedDeviceProvider {}\r\n\r\n#[repr(C)]\r\nstruct TrackedDeviceServerDriver {\r\n    vtable: *const TrackedDeviceServerDriverVTable,\r\n}\r\nunsafe impl Sync for TrackedDeviceServerDriver {}\r\n\r\nstatic DEVICE_INDEX: AtomicU32 = AtomicU32::new(u32::MAX);\r\nstatic DEVICE_ACTIVE: AtomicBool = AtomicBool::new(false);\r\n\r\nextern \"C\" fn provider_init(\r\n    _this: *mut ServerTrackedDeviceProvider,\r\n    driver_context: *mut c_void,\r\n) -> EvRInitError {\r\n    let ok = openvr_runtime::initialize(\r\n        driver_context,\r\n        (&DEVICE as *const TrackedDeviceServerDriver).cast_mut().cast(),\r\n    );\r\n    if !ok {\r\n        return VR_INIT_ERROR_DRIVER_FAILED;\r\n    }\r\n\r\n    VR_INIT_ERROR_NONE\r\n}\r\n\r\nextern \"C\" fn provider_cleanup(_this: *mut ServerTrackedDeviceProvider) {\r\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\r\n    openvr_runtime::cleanup();\r\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn provider_get_interface_versions(\r\n    _this: *mut ServerTrackedDeviceProvider,\r\n) -> *const *const c_char {\r\n    openvr_runtime::interface_versions_ptr()\r\n}\r\n\r\nextern \"C\" fn provider_run_frame(_this: *mut ServerTrackedDeviceProvider) {\r\n    if DEVICE_ACTIVE.load(Ordering::SeqCst) {\r\n        let object_id = DEVICE_INDEX.load(Ordering::SeqCst);\r\n        if object_id != u32::MAX {\r\n            let pose = connected_controller_pose();\r\n            openvr_runtime::push_pose_update(\r\n                object_id,\r\n                (&pose as *const DriverPose).cast(),\r\n                core::mem::size_of::<DriverPose>() as u32,\r\n            );\r\n            openvr_runtime::update_pose_components();\r\n        }\r\n    }\r\n\r\n    openvr_runtime::route_haptic_events();\r\n    haptics::on_frame();\r\n}\r\n\r\nextern \"C\" fn provider_should_block_standby_mode(_this: *mut ServerTrackedDeviceProvider) -> bool {\r\n    false\r\n}\r\n\r\nextern \"C\" fn provider_enter_standby(_this: *mut ServerTrackedDeviceProvider) {}\r\n\r\nextern \"C\" fn provider_leave_standby(_this: *mut ServerTrackedDeviceProvider) {}\r\n\r\nextern \"C\" fn device_activate(_this: *mut TrackedDeviceServerDriver, object_id: u32) -> EvRInitError {\r\n    DEVICE_INDEX.store(object_id, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(true, Ordering::SeqCst);\r\n\r\n    if !openvr_runtime::activate_device(object_id) {\r\n        return VR_INIT_ERROR_DRIVER_FAILED;\r\n    }\r\n\r\n    let pose = connected_controller_pose();\r\n    openvr_runtime::push_pose_update(\r\n        object_id,\r\n        (&pose as *const DriverPose).cast(),\r\n        core::mem::size_of::<DriverPose>() as u32,\r\n    );\r\n\r\n    VR_INIT_ERROR_NONE\r\n}\r\n\r\nextern \"C\" fn device_deactivate(_this: *mut TrackedDeviceServerDriver) {\r\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\r\n    openvr_runtime::deactivate_device();\r\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn device_enter_standby(_this: *mut TrackedDeviceServerDriver) {\r\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn device_get_component(\r\n    _this: *mut TrackedDeviceServerDriver,\r\n    _component_name: *const c_char,\r\n) -> *mut c_void {\r\n    core::ptr::null_mut()\r\n}\r\n\r\nextern \"C\" fn device_debug_request(\r\n    _this: *mut TrackedDeviceServerDriver,\r\n    _request: *const c_char,\r\n    response: *mut c_char,\r\n    response_size: u32,\r\n) {\r\n    if response.is_null() || response_size == 0 {\r\n        return;\r\n    }\r\n    unsafe {\r\n        *response = 0;\r\n    }\r\n}\r\n\r\nextern \"C\" fn device_get_pose(_this: *mut TrackedDeviceServerDriver) -> DriverPose {\r\n    connected_controller_pose()\r\n}\r\n\r\nstatic PROVIDER_VTABLE: ServerTrackedDeviceProviderVTable = ServerTrackedDeviceProviderVTable {\r\n    init: provider_init,\r\n    cleanup: provider_cleanup,\r\n    get_interface_versions: provider_get_interface_versions,\r\n    run_frame: provider_run_frame,\r\n    should_block_standby_mode: provider_should_block_standby_mode,\r\n    enter_standby: provider_enter_standby,\r\n    leave_standby: provider_leave_standby,\r\n};\r\n\r\nstatic DEVICE_VTABLE: TrackedDeviceServerDriverVTable = TrackedDeviceServerDriverVTable {\r\n    activate: device_activate,\r\n    deactivate: device_deactivate,\r\n    enter_standby: device_enter_standby,\r\n    get_component: device_get_component,\r\n    debug_request: device_debug_request,\r\n    get_pose: device_get_pose,\r\n};\r\n\r\nstatic PROVIDER: ServerTrackedDeviceProvider = ServerTrackedDeviceProvider {\r\n    vtable: &PROVIDER_VTABLE,\r\n};\r\n\r\nstatic DEVICE: TrackedDeviceServerDriver = TrackedDeviceServerDriver {\r\n    vtable: &DEVICE_VTABLE,\r\n};\r\n\r\nfn set_return_code(out: *mut i32, code: DriverReturnCode) {\r\n    if !out.is_null() {\r\n        unsafe {\r\n            *out = code as i32;\r\n        }\r\n    }\r\n}\r\n\r\nfn read_interface_name(ptr: *const i8) -> Option<&'static str> {\r\n    if ptr.is_null() {\r\n        return None;\r\n    }\r\n\r\n    let cstr = unsafe { CStr::from_ptr(ptr) };\r\n    let Ok(name) = cstr.to_str() else {\r\n        return None;\r\n    };\r\n\r\n    if name == SERVER_PROVIDER_INTERFACE {\r\n        return Some(SERVER_PROVIDER_INTERFACE);\r\n    }\r\n    if name == SERVER_PROVIDER_INTERFACE_ALT {\r\n        return Some(SERVER_PROVIDER_INTERFACE);\r\n    }\r\n    if name == TRACKED_DEVICE_INTERFACE {\r\n        return Some(TRACKED_DEVICE_INTERFACE);\r\n    }\r\n\r\n    None\r\n}\r\n\r\n/// Resolves OpenVR interface requests to static provider/device instances.\r\n///\r\n/// On match, writes `DriverReturnCode::None` to `p_return_code` and returns a\r\n/// stable interface pointer. On failure, writes an error code and returns null.\r\npub fn hmd_driver_factory(\r\n    p_interface_name: *const c_char,\r\n    p_return_code: *mut i32,\r\n) -> *mut c_void {\r\n    let Some(interface_name) = read_interface_name(p_interface_name) else {\r\n        set_return_code(p_return_code, DriverReturnCode::InitInterfaceNotFound);\r\n        return core::ptr::null_mut();\r\n    };\r\n\r\n    if interface_name == SERVER_PROVIDER_INTERFACE {\r\n        set_return_code(p_return_code, DriverReturnCode::None);\r\n        return (&PROVIDER as *const ServerTrackedDeviceProvider)\r\n            .cast_mut()\r\n            .cast();\r\n    }\r\n\r\n    if interface_name == TRACKED_DEVICE_INTERFACE {\r\n        set_return_code(p_return_code, DriverReturnCode::None);\r\n        return (&DEVICE as *const TrackedDeviceServerDriver)\r\n            .cast_mut()\r\n            .cast();\r\n    }\r\n\r\n    set_return_code(p_return_code, DriverReturnCode::Unknown);\r\n    core::ptr::null_mut()\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":402,"endColumn":1}},{"text":"","range":{"startLineNumber":402,"startColumn":1,"endLineNumber":411,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\openvr_runtime.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","scheme":"file"},"requestId":"request_6f13d5f3-b3de-4f61-a09f-3c6ee71f5876","epoch":1690,"edits":[{"text":"//! OpenVR runtime integration helpers.\r\n//!\r\n//! This module owns driver-context interface resolution, tracked-device\r\n//! registration, component/property setup, and OpenVR haptic event routing.\r\n\r\nuse core::ffi::c_void;\r\nuse core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU64, Ordering};\r\nuse std::ffi::CString;\r\nuse std::os::raw::c_char;\r\n\r\nuse crate::haptics;\r\n\r\ntype EvRInitError = i32;\r\n\r\nconst IVR_SERVER_DRIVER_HOST_VERSION: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\nconst IVR_DRIVER_INPUT_VERSION: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_PROPERTIES_VERSION: &[u8] = b\"IVRProperties_001\\0\";\r\nconst IVR_DRIVER_LOG_VERSION: &[u8] = b\"IVRDriverLog_001\\0\";\r\n\r\nconst TRACKED_DEVICE_CLASS_CONTROLLER: i32 = 2;\r\nconst VREVENT_INPUT_HAPTIC_VIBRATION: u32 = 1700;\r\n\r\nconst PROP_MODEL_NUMBER_STRING: u32 = 1001;\r\nconst PROP_SERIAL_NUMBER_STRING: u32 = 1002;\r\nconst PROP_TRACKING_SYSTEM_NAME_STRING: u32 = 1000;\r\nconst PROP_REGISTERED_DEVICE_TYPE_STRING: u32 = 1036;\r\nconst PROP_INPUT_PROFILE_PATH_STRING: u32 = 1037;\r\nconst PROP_CONTROLLER_TYPE_STRING: u32 = 7000;\r\nconst PROP_CONTROLLER_ROLE_HINT_INT32: u32 = 3007;\r\nconst PROP_HAS_CONTROLLER_COMPONENT_BOOL: u32 = 6003;\r\nconst TRACKED_DEVICE_INDEX_HMD: u32 = 0;\r\n\r\nconst K_UN_BOOL_PROPERTY_TAG: u32 = 4;\r\nconst K_UN_INT32_PROPERTY_TAG: u32 = 2;\r\nconst K_UN_STRING_PROPERTY_TAG: u32 = 5;\r\nconst PROPERTY_WRITE_SET: i32 = 0;\r\n\r\nconst DEVICE_SERIAL_CSTR: &[u8] = b\"qtpy-samd21-haptic-001\\0\";\r\nconst DEVICE_MODEL_CSTR: &[u8] = b\"QT Py SAMD21 Haptic\\0\";\r\nconst DEVICE_TRACKING_SYSTEM_NAME_CSTR: &[u8] = b\"big_haptic_driver\\0\";\r\nconst DEVICE_REGISTERED_TYPE_CSTR: &[u8] = b\"big_haptic_driver/controller\\0\";\r\nconst DEVICE_CONTROLLER_TYPE_CSTR: &[u8] = b\"big_haptic_driver\\0\";\r\nconst DEVICE_INPUT_PROFILE_CSTR: &[u8] = b\"{big_haptic_driver}/input/big_haptic_profile.json\\0\";\r\nconst DEVICE_HAPTIC_PATH_CSTR: &[u8] = b\"/output/haptic\\0\";\r\nconst DEVICE_POSE_RAW_PATH_CSTR: &[u8] = b\"/pose/raw\\0\";\r\nconst DEVICE_POSE_TIP_PATH_CSTR: &[u8] = b\"/pose/tip\\0\";\r\nconst DEVICE_SYSTEM_CLICK_PATH_CSTR: &[u8] = b\"/input/system/click\\0\";\nconst DEVICE_A_CLICK_PATH_CSTR: &[u8] = b\"/input/a/click\\0\";\nconst DEVICE_TRIGGER_CLICK_PATH_CSTR: &[u8] = b\"/input/trigger/click\\0\";\nconst DEVICE_TRIGGER_VALUE_PATH_CSTR: &[u8] = b\"/input/trigger/value\\0\";\n\nconst VR_SCALAR_TYPE_ABSOLUTE: i32 = 0;\nconst VR_SCALAR_UNITS_NORMALIZED_ONE_SIDED: i32 = 0;\n\r\nconst PROVIDER_IFACE_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_004\\0\";\r\nconst PROVIDER_IFACE_ALT_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_005\\0\";\r\nconst DEVICE_IFACE_CSTR: &[u8] = b\"ITrackedDeviceServerDriver_005\\0\";\r\n\r\nconst IVR_PROPERTIES_CSTR: &[u8] = b\"IVRProperties_001\\0\";\r\nconst IVR_DRIVER_INPUT_CSTR: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_SERVER_DRIVER_HOST_CSTR: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\nconst IVR_DRIVER_LOG_CSTR: &[u8] = b\"IVRDriverLog_001\\0\";\r\n\r\nconst IVR_SETTINGS_CSTR: &[u8] = b\"IVRSettings_003\\0\";\r\nconst IVR_DRIVER_MANAGER_CSTR: &[u8] = b\"IVRDriverManager_001\\0\";\r\nconst IVR_RESOURCES_CSTR: &[u8] = b\"IVRResources_001\\0\";\r\n\r\n#[repr(C)]\r\nstruct InterfaceVersions([*const c_char; 11]);\r\nunsafe impl Sync for InterfaceVersions {}\r\n\r\nstatic INTERFACE_VERSIONS: InterfaceVersions = InterfaceVersions([\r\n    IVR_SETTINGS_CSTR.as_ptr().cast(),\r\n    DEVICE_IFACE_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_CSTR.as_ptr().cast(),\r\n    IVR_PROPERTIES_CSTR.as_ptr().cast(),\r\n    IVR_SERVER_DRIVER_HOST_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_LOG_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_MANAGER_CSTR.as_ptr().cast(),\r\n    IVR_RESOURCES_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_INPUT_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_ALT_CSTR.as_ptr().cast(),\r\n    core::ptr::null(),\r\n]);\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREventHapticVibration {\r\n    container_handle: u64,\r\n    component_handle: u64,\r\n    duration_seconds: f32,\r\n    frequency: f32,\r\n    amplitude: f32,\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nunion VREventData {\r\n    haptic_vibration: VREventHapticVibration,\r\n    reserved: [u8; 48],\r\n}\r\n\r\nimpl Default for VREventData {\r\n    fn default() -> Self {\r\n        Self { reserved: [0; 48] }\r\n    }\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREvent {\r\n    event_type: u32,\r\n    tracked_device_index: u32,\r\n    event_age_seconds: f32,\r\n    data: VREventData,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContextVTable {\r\n    get_generic_interface:\r\n        extern \"C\" fn(*mut DriverContext, *const c_char, *mut EvRInitError) -> *mut c_void,\r\n    get_driver_handle: extern \"C\" fn(*mut DriverContext) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContext {\r\n    vtable: *const DriverContextVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHostVTable {\r\n    tracked_device_added:\r\n        extern \"C\" fn(*mut ServerDriverHost, *const c_char, i32, *mut c_void) -> bool,\r\n    tracked_device_pose_updated: extern \"C\" fn(*mut ServerDriverHost, u32, *const c_void, u32),\r\n    vsync_event: extern \"C\" fn(*mut ServerDriverHost, f64),\r\n    vendor_specific_event: extern \"C\" fn(*mut ServerDriverHost, u32, u32, *const VREventData, f64),\r\n    is_exiting: extern \"C\" fn(*mut ServerDriverHost) -> bool,\r\n    poll_next_event: extern \"C\" fn(*mut ServerDriverHost, *mut VREvent, u32) -> bool,\r\n    get_raw_tracked_device_poses:\r\n        extern \"C\" fn(*mut ServerDriverHost, f32, *mut TrackedDevicePose, u32),\r\n    request_restart:\r\n        extern \"C\" fn(*mut ServerDriverHost, *const c_char, *const c_char, *const c_char, *const c_char),\r\n    get_frame_timings: extern \"C\" fn(*mut ServerDriverHost, *mut c_void, u32) -> u32,\r\n    set_display_eye_to_head:\r\n        extern \"C\" fn(*mut ServerDriverHost, u32, *const c_void, *const c_void),\r\n    set_display_projection_raw:\r\n        extern \"C\" fn(*mut ServerDriverHost, u32, *const c_void, *const c_void),\r\n    set_recommended_render_target_size: extern \"C\" fn(*mut ServerDriverHost, u32, u32, u32),\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHost {\r\n    vtable: *const ServerDriverHostVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverLogVTable {\r\n    log: extern \"C\" fn(*mut DriverLog, *const c_char),\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverLog {\r\n    vtable: *const DriverLogVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverInputVTable {\r\n    create_boolean_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n    update_boolean_component: extern \"C\" fn(*mut DriverInput, u64, bool, f64) -> i32,\r\n    create_scalar_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64, i32, i32) -> i32,\r\n    update_scalar_component: extern \"C\" fn(*mut DriverInput, u64, f32, f64) -> i32,\r\n    create_haptic_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n    create_skeleton_component: extern \"C\" fn(\r\n        *mut DriverInput,\r\n        u64,\r\n        *const c_char,\r\n        *const c_char,\r\n        *const c_char,\r\n        i32,\r\n        *const c_void,\r\n        u32,\r\n        *mut u64,\r\n    ) -> i32,\r\n    update_skeleton_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, i32, *const c_void, u32) -> i32,\r\n    create_pose_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n    update_pose_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const HmdMatrix34, f64) -> i32,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverInput {\r\n    vtable: *const DriverInputVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct PropertiesVTable {\r\n    read_property_batch: extern \"C\" fn(*mut Properties, u64, *mut c_void, u32) -> i32,\r\n    write_property_batch: extern \"C\" fn(*mut Properties, u64, *mut PropertyWrite, u32) -> i32,\r\n    get_prop_error_name_from_enum: extern \"C\" fn(*mut Properties, i32) -> *const c_char,\r\n    tracked_device_to_property_container: extern \"C\" fn(*mut Properties, u32) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct Properties {\r\n    vtable: *const PropertiesVTable,\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct HmdMatrix34 {\r\n    m: [[f32; 4]; 3],\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct TrackedDevicePose {\r\n    m_device_to_absolute_tracking: HmdMatrix34,\r\n    v_velocity: [f32; 3],\r\n    v_angular_velocity: [f32; 3],\r\n    e_tracking_result: i32,\r\n    b_pose_is_valid: bool,\r\n    b_device_is_connected: bool,\r\n}\r\n\r\n#[derive(Clone, Copy, Default)]\r\npub struct RuntimeHmdPose {\r\n    pub position_m: [f32; 3],\r\n    pub rotation: [[f32; 3]; 3],\r\n}\r\n\r\n#[repr(C)]\r\nstruct PropertyWrite {\r\n    prop: u32,\r\n    write_type: i32,\r\n    set_error: i32,\r\n    pv_buffer: *mut c_void,\r\n    un_buffer_size: u32,\r\n    un_tag: u32,\r\n    e_error: i32,\r\n}\r\n\r\nstatic DEVICE_REGISTERED: AtomicBool = AtomicBool::new(false);\r\n\r\nstatic SERVER_HOST_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DRIVER_INPUT_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic PROPERTIES_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DRIVER_LOG_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\n\r\nstatic DEVICE_CONTAINER: AtomicU64 = AtomicU64::new(0);\r\nstatic HAPTIC_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\nstatic POSE_RAW_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\nstatic POSE_TIP_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\nstatic HAPTIC_EVENT_SEQ: AtomicU64 = AtomicU64::new(0);\r\n\r\npub fn interface_versions_ptr() -> *const *const c_char {\r\n    INTERFACE_VERSIONS.0.as_ptr()\r\n}\r\n\r\nfn get_server_host() -> Option<*mut ServerDriverHost> {\r\n    let ptr = SERVER_HOST_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_driver_input() -> Option<*mut DriverInput> {\r\n    let ptr = DRIVER_INPUT_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_properties() -> Option<*mut Properties> {\r\n    let ptr = PROPERTIES_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_driver_log() -> Option<*mut DriverLog> {\r\n    let ptr = DRIVER_LOG_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\npub(crate) fn log_driver_message(message: &str) {\r\n    let Some(driver_log) = get_driver_log() else {\r\n        return;\r\n    };\r\n\r\n    let Ok(c_message) = CString::new(message) else {\r\n        return;\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*driver_log).vtable).log)(driver_log, c_message.as_ptr());\r\n    }\r\n}\r\n\r\nfn write_string_property(props: *mut Properties, container: u64, prop: u32, value_nul: &'static [u8]) {\r\n    if props.is_null() || value_nul.is_empty() {\r\n        return;\r\n    }\r\n\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: value_nul.as_ptr().cast_mut().cast(),\r\n        un_buffer_size: value_nul.len() as u32,\r\n        un_tag: K_UN_STRING_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn write_bool_property(props: *mut Properties, container: u64, prop: u32, value: bool) {\r\n    if props.is_null() {\r\n        return;\r\n    }\r\n\r\n    let mut bool_value = value;\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: (&mut bool_value as *mut bool).cast(),\r\n        un_buffer_size: core::mem::size_of::<bool>() as u32,\r\n        un_tag: K_UN_BOOL_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn write_int32_property(props: *mut Properties, container: u64, prop: u32, value: i32) {\r\n    if props.is_null() {\r\n        return;\r\n    }\r\n\r\n    let mut int_value = value;\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: (&mut int_value as *mut i32).cast(),\r\n        un_buffer_size: core::mem::size_of::<i32>() as u32,\r\n        un_tag: K_UN_INT32_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn register_tracked_device_if_needed(device_ptr: *mut c_void) {\r\n    if DEVICE_REGISTERED.load(Ordering::SeqCst) {\r\n        return;\r\n    }\r\n\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    let added = unsafe {\r\n        ((*(*host).vtable).tracked_device_added)(\r\n            host,\r\n            DEVICE_SERIAL_CSTR.as_ptr().cast(),\r\n            TRACKED_DEVICE_CLASS_CONTROLLER,\r\n            device_ptr,\r\n        )\r\n    };\r\n\r\n    if added {\r\n        DEVICE_REGISTERED.store(true, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\npub fn initialize(driver_context: *mut c_void, device_ptr: *mut c_void) -> bool {\r\n    if driver_context.is_null() {\r\n        return false;\r\n    }\r\n\r\n    let ctx = driver_context.cast::<DriverContext>();\r\n    let mut err = 0;\r\n\r\n    let get_iface = unsafe { (*(*ctx).vtable).get_generic_interface };\r\n\r\n    let host = get_iface(\r\n        ctx,\r\n        IVR_SERVER_DRIVER_HOST_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let driver_input = get_iface(\r\n        ctx,\r\n        IVR_DRIVER_INPUT_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let properties = get_iface(\r\n        ctx,\r\n        IVR_PROPERTIES_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let driver_log = get_iface(\r\n        ctx,\r\n        IVR_DRIVER_LOG_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    SERVER_HOST_IFACE.store(host, Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(driver_input, Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(properties, Ordering::SeqCst);\r\n    DRIVER_LOG_IFACE.store(driver_log, Ordering::SeqCst);\r\n\r\n    let ok = !host.is_null() && !driver_input.is_null() && !properties.is_null();\r\n    if !ok {\r\n        return false;\r\n    }\r\n\r\n    log_driver_message(&format!(\r\n        \"[big_haptic_driver] OpenVR init ok; sizeof(VREvent)={}, sizeof(VREventHapticVibration)={}\",\r\n        core::mem::size_of::<VREvent>(),\r\n        core::mem::size_of::<VREventHapticVibration>()\r\n    ));\r\n\r\n    register_tracked_device_if_needed(device_ptr);\r\n    true\r\n}\r\n\r\npub fn cleanup() {\r\n    DEVICE_REGISTERED.store(false, Ordering::SeqCst);\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    POSE_RAW_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    POSE_TIP_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    SERVER_HOST_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DRIVER_LOG_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n}\r\n\r\npub fn route_haptic_events() {\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    loop {\r\n        let mut event = VREvent::default();\r\n        let ok = unsafe {\r\n            ((*(*host).vtable).poll_next_event)(\r\n                host,\r\n                &mut event as *mut VREvent,\r\n                core::mem::size_of::<VREvent>() as u32,\r\n            )\r\n        };\r\n\r\n        if !ok {\r\n            break;\r\n        }\r\n\r\n        if event.event_type != VREVENT_INPUT_HAPTIC_VIBRATION {\r\n            continue;\r\n        }\r\n\r\n        let hv = unsafe { event.data.haptic_vibration };\r\n        let seq = HAPTIC_EVENT_SEQ.fetch_add(1, Ordering::SeqCst).saturating_add(1);\r\n\r\n        log_driver_message(&format!(\r\n            \"[big_haptic_driver] haptic event #{}: dev={} age_s={:.6} container={} component={} duration_s={:.6} (0x{:08x}) freq={:.3} amp={:.6} (0x{:08x})\",\r\n            seq,\r\n            event.tracked_device_index,\r\n            event.event_age_seconds,\r\n            hv.container_handle,\r\n            hv.component_handle,\r\n            hv.duration_seconds,\r\n            hv.duration_seconds.to_bits(),\r\n            hv.frequency,\r\n            hv.amplitude,\r\n            hv.amplitude.to_bits()\r\n        ));\r\n\r\n        if hv.amplitude > 0.0 && hv.duration_seconds <= 0.0 {\r\n            log_driver_message(&format!(\r\n                \"[big_haptic_driver] note: OpenVR sent duration<=0; per OpenVR guidance driver will emit one finite pulse (freq={:.3})\",\r\n                hv.frequency\r\n            ));\r\n        }\r\n\r\n        let container = DEVICE_CONTAINER.load(Ordering::SeqCst);\r\n        if container != 0 && hv.container_handle != 0 && hv.container_handle != container {\r\n            log_driver_message(&format!(\r\n                \"[big_haptic_driver] skipped haptic event: container mismatch event={} driver={}\",\r\n                hv.container_handle, container\r\n            ));\r\n            continue;\r\n        }\r\n\r\n        let haptic_component = HAPTIC_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n        if haptic_component != 0 && hv.component_handle != 0 && hv.component_handle != haptic_component {\r\n            log_driver_message(&format!(\r\n                \"[big_haptic_driver] skipped haptic event: component mismatch event={} driver={}\",\r\n                hv.component_handle, haptic_component\r\n            ));\r\n            continue;\r\n        }\r\n\r\n        log_driver_message(\"[big_haptic_driver] forwarding haptic request to HID layer\");\r\n\r\n        haptics::handle_haptic_request(haptics::HapticVibrationRequest {\r\n            amplitude: hv.amplitude,\r\n            duration_seconds: hv.duration_seconds,\r\n            frequency: hv.frequency,\r\n        });\r\n    }\r\n}\r\n\r\npub fn push_pose_update(object_id: u32, pose: *const c_void, pose_size: u32) {\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    if pose.is_null() || pose_size == 0 {\r\n        return;\r\n    }\r\n\r\n    unsafe {\r\n        ((*(*host).vtable).tracked_device_pose_updated)(host, object_id, pose, pose_size);\r\n    }\r\n}\r\n\r\npub fn sample_hmd_pose() -> Option<RuntimeHmdPose> {\r\n    let host = get_server_host()?;\r\n\r\n    let mut poses = [TrackedDevicePose::default(); 1];\r\n    unsafe {\r\n        ((*(*host).vtable).get_raw_tracked_device_poses)(\r\n            host,\r\n            0.0,\r\n            poses.as_mut_ptr(),\r\n            poses.len() as u32,\r\n        );\r\n    }\r\n\r\n    let hmd = poses[TRACKED_DEVICE_INDEX_HMD as usize];\r\n    if !hmd.b_device_is_connected || !hmd.b_pose_is_valid {\r\n        return None;\r\n    }\r\n\r\n    Some(RuntimeHmdPose {\r\n        position_m: [\r\n            hmd.m_device_to_absolute_tracking.m[0][3],\r\n            hmd.m_device_to_absolute_tracking.m[1][3],\r\n            hmd.m_device_to_absolute_tracking.m[2][3],\r\n        ],\r\n        rotation: [\r\n            [\r\n                hmd.m_device_to_absolute_tracking.m[0][0],\r\n                hmd.m_device_to_absolute_tracking.m[0][1],\r\n                hmd.m_device_to_absolute_tracking.m[0][2],\r\n            ],\r\n            [\r\n                hmd.m_device_to_absolute_tracking.m[1][0],\r\n                hmd.m_device_to_absolute_tracking.m[1][1],\r\n                hmd.m_device_to_absolute_tracking.m[1][2],\r\n            ],\r\n            [\r\n                hmd.m_device_to_absolute_tracking.m[2][0],\r\n                hmd.m_device_to_absolute_tracking.m[2][1],\r\n                hmd.m_device_to_absolute_tracking.m[2][2],\r\n            ],\r\n        ],\r\n    })\r\n}\r\n\r\npub fn update_pose_components() {\r\n    let Some(input) = get_driver_input() else {\r\n        return;\r\n    };\r\n\r\n    let raw_handle = POSE_RAW_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n    let tip_handle = POSE_TIP_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n\r\n    let raw_offset = HmdMatrix34 {\r\n        m: [\r\n            [1.0, 0.0, 0.0, 0.0],\r\n            [0.0, 1.0, 0.0, 0.0],\r\n            [0.0, 0.0, 1.0, 0.0],\r\n        ],\r\n    };\r\n    let tip_offset = HmdMatrix34 {\r\n        m: [\r\n            [1.0, 0.0, 0.0, 0.0],\r\n            [0.0, 1.0, 0.0, 0.0],\r\n            [0.0, 0.0, 1.0, -0.04],\r\n        ],\r\n    };\r\n\r\n    if raw_handle != 0 {\r\n        unsafe {\r\n            ((*(*input).vtable).update_pose_component)(input, raw_handle, &raw_offset, 0.0);\r\n        }\r\n    }\r\n\r\n    if tip_handle != 0 {\r\n        unsafe {\r\n            ((*(*input).vtable).update_pose_component)(input, tip_handle, &tip_offset, 0.0);\r\n        }\r\n    }\r\n}\r\n\r\npub fn activate_device(object_id: u32) -> bool {\r\n    let Some(props) = get_properties() else {\r\n        return false;\r\n    };\r\n\r\n    let container = unsafe { ((*(*props).vtable).tracked_device_to_property_container)(props, object_id) };\r\n    DEVICE_CONTAINER.store(container, Ordering::SeqCst);\r\n\r\n    write_string_property(props, container, PROP_MODEL_NUMBER_STRING, DEVICE_MODEL_CSTR);\r\n    write_string_property(props, container, PROP_SERIAL_NUMBER_STRING, DEVICE_SERIAL_CSTR);\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_TRACKING_SYSTEM_NAME_STRING,\r\n        DEVICE_TRACKING_SYSTEM_NAME_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_REGISTERED_DEVICE_TYPE_STRING,\r\n        DEVICE_REGISTERED_TYPE_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_INPUT_PROFILE_PATH_STRING,\r\n        DEVICE_INPUT_PROFILE_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_CONTROLLER_TYPE_STRING,\r\n        DEVICE_CONTROLLER_TYPE_CSTR,\r\n    );\r\n    write_bool_property(props, container, PROP_HAS_CONTROLLER_COMPONENT_BOOL, true);\r\n    write_int32_property(props, container, PROP_CONTROLLER_ROLE_HINT_INT32, 2);\r\n\r\n    if let Some(input) = get_driver_input() {\r\n        let mut handle = 0_u64;\r\n        let rc = unsafe {\r\n            ((*(*input).vtable).create_haptic_component)(\r\n                input,\r\n                container,\r\n                DEVICE_HAPTIC_PATH_CSTR.as_ptr().cast(),\r\n                &mut handle as *mut u64,\r\n            )\r\n        };\r\n\r\n        if rc == 0 {\r\n            HAPTIC_COMPONENT_HANDLE.store(handle, Ordering::SeqCst);\r\n        }\r\n\r\n        let mut pose_raw_handle = 0_u64;\r\n        let rc_raw = unsafe {\r\n            ((*(*input).vtable).create_pose_component)(\r\n                input,\r\n                container,\r\n                DEVICE_POSE_RAW_PATH_CSTR.as_ptr().cast(),\r\n                &mut pose_raw_handle as *mut u64,\r\n            )\r\n        };\r\n        if rc_raw == 0 {\r\n            POSE_RAW_COMPONENT_HANDLE.store(pose_raw_handle, Ordering::SeqCst);\r\n        }\r\n\r\n        let mut pose_tip_handle = 0_u64;\r\n        let rc_tip = unsafe {\r\n            ((*(*input).vtable).create_pose_component)(\r\n                input,\r\n                container,\r\n                DEVICE_POSE_TIP_PATH_CSTR.as_ptr().cast(),\r\n                &mut pose_tip_handle as *mut u64,\r\n            )\r\n        };\r\n        if rc_tip == 0 {\r\n            POSE_TIP_COMPONENT_HANDLE.store(pose_tip_handle, Ordering::SeqCst);\r\n        }\r\n\r\n        update_pose_components();\r\n    }\r\n\r\n    true\r\n}\r\n\r\npub fn deactivate_device() {\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    POSE_RAW_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    POSE_TIP_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":725,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\openvr_runtime.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","scheme":"file"},"requestId":"request_6f13d5f3-b3de-4f61-a09f-3c6ee71f5876","epoch":1692,"edits":[{"text":"//! OpenVR runtime integration helpers.\r\n//!\r\n//! This module owns driver-context interface resolution, tracked-device\r\n//! registration, component/property setup, and OpenVR haptic event routing.\r\n\r\nuse core::ffi::c_void;\r\nuse core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU64, Ordering};\r\nuse std::ffi::CString;\r\nuse std::os::raw::c_char;\r\n\r\nuse crate::haptics;\r\n\r\ntype EvRInitError = i32;\r\n\r\nconst IVR_SERVER_DRIVER_HOST_VERSION: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\nconst IVR_DRIVER_INPUT_VERSION: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_PROPERTIES_VERSION: &[u8] = b\"IVRProperties_001\\0\";\r\nconst IVR_DRIVER_LOG_VERSION: &[u8] = b\"IVRDriverLog_001\\0\";\r\n\r\nconst TRACKED_DEVICE_CLASS_CONTROLLER: i32 = 2;\r\nconst VREVENT_INPUT_HAPTIC_VIBRATION: u32 = 1700;\r\n\r\nconst PROP_MODEL_NUMBER_STRING: u32 = 1001;\r\nconst PROP_SERIAL_NUMBER_STRING: u32 = 1002;\r\nconst PROP_TRACKING_SYSTEM_NAME_STRING: u32 = 1000;\r\nconst PROP_REGISTERED_DEVICE_TYPE_STRING: u32 = 1036;\r\nconst PROP_INPUT_PROFILE_PATH_STRING: u32 = 1037;\r\nconst PROP_CONTROLLER_TYPE_STRING: u32 = 7000;\r\nconst PROP_CONTROLLER_ROLE_HINT_INT32: u32 = 3007;\r\nconst PROP_HAS_CONTROLLER_COMPONENT_BOOL: u32 = 6003;\r\nconst TRACKED_DEVICE_INDEX_HMD: u32 = 0;\r\n\r\nconst K_UN_BOOL_PROPERTY_TAG: u32 = 4;\r\nconst K_UN_INT32_PROPERTY_TAG: u32 = 2;\r\nconst K_UN_STRING_PROPERTY_TAG: u32 = 5;\r\nconst PROPERTY_WRITE_SET: i32 = 0;\r\n\r\nconst DEVICE_SERIAL_CSTR: &[u8] = b\"qtpy-samd21-haptic-001\\0\";\r\nconst DEVICE_MODEL_CSTR: &[u8] = b\"QT Py SAMD21 Haptic\\0\";\r\nconst DEVICE_TRACKING_SYSTEM_NAME_CSTR: &[u8] = b\"big_haptic_driver\\0\";\r\nconst DEVICE_REGISTERED_TYPE_CSTR: &[u8] = b\"big_haptic_driver/controller\\0\";\r\nconst DEVICE_CONTROLLER_TYPE_CSTR: &[u8] = b\"big_haptic_driver\\0\";\r\nconst DEVICE_INPUT_PROFILE_CSTR: &[u8] = b\"{big_haptic_driver}/input/big_haptic_profile.json\\0\";\r\nconst DEVICE_HAPTIC_PATH_CSTR: &[u8] = b\"/output/haptic\\0\";\r\nconst DEVICE_POSE_RAW_PATH_CSTR: &[u8] = b\"/pose/raw\\0\";\r\nconst DEVICE_POSE_TIP_PATH_CSTR: &[u8] = b\"/pose/tip\\0\";\r\nconst DEVICE_SYSTEM_CLICK_PATH_CSTR: &[u8] = b\"/input/system/click\\0\";\r\nconst DEVICE_A_CLICK_PATH_CSTR: &[u8] = b\"/input/a/click\\0\";\r\nconst DEVICE_TRIGGER_CLICK_PATH_CSTR: &[u8] = b\"/input/trigger/click\\0\";\r\nconst DEVICE_TRIGGER_VALUE_PATH_CSTR: &[u8] = b\"/input/trigger/value\\0\";\r\n\r\nconst VR_SCALAR_TYPE_ABSOLUTE: i32 = 0;\r\nconst VR_SCALAR_UNITS_NORMALIZED_ONE_SIDED: i32 = 0;\r\n\r\nconst PROVIDER_IFACE_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_004\\0\";\r\nconst PROVIDER_IFACE_ALT_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_005\\0\";\r\nconst DEVICE_IFACE_CSTR: &[u8] = b\"ITrackedDeviceServerDriver_005\\0\";\r\n\r\nconst IVR_PROPERTIES_CSTR: &[u8] = b\"IVRProperties_001\\0\";\r\nconst IVR_DRIVER_INPUT_CSTR: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_SERVER_DRIVER_HOST_CSTR: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\nconst IVR_DRIVER_LOG_CSTR: &[u8] = b\"IVRDriverLog_001\\0\";\r\n\r\nconst IVR_SETTINGS_CSTR: &[u8] = b\"IVRSettings_003\\0\";\r\nconst IVR_DRIVER_MANAGER_CSTR: &[u8] = b\"IVRDriverManager_001\\0\";\r\nconst IVR_RESOURCES_CSTR: &[u8] = b\"IVRResources_001\\0\";\r\n\r\n#[repr(C)]\r\nstruct InterfaceVersions([*const c_char; 11]);\r\nunsafe impl Sync for InterfaceVersions {}\r\n\r\nstatic INTERFACE_VERSIONS: InterfaceVersions = InterfaceVersions([\r\n    IVR_SETTINGS_CSTR.as_ptr().cast(),\r\n    DEVICE_IFACE_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_CSTR.as_ptr().cast(),\r\n    IVR_PROPERTIES_CSTR.as_ptr().cast(),\r\n    IVR_SERVER_DRIVER_HOST_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_LOG_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_MANAGER_CSTR.as_ptr().cast(),\r\n    IVR_RESOURCES_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_INPUT_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_ALT_CSTR.as_ptr().cast(),\r\n    core::ptr::null(),\r\n]);\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREventHapticVibration {\r\n    container_handle: u64,\r\n    component_handle: u64,\r\n    duration_seconds: f32,\r\n    frequency: f32,\r\n    amplitude: f32,\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nunion VREventData {\r\n    haptic_vibration: VREventHapticVibration,\r\n    reserved: [u8; 48],\r\n}\r\n\r\nimpl Default for VREventData {\r\n    fn default() -> Self {\r\n        Self { reserved: [0; 48] }\r\n    }\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREvent {\r\n    event_type: u32,\r\n    tracked_device_index: u32,\r\n    event_age_seconds: f32,\r\n    data: VREventData,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContextVTable {\r\n    get_generic_interface:\r\n        extern \"C\" fn(*mut DriverContext, *const c_char, *mut EvRInitError) -> *mut c_void,\r\n    get_driver_handle: extern \"C\" fn(*mut DriverContext) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContext {\r\n    vtable: *const DriverContextVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHostVTable {\r\n    tracked_device_added:\r\n        extern \"C\" fn(*mut ServerDriverHost, *const c_char, i32, *mut c_void) -> bool,\r\n    tracked_device_pose_updated: extern \"C\" fn(*mut ServerDriverHost, u32, *const c_void, u32),\r\n    vsync_event: extern \"C\" fn(*mut ServerDriverHost, f64),\r\n    vendor_specific_event: extern \"C\" fn(*mut ServerDriverHost, u32, u32, *const VREventData, f64),\r\n    is_exiting: extern \"C\" fn(*mut ServerDriverHost) -> bool,\r\n    poll_next_event: extern \"C\" fn(*mut ServerDriverHost, *mut VREvent, u32) -> bool,\r\n    get_raw_tracked_device_poses:\r\n        extern \"C\" fn(*mut ServerDriverHost, f32, *mut TrackedDevicePose, u32),\r\n    request_restart:\r\n        extern \"C\" fn(*mut ServerDriverHost, *const c_char, *const c_char, *const c_char, *const c_char),\r\n    get_frame_timings: extern \"C\" fn(*mut ServerDriverHost, *mut c_void, u32) -> u32,\r\n    set_display_eye_to_head:\r\n        extern \"C\" fn(*mut ServerDriverHost, u32, *const c_void, *const c_void),\r\n    set_display_projection_raw:\r\n        extern \"C\" fn(*mut ServerDriverHost, u32, *const c_void, *const c_void),\r\n    set_recommended_render_target_size: extern \"C\" fn(*mut ServerDriverHost, u32, u32, u32),\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHost {\r\n    vtable: *const ServerDriverHostVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverLogVTable {\r\n    log: extern \"C\" fn(*mut DriverLog, *const c_char),\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverLog {\r\n    vtable: *const DriverLogVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverInputVTable {\r\n    create_boolean_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n    update_boolean_component: extern \"C\" fn(*mut DriverInput, u64, bool, f64) -> i32,\r\n    create_scalar_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64, i32, i32) -> i32,\r\n    update_scalar_component: extern \"C\" fn(*mut DriverInput, u64, f32, f64) -> i32,\r\n    create_haptic_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n    create_skeleton_component: extern \"C\" fn(\r\n        *mut DriverInput,\r\n        u64,\r\n        *const c_char,\r\n        *const c_char,\r\n        *const c_char,\r\n        i32,\r\n        *const c_void,\r\n        u32,\r\n        *mut u64,\r\n    ) -> i32,\r\n    update_skeleton_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, i32, *const c_void, u32) -> i32,\r\n    create_pose_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n    update_pose_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const HmdMatrix34, f64) -> i32,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverInput {\r\n    vtable: *const DriverInputVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct PropertiesVTable {\r\n    read_property_batch: extern \"C\" fn(*mut Properties, u64, *mut c_void, u32) -> i32,\r\n    write_property_batch: extern \"C\" fn(*mut Properties, u64, *mut PropertyWrite, u32) -> i32,\r\n    get_prop_error_name_from_enum: extern \"C\" fn(*mut Properties, i32) -> *const c_char,\r\n    tracked_device_to_property_container: extern \"C\" fn(*mut Properties, u32) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct Properties {\r\n    vtable: *const PropertiesVTable,\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct HmdMatrix34 {\r\n    m: [[f32; 4]; 3],\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct TrackedDevicePose {\r\n    m_device_to_absolute_tracking: HmdMatrix34,\r\n    v_velocity: [f32; 3],\r\n    v_angular_velocity: [f32; 3],\r\n    e_tracking_result: i32,\r\n    b_pose_is_valid: bool,\r\n    b_device_is_connected: bool,\r\n}\r\n\r\n#[derive(Clone, Copy, Default)]\r\npub struct RuntimeHmdPose {\r\n    pub position_m: [f32; 3],\r\n    pub rotation: [[f32; 3]; 3],\r\n}\r\n\r\n#[repr(C)]\r\nstruct PropertyWrite {\r\n    prop: u32,\r\n    write_type: i32,\r\n    set_error: i32,\r\n    pv_buffer: *mut c_void,\r\n    un_buffer_size: u32,\r\n    un_tag: u32,\r\n    e_error: i32,\r\n}\r\n\r\nstatic DEVICE_REGISTERED: AtomicBool = AtomicBool::new(false);\r\n\r\nstatic SERVER_HOST_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DRIVER_INPUT_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic PROPERTIES_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DRIVER_LOG_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\n\r\nstatic DEVICE_CONTAINER: AtomicU64 = AtomicU64::new(0);\r\nstatic HAPTIC_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\nstatic SYSTEM_CLICK_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\nstatic A_CLICK_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\nstatic TRIGGER_CLICK_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\nstatic TRIGGER_VALUE_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\nstatic POSE_RAW_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\nstatic POSE_TIP_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\nstatic HAPTIC_EVENT_SEQ: AtomicU64 = AtomicU64::new(0);\r\n\r\npub fn interface_versions_ptr() -> *const *const c_char {\r\n    INTERFACE_VERSIONS.0.as_ptr()\r\n}\r\n\r\nfn get_server_host() -> Option<*mut ServerDriverHost> {\r\n    let ptr = SERVER_HOST_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_driver_input() -> Option<*mut DriverInput> {\r\n    let ptr = DRIVER_INPUT_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_properties() -> Option<*mut Properties> {\r\n    let ptr = PROPERTIES_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_driver_log() -> Option<*mut DriverLog> {\r\n    let ptr = DRIVER_LOG_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\npub(crate) fn log_driver_message(message: &str) {\r\n    let Some(driver_log) = get_driver_log() else {\r\n        return;\r\n    };\r\n\r\n    let Ok(c_message) = CString::new(message) else {\r\n        return;\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*driver_log).vtable).log)(driver_log, c_message.as_ptr());\r\n    }\r\n}\r\n\r\nfn write_string_property(props: *mut Properties, container: u64, prop: u32, value_nul: &'static [u8]) {\r\n    if props.is_null() || value_nul.is_empty() {\r\n        return;\r\n    }\r\n\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: value_nul.as_ptr().cast_mut().cast(),\r\n        un_buffer_size: value_nul.len() as u32,\r\n        un_tag: K_UN_STRING_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn write_bool_property(props: *mut Properties, container: u64, prop: u32, value: bool) {\r\n    if props.is_null() {\r\n        return;\r\n    }\r\n\r\n    let mut bool_value = value;\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: (&mut bool_value as *mut bool).cast(),\r\n        un_buffer_size: core::mem::size_of::<bool>() as u32,\r\n        un_tag: K_UN_BOOL_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn write_int32_property(props: *mut Properties, container: u64, prop: u32, value: i32) {\r\n    if props.is_null() {\r\n        return;\r\n    }\r\n\r\n    let mut int_value = value;\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: (&mut int_value as *mut i32).cast(),\r\n        un_buffer_size: core::mem::size_of::<i32>() as u32,\r\n        un_tag: K_UN_INT32_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn register_tracked_device_if_needed(device_ptr: *mut c_void) {\r\n    if DEVICE_REGISTERED.load(Ordering::SeqCst) {\r\n        return;\r\n    }\r\n\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    let added = unsafe {\r\n        ((*(*host).vtable).tracked_device_added)(\r\n            host,\r\n            DEVICE_SERIAL_CSTR.as_ptr().cast(),\r\n            TRACKED_DEVICE_CLASS_CONTROLLER,\r\n            device_ptr,\r\n        )\r\n    };\r\n\r\n    if added {\r\n        DEVICE_REGISTERED.store(true, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\npub fn initialize(driver_context: *mut c_void, device_ptr: *mut c_void) -> bool {\r\n    if driver_context.is_null() {\r\n        return false;\r\n    }\r\n\r\n    let ctx = driver_context.cast::<DriverContext>();\r\n    let mut err = 0;\r\n\r\n    let get_iface = unsafe { (*(*ctx).vtable).get_generic_interface };\r\n\r\n    let host = get_iface(\r\n        ctx,\r\n        IVR_SERVER_DRIVER_HOST_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let driver_input = get_iface(\r\n        ctx,\r\n        IVR_DRIVER_INPUT_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let properties = get_iface(\r\n        ctx,\r\n        IVR_PROPERTIES_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let driver_log = get_iface(\r\n        ctx,\r\n        IVR_DRIVER_LOG_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    SERVER_HOST_IFACE.store(host, Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(driver_input, Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(properties, Ordering::SeqCst);\r\n    DRIVER_LOG_IFACE.store(driver_log, Ordering::SeqCst);\r\n\r\n    let ok = !host.is_null() && !driver_input.is_null() && !properties.is_null();\r\n    if !ok {\r\n        return false;\r\n    }\r\n\r\n    log_driver_message(&format!(\r\n        \"[big_haptic_driver] OpenVR init ok; sizeof(VREvent)={}, sizeof(VREventHapticVibration)={}\",\r\n        core::mem::size_of::<VREvent>(),\r\n        core::mem::size_of::<VREventHapticVibration>()\r\n    ));\r\n\r\n    register_tracked_device_if_needed(device_ptr);\r\n    true\r\n}\r\n\r\npub fn cleanup() {\r\n    DEVICE_REGISTERED.store(false, Ordering::SeqCst);\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    POSE_RAW_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    POSE_TIP_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    SERVER_HOST_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DRIVER_LOG_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n}\r\n\r\npub fn route_haptic_events() {\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    loop {\r\n        let mut event = VREvent::default();\r\n        let ok = unsafe {\r\n            ((*(*host).vtable).poll_next_event)(\r\n                host,\r\n                &mut event as *mut VREvent,\r\n                core::mem::size_of::<VREvent>() as u32,\r\n            )\r\n        };\r\n\r\n        if !ok {\r\n            break;\r\n        }\r\n\r\n        if event.event_type != VREVENT_INPUT_HAPTIC_VIBRATION {\r\n            continue;\r\n        }\r\n\r\n        let hv = unsafe { event.data.haptic_vibration };\r\n        let seq = HAPTIC_EVENT_SEQ.fetch_add(1, Ordering::SeqCst).saturating_add(1);\r\n\r\n        log_driver_message(&format!(\r\n            \"[big_haptic_driver] haptic event #{}: dev={} age_s={:.6} container={} component={} duration_s={:.6} (0x{:08x}) freq={:.3} amp={:.6} (0x{:08x})\",\r\n            seq,\r\n            event.tracked_device_index,\r\n            event.event_age_seconds,\r\n            hv.container_handle,\r\n            hv.component_handle,\r\n            hv.duration_seconds,\r\n            hv.duration_seconds.to_bits(),\r\n            hv.frequency,\r\n            hv.amplitude,\r\n            hv.amplitude.to_bits()\r\n        ));\r\n\r\n        if hv.amplitude > 0.0 && hv.duration_seconds <= 0.0 {\r\n            log_driver_message(&format!(\r\n                \"[big_haptic_driver] note: OpenVR sent duration<=0; per OpenVR guidance driver will emit one finite pulse (freq={:.3})\",\r\n                hv.frequency\r\n            ));\r\n        }\r\n\r\n        let container = DEVICE_CONTAINER.load(Ordering::SeqCst);\r\n        if container != 0 && hv.container_handle != 0 && hv.container_handle != container {\r\n            log_driver_message(&format!(\r\n                \"[big_haptic_driver] skipped haptic event: container mismatch event={} driver={}\",\r\n                hv.container_handle, container\r\n            ));\r\n            continue;\r\n        }\r\n\r\n        let haptic_component = HAPTIC_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n        if haptic_component != 0 && hv.component_handle != 0 && hv.component_handle != haptic_component {\r\n            log_driver_message(&format!(\r\n                \"[big_haptic_driver] skipped haptic event: component mismatch event={} driver={}\",\r\n                hv.component_handle, haptic_component\r\n            ));\r\n            continue;\r\n        }\r\n\r\n        log_driver_message(\"[big_haptic_driver] forwarding haptic request to HID layer\");\r\n\r\n        haptics::handle_haptic_request(haptics::HapticVibrationRequest {\r\n            amplitude: hv.amplitude,\r\n            duration_seconds: hv.duration_seconds,\r\n            frequency: hv.frequency,\r\n        });\r\n    }\r\n}\r\n\r\npub fn push_pose_update(object_id: u32, pose: *const c_void, pose_size: u32) {\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    if pose.is_null() || pose_size == 0 {\r\n        return;\r\n    }\r\n\r\n    unsafe {\r\n        ((*(*host).vtable).tracked_device_pose_updated)(host, object_id, pose, pose_size);\r\n    }\r\n}\r\n\r\npub fn sample_hmd_pose() -> Option<RuntimeHmdPose> {\r\n    let host = get_server_host()?;\r\n\r\n    let mut poses = [TrackedDevicePose::default(); 1];\r\n    unsafe {\r\n        ((*(*host).vtable).get_raw_tracked_device_poses)(\r\n            host,\r\n            0.0,\r\n            poses.as_mut_ptr(),\r\n            poses.len() as u32,\r\n        );\r\n    }\r\n\r\n    let hmd = poses[TRACKED_DEVICE_INDEX_HMD as usize];\r\n    if !hmd.b_device_is_connected || !hmd.b_pose_is_valid {\r\n        return None;\r\n    }\r\n\r\n    Some(RuntimeHmdPose {\r\n        position_m: [\r\n            hmd.m_device_to_absolute_tracking.m[0][3],\r\n            hmd.m_device_to_absolute_tracking.m[1][3],\r\n            hmd.m_device_to_absolute_tracking.m[2][3],\r\n        ],\r\n        rotation: [\r\n            [\r\n                hmd.m_device_to_absolute_tracking.m[0][0],\r\n                hmd.m_device_to_absolute_tracking.m[0][1],\r\n                hmd.m_device_to_absolute_tracking.m[0][2],\r\n            ],\r\n            [\r\n                hmd.m_device_to_absolute_tracking.m[1][0],\r\n                hmd.m_device_to_absolute_tracking.m[1][1],\r\n                hmd.m_device_to_absolute_tracking.m[1][2],\r\n            ],\r\n            [\r\n                hmd.m_device_to_absolute_tracking.m[2][0],\r\n                hmd.m_device_to_absolute_tracking.m[2][1],\r\n                hmd.m_device_to_absolute_tracking.m[2][2],\r\n            ],\r\n        ],\r\n    })\r\n}\r\n\r\npub fn update_pose_components() {\r\n    let Some(input) = get_driver_input() else {\r\n        return;\r\n    };\r\n\r\n    let raw_handle = POSE_RAW_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n    let tip_handle = POSE_TIP_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n\r\n    let raw_offset = HmdMatrix34 {\r\n        m: [\r\n            [1.0, 0.0, 0.0, 0.0],\r\n            [0.0, 1.0, 0.0, 0.0],\r\n            [0.0, 0.0, 1.0, 0.0],\r\n        ],\r\n    };\r\n    let tip_offset = HmdMatrix34 {\r\n        m: [\r\n            [1.0, 0.0, 0.0, 0.0],\r\n            [0.0, 1.0, 0.0, 0.0],\r\n            [0.0, 0.0, 1.0, -0.04],\r\n        ],\r\n    };\r\n\r\n    if raw_handle != 0 {\r\n        unsafe {\r\n            ((*(*input).vtable).update_pose_component)(input, raw_handle, &raw_offset, 0.0);\r\n        }\r\n    }\r\n\r\n    if tip_handle != 0 {\r\n        unsafe {\r\n            ((*(*input).vtable).update_pose_component)(input, tip_handle, &tip_offset, 0.0);\r\n        }\r\n    }\r\n}\r\n\r\npub fn activate_device(object_id: u32) -> bool {\r\n    let Some(props) = get_properties() else {\r\n        return false;\r\n    };\r\n\r\n    let container = unsafe { ((*(*props).vtable).tracked_device_to_property_container)(props, object_id) };\r\n    DEVICE_CONTAINER.store(container, Ordering::SeqCst);\r\n\r\n    write_string_property(props, container, PROP_MODEL_NUMBER_STRING, DEVICE_MODEL_CSTR);\r\n    write_string_property(props, container, PROP_SERIAL_NUMBER_STRING, DEVICE_SERIAL_CSTR);\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_TRACKING_SYSTEM_NAME_STRING,\r\n        DEVICE_TRACKING_SYSTEM_NAME_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_REGISTERED_DEVICE_TYPE_STRING,\r\n        DEVICE_REGISTERED_TYPE_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_INPUT_PROFILE_PATH_STRING,\r\n        DEVICE_INPUT_PROFILE_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_CONTROLLER_TYPE_STRING,\r\n        DEVICE_CONTROLLER_TYPE_CSTR,\r\n    );\r\n    write_bool_property(props, container, PROP_HAS_CONTROLLER_COMPONENT_BOOL, true);\r\n    write_int32_property(props, container, PROP_CONTROLLER_ROLE_HINT_INT32, 2);\r\n\r\n    if let Some(input) = get_driver_input() {\r\n        let mut handle = 0_u64;\r\n        let rc = unsafe {\r\n            ((*(*input).vtable).create_haptic_component)(\r\n                input,\r\n                container,\r\n                DEVICE_HAPTIC_PATH_CSTR.as_ptr().cast(),\r\n                &mut handle as *mut u64,\r\n            )\r\n        };\r\n\r\n        if rc == 0 {\r\n            HAPTIC_COMPONENT_HANDLE.store(handle, Ordering::SeqCst);\r\n        }\r\n\r\n        let mut pose_raw_handle = 0_u64;\r\n        let rc_raw = unsafe {\r\n            ((*(*input).vtable).create_pose_component)(\r\n                input,\r\n                container,\r\n                DEVICE_POSE_RAW_PATH_CSTR.as_ptr().cast(),\r\n                &mut pose_raw_handle as *mut u64,\r\n            )\r\n        };\r\n        if rc_raw == 0 {\r\n            POSE_RAW_COMPONENT_HANDLE.store(pose_raw_handle, Ordering::SeqCst);\r\n        }\r\n\r\n        let mut pose_tip_handle = 0_u64;\r\n        let rc_tip = unsafe {\r\n            ((*(*input).vtable).create_pose_component)(\r\n                input,\r\n                container,\r\n                DEVICE_POSE_TIP_PATH_CSTR.as_ptr().cast(),\r\n                &mut pose_tip_handle as *mut u64,\r\n            )\r\n        };\r\n        if rc_tip == 0 {\r\n            POSE_TIP_COMPONENT_HANDLE.store(pose_tip_handle, Ordering::SeqCst);\r\n        }\r\n\r\n        update_pose_components();\r\n    }\r\n\r\n    true\r\n}\r\n\r\npub fn deactivate_device() {\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    POSE_RAW_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    POSE_TIP_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":729,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\openvr_runtime.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","scheme":"file"},"requestId":"request_6f13d5f3-b3de-4f61-a09f-3c6ee71f5876","epoch":1694,"edits":[{"text":"//! OpenVR runtime integration helpers.\r\n//!\r\n//! This module owns driver-context interface resolution, tracked-device\r\n//! registration, component/property setup, and OpenVR haptic event routing.\r\n\r\nuse core::ffi::c_void;\r\nuse core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU64, Ordering};\r\nuse std::ffi::CString;\r\nuse std::os::raw::c_char;\r\n\r\nuse crate::haptics;\r\n\r\ntype EvRInitError = i32;\r\n\r\nconst IVR_SERVER_DRIVER_HOST_VERSION: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\nconst IVR_DRIVER_INPUT_VERSION: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_PROPERTIES_VERSION: &[u8] = b\"IVRProperties_001\\0\";\r\nconst IVR_DRIVER_LOG_VERSION: &[u8] = b\"IVRDriverLog_001\\0\";\r\n\r\nconst TRACKED_DEVICE_CLASS_CONTROLLER: i32 = 2;\r\nconst VREVENT_INPUT_HAPTIC_VIBRATION: u32 = 1700;\r\n\r\nconst PROP_MODEL_NUMBER_STRING: u32 = 1001;\r\nconst PROP_SERIAL_NUMBER_STRING: u32 = 1002;\r\nconst PROP_TRACKING_SYSTEM_NAME_STRING: u32 = 1000;\r\nconst PROP_REGISTERED_DEVICE_TYPE_STRING: u32 = 1036;\r\nconst PROP_INPUT_PROFILE_PATH_STRING: u32 = 1037;\r\nconst PROP_CONTROLLER_TYPE_STRING: u32 = 7000;\r\nconst PROP_CONTROLLER_ROLE_HINT_INT32: u32 = 3007;\r\nconst PROP_HAS_CONTROLLER_COMPONENT_BOOL: u32 = 6003;\r\nconst TRACKED_DEVICE_INDEX_HMD: u32 = 0;\r\n\r\nconst K_UN_BOOL_PROPERTY_TAG: u32 = 4;\r\nconst K_UN_INT32_PROPERTY_TAG: u32 = 2;\r\nconst K_UN_STRING_PROPERTY_TAG: u32 = 5;\r\nconst PROPERTY_WRITE_SET: i32 = 0;\r\n\r\nconst DEVICE_SERIAL_CSTR: &[u8] = b\"qtpy-samd21-haptic-001\\0\";\r\nconst DEVICE_MODEL_CSTR: &[u8] = b\"QT Py SAMD21 Haptic\\0\";\r\nconst DEVICE_TRACKING_SYSTEM_NAME_CSTR: &[u8] = b\"big_haptic_driver\\0\";\r\nconst DEVICE_REGISTERED_TYPE_CSTR: &[u8] = b\"big_haptic_driver/controller\\0\";\r\nconst DEVICE_CONTROLLER_TYPE_CSTR: &[u8] = b\"big_haptic_driver\\0\";\r\nconst DEVICE_INPUT_PROFILE_CSTR: &[u8] = b\"{big_haptic_driver}/input/big_haptic_profile.json\\0\";\r\nconst DEVICE_HAPTIC_PATH_CSTR: &[u8] = b\"/output/haptic\\0\";\r\nconst DEVICE_POSE_RAW_PATH_CSTR: &[u8] = b\"/pose/raw\\0\";\r\nconst DEVICE_POSE_TIP_PATH_CSTR: &[u8] = b\"/pose/tip\\0\";\r\nconst DEVICE_SYSTEM_CLICK_PATH_CSTR: &[u8] = b\"/input/system/click\\0\";\r\nconst DEVICE_A_CLICK_PATH_CSTR: &[u8] = b\"/input/a/click\\0\";\r\nconst DEVICE_TRIGGER_CLICK_PATH_CSTR: &[u8] = b\"/input/trigger/click\\0\";\r\nconst DEVICE_TRIGGER_VALUE_PATH_CSTR: &[u8] = b\"/input/trigger/value\\0\";\r\n\r\nconst VR_SCALAR_TYPE_ABSOLUTE: i32 = 0;\r\nconst VR_SCALAR_UNITS_NORMALIZED_ONE_SIDED: i32 = 0;\r\n\r\nconst PROVIDER_IFACE_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_004\\0\";\r\nconst PROVIDER_IFACE_ALT_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_005\\0\";\r\nconst DEVICE_IFACE_CSTR: &[u8] = b\"ITrackedDeviceServerDriver_005\\0\";\r\n\r\nconst IVR_PROPERTIES_CSTR: &[u8] = b\"IVRProperties_001\\0\";\r\nconst IVR_DRIVER_INPUT_CSTR: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_SERVER_DRIVER_HOST_CSTR: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\nconst IVR_DRIVER_LOG_CSTR: &[u8] = b\"IVRDriverLog_001\\0\";\r\n\r\nconst IVR_SETTINGS_CSTR: &[u8] = b\"IVRSettings_003\\0\";\r\nconst IVR_DRIVER_MANAGER_CSTR: &[u8] = b\"IVRDriverManager_001\\0\";\r\nconst IVR_RESOURCES_CSTR: &[u8] = b\"IVRResources_001\\0\";\r\n\r\n#[repr(C)]\r\nstruct InterfaceVersions([*const c_char; 11]);\r\nunsafe impl Sync for InterfaceVersions {}\r\n\r\nstatic INTERFACE_VERSIONS: InterfaceVersions = InterfaceVersions([\r\n    IVR_SETTINGS_CSTR.as_ptr().cast(),\r\n    DEVICE_IFACE_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_CSTR.as_ptr().cast(),\r\n    IVR_PROPERTIES_CSTR.as_ptr().cast(),\r\n    IVR_SERVER_DRIVER_HOST_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_LOG_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_MANAGER_CSTR.as_ptr().cast(),\r\n    IVR_RESOURCES_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_INPUT_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_ALT_CSTR.as_ptr().cast(),\r\n    core::ptr::null(),\r\n]);\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREventHapticVibration {\r\n    container_handle: u64,\r\n    component_handle: u64,\r\n    duration_seconds: f32,\r\n    frequency: f32,\r\n    amplitude: f32,\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nunion VREventData {\r\n    haptic_vibration: VREventHapticVibration,\r\n    reserved: [u8; 48],\r\n}\r\n\r\nimpl Default for VREventData {\r\n    fn default() -> Self {\r\n        Self { reserved: [0; 48] }\r\n    }\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREvent {\r\n    event_type: u32,\r\n    tracked_device_index: u32,\r\n    event_age_seconds: f32,\r\n    data: VREventData,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContextVTable {\r\n    get_generic_interface:\r\n        extern \"C\" fn(*mut DriverContext, *const c_char, *mut EvRInitError) -> *mut c_void,\r\n    get_driver_handle: extern \"C\" fn(*mut DriverContext) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContext {\r\n    vtable: *const DriverContextVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHostVTable {\r\n    tracked_device_added:\r\n        extern \"C\" fn(*mut ServerDriverHost, *const c_char, i32, *mut c_void) -> bool,\r\n    tracked_device_pose_updated: extern \"C\" fn(*mut ServerDriverHost, u32, *const c_void, u32),\r\n    vsync_event: extern \"C\" fn(*mut ServerDriverHost, f64),\r\n    vendor_specific_event: extern \"C\" fn(*mut ServerDriverHost, u32, u32, *const VREventData, f64),\r\n    is_exiting: extern \"C\" fn(*mut ServerDriverHost) -> bool,\r\n    poll_next_event: extern \"C\" fn(*mut ServerDriverHost, *mut VREvent, u32) -> bool,\r\n    get_raw_tracked_device_poses:\r\n        extern \"C\" fn(*mut ServerDriverHost, f32, *mut TrackedDevicePose, u32),\r\n    request_restart:\r\n        extern \"C\" fn(*mut ServerDriverHost, *const c_char, *const c_char, *const c_char, *const c_char),\r\n    get_frame_timings: extern \"C\" fn(*mut ServerDriverHost, *mut c_void, u32) -> u32,\r\n    set_display_eye_to_head:\r\n        extern \"C\" fn(*mut ServerDriverHost, u32, *const c_void, *const c_void),\r\n    set_display_projection_raw:\r\n        extern \"C\" fn(*mut ServerDriverHost, u32, *const c_void, *const c_void),\r\n    set_recommended_render_target_size: extern \"C\" fn(*mut ServerDriverHost, u32, u32, u32),\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHost {\r\n    vtable: *const ServerDriverHostVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverLogVTable {\r\n    log: extern \"C\" fn(*mut DriverLog, *const c_char),\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverLog {\r\n    vtable: *const DriverLogVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverInputVTable {\r\n    create_boolean_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n    update_boolean_component: extern \"C\" fn(*mut DriverInput, u64, bool, f64) -> i32,\r\n    create_scalar_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64, i32, i32) -> i32,\r\n    update_scalar_component: extern \"C\" fn(*mut DriverInput, u64, f32, f64) -> i32,\r\n    create_haptic_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n    create_skeleton_component: extern \"C\" fn(\r\n        *mut DriverInput,\r\n        u64,\r\n        *const c_char,\r\n        *const c_char,\r\n        *const c_char,\r\n        i32,\r\n        *const c_void,\r\n        u32,\r\n        *mut u64,\r\n    ) -> i32,\r\n    update_skeleton_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, i32, *const c_void, u32) -> i32,\r\n    create_pose_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n    update_pose_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const HmdMatrix34, f64) -> i32,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverInput {\r\n    vtable: *const DriverInputVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct PropertiesVTable {\r\n    read_property_batch: extern \"C\" fn(*mut Properties, u64, *mut c_void, u32) -> i32,\r\n    write_property_batch: extern \"C\" fn(*mut Properties, u64, *mut PropertyWrite, u32) -> i32,\r\n    get_prop_error_name_from_enum: extern \"C\" fn(*mut Properties, i32) -> *const c_char,\r\n    tracked_device_to_property_container: extern \"C\" fn(*mut Properties, u32) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct Properties {\r\n    vtable: *const PropertiesVTable,\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct HmdMatrix34 {\r\n    m: [[f32; 4]; 3],\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct TrackedDevicePose {\r\n    m_device_to_absolute_tracking: HmdMatrix34,\r\n    v_velocity: [f32; 3],\r\n    v_angular_velocity: [f32; 3],\r\n    e_tracking_result: i32,\r\n    b_pose_is_valid: bool,\r\n    b_device_is_connected: bool,\r\n}\r\n\r\n#[derive(Clone, Copy, Default)]\r\npub struct RuntimeHmdPose {\r\n    pub position_m: [f32; 3],\r\n    pub rotation: [[f32; 3]; 3],\r\n}\r\n\r\n#[repr(C)]\r\nstruct PropertyWrite {\r\n    prop: u32,\r\n    write_type: i32,\r\n    set_error: i32,\r\n    pv_buffer: *mut c_void,\r\n    un_buffer_size: u32,\r\n    un_tag: u32,\r\n    e_error: i32,\r\n}\r\n\r\nstatic DEVICE_REGISTERED: AtomicBool = AtomicBool::new(false);\r\n\r\nstatic SERVER_HOST_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DRIVER_INPUT_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic PROPERTIES_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DRIVER_LOG_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\n\r\nstatic DEVICE_CONTAINER: AtomicU64 = AtomicU64::new(0);\r\nstatic HAPTIC_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\nstatic SYSTEM_CLICK_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\nstatic A_CLICK_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\nstatic TRIGGER_CLICK_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\nstatic TRIGGER_VALUE_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\nstatic POSE_RAW_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\nstatic POSE_TIP_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\nstatic HAPTIC_EVENT_SEQ: AtomicU64 = AtomicU64::new(0);\r\n\r\npub fn interface_versions_ptr() -> *const *const c_char {\r\n    INTERFACE_VERSIONS.0.as_ptr()\r\n}\r\n\r\nfn get_server_host() -> Option<*mut ServerDriverHost> {\r\n    let ptr = SERVER_HOST_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_driver_input() -> Option<*mut DriverInput> {\r\n    let ptr = DRIVER_INPUT_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_properties() -> Option<*mut Properties> {\r\n    let ptr = PROPERTIES_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_driver_log() -> Option<*mut DriverLog> {\r\n    let ptr = DRIVER_LOG_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\npub(crate) fn log_driver_message(message: &str) {\r\n    let Some(driver_log) = get_driver_log() else {\r\n        return;\r\n    };\r\n\r\n    let Ok(c_message) = CString::new(message) else {\r\n        return;\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*driver_log).vtable).log)(driver_log, c_message.as_ptr());\r\n    }\r\n}\r\n\r\nfn write_string_property(props: *mut Properties, container: u64, prop: u32, value_nul: &'static [u8]) {\r\n    if props.is_null() || value_nul.is_empty() {\r\n        return;\r\n    }\r\n\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: value_nul.as_ptr().cast_mut().cast(),\r\n        un_buffer_size: value_nul.len() as u32,\r\n        un_tag: K_UN_STRING_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn write_bool_property(props: *mut Properties, container: u64, prop: u32, value: bool) {\r\n    if props.is_null() {\r\n        return;\r\n    }\r\n\r\n    let mut bool_value = value;\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: (&mut bool_value as *mut bool).cast(),\r\n        un_buffer_size: core::mem::size_of::<bool>() as u32,\r\n        un_tag: K_UN_BOOL_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn write_int32_property(props: *mut Properties, container: u64, prop: u32, value: i32) {\r\n    if props.is_null() {\r\n        return;\r\n    }\r\n\r\n    let mut int_value = value;\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: (&mut int_value as *mut i32).cast(),\r\n        un_buffer_size: core::mem::size_of::<i32>() as u32,\r\n        un_tag: K_UN_INT32_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn register_tracked_device_if_needed(device_ptr: *mut c_void) {\r\n    if DEVICE_REGISTERED.load(Ordering::SeqCst) {\r\n        return;\r\n    }\r\n\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    let added = unsafe {\r\n        ((*(*host).vtable).tracked_device_added)(\r\n            host,\r\n            DEVICE_SERIAL_CSTR.as_ptr().cast(),\r\n            TRACKED_DEVICE_CLASS_CONTROLLER,\r\n            device_ptr,\r\n        )\r\n    };\r\n\r\n    if added {\r\n        DEVICE_REGISTERED.store(true, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\npub fn initialize(driver_context: *mut c_void, device_ptr: *mut c_void) -> bool {\r\n    if driver_context.is_null() {\r\n        return false;\r\n    }\r\n\r\n    let ctx = driver_context.cast::<DriverContext>();\r\n    let mut err = 0;\r\n\r\n    let get_iface = unsafe { (*(*ctx).vtable).get_generic_interface };\r\n\r\n    let host = get_iface(\r\n        ctx,\r\n        IVR_SERVER_DRIVER_HOST_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let driver_input = get_iface(\r\n        ctx,\r\n        IVR_DRIVER_INPUT_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let properties = get_iface(\r\n        ctx,\r\n        IVR_PROPERTIES_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let driver_log = get_iface(\r\n        ctx,\r\n        IVR_DRIVER_LOG_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    SERVER_HOST_IFACE.store(host, Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(driver_input, Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(properties, Ordering::SeqCst);\r\n    DRIVER_LOG_IFACE.store(driver_log, Ordering::SeqCst);\r\n\r\n    let ok = !host.is_null() && !driver_input.is_null() && !properties.is_null();\r\n    if !ok {\r\n        return false;\r\n    }\r\n\r\n    log_driver_message(&format!(\r\n        \"[big_haptic_driver] OpenVR init ok; sizeof(VREvent)={}, sizeof(VREventHapticVibration)={}\",\r\n        core::mem::size_of::<VREvent>(),\r\n        core::mem::size_of::<VREventHapticVibration>()\r\n    ));\r\n\r\n    register_tracked_device_if_needed(device_ptr);\r\n    true\r\n}\r\n\r\npub fn cleanup() {\r\n    DEVICE_REGISTERED.store(false, Ordering::SeqCst);\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    SYSTEM_CLICK_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\n    A_CLICK_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\n    TRIGGER_CLICK_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\n    TRIGGER_VALUE_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\n    POSE_RAW_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    POSE_TIP_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    SERVER_HOST_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DRIVER_LOG_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n}\r\n\r\npub fn route_haptic_events() {\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    loop {\r\n        let mut event = VREvent::default();\r\n        let ok = unsafe {\r\n            ((*(*host).vtable).poll_next_event)(\r\n                host,\r\n                &mut event as *mut VREvent,\r\n                core::mem::size_of::<VREvent>() as u32,\r\n            )\r\n        };\r\n\r\n        if !ok {\r\n            break;\r\n        }\r\n\r\n        if event.event_type != VREVENT_INPUT_HAPTIC_VIBRATION {\r\n            continue;\r\n        }\r\n\r\n        let hv = unsafe { event.data.haptic_vibration };\r\n        let seq = HAPTIC_EVENT_SEQ.fetch_add(1, Ordering::SeqCst).saturating_add(1);\r\n\r\n        log_driver_message(&format!(\r\n            \"[big_haptic_driver] haptic event #{}: dev={} age_s={:.6} container={} component={} duration_s={:.6} (0x{:08x}) freq={:.3} amp={:.6} (0x{:08x})\",\r\n            seq,\r\n            event.tracked_device_index,\r\n            event.event_age_seconds,\r\n            hv.container_handle,\r\n            hv.component_handle,\r\n            hv.duration_seconds,\r\n            hv.duration_seconds.to_bits(),\r\n            hv.frequency,\r\n            hv.amplitude,\r\n            hv.amplitude.to_bits()\r\n        ));\r\n\r\n        if hv.amplitude > 0.0 && hv.duration_seconds <= 0.0 {\r\n            log_driver_message(&format!(\r\n                \"[big_haptic_driver] note: OpenVR sent duration<=0; per OpenVR guidance driver will emit one finite pulse (freq={:.3})\",\r\n                hv.frequency\r\n            ));\r\n        }\r\n\r\n        let container = DEVICE_CONTAINER.load(Ordering::SeqCst);\r\n        if container != 0 && hv.container_handle != 0 && hv.container_handle != container {\r\n            log_driver_message(&format!(\r\n                \"[big_haptic_driver] skipped haptic event: container mismatch event={} driver={}\",\r\n                hv.container_handle, container\r\n            ));\r\n            continue;\r\n        }\r\n\r\n        let haptic_component = HAPTIC_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n        if haptic_component != 0 && hv.component_handle != 0 && hv.component_handle != haptic_component {\r\n            log_driver_message(&format!(\r\n                \"[big_haptic_driver] skipped haptic event: component mismatch event={} driver={}\",\r\n                hv.component_handle, haptic_component\r\n            ));\r\n            continue;\r\n        }\r\n\r\n        log_driver_message(\"[big_haptic_driver] forwarding haptic request to HID layer\");\r\n\r\n        haptics::handle_haptic_request(haptics::HapticVibrationRequest {\r\n            amplitude: hv.amplitude,\r\n            duration_seconds: hv.duration_seconds,\r\n            frequency: hv.frequency,\r\n        });\r\n    }\r\n}\r\n\r\npub fn push_pose_update(object_id: u32, pose: *const c_void, pose_size: u32) {\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    if pose.is_null() || pose_size == 0 {\r\n        return;\r\n    }\r\n\r\n    unsafe {\r\n        ((*(*host).vtable).tracked_device_pose_updated)(host, object_id, pose, pose_size);\r\n    }\r\n}\r\n\r\npub fn sample_hmd_pose() -> Option<RuntimeHmdPose> {\r\n    let host = get_server_host()?;\r\n\r\n    let mut poses = [TrackedDevicePose::default(); 1];\r\n    unsafe {\r\n        ((*(*host).vtable).get_raw_tracked_device_poses)(\r\n            host,\r\n            0.0,\r\n            poses.as_mut_ptr(),\r\n            poses.len() as u32,\r\n        );\r\n    }\r\n\r\n    let hmd = poses[TRACKED_DEVICE_INDEX_HMD as usize];\r\n    if !hmd.b_device_is_connected || !hmd.b_pose_is_valid {\r\n        return None;\r\n    }\r\n\r\n    Some(RuntimeHmdPose {\r\n        position_m: [\r\n            hmd.m_device_to_absolute_tracking.m[0][3],\r\n            hmd.m_device_to_absolute_tracking.m[1][3],\r\n            hmd.m_device_to_absolute_tracking.m[2][3],\r\n        ],\r\n        rotation: [\r\n            [\r\n                hmd.m_device_to_absolute_tracking.m[0][0],\r\n                hmd.m_device_to_absolute_tracking.m[0][1],\r\n                hmd.m_device_to_absolute_tracking.m[0][2],\r\n            ],\r\n            [\r\n                hmd.m_device_to_absolute_tracking.m[1][0],\r\n                hmd.m_device_to_absolute_tracking.m[1][1],\r\n                hmd.m_device_to_absolute_tracking.m[1][2],\r\n            ],\r\n            [\r\n                hmd.m_device_to_absolute_tracking.m[2][0],\r\n                hmd.m_device_to_absolute_tracking.m[2][1],\r\n                hmd.m_device_to_absolute_tracking.m[2][2],\r\n            ],\r\n        ],\r\n    })\r\n}\r\n\r\npub fn update_pose_components() {\r\n    let Some(input) = get_driver_input() else {\r\n        return;\r\n    };\r\n\r\n    let raw_handle = POSE_RAW_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n    let tip_handle = POSE_TIP_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n\r\n    let raw_offset = HmdMatrix34 {\r\n        m: [\r\n            [1.0, 0.0, 0.0, 0.0],\r\n            [0.0, 1.0, 0.0, 0.0],\r\n            [0.0, 0.0, 1.0, 0.0],\r\n        ],\r\n    };\r\n    let tip_offset = HmdMatrix34 {\r\n        m: [\r\n            [1.0, 0.0, 0.0, 0.0],\r\n            [0.0, 1.0, 0.0, 0.0],\r\n            [0.0, 0.0, 1.0, -0.04],\r\n        ],\r\n    };\r\n\r\n    if raw_handle != 0 {\r\n        unsafe {\r\n            ((*(*input).vtable).update_pose_component)(input, raw_handle, &raw_offset, 0.0);\r\n        }\r\n    }\r\n\r\n    if tip_handle != 0 {\r\n        unsafe {\r\n            ((*(*input).vtable).update_pose_component)(input, tip_handle, &tip_offset, 0.0);\r\n        }\r\n    }\r\n}\r\n\r\npub fn activate_device(object_id: u32) -> bool {\r\n    let Some(props) = get_properties() else {\r\n        return false;\r\n    };\r\n\r\n    let container = unsafe { ((*(*props).vtable).tracked_device_to_property_container)(props, object_id) };\r\n    DEVICE_CONTAINER.store(container, Ordering::SeqCst);\r\n\r\n    write_string_property(props, container, PROP_MODEL_NUMBER_STRING, DEVICE_MODEL_CSTR);\r\n    write_string_property(props, container, PROP_SERIAL_NUMBER_STRING, DEVICE_SERIAL_CSTR);\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_TRACKING_SYSTEM_NAME_STRING,\r\n        DEVICE_TRACKING_SYSTEM_NAME_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_REGISTERED_DEVICE_TYPE_STRING,\r\n        DEVICE_REGISTERED_TYPE_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_INPUT_PROFILE_PATH_STRING,\r\n        DEVICE_INPUT_PROFILE_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_CONTROLLER_TYPE_STRING,\r\n        DEVICE_CONTROLLER_TYPE_CSTR,\r\n    );\r\n    write_bool_property(props, container, PROP_HAS_CONTROLLER_COMPONENT_BOOL, true);\r\n    write_int32_property(props, container, PROP_CONTROLLER_ROLE_HINT_INT32, 2);\r\n\r\n    if let Some(input) = get_driver_input() {\r\n        let mut handle = 0_u64;\r\n        let rc = unsafe {\r\n            ((*(*input).vtable).create_haptic_component)(\r\n                input,\r\n                container,\r\n                DEVICE_HAPTIC_PATH_CSTR.as_ptr().cast(),\r\n                &mut handle as *mut u64,\r\n            )\r\n        };\r\n\r\n        if rc == 0 {\r\n            HAPTIC_COMPONENT_HANDLE.store(handle, Ordering::SeqCst);\r\n        }\r\n\r\n        let mut pose_raw_handle = 0_u64;\r\n        let rc_raw = unsafe {\r\n            ((*(*input).vtable).create_pose_component)(\r\n                input,\r\n                container,\r\n                DEVICE_POSE_RAW_PATH_CSTR.as_ptr().cast(),\r\n                &mut pose_raw_handle as *mut u64,\r\n            )\r\n        };\r\n        if rc_raw == 0 {\r\n            POSE_RAW_COMPONENT_HANDLE.store(pose_raw_handle, Ordering::SeqCst);\r\n        }\r\n\r\n        let mut pose_tip_handle = 0_u64;\r\n        let rc_tip = unsafe {\r\n            ((*(*input).vtable).create_pose_component)(\r\n                input,\r\n                container,\r\n                DEVICE_POSE_TIP_PATH_CSTR.as_ptr().cast(),\r\n                &mut pose_tip_handle as *mut u64,\r\n            )\r\n        };\r\n        if rc_tip == 0 {\r\n            POSE_TIP_COMPONENT_HANDLE.store(pose_tip_handle, Ordering::SeqCst);\r\n        }\r\n\r\n        update_pose_components();\r\n    }\r\n\r\n    true\r\n}\r\n\r\npub fn deactivate_device() {\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    POSE_RAW_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    POSE_TIP_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":733,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\openvr_runtime.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","scheme":"file"},"requestId":"request_6f13d5f3-b3de-4f61-a09f-3c6ee71f5876","epoch":1696,"edits":[{"text":"//! OpenVR runtime integration helpers.\r\n//!\r\n//! This module owns driver-context interface resolution, tracked-device\r\n//! registration, component/property setup, and OpenVR haptic event routing.\r\n\r\nuse core::ffi::c_void;\r\nuse core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU64, Ordering};\r\nuse std::ffi::CString;\r\nuse std::os::raw::c_char;\r\n\r\nuse crate::haptics;\r\n\r\ntype EvRInitError = i32;\r\n\r\nconst IVR_SERVER_DRIVER_HOST_VERSION: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\nconst IVR_DRIVER_INPUT_VERSION: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_PROPERTIES_VERSION: &[u8] = b\"IVRProperties_001\\0\";\r\nconst IVR_DRIVER_LOG_VERSION: &[u8] = b\"IVRDriverLog_001\\0\";\r\n\r\nconst TRACKED_DEVICE_CLASS_CONTROLLER: i32 = 2;\r\nconst VREVENT_INPUT_HAPTIC_VIBRATION: u32 = 1700;\r\n\r\nconst PROP_MODEL_NUMBER_STRING: u32 = 1001;\r\nconst PROP_SERIAL_NUMBER_STRING: u32 = 1002;\r\nconst PROP_TRACKING_SYSTEM_NAME_STRING: u32 = 1000;\r\nconst PROP_REGISTERED_DEVICE_TYPE_STRING: u32 = 1036;\r\nconst PROP_INPUT_PROFILE_PATH_STRING: u32 = 1037;\r\nconst PROP_CONTROLLER_TYPE_STRING: u32 = 7000;\r\nconst PROP_CONTROLLER_ROLE_HINT_INT32: u32 = 3007;\r\nconst PROP_HAS_CONTROLLER_COMPONENT_BOOL: u32 = 6003;\r\nconst TRACKED_DEVICE_INDEX_HMD: u32 = 0;\r\n\r\nconst K_UN_BOOL_PROPERTY_TAG: u32 = 4;\r\nconst K_UN_INT32_PROPERTY_TAG: u32 = 2;\r\nconst K_UN_STRING_PROPERTY_TAG: u32 = 5;\r\nconst PROPERTY_WRITE_SET: i32 = 0;\r\n\r\nconst DEVICE_SERIAL_CSTR: &[u8] = b\"qtpy-samd21-haptic-001\\0\";\r\nconst DEVICE_MODEL_CSTR: &[u8] = b\"QT Py SAMD21 Haptic\\0\";\r\nconst DEVICE_TRACKING_SYSTEM_NAME_CSTR: &[u8] = b\"big_haptic_driver\\0\";\r\nconst DEVICE_REGISTERED_TYPE_CSTR: &[u8] = b\"big_haptic_driver/controller\\0\";\r\nconst DEVICE_CONTROLLER_TYPE_CSTR: &[u8] = b\"big_haptic_driver\\0\";\r\nconst DEVICE_INPUT_PROFILE_CSTR: &[u8] = b\"{big_haptic_driver}/input/big_haptic_profile.json\\0\";\r\nconst DEVICE_HAPTIC_PATH_CSTR: &[u8] = b\"/output/haptic\\0\";\r\nconst DEVICE_POSE_RAW_PATH_CSTR: &[u8] = b\"/pose/raw\\0\";\r\nconst DEVICE_POSE_TIP_PATH_CSTR: &[u8] = b\"/pose/tip\\0\";\r\nconst DEVICE_SYSTEM_CLICK_PATH_CSTR: &[u8] = b\"/input/system/click\\0\";\r\nconst DEVICE_A_CLICK_PATH_CSTR: &[u8] = b\"/input/a/click\\0\";\r\nconst DEVICE_TRIGGER_CLICK_PATH_CSTR: &[u8] = b\"/input/trigger/click\\0\";\r\nconst DEVICE_TRIGGER_VALUE_PATH_CSTR: &[u8] = b\"/input/trigger/value\\0\";\r\n\r\nconst VR_SCALAR_TYPE_ABSOLUTE: i32 = 0;\r\nconst VR_SCALAR_UNITS_NORMALIZED_ONE_SIDED: i32 = 0;\r\n\r\nconst PROVIDER_IFACE_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_004\\0\";\r\nconst PROVIDER_IFACE_ALT_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_005\\0\";\r\nconst DEVICE_IFACE_CSTR: &[u8] = b\"ITrackedDeviceServerDriver_005\\0\";\r\n\r\nconst IVR_PROPERTIES_CSTR: &[u8] = b\"IVRProperties_001\\0\";\r\nconst IVR_DRIVER_INPUT_CSTR: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_SERVER_DRIVER_HOST_CSTR: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\nconst IVR_DRIVER_LOG_CSTR: &[u8] = b\"IVRDriverLog_001\\0\";\r\n\r\nconst IVR_SETTINGS_CSTR: &[u8] = b\"IVRSettings_003\\0\";\r\nconst IVR_DRIVER_MANAGER_CSTR: &[u8] = b\"IVRDriverManager_001\\0\";\r\nconst IVR_RESOURCES_CSTR: &[u8] = b\"IVRResources_001\\0\";\r\n\r\n#[repr(C)]\r\nstruct InterfaceVersions([*const c_char; 11]);\r\nunsafe impl Sync for InterfaceVersions {}\r\n\r\nstatic INTERFACE_VERSIONS: InterfaceVersions = InterfaceVersions([\r\n    IVR_SETTINGS_CSTR.as_ptr().cast(),\r\n    DEVICE_IFACE_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_CSTR.as_ptr().cast(),\r\n    IVR_PROPERTIES_CSTR.as_ptr().cast(),\r\n    IVR_SERVER_DRIVER_HOST_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_LOG_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_MANAGER_CSTR.as_ptr().cast(),\r\n    IVR_RESOURCES_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_INPUT_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_ALT_CSTR.as_ptr().cast(),\r\n    core::ptr::null(),\r\n]);\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREventHapticVibration {\r\n    container_handle: u64,\r\n    component_handle: u64,\r\n    duration_seconds: f32,\r\n    frequency: f32,\r\n    amplitude: f32,\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nunion VREventData {\r\n    haptic_vibration: VREventHapticVibration,\r\n    reserved: [u8; 48],\r\n}\r\n\r\nimpl Default for VREventData {\r\n    fn default() -> Self {\r\n        Self { reserved: [0; 48] }\r\n    }\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREvent {\r\n    event_type: u32,\r\n    tracked_device_index: u32,\r\n    event_age_seconds: f32,\r\n    data: VREventData,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContextVTable {\r\n    get_generic_interface:\r\n        extern \"C\" fn(*mut DriverContext, *const c_char, *mut EvRInitError) -> *mut c_void,\r\n    get_driver_handle: extern \"C\" fn(*mut DriverContext) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContext {\r\n    vtable: *const DriverContextVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHostVTable {\r\n    tracked_device_added:\r\n        extern \"C\" fn(*mut ServerDriverHost, *const c_char, i32, *mut c_void) -> bool,\r\n    tracked_device_pose_updated: extern \"C\" fn(*mut ServerDriverHost, u32, *const c_void, u32),\r\n    vsync_event: extern \"C\" fn(*mut ServerDriverHost, f64),\r\n    vendor_specific_event: extern \"C\" fn(*mut ServerDriverHost, u32, u32, *const VREventData, f64),\r\n    is_exiting: extern \"C\" fn(*mut ServerDriverHost) -> bool,\r\n    poll_next_event: extern \"C\" fn(*mut ServerDriverHost, *mut VREvent, u32) -> bool,\r\n    get_raw_tracked_device_poses:\r\n        extern \"C\" fn(*mut ServerDriverHost, f32, *mut TrackedDevicePose, u32),\r\n    request_restart:\r\n        extern \"C\" fn(*mut ServerDriverHost, *const c_char, *const c_char, *const c_char, *const c_char),\r\n    get_frame_timings: extern \"C\" fn(*mut ServerDriverHost, *mut c_void, u32) -> u32,\r\n    set_display_eye_to_head:\r\n        extern \"C\" fn(*mut ServerDriverHost, u32, *const c_void, *const c_void),\r\n    set_display_projection_raw:\r\n        extern \"C\" fn(*mut ServerDriverHost, u32, *const c_void, *const c_void),\r\n    set_recommended_render_target_size: extern \"C\" fn(*mut ServerDriverHost, u32, u32, u32),\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHost {\r\n    vtable: *const ServerDriverHostVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverLogVTable {\r\n    log: extern \"C\" fn(*mut DriverLog, *const c_char),\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverLog {\r\n    vtable: *const DriverLogVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverInputVTable {\r\n    create_boolean_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n    update_boolean_component: extern \"C\" fn(*mut DriverInput, u64, bool, f64) -> i32,\r\n    create_scalar_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64, i32, i32) -> i32,\r\n    update_scalar_component: extern \"C\" fn(*mut DriverInput, u64, f32, f64) -> i32,\r\n    create_haptic_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n    create_skeleton_component: extern \"C\" fn(\r\n        *mut DriverInput,\r\n        u64,\r\n        *const c_char,\r\n        *const c_char,\r\n        *const c_char,\r\n        i32,\r\n        *const c_void,\r\n        u32,\r\n        *mut u64,\r\n    ) -> i32,\r\n    update_skeleton_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, i32, *const c_void, u32) -> i32,\r\n    create_pose_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n    update_pose_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const HmdMatrix34, f64) -> i32,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverInput {\r\n    vtable: *const DriverInputVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct PropertiesVTable {\r\n    read_property_batch: extern \"C\" fn(*mut Properties, u64, *mut c_void, u32) -> i32,\r\n    write_property_batch: extern \"C\" fn(*mut Properties, u64, *mut PropertyWrite, u32) -> i32,\r\n    get_prop_error_name_from_enum: extern \"C\" fn(*mut Properties, i32) -> *const c_char,\r\n    tracked_device_to_property_container: extern \"C\" fn(*mut Properties, u32) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct Properties {\r\n    vtable: *const PropertiesVTable,\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct HmdMatrix34 {\r\n    m: [[f32; 4]; 3],\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct TrackedDevicePose {\r\n    m_device_to_absolute_tracking: HmdMatrix34,\r\n    v_velocity: [f32; 3],\r\n    v_angular_velocity: [f32; 3],\r\n    e_tracking_result: i32,\r\n    b_pose_is_valid: bool,\r\n    b_device_is_connected: bool,\r\n}\r\n\r\n#[derive(Clone, Copy, Default)]\r\npub struct RuntimeHmdPose {\r\n    pub position_m: [f32; 3],\r\n    pub rotation: [[f32; 3]; 3],\r\n}\r\n\r\n#[repr(C)]\r\nstruct PropertyWrite {\r\n    prop: u32,\r\n    write_type: i32,\r\n    set_error: i32,\r\n    pv_buffer: *mut c_void,\r\n    un_buffer_size: u32,\r\n    un_tag: u32,\r\n    e_error: i32,\r\n}\r\n\r\nstatic DEVICE_REGISTERED: AtomicBool = AtomicBool::new(false);\r\n\r\nstatic SERVER_HOST_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DRIVER_INPUT_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic PROPERTIES_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DRIVER_LOG_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\n\r\nstatic DEVICE_CONTAINER: AtomicU64 = AtomicU64::new(0);\r\nstatic HAPTIC_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\nstatic SYSTEM_CLICK_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\nstatic A_CLICK_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\nstatic TRIGGER_CLICK_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\nstatic TRIGGER_VALUE_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\nstatic POSE_RAW_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\nstatic POSE_TIP_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\nstatic HAPTIC_EVENT_SEQ: AtomicU64 = AtomicU64::new(0);\r\n\r\npub fn interface_versions_ptr() -> *const *const c_char {\r\n    INTERFACE_VERSIONS.0.as_ptr()\r\n}\r\n\r\nfn get_server_host() -> Option<*mut ServerDriverHost> {\r\n    let ptr = SERVER_HOST_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_driver_input() -> Option<*mut DriverInput> {\r\n    let ptr = DRIVER_INPUT_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_properties() -> Option<*mut Properties> {\r\n    let ptr = PROPERTIES_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_driver_log() -> Option<*mut DriverLog> {\r\n    let ptr = DRIVER_LOG_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\npub(crate) fn log_driver_message(message: &str) {\r\n    let Some(driver_log) = get_driver_log() else {\r\n        return;\r\n    };\r\n\r\n    let Ok(c_message) = CString::new(message) else {\r\n        return;\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*driver_log).vtable).log)(driver_log, c_message.as_ptr());\r\n    }\r\n}\r\n\r\nfn write_string_property(props: *mut Properties, container: u64, prop: u32, value_nul: &'static [u8]) {\r\n    if props.is_null() || value_nul.is_empty() {\r\n        return;\r\n    }\r\n\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: value_nul.as_ptr().cast_mut().cast(),\r\n        un_buffer_size: value_nul.len() as u32,\r\n        un_tag: K_UN_STRING_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn write_bool_property(props: *mut Properties, container: u64, prop: u32, value: bool) {\r\n    if props.is_null() {\r\n        return;\r\n    }\r\n\r\n    let mut bool_value = value;\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: (&mut bool_value as *mut bool).cast(),\r\n        un_buffer_size: core::mem::size_of::<bool>() as u32,\r\n        un_tag: K_UN_BOOL_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn write_int32_property(props: *mut Properties, container: u64, prop: u32, value: i32) {\r\n    if props.is_null() {\r\n        return;\r\n    }\r\n\r\n    let mut int_value = value;\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: (&mut int_value as *mut i32).cast(),\r\n        un_buffer_size: core::mem::size_of::<i32>() as u32,\r\n        un_tag: K_UN_INT32_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn register_tracked_device_if_needed(device_ptr: *mut c_void) {\r\n    if DEVICE_REGISTERED.load(Ordering::SeqCst) {\r\n        return;\r\n    }\r\n\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    let added = unsafe {\r\n        ((*(*host).vtable).tracked_device_added)(\r\n            host,\r\n            DEVICE_SERIAL_CSTR.as_ptr().cast(),\r\n            TRACKED_DEVICE_CLASS_CONTROLLER,\r\n            device_ptr,\r\n        )\r\n    };\r\n\r\n    if added {\r\n        DEVICE_REGISTERED.store(true, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\npub fn initialize(driver_context: *mut c_void, device_ptr: *mut c_void) -> bool {\r\n    if driver_context.is_null() {\r\n        return false;\r\n    }\r\n\r\n    let ctx = driver_context.cast::<DriverContext>();\r\n    let mut err = 0;\r\n\r\n    let get_iface = unsafe { (*(*ctx).vtable).get_generic_interface };\r\n\r\n    let host = get_iface(\r\n        ctx,\r\n        IVR_SERVER_DRIVER_HOST_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let driver_input = get_iface(\r\n        ctx,\r\n        IVR_DRIVER_INPUT_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let properties = get_iface(\r\n        ctx,\r\n        IVR_PROPERTIES_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let driver_log = get_iface(\r\n        ctx,\r\n        IVR_DRIVER_LOG_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    SERVER_HOST_IFACE.store(host, Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(driver_input, Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(properties, Ordering::SeqCst);\r\n    DRIVER_LOG_IFACE.store(driver_log, Ordering::SeqCst);\r\n\r\n    let ok = !host.is_null() && !driver_input.is_null() && !properties.is_null();\r\n    if !ok {\r\n        return false;\r\n    }\r\n\r\n    log_driver_message(&format!(\r\n        \"[big_haptic_driver] OpenVR init ok; sizeof(VREvent)={}, sizeof(VREventHapticVibration)={}\",\r\n        core::mem::size_of::<VREvent>(),\r\n        core::mem::size_of::<VREventHapticVibration>()\r\n    ));\r\n\r\n    register_tracked_device_if_needed(device_ptr);\r\n    true\r\n}\r\n\r\npub fn cleanup() {\r\n    DEVICE_REGISTERED.store(false, Ordering::SeqCst);\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    SYSTEM_CLICK_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    A_CLICK_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    TRIGGER_CLICK_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    TRIGGER_VALUE_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    POSE_RAW_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    POSE_TIP_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    SERVER_HOST_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DRIVER_LOG_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n}\r\n\r\npub fn route_haptic_events() {\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    loop {\r\n        let mut event = VREvent::default();\r\n        let ok = unsafe {\r\n            ((*(*host).vtable).poll_next_event)(\r\n                host,\r\n                &mut event as *mut VREvent,\r\n                core::mem::size_of::<VREvent>() as u32,\r\n            )\r\n        };\r\n\r\n        if !ok {\r\n            break;\r\n        }\r\n\r\n        if event.event_type != VREVENT_INPUT_HAPTIC_VIBRATION {\r\n            continue;\r\n        }\r\n\r\n        let hv = unsafe { event.data.haptic_vibration };\r\n        let seq = HAPTIC_EVENT_SEQ.fetch_add(1, Ordering::SeqCst).saturating_add(1);\r\n\r\n        log_driver_message(&format!(\r\n            \"[big_haptic_driver] haptic event #{}: dev={} age_s={:.6} container={} component={} duration_s={:.6} (0x{:08x}) freq={:.3} amp={:.6} (0x{:08x})\",\r\n            seq,\r\n            event.tracked_device_index,\r\n            event.event_age_seconds,\r\n            hv.container_handle,\r\n            hv.component_handle,\r\n            hv.duration_seconds,\r\n            hv.duration_seconds.to_bits(),\r\n            hv.frequency,\r\n            hv.amplitude,\r\n            hv.amplitude.to_bits()\r\n        ));\r\n\r\n        if hv.amplitude > 0.0 && hv.duration_seconds <= 0.0 {\r\n            log_driver_message(&format!(\r\n                \"[big_haptic_driver] note: OpenVR sent duration<=0; per OpenVR guidance driver will emit one finite pulse (freq={:.3})\",\r\n                hv.frequency\r\n            ));\r\n        }\r\n\r\n        let container = DEVICE_CONTAINER.load(Ordering::SeqCst);\r\n        if container != 0 && hv.container_handle != 0 && hv.container_handle != container {\r\n            log_driver_message(&format!(\r\n                \"[big_haptic_driver] skipped haptic event: container mismatch event={} driver={}\",\r\n                hv.container_handle, container\r\n            ));\r\n            continue;\r\n        }\r\n\r\n        let haptic_component = HAPTIC_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n        if haptic_component != 0 && hv.component_handle != 0 && hv.component_handle != haptic_component {\r\n            log_driver_message(&format!(\r\n                \"[big_haptic_driver] skipped haptic event: component mismatch event={} driver={}\",\r\n                hv.component_handle, haptic_component\r\n            ));\r\n            continue;\r\n        }\r\n\r\n        log_driver_message(\"[big_haptic_driver] forwarding haptic request to HID layer\");\r\n\r\n        haptics::handle_haptic_request(haptics::HapticVibrationRequest {\r\n            amplitude: hv.amplitude,\r\n            duration_seconds: hv.duration_seconds,\r\n            frequency: hv.frequency,\r\n        });\r\n    }\r\n}\r\n\r\npub fn push_pose_update(object_id: u32, pose: *const c_void, pose_size: u32) {\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    if pose.is_null() || pose_size == 0 {\r\n        return;\r\n    }\r\n\r\n    unsafe {\r\n        ((*(*host).vtable).tracked_device_pose_updated)(host, object_id, pose, pose_size);\r\n    }\r\n}\r\n\r\npub fn sample_hmd_pose() -> Option<RuntimeHmdPose> {\r\n    let host = get_server_host()?;\r\n\r\n    let mut poses = [TrackedDevicePose::default(); 1];\r\n    unsafe {\r\n        ((*(*host).vtable).get_raw_tracked_device_poses)(\r\n            host,\r\n            0.0,\r\n            poses.as_mut_ptr(),\r\n            poses.len() as u32,\r\n        );\r\n    }\r\n\r\n    let hmd = poses[TRACKED_DEVICE_INDEX_HMD as usize];\r\n    if !hmd.b_device_is_connected || !hmd.b_pose_is_valid {\r\n        return None;\r\n    }\r\n\r\n    Some(RuntimeHmdPose {\r\n        position_m: [\r\n            hmd.m_device_to_absolute_tracking.m[0][3],\r\n            hmd.m_device_to_absolute_tracking.m[1][3],\r\n            hmd.m_device_to_absolute_tracking.m[2][3],\r\n        ],\r\n        rotation: [\r\n            [\r\n                hmd.m_device_to_absolute_tracking.m[0][0],\r\n                hmd.m_device_to_absolute_tracking.m[0][1],\r\n                hmd.m_device_to_absolute_tracking.m[0][2],\r\n            ],\r\n            [\r\n                hmd.m_device_to_absolute_tracking.m[1][0],\r\n                hmd.m_device_to_absolute_tracking.m[1][1],\r\n                hmd.m_device_to_absolute_tracking.m[1][2],\r\n            ],\r\n            [\r\n                hmd.m_device_to_absolute_tracking.m[2][0],\r\n                hmd.m_device_to_absolute_tracking.m[2][1],\r\n                hmd.m_device_to_absolute_tracking.m[2][2],\r\n            ],\r\n        ],\r\n    })\r\n}\r\n\r\npub fn update_pose_components() {\r\n    let Some(input) = get_driver_input() else {\r\n        return;\r\n    };\r\n\r\n    let raw_handle = POSE_RAW_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n    let tip_handle = POSE_TIP_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n\r\n    let raw_offset = HmdMatrix34 {\r\n        m: [\r\n            [1.0, 0.0, 0.0, 0.0],\r\n            [0.0, 1.0, 0.0, 0.0],\r\n            [0.0, 0.0, 1.0, 0.0],\r\n        ],\r\n    };\r\n    let tip_offset = HmdMatrix34 {\r\n        m: [\r\n            [1.0, 0.0, 0.0, 0.0],\r\n            [0.0, 1.0, 0.0, 0.0],\r\n            [0.0, 0.0, 1.0, -0.04],\r\n        ],\r\n    };\r\n\r\n    if raw_handle != 0 {\r\n        unsafe {\r\n            ((*(*input).vtable).update_pose_component)(input, raw_handle, &raw_offset, 0.0);\r\n        }\r\n    }\r\n\r\n    if tip_handle != 0 {\r\n        unsafe {\r\n            ((*(*input).vtable).update_pose_component)(input, tip_handle, &tip_offset, 0.0);\r\n        }\r\n    }\r\n}\r\n\npub fn update_input_components() {\n    let Some(input) = get_driver_input() else {\n        return;\n    };\n\n    let timestamp = 0.0;\n    let system_click_handle = SYSTEM_CLICK_COMPONENT_HANDLE.load(Ordering::SeqCst);\n    let a_click_handle = A_CLICK_COMPONENT_HANDLE.load(Ordering::SeqCst);\n    let trigger_click_handle = TRIGGER_CLICK_COMPONENT_HANDLE.load(Ordering::SeqCst);\n    let trigger_value_handle = TRIGGER_VALUE_COMPONENT_HANDLE.load(Ordering::SeqCst);\n\n    if system_click_handle != 0 {\n        unsafe {\n            ((*(*input).vtable).update_boolean_component)(input, system_click_handle, false, timestamp);\n        }\n    }\n\n    if a_click_handle != 0 {\n        unsafe {\n            ((*(*input).vtable).update_boolean_component)(input, a_click_handle, false, timestamp);\n        }\n    }\n\n    if trigger_click_handle != 0 {\n        unsafe {\n            ((*(*input).vtable).update_boolean_component)(input, trigger_click_handle, false, timestamp);\n        }\n    }\n\n    if trigger_value_handle != 0 {\n        unsafe {\n            ((*(*input).vtable).update_scalar_component)(input, trigger_value_handle, 0.0, timestamp);\n        }\n    }\n}\n\r\npub fn activate_device(object_id: u32) -> bool {\r\n    let Some(props) = get_properties() else {\r\n        return false;\r\n    };\r\n\r\n    let container = unsafe { ((*(*props).vtable).tracked_device_to_property_container)(props, object_id) };\r\n    DEVICE_CONTAINER.store(container, Ordering::SeqCst);\r\n\r\n    write_string_property(props, container, PROP_MODEL_NUMBER_STRING, DEVICE_MODEL_CSTR);\r\n    write_string_property(props, container, PROP_SERIAL_NUMBER_STRING, DEVICE_SERIAL_CSTR);\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_TRACKING_SYSTEM_NAME_STRING,\r\n        DEVICE_TRACKING_SYSTEM_NAME_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_REGISTERED_DEVICE_TYPE_STRING,\r\n        DEVICE_REGISTERED_TYPE_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_INPUT_PROFILE_PATH_STRING,\r\n        DEVICE_INPUT_PROFILE_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_CONTROLLER_TYPE_STRING,\r\n        DEVICE_CONTROLLER_TYPE_CSTR,\r\n    );\r\n    write_bool_property(props, container, PROP_HAS_CONTROLLER_COMPONENT_BOOL, true);\r\n    write_int32_property(props, container, PROP_CONTROLLER_ROLE_HINT_INT32, 2);\r\n\r\n    if let Some(input) = get_driver_input() {\r\n        let mut handle = 0_u64;\r\n        let rc = unsafe {\r\n            ((*(*input).vtable).create_haptic_component)(\r\n                input,\r\n                container,\r\n                DEVICE_HAPTIC_PATH_CSTR.as_ptr().cast(),\r\n                &mut handle as *mut u64,\r\n            )\r\n        };\r\n\r\n        if rc == 0 {\r\n            HAPTIC_COMPONENT_HANDLE.store(handle, Ordering::SeqCst);\r\n        }\r\n\r\n        let mut pose_raw_handle = 0_u64;\r\n        let rc_raw = unsafe {\r\n            ((*(*input).vtable).create_pose_component)(\r\n                input,\r\n                container,\r\n                DEVICE_POSE_RAW_PATH_CSTR.as_ptr().cast(),\r\n                &mut pose_raw_handle as *mut u64,\r\n            )\r\n        };\r\n        if rc_raw == 0 {\r\n            POSE_RAW_COMPONENT_HANDLE.store(pose_raw_handle, Ordering::SeqCst);\r\n        }\r\n\r\n        let mut pose_tip_handle = 0_u64;\r\n        let rc_tip = unsafe {\r\n            ((*(*input).vtable).create_pose_component)(\r\n                input,\r\n                container,\r\n                DEVICE_POSE_TIP_PATH_CSTR.as_ptr().cast(),\r\n                &mut pose_tip_handle as *mut u64,\r\n            )\r\n        };\r\n        if rc_tip == 0 {\r\n            POSE_TIP_COMPONENT_HANDLE.store(pose_tip_handle, Ordering::SeqCst);\r\n        }\r\n\r\n        update_pose_components();\r\n    }\r\n\r\n    true\r\n}\r\n\r\npub fn deactivate_device() {\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    POSE_RAW_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    POSE_TIP_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":769,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\openvr_runtime.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","scheme":"file"},"requestId":"request_6f13d5f3-b3de-4f61-a09f-3c6ee71f5876","epoch":1698,"edits":[{"text":"//! OpenVR runtime integration helpers.\r\n//!\r\n//! This module owns driver-context interface resolution, tracked-device\r\n//! registration, component/property setup, and OpenVR haptic event routing.\r\n\r\nuse core::ffi::c_void;\r\nuse core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU64, Ordering};\r\nuse std::ffi::CString;\r\nuse std::os::raw::c_char;\r\n\r\nuse crate::haptics;\r\n\r\ntype EvRInitError = i32;\r\n\r\nconst IVR_SERVER_DRIVER_HOST_VERSION: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\nconst IVR_DRIVER_INPUT_VERSION: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_PROPERTIES_VERSION: &[u8] = b\"IVRProperties_001\\0\";\r\nconst IVR_DRIVER_LOG_VERSION: &[u8] = b\"IVRDriverLog_001\\0\";\r\n\r\nconst TRACKED_DEVICE_CLASS_CONTROLLER: i32 = 2;\r\nconst VREVENT_INPUT_HAPTIC_VIBRATION: u32 = 1700;\r\n\r\nconst PROP_MODEL_NUMBER_STRING: u32 = 1001;\r\nconst PROP_SERIAL_NUMBER_STRING: u32 = 1002;\r\nconst PROP_TRACKING_SYSTEM_NAME_STRING: u32 = 1000;\r\nconst PROP_REGISTERED_DEVICE_TYPE_STRING: u32 = 1036;\r\nconst PROP_INPUT_PROFILE_PATH_STRING: u32 = 1037;\r\nconst PROP_CONTROLLER_TYPE_STRING: u32 = 7000;\r\nconst PROP_CONTROLLER_ROLE_HINT_INT32: u32 = 3007;\r\nconst PROP_HAS_CONTROLLER_COMPONENT_BOOL: u32 = 6003;\r\nconst TRACKED_DEVICE_INDEX_HMD: u32 = 0;\r\n\r\nconst K_UN_BOOL_PROPERTY_TAG: u32 = 4;\r\nconst K_UN_INT32_PROPERTY_TAG: u32 = 2;\r\nconst K_UN_STRING_PROPERTY_TAG: u32 = 5;\r\nconst PROPERTY_WRITE_SET: i32 = 0;\r\n\r\nconst DEVICE_SERIAL_CSTR: &[u8] = b\"qtpy-samd21-haptic-001\\0\";\r\nconst DEVICE_MODEL_CSTR: &[u8] = b\"QT Py SAMD21 Haptic\\0\";\r\nconst DEVICE_TRACKING_SYSTEM_NAME_CSTR: &[u8] = b\"big_haptic_driver\\0\";\r\nconst DEVICE_REGISTERED_TYPE_CSTR: &[u8] = b\"big_haptic_driver/controller\\0\";\r\nconst DEVICE_CONTROLLER_TYPE_CSTR: &[u8] = b\"big_haptic_driver\\0\";\r\nconst DEVICE_INPUT_PROFILE_CSTR: &[u8] = b\"{big_haptic_driver}/input/big_haptic_profile.json\\0\";\r\nconst DEVICE_HAPTIC_PATH_CSTR: &[u8] = b\"/output/haptic\\0\";\r\nconst DEVICE_POSE_RAW_PATH_CSTR: &[u8] = b\"/pose/raw\\0\";\r\nconst DEVICE_POSE_TIP_PATH_CSTR: &[u8] = b\"/pose/tip\\0\";\r\nconst DEVICE_SYSTEM_CLICK_PATH_CSTR: &[u8] = b\"/input/system/click\\0\";\r\nconst DEVICE_A_CLICK_PATH_CSTR: &[u8] = b\"/input/a/click\\0\";\r\nconst DEVICE_TRIGGER_CLICK_PATH_CSTR: &[u8] = b\"/input/trigger/click\\0\";\r\nconst DEVICE_TRIGGER_VALUE_PATH_CSTR: &[u8] = b\"/input/trigger/value\\0\";\r\n\r\nconst VR_SCALAR_TYPE_ABSOLUTE: i32 = 0;\r\nconst VR_SCALAR_UNITS_NORMALIZED_ONE_SIDED: i32 = 0;\r\n\r\nconst PROVIDER_IFACE_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_004\\0\";\r\nconst PROVIDER_IFACE_ALT_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_005\\0\";\r\nconst DEVICE_IFACE_CSTR: &[u8] = b\"ITrackedDeviceServerDriver_005\\0\";\r\n\r\nconst IVR_PROPERTIES_CSTR: &[u8] = b\"IVRProperties_001\\0\";\r\nconst IVR_DRIVER_INPUT_CSTR: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_SERVER_DRIVER_HOST_CSTR: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\nconst IVR_DRIVER_LOG_CSTR: &[u8] = b\"IVRDriverLog_001\\0\";\r\n\r\nconst IVR_SETTINGS_CSTR: &[u8] = b\"IVRSettings_003\\0\";\r\nconst IVR_DRIVER_MANAGER_CSTR: &[u8] = b\"IVRDriverManager_001\\0\";\r\nconst IVR_RESOURCES_CSTR: &[u8] = b\"IVRResources_001\\0\";\r\n\r\n#[repr(C)]\r\nstruct InterfaceVersions([*const c_char; 11]);\r\nunsafe impl Sync for InterfaceVersions {}\r\n\r\nstatic INTERFACE_VERSIONS: InterfaceVersions = InterfaceVersions([\r\n    IVR_SETTINGS_CSTR.as_ptr().cast(),\r\n    DEVICE_IFACE_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_CSTR.as_ptr().cast(),\r\n    IVR_PROPERTIES_CSTR.as_ptr().cast(),\r\n    IVR_SERVER_DRIVER_HOST_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_LOG_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_MANAGER_CSTR.as_ptr().cast(),\r\n    IVR_RESOURCES_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_INPUT_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_ALT_CSTR.as_ptr().cast(),\r\n    core::ptr::null(),\r\n]);\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREventHapticVibration {\r\n    container_handle: u64,\r\n    component_handle: u64,\r\n    duration_seconds: f32,\r\n    frequency: f32,\r\n    amplitude: f32,\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nunion VREventData {\r\n    haptic_vibration: VREventHapticVibration,\r\n    reserved: [u8; 48],\r\n}\r\n\r\nimpl Default for VREventData {\r\n    fn default() -> Self {\r\n        Self { reserved: [0; 48] }\r\n    }\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREvent {\r\n    event_type: u32,\r\n    tracked_device_index: u32,\r\n    event_age_seconds: f32,\r\n    data: VREventData,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContextVTable {\r\n    get_generic_interface:\r\n        extern \"C\" fn(*mut DriverContext, *const c_char, *mut EvRInitError) -> *mut c_void,\r\n    get_driver_handle: extern \"C\" fn(*mut DriverContext) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContext {\r\n    vtable: *const DriverContextVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHostVTable {\r\n    tracked_device_added:\r\n        extern \"C\" fn(*mut ServerDriverHost, *const c_char, i32, *mut c_void) -> bool,\r\n    tracked_device_pose_updated: extern \"C\" fn(*mut ServerDriverHost, u32, *const c_void, u32),\r\n    vsync_event: extern \"C\" fn(*mut ServerDriverHost, f64),\r\n    vendor_specific_event: extern \"C\" fn(*mut ServerDriverHost, u32, u32, *const VREventData, f64),\r\n    is_exiting: extern \"C\" fn(*mut ServerDriverHost) -> bool,\r\n    poll_next_event: extern \"C\" fn(*mut ServerDriverHost, *mut VREvent, u32) -> bool,\r\n    get_raw_tracked_device_poses:\r\n        extern \"C\" fn(*mut ServerDriverHost, f32, *mut TrackedDevicePose, u32),\r\n    request_restart:\r\n        extern \"C\" fn(*mut ServerDriverHost, *const c_char, *const c_char, *const c_char, *const c_char),\r\n    get_frame_timings: extern \"C\" fn(*mut ServerDriverHost, *mut c_void, u32) -> u32,\r\n    set_display_eye_to_head:\r\n        extern \"C\" fn(*mut ServerDriverHost, u32, *const c_void, *const c_void),\r\n    set_display_projection_raw:\r\n        extern \"C\" fn(*mut ServerDriverHost, u32, *const c_void, *const c_void),\r\n    set_recommended_render_target_size: extern \"C\" fn(*mut ServerDriverHost, u32, u32, u32),\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHost {\r\n    vtable: *const ServerDriverHostVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverLogVTable {\r\n    log: extern \"C\" fn(*mut DriverLog, *const c_char),\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverLog {\r\n    vtable: *const DriverLogVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverInputVTable {\r\n    create_boolean_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n    update_boolean_component: extern \"C\" fn(*mut DriverInput, u64, bool, f64) -> i32,\r\n    create_scalar_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64, i32, i32) -> i32,\r\n    update_scalar_component: extern \"C\" fn(*mut DriverInput, u64, f32, f64) -> i32,\r\n    create_haptic_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n    create_skeleton_component: extern \"C\" fn(\r\n        *mut DriverInput,\r\n        u64,\r\n        *const c_char,\r\n        *const c_char,\r\n        *const c_char,\r\n        i32,\r\n        *const c_void,\r\n        u32,\r\n        *mut u64,\r\n    ) -> i32,\r\n    update_skeleton_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, i32, *const c_void, u32) -> i32,\r\n    create_pose_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n    update_pose_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const HmdMatrix34, f64) -> i32,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverInput {\r\n    vtable: *const DriverInputVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct PropertiesVTable {\r\n    read_property_batch: extern \"C\" fn(*mut Properties, u64, *mut c_void, u32) -> i32,\r\n    write_property_batch: extern \"C\" fn(*mut Properties, u64, *mut PropertyWrite, u32) -> i32,\r\n    get_prop_error_name_from_enum: extern \"C\" fn(*mut Properties, i32) -> *const c_char,\r\n    tracked_device_to_property_container: extern \"C\" fn(*mut Properties, u32) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct Properties {\r\n    vtable: *const PropertiesVTable,\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct HmdMatrix34 {\r\n    m: [[f32; 4]; 3],\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct TrackedDevicePose {\r\n    m_device_to_absolute_tracking: HmdMatrix34,\r\n    v_velocity: [f32; 3],\r\n    v_angular_velocity: [f32; 3],\r\n    e_tracking_result: i32,\r\n    b_pose_is_valid: bool,\r\n    b_device_is_connected: bool,\r\n}\r\n\r\n#[derive(Clone, Copy, Default)]\r\npub struct RuntimeHmdPose {\r\n    pub position_m: [f32; 3],\r\n    pub rotation: [[f32; 3]; 3],\r\n}\r\n\r\n#[repr(C)]\r\nstruct PropertyWrite {\r\n    prop: u32,\r\n    write_type: i32,\r\n    set_error: i32,\r\n    pv_buffer: *mut c_void,\r\n    un_buffer_size: u32,\r\n    un_tag: u32,\r\n    e_error: i32,\r\n}\r\n\r\nstatic DEVICE_REGISTERED: AtomicBool = AtomicBool::new(false);\r\n\r\nstatic SERVER_HOST_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DRIVER_INPUT_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic PROPERTIES_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DRIVER_LOG_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\n\r\nstatic DEVICE_CONTAINER: AtomicU64 = AtomicU64::new(0);\r\nstatic HAPTIC_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\nstatic SYSTEM_CLICK_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\nstatic A_CLICK_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\nstatic TRIGGER_CLICK_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\nstatic TRIGGER_VALUE_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\nstatic POSE_RAW_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\nstatic POSE_TIP_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\nstatic HAPTIC_EVENT_SEQ: AtomicU64 = AtomicU64::new(0);\r\n\r\npub fn interface_versions_ptr() -> *const *const c_char {\r\n    INTERFACE_VERSIONS.0.as_ptr()\r\n}\r\n\r\nfn get_server_host() -> Option<*mut ServerDriverHost> {\r\n    let ptr = SERVER_HOST_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_driver_input() -> Option<*mut DriverInput> {\r\n    let ptr = DRIVER_INPUT_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_properties() -> Option<*mut Properties> {\r\n    let ptr = PROPERTIES_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_driver_log() -> Option<*mut DriverLog> {\r\n    let ptr = DRIVER_LOG_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\npub(crate) fn log_driver_message(message: &str) {\r\n    let Some(driver_log) = get_driver_log() else {\r\n        return;\r\n    };\r\n\r\n    let Ok(c_message) = CString::new(message) else {\r\n        return;\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*driver_log).vtable).log)(driver_log, c_message.as_ptr());\r\n    }\r\n}\r\n\r\nfn write_string_property(props: *mut Properties, container: u64, prop: u32, value_nul: &'static [u8]) {\r\n    if props.is_null() || value_nul.is_empty() {\r\n        return;\r\n    }\r\n\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: value_nul.as_ptr().cast_mut().cast(),\r\n        un_buffer_size: value_nul.len() as u32,\r\n        un_tag: K_UN_STRING_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn write_bool_property(props: *mut Properties, container: u64, prop: u32, value: bool) {\r\n    if props.is_null() {\r\n        return;\r\n    }\r\n\r\n    let mut bool_value = value;\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: (&mut bool_value as *mut bool).cast(),\r\n        un_buffer_size: core::mem::size_of::<bool>() as u32,\r\n        un_tag: K_UN_BOOL_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn write_int32_property(props: *mut Properties, container: u64, prop: u32, value: i32) {\r\n    if props.is_null() {\r\n        return;\r\n    }\r\n\r\n    let mut int_value = value;\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: (&mut int_value as *mut i32).cast(),\r\n        un_buffer_size: core::mem::size_of::<i32>() as u32,\r\n        un_tag: K_UN_INT32_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn register_tracked_device_if_needed(device_ptr: *mut c_void) {\r\n    if DEVICE_REGISTERED.load(Ordering::SeqCst) {\r\n        return;\r\n    }\r\n\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    let added = unsafe {\r\n        ((*(*host).vtable).tracked_device_added)(\r\n            host,\r\n            DEVICE_SERIAL_CSTR.as_ptr().cast(),\r\n            TRACKED_DEVICE_CLASS_CONTROLLER,\r\n            device_ptr,\r\n        )\r\n    };\r\n\r\n    if added {\r\n        DEVICE_REGISTERED.store(true, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\npub fn initialize(driver_context: *mut c_void, device_ptr: *mut c_void) -> bool {\r\n    if driver_context.is_null() {\r\n        return false;\r\n    }\r\n\r\n    let ctx = driver_context.cast::<DriverContext>();\r\n    let mut err = 0;\r\n\r\n    let get_iface = unsafe { (*(*ctx).vtable).get_generic_interface };\r\n\r\n    let host = get_iface(\r\n        ctx,\r\n        IVR_SERVER_DRIVER_HOST_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let driver_input = get_iface(\r\n        ctx,\r\n        IVR_DRIVER_INPUT_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let properties = get_iface(\r\n        ctx,\r\n        IVR_PROPERTIES_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let driver_log = get_iface(\r\n        ctx,\r\n        IVR_DRIVER_LOG_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    SERVER_HOST_IFACE.store(host, Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(driver_input, Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(properties, Ordering::SeqCst);\r\n    DRIVER_LOG_IFACE.store(driver_log, Ordering::SeqCst);\r\n\r\n    let ok = !host.is_null() && !driver_input.is_null() && !properties.is_null();\r\n    if !ok {\r\n        return false;\r\n    }\r\n\r\n    log_driver_message(&format!(\r\n        \"[big_haptic_driver] OpenVR init ok; sizeof(VREvent)={}, sizeof(VREventHapticVibration)={}\",\r\n        core::mem::size_of::<VREvent>(),\r\n        core::mem::size_of::<VREventHapticVibration>()\r\n    ));\r\n\r\n    register_tracked_device_if_needed(device_ptr);\r\n    true\r\n}\r\n\r\npub fn cleanup() {\r\n    DEVICE_REGISTERED.store(false, Ordering::SeqCst);\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    SYSTEM_CLICK_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    A_CLICK_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    TRIGGER_CLICK_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    TRIGGER_VALUE_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    POSE_RAW_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    POSE_TIP_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    SERVER_HOST_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DRIVER_LOG_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n}\r\n\r\npub fn route_haptic_events() {\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    loop {\r\n        let mut event = VREvent::default();\r\n        let ok = unsafe {\r\n            ((*(*host).vtable).poll_next_event)(\r\n                host,\r\n                &mut event as *mut VREvent,\r\n                core::mem::size_of::<VREvent>() as u32,\r\n            )\r\n        };\r\n\r\n        if !ok {\r\n            break;\r\n        }\r\n\r\n        if event.event_type != VREVENT_INPUT_HAPTIC_VIBRATION {\r\n            continue;\r\n        }\r\n\r\n        let hv = unsafe { event.data.haptic_vibration };\r\n        let seq = HAPTIC_EVENT_SEQ.fetch_add(1, Ordering::SeqCst).saturating_add(1);\r\n\r\n        log_driver_message(&format!(\r\n            \"[big_haptic_driver] haptic event #{}: dev={} age_s={:.6} container={} component={} duration_s={:.6} (0x{:08x}) freq={:.3} amp={:.6} (0x{:08x})\",\r\n            seq,\r\n            event.tracked_device_index,\r\n            event.event_age_seconds,\r\n            hv.container_handle,\r\n            hv.component_handle,\r\n            hv.duration_seconds,\r\n            hv.duration_seconds.to_bits(),\r\n            hv.frequency,\r\n            hv.amplitude,\r\n            hv.amplitude.to_bits()\r\n        ));\r\n\r\n        if hv.amplitude > 0.0 && hv.duration_seconds <= 0.0 {\r\n            log_driver_message(&format!(\r\n                \"[big_haptic_driver] note: OpenVR sent duration<=0; per OpenVR guidance driver will emit one finite pulse (freq={:.3})\",\r\n                hv.frequency\r\n            ));\r\n        }\r\n\r\n        let container = DEVICE_CONTAINER.load(Ordering::SeqCst);\r\n        if container != 0 && hv.container_handle != 0 && hv.container_handle != container {\r\n            log_driver_message(&format!(\r\n                \"[big_haptic_driver] skipped haptic event: container mismatch event={} driver={}\",\r\n                hv.container_handle, container\r\n            ));\r\n            continue;\r\n        }\r\n\r\n        let haptic_component = HAPTIC_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n        if haptic_component != 0 && hv.component_handle != 0 && hv.component_handle != haptic_component {\r\n            log_driver_message(&format!(\r\n                \"[big_haptic_driver] skipped haptic event: component mismatch event={} driver={}\",\r\n                hv.component_handle, haptic_component\r\n            ));\r\n            continue;\r\n        }\r\n\r\n        log_driver_message(\"[big_haptic_driver] forwarding haptic request to HID layer\");\r\n\r\n        haptics::handle_haptic_request(haptics::HapticVibrationRequest {\r\n            amplitude: hv.amplitude,\r\n            duration_seconds: hv.duration_seconds,\r\n            frequency: hv.frequency,\r\n        });\r\n    }\r\n}\r\n\r\npub fn push_pose_update(object_id: u32, pose: *const c_void, pose_size: u32) {\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    if pose.is_null() || pose_size == 0 {\r\n        return;\r\n    }\r\n\r\n    unsafe {\r\n        ((*(*host).vtable).tracked_device_pose_updated)(host, object_id, pose, pose_size);\r\n    }\r\n}\r\n\r\npub fn sample_hmd_pose() -> Option<RuntimeHmdPose> {\r\n    let host = get_server_host()?;\r\n\r\n    let mut poses = [TrackedDevicePose::default(); 1];\r\n    unsafe {\r\n        ((*(*host).vtable).get_raw_tracked_device_poses)(\r\n            host,\r\n            0.0,\r\n            poses.as_mut_ptr(),\r\n            poses.len() as u32,\r\n        );\r\n    }\r\n\r\n    let hmd = poses[TRACKED_DEVICE_INDEX_HMD as usize];\r\n    if !hmd.b_device_is_connected || !hmd.b_pose_is_valid {\r\n        return None;\r\n    }\r\n\r\n    Some(RuntimeHmdPose {\r\n        position_m: [\r\n            hmd.m_device_to_absolute_tracking.m[0][3],\r\n            hmd.m_device_to_absolute_tracking.m[1][3],\r\n            hmd.m_device_to_absolute_tracking.m[2][3],\r\n        ],\r\n        rotation: [\r\n            [\r\n                hmd.m_device_to_absolute_tracking.m[0][0],\r\n                hmd.m_device_to_absolute_tracking.m[0][1],\r\n                hmd.m_device_to_absolute_tracking.m[0][2],\r\n            ],\r\n            [\r\n                hmd.m_device_to_absolute_tracking.m[1][0],\r\n                hmd.m_device_to_absolute_tracking.m[1][1],\r\n                hmd.m_device_to_absolute_tracking.m[1][2],\r\n            ],\r\n            [\r\n                hmd.m_device_to_absolute_tracking.m[2][0],\r\n                hmd.m_device_to_absolute_tracking.m[2][1],\r\n                hmd.m_device_to_absolute_tracking.m[2][2],\r\n            ],\r\n        ],\r\n    })\r\n}\r\n\r\npub fn update_pose_components() {\r\n    let Some(input) = get_driver_input() else {\r\n        return;\r\n    };\r\n\r\n    let raw_handle = POSE_RAW_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n    let tip_handle = POSE_TIP_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n\r\n    let raw_offset = HmdMatrix34 {\r\n        m: [\r\n            [1.0, 0.0, 0.0, 0.0],\r\n            [0.0, 1.0, 0.0, 0.0],\r\n            [0.0, 0.0, 1.0, 0.0],\r\n        ],\r\n    };\r\n    let tip_offset = HmdMatrix34 {\r\n        m: [\r\n            [1.0, 0.0, 0.0, 0.0],\r\n            [0.0, 1.0, 0.0, 0.0],\r\n            [0.0, 0.0, 1.0, -0.04],\r\n        ],\r\n    };\r\n\r\n    if raw_handle != 0 {\r\n        unsafe {\r\n            ((*(*input).vtable).update_pose_component)(input, raw_handle, &raw_offset, 0.0);\r\n        }\r\n    }\r\n\r\n    if tip_handle != 0 {\r\n        unsafe {\r\n            ((*(*input).vtable).update_pose_component)(input, tip_handle, &tip_offset, 0.0);\r\n        }\r\n    }\r\n}\r\n\r\npub fn update_input_components() {\r\n    let Some(input) = get_driver_input() else {\r\n        return;\r\n    };\r\n\r\n    let timestamp = 0.0;\r\n    let system_click_handle = SYSTEM_CLICK_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n    let a_click_handle = A_CLICK_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n    let trigger_click_handle = TRIGGER_CLICK_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n    let trigger_value_handle = TRIGGER_VALUE_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n\r\n    if system_click_handle != 0 {\r\n        unsafe {\r\n            ((*(*input).vtable).update_boolean_component)(input, system_click_handle, false, timestamp);\r\n        }\r\n    }\r\n\r\n    if a_click_handle != 0 {\r\n        unsafe {\r\n            ((*(*input).vtable).update_boolean_component)(input, a_click_handle, false, timestamp);\r\n        }\r\n    }\r\n\r\n    if trigger_click_handle != 0 {\r\n        unsafe {\r\n            ((*(*input).vtable).update_boolean_component)(input, trigger_click_handle, false, timestamp);\r\n        }\r\n    }\r\n\r\n    if trigger_value_handle != 0 {\r\n        unsafe {\r\n            ((*(*input).vtable).update_scalar_component)(input, trigger_value_handle, 0.0, timestamp);\r\n        }\r\n    }\r\n}\r\n\r\npub fn activate_device(object_id: u32) -> bool {\r\n    let Some(props) = get_properties() else {\r\n        return false;\r\n    };\r\n\r\n    let container = unsafe { ((*(*props).vtable).tracked_device_to_property_container)(props, object_id) };\r\n    DEVICE_CONTAINER.store(container, Ordering::SeqCst);\r\n\r\n    write_string_property(props, container, PROP_MODEL_NUMBER_STRING, DEVICE_MODEL_CSTR);\r\n    write_string_property(props, container, PROP_SERIAL_NUMBER_STRING, DEVICE_SERIAL_CSTR);\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_TRACKING_SYSTEM_NAME_STRING,\r\n        DEVICE_TRACKING_SYSTEM_NAME_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_REGISTERED_DEVICE_TYPE_STRING,\r\n        DEVICE_REGISTERED_TYPE_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_INPUT_PROFILE_PATH_STRING,\r\n        DEVICE_INPUT_PROFILE_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_CONTROLLER_TYPE_STRING,\r\n        DEVICE_CONTROLLER_TYPE_CSTR,\r\n    );\r\n    write_bool_property(props, container, PROP_HAS_CONTROLLER_COMPONENT_BOOL, true);\r\n    write_int32_property(props, container, PROP_CONTROLLER_ROLE_HINT_INT32, 2);\r\n\r\n    if let Some(input) = get_driver_input() {\r\n        let mut system_click_handle = 0_u64;\n        let rc_system = unsafe {\n            ((*(*input).vtable).create_boolean_component)(\n                input,\n                container,\n                DEVICE_SYSTEM_CLICK_PATH_CSTR.as_ptr().cast(),\n                &mut system_click_handle as *mut u64,\n            )\n        };\n        if rc_system == 0 {\n            SYSTEM_CLICK_COMPONENT_HANDLE.store(system_click_handle, Ordering::SeqCst);\n        }\n\n        let mut a_click_handle = 0_u64;\n        let rc_a = unsafe {\n            ((*(*input).vtable).create_boolean_component)(\n                input,\n                container,\n                DEVICE_A_CLICK_PATH_CSTR.as_ptr().cast(),\n                &mut a_click_handle as *mut u64,\n            )\n        };\n        if rc_a == 0 {\n            A_CLICK_COMPONENT_HANDLE.store(a_click_handle, Ordering::SeqCst);\n        }\n\n        let mut trigger_click_handle = 0_u64;\n        let rc_trigger_click = unsafe {\n            ((*(*input).vtable).create_boolean_component)(\n                input,\n                container,\n                DEVICE_TRIGGER_CLICK_PATH_CSTR.as_ptr().cast(),\n                &mut trigger_click_handle as *mut u64,\n            )\n        };\n        if rc_trigger_click == 0 {\n            TRIGGER_CLICK_COMPONENT_HANDLE.store(trigger_click_handle, Ordering::SeqCst);\n        }\n\n        let mut trigger_value_handle = 0_u64;\n        let rc_trigger_value = unsafe {\n            ((*(*input).vtable).create_scalar_component)(\n                input,\n                container,\n                DEVICE_TRIGGER_VALUE_PATH_CSTR.as_ptr().cast(),\n                &mut trigger_value_handle as *mut u64,\n                VR_SCALAR_TYPE_ABSOLUTE,\n                VR_SCALAR_UNITS_NORMALIZED_ONE_SIDED,\n            )\n        };\n        if rc_trigger_value == 0 {\n            TRIGGER_VALUE_COMPONENT_HANDLE.store(trigger_value_handle, Ordering::SeqCst);\n        }\n\n        let mut handle = 0_u64;\r\n        let rc = unsafe {\r\n            ((*(*input).vtable).create_haptic_component)(\r\n                input,\r\n                container,\r\n                DEVICE_HAPTIC_PATH_CSTR.as_ptr().cast(),\r\n                &mut handle as *mut u64,\r\n            )\r\n        };\r\n\r\n        if rc == 0 {\r\n            HAPTIC_COMPONENT_HANDLE.store(handle, Ordering::SeqCst);\r\n        }\r\n\r\n        let mut pose_raw_handle = 0_u64;\r\n        let rc_raw = unsafe {\r\n            ((*(*input).vtable).create_pose_component)(\r\n                input,\r\n                container,\r\n                DEVICE_POSE_RAW_PATH_CSTR.as_ptr().cast(),\r\n                &mut pose_raw_handle as *mut u64,\r\n            )\r\n        };\r\n        if rc_raw == 0 {\r\n            POSE_RAW_COMPONENT_HANDLE.store(pose_raw_handle, Ordering::SeqCst);\r\n        }\r\n\r\n        let mut pose_tip_handle = 0_u64;\r\n        let rc_tip = unsafe {\r\n            ((*(*input).vtable).create_pose_component)(\r\n                input,\r\n                container,\r\n                DEVICE_POSE_TIP_PATH_CSTR.as_ptr().cast(),\r\n                &mut pose_tip_handle as *mut u64,\r\n            )\r\n        };\r\n        if rc_tip == 0 {\r\n            POSE_TIP_COMPONENT_HANDLE.store(pose_tip_handle, Ordering::SeqCst);\r\n        }\r\n\r\n        update_input_components();\n        update_pose_components();\r\n    }\r\n\r\n    true\r\n}\r\n\r\npub fn deactivate_device() {\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    POSE_RAW_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    POSE_TIP_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":824,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\openvr_runtime.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","scheme":"file"},"requestId":"request_253d5ff6-6f1d-4b2e-83a2-f98328aa01b9","epoch":1702,"edits":[{"text":"//! OpenVR runtime integration helpers.\r\n//!\r\n//! This module owns driver-context interface resolution, tracked-device\r\n//! registration, component/property setup, and OpenVR haptic event routing.\r\n\r\nuse core::ffi::c_void;\r\nuse core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU64, Ordering};\r\nuse std::ffi::CString;\r\nuse std::os::raw::c_char;\r\n\r\nuse crate::haptics;\r\n\r\ntype EvRInitError = i32;\r\n\r\nconst IVR_SERVER_DRIVER_HOST_VERSION: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\nconst IVR_DRIVER_INPUT_VERSION: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_PROPERTIES_VERSION: &[u8] = b\"IVRProperties_001\\0\";\r\nconst IVR_DRIVER_LOG_VERSION: &[u8] = b\"IVRDriverLog_001\\0\";\r\n\r\nconst TRACKED_DEVICE_CLASS_CONTROLLER: i32 = 2;\r\nconst VREVENT_INPUT_HAPTIC_VIBRATION: u32 = 1700;\r\n\r\nconst PROP_MODEL_NUMBER_STRING: u32 = 1001;\r\nconst PROP_SERIAL_NUMBER_STRING: u32 = 1002;\r\nconst PROP_TRACKING_SYSTEM_NAME_STRING: u32 = 1000;\r\nconst PROP_REGISTERED_DEVICE_TYPE_STRING: u32 = 1036;\r\nconst PROP_INPUT_PROFILE_PATH_STRING: u32 = 1037;\r\nconst PROP_CONTROLLER_TYPE_STRING: u32 = 7000;\r\nconst PROP_CONTROLLER_ROLE_HINT_INT32: u32 = 3007;\r\nconst PROP_HAS_CONTROLLER_COMPONENT_BOOL: u32 = 6003;\r\nconst TRACKED_DEVICE_INDEX_HMD: u32 = 0;\r\n\r\nconst K_UN_BOOL_PROPERTY_TAG: u32 = 4;\r\nconst K_UN_INT32_PROPERTY_TAG: u32 = 2;\r\nconst K_UN_STRING_PROPERTY_TAG: u32 = 5;\r\nconst PROPERTY_WRITE_SET: i32 = 0;\r\n\r\nconst DEVICE_SERIAL_CSTR: &[u8] = b\"qtpy-samd21-haptic-001\\0\";\r\nconst DEVICE_MODEL_CSTR: &[u8] = b\"QT Py SAMD21 Haptic\\0\";\r\nconst DEVICE_TRACKING_SYSTEM_NAME_CSTR: &[u8] = b\"big_haptic_driver\\0\";\r\nconst DEVICE_REGISTERED_TYPE_CSTR: &[u8] = b\"big_haptic_driver/controller\\0\";\r\nconst DEVICE_CONTROLLER_TYPE_CSTR: &[u8] = b\"big_haptic_driver\\0\";\r\nconst DEVICE_INPUT_PROFILE_CSTR: &[u8] = b\"{big_haptic_driver}/input/big_haptic_profile.json\\0\";\r\nconst DEVICE_HAPTIC_PATH_CSTR: &[u8] = b\"/output/haptic\\0\";\r\nconst DEVICE_POSE_RAW_PATH_CSTR: &[u8] = b\"/pose/raw\\0\";\r\nconst DEVICE_POSE_TIP_PATH_CSTR: &[u8] = b\"/pose/tip\\0\";\r\nconst DEVICE_SYSTEM_CLICK_PATH_CSTR: &[u8] = b\"/input/system/click\\0\";\r\nconst DEVICE_A_CLICK_PATH_CSTR: &[u8] = b\"/input/a/click\\0\";\r\nconst DEVICE_TRIGGER_CLICK_PATH_CSTR: &[u8] = b\"/input/trigger/click\\0\";\r\nconst DEVICE_TRIGGER_VALUE_PATH_CSTR: &[u8] = b\"/input/trigger/value\\0\";\r\n\r\nconst VR_SCALAR_TYPE_ABSOLUTE: i32 = 0;\r\nconst VR_SCALAR_UNITS_NORMALIZED_ONE_SIDED: i32 = 0;\r\n\r\nconst PROVIDER_IFACE_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_004\\0\";\r\nconst PROVIDER_IFACE_ALT_CSTR: &[u8] = b\"IServerTrackedDeviceProvider_005\\0\";\r\nconst DEVICE_IFACE_CSTR: &[u8] = b\"ITrackedDeviceServerDriver_005\\0\";\r\n\r\nconst IVR_PROPERTIES_CSTR: &[u8] = b\"IVRProperties_001\\0\";\r\nconst IVR_DRIVER_INPUT_CSTR: &[u8] = b\"IVRDriverInput_004\\0\";\r\nconst IVR_SERVER_DRIVER_HOST_CSTR: &[u8] = b\"IVRServerDriverHost_006\\0\";\r\nconst IVR_DRIVER_LOG_CSTR: &[u8] = b\"IVRDriverLog_001\\0\";\r\n\r\nconst IVR_SETTINGS_CSTR: &[u8] = b\"IVRSettings_003\\0\";\r\nconst IVR_DRIVER_MANAGER_CSTR: &[u8] = b\"IVRDriverManager_001\\0\";\r\nconst IVR_RESOURCES_CSTR: &[u8] = b\"IVRResources_001\\0\";\r\n\r\n#[repr(C)]\r\nstruct InterfaceVersions([*const c_char; 11]);\r\nunsafe impl Sync for InterfaceVersions {}\r\n\r\nstatic INTERFACE_VERSIONS: InterfaceVersions = InterfaceVersions([\r\n    IVR_SETTINGS_CSTR.as_ptr().cast(),\r\n    DEVICE_IFACE_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_CSTR.as_ptr().cast(),\r\n    IVR_PROPERTIES_CSTR.as_ptr().cast(),\r\n    IVR_SERVER_DRIVER_HOST_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_LOG_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_MANAGER_CSTR.as_ptr().cast(),\r\n    IVR_RESOURCES_CSTR.as_ptr().cast(),\r\n    IVR_DRIVER_INPUT_CSTR.as_ptr().cast(),\r\n    PROVIDER_IFACE_ALT_CSTR.as_ptr().cast(),\r\n    core::ptr::null(),\r\n]);\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREventHapticVibration {\r\n    container_handle: u64,\r\n    component_handle: u64,\r\n    duration_seconds: f32,\r\n    frequency: f32,\r\n    amplitude: f32,\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nunion VREventData {\r\n    haptic_vibration: VREventHapticVibration,\r\n    reserved: [u8; 48],\r\n}\r\n\r\nimpl Default for VREventData {\r\n    fn default() -> Self {\r\n        Self { reserved: [0; 48] }\r\n    }\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct VREvent {\r\n    event_type: u32,\r\n    tracked_device_index: u32,\r\n    event_age_seconds: f32,\r\n    data: VREventData,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContextVTable {\r\n    get_generic_interface:\r\n        extern \"C\" fn(*mut DriverContext, *const c_char, *mut EvRInitError) -> *mut c_void,\r\n    get_driver_handle: extern \"C\" fn(*mut DriverContext) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverContext {\r\n    vtable: *const DriverContextVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHostVTable {\r\n    tracked_device_added:\r\n        extern \"C\" fn(*mut ServerDriverHost, *const c_char, i32, *mut c_void) -> bool,\r\n    tracked_device_pose_updated: extern \"C\" fn(*mut ServerDriverHost, u32, *const c_void, u32),\r\n    vsync_event: extern \"C\" fn(*mut ServerDriverHost, f64),\r\n    vendor_specific_event: extern \"C\" fn(*mut ServerDriverHost, u32, u32, *const VREventData, f64),\r\n    is_exiting: extern \"C\" fn(*mut ServerDriverHost) -> bool,\r\n    poll_next_event: extern \"C\" fn(*mut ServerDriverHost, *mut VREvent, u32) -> bool,\r\n    get_raw_tracked_device_poses:\r\n        extern \"C\" fn(*mut ServerDriverHost, f32, *mut TrackedDevicePose, u32),\r\n    request_restart:\r\n        extern \"C\" fn(*mut ServerDriverHost, *const c_char, *const c_char, *const c_char, *const c_char),\r\n    get_frame_timings: extern \"C\" fn(*mut ServerDriverHost, *mut c_void, u32) -> u32,\r\n    set_display_eye_to_head:\r\n        extern \"C\" fn(*mut ServerDriverHost, u32, *const c_void, *const c_void),\r\n    set_display_projection_raw:\r\n        extern \"C\" fn(*mut ServerDriverHost, u32, *const c_void, *const c_void),\r\n    set_recommended_render_target_size: extern \"C\" fn(*mut ServerDriverHost, u32, u32, u32),\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerDriverHost {\r\n    vtable: *const ServerDriverHostVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverLogVTable {\r\n    log: extern \"C\" fn(*mut DriverLog, *const c_char),\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverLog {\r\n    vtable: *const DriverLogVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverInputVTable {\r\n    create_boolean_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n    update_boolean_component: extern \"C\" fn(*mut DriverInput, u64, bool, f64) -> i32,\r\n    create_scalar_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64, i32, i32) -> i32,\r\n    update_scalar_component: extern \"C\" fn(*mut DriverInput, u64, f32, f64) -> i32,\r\n    create_haptic_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n    create_skeleton_component: extern \"C\" fn(\r\n        *mut DriverInput,\r\n        u64,\r\n        *const c_char,\r\n        *const c_char,\r\n        *const c_char,\r\n        i32,\r\n        *const c_void,\r\n        u32,\r\n        *mut u64,\r\n    ) -> i32,\r\n    update_skeleton_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, i32, *const c_void, u32) -> i32,\r\n    create_pose_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const c_char, *mut u64) -> i32,\r\n    update_pose_component:\r\n        extern \"C\" fn(*mut DriverInput, u64, *const HmdMatrix34, f64) -> i32,\r\n}\r\n\r\n#[repr(C)]\r\nstruct DriverInput {\r\n    vtable: *const DriverInputVTable,\r\n}\r\n\r\n#[repr(C)]\r\nstruct PropertiesVTable {\r\n    read_property_batch: extern \"C\" fn(*mut Properties, u64, *mut c_void, u32) -> i32,\r\n    write_property_batch: extern \"C\" fn(*mut Properties, u64, *mut PropertyWrite, u32) -> i32,\r\n    get_prop_error_name_from_enum: extern \"C\" fn(*mut Properties, i32) -> *const c_char,\r\n    tracked_device_to_property_container: extern \"C\" fn(*mut Properties, u32) -> u64,\r\n}\r\n\r\n#[repr(C)]\r\nstruct Properties {\r\n    vtable: *const PropertiesVTable,\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct HmdMatrix34 {\r\n    m: [[f32; 4]; 3],\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\nstruct TrackedDevicePose {\r\n    m_device_to_absolute_tracking: HmdMatrix34,\r\n    v_velocity: [f32; 3],\r\n    v_angular_velocity: [f32; 3],\r\n    e_tracking_result: i32,\r\n    b_pose_is_valid: bool,\r\n    b_device_is_connected: bool,\r\n}\r\n\r\n#[derive(Clone, Copy, Default)]\r\npub struct RuntimeHmdPose {\r\n    pub position_m: [f32; 3],\r\n    pub rotation: [[f32; 3]; 3],\r\n}\r\n\r\n#[repr(C)]\r\nstruct PropertyWrite {\r\n    prop: u32,\r\n    write_type: i32,\r\n    set_error: i32,\r\n    pv_buffer: *mut c_void,\r\n    un_buffer_size: u32,\r\n    un_tag: u32,\r\n    e_error: i32,\r\n}\r\n\r\nstatic DEVICE_REGISTERED: AtomicBool = AtomicBool::new(false);\r\n\r\nstatic SERVER_HOST_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DRIVER_INPUT_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic PROPERTIES_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\nstatic DRIVER_LOG_IFACE: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());\r\n\r\nstatic DEVICE_CONTAINER: AtomicU64 = AtomicU64::new(0);\r\nstatic HAPTIC_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\nstatic SYSTEM_CLICK_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\nstatic A_CLICK_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\nstatic TRIGGER_CLICK_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\nstatic TRIGGER_VALUE_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\nstatic POSE_RAW_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\nstatic POSE_TIP_COMPONENT_HANDLE: AtomicU64 = AtomicU64::new(0);\r\nstatic HAPTIC_EVENT_SEQ: AtomicU64 = AtomicU64::new(0);\r\n\r\npub fn interface_versions_ptr() -> *const *const c_char {\r\n    INTERFACE_VERSIONS.0.as_ptr()\r\n}\r\n\r\nfn get_server_host() -> Option<*mut ServerDriverHost> {\r\n    let ptr = SERVER_HOST_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_driver_input() -> Option<*mut DriverInput> {\r\n    let ptr = DRIVER_INPUT_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_properties() -> Option<*mut Properties> {\r\n    let ptr = PROPERTIES_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\nfn get_driver_log() -> Option<*mut DriverLog> {\r\n    let ptr = DRIVER_LOG_IFACE.load(Ordering::SeqCst);\r\n    if ptr.is_null() {\r\n        None\r\n    } else {\r\n        Some(ptr.cast())\r\n    }\r\n}\r\n\r\npub(crate) fn log_driver_message(message: &str) {\r\n    let Some(driver_log) = get_driver_log() else {\r\n        return;\r\n    };\r\n\r\n    let Ok(c_message) = CString::new(message) else {\r\n        return;\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*driver_log).vtable).log)(driver_log, c_message.as_ptr());\r\n    }\r\n}\r\n\r\nfn write_string_property(props: *mut Properties, container: u64, prop: u32, value_nul: &'static [u8]) {\r\n    if props.is_null() || value_nul.is_empty() {\r\n        return;\r\n    }\r\n\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: value_nul.as_ptr().cast_mut().cast(),\r\n        un_buffer_size: value_nul.len() as u32,\r\n        un_tag: K_UN_STRING_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn write_bool_property(props: *mut Properties, container: u64, prop: u32, value: bool) {\r\n    if props.is_null() {\r\n        return;\r\n    }\r\n\r\n    let mut bool_value = value;\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: (&mut bool_value as *mut bool).cast(),\r\n        un_buffer_size: core::mem::size_of::<bool>() as u32,\r\n        un_tag: K_UN_BOOL_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn write_int32_property(props: *mut Properties, container: u64, prop: u32, value: i32) {\r\n    if props.is_null() {\r\n        return;\r\n    }\r\n\r\n    let mut int_value = value;\r\n    let mut write = PropertyWrite {\r\n        prop,\r\n        write_type: PROPERTY_WRITE_SET,\r\n        set_error: 0,\r\n        pv_buffer: (&mut int_value as *mut i32).cast(),\r\n        un_buffer_size: core::mem::size_of::<i32>() as u32,\r\n        un_tag: K_UN_INT32_PROPERTY_TAG,\r\n        e_error: 0,\r\n    };\r\n\r\n    unsafe {\r\n        ((*(*props).vtable).write_property_batch)(props, container, &mut write, 1);\r\n    }\r\n}\r\n\r\nfn register_tracked_device_if_needed(device_ptr: *mut c_void) {\r\n    if DEVICE_REGISTERED.load(Ordering::SeqCst) {\r\n        return;\r\n    }\r\n\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    let added = unsafe {\r\n        ((*(*host).vtable).tracked_device_added)(\r\n            host,\r\n            DEVICE_SERIAL_CSTR.as_ptr().cast(),\r\n            TRACKED_DEVICE_CLASS_CONTROLLER,\r\n            device_ptr,\r\n        )\r\n    };\r\n\r\n    if added {\r\n        DEVICE_REGISTERED.store(true, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\npub fn initialize(driver_context: *mut c_void, device_ptr: *mut c_void) -> bool {\r\n    if driver_context.is_null() {\r\n        return false;\r\n    }\r\n\r\n    let ctx = driver_context.cast::<DriverContext>();\r\n    let mut err = 0;\r\n\r\n    let get_iface = unsafe { (*(*ctx).vtable).get_generic_interface };\r\n\r\n    let host = get_iface(\r\n        ctx,\r\n        IVR_SERVER_DRIVER_HOST_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let driver_input = get_iface(\r\n        ctx,\r\n        IVR_DRIVER_INPUT_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let properties = get_iface(\r\n        ctx,\r\n        IVR_PROPERTIES_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    let driver_log = get_iface(\r\n        ctx,\r\n        IVR_DRIVER_LOG_VERSION.as_ptr().cast(),\r\n        &mut err as *mut EvRInitError,\r\n    );\r\n\r\n    SERVER_HOST_IFACE.store(host, Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(driver_input, Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(properties, Ordering::SeqCst);\r\n    DRIVER_LOG_IFACE.store(driver_log, Ordering::SeqCst);\r\n\r\n    let ok = !host.is_null() && !driver_input.is_null() && !properties.is_null();\r\n    if !ok {\r\n        return false;\r\n    }\r\n\r\n    log_driver_message(&format!(\r\n        \"[big_haptic_driver] OpenVR init ok; sizeof(VREvent)={}, sizeof(VREventHapticVibration)={}\",\r\n        core::mem::size_of::<VREvent>(),\r\n        core::mem::size_of::<VREventHapticVibration>()\r\n    ));\r\n\r\n    register_tracked_device_if_needed(device_ptr);\r\n    true\r\n}\r\n\r\npub fn cleanup() {\r\n    DEVICE_REGISTERED.store(false, Ordering::SeqCst);\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    SYSTEM_CLICK_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    A_CLICK_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    TRIGGER_CLICK_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    TRIGGER_VALUE_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    POSE_RAW_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    POSE_TIP_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    SERVER_HOST_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DRIVER_INPUT_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    PROPERTIES_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n    DRIVER_LOG_IFACE.store(core::ptr::null_mut(), Ordering::SeqCst);\r\n}\r\n\r\npub fn route_haptic_events() {\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    loop {\r\n        let mut event = VREvent::default();\r\n        let ok = unsafe {\r\n            ((*(*host).vtable).poll_next_event)(\r\n                host,\r\n                &mut event as *mut VREvent,\r\n                core::mem::size_of::<VREvent>() as u32,\r\n            )\r\n        };\r\n\r\n        if !ok {\r\n            break;\r\n        }\r\n\r\n        if event.event_type != VREVENT_INPUT_HAPTIC_VIBRATION {\r\n            continue;\r\n        }\r\n\r\n        let hv = unsafe { event.data.haptic_vibration };\r\n        let seq = HAPTIC_EVENT_SEQ.fetch_add(1, Ordering::SeqCst).saturating_add(1);\r\n\r\n        log_driver_message(&format!(\r\n            \"[big_haptic_driver] haptic event #{}: dev={} age_s={:.6} container={} component={} duration_s={:.6} (0x{:08x}) freq={:.3} amp={:.6} (0x{:08x})\",\r\n            seq,\r\n            event.tracked_device_index,\r\n            event.event_age_seconds,\r\n            hv.container_handle,\r\n            hv.component_handle,\r\n            hv.duration_seconds,\r\n            hv.duration_seconds.to_bits(),\r\n            hv.frequency,\r\n            hv.amplitude,\r\n            hv.amplitude.to_bits()\r\n        ));\r\n\r\n        if hv.amplitude > 0.0 && hv.duration_seconds <= 0.0 {\r\n            log_driver_message(&format!(\r\n                \"[big_haptic_driver] note: OpenVR sent duration<=0; per OpenVR guidance driver will emit one finite pulse (freq={:.3})\",\r\n                hv.frequency\r\n            ));\r\n        }\r\n\r\n        let container = DEVICE_CONTAINER.load(Ordering::SeqCst);\r\n        if container != 0 && hv.container_handle != 0 && hv.container_handle != container {\r\n            log_driver_message(&format!(\r\n                \"[big_haptic_driver] skipped haptic event: container mismatch event={} driver={}\",\r\n                hv.container_handle, container\r\n            ));\r\n            continue;\r\n        }\r\n\r\n        let haptic_component = HAPTIC_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n        if haptic_component != 0 && hv.component_handle != 0 && hv.component_handle != haptic_component {\r\n            log_driver_message(&format!(\r\n                \"[big_haptic_driver] skipped haptic event: component mismatch event={} driver={}\",\r\n                hv.component_handle, haptic_component\r\n            ));\r\n            continue;\r\n        }\r\n\r\n        log_driver_message(\"[big_haptic_driver] forwarding haptic request to HID layer\");\r\n\r\n        haptics::handle_haptic_request(haptics::HapticVibrationRequest {\r\n            amplitude: hv.amplitude,\r\n            duration_seconds: hv.duration_seconds,\r\n            frequency: hv.frequency,\r\n        });\r\n    }\r\n}\r\n\r\npub fn push_pose_update(object_id: u32, pose: *const c_void, pose_size: u32) {\r\n    let Some(host) = get_server_host() else {\r\n        return;\r\n    };\r\n\r\n    if pose.is_null() || pose_size == 0 {\r\n        return;\r\n    }\r\n\r\n    unsafe {\r\n        ((*(*host).vtable).tracked_device_pose_updated)(host, object_id, pose, pose_size);\r\n    }\r\n}\r\n\r\npub fn sample_hmd_pose() -> Option<RuntimeHmdPose> {\r\n    let host = get_server_host()?;\r\n\r\n    let mut poses = [TrackedDevicePose::default(); 1];\r\n    unsafe {\r\n        ((*(*host).vtable).get_raw_tracked_device_poses)(\r\n            host,\r\n            0.0,\r\n            poses.as_mut_ptr(),\r\n            poses.len() as u32,\r\n        );\r\n    }\r\n\r\n    let hmd = poses[TRACKED_DEVICE_INDEX_HMD as usize];\r\n    if !hmd.b_device_is_connected || !hmd.b_pose_is_valid {\r\n        return None;\r\n    }\r\n\r\n    Some(RuntimeHmdPose {\r\n        position_m: [\r\n            hmd.m_device_to_absolute_tracking.m[0][3],\r\n            hmd.m_device_to_absolute_tracking.m[1][3],\r\n            hmd.m_device_to_absolute_tracking.m[2][3],\r\n        ],\r\n        rotation: [\r\n            [\r\n                hmd.m_device_to_absolute_tracking.m[0][0],\r\n                hmd.m_device_to_absolute_tracking.m[0][1],\r\n                hmd.m_device_to_absolute_tracking.m[0][2],\r\n            ],\r\n            [\r\n                hmd.m_device_to_absolute_tracking.m[1][0],\r\n                hmd.m_device_to_absolute_tracking.m[1][1],\r\n                hmd.m_device_to_absolute_tracking.m[1][2],\r\n            ],\r\n            [\r\n                hmd.m_device_to_absolute_tracking.m[2][0],\r\n                hmd.m_device_to_absolute_tracking.m[2][1],\r\n                hmd.m_device_to_absolute_tracking.m[2][2],\r\n            ],\r\n        ],\r\n    })\r\n}\r\n\r\npub fn update_pose_components() {\r\n    let Some(input) = get_driver_input() else {\r\n        return;\r\n    };\r\n\r\n    let raw_handle = POSE_RAW_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n    let tip_handle = POSE_TIP_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n\r\n    let raw_offset = HmdMatrix34 {\r\n        m: [\r\n            [1.0, 0.0, 0.0, 0.0],\r\n            [0.0, 1.0, 0.0, 0.0],\r\n            [0.0, 0.0, 1.0, 0.0],\r\n        ],\r\n    };\r\n    let tip_offset = HmdMatrix34 {\r\n        m: [\r\n            [1.0, 0.0, 0.0, 0.0],\r\n            [0.0, 1.0, 0.0, 0.0],\r\n            [0.0, 0.0, 1.0, -0.04],\r\n        ],\r\n    };\r\n\r\n    if raw_handle != 0 {\r\n        unsafe {\r\n            ((*(*input).vtable).update_pose_component)(input, raw_handle, &raw_offset, 0.0);\r\n        }\r\n    }\r\n\r\n    if tip_handle != 0 {\r\n        unsafe {\r\n            ((*(*input).vtable).update_pose_component)(input, tip_handle, &tip_offset, 0.0);\r\n        }\r\n    }\r\n}\r\n\r\npub fn update_input_components() {\r\n    let Some(input) = get_driver_input() else {\r\n        return;\r\n    };\r\n\r\n    let timestamp = 0.0;\r\n    let system_click_handle = SYSTEM_CLICK_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n    let a_click_handle = A_CLICK_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n    let trigger_click_handle = TRIGGER_CLICK_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n    let trigger_value_handle = TRIGGER_VALUE_COMPONENT_HANDLE.load(Ordering::SeqCst);\r\n\r\n    if system_click_handle != 0 {\r\n        unsafe {\r\n            ((*(*input).vtable).update_boolean_component)(input, system_click_handle, false, timestamp);\r\n        }\r\n    }\r\n\r\n    if a_click_handle != 0 {\r\n        unsafe {\r\n            ((*(*input).vtable).update_boolean_component)(input, a_click_handle, false, timestamp);\r\n        }\r\n    }\r\n\r\n    if trigger_click_handle != 0 {\r\n        unsafe {\r\n            ((*(*input).vtable).update_boolean_component)(input, trigger_click_handle, false, timestamp);\r\n        }\r\n    }\r\n\r\n    if trigger_value_handle != 0 {\r\n        unsafe {\r\n            ((*(*input).vtable).update_scalar_component)(input, trigger_value_handle, 0.0, timestamp);\r\n        }\r\n    }\r\n}\r\n\r\npub fn activate_device(object_id: u32) -> bool {\r\n    let Some(props) = get_properties() else {\r\n        return false;\r\n    };\r\n\r\n    let container = unsafe { ((*(*props).vtable).tracked_device_to_property_container)(props, object_id) };\r\n    DEVICE_CONTAINER.store(container, Ordering::SeqCst);\r\n\r\n    write_string_property(props, container, PROP_MODEL_NUMBER_STRING, DEVICE_MODEL_CSTR);\r\n    write_string_property(props, container, PROP_SERIAL_NUMBER_STRING, DEVICE_SERIAL_CSTR);\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_TRACKING_SYSTEM_NAME_STRING,\r\n        DEVICE_TRACKING_SYSTEM_NAME_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_REGISTERED_DEVICE_TYPE_STRING,\r\n        DEVICE_REGISTERED_TYPE_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_INPUT_PROFILE_PATH_STRING,\r\n        DEVICE_INPUT_PROFILE_CSTR,\r\n    );\r\n    write_string_property(\r\n        props,\r\n        container,\r\n        PROP_CONTROLLER_TYPE_STRING,\r\n        DEVICE_CONTROLLER_TYPE_CSTR,\r\n    );\r\n    write_bool_property(props, container, PROP_HAS_CONTROLLER_COMPONENT_BOOL, true);\r\n    write_int32_property(props, container, PROP_CONTROLLER_ROLE_HINT_INT32, 2);\r\n\r\n    if let Some(input) = get_driver_input() {\r\n        let mut system_click_handle = 0_u64;\r\n        let rc_system = unsafe {\r\n            ((*(*input).vtable).create_boolean_component)(\r\n                input,\r\n                container,\r\n                DEVICE_SYSTEM_CLICK_PATH_CSTR.as_ptr().cast(),\r\n                &mut system_click_handle as *mut u64,\r\n            )\r\n        };\r\n        if rc_system == 0 {\r\n            SYSTEM_CLICK_COMPONENT_HANDLE.store(system_click_handle, Ordering::SeqCst);\r\n        }\r\n\r\n        let mut a_click_handle = 0_u64;\r\n        let rc_a = unsafe {\r\n            ((*(*input).vtable).create_boolean_component)(\r\n                input,\r\n                container,\r\n                DEVICE_A_CLICK_PATH_CSTR.as_ptr().cast(),\r\n                &mut a_click_handle as *mut u64,\r\n            )\r\n        };\r\n        if rc_a == 0 {\r\n            A_CLICK_COMPONENT_HANDLE.store(a_click_handle, Ordering::SeqCst);\r\n        }\r\n\r\n        let mut trigger_click_handle = 0_u64;\r\n        let rc_trigger_click = unsafe {\r\n            ((*(*input).vtable).create_boolean_component)(\r\n                input,\r\n                container,\r\n                DEVICE_TRIGGER_CLICK_PATH_CSTR.as_ptr().cast(),\r\n                &mut trigger_click_handle as *mut u64,\r\n            )\r\n        };\r\n        if rc_trigger_click == 0 {\r\n            TRIGGER_CLICK_COMPONENT_HANDLE.store(trigger_click_handle, Ordering::SeqCst);\r\n        }\r\n\r\n        let mut trigger_value_handle = 0_u64;\r\n        let rc_trigger_value = unsafe {\r\n            ((*(*input).vtable).create_scalar_component)(\r\n                input,\r\n                container,\r\n                DEVICE_TRIGGER_VALUE_PATH_CSTR.as_ptr().cast(),\r\n                &mut trigger_value_handle as *mut u64,\r\n                VR_SCALAR_TYPE_ABSOLUTE,\r\n                VR_SCALAR_UNITS_NORMALIZED_ONE_SIDED,\r\n            )\r\n        };\r\n        if rc_trigger_value == 0 {\r\n            TRIGGER_VALUE_COMPONENT_HANDLE.store(trigger_value_handle, Ordering::SeqCst);\r\n        }\r\n\r\n        let mut handle = 0_u64;\r\n        let rc = unsafe {\r\n            ((*(*input).vtable).create_haptic_component)(\r\n                input,\r\n                container,\r\n                DEVICE_HAPTIC_PATH_CSTR.as_ptr().cast(),\r\n                &mut handle as *mut u64,\r\n            )\r\n        };\r\n\r\n        if rc == 0 {\r\n            HAPTIC_COMPONENT_HANDLE.store(handle, Ordering::SeqCst);\r\n        }\r\n\r\n        let mut pose_raw_handle = 0_u64;\r\n        let rc_raw = unsafe {\r\n            ((*(*input).vtable).create_pose_component)(\r\n                input,\r\n                container,\r\n                DEVICE_POSE_RAW_PATH_CSTR.as_ptr().cast(),\r\n                &mut pose_raw_handle as *mut u64,\r\n            )\r\n        };\r\n        if rc_raw == 0 {\r\n            POSE_RAW_COMPONENT_HANDLE.store(pose_raw_handle, Ordering::SeqCst);\r\n        }\r\n\r\n        let mut pose_tip_handle = 0_u64;\r\n        let rc_tip = unsafe {\r\n            ((*(*input).vtable).create_pose_component)(\r\n                input,\r\n                container,\r\n                DEVICE_POSE_TIP_PATH_CSTR.as_ptr().cast(),\r\n                &mut pose_tip_handle as *mut u64,\r\n            )\r\n        };\r\n        if rc_tip == 0 {\r\n            POSE_TIP_COMPONENT_HANDLE.store(pose_tip_handle, Ordering::SeqCst);\r\n        }\r\n\r\n        update_input_components();\r\n        update_pose_components();\r\n    }\r\n\r\n    true\r\n}\r\n\r\npub fn deactivate_device() {\r\n    DEVICE_CONTAINER.store(0, Ordering::SeqCst);\r\n    HAPTIC_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    SYSTEM_CLICK_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\n    A_CLICK_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\n    TRIGGER_CLICK_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\n    TRIGGER_VALUE_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\n    POSE_RAW_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n    POSE_TIP_COMPONENT_HANDLE.store(0, Ordering::SeqCst);\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":828,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\openvr_scaffold.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_scaffold.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_scaffold.rs","scheme":"file"},"requestId":"request_253d5ff6-6f1d-4b2e-83a2-f98328aa01b9","epoch":1705,"edits":[{"text":"//! OpenVR ABI/vtable scaffolding for the Big Haptic Driver.\r\n//!\r\n//! This module owns C ABI exports and interface routing for SteamVR-facing\r\n//! provider/device entry points, while delegating runtime integration\r\n//! (device registration, component setup, and event routing) to\r\n//! `openvr_runtime`.\r\n\r\nuse core::ffi::{c_void, CStr};\r\nuse core::sync::atomic::{AtomicBool, AtomicU32, Ordering};\r\nuse std::os::raw::c_char;\r\n\r\nuse crate::haptics;\r\nuse crate::openvr_runtime;\r\n\r\nconst SERVER_PROVIDER_INTERFACE: &str = \"IServerTrackedDeviceProvider_004\";\r\nconst SERVER_PROVIDER_INTERFACE_ALT: &str = \"IServerTrackedDeviceProvider_005\";\r\nconst TRACKED_DEVICE_INTERFACE: &str = \"ITrackedDeviceServerDriver_005\";\r\nconst VR_INIT_ERROR_DRIVER_FAILED: EvRInitError = 200;\r\n\r\n#[repr(i32)]\r\nenum DriverReturnCode {\r\n    None = 0,\r\n    Unknown = 1,\r\n    InitInterfaceNotFound = 105,\r\n}\r\n\r\ntype EvRInitError = i32;\r\nconst VR_INIT_ERROR_NONE: EvRInitError = 0;\r\nconst TRACKING_RESULT_RUNNING_OK: i32 = 200;\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nstruct DriverPoseQuaternion {\r\n    w: f64,\r\n    x: f64,\r\n    y: f64,\r\n    z: f64,\r\n}\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy)]\r\nstruct DriverPose {\r\n    pose_time_offset: f64,\r\n    q_world_from_driver_rotation: DriverPoseQuaternion,\r\n    vec_world_from_driver_translation: [f64; 3],\r\n    q_driver_from_head_rotation: DriverPoseQuaternion,\r\n    vec_driver_from_head_translation: [f64; 3],\r\n    vec_position: [f64; 3],\r\n    vec_velocity: [f64; 3],\r\n    vec_acceleration: [f64; 3],\r\n    q_rotation: DriverPoseQuaternion,\r\n    vec_angular_velocity: [f64; 3],\r\n    vec_angular_acceleration: [f64; 3],\r\n    result: i32,\r\n    pose_is_valid: bool,\r\n    will_drift_in_yaw: bool,\r\n    should_apply_head_model: bool,\r\n    device_is_connected: bool,\r\n}\r\n\r\nconst IDENTITY_QUATERNION: DriverPoseQuaternion = DriverPoseQuaternion {\r\n    w: 1.0,\r\n    x: 0.0,\r\n    y: 0.0,\r\n    z: 0.0,\r\n};\r\n\r\nfn connected_controller_pose() -> DriverPose {\r\n    if let Some(hmd_pose) = openvr_runtime::sample_hmd_pose() {\r\n        let rotation = hmd_pose.rotation;\r\n        let position = hmd_pose.position_m;\r\n\r\n        let q_rotation = quaternion_from_rotation_matrix(rotation);\r\n\r\n        return DriverPose {\r\n            pose_time_offset: 0.0,\r\n            q_world_from_driver_rotation: IDENTITY_QUATERNION,\r\n            vec_world_from_driver_translation: [0.0, 0.0, 0.0],\r\n            q_driver_from_head_rotation: IDENTITY_QUATERNION,\r\n            vec_driver_from_head_translation: [0.0, 0.0, 0.0],\r\n            vec_position: [position[0] as f64, position[1] as f64, position[2] as f64],\r\n            vec_velocity: [0.0, 0.0, 0.0],\r\n            vec_acceleration: [0.0, 0.0, 0.0],\r\n            q_rotation,\r\n            vec_angular_velocity: [0.0, 0.0, 0.0],\r\n            vec_angular_acceleration: [0.0, 0.0, 0.0],\r\n            result: TRACKING_RESULT_RUNNING_OK,\r\n            pose_is_valid: true,\r\n            will_drift_in_yaw: false,\r\n            should_apply_head_model: false,\r\n            device_is_connected: true,\r\n        };\r\n    }\r\n\r\n    DriverPose {\r\n        pose_time_offset: 0.0,\r\n        q_world_from_driver_rotation: IDENTITY_QUATERNION,\r\n        vec_world_from_driver_translation: [0.0, 0.0, 0.0],\r\n        q_driver_from_head_rotation: IDENTITY_QUATERNION,\r\n        vec_driver_from_head_translation: [0.0, 0.0, 0.0],\r\n        vec_position: [0.0, 1.2, -0.4],\r\n        vec_velocity: [0.0, 0.0, 0.0],\r\n        vec_acceleration: [0.0, 0.0, 0.0],\r\n        q_rotation: IDENTITY_QUATERNION,\r\n        vec_angular_velocity: [0.0, 0.0, 0.0],\r\n        vec_angular_acceleration: [0.0, 0.0, 0.0],\r\n        result: TRACKING_RESULT_RUNNING_OK,\r\n        pose_is_valid: true,\r\n        will_drift_in_yaw: false,\r\n        should_apply_head_model: false,\r\n        device_is_connected: true,\r\n    }\r\n}\r\n\r\nfn quaternion_from_rotation_matrix(m: [[f32; 3]; 3]) -> DriverPoseQuaternion {\r\n    let m00 = m[0][0] as f64;\r\n    let m01 = m[0][1] as f64;\r\n    let m02 = m[0][2] as f64;\r\n    let m10 = m[1][0] as f64;\r\n    let m11 = m[1][1] as f64;\r\n    let m12 = m[1][2] as f64;\r\n    let m20 = m[2][0] as f64;\r\n    let m21 = m[2][1] as f64;\r\n    let m22 = m[2][2] as f64;\r\n\r\n    let trace = m00 + m11 + m22;\r\n    if trace > 0.0 {\r\n        let s = (trace + 1.0).sqrt() * 2.0;\r\n        return DriverPoseQuaternion {\r\n            w: 0.25 * s,\r\n            x: (m21 - m12) / s,\r\n            y: (m02 - m20) / s,\r\n            z: (m10 - m01) / s,\r\n        };\r\n    }\r\n\r\n    if m00 > m11 && m00 > m22 {\r\n        let s = (1.0 + m00 - m11 - m22).sqrt() * 2.0;\r\n        return DriverPoseQuaternion {\r\n            w: (m21 - m12) / s,\r\n            x: 0.25 * s,\r\n            y: (m01 + m10) / s,\r\n            z: (m02 + m20) / s,\r\n        };\r\n    }\r\n\r\n    if m11 > m22 {\r\n        let s = (1.0 + m11 - m00 - m22).sqrt() * 2.0;\r\n        return DriverPoseQuaternion {\r\n            w: (m02 - m20) / s,\r\n            x: (m01 + m10) / s,\r\n            y: 0.25 * s,\r\n            z: (m12 + m21) / s,\r\n        };\r\n    }\r\n\r\n    let s = (1.0 + m22 - m00 - m11).sqrt() * 2.0;\r\n    DriverPoseQuaternion {\r\n        w: (m10 - m01) / s,\r\n        x: (m02 + m20) / s,\r\n        y: (m12 + m21) / s,\r\n        z: 0.25 * s,\r\n    }\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerTrackedDeviceProviderVTable {\r\n    init: extern \"C\" fn(*mut ServerTrackedDeviceProvider, *mut c_void) -> EvRInitError,\r\n    cleanup: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    get_interface_versions:\r\n        extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> *const *const c_char,\r\n    run_frame: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    should_block_standby_mode: extern \"C\" fn(*mut ServerTrackedDeviceProvider) -> bool,\r\n    enter_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n    leave_standby: extern \"C\" fn(*mut ServerTrackedDeviceProvider),\r\n}\r\n\r\n#[repr(C)]\r\nstruct TrackedDeviceServerDriverVTable {\r\n    activate: extern \"C\" fn(*mut TrackedDeviceServerDriver, u32) -> EvRInitError,\r\n    deactivate: extern \"C\" fn(*mut TrackedDeviceServerDriver),\r\n    enter_standby: extern \"C\" fn(*mut TrackedDeviceServerDriver),\r\n    get_component:\r\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char) -> *mut c_void,\r\n    debug_request:\r\n        extern \"C\" fn(*mut TrackedDeviceServerDriver, *const c_char, *mut c_char, u32),\r\n    get_pose: extern \"C\" fn(*mut TrackedDeviceServerDriver) -> DriverPose,\r\n}\r\n\r\n#[repr(C)]\r\nstruct ServerTrackedDeviceProvider {\r\n    vtable: *const ServerTrackedDeviceProviderVTable,\r\n}\r\nunsafe impl Sync for ServerTrackedDeviceProvider {}\r\n\r\n#[repr(C)]\r\nstruct TrackedDeviceServerDriver {\r\n    vtable: *const TrackedDeviceServerDriverVTable,\r\n}\r\nunsafe impl Sync for TrackedDeviceServerDriver {}\r\n\r\nstatic DEVICE_INDEX: AtomicU32 = AtomicU32::new(u32::MAX);\r\nstatic DEVICE_ACTIVE: AtomicBool = AtomicBool::new(false);\r\n\r\nextern \"C\" fn provider_init(\r\n    _this: *mut ServerTrackedDeviceProvider,\r\n    driver_context: *mut c_void,\r\n) -> EvRInitError {\r\n    let ok = openvr_runtime::initialize(\r\n        driver_context,\r\n        (&DEVICE as *const TrackedDeviceServerDriver).cast_mut().cast(),\r\n    );\r\n    if !ok {\r\n        return VR_INIT_ERROR_DRIVER_FAILED;\r\n    }\r\n\r\n    VR_INIT_ERROR_NONE\r\n}\r\n\r\nextern \"C\" fn provider_cleanup(_this: *mut ServerTrackedDeviceProvider) {\r\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\r\n    openvr_runtime::cleanup();\r\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn provider_get_interface_versions(\r\n    _this: *mut ServerTrackedDeviceProvider,\r\n) -> *const *const c_char {\r\n    openvr_runtime::interface_versions_ptr()\r\n}\r\n\r\nextern \"C\" fn provider_run_frame(_this: *mut ServerTrackedDeviceProvider) {\r\n    if DEVICE_ACTIVE.load(Ordering::SeqCst) {\r\n        let object_id = DEVICE_INDEX.load(Ordering::SeqCst);\r\n        if object_id != u32::MAX {\r\n            let pose = connected_controller_pose();\r\n            openvr_runtime::push_pose_update(\r\n                object_id,\r\n                (&pose as *const DriverPose).cast(),\r\n                core::mem::size_of::<DriverPose>() as u32,\r\n            );\r\n            openvr_runtime::update_input_components();\n            openvr_runtime::update_pose_components();\r\n        }\r\n    }\r\n\r\n    openvr_runtime::route_haptic_events();\r\n    haptics::on_frame();\r\n}\r\n\r\nextern \"C\" fn provider_should_block_standby_mode(_this: *mut ServerTrackedDeviceProvider) -> bool {\r\n    false\r\n}\r\n\r\nextern \"C\" fn provider_enter_standby(_this: *mut ServerTrackedDeviceProvider) {}\r\n\r\nextern \"C\" fn provider_leave_standby(_this: *mut ServerTrackedDeviceProvider) {}\r\n\r\nextern \"C\" fn device_activate(_this: *mut TrackedDeviceServerDriver, object_id: u32) -> EvRInitError {\r\n    DEVICE_INDEX.store(object_id, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(true, Ordering::SeqCst);\r\n\r\n    if !openvr_runtime::activate_device(object_id) {\r\n        return VR_INIT_ERROR_DRIVER_FAILED;\r\n    }\r\n\r\n    let pose = connected_controller_pose();\r\n    openvr_runtime::push_pose_update(\r\n        object_id,\r\n        (&pose as *const DriverPose).cast(),\r\n        core::mem::size_of::<DriverPose>() as u32,\r\n    );\r\n\r\n    VR_INIT_ERROR_NONE\r\n}\r\n\r\nextern \"C\" fn device_deactivate(_this: *mut TrackedDeviceServerDriver) {\r\n    DEVICE_INDEX.store(u32::MAX, Ordering::SeqCst);\r\n    DEVICE_ACTIVE.store(false, Ordering::SeqCst);\r\n    openvr_runtime::deactivate_device();\r\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn device_enter_standby(_this: *mut TrackedDeviceServerDriver) {\r\n    let _ = haptics::send_stop();\r\n}\r\n\r\nextern \"C\" fn device_get_component(\r\n    _this: *mut TrackedDeviceServerDriver,\r\n    _component_name: *const c_char,\r\n) -> *mut c_void {\r\n    core::ptr::null_mut()\r\n}\r\n\r\nextern \"C\" fn device_debug_request(\r\n    _this: *mut TrackedDeviceServerDriver,\r\n    _request: *const c_char,\r\n    response: *mut c_char,\r\n    response_size: u32,\r\n) {\r\n    if response.is_null() || response_size == 0 {\r\n        return;\r\n    }\r\n    unsafe {\r\n        *response = 0;\r\n    }\r\n}\r\n\r\nextern \"C\" fn device_get_pose(_this: *mut TrackedDeviceServerDriver) -> DriverPose {\r\n    connected_controller_pose()\r\n}\r\n\r\nstatic PROVIDER_VTABLE: ServerTrackedDeviceProviderVTable = ServerTrackedDeviceProviderVTable {\r\n    init: provider_init,\r\n    cleanup: provider_cleanup,\r\n    get_interface_versions: provider_get_interface_versions,\r\n    run_frame: provider_run_frame,\r\n    should_block_standby_mode: provider_should_block_standby_mode,\r\n    enter_standby: provider_enter_standby,\r\n    leave_standby: provider_leave_standby,\r\n};\r\n\r\nstatic DEVICE_VTABLE: TrackedDeviceServerDriverVTable = TrackedDeviceServerDriverVTable {\r\n    activate: device_activate,\r\n    deactivate: device_deactivate,\r\n    enter_standby: device_enter_standby,\r\n    get_component: device_get_component,\r\n    debug_request: device_debug_request,\r\n    get_pose: device_get_pose,\r\n};\r\n\r\nstatic PROVIDER: ServerTrackedDeviceProvider = ServerTrackedDeviceProvider {\r\n    vtable: &PROVIDER_VTABLE,\r\n};\r\n\r\nstatic DEVICE: TrackedDeviceServerDriver = TrackedDeviceServerDriver {\r\n    vtable: &DEVICE_VTABLE,\r\n};\r\n\r\nfn set_return_code(out: *mut i32, code: DriverReturnCode) {\r\n    if !out.is_null() {\r\n        unsafe {\r\n            *out = code as i32;\r\n        }\r\n    }\r\n}\r\n\r\nfn read_interface_name(ptr: *const i8) -> Option<&'static str> {\r\n    if ptr.is_null() {\r\n        return None;\r\n    }\r\n\r\n    let cstr = unsafe { CStr::from_ptr(ptr) };\r\n    let Ok(name) = cstr.to_str() else {\r\n        return None;\r\n    };\r\n\r\n    if name == SERVER_PROVIDER_INTERFACE {\r\n        return Some(SERVER_PROVIDER_INTERFACE);\r\n    }\r\n    if name == SERVER_PROVIDER_INTERFACE_ALT {\r\n        return Some(SERVER_PROVIDER_INTERFACE);\r\n    }\r\n    if name == TRACKED_DEVICE_INTERFACE {\r\n        return Some(TRACKED_DEVICE_INTERFACE);\r\n    }\r\n\r\n    None\r\n}\r\n\r\n/// Resolves OpenVR interface requests to static provider/device instances.\r\n///\r\n/// On match, writes `DriverReturnCode::None` to `p_return_code` and returns a\r\n/// stable interface pointer. On failure, writes an error code and returns null.\r\npub fn hmd_driver_factory(\r\n    p_interface_name: *const c_char,\r\n    p_return_code: *mut i32,\r\n) -> *mut c_void {\r\n    let Some(interface_name) = read_interface_name(p_interface_name) else {\r\n        set_return_code(p_return_code, DriverReturnCode::InitInterfaceNotFound);\r\n        return core::ptr::null_mut();\r\n    };\r\n\r\n    if interface_name == SERVER_PROVIDER_INTERFACE {\r\n        set_return_code(p_return_code, DriverReturnCode::None);\r\n        return (&PROVIDER as *const ServerTrackedDeviceProvider)\r\n            .cast_mut()\r\n            .cast();\r\n    }\r\n\r\n    if interface_name == TRACKED_DEVICE_INTERFACE {\r\n        set_return_code(p_return_code, DriverReturnCode::None);\r\n        return (&DEVICE as *const TrackedDeviceServerDriver)\r\n            .cast_mut()\r\n            .cast();\r\n    }\r\n\r\n    set_return_code(p_return_code, DriverReturnCode::Unknown);\r\n    core::ptr::null_mut()\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":403,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\input\\big_haptic_profile.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/big_haptic_profile.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/big_haptic_profile.json","scheme":"file"},"requestId":"request_253d5ff6-6f1d-4b2e-83a2-f98328aa01b9","epoch":1708,"edits":[{"text":"{\r\n  \"jsonid\": \"input_profile\",\r\n  \"controller_type\": \"big_haptic_driver\",\r\n  \"device_class\": \"Controller\",\n  \"resource_root\": \"big_haptic_driver\",\n  \"driver_name\": \"big_haptic_driver\",\n  \"input_bindingui_mode\": \"controller_handed\",\r\n  \"input_bindingui_left\": {\n    \"image\": \"\"\n  },\n  \"input_bindingui_right\": {\n    \"image\": \"\"\n  },\n  \"legacy_binding\": \"{system}/legacy_bindings_generic.json\",\r\n  \"input_source\": {\r\n    \"/input/system/click\": {\n      \"type\": \"button\",\n      \"binding_image_point\": [0.0, 0.0],\n      \"order\": 1\n    },\n    \"/input/a/click\": {\n      \"type\": \"button\",\n      \"binding_image_point\": [0.0, 0.0],\n      \"order\": 2\n    },\n    \"/input/trigger/value\": {\n      \"type\": \"trigger\",\n      \"binding_image_point\": [0.0, 0.0],\n      \"order\": 3\n    },\n    \"/input/trigger/click\": {\n      \"type\": \"button\",\n      \"binding_image_point\": [0.0, 0.0],\n      \"order\": 4\n    },\n    \"/pose/raw\": {\n      \"type\": \"pose\",\n      \"binding_image_point\": [0.0, 0.0],\n      \"order\": 5\n    },\n    \"/output/haptic\": {\r\n      \"type\": \"vibration\",\r\n      \"binding_image_point\": [0.0, 0.0],\n      \"order\": 6\n    }\r\n  },\r\n  \"default_bindings\": [\r\n    {\r\n      \"app_key\": \"openvr.component.vrcompositor\",\r\n      \"binding_url\": \"vrcompositor_bindings_big_haptic_driver.json\"\r\n    }\r\n  ]\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":55,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\resources\\input\\vrcompositor_bindings_big_haptic_driver.json","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/vrcompositor_bindings_big_haptic_driver.json","scheme":"file"},"requestId":"request_253d5ff6-6f1d-4b2e-83a2-f98328aa01b9","epoch":1711,"edits":[{"text":"{\r\n  \"action_manifest_version\": 0,\r\n  \"alias_info\": {},\n  \"app_key\": \"openvr.component.vrcompositor\",\r\n  \"bindings\": {\r\n    \"/actions/lasermouse\": {\r\n      \"poses\": [\r\n        {\r\n          \"output\": \"/actions/lasermouse/in/Pointer\",\r\n          \"path\": \"/user/hand/right/pose/raw\"\n        }\r\n      ],\n      \"sources\": [\n        {\n          \"inputs\": {\n            \"click\": {\n              \"output\": \"/actions/lasermouse/in/leftclick\"\n            }\n          },\n          \"mode\": \"button\",\n          \"path\": \"/user/hand/right/input/a/click\"\n        },\n        {\n          \"inputs\": {\n            \"click\": {\n              \"output\": \"/actions/lasermouse/in/leftclick\"\n            }\n          },\n          \"mode\": \"button\",\n          \"parameters\": {\n            \"click_activate_threshold\": \"0.5\",\n            \"click_deactivate_threshold\": \"0.45\"\n          },\n          \"path\": \"/user/hand/right/input/trigger/value\"\n        }\n      ],\n      \"haptics\": [\n        {\n          \"output\": \"/actions/lasermouse/out/haptic\",\n          \"path\": \"/user/hand/right/output/haptic\"\n        }\n      ]\r\n    },\n    \"/actions/system\": {\n      \"sources\": [\n        {\n          \"inputs\": {\n            \"single\": {\n              \"output\": \"/actions/system/in/opendashboard\"\n            }\n          },\n          \"mode\": \"complex_button\",\n          \"path\": \"/user/hand/right/input/system/click\"\n        }\n      ]\n    }\r\n  },\n  \"category\": \"steamvr_input\",\n  \"controller_type\": \"big_haptic_driver\",\n  \"description\": \"\",\n  \"name\": \"Default VR Dashboard bindings for Big Haptic Driver\",\n  \"options\": {},\n  \"simulated_actions\": []\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":66,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_52caa11c-9347-4b92-bae5-d4d2c73cbf1f","epoch":1715,"edits":[{"text":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse core::sync::atomic::{AtomicU64, Ordering};\r\nuse std::sync::{mpsc, Mutex, OnceLock};\nuse std::time::Instant;\r\nuse std::time::Duration;\r\nuse std::thread;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic STOP_DEADLINE_MS: AtomicU64 = AtomicU64::new(0);\r\nstatic START_INSTANT: OnceLock<Instant> = OnceLock::new();\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\nstatic HID_TX: OnceLock<mpsc::Sender<HapticPacket>> = OnceLock::new();\n\r\nconst OPENVR_MIN_FREQUENCY_HZ: f32 = 1_000_000.0 / 65_535.0;\r\nconst OPENVR_MAX_FREQUENCY_HZ: f32 = 1_000_000.0 / 300.0;\r\nconst OPENVR_MAX_DURATION_SECONDS: f32 = 10.0;\r\nconst HARDWARE_MIN_PULSE_SECONDS: f32 = 0.02;\r\nconst HID_SEND_ATTEMPTS: usize = 3;\r\nconst HID_SEND_RETRY_DELAY_MS: u64 = 2;\r\n\r\nfn log_haptics(message: &str) {\r\n    crate::openvr_runtime::log_driver_message(message);\r\n}\r\n\r\nfn send_packet(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    let _hid_guard = HID_SEND_LOCK.lock().ok();\r\n\r\n    let mut last_error = None;\r\n\r\n    for attempt in 1..=HID_SEND_ATTEMPTS {\r\n        match hid_bridge::HidBridge::new()\r\n            .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n        {\r\n            Ok(()) => return Ok(()),\r\n            Err(err) => {\r\n                last_error = Some(err);\r\n                if attempt < HID_SEND_ATTEMPTS {\r\n                    thread::sleep(Duration::from_millis(HID_SEND_RETRY_DELAY_MS));\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    let err = last_error.expect(\"HID send attempts exhausted without captured error\");\r\n    log_haptics(&format!(\r\n        \"haptics: HID send failed after {} attempt(s); cmd=0x{:02X}, intensity={}, arg_lo={}, arg_hi={}, err={}\",\r\n        HID_SEND_ATTEMPTS,\r\n        packet.command_id,\r\n        packet.intensity,\r\n        packet.arg_lo,\r\n        packet.arg_hi,\r\n        err\r\n    ));\r\n    Err(err)\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    send_packet(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn now_ms() -> u64 {\r\n    let start = START_INSTANT.get_or_init(Instant::now);\r\n    let ms = Instant::now().duration_since(*start).as_millis();\r\n    ms.min(u128::from(u64::MAX)) as u64\r\n}\r\n\r\nfn schedule_stop_after(duration_seconds: f32) {\r\n    if duration_seconds <= 0.0 {\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let duration_ms = (duration_seconds * 1000.0).round().max(1.0) as u64;\r\n    let deadline = now_ms().saturating_add(duration_ms);\r\n    STOP_DEADLINE_MS.store(deadline, Ordering::SeqCst);\r\n\r\n    let token = STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst).saturating_add(1);\r\n    std::thread::spawn(move || {\r\n        std::thread::sleep(Duration::from_millis(duration_ms));\r\n\r\n        if STOP_TIMER_TOKEN.load(Ordering::SeqCst) != token {\r\n            return;\r\n        }\r\n\r\n        if let Err(err) = send_stop() {\r\n            log_haptics(&format!(\"haptics: delayed stop failed: {}\", err));\r\n        }\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    });\r\n}\r\n\r\nfn resolve_effective_duration_seconds(duration_seconds: f32, frequency_hz: f32) -> f32 {\r\n    let clamped_duration = duration_seconds.clamp(0.0, OPENVR_MAX_DURATION_SECONDS);\r\n    if clamped_duration > 0.0 {\r\n        return clamped_duration;\r\n    }\r\n\r\n    // OpenVR guidance: when duration is 0, trigger exactly one pulse.\r\n    // We approximate one pulse as half a period at the requested frequency,\r\n    // but enforce a hardware minimum so pulses are actually perceptible.\r\n    (0.5 / frequency_hz).max(HARDWARE_MIN_PULSE_SECONDS)\r\n}\r\n\r\nfn looks_like_identify_campaign(req: HapticVibrationRequest) -> bool {\r\n    req.duration_seconds <= 0.0\r\n        && (req.frequency - IDENTIFY_SIGNATURE_FREQUENCY_HZ).abs()\r\n            <= IDENTIFY_SIGNATURE_FREQUENCY_TOLERANCE_HZ\r\n        && req.amplitude > 0.0\r\n        && req.amplitude <= IDENTIFY_SIGNATURE_MAX_AMPLITUDE\r\n}\r\n\r\nfn process_pending_stop() {\r\n    let deadline = STOP_DEADLINE_MS.load(Ordering::SeqCst);\r\n    if deadline == 0 {\r\n        return;\r\n    }\r\n\r\n    if now_ms() >= deadline {\r\n        if let Err(err) = send_stop() {\r\n            log_haptics(&format!(\"haptics: frame stop failed: {}\", err));\r\n        }\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        if let Err(err) = send_stop() {\r\n            log_haptics(&format!(\"haptics: stop-on-zero-amplitude failed: {}\", err));\r\n        }\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    if req.frequency <= 0.0 {\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        if let Err(err) = send_stop() {\r\n            log_haptics(&format!(\"haptics: stop-on-nonpositive-frequency failed: {}\", err));\r\n        }\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let amplitude = req.amplitude.clamp(0.0, 1.0);\r\n    let frequency_hz = req\r\n        .frequency\r\n        .clamp(OPENVR_MIN_FREQUENCY_HZ, OPENVR_MAX_FREQUENCY_HZ);\r\n\r\n    let is_identify_campaign = looks_like_identify_campaign(req);\r\n\r\n    if is_identify_campaign {\r\n        let now = now_ms();\r\n        let active_until = IDENTIFY_BURST_DEADLINE_MS.load(Ordering::SeqCst);\r\n        if now < active_until {\r\n            return;\r\n        }\r\n\r\n        IDENTIFY_BURST_DEADLINE_MS\r\n            .store(now.saturating_add(IDENTIFY_BURST_WINDOW_MS), Ordering::SeqCst);\r\n    }\r\n\r\n    let effective_amplitude = if is_identify_campaign {\r\n        amplitude.max(IDENTIFY_MIN_AMPLITUDE)\r\n    } else {\r\n        amplitude\r\n    };\r\n\r\n    if let Err(err) = send_amplitude(effective_amplitude) {\r\n        log_haptics(&format!(\r\n            \"haptics: send_amplitude failed; amp={:.6}, freq={:.3}, duration_s={:.6}, err={}\",\r\n            effective_amplitude,\r\n            frequency_hz,\r\n            req.duration_seconds,\r\n            err\r\n        ));\r\n        return;\r\n    }\r\n\r\n    let mut effective_duration =\r\n        resolve_effective_duration_seconds(req.duration_seconds, frequency_hz);\r\n    if is_identify_campaign {\r\n        effective_duration = IDENTIFY_PULSE_SECONDS;\r\n    }\r\n\r\n    schedule_stop_after(effective_duration);\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n    process_pending_stop();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":263,"endColumn":1}},{"text":"","range":{"startLineNumber":263,"startColumn":1,"endLineNumber":271,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_52caa11c-9347-4b92-bae5-d4d2c73cbf1f","epoch":1717,"edits":[{"text":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse core::sync::atomic::{AtomicU64, Ordering};\r\nuse std::sync::{mpsc, Mutex, OnceLock};\r\nuse std::time::Instant;\r\nuse std::time::Duration;\r\nuse std::thread;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic STOP_DEADLINE_MS: AtomicU64 = AtomicU64::new(0);\r\nstatic START_INSTANT: OnceLock<Instant> = OnceLock::new();\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\nstatic HID_TX: OnceLock<mpsc::Sender<HapticPacket>> = OnceLock::new();\r\n\r\nconst OPENVR_MIN_FREQUENCY_HZ: f32 = 1_000_000.0 / 65_535.0;\r\nconst OPENVR_MAX_FREQUENCY_HZ: f32 = 1_000_000.0 / 300.0;\r\nconst OPENVR_MAX_DURATION_SECONDS: f32 = 10.0;\r\nconst HARDWARE_MIN_PULSE_SECONDS: f32 = 0.02;\r\nconst HID_SEND_ATTEMPTS: usize = 3;\r\nconst HID_SEND_RETRY_DELAY_MS: u64 = 2;\r\n\r\nfn log_haptics(message: &str) {\r\n    crate::openvr_runtime::log_driver_message(message);\r\n}\r\n\r\nfn send_packet_blocking(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\n    let mut last_error = None;\r\n\r\n    for attempt in 1..=HID_SEND_ATTEMPTS {\r\n        match hid_bridge::HidBridge::new()\r\n            .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n        {\r\n            Ok(()) => return Ok(()),\r\n            Err(err) => {\r\n                last_error = Some(err);\r\n                if attempt < HID_SEND_ATTEMPTS {\r\n                    thread::sleep(Duration::from_millis(HID_SEND_RETRY_DELAY_MS));\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    let err = last_error.expect(\"HID send attempts exhausted without captured error\");\r\n    log_haptics(&format!(\r\n        \"haptics: HID send failed after {} attempt(s); cmd=0x{:02X}, intensity={}, arg_lo={}, arg_hi={}, err={}\",\r\n        HID_SEND_ATTEMPTS,\r\n        packet.command_id,\r\n        packet.intensity,\r\n        packet.arg_lo,\r\n        packet.arg_hi,\r\n        err\r\n    ));\r\n    Err(err)\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet_blocking(HapticPacket::new(command::STOP, 0, 0, 0))\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    send_packet_blocking(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\n}\r\n\nfn hid_tx() -> &'static mpsc::Sender<HapticPacket> {\n    HID_TX.get_or_init(|| {\n        let (tx, rx) = mpsc::channel::<HapticPacket>();\n\n        std::thread::spawn(move || {\n            while let Ok(packet) = rx.recv() {\n                let _ = send_packet_blocking(packet);\n            }\n        });\n\n        tx\n    })\n}\n\nfn queue_packet(packet: HapticPacket) {\n    if let Err(err) = hid_tx().send(packet) {\n        log_haptics(&format!(\"haptics: queue send failed: {}\", err));\n    }\n}\n\nfn queue_stop() {\n    queue_packet(HapticPacket::new(command::STOP, 0, 0, 0));\n}\n\nfn queue_amplitude(amplitude: f32) {\n    let amp = amplitude.clamp(0.0, 1.0);\n    let intensity = (amp * 255.0).round() as u8;\n    queue_packet(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0));\n}\n\r\nfn now_ms() -> u64 {\r\n    let start = START_INSTANT.get_or_init(Instant::now);\r\n    let ms = Instant::now().duration_since(*start).as_millis();\r\n    ms.min(u128::from(u64::MAX)) as u64\r\n}\r\n\r\nfn schedule_stop_after(duration_seconds: f32) {\r\n    if duration_seconds <= 0.0 {\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let duration_ms = (duration_seconds * 1000.0).round().max(1.0) as u64;\r\n    let deadline = now_ms().saturating_add(duration_ms);\r\n    STOP_DEADLINE_MS.store(deadline, Ordering::SeqCst);\r\n\r\n    let token = STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst).saturating_add(1);\r\n    std::thread::spawn(move || {\r\n        std::thread::sleep(Duration::from_millis(duration_ms));\r\n\r\n        if STOP_TIMER_TOKEN.load(Ordering::SeqCst) != token {\r\n            return;\r\n        }\r\n\r\n        if let Err(err) = send_stop() {\r\n            log_haptics(&format!(\"haptics: delayed stop failed: {}\", err));\r\n        }\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    });\r\n}\r\n\r\nfn resolve_effective_duration_seconds(duration_seconds: f32, frequency_hz: f32) -> f32 {\r\n    let clamped_duration = duration_seconds.clamp(0.0, OPENVR_MAX_DURATION_SECONDS);\r\n    if clamped_duration > 0.0 {\r\n        return clamped_duration;\r\n    }\r\n\r\n    // OpenVR guidance: when duration is 0, trigger exactly one pulse.\r\n    // We approximate one pulse as half a period at the requested frequency,\r\n    // but enforce a hardware minimum so pulses are actually perceptible.\r\n    (0.5 / frequency_hz).max(HARDWARE_MIN_PULSE_SECONDS)\r\n}\r\n\r\nfn looks_like_identify_campaign(req: HapticVibrationRequest) -> bool {\r\n    req.duration_seconds <= 0.0\r\n        && (req.frequency - IDENTIFY_SIGNATURE_FREQUENCY_HZ).abs()\r\n            <= IDENTIFY_SIGNATURE_FREQUENCY_TOLERANCE_HZ\r\n        && req.amplitude > 0.0\r\n        && req.amplitude <= IDENTIFY_SIGNATURE_MAX_AMPLITUDE\r\n}\r\n\r\nfn process_pending_stop() {\r\n    let deadline = STOP_DEADLINE_MS.load(Ordering::SeqCst);\r\n    if deadline == 0 {\r\n        return;\r\n    }\r\n\r\n    if now_ms() >= deadline {\r\n        if let Err(err) = send_stop() {\r\n            log_haptics(&format!(\"haptics: frame stop failed: {}\", err));\r\n        }\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        if let Err(err) = send_stop() {\r\n            log_haptics(&format!(\"haptics: stop-on-zero-amplitude failed: {}\", err));\r\n        }\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    if req.frequency <= 0.0 {\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        if let Err(err) = send_stop() {\r\n            log_haptics(&format!(\"haptics: stop-on-nonpositive-frequency failed: {}\", err));\r\n        }\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let amplitude = req.amplitude.clamp(0.0, 1.0);\r\n    let frequency_hz = req\r\n        .frequency\r\n        .clamp(OPENVR_MIN_FREQUENCY_HZ, OPENVR_MAX_FREQUENCY_HZ);\r\n\r\n    let is_identify_campaign = looks_like_identify_campaign(req);\r\n\r\n    if is_identify_campaign {\r\n        let now = now_ms();\r\n        let active_until = IDENTIFY_BURST_DEADLINE_MS.load(Ordering::SeqCst);\r\n        if now < active_until {\r\n            return;\r\n        }\r\n\r\n        IDENTIFY_BURST_DEADLINE_MS\r\n            .store(now.saturating_add(IDENTIFY_BURST_WINDOW_MS), Ordering::SeqCst);\r\n    }\r\n\r\n    let effective_amplitude = if is_identify_campaign {\r\n        amplitude.max(IDENTIFY_MIN_AMPLITUDE)\r\n    } else {\r\n        amplitude\r\n    };\r\n\r\n    if let Err(err) = send_amplitude(effective_amplitude) {\r\n        log_haptics(&format!(\r\n            \"haptics: send_amplitude failed; amp={:.6}, freq={:.3}, duration_s={:.6}, err={}\",\r\n            effective_amplitude,\r\n            frequency_hz,\r\n            req.duration_seconds,\r\n            err\r\n        ));\r\n        return;\r\n    }\r\n\r\n    let mut effective_duration =\r\n        resolve_effective_duration_seconds(req.duration_seconds, frequency_hz);\r\n    if is_identify_campaign {\r\n        effective_duration = IDENTIFY_PULSE_SECONDS;\r\n    }\r\n\r\n    schedule_stop_after(effective_duration);\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n    process_pending_stop();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":291,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_52caa11c-9347-4b92-bae5-d4d2c73cbf1f","epoch":1719,"edits":[{"text":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse core::sync::atomic::{AtomicU64, Ordering};\r\nuse std::sync::{mpsc, Mutex, OnceLock};\r\nuse std::time::Instant;\r\nuse std::time::Duration;\r\nuse std::thread;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic STOP_DEADLINE_MS: AtomicU64 = AtomicU64::new(0);\r\nstatic START_INSTANT: OnceLock<Instant> = OnceLock::new();\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\nstatic HID_TX: OnceLock<mpsc::Sender<HapticPacket>> = OnceLock::new();\r\n\r\nconst OPENVR_MIN_FREQUENCY_HZ: f32 = 1_000_000.0 / 65_535.0;\r\nconst OPENVR_MAX_FREQUENCY_HZ: f32 = 1_000_000.0 / 300.0;\r\nconst OPENVR_MAX_DURATION_SECONDS: f32 = 10.0;\r\nconst HARDWARE_MIN_PULSE_SECONDS: f32 = 0.02;\r\nconst HID_SEND_ATTEMPTS: usize = 3;\r\nconst HID_SEND_RETRY_DELAY_MS: u64 = 2;\r\n\r\nfn log_haptics(message: &str) {\r\n    crate::openvr_runtime::log_driver_message(message);\r\n}\r\n\r\nfn send_packet_blocking(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    let mut last_error = None;\r\n\r\n    for attempt in 1..=HID_SEND_ATTEMPTS {\r\n        match hid_bridge::HidBridge::new()\r\n            .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n        {\r\n            Ok(()) => return Ok(()),\r\n            Err(err) => {\r\n                last_error = Some(err);\r\n                if attempt < HID_SEND_ATTEMPTS {\r\n                    thread::sleep(Duration::from_millis(HID_SEND_RETRY_DELAY_MS));\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    let err = last_error.expect(\"HID send attempts exhausted without captured error\");\r\n    log_haptics(&format!(\r\n        \"haptics: HID send failed after {} attempt(s); cmd=0x{:02X}, intensity={}, arg_lo={}, arg_hi={}, err={}\",\r\n        HID_SEND_ATTEMPTS,\r\n        packet.command_id,\r\n        packet.intensity,\r\n        packet.arg_lo,\r\n        packet.arg_hi,\r\n        err\r\n    ));\r\n    Err(err)\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet_blocking(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    send_packet_blocking(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn hid_tx() -> &'static mpsc::Sender<HapticPacket> {\r\n    HID_TX.get_or_init(|| {\r\n        let (tx, rx) = mpsc::channel::<HapticPacket>();\r\n\r\n        std::thread::spawn(move || {\r\n            while let Ok(packet) = rx.recv() {\r\n                let _ = send_packet_blocking(packet);\r\n            }\r\n        });\r\n\r\n        tx\r\n    })\r\n}\r\n\r\nfn queue_packet(packet: HapticPacket) {\r\n    if let Err(err) = hid_tx().send(packet) {\r\n        log_haptics(&format!(\"haptics: queue send failed: {}\", err));\r\n    }\r\n}\r\n\r\nfn queue_stop() {\r\n    queue_packet(HapticPacket::new(command::STOP, 0, 0, 0));\r\n}\r\n\r\nfn queue_amplitude(amplitude: f32) {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    queue_packet(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0));\r\n}\r\n\r\nfn now_ms() -> u64 {\r\n    let start = START_INSTANT.get_or_init(Instant::now);\r\n    let ms = Instant::now().duration_since(*start).as_millis();\r\n    ms.min(u128::from(u64::MAX)) as u64\r\n}\r\n\r\nfn schedule_stop_after(duration_seconds: f32) {\r\n    if duration_seconds <= 0.0 {\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let duration_ms = (duration_seconds * 1000.0).round().max(1.0) as u64;\r\n    let deadline = now_ms().saturating_add(duration_ms);\r\n    STOP_DEADLINE_MS.store(deadline, Ordering::SeqCst);\r\n}\r\n\r\nfn resolve_effective_duration_seconds(duration_seconds: f32, frequency_hz: f32) -> f32 {\r\n    let clamped_duration = duration_seconds.clamp(0.0, OPENVR_MAX_DURATION_SECONDS);\r\n    if clamped_duration > 0.0 {\r\n        return clamped_duration;\r\n    }\r\n\r\n    // OpenVR guidance: when duration is 0, trigger exactly one pulse.\r\n    // We approximate one pulse as half a period at the requested frequency,\r\n    // but enforce a hardware minimum so pulses are actually perceptible.\r\n    (0.5 / frequency_hz).max(HARDWARE_MIN_PULSE_SECONDS)\r\n}\r\n\r\nfn looks_like_identify_campaign(req: HapticVibrationRequest) -> bool {\r\n    req.duration_seconds <= 0.0\r\n        && (req.frequency - IDENTIFY_SIGNATURE_FREQUENCY_HZ).abs()\r\n            <= IDENTIFY_SIGNATURE_FREQUENCY_TOLERANCE_HZ\r\n        && req.amplitude > 0.0\r\n        && req.amplitude <= IDENTIFY_SIGNATURE_MAX_AMPLITUDE\r\n}\r\n\r\nfn process_pending_stop() {\r\n    let deadline = STOP_DEADLINE_MS.load(Ordering::SeqCst);\r\n    if deadline == 0 {\r\n        return;\r\n    }\r\n\r\n    if now_ms() >= deadline {\r\n        if let Err(err) = send_stop() {\r\n            log_haptics(&format!(\"haptics: frame stop failed: {}\", err));\r\n        }\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        if let Err(err) = send_stop() {\r\n            log_haptics(&format!(\"haptics: stop-on-zero-amplitude failed: {}\", err));\r\n        }\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    if req.frequency <= 0.0 {\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        if let Err(err) = send_stop() {\r\n            log_haptics(&format!(\"haptics: stop-on-nonpositive-frequency failed: {}\", err));\r\n        }\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let amplitude = req.amplitude.clamp(0.0, 1.0);\r\n    let frequency_hz = req\r\n        .frequency\r\n        .clamp(OPENVR_MIN_FREQUENCY_HZ, OPENVR_MAX_FREQUENCY_HZ);\r\n\r\n    let is_identify_campaign = looks_like_identify_campaign(req);\r\n\r\n    if is_identify_campaign {\r\n        let now = now_ms();\r\n        let active_until = IDENTIFY_BURST_DEADLINE_MS.load(Ordering::SeqCst);\r\n        if now < active_until {\r\n            return;\r\n        }\r\n\r\n        IDENTIFY_BURST_DEADLINE_MS\r\n            .store(now.saturating_add(IDENTIFY_BURST_WINDOW_MS), Ordering::SeqCst);\r\n    }\r\n\r\n    let effective_amplitude = if is_identify_campaign {\r\n        amplitude.max(IDENTIFY_MIN_AMPLITUDE)\r\n    } else {\r\n        amplitude\r\n    };\r\n\r\n    if let Err(err) = send_amplitude(effective_amplitude) {\r\n        log_haptics(&format!(\r\n            \"haptics: send_amplitude failed; amp={:.6}, freq={:.3}, duration_s={:.6}, err={}\",\r\n            effective_amplitude,\r\n            frequency_hz,\r\n            req.duration_seconds,\r\n            err\r\n        ));\r\n        return;\r\n    }\r\n\r\n    let mut effective_duration =\r\n        resolve_effective_duration_seconds(req.duration_seconds, frequency_hz);\r\n    if is_identify_campaign {\r\n        effective_duration = IDENTIFY_PULSE_SECONDS;\r\n    }\r\n\r\n    schedule_stop_after(effective_duration);\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n    process_pending_stop();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":276,"endColumn":1}},{"text":"","range":{"startLineNumber":276,"startColumn":1,"endLineNumber":291,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_52caa11c-9347-4b92-bae5-d4d2c73cbf1f","epoch":1721,"edits":[{"text":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse core::sync::atomic::{AtomicU64, Ordering};\r\nuse std::sync::{mpsc, Mutex, OnceLock};\r\nuse std::time::Instant;\r\nuse std::time::Duration;\r\nuse std::thread;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic STOP_DEADLINE_MS: AtomicU64 = AtomicU64::new(0);\r\nstatic START_INSTANT: OnceLock<Instant> = OnceLock::new();\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\nstatic HID_TX: OnceLock<mpsc::Sender<HapticPacket>> = OnceLock::new();\r\n\r\nconst OPENVR_MIN_FREQUENCY_HZ: f32 = 1_000_000.0 / 65_535.0;\r\nconst OPENVR_MAX_FREQUENCY_HZ: f32 = 1_000_000.0 / 300.0;\r\nconst OPENVR_MAX_DURATION_SECONDS: f32 = 10.0;\r\nconst HARDWARE_MIN_PULSE_SECONDS: f32 = 0.02;\r\nconst HID_SEND_ATTEMPTS: usize = 3;\r\nconst HID_SEND_RETRY_DELAY_MS: u64 = 2;\r\n\r\nfn log_haptics(message: &str) {\r\n    crate::openvr_runtime::log_driver_message(message);\r\n}\r\n\r\nfn send_packet_blocking(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    let mut last_error = None;\r\n\r\n    for attempt in 1..=HID_SEND_ATTEMPTS {\r\n        match hid_bridge::HidBridge::new()\r\n            .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n        {\r\n            Ok(()) => return Ok(()),\r\n            Err(err) => {\r\n                last_error = Some(err);\r\n                if attempt < HID_SEND_ATTEMPTS {\r\n                    thread::sleep(Duration::from_millis(HID_SEND_RETRY_DELAY_MS));\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    let err = last_error.expect(\"HID send attempts exhausted without captured error\");\r\n    log_haptics(&format!(\r\n        \"haptics: HID send failed after {} attempt(s); cmd=0x{:02X}, intensity={}, arg_lo={}, arg_hi={}, err={}\",\r\n        HID_SEND_ATTEMPTS,\r\n        packet.command_id,\r\n        packet.intensity,\r\n        packet.arg_lo,\r\n        packet.arg_hi,\r\n        err\r\n    ));\r\n    Err(err)\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet_blocking(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    send_packet_blocking(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn hid_tx() -> &'static mpsc::Sender<HapticPacket> {\r\n    HID_TX.get_or_init(|| {\r\n        let (tx, rx) = mpsc::channel::<HapticPacket>();\r\n\r\n        std::thread::spawn(move || {\r\n            while let Ok(packet) = rx.recv() {\r\n                let _ = send_packet_blocking(packet);\r\n            }\r\n        });\r\n\r\n        tx\r\n    })\r\n}\r\n\r\nfn queue_packet(packet: HapticPacket) {\r\n    if let Err(err) = hid_tx().send(packet) {\r\n        log_haptics(&format!(\"haptics: queue send failed: {}\", err));\r\n    }\r\n}\r\n\r\nfn queue_stop() {\r\n    queue_packet(HapticPacket::new(command::STOP, 0, 0, 0));\r\n}\r\n\r\nfn queue_amplitude(amplitude: f32) {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    queue_packet(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0));\r\n}\r\n\r\nfn now_ms() -> u64 {\r\n    let start = START_INSTANT.get_or_init(Instant::now);\r\n    let ms = Instant::now().duration_since(*start).as_millis();\r\n    ms.min(u128::from(u64::MAX)) as u64\r\n}\r\n\r\nfn schedule_stop_after(duration_seconds: f32) {\r\n    if duration_seconds <= 0.0 {\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let duration_ms = (duration_seconds * 1000.0).round().max(1.0) as u64;\r\n    let deadline = now_ms().saturating_add(duration_ms);\r\n    STOP_DEADLINE_MS.store(deadline, Ordering::SeqCst);\r\n}\r\n\r\nfn resolve_effective_duration_seconds(duration_seconds: f32, frequency_hz: f32) -> f32 {\r\n    let clamped_duration = duration_seconds.clamp(0.0, OPENVR_MAX_DURATION_SECONDS);\r\n    if clamped_duration > 0.0 {\r\n        return clamped_duration;\r\n    }\r\n\r\n    // OpenVR guidance: when duration is 0, trigger exactly one pulse.\r\n    // We approximate one pulse as half a period at the requested frequency,\r\n    // but enforce a hardware minimum so pulses are actually perceptible.\r\n    (0.5 / frequency_hz).max(HARDWARE_MIN_PULSE_SECONDS)\r\n}\r\n\r\nfn process_pending_stop() {\r\n    let deadline = STOP_DEADLINE_MS.load(Ordering::SeqCst);\r\n    if deadline == 0 {\r\n        return;\r\n    }\r\n\r\n    if now_ms() >= deadline {\r\n        queue_stop();\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        if let Err(err) = send_stop() {\r\n            log_haptics(&format!(\"haptics: stop-on-zero-amplitude failed: {}\", err));\r\n        }\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    if req.frequency <= 0.0 {\r\n        STOP_TIMER_TOKEN.fetch_add(1, Ordering::SeqCst);\r\n        if let Err(err) = send_stop() {\r\n            log_haptics(&format!(\"haptics: stop-on-nonpositive-frequency failed: {}\", err));\r\n        }\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let amplitude = req.amplitude.clamp(0.0, 1.0);\r\n    let frequency_hz = req\r\n        .frequency\r\n        .clamp(OPENVR_MIN_FREQUENCY_HZ, OPENVR_MAX_FREQUENCY_HZ);\r\n\r\n    let is_identify_campaign = looks_like_identify_campaign(req);\r\n\r\n    if is_identify_campaign {\r\n        let now = now_ms();\r\n        let active_until = IDENTIFY_BURST_DEADLINE_MS.load(Ordering::SeqCst);\r\n        if now < active_until {\r\n            return;\r\n        }\r\n\r\n        IDENTIFY_BURST_DEADLINE_MS\r\n            .store(now.saturating_add(IDENTIFY_BURST_WINDOW_MS), Ordering::SeqCst);\r\n    }\r\n\r\n    let effective_amplitude = if is_identify_campaign {\r\n        amplitude.max(IDENTIFY_MIN_AMPLITUDE)\r\n    } else {\r\n        amplitude\r\n    };\r\n\r\n    if let Err(err) = send_amplitude(effective_amplitude) {\r\n        log_haptics(&format!(\r\n            \"haptics: send_amplitude failed; amp={:.6}, freq={:.3}, duration_s={:.6}, err={}\",\r\n            effective_amplitude,\r\n            frequency_hz,\r\n            req.duration_seconds,\r\n            err\r\n        ));\r\n        return;\r\n    }\r\n\r\n    let mut effective_duration =\r\n        resolve_effective_duration_seconds(req.duration_seconds, frequency_hz);\r\n    if is_identify_campaign {\r\n        effective_duration = IDENTIFY_PULSE_SECONDS;\r\n    }\r\n\r\n    schedule_stop_after(effective_duration);\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n    process_pending_stop();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":266,"endColumn":1}},{"text":"","range":{"startLineNumber":266,"startColumn":1,"endLineNumber":276,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_52caa11c-9347-4b92-bae5-d4d2c73cbf1f","epoch":1723,"edits":[{"text":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse core::sync::atomic::{AtomicU64, Ordering};\r\nuse std::sync::{mpsc, Mutex, OnceLock};\r\nuse std::time::Instant;\r\nuse std::time::Duration;\r\nuse std::thread;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic STOP_DEADLINE_MS: AtomicU64 = AtomicU64::new(0);\r\nstatic START_INSTANT: OnceLock<Instant> = OnceLock::new();\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\nstatic HID_TX: OnceLock<mpsc::Sender<HapticPacket>> = OnceLock::new();\r\n\r\nconst OPENVR_MIN_FREQUENCY_HZ: f32 = 1_000_000.0 / 65_535.0;\r\nconst OPENVR_MAX_FREQUENCY_HZ: f32 = 1_000_000.0 / 300.0;\r\nconst OPENVR_MAX_DURATION_SECONDS: f32 = 10.0;\r\nconst HARDWARE_MIN_PULSE_SECONDS: f32 = 0.02;\r\nconst HID_SEND_ATTEMPTS: usize = 3;\r\nconst HID_SEND_RETRY_DELAY_MS: u64 = 2;\r\n\r\nfn log_haptics(message: &str) {\r\n    crate::openvr_runtime::log_driver_message(message);\r\n}\r\n\r\nfn send_packet_blocking(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    let mut last_error = None;\r\n\r\n    for attempt in 1..=HID_SEND_ATTEMPTS {\r\n        match hid_bridge::HidBridge::new()\r\n            .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n        {\r\n            Ok(()) => return Ok(()),\r\n            Err(err) => {\r\n                last_error = Some(err);\r\n                if attempt < HID_SEND_ATTEMPTS {\r\n                    thread::sleep(Duration::from_millis(HID_SEND_RETRY_DELAY_MS));\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    let err = last_error.expect(\"HID send attempts exhausted without captured error\");\r\n    log_haptics(&format!(\r\n        \"haptics: HID send failed after {} attempt(s); cmd=0x{:02X}, intensity={}, arg_lo={}, arg_hi={}, err={}\",\r\n        HID_SEND_ATTEMPTS,\r\n        packet.command_id,\r\n        packet.intensity,\r\n        packet.arg_lo,\r\n        packet.arg_hi,\r\n        err\r\n    ));\r\n    Err(err)\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet_blocking(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    send_packet_blocking(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn hid_tx() -> &'static mpsc::Sender<HapticPacket> {\r\n    HID_TX.get_or_init(|| {\r\n        let (tx, rx) = mpsc::channel::<HapticPacket>();\r\n\r\n        std::thread::spawn(move || {\r\n            while let Ok(packet) = rx.recv() {\r\n                let _ = send_packet_blocking(packet);\r\n            }\r\n        });\r\n\r\n        tx\r\n    })\r\n}\r\n\r\nfn queue_packet(packet: HapticPacket) {\r\n    if let Err(err) = hid_tx().send(packet) {\r\n        log_haptics(&format!(\"haptics: queue send failed: {}\", err));\r\n    }\r\n}\r\n\r\nfn queue_stop() {\r\n    queue_packet(HapticPacket::new(command::STOP, 0, 0, 0));\r\n}\r\n\r\nfn queue_amplitude(amplitude: f32) {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    queue_packet(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0));\r\n}\r\n\r\nfn now_ms() -> u64 {\r\n    let start = START_INSTANT.get_or_init(Instant::now);\r\n    let ms = Instant::now().duration_since(*start).as_millis();\r\n    ms.min(u128::from(u64::MAX)) as u64\r\n}\r\n\r\nfn schedule_stop_after(duration_seconds: f32) {\r\n    if duration_seconds <= 0.0 {\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let duration_ms = (duration_seconds * 1000.0).round().max(1.0) as u64;\r\n    let deadline = now_ms().saturating_add(duration_ms);\r\n    STOP_DEADLINE_MS.store(deadline, Ordering::SeqCst);\r\n}\r\n\r\nfn resolve_effective_duration_seconds(duration_seconds: f32, frequency_hz: f32) -> f32 {\r\n    let clamped_duration = duration_seconds.clamp(0.0, OPENVR_MAX_DURATION_SECONDS);\r\n    if clamped_duration > 0.0 {\r\n        return clamped_duration;\r\n    }\r\n\r\n    // OpenVR guidance: when duration is 0, trigger exactly one pulse.\r\n    // We approximate one pulse as half a period at the requested frequency,\r\n    // but enforce a hardware minimum so pulses are actually perceptible.\r\n    (0.5 / frequency_hz).max(HARDWARE_MIN_PULSE_SECONDS)\r\n}\r\n\r\nfn process_pending_stop() {\r\n    let deadline = STOP_DEADLINE_MS.load(Ordering::SeqCst);\r\n    if deadline == 0 {\r\n        return;\r\n    }\r\n\r\n    if now_ms() >= deadline {\r\n        queue_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        queue_stop();\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    if req.frequency <= 0.0 {\r\n        queue_stop();\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let amplitude = req.amplitude.clamp(0.0, 1.0);\r\n    let frequency_hz = req\r\n        .frequency\r\n        .clamp(OPENVR_MIN_FREQUENCY_HZ, OPENVR_MAX_FREQUENCY_HZ);\r\n\n    queue_amplitude(amplitude);\n\r\n    let effective_duration =\n        resolve_effective_duration_seconds(req.duration_seconds, frequency_hz);\n\r\n    schedule_stop_after(effective_duration);\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n    process_pending_stop();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":229,"endColumn":1}},{"text":"","range":{"startLineNumber":229,"startColumn":1,"endLineNumber":266,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_f1f75ee3-7832-415f-ba36-db253bd15323","epoch":1727,"edits":[{"text":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse core::sync::atomic::{AtomicU64, Ordering};\r\nuse std::sync::{mpsc, Mutex, OnceLock};\r\nuse std::time::Instant;\r\nuse std::time::Duration;\r\nuse std::thread;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic STOP_DEADLINE_MS: AtomicU64 = AtomicU64::new(0);\r\nstatic START_INSTANT: OnceLock<Instant> = OnceLock::new();\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\nstatic HID_TX: OnceLock<mpsc::Sender<HapticPacket>> = OnceLock::new();\r\n\r\nconst OPENVR_MIN_FREQUENCY_HZ: f32 = 1_000_000.0 / 65_535.0;\r\nconst OPENVR_MAX_FREQUENCY_HZ: f32 = 1_000_000.0 / 300.0;\r\nconst OPENVR_MAX_DURATION_SECONDS: f32 = 10.0;\r\nconst HARDWARE_MIN_PULSE_SECONDS: f32 = 0.02;\r\nconst ERM_AMPLITUDE_GAIN: f32 = 1.8;\nconst ERM_MIN_NONZERO_AMPLITUDE: f32 = 0.25;\nconst HID_SEND_ATTEMPTS: usize = 3;\r\nconst HID_SEND_RETRY_DELAY_MS: u64 = 2;\r\n\r\nfn log_haptics(message: &str) {\r\n    crate::openvr_runtime::log_driver_message(message);\r\n}\r\n\r\nfn send_packet_blocking(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    let mut last_error = None;\r\n\r\n    for attempt in 1..=HID_SEND_ATTEMPTS {\r\n        match hid_bridge::HidBridge::new()\r\n            .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n        {\r\n            Ok(()) => return Ok(()),\r\n            Err(err) => {\r\n                last_error = Some(err);\r\n                if attempt < HID_SEND_ATTEMPTS {\r\n                    thread::sleep(Duration::from_millis(HID_SEND_RETRY_DELAY_MS));\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    let err = last_error.expect(\"HID send attempts exhausted without captured error\");\r\n    log_haptics(&format!(\r\n        \"haptics: HID send failed after {} attempt(s); cmd=0x{:02X}, intensity={}, arg_lo={}, arg_hi={}, err={}\",\r\n        HID_SEND_ATTEMPTS,\r\n        packet.command_id,\r\n        packet.intensity,\r\n        packet.arg_lo,\r\n        packet.arg_hi,\r\n        err\r\n    ));\r\n    Err(err)\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet_blocking(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    send_packet_blocking(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn hid_tx() -> &'static mpsc::Sender<HapticPacket> {\r\n    HID_TX.get_or_init(|| {\r\n        let (tx, rx) = mpsc::channel::<HapticPacket>();\r\n\r\n        std::thread::spawn(move || {\r\n            while let Ok(packet) = rx.recv() {\r\n                let _ = send_packet_blocking(packet);\r\n            }\r\n        });\r\n\r\n        tx\r\n    })\r\n}\r\n\r\nfn queue_packet(packet: HapticPacket) {\r\n    if let Err(err) = hid_tx().send(packet) {\r\n        log_haptics(&format!(\"haptics: queue send failed: {}\", err));\r\n    }\r\n}\r\n\r\nfn queue_stop() {\r\n    queue_packet(HapticPacket::new(command::STOP, 0, 0, 0));\r\n}\r\n\r\nfn queue_amplitude(amplitude: f32) {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    queue_packet(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0));\r\n}\r\n\nfn shape_amplitude_for_erm(amplitude: f32) -> f32 {\n    if amplitude <= 0.0 {\n        return 0.0;\n    }\n\n    let boosted = (amplitude * ERM_AMPLITUDE_GAIN).clamp(0.0, 1.0);\n    boosted.max(ERM_MIN_NONZERO_AMPLITUDE)\n}\n\r\nfn now_ms() -> u64 {\r\n    let start = START_INSTANT.get_or_init(Instant::now);\r\n    let ms = Instant::now().duration_since(*start).as_millis();\r\n    ms.min(u128::from(u64::MAX)) as u64\r\n}\r\n\r\nfn schedule_stop_after(duration_seconds: f32) {\r\n    if duration_seconds <= 0.0 {\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let duration_ms = (duration_seconds * 1000.0).round().max(1.0) as u64;\r\n    let deadline = now_ms().saturating_add(duration_ms);\r\n    STOP_DEADLINE_MS.store(deadline, Ordering::SeqCst);\r\n}\r\n\r\nfn resolve_effective_duration_seconds(duration_seconds: f32, frequency_hz: f32) -> f32 {\r\n    let clamped_duration = duration_seconds.clamp(0.0, OPENVR_MAX_DURATION_SECONDS);\r\n    if clamped_duration > 0.0 {\r\n        return clamped_duration;\r\n    }\r\n\r\n    // OpenVR guidance: when duration is 0, trigger exactly one pulse.\r\n    // We approximate one pulse as half a period at the requested frequency,\r\n    // but enforce a hardware minimum so pulses are actually perceptible.\r\n    (0.5 / frequency_hz).max(HARDWARE_MIN_PULSE_SECONDS)\r\n}\r\n\r\nfn process_pending_stop() {\r\n    let deadline = STOP_DEADLINE_MS.load(Ordering::SeqCst);\r\n    if deadline == 0 {\r\n        return;\r\n    }\r\n\r\n    if now_ms() >= deadline {\r\n        queue_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        queue_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    if req.frequency <= 0.0 {\r\n        queue_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let amplitude = shape_amplitude_for_erm(req.amplitude.clamp(0.0, 1.0));\n    let frequency_hz = req\r\n        .frequency\r\n        .clamp(OPENVR_MIN_FREQUENCY_HZ, OPENVR_MAX_FREQUENCY_HZ);\r\n\r\n    queue_amplitude(amplitude);\r\n\r\n    let effective_duration =\r\n        resolve_effective_duration_seconds(req.duration_seconds, frequency_hz);\r\n\r\n    schedule_stop_after(effective_duration);\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n    process_pending_stop();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":240,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_19ae42fe-2a8e-4927-9a95-27d34479b063","epoch":1731,"edits":[{"text":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse core::sync::atomic::{AtomicU64, Ordering};\r\nuse std::sync::{Condvar, Mutex, OnceLock};\nuse std::time::Instant;\r\nuse std::time::Duration;\r\nuse std::thread;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic STOP_DEADLINE_MS: AtomicU64 = AtomicU64::new(0);\r\nstatic START_INSTANT: OnceLock<Instant> = OnceLock::new();\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\n\nstruct HidMailbox {\n    packet: Mutex<Option<HapticPacket>>,\n    signal: Condvar,\n}\n\nstatic HID_MAILBOX: OnceLock<HidMailbox> = OnceLock::new();\n\r\nconst OPENVR_MIN_FREQUENCY_HZ: f32 = 1_000_000.0 / 65_535.0;\r\nconst OPENVR_MAX_FREQUENCY_HZ: f32 = 1_000_000.0 / 300.0;\r\nconst OPENVR_MAX_DURATION_SECONDS: f32 = 10.0;\r\nconst HARDWARE_MIN_PULSE_SECONDS: f32 = 0.02;\r\nconst ERM_AMPLITUDE_GAIN: f32 = 1.8;\r\nconst ERM_MIN_NONZERO_AMPLITUDE: f32 = 0.25;\r\nconst HID_SEND_ATTEMPTS: usize = 3;\r\nconst HID_SEND_RETRY_DELAY_MS: u64 = 2;\r\n\r\nfn log_haptics(message: &str) {\r\n    crate::openvr_runtime::log_driver_message(message);\r\n}\r\n\r\nfn send_packet_blocking(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    let mut last_error = None;\r\n\r\n    for attempt in 1..=HID_SEND_ATTEMPTS {\r\n        match hid_bridge::HidBridge::new()\r\n            .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n        {\r\n            Ok(()) => return Ok(()),\r\n            Err(err) => {\r\n                last_error = Some(err);\r\n                if attempt < HID_SEND_ATTEMPTS {\r\n                    thread::sleep(Duration::from_millis(HID_SEND_RETRY_DELAY_MS));\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    let err = last_error.expect(\"HID send attempts exhausted without captured error\");\r\n    log_haptics(&format!(\r\n        \"haptics: HID send failed after {} attempt(s); cmd=0x{:02X}, intensity={}, arg_lo={}, arg_hi={}, err={}\",\r\n        HID_SEND_ATTEMPTS,\r\n        packet.command_id,\r\n        packet.intensity,\r\n        packet.arg_lo,\r\n        packet.arg_hi,\r\n        err\r\n    ));\r\n    Err(err)\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet_blocking(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    send_packet_blocking(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn hid_mailbox() -> &'static HidMailbox {\n    HID_MAILBOX.get_or_init(|| {\n        let mailbox = HidMailbox {\n            packet: Mutex::new(None),\n            signal: Condvar::new(),\n        };\n\n        std::thread::spawn(|| {\n            let mailbox = hid_mailbox();\n            loop {\n                let packet = {\n                    let mut guard = match mailbox.packet.lock() {\n                        Ok(g) => g,\n                        Err(_) => continue,\n                    };\n\n                    while guard.is_none() {\n                        guard = match mailbox.signal.wait(guard) {\n                            Ok(g) => g,\n                            Err(_) => break,\n                        };\n                    }\n\n                    match guard.take() {\n                        Some(packet) => packet,\n                        None => continue,\n                    }\n                };\n\n                let _ = send_packet_blocking(packet);\n            }\n        });\n\n        mailbox\n    })\n}\r\n\r\nfn queue_packet(packet: HapticPacket) {\r\n    let mailbox = hid_mailbox();\n    if let Ok(mut guard) = mailbox.packet.lock() {\n        *guard = Some(packet);\n        mailbox.signal.notify_one();\n    } else {\n        log_haptics(\"haptics: queue send failed: mailbox lock poisoned\");\n    }\r\n}\r\n\r\nfn queue_stop() {\r\n    queue_packet(HapticPacket::new(command::STOP, 0, 0, 0));\r\n}\r\n\r\nfn queue_amplitude(amplitude: f32) {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    queue_packet(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0));\r\n}\r\n\r\nfn shape_amplitude_for_erm(amplitude: f32) -> f32 {\r\n    if amplitude <= 0.0 {\r\n        return 0.0;\r\n    }\r\n\r\n    let boosted = (amplitude * ERM_AMPLITUDE_GAIN).clamp(0.0, 1.0);\r\n    boosted.max(ERM_MIN_NONZERO_AMPLITUDE)\r\n}\r\n\r\nfn now_ms() -> u64 {\r\n    let start = START_INSTANT.get_or_init(Instant::now);\r\n    let ms = Instant::now().duration_since(*start).as_millis();\r\n    ms.min(u128::from(u64::MAX)) as u64\r\n}\r\n\r\nfn schedule_stop_after(duration_seconds: f32) {\r\n    if duration_seconds <= 0.0 {\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let duration_ms = (duration_seconds * 1000.0).round().max(1.0) as u64;\r\n    let deadline = now_ms().saturating_add(duration_ms);\r\n    STOP_DEADLINE_MS.store(deadline, Ordering::SeqCst);\r\n}\r\n\r\nfn resolve_effective_duration_seconds(duration_seconds: f32, frequency_hz: f32) -> f32 {\r\n    let clamped_duration = duration_seconds.clamp(0.0, OPENVR_MAX_DURATION_SECONDS);\r\n    if clamped_duration > 0.0 {\r\n        return clamped_duration;\r\n    }\r\n\r\n    // OpenVR guidance: when duration is 0, trigger exactly one pulse.\r\n    // We approximate one pulse as half a period at the requested frequency,\r\n    // but enforce a hardware minimum so pulses are actually perceptible.\r\n    (0.5 / frequency_hz).max(HARDWARE_MIN_PULSE_SECONDS)\r\n}\r\n\r\nfn process_pending_stop() {\r\n    let deadline = STOP_DEADLINE_MS.load(Ordering::SeqCst);\r\n    if deadline == 0 {\r\n        return;\r\n    }\r\n\r\n    if now_ms() >= deadline {\r\n        queue_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        queue_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    if req.frequency <= 0.0 {\r\n        queue_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let amplitude = shape_amplitude_for_erm(req.amplitude.clamp(0.0, 1.0));\r\n    let frequency_hz = req\r\n        .frequency\r\n        .clamp(OPENVR_MIN_FREQUENCY_HZ, OPENVR_MAX_FREQUENCY_HZ);\r\n\r\n    queue_amplitude(amplitude);\r\n\r\n    let effective_duration =\r\n        resolve_effective_duration_seconds(req.duration_seconds, frequency_hz);\r\n\r\n    schedule_stop_after(effective_duration);\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n    process_pending_stop();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":273,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_19ae42fe-2a8e-4927-9a95-27d34479b063","epoch":1733,"edits":[{"text":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse core::sync::atomic::{AtomicU64, Ordering};\r\nuse std::sync::{Condvar, Mutex, OnceLock};\r\nuse std::time::Instant;\r\nuse std::time::Duration;\r\nuse std::thread;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic STOP_DEADLINE_MS: AtomicU64 = AtomicU64::new(0);\r\nstatic START_INSTANT: OnceLock<Instant> = OnceLock::new();\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\n\r\nstruct HidMailbox {\r\n    packet: Mutex<Option<HapticPacket>>,\r\n    signal: Condvar,\r\n}\r\n\r\nstatic HID_MAILBOX: OnceLock<HidMailbox> = OnceLock::new();\r\n\r\nconst OPENVR_MIN_FREQUENCY_HZ: f32 = 1_000_000.0 / 65_535.0;\r\nconst OPENVR_MAX_FREQUENCY_HZ: f32 = 1_000_000.0 / 300.0;\r\nconst OPENVR_MAX_DURATION_SECONDS: f32 = 10.0;\r\nconst HARDWARE_MIN_PULSE_SECONDS: f32 = 0.02;\r\nconst ERM_AMPLITUDE_GAIN: f32 = 1.8;\r\nconst ERM_MIN_NONZERO_AMPLITUDE: f32 = 0.25;\r\nconst HID_SEND_ATTEMPTS: usize = 3;\r\nconst HID_SEND_RETRY_DELAY_MS: u64 = 2;\r\n\r\nfn log_haptics(message: &str) {\r\n    crate::openvr_runtime::log_driver_message(message);\r\n}\r\n\r\nfn send_packet_blocking(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    let mut last_error = None;\r\n\r\n    for attempt in 1..=HID_SEND_ATTEMPTS {\r\n        match hid_bridge::HidBridge::new()\r\n            .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n        {\r\n            Ok(()) => return Ok(()),\r\n            Err(err) => {\r\n                last_error = Some(err);\r\n                if attempt < HID_SEND_ATTEMPTS {\r\n                    thread::sleep(Duration::from_millis(HID_SEND_RETRY_DELAY_MS));\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    let err = last_error.expect(\"HID send attempts exhausted without captured error\");\r\n    log_haptics(&format!(\r\n        \"haptics: HID send failed after {} attempt(s); cmd=0x{:02X}, intensity={}, arg_lo={}, arg_hi={}, err={}\",\r\n        HID_SEND_ATTEMPTS,\r\n        packet.command_id,\r\n        packet.intensity,\r\n        packet.arg_lo,\r\n        packet.arg_hi,\r\n        err\r\n    ));\r\n    Err(err)\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet_blocking(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    send_packet_blocking(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn hid_mailbox() -> &'static HidMailbox {\r\n    HID_MAILBOX.get_or_init(|| {\r\n        let mailbox = HidMailbox {\r\n            packet: Mutex::new(None),\r\n            signal: Condvar::new(),\r\n        };\r\n\r\n        std::thread::spawn(|| {\r\n            let mailbox = hid_mailbox();\r\n            'worker: loop {\n                let packet = {\r\n                    let mut guard = match mailbox.packet.lock() {\r\n                        Ok(g) => g,\r\n                        Err(_) => continue 'worker,\n                    };\r\n\r\n                    while guard.is_none() {\r\n                        let waited = mailbox.signal.wait(guard);\n                        guard = match waited {\n                            Ok(g) => g,\r\n                            Err(_) => continue 'worker,\n                        };\r\n                    }\r\n\r\n                    guard.take().expect(\"mailbox packet should exist\")\n                };\r\n\r\n                let _ = send_packet_blocking(packet);\r\n            }\r\n        });\r\n\r\n        mailbox\r\n    })\r\n}\r\n\r\nfn queue_packet(packet: HapticPacket) {\r\n    let mailbox = hid_mailbox();\r\n    if let Ok(mut guard) = mailbox.packet.lock() {\r\n        *guard = Some(packet);\r\n        mailbox.signal.notify_one();\r\n    } else {\r\n        log_haptics(\"haptics: queue send failed: mailbox lock poisoned\");\r\n    }\r\n}\r\n\r\nfn queue_stop() {\r\n    queue_packet(HapticPacket::new(command::STOP, 0, 0, 0));\r\n}\r\n\r\nfn queue_amplitude(amplitude: f32) {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = (amp * 255.0).round() as u8;\r\n    queue_packet(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0));\r\n}\r\n\r\nfn shape_amplitude_for_erm(amplitude: f32) -> f32 {\r\n    if amplitude <= 0.0 {\r\n        return 0.0;\r\n    }\r\n\r\n    let boosted = (amplitude * ERM_AMPLITUDE_GAIN).clamp(0.0, 1.0);\r\n    boosted.max(ERM_MIN_NONZERO_AMPLITUDE)\r\n}\r\n\r\nfn now_ms() -> u64 {\r\n    let start = START_INSTANT.get_or_init(Instant::now);\r\n    let ms = Instant::now().duration_since(*start).as_millis();\r\n    ms.min(u128::from(u64::MAX)) as u64\r\n}\r\n\r\nfn schedule_stop_after(duration_seconds: f32) {\r\n    if duration_seconds <= 0.0 {\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let duration_ms = (duration_seconds * 1000.0).round().max(1.0) as u64;\r\n    let deadline = now_ms().saturating_add(duration_ms);\r\n    STOP_DEADLINE_MS.store(deadline, Ordering::SeqCst);\r\n}\r\n\r\nfn resolve_effective_duration_seconds(duration_seconds: f32, frequency_hz: f32) -> f32 {\r\n    let clamped_duration = duration_seconds.clamp(0.0, OPENVR_MAX_DURATION_SECONDS);\r\n    if clamped_duration > 0.0 {\r\n        return clamped_duration;\r\n    }\r\n\r\n    // OpenVR guidance: when duration is 0, trigger exactly one pulse.\r\n    // We approximate one pulse as half a period at the requested frequency,\r\n    // but enforce a hardware minimum so pulses are actually perceptible.\r\n    (0.5 / frequency_hz).max(HARDWARE_MIN_PULSE_SECONDS)\r\n}\r\n\r\nfn process_pending_stop() {\r\n    let deadline = STOP_DEADLINE_MS.load(Ordering::SeqCst);\r\n    if deadline == 0 {\r\n        return;\r\n    }\r\n\r\n    if now_ms() >= deadline {\r\n        queue_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n    }\r\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        queue_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    if req.frequency <= 0.0 {\r\n        queue_stop();\r\n        STOP_DEADLINE_MS.store(0, Ordering::SeqCst);\r\n        return;\r\n    }\r\n\r\n    let amplitude = shape_amplitude_for_erm(req.amplitude.clamp(0.0, 1.0));\r\n    let frequency_hz = req\r\n        .frequency\r\n        .clamp(OPENVR_MIN_FREQUENCY_HZ, OPENVR_MAX_FREQUENCY_HZ);\r\n\r\n    queue_amplitude(amplitude);\r\n\r\n    let effective_duration =\r\n        resolve_effective_duration_seconds(req.duration_seconds, frequency_hz);\r\n\r\n    schedule_stop_after(effective_duration);\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n    process_pending_stop();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":271,"endColumn":1}},{"text":"","range":{"startLineNumber":271,"startColumn":1,"endLineNumber":273,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_ea2c63b4-ffe0-48a8-aaf1-044afe929342","epoch":1737,"edits":[{"text":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse std::sync::{mpsc, Mutex, OnceLock};\nuse std::time::Duration;\r\nuse std::time::Instant;\nuse std::thread;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\n\nenum WorkerCommand {\n    Pulse {\n        intensity: u8,\n        duration_ms: u64,\n    },\n    Stop,\n}\n\nstatic HID_TX: OnceLock<mpsc::Sender<WorkerCommand>> = OnceLock::new();\n\r\nconst OPENVR_MIN_FREQUENCY_HZ: f32 = 1_000_000.0 / 65_535.0;\r\nconst OPENVR_MAX_FREQUENCY_HZ: f32 = 1_000_000.0 / 300.0;\r\nconst OPENVR_MAX_DURATION_SECONDS: f32 = 10.0;\r\nconst HARDWARE_MIN_PULSE_SECONDS: f32 = 0.05;\nconst ERM_AMPLITUDE_GAIN: f32 = 1.8;\r\nconst ERM_MIN_NONZERO_AMPLITUDE: f32 = 0.25;\r\nconst HID_SEND_ATTEMPTS: usize = 3;\r\nconst HID_SEND_RETRY_DELAY_MS: u64 = 2;\r\n\r\nfn log_haptics(message: &str) {\r\n    crate::openvr_runtime::log_driver_message(message);\r\n}\r\n\r\nfn send_packet_blocking(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    let mut last_error = None;\r\n\r\n    for attempt in 1..=HID_SEND_ATTEMPTS {\r\n        match hid_bridge::HidBridge::new()\r\n            .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n        {\r\n            Ok(()) => return Ok(()),\r\n            Err(err) => {\r\n                last_error = Some(err);\r\n                if attempt < HID_SEND_ATTEMPTS {\r\n                    thread::sleep(Duration::from_millis(HID_SEND_RETRY_DELAY_MS));\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    let err = last_error.expect(\"HID send attempts exhausted without captured error\");\r\n    log_haptics(&format!(\r\n        \"haptics: HID send failed after {} attempt(s); cmd=0x{:02X}, intensity={}, arg_lo={}, arg_hi={}, err={}\",\r\n        HID_SEND_ATTEMPTS,\r\n        packet.command_id,\r\n        packet.intensity,\r\n        packet.arg_lo,\r\n        packet.arg_hi,\r\n        err\r\n    ));\r\n    Err(err)\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet_blocking(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = amplitude_to_intensity(amp);\n    send_packet_blocking(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn amplitude_to_intensity(amplitude: f32) -> u8 {\n    (amplitude.clamp(0.0, 1.0) * 255.0).round() as u8\n}\r\n\r\nfn run_hid_worker(rx: mpsc::Receiver<WorkerCommand>) {\n    let mut active_until: Option<Instant> = None;\n\n    loop {\n        let incoming = if let Some(deadline) = active_until {\n            let now = Instant::now();\n            if now >= deadline {\n                let _ = send_stop();\n                active_until = None;\n                continue;\n            }\n\n            match rx.recv_timeout(deadline.saturating_duration_since(now)) {\n                Ok(cmd) => Some(cmd),\n                Err(mpsc::RecvTimeoutError::Timeout) => {\n                    let _ = send_stop();\n                    active_until = None;\n                    continue;\n                }\n                Err(mpsc::RecvTimeoutError::Disconnected) => None,\n            }\n        } else {\n            match rx.recv() {\n                Ok(cmd) => Some(cmd),\n                Err(_) => None,\n            }\n        };\n\n        let Some(cmd) = incoming else {\n            break;\n        };\n\n        match cmd {\n            WorkerCommand::Stop => {\n                let _ = send_stop();\n                active_until = None;\n            }\n            WorkerCommand::Pulse {\n                intensity,\n                duration_ms,\n            } => {\n                // Force a fresh motor edge so each pulse interrupts immediately.\n                let _ = send_stop();\n                let _ = send_packet_blocking(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0));\n                active_until = Some(Instant::now() + Duration::from_millis(duration_ms.max(1)));\n            }\n        }\n    }\r\n}\r\n\r\nfn hid_tx() -> &'static mpsc::Sender<WorkerCommand> {\n    HID_TX.get_or_init(|| {\n        let (tx, rx) = mpsc::channel::<WorkerCommand>();\n        std::thread::spawn(move || run_hid_worker(rx));\n        tx\n    })\n}\r\n\r\nfn queue_worker_command(cmd: WorkerCommand) {\n    if let Err(err) = hid_tx().send(cmd) {\n        log_haptics(&format!(\"haptics: worker queue send failed: {}\", err));\n    }\n}\r\n\r\nfn shape_amplitude_for_erm(amplitude: f32) -> f32 {\r\n    if amplitude <= 0.0 {\r\n        return 0.0;\r\n    }\r\n\r\n    let boosted = (amplitude * ERM_AMPLITUDE_GAIN).clamp(0.0, 1.0);\r\n    boosted.max(ERM_MIN_NONZERO_AMPLITUDE)\r\n}\r\n\r\nfn resolve_effective_duration_seconds(duration_seconds: f32, frequency_hz: f32) -> f32 {\r\n    let clamped_duration = duration_seconds.clamp(0.0, OPENVR_MAX_DURATION_SECONDS);\r\n    if clamped_duration > 0.0 {\r\n        return clamped_duration;\r\n    }\r\n\r\n    // OpenVR guidance: when duration is 0, trigger exactly one pulse.\n    // One pulse ~= one period at requested frequency; apply ERM minimum floor.\n    (1.0 / frequency_hz).max(HARDWARE_MIN_PULSE_SECONDS)\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        queue_worker_command(WorkerCommand::Stop);\n        return;\r\n    }\r\n\r\n    if req.frequency <= 0.0 {\r\n        queue_worker_command(WorkerCommand::Stop);\n        return;\r\n    }\r\n\r\n    let amplitude = shape_amplitude_for_erm(req.amplitude.clamp(0.0, 1.0));\r\n    let frequency_hz = req\r\n        .frequency\r\n        .clamp(OPENVR_MIN_FREQUENCY_HZ, OPENVR_MAX_FREQUENCY_HZ);\r\n\r\n    let effective_duration =\r\n        resolve_effective_duration_seconds(req.duration_seconds, frequency_hz);\r\n    let duration_ms = (effective_duration * 1000.0).round().max(1.0) as u64;\n\n    queue_worker_command(WorkerCommand::Pulse {\n        intensity: amplitude_to_intensity(amplitude),\n        duration_ms,\n    });\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":253,"endColumn":1}},{"text":"","range":{"startLineNumber":253,"startColumn":1,"endLineNumber":271,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_9a5be07b-e7a2-40f4-9005-8520832f2361","epoch":1741,"edits":[{"text":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse std::sync::{Condvar, Mutex, OnceLock};\nuse std::time::Duration;\r\nuse std::time::Instant;\r\nuse std::thread;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\n\r\nenum WorkerCommand {\r\n    Pulse {\r\n        intensity: u8,\r\n        duration_ms: u64,\r\n    },\r\n    Stop,\r\n}\r\n\r\n#[derive(Default)]\nstruct WorkerState {\n    pending: Option<WorkerCommand>,\n    active_until: Option<Instant>,\n}\n\nstruct WorkerMailbox {\n    state: Mutex<WorkerState>,\n    signal: Condvar,\n}\n\nstatic HID_MAILBOX: OnceLock<WorkerMailbox> = OnceLock::new();\n\r\nconst OPENVR_MIN_FREQUENCY_HZ: f32 = 1_000_000.0 / 65_535.0;\r\nconst OPENVR_MAX_FREQUENCY_HZ: f32 = 1_000_000.0 / 300.0;\r\nconst OPENVR_MAX_DURATION_SECONDS: f32 = 10.0;\r\nconst HARDWARE_MIN_PULSE_SECONDS: f32 = 0.05;\r\nconst ERM_AMPLITUDE_GAIN: f32 = 1.8;\r\nconst ERM_MIN_NONZERO_AMPLITUDE: f32 = 0.25;\r\nconst HID_SEND_ATTEMPTS: usize = 3;\r\nconst HID_SEND_RETRY_DELAY_MS: u64 = 2;\r\n\r\nfn log_haptics(message: &str) {\r\n    crate::openvr_runtime::log_driver_message(message);\r\n}\r\n\r\nfn send_packet_blocking(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    let mut last_error = None;\r\n\r\n    for attempt in 1..=HID_SEND_ATTEMPTS {\r\n        match hid_bridge::HidBridge::new()\r\n            .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n        {\r\n            Ok(()) => return Ok(()),\r\n            Err(err) => {\r\n                last_error = Some(err);\r\n                if attempt < HID_SEND_ATTEMPTS {\r\n                    thread::sleep(Duration::from_millis(HID_SEND_RETRY_DELAY_MS));\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    let err = last_error.expect(\"HID send attempts exhausted without captured error\");\r\n    log_haptics(&format!(\r\n        \"haptics: HID send failed after {} attempt(s); cmd=0x{:02X}, intensity={}, arg_lo={}, arg_hi={}, err={}\",\r\n        HID_SEND_ATTEMPTS,\r\n        packet.command_id,\r\n        packet.intensity,\r\n        packet.arg_lo,\r\n        packet.arg_hi,\r\n        err\r\n    ));\r\n    Err(err)\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet_blocking(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = amplitude_to_intensity(amp);\r\n    send_packet_blocking(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn amplitude_to_intensity(amplitude: f32) -> u8 {\r\n    (amplitude.clamp(0.0, 1.0) * 255.0).round() as u8\r\n}\r\n\r\nfn run_hid_worker(rx: mpsc::Receiver<WorkerCommand>) {\r\n    let mut active_until: Option<Instant> = None;\r\n\r\n    loop {\r\n        let incoming = if let Some(deadline) = active_until {\r\n            let now = Instant::now();\r\n            if now >= deadline {\r\n                let _ = send_stop();\r\n                active_until = None;\r\n                continue;\r\n            }\r\n\r\n            match rx.recv_timeout(deadline.saturating_duration_since(now)) {\r\n                Ok(cmd) => Some(cmd),\r\n                Err(mpsc::RecvTimeoutError::Timeout) => {\r\n                    let _ = send_stop();\r\n                    active_until = None;\r\n                    continue;\r\n                }\r\n                Err(mpsc::RecvTimeoutError::Disconnected) => None,\r\n            }\r\n        } else {\r\n            match rx.recv() {\r\n                Ok(cmd) => Some(cmd),\r\n                Err(_) => None,\r\n            }\r\n        };\r\n\r\n        let Some(cmd) = incoming else {\r\n            break;\r\n        };\r\n\r\n        match cmd {\r\n            WorkerCommand::Stop => {\r\n                let _ = send_stop();\r\n                active_until = None;\r\n            }\r\n            WorkerCommand::Pulse {\r\n                intensity,\r\n                duration_ms,\r\n            } => {\r\n                // Force a fresh motor edge so each pulse interrupts immediately.\r\n                let _ = send_stop();\r\n                let _ = send_packet_blocking(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0));\r\n                active_until = Some(Instant::now() + Duration::from_millis(duration_ms.max(1)));\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\nfn hid_tx() -> &'static mpsc::Sender<WorkerCommand> {\r\n    HID_TX.get_or_init(|| {\r\n        let (tx, rx) = mpsc::channel::<WorkerCommand>();\r\n        std::thread::spawn(move || run_hid_worker(rx));\r\n        tx\r\n    })\r\n}\r\n\r\nfn queue_worker_command(cmd: WorkerCommand) {\r\n    if let Err(err) = hid_tx().send(cmd) {\r\n        log_haptics(&format!(\"haptics: worker queue send failed: {}\", err));\r\n    }\r\n}\r\n\r\nfn shape_amplitude_for_erm(amplitude: f32) -> f32 {\r\n    if amplitude <= 0.0 {\r\n        return 0.0;\r\n    }\r\n\r\n    let boosted = (amplitude * ERM_AMPLITUDE_GAIN).clamp(0.0, 1.0);\r\n    boosted.max(ERM_MIN_NONZERO_AMPLITUDE)\r\n}\r\n\r\nfn resolve_effective_duration_seconds(duration_seconds: f32, frequency_hz: f32) -> f32 {\r\n    let clamped_duration = duration_seconds.clamp(0.0, OPENVR_MAX_DURATION_SECONDS);\r\n    if clamped_duration > 0.0 {\r\n        return clamped_duration;\r\n    }\r\n\r\n    // OpenVR guidance: when duration is 0, trigger exactly one pulse.\r\n    // One pulse ~= one period at requested frequency; apply ERM minimum floor.\r\n    (1.0 / frequency_hz).max(HARDWARE_MIN_PULSE_SECONDS)\r\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        queue_worker_command(WorkerCommand::Stop);\r\n        return;\r\n    }\r\n\r\n    if req.frequency <= 0.0 {\r\n        queue_worker_command(WorkerCommand::Stop);\r\n        return;\r\n    }\r\n\r\n    let amplitude = shape_amplitude_for_erm(req.amplitude.clamp(0.0, 1.0));\r\n    let frequency_hz = req\r\n        .frequency\r\n        .clamp(OPENVR_MIN_FREQUENCY_HZ, OPENVR_MAX_FREQUENCY_HZ);\r\n\r\n    let effective_duration =\r\n        resolve_effective_duration_seconds(req.duration_seconds, frequency_hz);\r\n    let duration_ms = (effective_duration * 1000.0).round().max(1.0) as u64;\r\n\r\n    queue_worker_command(WorkerCommand::Pulse {\r\n        intensity: amplitude_to_intensity(amplitude),\r\n        duration_ms,\r\n    });\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":264,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_9a5be07b-e7a2-40f4-9005-8520832f2361","epoch":1743,"edits":[{"text":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse std::sync::{Condvar, Mutex, OnceLock};\r\nuse std::time::Duration;\r\nuse std::time::Instant;\r\nuse std::thread;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\n\r\nenum WorkerCommand {\r\n    Pulse {\r\n        intensity: u8,\r\n        duration_ms: u64,\r\n    },\r\n    Stop,\r\n}\r\n\r\n#[derive(Default)]\r\nstruct WorkerState {\r\n    pending: Option<WorkerCommand>,\r\n    active_until: Option<Instant>,\r\n}\r\n\r\nstruct WorkerMailbox {\r\n    state: Mutex<WorkerState>,\r\n    signal: Condvar,\r\n}\r\n\r\nstatic HID_MAILBOX: OnceLock<WorkerMailbox> = OnceLock::new();\r\n\r\nconst OPENVR_MIN_FREQUENCY_HZ: f32 = 1_000_000.0 / 65_535.0;\r\nconst OPENVR_MAX_FREQUENCY_HZ: f32 = 1_000_000.0 / 300.0;\r\nconst OPENVR_MAX_DURATION_SECONDS: f32 = 10.0;\r\nconst HARDWARE_MIN_PULSE_SECONDS: f32 = 0.05;\r\nconst ERM_AMPLITUDE_GAIN: f32 = 1.8;\r\nconst ERM_MIN_NONZERO_AMPLITUDE: f32 = 0.25;\r\nconst HID_SEND_ATTEMPTS: usize = 3;\r\nconst HID_SEND_RETRY_DELAY_MS: u64 = 2;\r\n\r\nfn log_haptics(message: &str) {\r\n    crate::openvr_runtime::log_driver_message(message);\r\n}\r\n\r\nfn send_packet_blocking(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    let mut last_error = None;\r\n\r\n    for attempt in 1..=HID_SEND_ATTEMPTS {\r\n        match hid_bridge::HidBridge::new()\r\n            .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n        {\r\n            Ok(()) => return Ok(()),\r\n            Err(err) => {\r\n                last_error = Some(err);\r\n                if attempt < HID_SEND_ATTEMPTS {\r\n                    thread::sleep(Duration::from_millis(HID_SEND_RETRY_DELAY_MS));\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    let err = last_error.expect(\"HID send attempts exhausted without captured error\");\r\n    log_haptics(&format!(\r\n        \"haptics: HID send failed after {} attempt(s); cmd=0x{:02X}, intensity={}, arg_lo={}, arg_hi={}, err={}\",\r\n        HID_SEND_ATTEMPTS,\r\n        packet.command_id,\r\n        packet.intensity,\r\n        packet.arg_lo,\r\n        packet.arg_hi,\r\n        err\r\n    ));\r\n    Err(err)\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet_blocking(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = amplitude_to_intensity(amp);\r\n    send_packet_blocking(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn amplitude_to_intensity(amplitude: f32) -> u8 {\r\n    (amplitude.clamp(0.0, 1.0) * 255.0).round() as u8\r\n}\r\n\r\nfn run_hid_worker(mailbox: &'static WorkerMailbox) {\n    loop {\n        let cmd = {\n            let mut state = match mailbox.state.lock() {\n                Ok(s) => s,\n                Err(_) => continue,\n            };\n\n            while state.pending.is_none() {\n                if let Some(deadline) = state.active_until {\n                    let now = Instant::now();\n                    if now >= deadline {\n                        drop(state);\n                        let _ = send_stop();\n                        state = match mailbox.state.lock() {\n                            Ok(s) => s,\n                            Err(_) => continue,\n                        };\n                        state.active_until = None;\n                        continue;\n                    }\n\n                    let wait_dur = deadline.saturating_duration_since(now);\n                    let waited = mailbox.signal.wait_timeout(state, wait_dur);\n                    let (new_state, timeout_res) = match waited {\n                        Ok(r) => r,\n                        Err(_) => continue,\n                    };\n                    state = new_state;\n\n                    if timeout_res.timed_out() && state.pending.is_none() {\n                        drop(state);\n                        let _ = send_stop();\n                        state = match mailbox.state.lock() {\n                            Ok(s) => s,\n                            Err(_) => continue,\n                        };\n                        state.active_until = None;\n                    }\n                } else {\n                    state = match mailbox.signal.wait(state) {\n                        Ok(s) => s,\n                        Err(_) => continue,\n                    };\n                }\n            }\n\n            state.pending.take().expect(\"pending command expected\")\n        };\n\n        match cmd {\n            WorkerCommand::Stop => {\n                let _ = send_stop();\n                if let Ok(mut state) = mailbox.state.lock() {\n                    state.active_until = None;\n                }\n            }\n            WorkerCommand::Pulse {\n                intensity,\n                duration_ms,\n            } => {\n                // Force a fresh motor edge so each new pulse preempts immediately.\n                let _ = send_stop();\n                let _ = send_packet_blocking(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0));\n                if let Ok(mut state) = mailbox.state.lock() {\n                    state.active_until = Some(Instant::now() + Duration::from_millis(duration_ms.max(1)));\n                }\n            }\n        }\n    }\n}\n\nfn hid_mailbox() -> &'static WorkerMailbox {\n    HID_MAILBOX.get_or_init(|| {\n        let mailbox = WorkerMailbox {\n            state: Mutex::new(WorkerState::default()),\n            signal: Condvar::new(),\n        };\n        std::thread::spawn(|| run_hid_worker(hid_mailbox()));\n        mailbox\n    })\n}\n\nfn queue_worker_command(cmd: WorkerCommand) {\n    let mailbox = hid_mailbox();\n    if let Ok(mut state) = mailbox.state.lock() {\n        // Latest command wins: overwrite anything pending.\n        state.pending = Some(cmd);\n        mailbox.signal.notify_one();\n    } else {\n        log_haptics(\"haptics: worker queue send failed: mailbox lock poisoned\");\n    }\n}\n\r\nfn shape_amplitude_for_erm(amplitude: f32) -> f32 {\r\n    if amplitude <= 0.0 {\r\n        return 0.0;\r\n    }\r\n\r\n    let boosted = (amplitude * ERM_AMPLITUDE_GAIN).clamp(0.0, 1.0);\r\n    boosted.max(ERM_MIN_NONZERO_AMPLITUDE)\r\n}\r\n\r\nfn resolve_effective_duration_seconds(duration_seconds: f32, frequency_hz: f32) -> f32 {\r\n    let clamped_duration = duration_seconds.clamp(0.0, OPENVR_MAX_DURATION_SECONDS);\r\n    if clamped_duration > 0.0 {\r\n        return clamped_duration;\r\n    }\r\n\r\n    // OpenVR guidance: when duration is 0, trigger exactly one pulse.\r\n    // One pulse ~= one period at requested frequency; apply ERM minimum floor.\r\n    (1.0 / frequency_hz).max(HARDWARE_MIN_PULSE_SECONDS)\r\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        queue_worker_command(WorkerCommand::Stop);\r\n        return;\r\n    }\r\n\r\n    if req.frequency <= 0.0 {\r\n        queue_worker_command(WorkerCommand::Stop);\r\n        return;\r\n    }\r\n\r\n    let amplitude = shape_amplitude_for_erm(req.amplitude.clamp(0.0, 1.0));\r\n    let frequency_hz = req\r\n        .frequency\r\n        .clamp(OPENVR_MIN_FREQUENCY_HZ, OPENVR_MAX_FREQUENCY_HZ);\r\n\r\n    let effective_duration =\r\n        resolve_effective_duration_seconds(req.duration_seconds, frequency_hz);\r\n    let duration_ms = (effective_duration * 1000.0).round().max(1.0) as u64;\r\n\r\n    queue_worker_command(WorkerCommand::Pulse {\r\n        intensity: amplitude_to_intensity(amplitude),\r\n        duration_ms,\r\n    });\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":294,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_9a5be07b-e7a2-40f4-9005-8520832f2361","epoch":1745,"edits":[{"text":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse std::sync::{Condvar, Mutex, OnceLock};\r\nuse std::time::Duration;\r\nuse std::time::Instant;\r\nuse std::thread;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\n\r\nenum WorkerCommand {\r\n    Pulse {\r\n        intensity: u8,\r\n        duration_ms: u64,\r\n    },\r\n    Stop,\r\n}\r\n\r\n#[derive(Default)]\r\nstruct WorkerState {\r\n    pending: Option<WorkerCommand>,\r\n    active_until: Option<Instant>,\r\n}\r\n\r\nstruct WorkerMailbox {\r\n    state: Mutex<WorkerState>,\r\n    signal: Condvar,\r\n}\r\n\r\nstatic HID_MAILBOX: OnceLock<WorkerMailbox> = OnceLock::new();\r\n\r\nconst OPENVR_MIN_FREQUENCY_HZ: f32 = 1_000_000.0 / 65_535.0;\r\nconst OPENVR_MAX_FREQUENCY_HZ: f32 = 1_000_000.0 / 300.0;\r\nconst OPENVR_MAX_DURATION_SECONDS: f32 = 10.0;\r\nconst HARDWARE_MIN_PULSE_SECONDS: f32 = 0.05;\r\nconst ERM_MAX_PULSE_SECONDS: f32 = 0.10;\nconst ERM_AMPLITUDE_GAIN: f32 = 1.8;\r\nconst ERM_MIN_NONZERO_AMPLITUDE: f32 = 0.25;\r\nconst HID_SEND_ATTEMPTS: usize = 3;\r\nconst HID_SEND_RETRY_DELAY_MS: u64 = 2;\r\n\r\nfn log_haptics(message: &str) {\r\n    crate::openvr_runtime::log_driver_message(message);\r\n}\r\n\r\nfn send_packet_blocking(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    let mut last_error = None;\r\n\r\n    for attempt in 1..=HID_SEND_ATTEMPTS {\r\n        match hid_bridge::HidBridge::new()\r\n            .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n        {\r\n            Ok(()) => return Ok(()),\r\n            Err(err) => {\r\n                last_error = Some(err);\r\n                if attempt < HID_SEND_ATTEMPTS {\r\n                    thread::sleep(Duration::from_millis(HID_SEND_RETRY_DELAY_MS));\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    let err = last_error.expect(\"HID send attempts exhausted without captured error\");\r\n    log_haptics(&format!(\r\n        \"haptics: HID send failed after {} attempt(s); cmd=0x{:02X}, intensity={}, arg_lo={}, arg_hi={}, err={}\",\r\n        HID_SEND_ATTEMPTS,\r\n        packet.command_id,\r\n        packet.intensity,\r\n        packet.arg_lo,\r\n        packet.arg_hi,\r\n        err\r\n    ));\r\n    Err(err)\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet_blocking(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = amplitude_to_intensity(amp);\r\n    send_packet_blocking(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn amplitude_to_intensity(amplitude: f32) -> u8 {\r\n    (amplitude.clamp(0.0, 1.0) * 255.0).round() as u8\r\n}\r\n\r\nfn run_hid_worker(mailbox: &'static WorkerMailbox) {\r\n    loop {\r\n        let cmd = {\r\n            let mut state = match mailbox.state.lock() {\r\n                Ok(s) => s,\r\n                Err(_) => continue,\r\n            };\r\n\r\n            while state.pending.is_none() {\r\n                if let Some(deadline) = state.active_until {\r\n                    let now = Instant::now();\r\n                    if now >= deadline {\r\n                        drop(state);\r\n                        let _ = send_stop();\r\n                        state = match mailbox.state.lock() {\r\n                            Ok(s) => s,\r\n                            Err(_) => continue,\r\n                        };\r\n                        state.active_until = None;\r\n                        continue;\r\n                    }\r\n\r\n                    let wait_dur = deadline.saturating_duration_since(now);\r\n                    let waited = mailbox.signal.wait_timeout(state, wait_dur);\r\n                    let (new_state, timeout_res) = match waited {\r\n                        Ok(r) => r,\r\n                        Err(_) => continue,\r\n                    };\r\n                    state = new_state;\r\n\r\n                    if timeout_res.timed_out() && state.pending.is_none() {\r\n                        drop(state);\r\n                        let _ = send_stop();\r\n                        state = match mailbox.state.lock() {\r\n                            Ok(s) => s,\r\n                            Err(_) => continue,\r\n                        };\r\n                        state.active_until = None;\r\n                    }\r\n                } else {\r\n                    state = match mailbox.signal.wait(state) {\r\n                        Ok(s) => s,\r\n                        Err(_) => continue,\r\n                    };\r\n                }\r\n            }\r\n\r\n            state.pending.take().expect(\"pending command expected\")\r\n        };\r\n\r\n        match cmd {\r\n            WorkerCommand::Stop => {\r\n                let _ = send_stop();\r\n                if let Ok(mut state) = mailbox.state.lock() {\r\n                    state.active_until = None;\r\n                }\r\n            }\r\n            WorkerCommand::Pulse {\r\n                intensity,\r\n                duration_ms,\r\n            } => {\r\n                // Force a fresh motor edge so each new pulse preempts immediately.\r\n                let _ = send_stop();\r\n                let _ = send_packet_blocking(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0));\r\n                if let Ok(mut state) = mailbox.state.lock() {\r\n                    state.active_until = Some(Instant::now() + Duration::from_millis(duration_ms.max(1)));\r\n                }\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\nfn hid_mailbox() -> &'static WorkerMailbox {\r\n    HID_MAILBOX.get_or_init(|| {\r\n        let mailbox = WorkerMailbox {\r\n            state: Mutex::new(WorkerState::default()),\r\n            signal: Condvar::new(),\r\n        };\r\n        std::thread::spawn(|| run_hid_worker(hid_mailbox()));\r\n        mailbox\r\n    })\r\n}\r\n\r\nfn queue_worker_command(cmd: WorkerCommand) {\r\n    let mailbox = hid_mailbox();\r\n    if let Ok(mut state) = mailbox.state.lock() {\r\n        // Latest command wins: overwrite anything pending.\r\n        state.pending = Some(cmd);\r\n        mailbox.signal.notify_one();\r\n    } else {\r\n        log_haptics(\"haptics: worker queue send failed: mailbox lock poisoned\");\r\n    }\r\n}\r\n\r\nfn shape_amplitude_for_erm(amplitude: f32) -> f32 {\r\n    if amplitude <= 0.0 {\r\n        return 0.0;\r\n    }\r\n\r\n    let boosted = (amplitude * ERM_AMPLITUDE_GAIN).clamp(0.0, 1.0);\r\n    boosted.max(ERM_MIN_NONZERO_AMPLITUDE)\r\n}\r\n\r\nfn resolve_effective_duration_seconds(duration_seconds: f32, frequency_hz: f32) -> f32 {\r\n    let clamped_duration = duration_seconds.clamp(0.0, OPENVR_MAX_DURATION_SECONDS);\r\n    if clamped_duration > 0.0 {\r\n        return clamped_duration.min(ERM_MAX_PULSE_SECONDS);\n    }\r\n\r\n    // OpenVR guidance: when duration is 0, trigger exactly one pulse.\r\n    // One pulse ~= one period at requested frequency; apply ERM minimum floor.\r\n    (1.0 / frequency_hz)\n        .max(HARDWARE_MIN_PULSE_SECONDS)\n        .min(ERM_MAX_PULSE_SECONDS)\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        queue_worker_command(WorkerCommand::Stop);\r\n        return;\r\n    }\r\n\r\n    if req.frequency <= 0.0 {\r\n        queue_worker_command(WorkerCommand::Stop);\r\n        return;\r\n    }\r\n\r\n    let amplitude = shape_amplitude_for_erm(req.amplitude.clamp(0.0, 1.0));\r\n    let frequency_hz = req\r\n        .frequency\r\n        .clamp(OPENVR_MIN_FREQUENCY_HZ, OPENVR_MAX_FREQUENCY_HZ);\r\n\r\n    let effective_duration =\r\n        resolve_effective_duration_seconds(req.duration_seconds, frequency_hz);\r\n    let duration_ms = (effective_duration * 1000.0).round().max(1.0) as u64;\r\n\r\n    queue_worker_command(WorkerCommand::Pulse {\r\n        intensity: amplitude_to_intensity(amplitude),\r\n        duration_ms,\r\n    });\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":297,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_44941874-eadc-4ec9-81a4-51cfe2a1ad4e","epoch":1749,"edits":[{"text":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse std::sync::{Condvar, Mutex, OnceLock};\r\nuse std::time::Duration;\r\nuse std::time::Instant;\r\nuse std::thread;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\n\r\nenum WorkerCommand {\r\n    Pulse {\r\n        intensity: u8,\r\n        duration_ms: u64,\r\n    },\r\n    Stop,\r\n}\r\n\r\n#[derive(Default)]\r\nstruct WorkerState {\r\n    pending: Option<WorkerCommand>,\r\n    active_until: Option<Instant>,\r\n}\r\n\r\nstruct WorkerMailbox {\r\n    state: Mutex<WorkerState>,\r\n    signal: Condvar,\r\n}\r\n\r\nstatic HID_MAILBOX: OnceLock<WorkerMailbox> = OnceLock::new();\r\n\r\nconst OPENVR_MIN_FREQUENCY_HZ: f32 = 1_000_000.0 / 65_535.0;\r\nconst OPENVR_MAX_FREQUENCY_HZ: f32 = 1_000_000.0 / 300.0;\r\nconst OPENVR_MAX_DURATION_SECONDS: f32 = 10.0;\r\nconst HARDWARE_MIN_PULSE_SECONDS: f32 = 0.05;\r\nconst ERM_AMPLITUDE_GAIN: f32 = 1.8;\r\nconst ERM_MIN_NONZERO_AMPLITUDE: f32 = 0.25;\r\nconst HID_SEND_ATTEMPTS: usize = 3;\r\nconst HID_SEND_RETRY_DELAY_MS: u64 = 2;\r\n\r\nfn log_haptics(message: &str) {\r\n    crate::openvr_runtime::log_driver_message(message);\r\n}\r\n\r\nfn send_packet_blocking(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    let mut last_error = None;\r\n\r\n    for attempt in 1..=HID_SEND_ATTEMPTS {\r\n        match hid_bridge::HidBridge::new()\r\n            .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n        {\r\n            Ok(()) => return Ok(()),\r\n            Err(err) => {\r\n                last_error = Some(err);\r\n                if attempt < HID_SEND_ATTEMPTS {\r\n                    thread::sleep(Duration::from_millis(HID_SEND_RETRY_DELAY_MS));\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    let err = last_error.expect(\"HID send attempts exhausted without captured error\");\r\n    log_haptics(&format!(\r\n        \"haptics: HID send failed after {} attempt(s); cmd=0x{:02X}, intensity={}, arg_lo={}, arg_hi={}, err={}\",\r\n        HID_SEND_ATTEMPTS,\r\n        packet.command_id,\r\n        packet.intensity,\r\n        packet.arg_lo,\r\n        packet.arg_hi,\r\n        err\r\n    ));\r\n    Err(err)\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet_blocking(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = amplitude_to_intensity(amp);\r\n    send_packet_blocking(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn amplitude_to_intensity(amplitude: f32) -> u8 {\r\n    (amplitude.clamp(0.0, 1.0) * 255.0).round() as u8\r\n}\r\n\r\nfn run_hid_worker(mailbox: &'static WorkerMailbox) {\r\n    loop {\r\n        let cmd = {\r\n            let mut state = match mailbox.state.lock() {\r\n                Ok(s) => s,\r\n                Err(_) => continue,\r\n            };\r\n\r\n            while state.pending.is_none() {\r\n                if let Some(deadline) = state.active_until {\r\n                    let now = Instant::now();\r\n                    if now >= deadline {\r\n                        drop(state);\r\n                        let _ = send_stop();\r\n                        state = match mailbox.state.lock() {\r\n                            Ok(s) => s,\r\n                            Err(_) => continue,\r\n                        };\r\n                        state.active_until = None;\r\n                        continue;\r\n                    }\r\n\r\n                    let wait_dur = deadline.saturating_duration_since(now);\r\n                    let waited = mailbox.signal.wait_timeout(state, wait_dur);\r\n                    let (new_state, timeout_res) = match waited {\r\n                        Ok(r) => r,\r\n                        Err(_) => continue,\r\n                    };\r\n                    state = new_state;\r\n\r\n                    if timeout_res.timed_out() && state.pending.is_none() {\r\n                        drop(state);\r\n                        let _ = send_stop();\r\n                        state = match mailbox.state.lock() {\r\n                            Ok(s) => s,\r\n                            Err(_) => continue,\r\n                        };\r\n                        state.active_until = None;\r\n                    }\r\n                } else {\r\n                    state = match mailbox.signal.wait(state) {\r\n                        Ok(s) => s,\r\n                        Err(_) => continue,\r\n                    };\r\n                }\r\n            }\r\n\r\n            state.pending.take().expect(\"pending command expected\")\r\n        };\r\n\r\n        match cmd {\r\n            WorkerCommand::Stop => {\r\n                let _ = send_stop();\r\n                if let Ok(mut state) = mailbox.state.lock() {\r\n                    state.active_until = None;\r\n                }\r\n            }\r\n            WorkerCommand::Pulse {\r\n                intensity,\r\n                duration_ms,\r\n            } => {\r\n                // Force a fresh motor edge so each new pulse preempts immediately.\r\n                let _ = send_stop();\r\n                let _ = send_packet_blocking(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0));\r\n                if let Ok(mut state) = mailbox.state.lock() {\r\n                    state.active_until = Some(Instant::now() + Duration::from_millis(duration_ms.max(1)));\r\n                }\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\nfn hid_mailbox() -> &'static WorkerMailbox {\r\n    HID_MAILBOX.get_or_init(|| {\r\n        let mailbox = WorkerMailbox {\r\n            state: Mutex::new(WorkerState::default()),\r\n            signal: Condvar::new(),\r\n        };\r\n        std::thread::spawn(|| run_hid_worker(hid_mailbox()));\r\n        mailbox\r\n    })\r\n}\r\n\r\nfn queue_worker_command(cmd: WorkerCommand) {\r\n    let mailbox = hid_mailbox();\r\n    if let Ok(mut state) = mailbox.state.lock() {\r\n        // Latest command wins: overwrite anything pending.\r\n        state.pending = Some(cmd);\r\n        mailbox.signal.notify_one();\r\n    } else {\r\n        log_haptics(\"haptics: worker queue send failed: mailbox lock poisoned\");\r\n    }\r\n}\r\n\r\nfn shape_amplitude_for_erm(amplitude: f32) -> f32 {\r\n    if amplitude <= 0.0 {\r\n        return 0.0;\r\n    }\r\n\r\n    let boosted = (amplitude * ERM_AMPLITUDE_GAIN).clamp(0.0, 1.0);\r\n    boosted.max(ERM_MIN_NONZERO_AMPLITUDE)\r\n}\r\n\r\nfn resolve_effective_duration_seconds(duration_seconds: f32, frequency_hz: f32) -> f32 {\r\n    let clamped_duration = duration_seconds.clamp(0.0, OPENVR_MAX_DURATION_SECONDS);\r\n    if clamped_duration > 0.0 {\r\n        return clamped_duration;\n    }\r\n\r\n    // OpenVR guidance: when duration is 0, trigger exactly one pulse.\r\n    // One pulse ~= one period at requested frequency; apply ERM minimum floor.\r\n    (1.0 / frequency_hz).max(HARDWARE_MIN_PULSE_SECONDS)\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        queue_worker_command(WorkerCommand::Stop);\r\n        return;\r\n    }\r\n\r\n    if req.frequency <= 0.0 {\r\n        queue_worker_command(WorkerCommand::Stop);\r\n        return;\r\n    }\r\n\r\n    let amplitude = shape_amplitude_for_erm(req.amplitude.clamp(0.0, 1.0));\r\n    let frequency_hz = req\r\n        .frequency\r\n        .clamp(OPENVR_MIN_FREQUENCY_HZ, OPENVR_MAX_FREQUENCY_HZ);\r\n\r\n    let effective_duration =\r\n        resolve_effective_duration_seconds(req.duration_seconds, frequency_hz);\r\n    let duration_ms = (effective_duration * 1000.0).round().max(1.0) as u64;\r\n\r\n    queue_worker_command(WorkerCommand::Pulse {\r\n        intensity: amplitude_to_intensity(amplitude),\r\n        duration_ms,\r\n    });\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":294,"endColumn":1}},{"text":"","range":{"startLineNumber":294,"startColumn":1,"endLineNumber":297,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_44941874-eadc-4ec9-81a4-51cfe2a1ad4e","epoch":1751,"edits":[{"text":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse std::sync::{Condvar, Mutex, OnceLock};\r\nuse std::time::Duration;\r\nuse std::time::Instant;\r\nuse std::thread;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\n\r\nenum WorkerCommand {\r\n    Pulse {\r\n        intensity: u8,\r\n        duration_ms: u64,\r\n    },\r\n    Stop,\r\n}\r\n\r\n#[derive(Default)]\r\nstruct WorkerState {\r\n    pending: Option<WorkerCommand>,\r\n    active_until: Option<Instant>,\r\n}\r\n\r\nstruct WorkerMailbox {\r\n    state: Mutex<WorkerState>,\r\n    signal: Condvar,\r\n}\r\n\r\nstatic HID_MAILBOX: OnceLock<WorkerMailbox> = OnceLock::new();\r\n\r\nconst OPENVR_MIN_FREQUENCY_HZ: f32 = 1_000_000.0 / 65_535.0;\r\nconst OPENVR_MAX_FREQUENCY_HZ: f32 = 1_000_000.0 / 300.0;\r\nconst OPENVR_MAX_DURATION_SECONDS: f32 = 10.0;\r\nconst HARDWARE_MIN_PULSE_SECONDS: f32 = 0.05;\r\nconst ERM_AMPLITUDE_GAIN: f32 = 1.8;\r\nconst ERM_MIN_NONZERO_AMPLITUDE: f32 = 0.25;\r\nconst HID_SEND_ATTEMPTS: usize = 3;\r\nconst HID_SEND_RETRY_DELAY_MS: u64 = 2;\r\n\r\nfn log_haptics(message: &str) {\r\n    crate::openvr_runtime::log_driver_message(message);\r\n}\r\n\r\nfn send_packet_blocking(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    let mut last_error = None;\r\n\r\n    for attempt in 1..=HID_SEND_ATTEMPTS {\r\n        match hid_bridge::HidBridge::new()\r\n            .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n        {\r\n            Ok(()) => return Ok(()),\r\n            Err(err) => {\r\n                last_error = Some(err);\r\n                if attempt < HID_SEND_ATTEMPTS {\r\n                    thread::sleep(Duration::from_millis(HID_SEND_RETRY_DELAY_MS));\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    let err = last_error.expect(\"HID send attempts exhausted without captured error\");\r\n    log_haptics(&format!(\r\n        \"haptics: HID send failed after {} attempt(s); cmd=0x{:02X}, intensity={}, arg_lo={}, arg_hi={}, err={}\",\r\n        HID_SEND_ATTEMPTS,\r\n        packet.command_id,\r\n        packet.intensity,\r\n        packet.arg_lo,\r\n        packet.arg_hi,\r\n        err\r\n    ));\r\n    Err(err)\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet_blocking(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = amplitude_to_intensity(amp);\r\n    send_packet_blocking(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn amplitude_to_intensity(amplitude: f32) -> u8 {\r\n    (amplitude.clamp(0.0, 1.0) * 255.0).round() as u8\r\n}\r\n\r\nfn run_hid_worker(mailbox: &'static WorkerMailbox) {\r\n    'worker: loop {\n        let cmd = {\r\n            let mut state = match mailbox.state.lock() {\r\n                Ok(s) => s,\r\n                Err(_) => continue 'worker,\n            };\r\n\r\n            loop {\n                if let Some(cmd) = state.pending.take() {\n                    break cmd;\n                }\n\n                if let Some(deadline) = state.active_until {\r\n                    let now = Instant::now();\r\n                    if now >= deadline {\r\n                        state.active_until = None;\n                        drop(state);\r\n                        let _ = send_stop();\r\n                        state = match mailbox.state.lock() {\r\n                            Ok(s) => s,\r\n                            Err(_) => continue 'worker,\n                        };\r\n                        continue;\r\n                    }\r\n\r\n                    let wait_dur = deadline.saturating_duration_since(now);\r\n                    let waited = match mailbox.signal.wait_timeout(state, wait_dur) {\n                        Ok(w) => w,\n                        Err(_) => continue 'worker,\n                    };\n\n                    let (new_state, timeout_res) = match waited {\r\n                        r => r,\n                    };\r\n                    state = new_state;\r\n\r\n                    if timeout_res.timed_out() && state.pending.is_none() {\r\n                        state.active_until = None;\n                        drop(state);\r\n                        let _ = send_stop();\r\n                        state = match mailbox.state.lock() {\r\n                            Ok(s) => s,\r\n                            Err(_) => continue 'worker,\n                        };\r\n                    }\r\n                } else {\r\n                    state = match mailbox.signal.wait(state) {\r\n                        Ok(s) => s,\r\n                        Err(_) => continue 'worker,\n                    };\r\n                }\r\n            }\r\n        };\r\n\r\n        match cmd {\r\n            WorkerCommand::Stop => {\r\n                let _ = send_stop();\r\n                if let Ok(mut state) = mailbox.state.lock() {\r\n                    state.active_until = None;\r\n                }\r\n            }\r\n            WorkerCommand::Pulse {\r\n                intensity,\r\n                duration_ms,\r\n            } => {\r\n                // Force a fresh motor edge so each new pulse preempts immediately.\r\n                let _ = send_stop();\r\n                let _ = send_packet_blocking(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0));\r\n                if let Ok(mut state) = mailbox.state.lock() {\r\n                    state.active_until = Some(Instant::now() + Duration::from_millis(duration_ms.max(1)));\r\n                }\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\nfn hid_mailbox() -> &'static WorkerMailbox {\r\n    HID_MAILBOX.get_or_init(|| {\r\n        let mailbox = WorkerMailbox {\r\n            state: Mutex::new(WorkerState::default()),\r\n            signal: Condvar::new(),\r\n        };\r\n        std::thread::spawn(|| run_hid_worker(hid_mailbox()));\r\n        mailbox\r\n    })\r\n}\r\n\r\nfn queue_worker_command(cmd: WorkerCommand) {\r\n    let mailbox = hid_mailbox();\r\n    if let Ok(mut state) = mailbox.state.lock() {\r\n        // Latest command wins: overwrite anything pending.\r\n        state.pending = Some(cmd);\r\n        mailbox.signal.notify_one();\r\n    } else {\r\n        log_haptics(\"haptics: worker queue send failed: mailbox lock poisoned\");\r\n    }\r\n}\r\n\r\nfn shape_amplitude_for_erm(amplitude: f32) -> f32 {\r\n    if amplitude <= 0.0 {\r\n        return 0.0;\r\n    }\r\n\r\n    let boosted = (amplitude * ERM_AMPLITUDE_GAIN).clamp(0.0, 1.0);\r\n    boosted.max(ERM_MIN_NONZERO_AMPLITUDE)\r\n}\r\n\r\nfn resolve_effective_duration_seconds(duration_seconds: f32, frequency_hz: f32) -> f32 {\r\n    let clamped_duration = duration_seconds.clamp(0.0, OPENVR_MAX_DURATION_SECONDS);\r\n    if clamped_duration > 0.0 {\r\n        return clamped_duration;\r\n    }\r\n\r\n    // OpenVR guidance: when duration is 0, trigger exactly one pulse.\r\n    // One pulse ~= one period at requested frequency; apply ERM minimum floor.\r\n    (1.0 / frequency_hz).max(HARDWARE_MIN_PULSE_SECONDS)\r\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        queue_worker_command(WorkerCommand::Stop);\r\n        return;\r\n    }\r\n\r\n    if req.frequency <= 0.0 {\r\n        queue_worker_command(WorkerCommand::Stop);\r\n        return;\r\n    }\r\n\r\n    let amplitude = shape_amplitude_for_erm(req.amplitude.clamp(0.0, 1.0));\r\n    let frequency_hz = req\r\n        .frequency\r\n        .clamp(OPENVR_MIN_FREQUENCY_HZ, OPENVR_MAX_FREQUENCY_HZ);\r\n\r\n    let effective_duration =\r\n        resolve_effective_duration_seconds(req.duration_seconds, frequency_hz);\r\n    let duration_ms = (effective_duration * 1000.0).round().max(1.0) as u64;\r\n\r\n    queue_worker_command(WorkerCommand::Pulse {\r\n        intensity: amplitude_to_intensity(amplitude),\r\n        duration_ms,\r\n    });\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":299,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_9257a937-1bc9-4027-ad1c-0dd52ffd875f","epoch":1755,"edits":[{"text":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse std::sync::{Condvar, Mutex, OnceLock};\r\nuse std::time::Duration;\r\nuse std::time::Instant;\r\nuse std::thread;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\n\r\nenum WorkerCommand {\r\n    Pulse {\r\n        intensity: u8,\r\n        duration_ms: u64,\r\n    },\r\n    Stop,\r\n}\r\n\r\n#[derive(Default)]\r\nstruct WorkerState {\r\n    pending: Option<WorkerCommand>,\r\n    active_until: Option<Instant>,\r\n}\r\n\r\nstruct WorkerMailbox {\r\n    state: Mutex<WorkerState>,\r\n    signal: Condvar,\r\n}\r\n\r\nstatic HID_MAILBOX: OnceLock<WorkerMailbox> = OnceLock::new();\r\n\r\nconst OPENVR_MIN_FREQUENCY_HZ: f32 = 1_000_000.0 / 65_535.0;\r\nconst OPENVR_MAX_FREQUENCY_HZ: f32 = 1_000_000.0 / 300.0;\r\nconst OPENVR_MAX_DURATION_SECONDS: f32 = 10.0;\r\nconst HARDWARE_MIN_PULSE_SECONDS: f32 = 0.05;\r\nconst ERM_AMPLITUDE_GAIN: f32 = 1.8;\r\nconst ERM_MIN_NONZERO_AMPLITUDE: f32 = 0.25;\r\nconst HID_SEND_ATTEMPTS: usize = 3;\r\nconst HID_SEND_RETRY_DELAY_MS: u64 = 2;\r\n\r\nfn log_haptics(message: &str) {\r\n    crate::openvr_runtime::log_driver_message(message);\r\n}\r\n\r\nfn send_packet_blocking(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    let mut last_error = None;\r\n\r\n    for attempt in 1..=HID_SEND_ATTEMPTS {\r\n        match hid_bridge::HidBridge::new()\r\n            .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n        {\r\n            Ok(()) => return Ok(()),\r\n            Err(err) => {\r\n                last_error = Some(err);\r\n                if attempt < HID_SEND_ATTEMPTS {\r\n                    thread::sleep(Duration::from_millis(HID_SEND_RETRY_DELAY_MS));\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    let err = last_error.expect(\"HID send attempts exhausted without captured error\");\r\n    log_haptics(&format!(\r\n        \"haptics: HID send failed after {} attempt(s); cmd=0x{:02X}, intensity={}, arg_lo={}, arg_hi={}, err={}\",\r\n        HID_SEND_ATTEMPTS,\r\n        packet.command_id,\r\n        packet.intensity,\r\n        packet.arg_lo,\r\n        packet.arg_hi,\r\n        err\r\n    ));\r\n    Err(err)\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet_blocking(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = amplitude_to_intensity(amp);\r\n    send_packet_blocking(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn amplitude_to_intensity(amplitude: f32) -> u8 {\r\n    (amplitude.clamp(0.0, 1.0) * 255.0).round() as u8\r\n}\r\n\r\nfn run_hid_worker(mailbox: &'static WorkerMailbox) {\r\n    'worker: loop {\r\n        let cmd = {\r\n            let mut state = match mailbox.state.lock() {\r\n                Ok(s) => s,\r\n                Err(_) => continue 'worker,\r\n            };\r\n\r\n            loop {\r\n                if let Some(cmd) = state.pending.take() {\r\n                    break cmd;\r\n                }\r\n\r\n                if let Some(deadline) = state.active_until {\r\n                    let now = Instant::now();\r\n                    if now >= deadline {\r\n                        state.active_until = None;\r\n                        drop(state);\r\n                        let _ = send_stop();\r\n                        state = match mailbox.state.lock() {\r\n                            Ok(s) => s,\r\n                            Err(_) => continue 'worker,\r\n                        };\r\n                        continue;\r\n                    }\r\n\r\n                    let wait_dur = deadline.saturating_duration_since(now);\r\n                    let waited = match mailbox.signal.wait_timeout(state, wait_dur) {\r\n                        Ok(w) => w,\r\n                        Err(_) => continue 'worker,\r\n                    };\r\n\r\n                    let (new_state, timeout_res) = match waited {\r\n                        r => r,\r\n                    };\r\n                    state = new_state;\r\n\r\n                    if timeout_res.timed_out() && state.pending.is_none() {\r\n                        state.active_until = None;\r\n                        drop(state);\r\n                        let _ = send_stop();\r\n                        state = match mailbox.state.lock() {\r\n                            Ok(s) => s,\r\n                            Err(_) => continue 'worker,\r\n                        };\r\n                    }\r\n                } else {\r\n                    state = match mailbox.signal.wait(state) {\r\n                        Ok(s) => s,\r\n                        Err(_) => continue 'worker,\r\n                    };\r\n                }\r\n            }\r\n        };\r\n\r\n        match cmd {\r\n            WorkerCommand::Stop => {\r\n                let _ = send_stop();\r\n                if let Ok(mut state) = mailbox.state.lock() {\r\n                    state.active_until = None;\r\n                }\r\n            }\r\n            WorkerCommand::Pulse {\r\n                intensity,\r\n                duration_ms,\r\n            } => {\r\n                // Force a fresh motor edge so each new pulse preempts immediately.\r\n                let _ = send_stop();\r\n                let _ = send_packet_blocking(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0));\r\n                if let Ok(mut state) = mailbox.state.lock() {\r\n                    state.active_until = Some(Instant::now() + Duration::from_millis(duration_ms.max(1)));\r\n                }\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\nfn hid_mailbox() -> &'static WorkerMailbox {\r\n    HID_MAILBOX.get_or_init(|| {\r\n        let mailbox = WorkerMailbox {\r\n            state: Mutex::new(WorkerState::default()),\r\n            signal: Condvar::new(),\r\n        };\r\n        std::thread::spawn(|| run_hid_worker(hid_mailbox()));\r\n        mailbox\r\n    })\r\n}\r\n\r\nfn queue_worker_command(cmd: WorkerCommand) {\r\n    let mailbox = hid_mailbox();\r\n    if let Ok(mut state) = mailbox.state.lock() {\r\n        // Latest command wins: overwrite anything pending.\r\n        state.pending = Some(cmd);\r\n        mailbox.signal.notify_one();\r\n    } else {\r\n        log_haptics(\"haptics: worker queue send failed: mailbox lock poisoned\");\r\n    }\r\n}\r\n\r\nfn shape_amplitude_for_erm(amplitude: f32) -> f32 {\r\n    if amplitude <= 0.0 {\r\n        return 0.0;\r\n    }\r\n\r\n    let boosted = (amplitude * ERM_AMPLITUDE_GAIN).clamp(0.0, 1.0);\r\n    boosted.max(ERM_MIN_NONZERO_AMPLITUDE)\r\n}\r\n\r\nfn resolve_effective_duration_seconds(duration_seconds: f32, frequency_hz: f32) -> f32 {\r\n    let clamped_duration = duration_seconds.clamp(0.0, OPENVR_MAX_DURATION_SECONDS);\r\n    if clamped_duration > 0.0 {\r\n        return clamped_duration;\r\n    }\r\n\r\n    // OpenVR guidance: when duration is 0, trigger exactly one pulse.\r\n    // One pulse ~= one period at requested frequency; apply ERM minimum floor.\r\n    (1.0 / frequency_hz).max(HARDWARE_MIN_PULSE_SECONDS)\r\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        queue_worker_command(WorkerCommand::Stop);\r\n        return;\r\n    }\r\n\r\n    if req.frequency <= 0.0 {\r\n        queue_worker_command(WorkerCommand::Stop);\r\n        return;\r\n    }\r\n\r\n    let frequency_hz = req\r\n        .frequency\r\n        .clamp(OPENVR_MIN_FREQUENCY_HZ, OPENVR_MAX_FREQUENCY_HZ);\r\n\r\n    let effective_duration =\r\n        resolve_effective_duration_seconds(req.duration_seconds, frequency_hz);\r\n    let duration_ms = (effective_duration * 1000.0).round().max(1.0) as u64;\r\n\n    let amplitude = shape_amplitude_for_erm(req.amplitude.clamp(0.0, 1.0));\n\r\n    queue_worker_command(WorkerCommand::Pulse {\r\n        intensity: amplitude_to_intensity(amplitude),\r\n        duration_ms,\r\n    });\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":300,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_cdd53ada-76bc-4a42-a740-f974d081fa95","epoch":1759,"edits":[{"text":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse std::sync::{Condvar, Mutex, OnceLock};\r\nuse std::time::Duration;\r\nuse std::time::Instant;\r\nuse std::thread;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\n\r\nenum WorkerCommand {\r\n    PulseTrain {\n        intensity: u8,\r\n        pulse_on_ms: u64,\n        pulse_off_ms: u64,\n        pulse_count: u32,\n    },\r\n    Stop,\r\n}\r\n\r\n#[derive(Default)]\r\nstruct WorkerState {\r\n    pending: Option<WorkerCommand>,\r\n}\r\n\r\nstruct WorkerMailbox {\r\n    state: Mutex<WorkerState>,\r\n    signal: Condvar,\r\n}\r\n\r\nstatic HID_MAILBOX: OnceLock<WorkerMailbox> = OnceLock::new();\r\n\r\nconst OPENVR_MIN_FREQUENCY_HZ: f32 = 1_000_000.0 / 65_535.0;\r\nconst OPENVR_MAX_FREQUENCY_HZ: f32 = 1_000_000.0 / 300.0;\r\nconst OPENVR_MAX_DURATION_SECONDS: f32 = 10.0;\r\nconst HARDWARE_MIN_PULSE_SECONDS: f32 = 0.05;\r\nconst MAX_SINGLE_PULSE_SECONDS: f32 = 0.10;\nconst ERM_AMPLITUDE_GAIN: f32 = 1.8;\r\nconst ERM_MIN_NONZERO_AMPLITUDE: f32 = 0.25;\r\nconst HID_SEND_ATTEMPTS: usize = 3;\r\nconst HID_SEND_RETRY_DELAY_MS: u64 = 2;\r\n\r\nfn log_haptics(message: &str) {\r\n    crate::openvr_runtime::log_driver_message(message);\r\n}\r\n\r\nfn send_packet_blocking(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    let mut last_error = None;\r\n\r\n    for attempt in 1..=HID_SEND_ATTEMPTS {\r\n        match hid_bridge::HidBridge::new()\r\n            .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n        {\r\n            Ok(()) => return Ok(()),\r\n            Err(err) => {\r\n                last_error = Some(err);\r\n                if attempt < HID_SEND_ATTEMPTS {\r\n                    thread::sleep(Duration::from_millis(HID_SEND_RETRY_DELAY_MS));\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    let err = last_error.expect(\"HID send attempts exhausted without captured error\");\r\n    log_haptics(&format!(\r\n        \"haptics: HID send failed after {} attempt(s); cmd=0x{:02X}, intensity={}, arg_lo={}, arg_hi={}, err={}\",\r\n        HID_SEND_ATTEMPTS,\r\n        packet.command_id,\r\n        packet.intensity,\r\n        packet.arg_lo,\r\n        packet.arg_hi,\r\n        err\r\n    ));\r\n    Err(err)\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet_blocking(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = amplitude_to_intensity(amp);\r\n    send_packet_blocking(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn amplitude_to_intensity(amplitude: f32) -> u8 {\r\n    (amplitude.clamp(0.0, 1.0) * 255.0).round() as u8\r\n}\r\n\r\nfn run_hid_worker(mailbox: &'static WorkerMailbox) {\r\n    'worker: loop {\r\n        let cmd = {\r\n            let mut state = match mailbox.state.lock() {\r\n                Ok(s) => s,\r\n                Err(_) => continue 'worker,\r\n            };\r\n\r\n            loop {\r\n                if let Some(cmd) = state.pending.take() {\r\n                    break cmd;\r\n                }\r\n\r\n                if let Some(deadline) = state.active_until {\r\n                    let now = Instant::now();\r\n                    if now >= deadline {\r\n                        state.active_until = None;\r\n                        drop(state);\r\n                        let _ = send_stop();\r\n                        state = match mailbox.state.lock() {\r\n                            Ok(s) => s,\r\n                            Err(_) => continue 'worker,\r\n                        };\r\n                        continue;\r\n                    }\r\n\r\n                    let wait_dur = deadline.saturating_duration_since(now);\r\n                    let waited = match mailbox.signal.wait_timeout(state, wait_dur) {\r\n                        Ok(w) => w,\r\n                        Err(_) => continue 'worker,\r\n                    };\r\n\r\n                    let (new_state, timeout_res) = match waited {\r\n                        r => r,\r\n                    };\r\n                    state = new_state;\r\n\r\n                    if timeout_res.timed_out() && state.pending.is_none() {\r\n                        state.active_until = None;\r\n                        drop(state);\r\n                        let _ = send_stop();\r\n                        state = match mailbox.state.lock() {\r\n                            Ok(s) => s,\r\n                            Err(_) => continue 'worker,\r\n                        };\r\n                    }\r\n                } else {\r\n                    state = match mailbox.signal.wait(state) {\r\n                        Ok(s) => s,\r\n                        Err(_) => continue 'worker,\r\n                    };\r\n                }\r\n            }\r\n        };\r\n\r\n        match cmd {\r\n            WorkerCommand::Stop => {\r\n                let _ = send_stop();\r\n                if let Ok(mut state) = mailbox.state.lock() {\r\n                    state.active_until = None;\r\n                }\r\n            }\r\n            WorkerCommand::Pulse {\r\n                intensity,\r\n                duration_ms,\r\n            } => {\r\n                // Force a fresh motor edge so each new pulse preempts immediately.\r\n                let _ = send_stop();\r\n                let _ = send_packet_blocking(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0));\r\n                if let Ok(mut state) = mailbox.state.lock() {\r\n                    state.active_until = Some(Instant::now() + Duration::from_millis(duration_ms.max(1)));\r\n                }\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\nfn hid_mailbox() -> &'static WorkerMailbox {\r\n    HID_MAILBOX.get_or_init(|| {\r\n        let mailbox = WorkerMailbox {\r\n            state: Mutex::new(WorkerState::default()),\r\n            signal: Condvar::new(),\r\n        };\r\n        std::thread::spawn(|| run_hid_worker(hid_mailbox()));\r\n        mailbox\r\n    })\r\n}\r\n\r\nfn queue_worker_command(cmd: WorkerCommand) {\r\n    let mailbox = hid_mailbox();\r\n    if let Ok(mut state) = mailbox.state.lock() {\r\n        // Latest command wins: overwrite anything pending.\r\n        state.pending = Some(cmd);\r\n        mailbox.signal.notify_one();\r\n    } else {\r\n        log_haptics(\"haptics: worker queue send failed: mailbox lock poisoned\");\r\n    }\r\n}\r\n\r\nfn shape_amplitude_for_erm(amplitude: f32) -> f32 {\r\n    if amplitude <= 0.0 {\r\n        return 0.0;\r\n    }\r\n\r\n    let boosted = (amplitude * ERM_AMPLITUDE_GAIN).clamp(0.0, 1.0);\r\n    boosted.max(ERM_MIN_NONZERO_AMPLITUDE)\r\n}\r\n\r\nfn resolve_effective_duration_seconds(duration_seconds: f32, frequency_hz: f32) -> f32 {\r\n    let clamped_duration = duration_seconds.clamp(0.0, OPENVR_MAX_DURATION_SECONDS);\r\n    if clamped_duration > 0.0 {\r\n        return clamped_duration;\r\n    }\r\n\r\n    // OpenVR guidance: when duration is 0, trigger exactly one pulse.\r\n    // One pulse ~= one period at requested frequency; apply ERM minimum floor.\r\n    (1.0 / frequency_hz).max(HARDWARE_MIN_PULSE_SECONDS)\r\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        queue_worker_command(WorkerCommand::Stop);\r\n        return;\r\n    }\r\n\r\n    if req.frequency <= 0.0 {\r\n        queue_worker_command(WorkerCommand::Stop);\r\n        return;\r\n    }\r\n\r\n    let frequency_hz = req\r\n        .frequency\r\n        .clamp(OPENVR_MIN_FREQUENCY_HZ, OPENVR_MAX_FREQUENCY_HZ);\r\n\r\n    let effective_duration =\r\n        resolve_effective_duration_seconds(req.duration_seconds, frequency_hz);\r\n    let duration_ms = (effective_duration * 1000.0).round().max(1.0) as u64;\r\n\r\n    let amplitude = shape_amplitude_for_erm(req.amplitude.clamp(0.0, 1.0));\r\n\r\n    queue_worker_command(WorkerCommand::Pulse {\r\n        intensity: amplitude_to_intensity(amplitude),\r\n        duration_ms,\r\n    });\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":302,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_cdd53ada-76bc-4a42-a740-f974d081fa95","epoch":1761,"edits":[{"text":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse std::sync::{Condvar, Mutex, OnceLock};\r\nuse std::time::Duration;\r\nuse std::time::Instant;\r\nuse std::thread;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\n\r\nenum WorkerCommand {\r\n    PulseTrain {\r\n        intensity: u8,\r\n        pulse_on_ms: u64,\r\n        pulse_off_ms: u64,\r\n        pulse_count: u32,\r\n    },\r\n    Stop,\r\n}\r\n\r\n#[derive(Default)]\r\nstruct WorkerState {\r\n    pending: Option<WorkerCommand>,\r\n}\r\n\r\nstruct WorkerMailbox {\r\n    state: Mutex<WorkerState>,\r\n    signal: Condvar,\r\n}\r\n\r\nstatic HID_MAILBOX: OnceLock<WorkerMailbox> = OnceLock::new();\r\n\r\nconst OPENVR_MIN_FREQUENCY_HZ: f32 = 1_000_000.0 / 65_535.0;\r\nconst OPENVR_MAX_FREQUENCY_HZ: f32 = 1_000_000.0 / 300.0;\r\nconst OPENVR_MAX_DURATION_SECONDS: f32 = 10.0;\r\nconst HARDWARE_MIN_PULSE_SECONDS: f32 = 0.05;\r\nconst MAX_SINGLE_PULSE_SECONDS: f32 = 0.10;\r\nconst ERM_AMPLITUDE_GAIN: f32 = 1.8;\r\nconst ERM_MIN_NONZERO_AMPLITUDE: f32 = 0.25;\r\nconst HID_SEND_ATTEMPTS: usize = 3;\r\nconst HID_SEND_RETRY_DELAY_MS: u64 = 2;\r\n\r\nfn log_haptics(message: &str) {\r\n    crate::openvr_runtime::log_driver_message(message);\r\n}\r\n\r\nfn send_packet_blocking(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    let mut last_error = None;\r\n\r\n    for attempt in 1..=HID_SEND_ATTEMPTS {\r\n        match hid_bridge::HidBridge::new()\r\n            .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n        {\r\n            Ok(()) => return Ok(()),\r\n            Err(err) => {\r\n                last_error = Some(err);\r\n                if attempt < HID_SEND_ATTEMPTS {\r\n                    thread::sleep(Duration::from_millis(HID_SEND_RETRY_DELAY_MS));\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    let err = last_error.expect(\"HID send attempts exhausted without captured error\");\r\n    log_haptics(&format!(\r\n        \"haptics: HID send failed after {} attempt(s); cmd=0x{:02X}, intensity={}, arg_lo={}, arg_hi={}, err={}\",\r\n        HID_SEND_ATTEMPTS,\r\n        packet.command_id,\r\n        packet.intensity,\r\n        packet.arg_lo,\r\n        packet.arg_hi,\r\n        err\r\n    ));\r\n    Err(err)\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet_blocking(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = amplitude_to_intensity(amp);\r\n    send_packet_blocking(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn amplitude_to_intensity(amplitude: f32) -> u8 {\r\n    (amplitude.clamp(0.0, 1.0) * 255.0).round() as u8\r\n}\r\n\nfn wait_for_preempt(mailbox: &'static WorkerMailbox, timeout: Duration) -> Option<WorkerCommand> {\n    let mut state = mailbox.state.lock().ok()?;\n    if let Some(cmd) = state.pending.take() {\n        return Some(cmd);\n    }\n\n    let (mut state, _timeout_res) = mailbox.signal.wait_timeout(state, timeout).ok()?;\n    state.pending.take()\n}\n\nfn wait_for_next_command(mailbox: &'static WorkerMailbox) -> Option<WorkerCommand> {\n    let mut state = mailbox.state.lock().ok()?;\n    loop {\n        if let Some(cmd) = state.pending.take() {\n            return Some(cmd);\n        }\n\n        state = mailbox.signal.wait(state).ok()?;\n    }\n}\n\r\nfn run_hid_worker(mailbox: &'static WorkerMailbox) {\r\n    let mut cmd = match wait_for_next_command(mailbox) {\n        Some(c) => c,\n        None => return,\n    };\n\n    loop {\n        match cmd {\n            WorkerCommand::Stop => {\r\n                let _ = send_stop();\r\n                cmd = match wait_for_next_command(mailbox) {\n                    Some(c) => c,\n                    None => return,\n                };\n            }\r\n            WorkerCommand::PulseTrain {\n                intensity,\r\n                pulse_on_ms,\n                pulse_off_ms,\n                pulse_count,\n            } => {\r\n                let mut preempted = None;\n\n                for index in 0..pulse_count {\n                    let _ = send_stop();\n                    let _ = send_packet_blocking(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0));\n\n                    if let Some(next_cmd) = wait_for_preempt(mailbox, Duration::from_millis(pulse_on_ms.max(1))) {\n                        preempted = Some(next_cmd);\n                        break;\n                    }\n\n                    let _ = send_stop();\n\n                    if index + 1 < pulse_count && pulse_off_ms > 0 {\n                        if let Some(next_cmd) = wait_for_preempt(mailbox, Duration::from_millis(pulse_off_ms)) {\n                            preempted = Some(next_cmd);\n                            break;\n                        }\n                    }\n                }\r\n\n                cmd = if let Some(next_cmd) = preempted {\n                    next_cmd\n                } else {\n                    match wait_for_next_command(mailbox) {\n                        Some(c) => c,\n                        None => return,\n                    }\n                };\n            }\r\n        }\r\n    }\r\n}\r\n\r\nfn hid_mailbox() -> &'static WorkerMailbox {\r\n    HID_MAILBOX.get_or_init(|| {\r\n        let mailbox = WorkerMailbox {\r\n            state: Mutex::new(WorkerState::default()),\r\n            signal: Condvar::new(),\r\n        };\r\n        std::thread::spawn(|| run_hid_worker(hid_mailbox()));\r\n        mailbox\r\n    })\r\n}\r\n\r\nfn queue_worker_command(cmd: WorkerCommand) {\r\n    let mailbox = hid_mailbox();\r\n    if let Ok(mut state) = mailbox.state.lock() {\r\n        // Latest command wins: overwrite anything pending.\r\n        state.pending = Some(cmd);\r\n        mailbox.signal.notify_one();\r\n    } else {\r\n        log_haptics(\"haptics: worker queue send failed: mailbox lock poisoned\");\r\n    }\r\n}\r\n\r\nfn shape_amplitude_for_erm(amplitude: f32) -> f32 {\r\n    if amplitude <= 0.0 {\r\n        return 0.0;\r\n    }\r\n\r\n    let boosted = (amplitude * ERM_AMPLITUDE_GAIN).clamp(0.0, 1.0);\r\n    boosted.max(ERM_MIN_NONZERO_AMPLITUDE)\r\n}\r\n\r\nfn resolve_effective_duration_seconds(duration_seconds: f32, frequency_hz: f32) -> f32 {\r\n    let clamped_duration = duration_seconds.clamp(0.0, OPENVR_MAX_DURATION_SECONDS);\r\n    if clamped_duration > 0.0 {\r\n        return clamped_duration;\r\n    }\r\n\r\n    // OpenVR guidance: when duration is 0, trigger exactly one pulse.\r\n    // One pulse ~= one period at requested frequency; apply ERM minimum floor.\r\n    (1.0 / frequency_hz).max(HARDWARE_MIN_PULSE_SECONDS)\r\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        queue_worker_command(WorkerCommand::Stop);\r\n        return;\r\n    }\r\n\r\n    if req.frequency <= 0.0 {\r\n        queue_worker_command(WorkerCommand::Stop);\r\n        return;\r\n    }\r\n\r\n    let frequency_hz = req\r\n        .frequency\r\n        .clamp(OPENVR_MIN_FREQUENCY_HZ, OPENVR_MAX_FREQUENCY_HZ);\r\n\r\n    let effective_duration =\r\n        resolve_effective_duration_seconds(req.duration_seconds, frequency_hz);\r\n    let duration_ms = (effective_duration * 1000.0).round().max(1.0) as u64;\r\n\r\n    let amplitude = shape_amplitude_for_erm(req.amplitude.clamp(0.0, 1.0));\r\n\r\n    queue_worker_command(WorkerCommand::Pulse {\r\n        intensity: amplitude_to_intensity(amplitude),\r\n        duration_ms,\r\n    });\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":301,"endColumn":1}},{"text":"","range":{"startLineNumber":301,"startColumn":1,"endLineNumber":302,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_cdd53ada-76bc-4a42-a740-f974d081fa95","epoch":1763,"edits":[{"text":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse std::sync::{Condvar, Mutex, OnceLock};\r\nuse std::time::Duration;\r\nuse std::time::Instant;\r\nuse std::thread;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\n\r\nenum WorkerCommand {\r\n    PulseTrain {\r\n        intensity: u8,\r\n        pulse_on_ms: u64,\r\n        pulse_off_ms: u64,\r\n        pulse_count: u32,\r\n    },\r\n    Stop,\r\n}\r\n\r\n#[derive(Default)]\r\nstruct WorkerState {\r\n    pending: Option<WorkerCommand>,\r\n}\r\n\r\nstruct WorkerMailbox {\r\n    state: Mutex<WorkerState>,\r\n    signal: Condvar,\r\n}\r\n\r\nstatic HID_MAILBOX: OnceLock<WorkerMailbox> = OnceLock::new();\r\n\r\nconst OPENVR_MIN_FREQUENCY_HZ: f32 = 1_000_000.0 / 65_535.0;\r\nconst OPENVR_MAX_FREQUENCY_HZ: f32 = 1_000_000.0 / 300.0;\r\nconst OPENVR_MAX_DURATION_SECONDS: f32 = 10.0;\r\nconst HARDWARE_MIN_PULSE_SECONDS: f32 = 0.05;\r\nconst MAX_SINGLE_PULSE_SECONDS: f32 = 0.10;\r\nconst ERM_AMPLITUDE_GAIN: f32 = 1.8;\r\nconst ERM_MIN_NONZERO_AMPLITUDE: f32 = 0.25;\r\nconst HID_SEND_ATTEMPTS: usize = 3;\r\nconst HID_SEND_RETRY_DELAY_MS: u64 = 2;\r\n\r\nfn log_haptics(message: &str) {\r\n    crate::openvr_runtime::log_driver_message(message);\r\n}\r\n\r\nfn send_packet_blocking(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    let mut last_error = None;\r\n\r\n    for attempt in 1..=HID_SEND_ATTEMPTS {\r\n        match hid_bridge::HidBridge::new()\r\n            .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n        {\r\n            Ok(()) => return Ok(()),\r\n            Err(err) => {\r\n                last_error = Some(err);\r\n                if attempt < HID_SEND_ATTEMPTS {\r\n                    thread::sleep(Duration::from_millis(HID_SEND_RETRY_DELAY_MS));\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    let err = last_error.expect(\"HID send attempts exhausted without captured error\");\r\n    log_haptics(&format!(\r\n        \"haptics: HID send failed after {} attempt(s); cmd=0x{:02X}, intensity={}, arg_lo={}, arg_hi={}, err={}\",\r\n        HID_SEND_ATTEMPTS,\r\n        packet.command_id,\r\n        packet.intensity,\r\n        packet.arg_lo,\r\n        packet.arg_hi,\r\n        err\r\n    ));\r\n    Err(err)\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet_blocking(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = amplitude_to_intensity(amp);\r\n    send_packet_blocking(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn amplitude_to_intensity(amplitude: f32) -> u8 {\r\n    (amplitude.clamp(0.0, 1.0) * 255.0).round() as u8\r\n}\r\n\r\nfn wait_for_preempt(mailbox: &'static WorkerMailbox, timeout: Duration) -> Option<WorkerCommand> {\r\n    let mut state = mailbox.state.lock().ok()?;\r\n    if let Some(cmd) = state.pending.take() {\r\n        return Some(cmd);\r\n    }\r\n\r\n    let (mut state, _timeout_res) = mailbox.signal.wait_timeout(state, timeout).ok()?;\r\n    state.pending.take()\r\n}\r\n\r\nfn wait_for_next_command(mailbox: &'static WorkerMailbox) -> Option<WorkerCommand> {\r\n    let mut state = mailbox.state.lock().ok()?;\r\n    loop {\r\n        if let Some(cmd) = state.pending.take() {\r\n            return Some(cmd);\r\n        }\r\n\r\n        state = mailbox.signal.wait(state).ok()?;\r\n    }\r\n}\r\n\r\nfn run_hid_worker(mailbox: &'static WorkerMailbox) {\r\n    let mut cmd = match wait_for_next_command(mailbox) {\r\n        Some(c) => c,\r\n        None => return,\r\n    };\r\n\r\n    loop {\r\n        match cmd {\r\n            WorkerCommand::Stop => {\r\n                let _ = send_stop();\r\n                cmd = match wait_for_next_command(mailbox) {\r\n                    Some(c) => c,\r\n                    None => return,\r\n                };\r\n            }\r\n            WorkerCommand::PulseTrain {\r\n                intensity,\r\n                pulse_on_ms,\r\n                pulse_off_ms,\r\n                pulse_count,\r\n            } => {\r\n                let mut preempted = None;\r\n\r\n                for index in 0..pulse_count {\r\n                    let _ = send_stop();\r\n                    let _ = send_packet_blocking(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0));\r\n\r\n                    if let Some(next_cmd) = wait_for_preempt(mailbox, Duration::from_millis(pulse_on_ms.max(1))) {\r\n                        preempted = Some(next_cmd);\r\n                        break;\r\n                    }\r\n\r\n                    let _ = send_stop();\r\n\r\n                    if index + 1 < pulse_count && pulse_off_ms > 0 {\r\n                        if let Some(next_cmd) = wait_for_preempt(mailbox, Duration::from_millis(pulse_off_ms)) {\r\n                            preempted = Some(next_cmd);\r\n                            break;\r\n                        }\r\n                    }\r\n                }\r\n\r\n                cmd = if let Some(next_cmd) = preempted {\r\n                    next_cmd\r\n                } else {\r\n                    match wait_for_next_command(mailbox) {\r\n                        Some(c) => c,\r\n                        None => return,\r\n                    }\r\n                };\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\nfn hid_mailbox() -> &'static WorkerMailbox {\r\n    HID_MAILBOX.get_or_init(|| {\r\n        let mailbox = WorkerMailbox {\r\n            state: Mutex::new(WorkerState::default()),\r\n            signal: Condvar::new(),\r\n        };\r\n        std::thread::spawn(|| run_hid_worker(hid_mailbox()));\r\n        mailbox\r\n    })\r\n}\r\n\r\nfn queue_worker_command(cmd: WorkerCommand) {\r\n    let mailbox = hid_mailbox();\r\n    if let Ok(mut state) = mailbox.state.lock() {\r\n        // Latest command wins: overwrite anything pending.\r\n        state.pending = Some(cmd);\r\n        mailbox.signal.notify_one();\r\n    } else {\r\n        log_haptics(\"haptics: worker queue send failed: mailbox lock poisoned\");\r\n    }\r\n}\r\n\r\nfn shape_amplitude_for_erm(amplitude: f32) -> f32 {\r\n    if amplitude <= 0.0 {\r\n        return 0.0;\r\n    }\r\n\r\n    let boosted = (amplitude * ERM_AMPLITUDE_GAIN).clamp(0.0, 1.0);\r\n    boosted.max(ERM_MIN_NONZERO_AMPLITUDE)\r\n}\r\n\r\nfn resolve_effective_duration_seconds(duration_seconds: f32, frequency_hz: f32) -> f32 {\r\n    let clamped_duration = duration_seconds.clamp(0.0, OPENVR_MAX_DURATION_SECONDS);\r\n    if clamped_duration > 0.0 {\r\n        return clamped_duration;\r\n    }\r\n\r\n    // OpenVR guidance: when duration is 0, trigger exactly one pulse.\r\n    // One pulse ~= one period at requested frequency; apply ERM minimum floor.\r\n    (1.0 / frequency_hz).max(HARDWARE_MIN_PULSE_SECONDS)\r\n}\r\n\nfn resolve_pulse_train_parameters(\n    amplitude_for_timing: f32,\n    effective_duration_seconds: f32,\n    frequency_hz: f32,\n) -> (u64, u64, u32) {\n    let period_s = 1.0 / frequency_hz;\n    let max_pulse_s = (0.5 * period_s).min(MAX_SINGLE_PULSE_SECONDS);\n    let min_pulse_s = HARDWARE_MIN_PULSE_SECONDS.min(max_pulse_s);\n\n    let pulse_on_s = min_pulse_s + amplitude_for_timing * (max_pulse_s - min_pulse_s);\n    let pulse_off_s = (period_s - pulse_on_s).max(0.0);\n\n    let pulse_count = if effective_duration_seconds <= 0.0 {\n        1\n    } else {\n        (effective_duration_seconds * frequency_hz).round().max(1.0) as u32\n    };\n\n    let pulse_on_ms = (pulse_on_s * 1000.0).round().max(1.0) as u64;\n    let pulse_off_ms = (pulse_off_s * 1000.0).round().max(0.0) as u64;\n\n    (pulse_on_ms, pulse_off_ms, pulse_count)\n}\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        queue_worker_command(WorkerCommand::Stop);\r\n        return;\r\n    }\r\n\r\n    if req.frequency <= 0.0 {\r\n        queue_worker_command(WorkerCommand::Stop);\r\n        return;\r\n    }\r\n\r\n    let frequency_hz = req\r\n        .frequency\r\n        .clamp(OPENVR_MIN_FREQUENCY_HZ, OPENVR_MAX_FREQUENCY_HZ);\r\n\r\n    let effective_duration =\r\n        resolve_effective_duration_seconds(req.duration_seconds, frequency_hz);\r\n    let duration_ms = (effective_duration * 1000.0).round().max(1.0) as u64;\r\n\r\n    let amplitude = shape_amplitude_for_erm(req.amplitude.clamp(0.0, 1.0));\r\n\r\n    queue_worker_command(WorkerCommand::Pulse {\r\n        intensity: amplitude_to_intensity(amplitude),\r\n        duration_ms,\r\n    });\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":325,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_cdd53ada-76bc-4a42-a740-f974d081fa95","epoch":1765,"edits":[{"text":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse std::sync::{Condvar, Mutex, OnceLock};\r\nuse std::time::Duration;\r\nuse std::time::Instant;\r\nuse std::thread;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\n\r\nenum WorkerCommand {\r\n    PulseTrain {\r\n        intensity: u8,\r\n        pulse_on_ms: u64,\r\n        pulse_off_ms: u64,\r\n        pulse_count: u32,\r\n    },\r\n    Stop,\r\n}\r\n\r\n#[derive(Default)]\r\nstruct WorkerState {\r\n    pending: Option<WorkerCommand>,\r\n}\r\n\r\nstruct WorkerMailbox {\r\n    state: Mutex<WorkerState>,\r\n    signal: Condvar,\r\n}\r\n\r\nstatic HID_MAILBOX: OnceLock<WorkerMailbox> = OnceLock::new();\r\n\r\nconst OPENVR_MIN_FREQUENCY_HZ: f32 = 1_000_000.0 / 65_535.0;\r\nconst OPENVR_MAX_FREQUENCY_HZ: f32 = 1_000_000.0 / 300.0;\r\nconst OPENVR_MAX_DURATION_SECONDS: f32 = 10.0;\r\nconst HARDWARE_MIN_PULSE_SECONDS: f32 = 0.05;\r\nconst MAX_SINGLE_PULSE_SECONDS: f32 = 0.10;\r\nconst ERM_AMPLITUDE_GAIN: f32 = 1.8;\r\nconst ERM_MIN_NONZERO_AMPLITUDE: f32 = 0.25;\r\nconst HID_SEND_ATTEMPTS: usize = 3;\r\nconst HID_SEND_RETRY_DELAY_MS: u64 = 2;\r\n\r\nfn log_haptics(message: &str) {\r\n    crate::openvr_runtime::log_driver_message(message);\r\n}\r\n\r\nfn send_packet_blocking(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    let mut last_error = None;\r\n\r\n    for attempt in 1..=HID_SEND_ATTEMPTS {\r\n        match hid_bridge::HidBridge::new()\r\n            .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n        {\r\n            Ok(()) => return Ok(()),\r\n            Err(err) => {\r\n                last_error = Some(err);\r\n                if attempt < HID_SEND_ATTEMPTS {\r\n                    thread::sleep(Duration::from_millis(HID_SEND_RETRY_DELAY_MS));\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    let err = last_error.expect(\"HID send attempts exhausted without captured error\");\r\n    log_haptics(&format!(\r\n        \"haptics: HID send failed after {} attempt(s); cmd=0x{:02X}, intensity={}, arg_lo={}, arg_hi={}, err={}\",\r\n        HID_SEND_ATTEMPTS,\r\n        packet.command_id,\r\n        packet.intensity,\r\n        packet.arg_lo,\r\n        packet.arg_hi,\r\n        err\r\n    ));\r\n    Err(err)\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet_blocking(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = amplitude_to_intensity(amp);\r\n    send_packet_blocking(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn amplitude_to_intensity(amplitude: f32) -> u8 {\r\n    (amplitude.clamp(0.0, 1.0) * 255.0).round() as u8\r\n}\r\n\r\nfn wait_for_preempt(mailbox: &'static WorkerMailbox, timeout: Duration) -> Option<WorkerCommand> {\r\n    let mut state = mailbox.state.lock().ok()?;\r\n    if let Some(cmd) = state.pending.take() {\r\n        return Some(cmd);\r\n    }\r\n\r\n    let (mut state, _timeout_res) = mailbox.signal.wait_timeout(state, timeout).ok()?;\r\n    state.pending.take()\r\n}\r\n\r\nfn wait_for_next_command(mailbox: &'static WorkerMailbox) -> Option<WorkerCommand> {\r\n    let mut state = mailbox.state.lock().ok()?;\r\n    loop {\r\n        if let Some(cmd) = state.pending.take() {\r\n            return Some(cmd);\r\n        }\r\n\r\n        state = mailbox.signal.wait(state).ok()?;\r\n    }\r\n}\r\n\r\nfn run_hid_worker(mailbox: &'static WorkerMailbox) {\r\n    let mut cmd = match wait_for_next_command(mailbox) {\r\n        Some(c) => c,\r\n        None => return,\r\n    };\r\n\r\n    loop {\r\n        match cmd {\r\n            WorkerCommand::Stop => {\r\n                let _ = send_stop();\r\n                cmd = match wait_for_next_command(mailbox) {\r\n                    Some(c) => c,\r\n                    None => return,\r\n                };\r\n            }\r\n            WorkerCommand::PulseTrain {\r\n                intensity,\r\n                pulse_on_ms,\r\n                pulse_off_ms,\r\n                pulse_count,\r\n            } => {\r\n                let mut preempted = None;\r\n\r\n                for index in 0..pulse_count {\r\n                    let _ = send_stop();\r\n                    let _ = send_packet_blocking(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0));\r\n\r\n                    if let Some(next_cmd) = wait_for_preempt(mailbox, Duration::from_millis(pulse_on_ms.max(1))) {\r\n                        preempted = Some(next_cmd);\r\n                        break;\r\n                    }\r\n\r\n                    let _ = send_stop();\r\n\r\n                    if index + 1 < pulse_count && pulse_off_ms > 0 {\r\n                        if let Some(next_cmd) = wait_for_preempt(mailbox, Duration::from_millis(pulse_off_ms)) {\r\n                            preempted = Some(next_cmd);\r\n                            break;\r\n                        }\r\n                    }\r\n                }\r\n\r\n                cmd = if let Some(next_cmd) = preempted {\r\n                    next_cmd\r\n                } else {\r\n                    match wait_for_next_command(mailbox) {\r\n                        Some(c) => c,\r\n                        None => return,\r\n                    }\r\n                };\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\nfn hid_mailbox() -> &'static WorkerMailbox {\r\n    HID_MAILBOX.get_or_init(|| {\r\n        let mailbox = WorkerMailbox {\r\n            state: Mutex::new(WorkerState::default()),\r\n            signal: Condvar::new(),\r\n        };\r\n        std::thread::spawn(|| run_hid_worker(hid_mailbox()));\r\n        mailbox\r\n    })\r\n}\r\n\r\nfn queue_worker_command(cmd: WorkerCommand) {\r\n    let mailbox = hid_mailbox();\r\n    if let Ok(mut state) = mailbox.state.lock() {\r\n        // Latest command wins: overwrite anything pending.\r\n        state.pending = Some(cmd);\r\n        mailbox.signal.notify_one();\r\n    } else {\r\n        log_haptics(\"haptics: worker queue send failed: mailbox lock poisoned\");\r\n    }\r\n}\r\n\r\nfn shape_amplitude_for_erm(amplitude: f32) -> f32 {\r\n    if amplitude <= 0.0 {\r\n        return 0.0;\r\n    }\r\n\r\n    let boosted = (amplitude * ERM_AMPLITUDE_GAIN).clamp(0.0, 1.0);\r\n    boosted.max(ERM_MIN_NONZERO_AMPLITUDE)\r\n}\r\n\r\nfn resolve_effective_duration_seconds(duration_seconds: f32, frequency_hz: f32) -> f32 {\r\n    let clamped_duration = duration_seconds.clamp(0.0, OPENVR_MAX_DURATION_SECONDS);\r\n    if clamped_duration > 0.0 {\r\n        return clamped_duration;\r\n    }\r\n\r\n    // OpenVR guidance: when duration is 0, trigger exactly one pulse.\r\n    // One pulse ~= one period at requested frequency; apply ERM minimum floor.\r\n    (1.0 / frequency_hz).max(HARDWARE_MIN_PULSE_SECONDS)\r\n}\r\n\r\nfn resolve_pulse_train_parameters(\r\n    amplitude_for_timing: f32,\r\n    effective_duration_seconds: f32,\r\n    frequency_hz: f32,\r\n) -> (u64, u64, u32) {\r\n    let period_s = 1.0 / frequency_hz;\r\n    let max_pulse_s = (0.5 * period_s).min(MAX_SINGLE_PULSE_SECONDS);\r\n    let min_pulse_s = HARDWARE_MIN_PULSE_SECONDS.min(max_pulse_s);\r\n\r\n    let pulse_on_s = min_pulse_s + amplitude_for_timing * (max_pulse_s - min_pulse_s);\r\n    let pulse_off_s = (period_s - pulse_on_s).max(0.0);\r\n\r\n    let pulse_count = if effective_duration_seconds <= 0.0 {\r\n        1\r\n    } else {\r\n        (effective_duration_seconds * frequency_hz).round().max(1.0) as u32\r\n    };\r\n\r\n    let pulse_on_ms = (pulse_on_s * 1000.0).round().max(1.0) as u64;\r\n    let pulse_off_ms = (pulse_off_s * 1000.0).round().max(0.0) as u64;\r\n\r\n    (pulse_on_ms, pulse_off_ms, pulse_count)\r\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        queue_worker_command(WorkerCommand::Stop);\r\n        return;\r\n    }\r\n\r\n    if req.frequency <= 0.0 {\r\n        queue_worker_command(WorkerCommand::Stop);\r\n        return;\r\n    }\r\n\r\n    let amplitude_for_timing = req.amplitude.clamp(0.0, 1.0);\n    let frequency_hz = req\n        .frequency\r\n        .clamp(OPENVR_MIN_FREQUENCY_HZ, OPENVR_MAX_FREQUENCY_HZ);\r\n\r\n    let effective_duration =\r\n        resolve_effective_duration_seconds(req.duration_seconds, frequency_hz);\r\n    let (pulse_on_ms, pulse_off_ms, pulse_count) = resolve_pulse_train_parameters(\n        amplitude_for_timing,\n        effective_duration,\n        frequency_hz,\n    );\n\n    let amplitude = shape_amplitude_for_erm(amplitude_for_timing);\n\n    queue_worker_command(WorkerCommand::PulseTrain {\n        intensity: amplitude_to_intensity(amplitude),\r\n        pulse_on_ms,\n        pulse_off_ms,\n        pulse_count,\n    });\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":332,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_cdd53ada-76bc-4a42-a740-f974d081fa95","epoch":1767,"edits":[{"text":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse std::sync::{Condvar, Mutex, OnceLock};\r\nuse std::time::Duration;\r\nuse std::thread;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\n\r\nenum WorkerCommand {\r\n    PulseTrain {\r\n        intensity: u8,\r\n        pulse_on_ms: u64,\r\n        pulse_off_ms: u64,\r\n        pulse_count: u32,\r\n    },\r\n    Stop,\r\n}\r\n\r\n#[derive(Default)]\r\nstruct WorkerState {\r\n    pending: Option<WorkerCommand>,\r\n}\r\n\r\nstruct WorkerMailbox {\r\n    state: Mutex<WorkerState>,\r\n    signal: Condvar,\r\n}\r\n\r\nstatic HID_MAILBOX: OnceLock<WorkerMailbox> = OnceLock::new();\r\n\r\nconst OPENVR_MIN_FREQUENCY_HZ: f32 = 1_000_000.0 / 65_535.0;\r\nconst OPENVR_MAX_FREQUENCY_HZ: f32 = 1_000_000.0 / 300.0;\r\nconst OPENVR_MAX_DURATION_SECONDS: f32 = 10.0;\r\nconst HARDWARE_MIN_PULSE_SECONDS: f32 = 0.05;\r\nconst MAX_SINGLE_PULSE_SECONDS: f32 = 0.10;\r\nconst ERM_AMPLITUDE_GAIN: f32 = 1.8;\r\nconst ERM_MIN_NONZERO_AMPLITUDE: f32 = 0.25;\r\nconst HID_SEND_ATTEMPTS: usize = 3;\r\nconst HID_SEND_RETRY_DELAY_MS: u64 = 2;\r\n\r\nfn log_haptics(message: &str) {\r\n    crate::openvr_runtime::log_driver_message(message);\r\n}\r\n\r\nfn send_packet_blocking(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    let mut last_error = None;\r\n\r\n    for attempt in 1..=HID_SEND_ATTEMPTS {\r\n        match hid_bridge::HidBridge::new()\r\n            .and_then(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet))\r\n        {\r\n            Ok(()) => return Ok(()),\r\n            Err(err) => {\r\n                last_error = Some(err);\r\n                if attempt < HID_SEND_ATTEMPTS {\r\n                    thread::sleep(Duration::from_millis(HID_SEND_RETRY_DELAY_MS));\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    let err = last_error.expect(\"HID send attempts exhausted without captured error\");\r\n    log_haptics(&format!(\r\n        \"haptics: HID send failed after {} attempt(s); cmd=0x{:02X}, intensity={}, arg_lo={}, arg_hi={}, err={}\",\r\n        HID_SEND_ATTEMPTS,\r\n        packet.command_id,\r\n        packet.intensity,\r\n        packet.arg_lo,\r\n        packet.arg_hi,\r\n        err\r\n    ));\r\n    Err(err)\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet_blocking(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = amplitude_to_intensity(amp);\r\n    send_packet_blocking(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn amplitude_to_intensity(amplitude: f32) -> u8 {\r\n    (amplitude.clamp(0.0, 1.0) * 255.0).round() as u8\r\n}\r\n\r\nfn wait_for_preempt(mailbox: &'static WorkerMailbox, timeout: Duration) -> Option<WorkerCommand> {\r\n    let mut state = mailbox.state.lock().ok()?;\r\n    if let Some(cmd) = state.pending.take() {\r\n        return Some(cmd);\r\n    }\r\n\r\n    let (mut state, _timeout_res) = mailbox.signal.wait_timeout(state, timeout).ok()?;\r\n    state.pending.take()\r\n}\r\n\r\nfn wait_for_next_command(mailbox: &'static WorkerMailbox) -> Option<WorkerCommand> {\r\n    let mut state = mailbox.state.lock().ok()?;\r\n    loop {\r\n        if let Some(cmd) = state.pending.take() {\r\n            return Some(cmd);\r\n        }\r\n\r\n        state = mailbox.signal.wait(state).ok()?;\r\n    }\r\n}\r\n\r\nfn run_hid_worker(mailbox: &'static WorkerMailbox) {\r\n    let mut cmd = match wait_for_next_command(mailbox) {\r\n        Some(c) => c,\r\n        None => return,\r\n    };\r\n\r\n    loop {\r\n        match cmd {\r\n            WorkerCommand::Stop => {\r\n                let _ = send_stop();\r\n                cmd = match wait_for_next_command(mailbox) {\r\n                    Some(c) => c,\r\n                    None => return,\r\n                };\r\n            }\r\n            WorkerCommand::PulseTrain {\r\n                intensity,\r\n                pulse_on_ms,\r\n                pulse_off_ms,\r\n                pulse_count,\r\n            } => {\r\n                let mut preempted = None;\r\n\r\n                for index in 0..pulse_count {\r\n                    let _ = send_stop();\r\n                    let _ = send_packet_blocking(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0));\r\n\r\n                    if let Some(next_cmd) = wait_for_preempt(mailbox, Duration::from_millis(pulse_on_ms.max(1))) {\r\n                        preempted = Some(next_cmd);\r\n                        break;\r\n                    }\r\n\r\n                    let _ = send_stop();\r\n\r\n                    if index + 1 < pulse_count && pulse_off_ms > 0 {\r\n                        if let Some(next_cmd) = wait_for_preempt(mailbox, Duration::from_millis(pulse_off_ms)) {\r\n                            preempted = Some(next_cmd);\r\n                            break;\r\n                        }\r\n                    }\r\n                }\r\n\r\n                cmd = if let Some(next_cmd) = preempted {\r\n                    next_cmd\r\n                } else {\r\n                    match wait_for_next_command(mailbox) {\r\n                        Some(c) => c,\r\n                        None => return,\r\n                    }\r\n                };\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\nfn hid_mailbox() -> &'static WorkerMailbox {\r\n    HID_MAILBOX.get_or_init(|| {\r\n        let mailbox = WorkerMailbox {\r\n            state: Mutex::new(WorkerState::default()),\r\n            signal: Condvar::new(),\r\n        };\r\n        std::thread::spawn(|| run_hid_worker(hid_mailbox()));\r\n        mailbox\r\n    })\r\n}\r\n\r\nfn queue_worker_command(cmd: WorkerCommand) {\r\n    let mailbox = hid_mailbox();\r\n    if let Ok(mut state) = mailbox.state.lock() {\r\n        // Latest command wins: overwrite anything pending.\r\n        state.pending = Some(cmd);\r\n        mailbox.signal.notify_one();\r\n    } else {\r\n        log_haptics(\"haptics: worker queue send failed: mailbox lock poisoned\");\r\n    }\r\n}\r\n\r\nfn shape_amplitude_for_erm(amplitude: f32) -> f32 {\r\n    if amplitude <= 0.0 {\r\n        return 0.0;\r\n    }\r\n\r\n    let boosted = (amplitude * ERM_AMPLITUDE_GAIN).clamp(0.0, 1.0);\r\n    boosted.max(ERM_MIN_NONZERO_AMPLITUDE)\r\n}\r\n\r\nfn resolve_effective_duration_seconds(duration_seconds: f32, frequency_hz: f32) -> f32 {\r\n    let clamped_duration = duration_seconds.clamp(0.0, OPENVR_MAX_DURATION_SECONDS);\r\n    if clamped_duration > 0.0 {\r\n        return clamped_duration;\r\n    }\r\n\r\n    // OpenVR guidance: when duration is 0, trigger exactly one pulse.\r\n    // One pulse ~= one period at requested frequency; apply ERM minimum floor.\r\n    (1.0 / frequency_hz).max(HARDWARE_MIN_PULSE_SECONDS)\r\n}\r\n\r\nfn resolve_pulse_train_parameters(\r\n    amplitude_for_timing: f32,\r\n    effective_duration_seconds: f32,\r\n    frequency_hz: f32,\r\n) -> (u64, u64, u32) {\r\n    let period_s = 1.0 / frequency_hz;\r\n    let max_pulse_s = (0.5 * period_s).min(MAX_SINGLE_PULSE_SECONDS);\r\n    let min_pulse_s = HARDWARE_MIN_PULSE_SECONDS.min(max_pulse_s);\r\n\r\n    let pulse_on_s = min_pulse_s + amplitude_for_timing * (max_pulse_s - min_pulse_s);\r\n    let pulse_off_s = (period_s - pulse_on_s).max(0.0);\r\n\r\n    let pulse_count = if effective_duration_seconds <= 0.0 {\r\n        1\r\n    } else {\r\n        (effective_duration_seconds * frequency_hz).round().max(1.0) as u32\r\n    };\r\n\r\n    let pulse_on_ms = (pulse_on_s * 1000.0).round().max(1.0) as u64;\r\n    let pulse_off_ms = (pulse_off_s * 1000.0).round().max(0.0) as u64;\r\n\r\n    (pulse_on_ms, pulse_off_ms, pulse_count)\r\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        queue_worker_command(WorkerCommand::Stop);\r\n        return;\r\n    }\r\n\r\n    if req.frequency <= 0.0 {\r\n        queue_worker_command(WorkerCommand::Stop);\r\n        return;\r\n    }\r\n\r\n    let amplitude_for_timing = req.amplitude.clamp(0.0, 1.0);\r\n    let frequency_hz = req\r\n        .frequency\r\n        .clamp(OPENVR_MIN_FREQUENCY_HZ, OPENVR_MAX_FREQUENCY_HZ);\r\n\r\n    let effective_duration =\r\n        resolve_effective_duration_seconds(req.duration_seconds, frequency_hz);\r\n    let (pulse_on_ms, pulse_off_ms, pulse_count) = resolve_pulse_train_parameters(\r\n        amplitude_for_timing,\r\n        effective_duration,\r\n        frequency_hz,\r\n    );\r\n\r\n    let amplitude = shape_amplitude_for_erm(amplitude_for_timing);\r\n\r\n    queue_worker_command(WorkerCommand::PulseTrain {\r\n        intensity: amplitude_to_intensity(amplitude),\r\n        pulse_on_ms,\r\n        pulse_off_ms,\r\n        pulse_count,\r\n    });\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":331,"endColumn":1}},{"text":"","range":{"startLineNumber":331,"startColumn":1,"endLineNumber":332,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\host\\hid-bridge\\src\\lib.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/src/lib.rs","scheme":"file"},"requestId":"request_ae3b7ebe-5e42-40b0-acca-788a8aa0cfec","epoch":1771,"edits":[{"text":"use haptics_protocol::HapticPacket;\r\nuse std::sync::Mutex;\nuse thiserror::Error;\r\n\r\n#[derive(Debug, Error)]\r\npub enum BridgeError {\r\n    #[error(\"HID error: {0}\")]\r\n    Hid(#[from] hidapi::HidError),\r\n    #[error(\"short HID write: expected {expected} bytes, wrote {actual}\")]\r\n    ShortWrite { expected: usize, actual: usize },\r\n}\r\n\nstruct CachedDevice {\n    vid: u16,\n    pid: u16,\n    device: hidapi::HidDevice,\n}\n\r\npub struct HidBridge {\r\n    api: hidapi::HidApi,\r\n    cached_device: Mutex<Option<CachedDevice>>,\n}\r\n\r\nimpl HidBridge {\r\n    pub fn new() -> Result<Self, BridgeError> {\r\n        let api = hidapi::HidApi::new()?;\r\n        Ok(Self {\n            api,\n            cached_device: Mutex::new(None),\n        })\n    }\r\n\r\n    pub fn send_packet(&self, vid: u16, pid: u16, packet: HapticPacket) -> Result<(), BridgeError> {\r\n        let mut cached = self.cached_device.lock().map_err(|_| {\n            hidapi::HidError::HidApiError {\n                message: \"cached HID device lock poisoned\".to_string(),\n            }\n        })?;\n\n        if cached\n            .as_ref()\n            .map(|d| d.vid != vid || d.pid != pid)\n            .unwrap_or(true)\n        {\n            let device = self.api.open(vid, pid)?;\n            *cached = Some(CachedDevice { vid, pid, device });\n        }\n\r\n        // hidapi expects report ID in byte 0 for `write`.\r\n        // For single-report devices, report ID is usually 0.\r\n        let mut report = [0u8; 5];\r\n        report[1..].copy_from_slice(&packet.to_bytes());\r\n\r\n        let written = if let Some(current) = cached.as_ref() {\n            match current.device.write(&report) {\n                Ok(w) => w,\n                Err(_) => {\n                    // Reopen once to recover from stale handles (e.g. USB reset).\n                    let device = self.api.open(vid, pid)?;\n                    *cached = Some(CachedDevice { vid, pid, device });\n                    cached\n                        .as_ref()\n                        .expect(\"cached device should exist after reopen\")\n                        .device\n                        .write(&report)?\n                }\n            }\n        } else {\n            let device = self.api.open(vid, pid)?;\n            *cached = Some(CachedDevice { vid, pid, device });\n            cached\n                .as_ref()\n                .expect(\"cached device should exist after open\")\n                .device\n                .write(&report)?\n        };\n\n        if written != report.len() {\r\n            return Err(BridgeError::ShortWrite {\r\n                expected: report.len(),\r\n                actual: written,\r\n            });\r\n        }\r\n\r\n        Ok(())\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":89,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_ae3b7ebe-5e42-40b0-acca-788a8aa0cfec","epoch":1774,"edits":[{"text":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse std::sync::{Condvar, Mutex, OnceLock};\r\nuse std::time::Duration;\r\nuse std::thread;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\nstatic HID_BRIDGE: OnceLock<Mutex<Option<hid_bridge::HidBridge>>> = OnceLock::new();\n\r\nenum WorkerCommand {\r\n    PulseTrain {\r\n        intensity: u8,\r\n        pulse_on_ms: u64,\r\n        pulse_off_ms: u64,\r\n        pulse_count: u32,\r\n    },\r\n    Stop,\r\n}\r\n\r\n#[derive(Default)]\r\nstruct WorkerState {\r\n    pending: Option<WorkerCommand>,\r\n}\r\n\r\nstruct WorkerMailbox {\r\n    state: Mutex<WorkerState>,\r\n    signal: Condvar,\r\n}\r\n\r\nstatic HID_MAILBOX: OnceLock<WorkerMailbox> = OnceLock::new();\r\n\r\nconst OPENVR_MIN_FREQUENCY_HZ: f32 = 1_000_000.0 / 65_535.0;\r\nconst OPENVR_MAX_FREQUENCY_HZ: f32 = 1_000_000.0 / 300.0;\r\nconst OPENVR_MAX_DURATION_SECONDS: f32 = 10.0;\r\nconst HARDWARE_MIN_PULSE_SECONDS: f32 = 0.05;\r\nconst MAX_SINGLE_PULSE_SECONDS: f32 = 0.10;\r\nconst ERM_AMPLITUDE_GAIN: f32 = 1.8;\r\nconst ERM_MIN_NONZERO_AMPLITUDE: f32 = 0.25;\r\nconst HID_SEND_ATTEMPTS: usize = 3;\r\nconst HID_SEND_RETRY_DELAY_MS: u64 = 2;\r\n\r\nfn log_haptics(message: &str) {\r\n    crate::openvr_runtime::log_driver_message(message);\r\n}\r\n\nfn with_hid_bridge<F>(f: F) -> Result<(), hid_bridge::BridgeError>\nwhere\n    F: FnOnce(&hid_bridge::HidBridge) -> Result<(), hid_bridge::BridgeError>,\n{\n    let bridge_slot = HID_BRIDGE.get_or_init(|| Mutex::new(None));\n    let mut guard = match bridge_slot.lock() {\n        Ok(g) => g,\n        Err(_) => return hid_bridge::HidBridge::new().and_then(|bridge| f(&bridge)),\n    };\n\n    if guard.is_none() {\n        *guard = Some(hid_bridge::HidBridge::new()?);\n    }\n\n    if let Some(bridge) = guard.as_ref() {\n        f(bridge)\n    } else {\n        Err(hid_bridge::BridgeError::Hid(hidapi::HidError::HidApiError {\n            message: \"failed to initialize HID bridge\".to_string(),\n        }))\n    }\n}\n\r\nfn send_packet_blocking(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    let mut last_error = None;\r\n\r\n    for attempt in 1..=HID_SEND_ATTEMPTS {\r\n        match with_hid_bridge(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet)) {\n            Ok(()) => return Ok(()),\r\n            Err(err) => {\r\n                last_error = Some(err);\r\n                if attempt < HID_SEND_ATTEMPTS {\r\n                    thread::sleep(Duration::from_millis(HID_SEND_RETRY_DELAY_MS));\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    let err = last_error.expect(\"HID send attempts exhausted without captured error\");\r\n    log_haptics(&format!(\r\n        \"haptics: HID send failed after {} attempt(s); cmd=0x{:02X}, intensity={}, arg_lo={}, arg_hi={}, err={}\",\r\n        HID_SEND_ATTEMPTS,\r\n        packet.command_id,\r\n        packet.intensity,\r\n        packet.arg_lo,\r\n        packet.arg_hi,\r\n        err\r\n    ));\r\n    Err(err)\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet_blocking(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = amplitude_to_intensity(amp);\r\n    send_packet_blocking(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn amplitude_to_intensity(amplitude: f32) -> u8 {\r\n    (amplitude.clamp(0.0, 1.0) * 255.0).round() as u8\r\n}\r\n\r\nfn wait_for_preempt(mailbox: &'static WorkerMailbox, timeout: Duration) -> Option<WorkerCommand> {\r\n    let mut state = mailbox.state.lock().ok()?;\r\n    if let Some(cmd) = state.pending.take() {\r\n        return Some(cmd);\r\n    }\r\n\r\n    let (mut state, _timeout_res) = mailbox.signal.wait_timeout(state, timeout).ok()?;\r\n    state.pending.take()\r\n}\r\n\r\nfn wait_for_next_command(mailbox: &'static WorkerMailbox) -> Option<WorkerCommand> {\r\n    let mut state = mailbox.state.lock().ok()?;\r\n    loop {\r\n        if let Some(cmd) = state.pending.take() {\r\n            return Some(cmd);\r\n        }\r\n\r\n        state = mailbox.signal.wait(state).ok()?;\r\n    }\r\n}\r\n\r\nfn run_hid_worker(mailbox: &'static WorkerMailbox) {\r\n    let mut cmd = match wait_for_next_command(mailbox) {\r\n        Some(c) => c,\r\n        None => return,\r\n    };\r\n\r\n    loop {\r\n        match cmd {\r\n            WorkerCommand::Stop => {\r\n                let _ = send_stop();\r\n                cmd = match wait_for_next_command(mailbox) {\r\n                    Some(c) => c,\r\n                    None => return,\r\n                };\r\n            }\r\n            WorkerCommand::PulseTrain {\r\n                intensity,\r\n                pulse_on_ms,\r\n                pulse_off_ms,\r\n                pulse_count,\r\n            } => {\r\n                let mut preempted = None;\r\n\r\n                for index in 0..pulse_count {\r\n                    let _ = send_stop();\r\n                    let _ = send_packet_blocking(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0));\r\n\r\n                    if let Some(next_cmd) = wait_for_preempt(mailbox, Duration::from_millis(pulse_on_ms.max(1))) {\r\n                        preempted = Some(next_cmd);\r\n                        break;\r\n                    }\r\n\r\n                    let _ = send_stop();\r\n\r\n                    if index + 1 < pulse_count && pulse_off_ms > 0 {\r\n                        if let Some(next_cmd) = wait_for_preempt(mailbox, Duration::from_millis(pulse_off_ms)) {\r\n                            preempted = Some(next_cmd);\r\n                            break;\r\n                        }\r\n                    }\r\n                }\r\n\r\n                cmd = if let Some(next_cmd) = preempted {\r\n                    next_cmd\r\n                } else {\r\n                    match wait_for_next_command(mailbox) {\r\n                        Some(c) => c,\r\n                        None => return,\r\n                    }\r\n                };\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\nfn hid_mailbox() -> &'static WorkerMailbox {\r\n    HID_MAILBOX.get_or_init(|| {\r\n        let mailbox = WorkerMailbox {\r\n            state: Mutex::new(WorkerState::default()),\r\n            signal: Condvar::new(),\r\n        };\r\n        std::thread::spawn(|| run_hid_worker(hid_mailbox()));\r\n        mailbox\r\n    })\r\n}\r\n\r\nfn queue_worker_command(cmd: WorkerCommand) {\r\n    let mailbox = hid_mailbox();\r\n    if let Ok(mut state) = mailbox.state.lock() {\r\n        // Latest command wins: overwrite anything pending.\r\n        state.pending = Some(cmd);\r\n        mailbox.signal.notify_one();\r\n    } else {\r\n        log_haptics(\"haptics: worker queue send failed: mailbox lock poisoned\");\r\n    }\r\n}\r\n\r\nfn shape_amplitude_for_erm(amplitude: f32) -> f32 {\r\n    if amplitude <= 0.0 {\r\n        return 0.0;\r\n    }\r\n\r\n    let boosted = (amplitude * ERM_AMPLITUDE_GAIN).clamp(0.0, 1.0);\r\n    boosted.max(ERM_MIN_NONZERO_AMPLITUDE)\r\n}\r\n\r\nfn resolve_effective_duration_seconds(duration_seconds: f32, frequency_hz: f32) -> f32 {\r\n    let clamped_duration = duration_seconds.clamp(0.0, OPENVR_MAX_DURATION_SECONDS);\r\n    if clamped_duration > 0.0 {\r\n        return clamped_duration;\r\n    }\r\n\r\n    // OpenVR guidance: when duration is 0, trigger exactly one pulse.\r\n    // One pulse ~= one period at requested frequency; apply ERM minimum floor.\r\n    (1.0 / frequency_hz).max(HARDWARE_MIN_PULSE_SECONDS)\r\n}\r\n\r\nfn resolve_pulse_train_parameters(\r\n    amplitude_for_timing: f32,\r\n    effective_duration_seconds: f32,\r\n    frequency_hz: f32,\r\n) -> (u64, u64, u32) {\r\n    let period_s = 1.0 / frequency_hz;\r\n    let max_pulse_s = (0.5 * period_s).min(MAX_SINGLE_PULSE_SECONDS);\r\n    let min_pulse_s = HARDWARE_MIN_PULSE_SECONDS.min(max_pulse_s);\r\n\r\n    let pulse_on_s = min_pulse_s + amplitude_for_timing * (max_pulse_s - min_pulse_s);\r\n    let pulse_off_s = (period_s - pulse_on_s).max(0.0);\r\n\r\n    let pulse_count = if effective_duration_seconds <= 0.0 {\r\n        1\r\n    } else {\r\n        (effective_duration_seconds * frequency_hz).round().max(1.0) as u32\r\n    };\r\n\r\n    let pulse_on_ms = (pulse_on_s * 1000.0).round().max(1.0) as u64;\r\n    let pulse_off_ms = (pulse_off_s * 1000.0).round().max(0.0) as u64;\r\n\r\n    (pulse_on_ms, pulse_off_ms, pulse_count)\r\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        queue_worker_command(WorkerCommand::Stop);\r\n        return;\r\n    }\r\n\r\n    if req.frequency <= 0.0 {\r\n        queue_worker_command(WorkerCommand::Stop);\r\n        return;\r\n    }\r\n\r\n    let amplitude_for_timing = req.amplitude.clamp(0.0, 1.0);\r\n    let frequency_hz = req\r\n        .frequency\r\n        .clamp(OPENVR_MIN_FREQUENCY_HZ, OPENVR_MAX_FREQUENCY_HZ);\r\n\r\n    let effective_duration =\r\n        resolve_effective_duration_seconds(req.duration_seconds, frequency_hz);\r\n    let (pulse_on_ms, pulse_off_ms, pulse_count) = resolve_pulse_train_parameters(\r\n        amplitude_for_timing,\r\n        effective_duration,\r\n        frequency_hz,\r\n    );\r\n\r\n    let amplitude = shape_amplitude_for_erm(amplitude_for_timing);\r\n\r\n    queue_worker_command(WorkerCommand::PulseTrain {\r\n        intensity: amplitude_to_intensity(amplitude),\r\n        pulse_on_ms,\r\n        pulse_off_ms,\r\n        pulse_count,\r\n    });\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":353,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_ae3b7ebe-5e42-40b0-acca-788a8aa0cfec","epoch":1776,"edits":[{"text":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse std::sync::{Condvar, Mutex, OnceLock};\r\nuse std::time::Duration;\r\nuse std::thread;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\nstatic HID_BRIDGE: OnceLock<Mutex<Option<hid_bridge::HidBridge>>> = OnceLock::new();\r\n\r\nenum WorkerCommand {\r\n    PulseTrain {\r\n        intensity: u8,\r\n        pulse_on_ms: u64,\r\n        pulse_off_ms: u64,\r\n        pulse_count: u32,\r\n    },\r\n    Stop,\r\n}\r\n\r\n#[derive(Default)]\r\nstruct WorkerState {\r\n    pending: Option<WorkerCommand>,\r\n}\r\n\r\nstruct WorkerMailbox {\r\n    state: Mutex<WorkerState>,\r\n    signal: Condvar,\r\n}\r\n\r\nstatic HID_MAILBOX: OnceLock<WorkerMailbox> = OnceLock::new();\r\n\r\nconst OPENVR_MIN_FREQUENCY_HZ: f32 = 1_000_000.0 / 65_535.0;\r\nconst OPENVR_MAX_FREQUENCY_HZ: f32 = 1_000_000.0 / 300.0;\r\nconst OPENVR_MAX_DURATION_SECONDS: f32 = 10.0;\r\nconst HARDWARE_MIN_PULSE_SECONDS: f32 = 0.05;\r\nconst MAX_SINGLE_PULSE_SECONDS: f32 = 0.10;\r\nconst ERM_AMPLITUDE_GAIN: f32 = 1.8;\r\nconst ERM_MIN_NONZERO_AMPLITUDE: f32 = 0.25;\r\nconst HID_SEND_ATTEMPTS: usize = 3;\r\nconst HID_SEND_RETRY_DELAY_MS: u64 = 2;\r\n\r\nfn log_haptics(message: &str) {\r\n    crate::openvr_runtime::log_driver_message(message);\r\n}\r\n\r\nfn with_hid_bridge<F>(f: F) -> Result<(), hid_bridge::BridgeError>\r\nwhere\r\n    F: FnOnce(&hid_bridge::HidBridge) -> Result<(), hid_bridge::BridgeError>,\r\n{\r\n    let bridge_slot = HID_BRIDGE.get_or_init(|| Mutex::new(None));\r\n    let mut guard = match bridge_slot.lock() {\r\n        Ok(g) => g,\r\n        Err(_) => return hid_bridge::HidBridge::new().and_then(|bridge| f(&bridge)),\r\n    };\r\n\r\n    if guard.is_none() {\r\n        *guard = Some(hid_bridge::HidBridge::new()?);\r\n    }\r\n\r\n    let bridge = guard.as_ref().expect(\"HID bridge should be initialized\");\n    f(bridge)\n}\r\n\r\nfn send_packet_blocking(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    let mut last_error = None;\r\n\r\n    for attempt in 1..=HID_SEND_ATTEMPTS {\r\n        match with_hid_bridge(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet)) {\r\n            Ok(()) => return Ok(()),\r\n            Err(err) => {\r\n                last_error = Some(err);\r\n                if attempt < HID_SEND_ATTEMPTS {\r\n                    thread::sleep(Duration::from_millis(HID_SEND_RETRY_DELAY_MS));\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    let err = last_error.expect(\"HID send attempts exhausted without captured error\");\r\n    log_haptics(&format!(\r\n        \"haptics: HID send failed after {} attempt(s); cmd=0x{:02X}, intensity={}, arg_lo={}, arg_hi={}, err={}\",\r\n        HID_SEND_ATTEMPTS,\r\n        packet.command_id,\r\n        packet.intensity,\r\n        packet.arg_lo,\r\n        packet.arg_hi,\r\n        err\r\n    ));\r\n    Err(err)\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet_blocking(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = amplitude_to_intensity(amp);\r\n    send_packet_blocking(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn amplitude_to_intensity(amplitude: f32) -> u8 {\r\n    (amplitude.clamp(0.0, 1.0) * 255.0).round() as u8\r\n}\r\n\r\nfn wait_for_preempt(mailbox: &'static WorkerMailbox, timeout: Duration) -> Option<WorkerCommand> {\r\n    let mut state = mailbox.state.lock().ok()?;\r\n    if let Some(cmd) = state.pending.take() {\r\n        return Some(cmd);\r\n    }\r\n\r\n    let (mut state, _timeout_res) = mailbox.signal.wait_timeout(state, timeout).ok()?;\r\n    state.pending.take()\r\n}\r\n\r\nfn wait_for_next_command(mailbox: &'static WorkerMailbox) -> Option<WorkerCommand> {\r\n    let mut state = mailbox.state.lock().ok()?;\r\n    loop {\r\n        if let Some(cmd) = state.pending.take() {\r\n            return Some(cmd);\r\n        }\r\n\r\n        state = mailbox.signal.wait(state).ok()?;\r\n    }\r\n}\r\n\r\nfn run_hid_worker(mailbox: &'static WorkerMailbox) {\r\n    let mut cmd = match wait_for_next_command(mailbox) {\r\n        Some(c) => c,\r\n        None => return,\r\n    };\r\n\r\n    loop {\r\n        match cmd {\r\n            WorkerCommand::Stop => {\r\n                let _ = send_stop();\r\n                cmd = match wait_for_next_command(mailbox) {\r\n                    Some(c) => c,\r\n                    None => return,\r\n                };\r\n            }\r\n            WorkerCommand::PulseTrain {\r\n                intensity,\r\n                pulse_on_ms,\r\n                pulse_off_ms,\r\n                pulse_count,\r\n            } => {\r\n                let mut preempted = None;\r\n\r\n                for index in 0..pulse_count {\r\n                    let _ = send_stop();\r\n                    let _ = send_packet_blocking(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0));\r\n\r\n                    if let Some(next_cmd) = wait_for_preempt(mailbox, Duration::from_millis(pulse_on_ms.max(1))) {\r\n                        preempted = Some(next_cmd);\r\n                        break;\r\n                    }\r\n\r\n                    let _ = send_stop();\r\n\r\n                    if index + 1 < pulse_count && pulse_off_ms > 0 {\r\n                        if let Some(next_cmd) = wait_for_preempt(mailbox, Duration::from_millis(pulse_off_ms)) {\r\n                            preempted = Some(next_cmd);\r\n                            break;\r\n                        }\r\n                    }\r\n                }\r\n\r\n                cmd = if let Some(next_cmd) = preempted {\r\n                    next_cmd\r\n                } else {\r\n                    match wait_for_next_command(mailbox) {\r\n                        Some(c) => c,\r\n                        None => return,\r\n                    }\r\n                };\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\nfn hid_mailbox() -> &'static WorkerMailbox {\r\n    HID_MAILBOX.get_or_init(|| {\r\n        let mailbox = WorkerMailbox {\r\n            state: Mutex::new(WorkerState::default()),\r\n            signal: Condvar::new(),\r\n        };\r\n        std::thread::spawn(|| run_hid_worker(hid_mailbox()));\r\n        mailbox\r\n    })\r\n}\r\n\r\nfn queue_worker_command(cmd: WorkerCommand) {\r\n    let mailbox = hid_mailbox();\r\n    if let Ok(mut state) = mailbox.state.lock() {\r\n        // Latest command wins: overwrite anything pending.\r\n        state.pending = Some(cmd);\r\n        mailbox.signal.notify_one();\r\n    } else {\r\n        log_haptics(\"haptics: worker queue send failed: mailbox lock poisoned\");\r\n    }\r\n}\r\n\r\nfn shape_amplitude_for_erm(amplitude: f32) -> f32 {\r\n    if amplitude <= 0.0 {\r\n        return 0.0;\r\n    }\r\n\r\n    let boosted = (amplitude * ERM_AMPLITUDE_GAIN).clamp(0.0, 1.0);\r\n    boosted.max(ERM_MIN_NONZERO_AMPLITUDE)\r\n}\r\n\r\nfn resolve_effective_duration_seconds(duration_seconds: f32, frequency_hz: f32) -> f32 {\r\n    let clamped_duration = duration_seconds.clamp(0.0, OPENVR_MAX_DURATION_SECONDS);\r\n    if clamped_duration > 0.0 {\r\n        return clamped_duration;\r\n    }\r\n\r\n    // OpenVR guidance: when duration is 0, trigger exactly one pulse.\r\n    // One pulse ~= one period at requested frequency; apply ERM minimum floor.\r\n    (1.0 / frequency_hz).max(HARDWARE_MIN_PULSE_SECONDS)\r\n}\r\n\r\nfn resolve_pulse_train_parameters(\r\n    amplitude_for_timing: f32,\r\n    effective_duration_seconds: f32,\r\n    frequency_hz: f32,\r\n) -> (u64, u64, u32) {\r\n    let period_s = 1.0 / frequency_hz;\r\n    let max_pulse_s = (0.5 * period_s).min(MAX_SINGLE_PULSE_SECONDS);\r\n    let min_pulse_s = HARDWARE_MIN_PULSE_SECONDS.min(max_pulse_s);\r\n\r\n    let pulse_on_s = min_pulse_s + amplitude_for_timing * (max_pulse_s - min_pulse_s);\r\n    let pulse_off_s = (period_s - pulse_on_s).max(0.0);\r\n\r\n    let pulse_count = if effective_duration_seconds <= 0.0 {\r\n        1\r\n    } else {\r\n        (effective_duration_seconds * frequency_hz).round().max(1.0) as u32\r\n    };\r\n\r\n    let pulse_on_ms = (pulse_on_s * 1000.0).round().max(1.0) as u64;\r\n    let pulse_off_ms = (pulse_off_s * 1000.0).round().max(0.0) as u64;\r\n\r\n    (pulse_on_ms, pulse_off_ms, pulse_count)\r\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        queue_worker_command(WorkerCommand::Stop);\r\n        return;\r\n    }\r\n\r\n    if req.frequency <= 0.0 {\r\n        queue_worker_command(WorkerCommand::Stop);\r\n        return;\r\n    }\r\n\r\n    let amplitude_for_timing = req.amplitude.clamp(0.0, 1.0);\r\n    let frequency_hz = req\r\n        .frequency\r\n        .clamp(OPENVR_MIN_FREQUENCY_HZ, OPENVR_MAX_FREQUENCY_HZ);\r\n\r\n    let effective_duration =\r\n        resolve_effective_duration_seconds(req.duration_seconds, frequency_hz);\r\n    let (pulse_on_ms, pulse_off_ms, pulse_count) = resolve_pulse_train_parameters(\r\n        amplitude_for_timing,\r\n        effective_duration,\r\n        frequency_hz,\r\n    );\r\n\r\n    let amplitude = shape_amplitude_for_erm(amplitude_for_timing);\r\n\r\n    queue_worker_command(WorkerCommand::PulseTrain {\r\n        intensity: amplitude_to_intensity(amplitude),\r\n        pulse_on_ms,\r\n        pulse_off_ms,\r\n        pulse_count,\r\n    });\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":348,"endColumn":1}},{"text":"","range":{"startLineNumber":348,"startColumn":1,"endLineNumber":353,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\firmware\\qtpy-samd21\\src\\drv2605l.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","scheme":"file"},"requestId":"request_ae3b7ebe-5e42-40b0-acca-788a8aa0cfec","epoch":1779,"edits":[{"text":"use embedded_hal::i2c::I2c;\r\n\r\nuse crate::drv2605l_parser::Drv2605lCommand;\r\n\r\npub const DRV2605L_I2C_ADDR: u8 = 0x5a;\r\n\r\nconst REG_MODE: u8 = 0x01;\r\nconst REG_RTP_INPUT: u8 = 0x02;\r\nconst REG_LIBRARY_SELECTION: u8 = 0x03;\r\nconst REG_WAVEFORM_SEQ1: u8 = 0x04;\r\nconst REG_WAVEFORM_SEQ2: u8 = 0x05;\r\nconst REG_GO: u8 = 0x0c;\r\n\r\nconst MODE_INTERNAL_TRIGGER: u8 = 0x00;\r\nconst MODE_REALTIME_PLAYBACK: u8 = 0x05;\r\n\r\n#[derive(Clone, Copy, Debug, Eq, PartialEq)]\r\npub enum DispatchError<E> {\r\n    Parse(crate::drv2605l_parser::ParseError),\r\n    I2c(E),\r\n}\r\n\r\npub struct Drv2605l<I2C> {\r\n    i2c: I2C,\r\n    address: u8,\r\n}\r\n\r\nimpl<I2C> Drv2605l<I2C>\r\nwhere\r\n    I2C: I2c,\r\n{\r\n    pub fn new(i2c: I2C) -> Self {\r\n        Self {\r\n            i2c,\r\n            address: DRV2605L_I2C_ADDR,\r\n        }\r\n    }\r\n\r\n    pub fn with_address(i2c: I2C, address: u8) -> Self {\r\n        Self { i2c, address }\r\n    }\r\n\r\n    pub fn release(self) -> I2C {\r\n        self.i2c\r\n    }\r\n\r\n    pub fn write_register(&mut self, register: u8, value: u8) -> Result<(), I2C::Error> {\r\n        self.i2c.write(self.address, &[register, value])\r\n    }\r\n\r\n    pub fn set_rtp_mode(&mut self) -> Result<(), I2C::Error> {\r\n        self.write_register(REG_MODE, MODE_REALTIME_PLAYBACK)\r\n    }\r\n\r\n    pub fn set_rtp_input(&mut self, intensity: u8) -> Result<(), I2C::Error> {\r\n        self.write_register(REG_RTP_INPUT, intensity)\r\n    }\r\n\r\n    pub fn trigger_rom_effect(&mut self, effect_id: u8) -> Result<(), I2C::Error> {\r\n        self.write_register(REG_MODE, MODE_INTERNAL_TRIGGER)?;\r\n        self.write_register(REG_LIBRARY_SELECTION, 1)?;\r\n        self.write_register(REG_WAVEFORM_SEQ1, effect_id)?;\r\n        self.write_register(REG_WAVEFORM_SEQ2, 0)?;\r\n        self.write_register(REG_GO, 1)\r\n    }\r\n\r\n    pub fn stop(&mut self) -> Result<(), I2C::Error> {\r\n        self.write_register(REG_GO, 0)?;\r\n        self.set_rtp_mode()?;\r\n        self.set_rtp_input(0)\r\n    }\r\n\r\n    pub fn apply_command(&mut self, cmd: Drv2605lCommand) -> Result<(), I2C::Error> {\r\n        match cmd {\r\n            Drv2605lCommand::SetIntensity { intensity } => {\r\n                self.set_rtp_mode()?;\r\n                self.set_rtp_input(intensity)\r\n            }\r\n            Drv2605lCommand::TriggerRomEffect { effect_id: _, intensity } => {\n                // Keep behavior fully interruptible by always using RTP path.\n                // ROM/internal-trigger effects can queue and feel delayed on hover.\n                self.set_rtp_mode()?;\n                self.set_rtp_input(intensity)\n            }\r\n            Drv2605lCommand::Stop => self.stop(),\r\n            Drv2605lCommand::Unknown { .. } => Ok(()),\r\n        }\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":91,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\openvr-driver\\big-haptic-driver\\src\\haptics.rs","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","scheme":"file"},"requestId":"request_61a7ab1c-d8bf-428d-b25c-57a9185ebc48","epoch":1783,"edits":[{"text":"//! Haptics domain logic for the Big Haptic Driver.\r\n//!\r\n//! This module owns:\r\n//! - mapping vibration requests to HID packets,\r\n//! - scheduling delayed stop behavior,\r\n//! - polling an optional host-provided callback each frame.\r\n\r\nuse std::sync::{Condvar, Mutex, OnceLock};\r\nuse std::time::Duration;\r\nuse std::thread;\r\n\r\nuse haptics_protocol::{command, HapticPacket, DEFAULT_USB_PID, DEFAULT_USB_VID};\r\n\r\n#[repr(C)]\r\n#[derive(Clone, Copy, Default)]\r\n/// C-compatible vibration request passed across the temporary OpenVR bridge.\r\npub struct HapticVibrationRequest {\r\n    /// Normalized amplitude in the range `[0.0, 1.0]`.\r\n    pub amplitude: f32,\r\n    /// Requested pulse length in seconds. Values `<= 0` clear scheduled stop.\r\n    pub duration_seconds: f32,\r\n    /// Requested frequency in Hz (currently accepted, not yet hardware-shaped).\r\n    pub frequency: f32,\r\n}\r\n\r\n/// C callback signature for polling pending haptic requests from host/OpenVR.\r\n///\r\n/// Return `true` when a request was written to the output pointer, otherwise\r\n/// return `false` to indicate no more requests for this frame.\r\npub type PollHapticCallback = extern \"C\" fn(*mut HapticVibrationRequest) -> bool;\r\n\r\nstatic POLL_HAPTIC_CB: Mutex<Option<PollHapticCallback>> = Mutex::new(None);\r\nstatic HID_BRIDGE: OnceLock<Mutex<Option<hid_bridge::HidBridge>>> = OnceLock::new();\r\n\r\nenum WorkerCommand {\r\n    PulseTrain {\r\n        intensity: u8,\r\n        pulse_on_ms: u64,\r\n        pulse_off_ms: u64,\r\n        pulse_count: u32,\r\n    },\r\n    Stop,\r\n}\r\n\r\n#[derive(Default)]\r\nstruct WorkerState {\r\n    pending: Option<WorkerCommand>,\r\n}\r\n\r\nstruct WorkerMailbox {\r\n    state: Mutex<WorkerState>,\r\n    signal: Condvar,\r\n}\r\n\r\nstatic HID_MAILBOX: OnceLock<WorkerMailbox> = OnceLock::new();\r\n\r\nconst OPENVR_MIN_FREQUENCY_HZ: f32 = 1_000_000.0 / 65_535.0;\r\nconst OPENVR_MAX_FREQUENCY_HZ: f32 = 1_000_000.0 / 300.0;\r\nconst OPENVR_MAX_DURATION_SECONDS: f32 = 10.0;\r\nconst HARDWARE_MIN_PULSE_SECONDS: f32 = 0.05;\r\nconst MAX_SINGLE_PULSE_SECONDS: f32 = 0.10;\r\nconst ERM_AMPLITUDE_GAIN: f32 = 1.8;\r\nconst ERM_MIN_NONZERO_AMPLITUDE: f32 = 0.35;\nconst HID_SEND_ATTEMPTS: usize = 3;\r\nconst HID_SEND_RETRY_DELAY_MS: u64 = 2;\r\n\r\nfn log_haptics(message: &str) {\r\n    crate::openvr_runtime::log_driver_message(message);\r\n}\r\n\r\nfn with_hid_bridge<F>(f: F) -> Result<(), hid_bridge::BridgeError>\r\nwhere\r\n    F: FnOnce(&hid_bridge::HidBridge) -> Result<(), hid_bridge::BridgeError>,\r\n{\r\n    let bridge_slot = HID_BRIDGE.get_or_init(|| Mutex::new(None));\r\n    let mut guard = match bridge_slot.lock() {\r\n        Ok(g) => g,\r\n        Err(_) => return hid_bridge::HidBridge::new().and_then(|bridge| f(&bridge)),\r\n    };\r\n\r\n    if guard.is_none() {\r\n        *guard = Some(hid_bridge::HidBridge::new()?);\r\n    }\r\n\r\n    let bridge = guard.as_ref().expect(\"HID bridge should be initialized\");\r\n    f(bridge)\r\n}\r\n\r\nfn send_packet_blocking(packet: HapticPacket) -> Result<(), hid_bridge::BridgeError> {\r\n    let mut last_error = None;\r\n\r\n    for attempt in 1..=HID_SEND_ATTEMPTS {\r\n        match with_hid_bridge(|bridge| bridge.send_packet(DEFAULT_USB_VID, DEFAULT_USB_PID, packet)) {\r\n            Ok(()) => return Ok(()),\r\n            Err(err) => {\r\n                last_error = Some(err);\r\n                if attempt < HID_SEND_ATTEMPTS {\r\n                    thread::sleep(Duration::from_millis(HID_SEND_RETRY_DELAY_MS));\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    let err = last_error.expect(\"HID send attempts exhausted without captured error\");\r\n    log_haptics(&format!(\r\n        \"haptics: HID send failed after {} attempt(s); cmd=0x{:02X}, intensity={}, arg_lo={}, arg_hi={}, err={}\",\r\n        HID_SEND_ATTEMPTS,\r\n        packet.command_id,\r\n        packet.intensity,\r\n        packet.arg_lo,\r\n        packet.arg_hi,\r\n        err\r\n    ));\r\n    Err(err)\r\n}\r\n\r\n/// Sends an explicit stop command to firmware.\r\npub fn send_stop() -> Result<(), hid_bridge::BridgeError> {\r\n    send_packet_blocking(HapticPacket::new(command::STOP, 0, 0, 0))\r\n}\r\n\r\n/// Sends a normalized amplitude request to firmware using `SET_INTENSITY`.\r\n///\r\n/// Input is clamped to `[0.0, 1.0]` and converted to an 8-bit intensity.\r\npub fn send_amplitude(amplitude: f32) -> Result<(), hid_bridge::BridgeError> {\r\n    let amp = amplitude.clamp(0.0, 1.0);\r\n    let intensity = amplitude_to_intensity(amp);\r\n    send_packet_blocking(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0))\r\n}\r\n\r\nfn amplitude_to_intensity(amplitude: f32) -> u8 {\r\n    (amplitude.clamp(0.0, 1.0) * 255.0).round() as u8\r\n}\r\n\r\nfn wait_for_preempt(mailbox: &'static WorkerMailbox, timeout: Duration) -> Option<WorkerCommand> {\r\n    let mut state = mailbox.state.lock().ok()?;\r\n    if let Some(cmd) = state.pending.take() {\r\n        return Some(cmd);\r\n    }\r\n\r\n    let (mut state, _timeout_res) = mailbox.signal.wait_timeout(state, timeout).ok()?;\r\n    state.pending.take()\r\n}\r\n\r\nfn wait_for_next_command(mailbox: &'static WorkerMailbox) -> Option<WorkerCommand> {\r\n    let mut state = mailbox.state.lock().ok()?;\r\n    loop {\r\n        if let Some(cmd) = state.pending.take() {\r\n            return Some(cmd);\r\n        }\r\n\r\n        state = mailbox.signal.wait(state).ok()?;\r\n    }\r\n}\r\n\r\nfn run_hid_worker(mailbox: &'static WorkerMailbox) {\r\n    let mut cmd = match wait_for_next_command(mailbox) {\r\n        Some(c) => c,\r\n        None => return,\r\n    };\r\n\r\n    loop {\r\n        match cmd {\r\n            WorkerCommand::Stop => {\r\n                let _ = send_stop();\r\n                cmd = match wait_for_next_command(mailbox) {\r\n                    Some(c) => c,\r\n                    None => return,\r\n                };\r\n            }\r\n            WorkerCommand::PulseTrain {\r\n                intensity,\r\n                pulse_on_ms,\r\n                pulse_off_ms,\r\n                pulse_count,\r\n            } => {\r\n                let mut preempted = None;\r\n\r\n                for index in 0..pulse_count {\r\n                    let _ = send_stop();\r\n                    let _ = send_packet_blocking(HapticPacket::new(command::SET_INTENSITY, intensity, 0, 0));\r\n\r\n                    if let Some(next_cmd) = wait_for_preempt(mailbox, Duration::from_millis(pulse_on_ms.max(1))) {\r\n                        preempted = Some(next_cmd);\r\n                        break;\r\n                    }\r\n\r\n                    let _ = send_stop();\r\n\r\n                    if index + 1 < pulse_count && pulse_off_ms > 0 {\r\n                        if let Some(next_cmd) = wait_for_preempt(mailbox, Duration::from_millis(pulse_off_ms)) {\r\n                            preempted = Some(next_cmd);\r\n                            break;\r\n                        }\r\n                    }\r\n                }\r\n\r\n                cmd = if let Some(next_cmd) = preempted {\r\n                    next_cmd\r\n                } else {\r\n                    match wait_for_next_command(mailbox) {\r\n                        Some(c) => c,\r\n                        None => return,\r\n                    }\r\n                };\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\nfn hid_mailbox() -> &'static WorkerMailbox {\r\n    HID_MAILBOX.get_or_init(|| {\r\n        let mailbox = WorkerMailbox {\r\n            state: Mutex::new(WorkerState::default()),\r\n            signal: Condvar::new(),\r\n        };\r\n        std::thread::spawn(|| run_hid_worker(hid_mailbox()));\r\n        mailbox\r\n    })\r\n}\r\n\r\nfn queue_worker_command(cmd: WorkerCommand) {\r\n    let mailbox = hid_mailbox();\r\n    if let Ok(mut state) = mailbox.state.lock() {\r\n        // Latest command wins: overwrite anything pending.\r\n        state.pending = Some(cmd);\r\n        mailbox.signal.notify_one();\r\n    } else {\r\n        log_haptics(\"haptics: worker queue send failed: mailbox lock poisoned\");\r\n    }\r\n}\r\n\r\nfn shape_amplitude_for_erm(amplitude: f32) -> f32 {\r\n    if amplitude <= 0.0 {\r\n        return 0.0;\r\n    }\r\n\r\n    let boosted = (amplitude * ERM_AMPLITUDE_GAIN).clamp(0.0, 1.0);\r\n    boosted.max(ERM_MIN_NONZERO_AMPLITUDE)\r\n}\r\n\r\nfn resolve_effective_duration_seconds(duration_seconds: f32, frequency_hz: f32) -> f32 {\r\n    let clamped_duration = duration_seconds.clamp(0.0, OPENVR_MAX_DURATION_SECONDS);\r\n    if clamped_duration > 0.0 {\r\n        return clamped_duration;\r\n    }\r\n\r\n    // OpenVR guidance: when duration is 0, trigger exactly one pulse.\r\n    // One pulse ~= one period at requested frequency; apply ERM minimum floor.\r\n    (1.0 / frequency_hz).max(HARDWARE_MIN_PULSE_SECONDS)\r\n}\r\n\r\nfn resolve_pulse_train_parameters(\r\n    amplitude_for_timing: f32,\r\n    effective_duration_seconds: f32,\r\n    frequency_hz: f32,\r\n) -> (u64, u64, u32) {\r\n    let period_s = 1.0 / frequency_hz;\r\n    let max_pulse_s = (0.5 * period_s).min(MAX_SINGLE_PULSE_SECONDS);\r\n    let min_pulse_s = HARDWARE_MIN_PULSE_SECONDS.min(max_pulse_s);\r\n\r\n    let pulse_on_s = min_pulse_s + amplitude_for_timing * (max_pulse_s - min_pulse_s);\r\n    let pulse_off_s = (period_s - pulse_on_s).max(0.0);\r\n\r\n    let pulse_count = if effective_duration_seconds <= 0.0 {\r\n        1\r\n    } else {\r\n        (effective_duration_seconds * frequency_hz).round().max(1.0) as u32\r\n    };\r\n\r\n    let pulse_on_ms = (pulse_on_s * 1000.0).round().max(1.0) as u64;\r\n    let pulse_off_ms = (pulse_off_s * 1000.0).round().max(0.0) as u64;\r\n\r\n    (pulse_on_ms, pulse_off_ms, pulse_count)\r\n}\r\n\r\n/// Applies a single vibration request and updates stop scheduling.\r\n///\r\n/// A non-positive amplitude is treated as a stop request.\r\npub fn handle_haptic_request(req: HapticVibrationRequest) {\r\n    if req.amplitude <= 0.0 {\r\n        queue_worker_command(WorkerCommand::Stop);\r\n        return;\r\n    }\r\n\r\n    if req.frequency <= 0.0 {\r\n        queue_worker_command(WorkerCommand::Stop);\r\n        return;\r\n    }\r\n\r\n    let amplitude_for_timing = req.amplitude.clamp(0.0, 1.0);\r\n    let frequency_hz = req\r\n        .frequency\r\n        .clamp(OPENVR_MIN_FREQUENCY_HZ, OPENVR_MAX_FREQUENCY_HZ);\r\n\r\n    let effective_duration =\r\n        resolve_effective_duration_seconds(req.duration_seconds, frequency_hz);\r\n    let (pulse_on_ms, pulse_off_ms, pulse_count) = resolve_pulse_train_parameters(\r\n        amplitude_for_timing,\r\n        effective_duration,\r\n        frequency_hz,\r\n    );\r\n\r\n    let amplitude = shape_amplitude_for_erm(amplitude_for_timing);\r\n\r\n    queue_worker_command(WorkerCommand::PulseTrain {\r\n        intensity: amplitude_to_intensity(amplitude),\r\n        pulse_on_ms,\r\n        pulse_off_ms,\r\n        pulse_count,\r\n    });\r\n}\r\n\r\nfn process_haptic_callback() {\r\n    let cb = {\r\n        let guard = POLL_HAPTIC_CB.lock().ok();\r\n        guard.and_then(|g| *g)\r\n    };\r\n\r\n    let Some(callback) = cb else {\r\n        return;\r\n    };\r\n\r\n    for _ in 0..8 {\r\n        let mut req = HapticVibrationRequest::default();\r\n        if !callback(&mut req as *mut HapticVibrationRequest) {\r\n            break;\r\n        }\r\n        handle_haptic_request(req);\r\n    }\r\n}\r\n\r\n/// Per-frame pump for callback ingestion and delayed stop execution.\r\npub fn on_frame() {\r\n    process_haptic_callback();\r\n}\r\n\r\n/// Registers or clears the per-frame polling callback.\r\n///\r\n/// The callback is invoked from `on_frame()` and may provide multiple requests\r\n/// per frame.\r\npub fn set_poll_haptic_callback(cb: Option<PollHapticCallback>) {\r\n    if let Ok(mut guard) = POLL_HAPTIC_CB.lock() {\r\n        *guard = cb;\r\n    }\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":348,"endColumn":1}}]},{"type":"create","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1785,"initialContent":"","telemetryInfo":{}},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1786,"edits":[{"text":"# QT Py + SteamVR Haptics Project Postmortem","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":1,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1787,"edits":[{"text":"\n","range":{"startLineNumber":2,"startColumn":1,"endLineNumber":2,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1788,"edits":[{"text":"\n## Executive Summary","range":{"startLineNumber":3,"startColumn":1,"endLineNumber":3,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1789,"edits":[{"text":"\n","range":{"startLineNumber":4,"startColumn":1,"endLineNumber":4,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1790,"edits":[{"text":"\nThis project started as a full-stack haptics bring-up effort and ended as a real-time interaction tuning problem.","range":{"startLineNumber":5,"startColumn":1,"endLineNumber":5,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1791,"edits":[{"text":"\nWe successfully built a working pipeline from SteamVR haptic events all the way down to a DRV2605L-driven ERM motor on a QT Py SAMD21, then iteratively removed latency, queueing artifacts, and pulse-shaping mismatches until hover interactions felt immediate and reliable.","range":{"startLineNumber":6,"startColumn":1,"endLineNumber":6,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1792,"edits":[{"text":"\n","range":{"startLineNumber":7,"startColumn":1,"endLineNumber":7,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1793,"edits":[{"text":"\nAt a high level, success required coordinated progress across:","range":{"startLineNumber":8,"startColumn":1,"endLineNumber":8,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1794,"edits":[{"text":"\n","range":{"startLineNumber":9,"startColumn":1,"endLineNumber":9,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1795,"edits":[{"text":"\n- Embedded firmware (USB HID + DRV2605L command handling)","range":{"startLineNumber":10,"startColumn":1,"endLineNumber":10,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1796,"edits":[{"text":"\n- Host transport (Rust HID bridge and packet semantics)","range":{"startLineNumber":11,"startColumn":1,"endLineNumber":11,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1797,"edits":[{"text":"\n- OpenVR driver integration (device registration, input profile, pose, bindings)","range":{"startLineNumber":12,"startColumn":1,"endLineNumber":12,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1798,"edits":[{"text":"\n- SteamVR behavior tuning (Identify pulses, hover pulses, dashboard pointer)","range":{"startLineNumber":13,"startColumn":1,"endLineNumber":13,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1799,"edits":[{"text":"\n- Real-time systems behavior (preemption, overlapping pulses, non-blocking execution)","range":{"startLineNumber":14,"startColumn":1,"endLineNumber":14,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1800,"edits":[{"text":"\n","range":{"startLineNumber":15,"startColumn":1,"endLineNumber":15,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1801,"edits":[{"text":"\n---","range":{"startLineNumber":16,"startColumn":1,"endLineNumber":16,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1802,"edits":[{"text":"\n","range":{"startLineNumber":17,"startColumn":1,"endLineNumber":17,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1803,"edits":[{"text":"\n## Timeline and Major Hurdles We Overcame","range":{"startLineNumber":18,"startColumn":1,"endLineNumber":18,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1804,"edits":[{"text":"\n","range":{"startLineNumber":19,"startColumn":1,"endLineNumber":19,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1805,"edits":[{"text":"\n## 1) Foundational bring-up: firmware and hardware control","range":{"startLineNumber":20,"startColumn":1,"endLineNumber":20,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1806,"edits":[{"text":"\n","range":{"startLineNumber":21,"startColumn":1,"endLineNumber":21,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1807,"edits":[{"text":"\n### What we were trying to do","range":{"startLineNumber":22,"startColumn":1,"endLineNumber":22,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1808,"edits":[{"text":"\nGet the QT Py SAMD21 firmware to reliably receive commands over USB HID and drive the DRV2605L haptics chip.","range":{"startLineNumber":23,"startColumn":1,"endLineNumber":23,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1809,"edits":[{"text":"\n","range":{"startLineNumber":24,"startColumn":1,"endLineNumber":24,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1810,"edits":[{"text":"\n### Major hurdles","range":{"startLineNumber":25,"startColumn":1,"endLineNumber":25,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1811,"edits":[{"text":"\n","range":{"startLineNumber":26,"startColumn":1,"endLineNumber":26,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1812,"edits":[{"text":"\n1. **Reliable USB HID output report handling**  ","range":{"startLineNumber":27,"startColumn":1,"endLineNumber":27,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1813,"edits":[{"text":"\n   We needed robust parsing for the expected report format and strict command decoding.","range":{"startLineNumber":28,"startColumn":1,"endLineNumber":28,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1814,"edits":[{"text":"\n","range":{"startLineNumber":29,"startColumn":1,"endLineNumber":29,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1815,"edits":[{"text":"\n2. **DRV2605L mode management**  ","range":{"startLineNumber":30,"startColumn":1,"endLineNumber":30,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1816,"edits":[{"text":"\n   The driver had to switch correctly into realtime playback mode and stop cleanly.","range":{"startLineNumber":31,"startColumn":1,"endLineNumber":31,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1817,"edits":[{"text":"\n","range":{"startLineNumber":32,"startColumn":1,"endLineNumber":32,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1818,"edits":[{"text":"\n3. **Safe command dispatch path**  ","range":{"startLineNumber":33,"startColumn":1,"endLineNumber":33,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1819,"edits":[{"text":"\n   Unknown/malformed commands could not destabilize runtime behavior.","range":{"startLineNumber":34,"startColumn":1,"endLineNumber":34,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1820,"edits":[{"text":"\n","range":{"startLineNumber":35,"startColumn":1,"endLineNumber":35,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1821,"edits":[{"text":"\n### Outcome","range":{"startLineNumber":36,"startColumn":1,"endLineNumber":36,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1822,"edits":[{"text":"\nA stable embedded command path was established: parse HID report -> map to driver command -> apply to DRV2605L.","range":{"startLineNumber":37,"startColumn":1,"endLineNumber":37,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1823,"edits":[{"text":"\n","range":{"startLineNumber":38,"startColumn":1,"endLineNumber":38,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1824,"edits":[{"text":"\n---","range":{"startLineNumber":39,"startColumn":1,"endLineNumber":39,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1825,"edits":[{"text":"\n","range":{"startLineNumber":40,"startColumn":1,"endLineNumber":40,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1826,"edits":[{"text":"\n## 2) Host-to-firmware transport correctness","range":{"startLineNumber":41,"startColumn":1,"endLineNumber":41,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1827,"edits":[{"text":"\n","range":{"startLineNumber":42,"startColumn":1,"endLineNumber":42,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1828,"edits":[{"text":"\n### What we were trying to do","range":{"startLineNumber":43,"startColumn":1,"endLineNumber":43,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1829,"edits":[{"text":"\nMake the host reliably deliver haptic packets to the device with predictable timing.","range":{"startLineNumber":44,"startColumn":1,"endLineNumber":44,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1830,"edits":[{"text":"\n","range":{"startLineNumber":45,"startColumn":1,"endLineNumber":45,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1831,"edits":[{"text":"\n### Major hurdles","range":{"startLineNumber":46,"startColumn":1,"endLineNumber":46,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1832,"edits":[{"text":"\n","range":{"startLineNumber":47,"startColumn":1,"endLineNumber":47,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1833,"edits":[{"text":"\n4. **Packet framing and report ID conventions**  ","range":{"startLineNumber":48,"startColumn":1,"endLineNumber":48,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1834,"edits":[{"text":"\n   HID writes needed correct report layout (including report ID behavior).","range":{"startLineNumber":49,"startColumn":1,"endLineNumber":49,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1835,"edits":[{"text":"\n","range":{"startLineNumber":50,"startColumn":1,"endLineNumber":50,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1836,"edits":[{"text":"\n5. **Error handling and retries**  ","range":{"startLineNumber":51,"startColumn":1,"endLineNumber":51,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1837,"edits":[{"text":"\n   Intermittent HID failures needed bounded retries and useful logs.","range":{"startLineNumber":52,"startColumn":1,"endLineNumber":52,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1838,"edits":[{"text":"\n","range":{"startLineNumber":53,"startColumn":1,"endLineNumber":53,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1839,"edits":[{"text":"\n6. **Early hidden latency from repeated device open**  ","range":{"startLineNumber":54,"startColumn":1,"endLineNumber":54,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1840,"edits":[{"text":"\n   Reopening HID handles per packet introduced avoidable overhead.","range":{"startLineNumber":55,"startColumn":1,"endLineNumber":55,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1841,"edits":[{"text":"\n","range":{"startLineNumber":56,"startColumn":1,"endLineNumber":56,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1842,"edits":[{"text":"\n### Outcome","range":{"startLineNumber":57,"startColumn":1,"endLineNumber":57,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1843,"edits":[{"text":"\nTransport became both resilient and lower-latency, with improved send behavior and diagnostics.","range":{"startLineNumber":58,"startColumn":1,"endLineNumber":58,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1844,"edits":[{"text":"\n","range":{"startLineNumber":59,"startColumn":1,"endLineNumber":59,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1845,"edits":[{"text":"\n---","range":{"startLineNumber":60,"startColumn":1,"endLineNumber":60,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1846,"edits":[{"text":"\n","range":{"startLineNumber":61,"startColumn":1,"endLineNumber":61,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1847,"edits":[{"text":"\n## 3) OpenVR runtime integration and event routing","range":{"startLineNumber":62,"startColumn":1,"endLineNumber":62,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1848,"edits":[{"text":"\n","range":{"startLineNumber":63,"startColumn":1,"endLineNumber":63,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1849,"edits":[{"text":"\n### What we were trying to do","range":{"startLineNumber":64,"startColumn":1,"endLineNumber":64,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1850,"edits":[{"text":"\nTurn SteamVR/OpenVR haptic vibration events into firmware commands while maintaining driver lifecycle stability.","range":{"startLineNumber":65,"startColumn":1,"endLineNumber":65,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1851,"edits":[{"text":"\n","range":{"startLineNumber":66,"startColumn":1,"endLineNumber":66,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1852,"edits":[{"text":"\n### Major hurdles","range":{"startLineNumber":67,"startColumn":1,"endLineNumber":67,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1853,"edits":[{"text":"\n","range":{"startLineNumber":68,"startColumn":1,"endLineNumber":68,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1854,"edits":[{"text":"\n7. **Manual OpenVR ABI/vtable wiring in Rust**  ","range":{"startLineNumber":69,"startColumn":1,"endLineNumber":69,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1855,"edits":[{"text":"\n   Interface declarations and call signatures needed to match OpenVR expectations exactly.","range":{"startLineNumber":70,"startColumn":1,"endLineNumber":70,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1856,"edits":[{"text":"\n","range":{"startLineNumber":71,"startColumn":1,"endLineNumber":71,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1857,"edits":[{"text":"\n8. **Lifecycle correctness**  ","range":{"startLineNumber":72,"startColumn":1,"endLineNumber":72,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1858,"edits":[{"text":"\n   Device activation/deactivation/cleanup had to avoid stale handles and undefined state.","range":{"startLineNumber":73,"startColumn":1,"endLineNumber":73,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1859,"edits":[{"text":"\n","range":{"startLineNumber":74,"startColumn":1,"endLineNumber":74,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1860,"edits":[{"text":"\n9. **Event filtering and component matching**  ","range":{"startLineNumber":75,"startColumn":1,"endLineNumber":75,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1861,"edits":[{"text":"\n   We had to ensure only relevant haptic events reached the output path.","range":{"startLineNumber":76,"startColumn":1,"endLineNumber":76,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1862,"edits":[{"text":"\n","range":{"startLineNumber":77,"startColumn":1,"endLineNumber":77,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1863,"edits":[{"text":"\n### Outcome","range":{"startLineNumber":78,"startColumn":1,"endLineNumber":78,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1864,"edits":[{"text":"\nA dependable event routing loop was built, allowing OpenVR haptics to flow into the HID layer.","range":{"startLineNumber":79,"startColumn":1,"endLineNumber":79,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1865,"edits":[{"text":"\n","range":{"startLineNumber":80,"startColumn":1,"endLineNumber":80,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1866,"edits":[{"text":"\n---","range":{"startLineNumber":81,"startColumn":1,"endLineNumber":81,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1867,"edits":[{"text":"\n","range":{"startLineNumber":82,"startColumn":1,"endLineNumber":82,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1868,"edits":[{"text":"\n## 4) Input profile, bindings, and pointer visibility","range":{"startLineNumber":83,"startColumn":1,"endLineNumber":83,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1869,"edits":[{"text":"\n","range":{"startLineNumber":84,"startColumn":1,"endLineNumber":84,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1870,"edits":[{"text":"\n### What we were trying to do","range":{"startLineNumber":85,"startColumn":1,"endLineNumber":85,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1871,"edits":[{"text":"\nEnable dashboard laser pointer interactions and hover-triggered haptics for testing.","range":{"startLineNumber":86,"startColumn":1,"endLineNumber":86,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1872,"edits":[{"text":"\n","range":{"startLineNumber":87,"startColumn":1,"endLineNumber":87,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1873,"edits":[{"text":"\n### Major hurdles","range":{"startLineNumber":88,"startColumn":1,"endLineNumber":88,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1874,"edits":[{"text":"\n","range":{"startLineNumber":89,"startColumn":1,"endLineNumber":89,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1875,"edits":[{"text":"\n10. **No visible pointer despite controller registration**  ","range":{"startLineNumber":90,"startColumn":1,"endLineNumber":90,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1876,"edits":[{"text":"\n    SteamVR requires correct profile + compositor binding semantics, not just pose updates.","range":{"startLineNumber":91,"startColumn":1,"endLineNumber":91,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1877,"edits":[{"text":"\n","range":{"startLineNumber":92,"startColumn":1,"endLineNumber":92,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1878,"edits":[{"text":"\n11. **Mismatch with known-good reference implementation**  ","range":{"startLineNumber":93,"startColumn":1,"endLineNumber":93,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1879,"edits":[{"text":"\n    We aligned against mic-map behavior (right-hand role, `/pose/raw`, compositor bindings).","range":{"startLineNumber":94,"startColumn":1,"endLineNumber":94,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1880,"edits":[{"text":"\n","range":{"startLineNumber":95,"startColumn":1,"endLineNumber":95,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1881,"edits":[{"text":"\n12. **Missing/insufficient input components for compositor actions**  ","range":{"startLineNumber":96,"startColumn":1,"endLineNumber":96,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1882,"edits":[{"text":"\n    Pose alone was not enough; right-hand click/trigger/system inputs needed explicit components.","range":{"startLineNumber":97,"startColumn":1,"endLineNumber":97,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1883,"edits":[{"text":"\n","range":{"startLineNumber":98,"startColumn":1,"endLineNumber":98,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1884,"edits":[{"text":"\n### Outcome","range":{"startLineNumber":99,"startColumn":1,"endLineNumber":99,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1885,"edits":[{"text":"\nPointer visibility and dashboard interaction plumbing were restored through profile/binding parity and component completeness.","range":{"startLineNumber":100,"startColumn":1,"endLineNumber":100,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1886,"edits":[{"text":"\n","range":{"startLineNumber":101,"startColumn":1,"endLineNumber":101,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1887,"edits":[{"text":"\n---","range":{"startLineNumber":102,"startColumn":1,"endLineNumber":102,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1888,"edits":[{"text":"\n","range":{"startLineNumber":103,"startColumn":1,"endLineNumber":103,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1889,"edits":[{"text":"\n## 5) Pose behavior and interaction feel","range":{"startLineNumber":104,"startColumn":1,"endLineNumber":104,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1890,"edits":[{"text":"\n","range":{"startLineNumber":105,"startColumn":1,"endLineNumber":105,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1891,"edits":[{"text":"\n### What we were trying to do","range":{"startLineNumber":106,"startColumn":1,"endLineNumber":106,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1892,"edits":[{"text":"\nMake controller/laser behavior practical for hover haptic testing and eliminate awkward offsets.","range":{"startLineNumber":107,"startColumn":1,"endLineNumber":107,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1893,"edits":[{"text":"\n","range":{"startLineNumber":108,"startColumn":1,"endLineNumber":108,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1894,"edits":[{"text":"\n### Major hurdles","range":{"startLineNumber":109,"startColumn":1,"endLineNumber":109,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1895,"edits":[{"text":"\n","range":{"startLineNumber":110,"startColumn":1,"endLineNumber":110,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1896,"edits":[{"text":"\n13. **Controller offset and orientation mismatch**  ","range":{"startLineNumber":111,"startColumn":1,"endLineNumber":111,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1897,"edits":[{"text":"\n    Initial transforms felt wrong for practical dashboard use.","range":{"startLineNumber":112,"startColumn":1,"endLineNumber":112,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1898,"edits":[{"text":"\n","range":{"startLineNumber":113,"startColumn":1,"endLineNumber":113,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1899,"edits":[{"text":"\n14. **Tracking consistency while haptics were active**  ","range":{"startLineNumber":114,"startColumn":1,"endLineNumber":114,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1900,"edits":[{"text":"\n    Pose updates needed to stay smooth even during high-frequency haptic events.","range":{"startLineNumber":115,"startColumn":1,"endLineNumber":115,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1901,"edits":[{"text":"\n","range":{"startLineNumber":116,"startColumn":1,"endLineNumber":116,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1902,"edits":[{"text":"\n### Outcome","range":{"startLineNumber":117,"startColumn":1,"endLineNumber":117,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1903,"edits":[{"text":"\nPose updates were stabilized and aligned with the intended HMD-follow behavior.","range":{"startLineNumber":118,"startColumn":1,"endLineNumber":118,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1904,"edits":[{"text":"\n","range":{"startLineNumber":119,"startColumn":1,"endLineNumber":119,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1905,"edits":[{"text":"\n---","range":{"startLineNumber":120,"startColumn":1,"endLineNumber":120,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1906,"edits":[{"text":"\n","range":{"startLineNumber":121,"startColumn":1,"endLineNumber":121,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1907,"edits":[{"text":"\n## 6) Identify and hover pulse interpretation","range":{"startLineNumber":122,"startColumn":1,"endLineNumber":122,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1908,"edits":[{"text":"\n","range":{"startLineNumber":123,"startColumn":1,"endLineNumber":123,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1909,"edits":[{"text":"\n### What we were trying to do","range":{"startLineNumber":124,"startColumn":1,"endLineNumber":124,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1910,"edits":[{"text":"\nTranslate SteamVR pulse patterns into meaningful ERM output while preserving responsiveness.","range":{"startLineNumber":125,"startColumn":1,"endLineNumber":125,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1911,"edits":[{"text":"\n","range":{"startLineNumber":126,"startColumn":1,"endLineNumber":126,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1912,"edits":[{"text":"\n### Major hurdles","range":{"startLineNumber":127,"startColumn":1,"endLineNumber":127,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1913,"edits":[{"text":"\n","range":{"startLineNumber":128,"startColumn":1,"endLineNumber":128,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1914,"edits":[{"text":"\n15. **LRA-oriented SteamVR events on ERM hardware**  ","range":{"startLineNumber":129,"startColumn":1,"endLineNumber":129,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1915,"edits":[{"text":"\n    Raw amplitudes were often too weak for ERM detectability.","range":{"startLineNumber":130,"startColumn":1,"endLineNumber":130,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1916,"edits":[{"text":"\n","range":{"startLineNumber":131,"startColumn":1,"endLineNumber":131,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1917,"edits":[{"text":"\n16. **Minimum detectability vs. overdriving**  ","range":{"startLineNumber":132,"startColumn":1,"endLineNumber":132,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1918,"edits":[{"text":"\n    We introduced ERM amplitude shaping (gain + non-zero floor), then tuned for perceptibility.","range":{"startLineNumber":133,"startColumn":1,"endLineNumber":133,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1919,"edits":[{"text":"\n","range":{"startLineNumber":134,"startColumn":1,"endLineNumber":134,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1920,"edits":[{"text":"\n17. **Zero-duration pulse semantics**  ","range":{"startLineNumber":135,"startColumn":1,"endLineNumber":135,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1921,"edits":[{"text":"\n    We clarified OpenVR behavior and adjusted pulse construction to respect one-pulse semantics.","range":{"startLineNumber":136,"startColumn":1,"endLineNumber":136,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1922,"edits":[{"text":"\n","range":{"startLineNumber":137,"startColumn":1,"endLineNumber":137,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1923,"edits":[{"text":"\n18. **Amplitude-dependent pulse width**  ","range":{"startLineNumber":138,"startColumn":1,"endLineNumber":138,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1924,"edits":[{"text":"\n    We adapted pulse timing logic to align with OpenVR guidance (pulse width interpolation by amplitude).","range":{"startLineNumber":139,"startColumn":1,"endLineNumber":139,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1925,"edits":[{"text":"\n","range":{"startLineNumber":140,"startColumn":1,"endLineNumber":140,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1926,"edits":[{"text":"\n### Outcome","range":{"startLineNumber":141,"startColumn":1,"endLineNumber":141,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1927,"edits":[{"text":"\nSingle pulses became consistently detectable while preserving protocol intent.","range":{"startLineNumber":142,"startColumn":1,"endLineNumber":142,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1928,"edits":[{"text":"\n","range":{"startLineNumber":143,"startColumn":1,"endLineNumber":143,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1929,"edits":[{"text":"\n---","range":{"startLineNumber":144,"startColumn":1,"endLineNumber":144,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1930,"edits":[{"text":"\n","range":{"startLineNumber":145,"startColumn":1,"endLineNumber":145,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1931,"edits":[{"text":"\n## 7) Real-time responsiveness: overlapping pulses and preemption","range":{"startLineNumber":146,"startColumn":1,"endLineNumber":146,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1932,"edits":[{"text":"\n","range":{"startLineNumber":147,"startColumn":1,"endLineNumber":147,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1933,"edits":[{"text":"\n### What we were trying to do","range":{"startLineNumber":148,"startColumn":1,"endLineNumber":148,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1934,"edits":[{"text":"\nEnsure new pulses interrupt immediately instead of forming delayed tails during fast hover sweeps.","range":{"startLineNumber":149,"startColumn":1,"endLineNumber":149,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1935,"edits":[{"text":"\n","range":{"startLineNumber":150,"startColumn":1,"endLineNumber":150,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1936,"edits":[{"text":"\n### Major hurdles","range":{"startLineNumber":151,"startColumn":1,"endLineNumber":151,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1937,"edits":[{"text":"\n","range":{"startLineNumber":152,"startColumn":1,"endLineNumber":152,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1938,"edits":[{"text":"\n19. **FIFO queue buildup under rapid hover**  ","range":{"startLineNumber":153,"startColumn":1,"endLineNumber":153,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1939,"edits":[{"text":"\n    Backlog caused delayed “ghost” pulses long after the user moved on.","range":{"startLineNumber":154,"startColumn":1,"endLineNumber":154,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1940,"edits":[{"text":"\n","range":{"startLineNumber":155,"startColumn":1,"endLineNumber":155,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1941,"edits":[{"text":"\n20. **Dropped vs. delayed pulses tradeoff**  ","range":{"startLineNumber":156,"startColumn":1,"endLineNumber":156,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1942,"edits":[{"text":"\n    Simple coalescing could skip too much; strict queueing delayed too much.","range":{"startLineNumber":157,"startColumn":1,"endLineNumber":157,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1943,"edits":[{"text":"\n","range":{"startLineNumber":158,"startColumn":1,"endLineNumber":158,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1944,"edits":[{"text":"\n21. **Worker preemption model design**  ","range":{"startLineNumber":159,"startColumn":1,"endLineNumber":159,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1945,"edits":[{"text":"\n    We needed latest-intent behavior with immediate interruptibility.","range":{"startLineNumber":160,"startColumn":1,"endLineNumber":160,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1946,"edits":[{"text":"\n","range":{"startLineNumber":161,"startColumn":1,"endLineNumber":161,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1947,"edits":[{"text":"\n22. **Firmware mode side effects**  ","range":{"startLineNumber":162,"startColumn":1,"endLineNumber":162,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1948,"edits":[{"text":"\n    Internal library-triggered effects can feel queued/non-interruptible compared to realtime actuation.","range":{"startLineNumber":163,"startColumn":1,"endLineNumber":163,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1949,"edits":[{"text":"\n","range":{"startLineNumber":164,"startColumn":1,"endLineNumber":164,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1950,"edits":[{"text":"\n### Outcome","range":{"startLineNumber":165,"startColumn":1,"endLineNumber":165,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1951,"edits":[{"text":"\nWe moved toward interruptible, realtime-oriented pulse execution and eliminated the worst queue-tail behaviors that blocked responsiveness.","range":{"startLineNumber":166,"startColumn":1,"endLineNumber":166,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1952,"edits":[{"text":"\n","range":{"startLineNumber":167,"startColumn":1,"endLineNumber":167,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1953,"edits":[{"text":"\n---","range":{"startLineNumber":168,"startColumn":1,"endLineNumber":168,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1954,"edits":[{"text":"\n","range":{"startLineNumber":169,"startColumn":1,"endLineNumber":169,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1955,"edits":[{"text":"\n## 8) Cross-layer debugging and deployment operations","range":{"startLineNumber":170,"startColumn":1,"endLineNumber":170,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1956,"edits":[{"text":"\n","range":{"startLineNumber":171,"startColumn":1,"endLineNumber":171,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1957,"edits":[{"text":"\n### What we were trying to do","range":{"startLineNumber":172,"startColumn":1,"endLineNumber":172,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1958,"edits":[{"text":"\nMaintain fast iteration while touching firmware, host, and OpenVR driver layers.","range":{"startLineNumber":173,"startColumn":1,"endLineNumber":173,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1959,"edits":[{"text":"\n","range":{"startLineNumber":174,"startColumn":1,"endLineNumber":174,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1960,"edits":[{"text":"\n### Major hurdles","range":{"startLineNumber":175,"startColumn":1,"endLineNumber":175,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1961,"edits":[{"text":"\n","range":{"startLineNumber":176,"startColumn":1,"endLineNumber":176,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1962,"edits":[{"text":"\n23. **Multi-target build noise and unrelated workspace errors**  ","range":{"startLineNumber":177,"startColumn":1,"endLineNumber":177,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1963,"edits":[{"text":"\n    Some workspace diagnostics were unrelated to the active haptics path and had to be isolated.","range":{"startLineNumber":178,"startColumn":1,"endLineNumber":178,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1964,"edits":[{"text":"\n","range":{"startLineNumber":179,"startColumn":1,"endLineNumber":179,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1965,"edits":[{"text":"\n24. **Ensuring deployed artifacts matched source changes**  ","range":{"startLineNumber":180,"startColumn":1,"endLineNumber":180,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1966,"edits":[{"text":"\n    Frequent build/deploy/retest loops were required to validate each hypothesis.","range":{"startLineNumber":181,"startColumn":1,"endLineNumber":181,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1967,"edits":[{"text":"\n","range":{"startLineNumber":182,"startColumn":1,"endLineNumber":182,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1968,"edits":[{"text":"\n25. **Configuration caching in SteamVR**  ","range":{"startLineNumber":183,"startColumn":1,"endLineNumber":183,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1969,"edits":[{"text":"\n    Binding and driver changes sometimes required restart-level refreshes to take effect.","range":{"startLineNumber":184,"startColumn":1,"endLineNumber":184,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1970,"edits":[{"text":"\n","range":{"startLineNumber":185,"startColumn":1,"endLineNumber":185,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1971,"edits":[{"text":"\n### Outcome","range":{"startLineNumber":186,"startColumn":1,"endLineNumber":186,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1972,"edits":[{"text":"\nA repeatable edit-build-deploy-test cycle was established and used effectively through the final tuning stages.","range":{"startLineNumber":187,"startColumn":1,"endLineNumber":187,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1973,"edits":[{"text":"\n","range":{"startLineNumber":188,"startColumn":1,"endLineNumber":188,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1974,"edits":[{"text":"\n---","range":{"startLineNumber":189,"startColumn":1,"endLineNumber":189,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1975,"edits":[{"text":"\n","range":{"startLineNumber":190,"startColumn":1,"endLineNumber":190,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1976,"edits":[{"text":"\n## Final State","range":{"startLineNumber":191,"startColumn":1,"endLineNumber":191,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1977,"edits":[{"text":"\n","range":{"startLineNumber":192,"startColumn":1,"endLineNumber":192,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1978,"edits":[{"text":"\nBy the end of this effort, we achieved:","range":{"startLineNumber":193,"startColumn":1,"endLineNumber":193,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1979,"edits":[{"text":"\n","range":{"startLineNumber":194,"startColumn":1,"endLineNumber":194,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1980,"edits":[{"text":"\n- End-to-end haptics from OpenVR events to QT Py firmware actuation","range":{"startLineNumber":195,"startColumn":1,"endLineNumber":195,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1981,"edits":[{"text":"\n- Stable pose and pointer behavior for hover testing","range":{"startLineNumber":196,"startColumn":1,"endLineNumber":196,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1982,"edits":[{"text":"\n- Detectable ERM pulse shaping for real hardware constraints","range":{"startLineNumber":197,"startColumn":1,"endLineNumber":197,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1983,"edits":[{"text":"\n- Significantly improved real-time responsiveness under dense hover interactions","range":{"startLineNumber":198,"startColumn":1,"endLineNumber":198,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1984,"edits":[{"text":"\n","range":{"startLineNumber":199,"startColumn":1,"endLineNumber":199,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1985,"edits":[{"text":"\nThe biggest lesson: this was not one bug, but a **stacked systems problem**. We had to fix semantics and timing at every layer (SteamVR bindings, OpenVR runtime integration, host HID transport, firmware driver mode, and motor-specific pulse shaping) before the interaction felt correct.","range":{"startLineNumber":200,"startColumn":1,"endLineNumber":200,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\docs\\postmortem.md","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/docs/postmortem.md","scheme":"file"},"requestId":"request_645697bc-271a-4407-9dc4-d557e69d94d4","epoch":1986,"edits":[{"text":"\n","range":{"startLineNumber":201,"startColumn":1,"endLineNumber":201,"endColumn":1}}]},{"type":"textEdit","uri":{"$mid":1,"fsPath":"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\\tools\\scripts\\host_hid_smoke_test.ps1","_sep":1,"external":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/host_hid_smoke_test.ps1","path":"/c:/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/host_hid_smoke_test.ps1","scheme":"file"},"requestId":"request_303b5792-8ec1-4d5c-ab9a-948563589794","epoch":1990,"edits":[{"text":"param(\r\n    [UInt16]$Vid = 0x35bd,\r\n    [Alias('Pid')]\r\n    [UInt16]$ProductId = 0xfe01,\r\n    [byte]$Command = 0x01,\r\n    [byte]$Intensity = 160,\n    [int]$AutoStopMs = 200,\n    [switch]$NoAutoStop,\r\n    [switch]$SkipPreflight\r\n)\r\n\r\n$ErrorActionPreference = \"Stop\"\r\n$root = \"c:\\Users\\decid\\Documents\\projects\\qt-py-haptics\"\r\nSet-Location $root\r\n\r\nfunction Test-HidPresent {\r\n    param(\r\n        [UInt16]$CheckVid,\r\n        [UInt16]$CheckPid\r\n    )\r\n\r\n    $vidHex = ('{0:X4}' -f $CheckVid)\r\n    $pidHex = ('{0:X4}' -f $CheckPid)\r\n    $pattern = \"VID_$vidHex&PID_$pidHex\"\r\n\r\n    $matches = Get-CimInstance -ClassName Win32_PnPEntity -ErrorAction SilentlyContinue |\r\n        Where-Object { $_.DeviceID -like \"*$pattern*\" }\r\n\r\n    return ($null -ne $matches -and $matches.Count -gt 0)\r\n}\r\n\r\nif (-not $SkipPreflight) {\r\n    if (-not (Test-HidPresent -CheckVid $Vid -CheckPid $ProductId)) {\r\n        throw \"HID device not detected for VID=0x$('{0:X4}' -f $Vid), PID=0x$('{0:X4}' -f $ProductId). Check cable, firmware, and device enumeration.\"\r\n    }\r\n}\r\n\r\ncargo run -p hid-cli -- --vid $Vid --pid $ProductId --cmd $Command --intensity $Intensity\r\n\nWrite-Host \"Smoke test note: successful command send only confirms HID transport; firmware I2C/actuator failures are not acknowledged over HID.\" -ForegroundColor Yellow\n\r\n# Safety: for `SET_INTENSITY` smoke tests, send STOP shortly after start\r\n# unless explicitly disabled.\r\nif (-not $NoAutoStop -and $Command -eq 0x01 -and $Intensity -gt 0) {\r\n    Start-Sleep -Milliseconds $AutoStopMs\r\n    cargo run -p hid-cli -- --vid $Vid --pid $ProductId --cmd 3 --intensity 0\r\n}\r\n","range":{"startLineNumber":1,"startColumn":1,"endLineNumber":49,"endColumn":1}}]}],"epochCounter":1992},"recentSnapshot":{"entries":[{"resource":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/Cargo.toml","languageId":"toml","originalHash":"3dfb70a","currentHash":"3dfb70a","state":1,"snapshotUri":"chat-editing-snapshot-text-model:/c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/Cargo.toml?%7B%22session%22%3A%7B%22%24mid%22%3A1%2C%22external%22%3A%22vscode-chat-session%3A%2F%2Flocal%2FMzQ4N2Y0YWYtOWZkMC00Mzg3LWI0MTctYWQyYThmNzhhMjg3%22%2C%22path%22%3A%22%2FMzQ4N2Y0YWYtOWZkMC00Mzg3LWI0MTctYWQyYThmNzhhMjg3%22%2C%22scheme%22%3A%22vscode-chat-session%22%2C%22authority%22%3A%22local%22%7D%2C%22requestId%22%3A%22%22%2C%22undoStop%22%3A%22%22%7D","telemetryInfo":{"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","agentId":"github.copilot.editsAgent","modelId":"copilot/gpt-5.3-codex","modeId":"agent"}},{"resource":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs","languageId":"rust","originalHash":"4d3a8c3","currentHash":"4d3a8c3","state":1,"snapshotUri":"chat-editing-snapshot-text-model:/c%3A/Users/decid/Documents/projects/qt-py-haptics/shared/haptics-protocol/src/lib.rs?%7B%22session%22%3A%7B%22%24mid%22%3A1%2C%22external%22%3A%22vscode-chat-session%3A%2F%2Flocal%2FMzQ4N2Y0YWYtOWZkMC00Mzg3LWI0MTctYWQyYThmNzhhMjg3%22%2C%22path%22%3A%22%2FMzQ4N2Y0YWYtOWZkMC00Mzg3LWI0MTctYWQyYThmNzhhMjg3%22%2C%22scheme%22%3A%22vscode-chat-session%22%2C%22authority%22%3A%22local%22%7D%2C%22requestId%22%3A%22%22%2C%22undoStop%22%3A%22%22%7D","telemetryInfo":{"requestId":"request_32e09b23-ccf2-430e-8354-01b3c60ea6ea","agentId":"github.copilot.editsAgent","modelId":"copilot/gpt-5.3-codex","modeId":"agent"}},{"resource":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/Cargo.toml","languageId":"toml","originalHash":"2a1a883","currentHash":"2a1a883","state":1,"snapshotUri":"chat-editing-snapshot-text-model:/c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-bridge/Cargo.toml?%7B%22session%22%3A%7B%22%24mid%22%3A1%2C%22external%22%3A%22vscode-chat-session%3A%2F%2Flocal%2FMzQ4N2Y0YWYtOWZkMC00Mzg3LWI0MTctYWQyYThmNzhhMjg3%22%2C%22path%22%3A%22%2FMzQ4N2Y0YWYtOWZkMC00Mzg3LWI0MTctYWQyYThmNzhhMjg3%22%2C%22scheme%22%3A%22vscode-chat-session%22%2C%22authority%22%3A%22local%22%7D%2C%22requestId%22%3A%22%22%2C%22undoStop%22%3A%22%22%7D","telemetryInfo":{"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","agentId":"github.copilot.editsAgent","modelId":"copilot/gpt-5.3-codex","modeId":"agent"}},{"resource":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/Cargo.toml","languageId":"toml","originalHash":"6b1a256","currentHash":"6b1a256","state":1,"snapshotUri":"chat-editing-snapshot-text-model:/c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/Cargo.toml?%7B%22session%22%3A%7B%22%24mid%22%3A1%2C%22external%22%3A%22vscode-chat-session%3A%2F%2Flocal%2FMzQ4N2Y0YWYtOWZkMC00Mzg3LWI0MTctYWQyYThmNzhhMjg3%22%2C%22path%22%3A%22%2FMzQ4N2Y0YWYtOWZkMC00Mzg3LWI0MTctYWQyYThmNzhhMjg3%22%2C%22scheme%22%3A%22vscode-chat-session%22%2C%22authority%22%3A%22local%22%7D%2C%22requestId%22%3A%22%22%2C%22undoStop%22%3A%22%22%7D","telemetryInfo":{"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","agentId":"github.copilot.editsAgent","modelId":"copilot/gpt-5.3-codex","modeId":"agent"}},{"resource":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs","languageId":"rust","originalHash":"d0721b9","currentHash":"d0721b9","state":1,"snapshotUri":"chat-editing-snapshot-text-model:/c%3A/Users/decid/Documents/projects/qt-py-haptics/host/hid-cli/src/main.rs?%7B%22session%22%3A%7B%22%24mid%22%3A1%2C%22external%22%3A%22vscode-chat-session%3A%2F%2Flocal%2FMzQ4N2Y0YWYtOWZkMC00Mzg3LWI0MTctYWQyYThmNzhhMjg3%22%2C%22path%22%3A%22%2FMzQ4N2Y0YWYtOWZkMC00Mzg3LWI0MTctYWQyYThmNzhhMjg3%22%2C%22scheme%22%3A%22vscode-chat-session%22%2C%22authority%22%3A%22local%22%7D%2C%22requestId%22%3A%22%22%2C%22undoStop%22%3A%22%22%7D","telemetryInfo":{"requestId":"request_32e09b23-ccf2-430e-8354-01b3c60ea6ea","agentId":"github.copilot.editsAgent","modelId":"copilot/gpt-5.3-codex","modeId":"agent"}},{"resource":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/.cargo/config.toml","languageId":"toml","originalHash":"20cd78c","currentHash":"20cd78c","state":1,"snapshotUri":"chat-editing-snapshot-text-model:/c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/.cargo/config.toml?%7B%22session%22%3A%7B%22%24mid%22%3A1%2C%22external%22%3A%22vscode-chat-session%3A%2F%2Flocal%2FMzQ4N2Y0YWYtOWZkMC00Mzg3LWI0MTctYWQyYThmNzhhMjg3%22%2C%22path%22%3A%22%2FMzQ4N2Y0YWYtOWZkMC00Mzg3LWI0MTctYWQyYThmNzhhMjg3%22%2C%22scheme%22%3A%22vscode-chat-session%22%2C%22authority%22%3A%22local%22%7D%2C%22requestId%22%3A%22%22%2C%22undoStop%22%3A%22%22%7D","telemetryInfo":{"requestId":"request_a6e9ece0-d739-40fb-bfd8-8080e026389e","agentId":"github.copilot.editsAgent","modelId":"copilot/gpt-5.3-codex","modeId":"agent"}},{"resource":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs","languageId":"rust","originalHash":"6b36153","currentHash":"6b36153","state":1,"snapshotUri":"chat-editing-snapshot-text-model:/c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l_parser.rs?%7B%22session%22%3A%7B%22%24mid%22%3A1%2C%22external%22%3A%22vscode-chat-session%3A%2F%2Flocal%2FMzQ4N2Y0YWYtOWZkMC00Mzg3LWI0MTctYWQyYThmNzhhMjg3%22%2C%22path%22%3A%22%2FMzQ4N2Y0YWYtOWZkMC00Mzg3LWI0MTctYWQyYThmNzhhMjg3%22%2C%22scheme%22%3A%22vscode-chat-session%22%2C%22authority%22%3A%22local%22%7D%2C%22requestId%22%3A%22%22%2C%22undoStop%22%3A%22%22%7D","telemetryInfo":{"requestId":"request_da61cd7c-da91-4ac6-9f3e-da45d99473b8","agentId":"github.copilot.editsAgent","modelId":"copilot/gpt-5.3-codex","modeId":"agent"}},{"resource":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs","languageId":"rust","originalHash":"2cae46b","currentHash":"2cae46b","state":1,"snapshotUri":"chat-editing-snapshot-text-model:/c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/drv2605l.rs?%7B%22session%22%3A%7B%22%24mid%22%3A1%2C%22external%22%3A%22vscode-chat-session%3A%2F%2Flocal%2FMzQ4N2Y0YWYtOWZkMC00Mzg3LWI0MTctYWQyYThmNzhhMjg3%22%2C%22path%22%3A%22%2FMzQ4N2Y0YWYtOWZkMC00Mzg3LWI0MTctYWQyYThmNzhhMjg3%22%2C%22scheme%22%3A%22vscode-chat-session%22%2C%22authority%22%3A%22local%22%7D%2C%22requestId%22%3A%22%22%2C%22undoStop%22%3A%22%22%7D","telemetryInfo":{"requestId":"request_ae3b7ebe-5e42-40b0-acca-788a8aa0cfec","agentId":"github.copilot.editsAgent","modelId":"copilot/gpt-5.3-codex","modeId":"agent"}},{"resource":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/usb_hid.rs","languageId":"rust","originalHash":"b112df4","currentHash":"b112df4","state":1,"snapshotUri":"chat-editing-snapshot-text-model:/c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/src/usb_hid.rs?%7B%22session%22%3A%7B%22%24mid%22%3A1%2C%22external%22%3A%22vscode-chat-session%3A%2F%2Flocal%2FMzQ4N2Y0YWYtOWZkMC00Mzg3LWI0MTctYWQyYThmNzhhMjg3%22%2C%22path%22%3A%22%2FMzQ4N2Y0YWYtOWZkMC00Mzg3LWI0MTctYWQyYThmNzhhMjg3%22%2C%22scheme%22%3A%22vscode-chat-session%22%2C%22authority%22%3A%22local%22%7D%2C%22requestId%22%3A%22%22%2C%22undoStop%22%3A%22%22%7D","telemetryInfo":{"requestId":"request_bb3daaf3-3e71-43cc-b251-408aa3e5fe95","agentId":"github.copilot.editsAgent","modelId":"copilot/gpt-5.3-codex","modeId":"agent"}},{"resource":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md","languageId":"markdown","originalHash":"df1ce15","currentHash":"df1ce15","state":1,"snapshotUri":"chat-editing-snapshot-text-model:/c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/FLASH_AND_TEST.md?%7B%22session%22%3A%7B%22%24mid%22%3A1%2C%22external%22%3A%22vscode-chat-session%3A%2F%2Flocal%2FMzQ4N2Y0YWYtOWZkMC00Mzg3LWI0MTctYWQyYThmNzhhMjg3%22%2C%22path%22%3A%22%2FMzQ4N2Y0YWYtOWZkMC00Mzg3LWI0MTctYWQyYThmNzhhMjg3%22%2C%22scheme%22%3A%22vscode-chat-session%22%2C%22authority%22%3A%22local%22%7D%2C%22requestId%22%3A%22%22%2C%22undoStop%22%3A%22%22%7D","telemetryInfo":{"requestId":"request_4692cace-c687-4f02-91bd-cae5b881aab8","agentId":"github.copilot.editsAgent","modelId":"copilot/gpt-5.3-codex","modeId":"agent"}},{"resource":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/.gitignore","languageId":"ignore","originalHash":"0829d3f","currentHash":"0829d3f","state":1,"snapshotUri":"chat-editing-snapshot-text-model:/c%3A/Users/decid/Documents/projects/qt-py-haptics/.gitignore?%7B%22session%22%3A%7B%22%24mid%22%3A1%2C%22external%22%3A%22vscode-chat-session%3A%2F%2Flocal%2FMzQ4N2Y0YWYtOWZkMC00Mzg3LWI0MTctYWQyYThmNzhhMjg3%22%2C%22path%22%3A%22%2FMzQ4N2Y0YWYtOWZkMC00Mzg3LWI0MTctYWQyYThmNzhhMjg3%22%2C%22scheme%22%3A%22vscode-chat-session%22%2C%22authority%22%3A%22local%22%7D%2C%22requestId%22%3A%22%22%2C%22undoStop%22%3A%22%22%7D","telemetryInfo":{"requestId":"request_dc7aedcd-b1dc-443b-8063-d3b5527768d7","agentId":"github.copilot.editsAgent","modelId":"copilot/gpt-5.3-codex","modeId":"agent"}},{"resource":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml","languageId":"toml","originalHash":"8edd027","currentHash":"8edd027","state":1,"snapshotUri":"chat-editing-snapshot-text-model:/c%3A/Users/decid/Documents/projects/qt-py-haptics/firmware/qtpy-samd21/Cargo.toml?%7B%22session%22%3A%7B%22%24mid%22%3A1%2C%22external%22%3A%22vscode-chat-session%3A%2F%2Flocal%2FMzQ4N2Y0YWYtOWZkMC00Mzg3LWI0MTctYWQyYThmNzhhMjg3%22%2C%22path%22%3A%22%2FMzQ4N2Y0YWYtOWZkMC00Mzg3LWI0MTctYWQyYThmNzhhMjg3%22%2C%22scheme%22%3A%22vscode-chat-session%22%2C%22authority%22%3A%22local%22%7D%2C%22requestId%22%3A%22%22%2C%22undoStop%22%3A%22%22%7D","telemetryInfo":{"requestId":"request_5ffebef5-f6de-4fca-a46a-31d52f96616e","agentId":"github.copilot.editsAgent","modelId":"copilot/gpt-5.3-codex","modeId":"agent"}},{"resource":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/host_hid_smoke_test.ps1","languageId":"powershell","originalHash":"b3e8e8f","currentHash":"62f1858","state":0,"snapshotUri":"chat-editing-snapshot-text-model:/c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/host_hid_smoke_test.ps1?%7B%22session%22%3A%7B%22%24mid%22%3A1%2C%22external%22%3A%22vscode-chat-session%3A%2F%2Flocal%2FMzQ4N2Y0YWYtOWZkMC00Mzg3LWI0MTctYWQyYThmNzhhMjg3%22%2C%22path%22%3A%22%2FMzQ4N2Y0YWYtOWZkMC00Mzg3LWI0MTctYWQyYThmNzhhMjg3%22%2C%22scheme%22%3A%22vscode-chat-session%22%2C%22authority%22%3A%22local%22%7D%2C%22requestId%22%3A%22%22%2C%22undoStop%22%3A%22%22%7D","telemetryInfo":{"requestId":"request_303b5792-8ec1-4d5c-ab9a-948563589794","agentId":"github.copilot.editsAgent","modelId":"copilot/gpt-5.3-codex","modeId":"agent"}},{"resource":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml","languageId":"toml","originalHash":"aa030df","currentHash":"aa030df","state":1,"snapshotUri":"chat-editing-snapshot-text-model:/c%3A/Users/decid/Documents/projects/qt-py-haptics/Cargo.toml?%7B%22session%22%3A%7B%22%24mid%22%3A1%2C%22external%22%3A%22vscode-chat-session%3A%2F%2Flocal%2FMzQ4N2Y0YWYtOWZkMC00Mzg3LWI0MTctYWQyYThmNzhhMjg3%22%2C%22path%22%3A%22%2FMzQ4N2Y0YWYtOWZkMC00Mzg3LWI0MTctYWQyYThmNzhhMjg3%22%2C%22scheme%22%3A%22vscode-chat-session%22%2C%22authority%22%3A%22local%22%7D%2C%22requestId%22%3A%22%22%2C%22undoStop%22%3A%22%22%7D","telemetryInfo":{"requestId":"request_dc7aedcd-b1dc-443b-8063-d3b5527768d7","agentId":"github.copilot.editsAgent","modelId":"copilot/gpt-5.3-codex","modeId":"agent"}},{"resource":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1","languageId":"powershell","originalHash":"f236d07","currentHash":"f236d07","state":1,"snapshotUri":"chat-editing-snapshot-text-model:/c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/build_and_deploy_openvr_driver.ps1?%7B%22session%22%3A%7B%22%24mid%22%3A1%2C%22external%22%3A%22vscode-chat-session%3A%2F%2Flocal%2FMzQ4N2Y0YWYtOWZkMC00Mzg3LWI0MTctYWQyYThmNzhhMjg3%22%2C%22path%22%3A%22%2FMzQ4N2Y0YWYtOWZkMC00Mzg3LWI0MTctYWQyYThmNzhhMjg3%22%2C%22scheme%22%3A%22vscode-chat-session%22%2C%22authority%22%3A%22local%22%7D%2C%22requestId%22%3A%22%22%2C%22undoStop%22%3A%22%22%7D","telemetryInfo":{"requestId":"request_dc7aedcd-b1dc-443b-8063-d3b5527768d7","agentId":"github.copilot.editsAgent","modelId":"copilot/gpt-5.3-codex","modeId":"agent"}},{"resource":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/README.md","languageId":"markdown","originalHash":"0943185","currentHash":"0943185","state":1,"snapshotUri":"chat-editing-snapshot-text-model:/c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/README.md?%7B%22session%22%3A%7B%22%24mid%22%3A1%2C%22external%22%3A%22vscode-chat-session%3A%2F%2Flocal%2FMzQ4N2Y0YWYtOWZkMC00Mzg3LWI0MTctYWQyYThmNzhhMjg3%22%2C%22path%22%3A%22%2FMzQ4N2Y0YWYtOWZkMC00Mzg3LWI0MTctYWQyYThmNzhhMjg3%22%2C%22scheme%22%3A%22vscode-chat-session%22%2C%22authority%22%3A%22local%22%7D%2C%22requestId%22%3A%22%22%2C%22undoStop%22%3A%22%22%7D","telemetryInfo":{"requestId":"request_244f6538-b0e5-4f3c-84eb-c094cc01ec2c","agentId":"github.copilot.editsAgent","modelId":"copilot/gpt-5.3-codex","modeId":"agent"}},{"resource":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md","languageId":"markdown","originalHash":"8fddee3","currentHash":"8fddee3","state":1,"snapshotUri":"chat-editing-snapshot-text-model:/c%3A/Users/decid/Documents/projects/qt-py-haptics/PROJECT_STRUCTURE_OUTLINE.md?%7B%22session%22%3A%7B%22%24mid%22%3A1%2C%22external%22%3A%22vscode-chat-session%3A%2F%2Flocal%2FMzQ4N2Y0YWYtOWZkMC00Mzg3LWI0MTctYWQyYThmNzhhMjg3%22%2C%22path%22%3A%22%2FMzQ4N2Y0YWYtOWZkMC00Mzg3LWI0MTctYWQyYThmNzhhMjg3%22%2C%22scheme%22%3A%22vscode-chat-session%22%2C%22authority%22%3A%22local%22%7D%2C%22requestId%22%3A%22%22%2C%22undoStop%22%3A%22%22%7D","telemetryInfo":{"requestId":"request_dc7aedcd-b1dc-443b-8063-d3b5527768d7","agentId":"github.copilot.editsAgent","modelId":"copilot/gpt-5.3-codex","modeId":"agent"}},{"resource":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1","languageId":"powershell","originalHash":"d2ffae2","currentHash":"d2ffae2","state":1,"snapshotUri":"chat-editing-snapshot-text-model:/c%3A/Users/decid/Documents/projects/qt-py-haptics/tools/scripts/test_big_haptic_driver_exports.ps1?%7B%22session%22%3A%7B%22%24mid%22%3A1%2C%22external%22%3A%22vscode-chat-session%3A%2F%2Flocal%2FMzQ4N2Y0YWYtOWZkMC00Mzg3LWI0MTctYWQyYThmNzhhMjg3%22%2C%22path%22%3A%22%2FMzQ4N2Y0YWYtOWZkMC00Mzg3LWI0MTctYWQyYThmNzhhMjg3%22%2C%22scheme%22%3A%22vscode-chat-session%22%2C%22authority%22%3A%22local%22%7D%2C%22requestId%22%3A%22%22%2C%22undoStop%22%3A%22%22%7D","telemetryInfo":{"requestId":"request_0837dd8d-03e4-419a-ab9a-44ad661c4a6c","agentId":"github.copilot.editsAgent","modelId":"copilot/gpt-5.3-codex","modeId":"agent"}},{"resource":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_scaffold.rs","languageId":"rust","originalHash":"7a3813b","currentHash":"7a3813b","state":1,"snapshotUri":"chat-editing-snapshot-text-model:/c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_scaffold.rs?%7B%22session%22%3A%7B%22%24mid%22%3A1%2C%22external%22%3A%22vscode-chat-session%3A%2F%2Flocal%2FMzQ4N2Y0YWYtOWZkMC00Mzg3LWI0MTctYWQyYThmNzhhMjg3%22%2C%22path%22%3A%22%2FMzQ4N2Y0YWYtOWZkMC00Mzg3LWI0MTctYWQyYThmNzhhMjg3%22%2C%22scheme%22%3A%22vscode-chat-session%22%2C%22authority%22%3A%22local%22%7D%2C%22requestId%22%3A%22%22%2C%22undoStop%22%3A%22%22%7D","telemetryInfo":{"requestId":"request_253d5ff6-6f1d-4b2e-83a2-f98328aa01b9","agentId":"github.copilot.editsAgent","modelId":"copilot/gpt-5.3-codex","modeId":"agent"}},{"resource":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/lib.rs","languageId":"rust","originalHash":"3d5c32c","currentHash":"3d5c32c","state":1,"snapshotUri":"chat-editing-snapshot-text-model:/c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/lib.rs?%7B%22session%22%3A%7B%22%24mid%22%3A1%2C%22external%22%3A%22vscode-chat-session%3A%2F%2Flocal%2FMzQ4N2Y0YWYtOWZkMC00Mzg3LWI0MTctYWQyYThmNzhhMjg3%22%2C%22path%22%3A%22%2FMzQ4N2Y0YWYtOWZkMC00Mzg3LWI0MTctYWQyYThmNzhhMjg3%22%2C%22scheme%22%3A%22vscode-chat-session%22%2C%22authority%22%3A%22local%22%7D%2C%22requestId%22%3A%22%22%2C%22undoStop%22%3A%22%22%7D","telemetryInfo":{"requestId":"request_cd9235f7-4b6a-4823-ab9f-74e85fd0e64c","agentId":"github.copilot.editsAgent","modelId":"copilot/gpt-5.3-codex","modeId":"agent"}},{"resource":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/driver.vrdrivermanifest","languageId":"plaintext","originalHash":"d10de5b","currentHash":"d10de5b","state":1,"snapshotUri":"chat-editing-snapshot-text-model:/c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/driver.vrdrivermanifest?%7B%22session%22%3A%7B%22%24mid%22%3A1%2C%22external%22%3A%22vscode-chat-session%3A%2F%2Flocal%2FMzQ4N2Y0YWYtOWZkMC00Mzg3LWI0MTctYWQyYThmNzhhMjg3%22%2C%22path%22%3A%22%2FMzQ4N2Y0YWYtOWZkMC00Mzg3LWI0MTctYWQyYThmNzhhMjg3%22%2C%22scheme%22%3A%22vscode-chat-session%22%2C%22authority%22%3A%22local%22%7D%2C%22requestId%22%3A%22%22%2C%22undoStop%22%3A%22%22%7D","telemetryInfo":{"requestId":"request_2b97d49c-137d-4738-aaf4-66378596ea73","agentId":"github.copilot.editsAgent","modelId":"copilot/gpt-5.3-codex","modeId":"agent"}},{"resource":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs","languageId":"rust","originalHash":"9bb44f0","currentHash":"9bb44f0","state":1,"snapshotUri":"chat-editing-snapshot-text-model:/c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/haptics.rs?%7B%22session%22%3A%7B%22%24mid%22%3A1%2C%22external%22%3A%22vscode-chat-session%3A%2F%2Flocal%2FMzQ4N2Y0YWYtOWZkMC00Mzg3LWI0MTctYWQyYThmNzhhMjg3%22%2C%22path%22%3A%22%2FMzQ4N2Y0YWYtOWZkMC00Mzg3LWI0MTctYWQyYThmNzhhMjg3%22%2C%22scheme%22%3A%22vscode-chat-session%22%2C%22authority%22%3A%22local%22%7D%2C%22requestId%22%3A%22%22%2C%22undoStop%22%3A%22%22%7D","telemetryInfo":{"requestId":"request_61a7ab1c-d8bf-428d-b25c-57a9185ebc48","agentId":"github.copilot.editsAgent","modelId":"copilot/gpt-5.3-codex","modeId":"agent"}},{"resource":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs","languageId":"rust","originalHash":"87ea942","currentHash":"87ea942","state":1,"snapshotUri":"chat-editing-snapshot-text-model:/c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/src/openvr_runtime.rs?%7B%22session%22%3A%7B%22%24mid%22%3A1%2C%22external%22%3A%22vscode-chat-session%3A%2F%2Flocal%2FMzQ4N2Y0YWYtOWZkMC00Mzg3LWI0MTctYWQyYThmNzhhMjg3%22%2C%22path%22%3A%22%2FMzQ4N2Y0YWYtOWZkMC00Mzg3LWI0MTctYWQyYThmNzhhMjg3%22%2C%22scheme%22%3A%22vscode-chat-session%22%2C%22authority%22%3A%22local%22%7D%2C%22requestId%22%3A%22%22%2C%22undoStop%22%3A%22%22%7D","telemetryInfo":{"requestId":"request_253d5ff6-6f1d-4b2e-83a2-f98328aa01b9","agentId":"github.copilot.editsAgent","modelId":"copilot/gpt-5.3-codex","modeId":"agent"}},{"resource":"file:///c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/big_haptic_profile.json","languageId":"json","originalHash":"3c38674","currentHash":"3c38674","state":1,"snapshotUri":"chat-editing-snapshot-text-model:/c%3A/Users/decid/Documents/projects/qt-py-haptics/openvr-driver/big-haptic-driver/resources/input/big_haptic_profile.json?%7B%22session%22%3A%7B%22%24mid%22%3A1%2C%22external%22%3A%22vscode-chat-session%3A%2F%2Flocal%2FMzQ4N2Y0YWYtOWZkMC00Mzg3LWI0MTctYWQyYThmNzhhMjg3%22%2C%22path%22%3A%22%2FMzQ4N2Y0YWYtOWZkMC00Mzg3LWI0MTctYWQyYThmNzhhMjg3%22%2C%22scheme%22%3A%22vscode-chat-session%22%2C%22authority%22%3A%22local%22%7D%2C%22requestId%22%3A%22%22%2C%22undoStop%22%3A%22%22%7D","telemetryInfo":{"requestId":"request_253d5ff6-6f1d-4b2e-83a2-f98328aa01b9","agentId":"github.copilot.editsAgent","modelId":"copilot/gpt-5.3-codex","modeId":"agent"}}]}}