#pragma once #include #include #include #include #include #include #include #include class HidDevice; class LedController; class DeviceProvider : public vr::IServerTrackedDeviceProvider { public: DeviceProvider(); ~DeviceProvider(); vr::EVRInitError Init( vr::IVRDriverContext* pDriverContext ) override; void Cleanup() override; const char* const* GetInterfaceVersions() override; void RunFrame() override; bool ShouldBlockStandbyMode() override; void EnterStandby() override; void LeaveStandby() override; private: std::unique_ptr m_pHidDevice; // Configuration (read from VRSettings on Init) int32_t m_reportRateMs = 200; int32_t m_movingAvgLength = 8; bool m_logVerbose = false; // Proximity state -- input component on HMD container (Phase 11.1) bool m_bProximity = false; bool m_bManualOverride = false; vr::VRInputComponentHandle_t m_hProximityComponent = vr::k_ulInvalidInputComponentHandle; bool m_bProximityComponentAttempted = false; void SetHmdProximity(bool on); void TryCreateProximityComponent(); // IPD state float m_fCurrentIpd = 0.0f; // Current IPD in meters (0 = unknown) bool m_bLhConfigLoaded = false; // true after rotation matrices cached from config.json std::string m_sTrackingSerial; // from HID user flash, for lhr-* folder matching float m_cachedLeftRot[3][3] = {}; float m_cachedRightRot[3][3] = {}; #ifdef ENABLE_IPD_PERSIST // IPD persistence (Phase 13) float m_fStartupIpd = 0.0f; // IPD at session start for D-03 change detection void PersistIpdToConfig(); std::string FindLighthouseConsole(); #endif #ifdef ENABLE_BACKGLOW // Phase 14: Backglow LED subsystem (D-21) std::unique_ptr m_pLedController; int32_t m_backglowCeiling = 50; // D-12 default std::string m_backglowPort; // D-14 bool m_backglowDisabled = false; // D-15 (true = no port / open failed) bool m_backglowDisabledLogged = false; // log INFO only once // Phase 15: transport selection (D-01..D-04, D-18) enum class BackglowXp { USB, DDP, AUTO }; BackglowXp m_backglowTransport = BackglowXp::USB; // D-01 default (planner discretion) std::string m_backglowDdpHost; // D-05 IPv4-only std::string m_backglowDisabledReason; // D-15 err: token (e.g. "scan_no_match") uint8_t m_lastReportedBri = 0; // surfaces in `status` (D-14) bool m_bWsaInitialized = false; // WSAStartup/Cleanup pair flag // Hotplug (D-15) HWND m_hHotplugWindow = nullptr; HDEVNOTIFY m_hHotplugNotify = nullptr; std::thread m_hotplugThread; std::atomic m_bHotplugStop{false}; // CR-01 (Phase 15 review): serialises access to backglow shared state // between the hotplug thread (OnHotplugArrival) and the vrserver // RunFrame/PollPipe thread (HandleBackglowCommand). Guards: // m_backglowDisabled, m_backglowDisabledReason, // m_backglowPort, m_pLedController. std::mutex m_backglowMtx; void InitBackglow(); void StartHotplugWatcher(); void StopHotplugWatcher(); void HotplugThreadFunc(); void HandleBackglowCommand(const char* args, char* response, size_t responseSize); // Phase 16 VRCH-01 daemon lifecycle (D-02..D-06, D-24). // Phase 16 CR-01/WR-03 fix: m_hBackglowProcess and m_hBackglowStdinWrite // are read on the watchdog thread and written on the spawn/stop threads. // Promote to std::atomic so handle reads/writes across threads are // defined behavior, and use .exchange(nullptr) so only one path ever // CloseHandles any given value (avoids double-close race with the // watchdog when Stop() is called while the watchdog's Wait is returning). // Phase 16 re-review WR-01 fix: m_hBackglowJob is also read + closed // across threads (watchdog-thread respawn path writes it; main-thread // StopBackglowDaemon reads + closes it). Promote to std::atomic // with the same exchange(nullptr) discipline to avoid double-close / // STATUS_INVALID_HANDLE races with SetInformationJobObject on respawn. std::atomic m_hBackglowJob{nullptr}; std::atomic m_hBackglowProcess{nullptr}; std::atomic m_hBackglowStdinWrite{nullptr}; std::thread m_backglowWatchdogThread; std::atomic m_bStopBackglowWatchdog{false}; std::atomic m_backglowRespawnCount{0}; std::chrono::steady_clock::time_point m_backglowSpawnTime{}; bool SpawnBackglowDaemon(); void StopBackglowDaemon(); void BackglowWatchdogThreadFunc(); public: // Public trampoline invoked by the message-only hotplug window proc // (static free function in device_provider.cpp). Called from the hotplug // thread on DBT_DEVICEARRIVAL with a 500 ms debounce inside. void OnHotplugArrival(); private: #endif // Beyond 1 detection (per D-09, D-10, D-12) bool m_bIsBeyond1 = false; bool m_bHmdSerialChecked = false; void CheckHmdSerial(); // Track B native slider (per D-01) bool m_bSliderPropsAttempted = false; void TrySetSliderProperties(); // Track A settings tab polling (per D-03) float m_fLastSettingsIpd = 0.0f; // last value read from VRSettings ipd_mm // Shared IPD application (refactored from HandleIpdSet) bool ApplyIpd(float mm); // IPD methods bool LoadLighthouseConfig(); bool LoadLighthouseConfigFromPath(const std::string& path); bool ReadEyeToHeadFromConfigFile(const std::string& configPath); void HandleIpdSet(float mm, char* response, size_t responseSize); void HandleIpdQuery(char* response, size_t responseSize); void HandleLoadLhConfig(const char* path, char* response, size_t responseSize); // Named pipe server for debug control (CLI — request/response, single-shot // per connection). Phase 14. HANDLE m_hPipe = INVALID_HANDLE_VALUE; bool m_bClientConnected = false; void CreatePipeServer(); void PollPipe(); void HandlePipeCommand(const char* cmd, DWORD len); void DestroyPipeServer(); #ifdef ENABLE_BACKGLOW // Phase 16 VRCH-01 fix: dedicated daemon pipe. The CLI pipe above is // request/response with per-message FlushFileBuffers + DisconnectNamedPipe // — fundamentally incompatible with the daemon's fire-and-forget 90 Hz // telemetry stream (FlushFileBuffers waits for the client to ReadFile, // daemon never reads — blocks server_main and trips vrserver's 12 s // watchdog). This second pipe is daemon-only: inbound message mode, // multi-message, no response, no disconnect-per-message. HANDLE m_hDaemonPipe = INVALID_HANDLE_VALUE; bool m_bDaemonPipeClientConnected = false; void CreateDaemonPipeServer(); void PollDaemonPipe(); void DestroyDaemonPipeServer(); #endif // Debug: handle testing (retained from spike) void HandleTestHandle(uint64_t handle, bool value, char* response, size_t responseSize); };