/** * @file device_provider.hpp * @brief OpenVR device provider for the MicMap sidecar driver. * * The MicMap driver is a pure sidecar: it registers ZERO tracked devices and * instead creates a `/input/system/click` boolean component directly on the * HMD property container. Press commands arrive via an HTTP server running * on a worker thread; they are enqueued on a CommandQueue and drained in * RunFrame on the SteamVR driver thread (which is where the OpenVR driver-API * calls legally live). See .planning/phases/01-driver-sidecar-migration/ * 01-RESEARCH.md Pattern 1 for the full state machine. */ #pragma once #include #include #include #include #include namespace micmap::driver { // Forward declarations class HttpServer; class CommandQueue; /** * @brief HMD `/input/system/click` component lifecycle state (Pattern 1). * * - NotReady: cold-start; we have not yet observed a valid HMD property * container. Next RunFrame will poll for one. * - Ready: CreateBooleanComponent succeeded and hSystemClick_ is valid. * UpdateBooleanComponent may be called. * - Invalidated: HMD was deactivated OR an UpdateBooleanComponent call * returned a non-None error. RunFrame will attempt to recreate on the * next tick. */ enum class HmdComponentState { NotReady, Ready, Invalidated }; /** * @brief Device provider implementing IServerTrackedDeviceProvider. * * This provider does NOT call TrackedDeviceAdded. It owns: * - An HttpServer (worker thread, localhost-only). * - A CommandQueue (MPSC, depth-8, drop-oldest). * - A single VRInputComponentHandle_t for /input/system/click on the HMD * property container. * - Min-hold / max-hold state for the button-press safety envelope. */ class DeviceProvider : public vr::IServerTrackedDeviceProvider { public: DeviceProvider(); ~DeviceProvider(); // IServerTrackedDeviceProvider interface 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: /** * @brief Write a new value to /input/system/click if it differs from the * last written value. On error, invalidates handle + flips to Invalidated. */ void writeValue(bool v); std::unique_ptr commandQueue_; std::unique_ptr httpServer_; vr::VRInputComponentHandle_t hSystemClick_{vr::k_ulInvalidInputComponentHandle}; HmdComponentState state_{HmdComponentState::NotReady}; // Min-hold timing (D-04, D-05) std::chrono::steady_clock::time_point pressTimestamp_{}; std::optional pendingReleaseAt_; bool isPressed_{false}; bool lastWrittenValue_{false}; // Transition-only logging flags (D-08) bool initLogged_{false}; bool loggedAwaitingHmd_{false}; std::atomic initialized_{false}; // Min-hold / max-hold durations static constexpr std::chrono::milliseconds kMinHold{100}; static constexpr std::chrono::milliseconds kMaxHold{5000}; }; } // namespace micmap::driver