Android / iOS where the two platforms spell them differently.| Field | Type | Notes |
|---|---|---|
source | Enum | Which surface produced the event. See Input sources. |
timestampMs | 64-bit integer | An on-device monotonic timestamp in milliseconds, recorded when the glasses forward the interaction. Use it for ordering and for measuring intervals within the same device boot, not as a wall-clock time, a raw hardware-event time, or a phone timestamp. |
| Event (Android / iOS) | Extra fields | Meaning |
|---|---|---|
Nav / .nav | direction: UP, DOWN, LEFT, RIGHT / .up, .down, .left, .right | Directional navigation. |
Select / .select | None | Confirm or activate the focused item. |
Back / .back | None | Dismiss or go back. Not currently delivered by real glasses. See Current limitations. |
Button / .button | button: ACTION / .action | The physical action button was pressed. |
Capture / .capture | pressType: SHORT_PRESS, HOLD, DOUBLE_PRESS / .shortPress, .hold, .doublePress | The physical capture button was pressed. |
Drag / .drag | action, x, y, dx, dy | One event reporting the start, movement, or end of a drag interaction. |
Nav, Select, or Capture interaction produces exactly one event, emitted when the press is released. Do not expect separate down and up events for those; only Drag reports a multi-sample interaction. The action button is also reported as a single Button event per press, but see Current limitations for its hardware caveat.Drag event reports the phase of the interaction plus its position:| Field | Meaning |
|---|---|
action | The phase of the drag: DOWN / .down begins the stream, MOVE / .move continues it, UP / .up ends it. A complete drag is one DOWN, zero or more MOVE samples, and one UP. |
x, y | The absolute position of this sample, in the device’s input coordinate space. |
dx, dy | The change in position since the previous sample of the same drag, in the same units. |
Use https://wearables.developer.meta.com/docs/develop/dat/inputs/, then use the Wearables MCP endpoint https://mcp.developer.meta.com/wearables to call search_dat_docs for current Device Access Toolkit Inputs guidance. Inspect my code first, then add the smallest working slice: add the Inputs capability to a started device session, consume the event stream, and observe capability state and errors. Remember that adding the capability begins activation with no separate start call, so wait for the active state before relying on event delivery. Handle every InputEvent type including an unknown-default branch on iOS, request only the input sources my feature needs, retain the listener tokens for as long as I want callbacks, and remove the capability during teardown.
Permission value for input data, so you do not call checkPermissionStatus or requestPermission before using it. See Permissions and registration for the permissions that do require a user prompt.| Source (Android / iOS) | Surface | Events it produces |
|---|---|---|
CAPTOUCH / .captouch | The touchpad on the temple arm of the glasses. | Nav, Select |
NEURAL_BAND / .neuralBand | Discrete band inputs, such as a tap. | Nav, Select |
CAPTURE_BUTTON / .captureButton | The physical capture (camera) button. | Capture |
ACTION_BUTTON / .actionButton | The physical action button. | Button |
NEURAL_BAND_DRAG / .neuralBandDrag | The continuous band drag stream. | Drag |
UNKNOWN / .unknown | An unset or unrecognized surface. | Any |
UNKNOWN is the SDK’s decoding fallback, not a delivery guarantee: if an unset or unrecognized source reaches the SDK, it is represented as UNKNOWN. Because the default configuration requests the five named sources and does not include UNKNOWN, source filtering can discard an event from a future or unrecognized surface before the SDK decodes it. Handle the UNKNOWN case so your code stays exhaustive, but do not rely on it to receive events from surfaces this SDK version does not know about.Select, and a swipe arrives as Nav with the direction produced by the current device interaction mapping and settings. The wearer’s device settings can invert horizontal and vertical swipe output, so a forward swipe does not always produce the same direction. Treat the exact mapping as device behavior that can differ across device models and settings.| Interaction | Device behavior |
|---|---|
Single tap with one finger | Pauses or resumes the device session. |
Tap and hold with one finger | Stops the device session. |
HOLD / .hold press type if you need the button’s hold action. Because tapping and holding with one finger stops the session, a user who taps and holds the temple arm ends your session rather than triggering an in-app action; handle the resulting session state change as you would any other external termination. See Session lifecycle.InputsConfiguration to request specific sources. By default the capability requests every named source, so subscribe more narrowly when your feature only needs part of the surface area.| Setting | Default | Meaning |
|---|---|---|
sources | The five named sources; UNKNOWN is not included | The set of surfaces to capture. Events from a source you did not request are not delivered. An empty set ( emptySet() / []) is a wildcard meaning every available source. |
consumeBack | true | Whether a back interaction is delivered to your app instead of being handled by the system. Real glasses do not deliver Back yet. See Current limitations. |
start() call. Activation is asynchronous: an Android success result or an iOS return does not mean Inputs is already active. Wait for ACTIVE / .active before relying on event delivery or injecting Mock Device Kit events.| State (Android / iOS) | Meaning |
|---|---|
INACTIVE / .inactive | Not running. This is the initial state and the resting state after the capability stops. |
ACTIVATING / .activating | Activation was requested but the device has not acknowledged it yet. |
ACTIVE / .active | Events are being delivered as they occur. |
DEACTIVATING / .deactivating | Teardown is in progress. |
DEACTIVATING to INACTIVE. An error, a device disconnect, or the wearer doffing the glasses also ends at INACTIVE.INACTIVE.| Error | Meaning |
|---|---|
Permission denied | Your app is not permitted to receive input events from the glasses. |
Connection closed | The connection delivering input events closed. |
Activation timeout | Activation was not acknowledged within 10 seconds. |
Activation failed | Activation was refused or is unsupported. Unlike a timeout, this is a definitive failure. |
Device disconnected | The glasses disconnected while Inputs was active. |
Communication error | A communication failure occurred on the input channel. |
Capability unavailable | iOS only. The capability is not available on this device. |
InputsError in the API reference for Android and iOS.addInputs while an instance is still attached returns CAPABILITY_ALREADY_ADDED; call removeInputs() first if you deliberately want to replace the capability with a different configuration.removeInputs() before adding a replacement, or the next addInputs call throws an error.Back events are not currently delivered by real glasses. The Back event type exists in the SDK, but the on-glasses delivery path is not wired yet, so no Back event reaches your app from real hardware. Mock Device Kit can inject Back and honors consumeBack, so you can write and test your back handling today. Until real glasses deliver it, also provide another way to dismiss or go back, such as a Nav target or a Select on an explicit back item.Button with ACTION / .action is the only button event, and physical action-button support has not been validated on every target device. Treat it as unconfirmed on hardware you have not tested, and do not assume a press-and-hold of the action button produces an event.[libraries]
mwdat-core = { group = "com.meta.wearable", name = "mwdat-core", version.ref = "mwdat" }
mwdat-inputs = { group = "com.meta.wearable", name = "mwdat-inputs", version.ref = "mwdat" }
dependencies {
implementation(libs.mwdat.core)
implementation(libs.mwdat.inputs)
}
addInputs on a started DeviceSession. It returns a DatResult, so handle the failure branch rather than assuming success. A successful call attaches Inputs and begins activation; wait for ACTIVE before relying on events.import com.meta.wearable.dat.inputs.Inputs
import com.meta.wearable.dat.inputs.addInputs
import com.meta.wearable.dat.inputs.types.InputSource
import com.meta.wearable.dat.inputs.types.InputsConfiguration
val configuration = InputsConfiguration(
sources = setOf(InputSource.CAPTOUCH, InputSource.NEURAL_BAND),
)
val result = session.addInputs(configuration)
val inputs = result.getOrNull()
if (inputs == null) {
showError(result.errorOrNull()?.description.orEmpty())
return
}
getOrNull() and errorOrNull() to access the typed DeviceSessionError. You can also use fold or the two-parameter onFailure callback, which receives the typed error and its cause. A lambda is an unnamed function that you pass to another function. getOrElse takes one callback argument: a lambda that it calls on failure with one Throwable parameter. The Throwable type does not expose the DAT error’s description property, but it can contain a message and a cause.events, state, and errors on a coroutine scope that you cancel during teardown.import com.meta.wearable.dat.inputs.types.InputEvent
import com.meta.wearable.dat.inputs.types.NavDirection
scope.launch {
inputs.events.collect { event ->
when (event) {
is InputEvent.Nav ->
when (event.direction) {
NavDirection.UP -> moveFocusUp()
NavDirection.DOWN -> moveFocusDown()
NavDirection.LEFT -> moveFocusLeft()
NavDirection.RIGHT -> moveFocusRight()
}
is InputEvent.Select -> activateFocusedItem()
is InputEvent.Back -> dismiss()
is InputEvent.Button -> onActionButton()
is InputEvent.Capture -> onCapturePress(event.pressType)
is InputEvent.Drag -> onDrag(event.action, event.x, event.y, event.dx, event.dy)
}
}
}
InputEvent is a sealed class, so an exhaustive when gives you a compile error if a future SDK version adds an event type.state and errors to know when events will flow and why they stopped.import com.meta.wearable.dat.inputs.types.InputsState
scope.launch {
inputs.state.collect { state ->
if (state == InputsState.ACTIVE) onReceivingInput() else onNotReceivingInput()
}
}
scope.launch {
inputs.errors.collect { error ->
if (error != null) showError(error.description)
}
}
state and errors are state flows, so a new collector immediately receives the current value. errors holds null until something goes wrong and retains the last error afterward, so treat a repeated value as the same error rather than a new one. After a session reconnect the SDK restarts Inputs and resets errors to null.removeInputs is a separate extension function, so it needs its own import.import com.meta.wearable.dat.inputs.removeInputs session.removeInputs()
val glasses = mockKit.pairGlasses(GlassesModel.RAYBAN_META).getOrThrow() // ...after your app has added the Inputs capability and it has reached ACTIVE... glasses.services.input.navDown() glasses.services.input.select() glasses.services.input.capture(CapturePressType.SHORT_PRESS)
source, defaulting to CAPTOUCH, so you can simulate the same action arriving from the band. capture(), button(), and drag() take no source: their events always report the capture button, the action button, and the band drag stream respectively. See Android testing for how to create a mock device.import MWDATCore import MWDATInputs
addInputs returns nil if the session has not started, and throws if an Inputs capability is already attached. A successful return attaches Inputs and begins activation; wait for .active before relying on events.let configuration = InputsConfiguration(sources: [.captouch, .neuralBand])
guard let inputs = try session.addInputs(configuration: configuration) else { return }
addInputs creates the Inputs object and begins activating the capability before the addInputs call returns, so register your listeners on the returned Inputs object immediately. Neither publisher replays, so an error published in the window before you register cannot be retrieved later; inputs.state always reports the current state.ListenerTokenBag held by the same object that owns the capability.private let listenerTokens = ListenerTokenBag()
inputs.statePublisher.listen { state in
Task { @MainActor in
// Update your UI for .activating, .active, .deactivating, or .inactive
}
}.store(in: listenerTokens)
inputs.errorPublisher.listen { error in
Task { @MainActor in
switch error {
case .permissionDenied:
showPermissionHelp()
case .connectionClosed, .activationTimeout, .activationFailed, .capabilityUnavailable,
.deviceDisconnected, .communicationError:
showError(error)
@unknown default:
showError(error)
}
// Every error leaves the capability attached: remove it before adding a replacement.
}
}
events is an AsyncStream, so iterate it in a task that you retain and cancel during teardown.let events = inputs.events
eventsTask = Task {
for await event in events {
switch event {
case let .nav(direction, _, _):
await moveFocus(direction)
case .select:
await activateFocusedItem()
case .back:
await dismiss()
case .button:
await onActionButton()
case let .capture(pressType, _, _):
await onCapturePress(pressType)
case let .drag(action, x, y, dx, dy, _, _):
await onDrag(action, x, y, dx, dy)
@unknown default:
break
}
}
}
InputEvent and InputsError are public enums that are not @frozen, so add an @unknown default branch to every switch over them. Without it your code stops compiling when a future SDK version adds a case.source and timestampMs directly on the event, so you can read them without unpacking the associated values.eventsTask?.cancel() try session.removeInputs()
mockGlasses.services.input.navDown() mockGlasses.services.input.select() mockGlasses.services.input.capture(pressType: .shortPress)
consumeBack filters, so an injected event from a source you did not request, or a Back injection when consumeBack is false, is dropped exactly as it would be on device.DatResult you unwrap. iOS returns an optional that is nil when the session has not started, and throws when a capability is already attached.InputEvent as a sealed class with a data class per event, so an exhaustive when is checked at compile time. iOS models it as a non-frozen enum with associated values, so switches need an @unknown default.errors as a Kotlin StateFlow, which holds a current value your app can observe. A collector is app code that calls collect to read that value and receive updates. A new collector receives the current value: a retained error, or null if no error is stored. It does not receive an error history. The SDK resets the value to null when Inputs restarts after a session reconnect. On iOS, a listener is an app callback registered with errorPublisher.listen. It receives errors published while it is registered. Errors published before registration are not sent again.