# HMD Button Stub: Creating Input Components on the HMD from a Sidecar Driver

This document describes how a SteamVR sidecar driver (one that does not register any devices via `TrackedDeviceAdded`) can create and update input components on the HMD device owned by another driver (e.g., the lighthouse driver).

This technique was discovered during the BeyondProximity project and is applicable to any sidecar driver that needs to inject input state onto the HMD.

## Background

The lighthouse driver creates input components on the HMD at activation time, including:
- `/input/system/click` (handle 1) — system button
- `/proximity` (handle 2) — proximity sensor

These handles are owned by the lighthouse driver. **You cannot update another driver's input components** — `UpdateBooleanComponent` returns `VRInputError_WrongType` (err=2) for handles you didn't create, regardless of interface version.

However, you **can** create a new component with the same path name on the same device container, and SteamVR will accept and propagate updates from your component to the application-side input system.

## Steps

### 1. Get the HMD property container handle

```cpp
vr::PropertyContainerHandle_t hmdContainer =
    vr::VRProperties()->TrackedDeviceToPropertyContainer(
        vr::k_unTrackedDeviceIndex_Hmd);
```

**Important:** This returns `k_ulInvalidPropertyContainer` if the HMD has not been activated yet by its owning driver. At `Init()` time, the HMD is typically not ready. You must defer this call.

### 2. Defer component creation until the HMD is ready

Call `TrackedDeviceToPropertyContainer` on each `RunFrame()` tick until it returns a valid handle. Once valid, create your component and stop retrying.

```cpp
// In your header:
vr::VRInputComponentHandle_t m_hMyComponent = vr::k_ulInvalidInputComponentHandle;
bool m_bComponentAttempted = false;

// In RunFrame():
if (!m_bComponentAttempted)
{
    vr::PropertyContainerHandle_t hmd =
        vr::VRProperties()->TrackedDeviceToPropertyContainer(
            vr::k_unTrackedDeviceIndex_Hmd);

    if (hmd != vr::k_ulInvalidPropertyContainer)
    {
        m_bComponentAttempted = true;

        vr::EVRInputError err = vr::VRDriverInput()->CreateBooleanComponent(
            hmd, "/input/system/click", &m_hMyComponent);

        if (err == vr::VRInputError_None)
        {
            // Success — m_hMyComponent is now a valid handle you own
            // and can update freely
        }
        else
        {
            m_hMyComponent = vr::k_ulInvalidInputComponentHandle;
            // Fallback behavior
        }
    }
}
```

Replace `"/input/system/click"` with whatever component path you need. For proximity, use `"/proximity"`.

### 3. Update the component

```cpp
if (m_hMyComponent != vr::k_ulInvalidInputComponentHandle)
{
    vr::EVRInputError err = vr::VRDriverInput()->UpdateBooleanComponent(
        m_hMyComponent, newValue, 0.0);
    // err should be VRInputError_None (0)
}
```

This propagates to the application side:
- `IVRSystem::GetTrackedDeviceActivityLevel(0)` reflects the state (for `/proximity`)
- `VREvent_TrackedDeviceUserInteractionStarted` / `Ended` events fire (for `/proximity`)
- Button state in `VRControllerState_t::ulButtonPressed` may reflect the state (for `/input/system/click`)

### 4. For scalar components

The same pattern works with `CreateScalarComponent` / `UpdateScalarComponent`:

```cpp
vr::VRInputComponentHandle_t hScalar;
vr::VRDriverInput()->CreateScalarComponent(
    hmd, "/input/my_axis/value", &hScalar,
    vr::VRScalarType_Absolute, vr::VRScalarUnits_NormalizedOneSided);

vr::VRDriverInput()->UpdateScalarComponent(hScalar, 0.75f, 0.0);
```

## Key Facts

- **No device registration required.** The sidecar driver never calls `TrackedDeviceAdded`. It creates components directly on the HMD container.
- **Duplicate path names are allowed.** SteamVR permits multiple components with the same path on the same device from different drivers. Your component coexists with the original driver's component.
- **Handles are per-driver.** Handle 3 in your driver is not the same as handle 3 in another driver. Each driver gets its own sequential handle allocation starting from the next available integer.
- **Cross-driver updates are blocked.** You cannot call `UpdateBooleanComponent` on a handle created by another driver. It returns `VRInputError_WrongType` (2). Create your own component instead.
- **Timing matters.** `CreateBooleanComponent` on the HMD container fails with `VRInputError_InvalidParam` (4) if called before the HMD is activated. Defer to `RunFrame()`.
- **IVRDriverInput version doesn't matter.** Both `IVRDriverInput_003` and `_004` share the same handle space and behave identically for Create/Update operations.
- **Tested on:** SteamVR (March 2026), OpenVR SDK v2.5.1, Windows 11
