1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/dom/XRInputSource.h"
8 #include "mozilla/dom/XRInputSourceEvent.h"
9 #include "XRNativeOriginViewer.h"
10 #include "XRNativeOriginTracker.h"
11 #include "XRInputSpace.h"
12 #include "VRDisplayClient.h"
14 #include "mozilla/HoldDropJSObjects.h"
15 #include "mozilla/dom/Gamepad.h"
16 #include "mozilla/dom/GamepadManager.h"
21 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(XRInputSource
, mParent
, mTargetRaySpace
,
23 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(XRInputSource
, AddRef
)
24 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(XRInputSource
, Release
)
26 // Follow the controller profile ids from
27 // https://github.com/immersive-web/webxr-input-profiles.
28 nsTArray
<nsString
> GetInputSourceProfile(gfx::VRControllerType aType
) {
29 nsTArray
<nsString
> profile
;
33 case gfx::VRControllerType::HTCVive
:
34 id
.AssignLiteral("htc-vive");
35 profile
.AppendElement(id
);
36 id
.AssignLiteral("generic-trigger-squeeze-touchpad");
37 profile
.AppendElement(id
);
39 case gfx::VRControllerType::HTCViveCosmos
:
40 id
.AssignLiteral("htc-vive-cosmos");
41 profile
.AppendElement(id
);
42 id
.AssignLiteral("generic-trigger-squeeze-thumbstick");
43 profile
.AppendElement(id
);
45 case gfx::VRControllerType::HTCViveFocus
:
46 id
.AssignLiteral("htc-vive-focus");
47 profile
.AppendElement(id
);
48 id
.AssignLiteral("generic-trigger-touchpad");
49 profile
.AppendElement(id
);
51 case gfx::VRControllerType::HTCViveFocusPlus
:
52 id
.AssignLiteral("htc-vive-focus-plus");
53 profile
.AppendElement(id
);
54 id
.AssignLiteral("generic-trigger-squeeze-touchpad");
55 profile
.AppendElement(id
);
57 case gfx::VRControllerType::MSMR
:
58 id
.AssignLiteral("microsoft-mixed-reality");
59 profile
.AppendElement(id
);
60 id
.AssignLiteral("generic-trigger-squeeze-touchpad-thumbstick");
61 profile
.AppendElement(id
);
63 case gfx::VRControllerType::ValveIndex
:
64 id
.AssignLiteral("valve-index");
65 profile
.AppendElement(id
);
66 id
.AssignLiteral("generic-trigger-squeeze-touchpad-thumbstick");
67 profile
.AppendElement(id
);
69 case gfx::VRControllerType::OculusGo
:
70 id
.AssignLiteral("oculus-go");
71 profile
.AppendElement(id
);
72 id
.AssignLiteral("generic-trigger-touchpad");
73 profile
.AppendElement(id
);
75 case gfx::VRControllerType::OculusTouch
:
76 id
.AssignLiteral("oculus-touch");
77 profile
.AppendElement(id
);
78 id
.AssignLiteral("generic-trigger-squeeze-thumbstick");
79 profile
.AppendElement(id
);
81 case gfx::VRControllerType::OculusTouch2
:
82 id
.AssignLiteral("oculus-touch-v2");
83 profile
.AppendElement(id
);
84 id
.AssignLiteral("oculus-touch");
85 profile
.AppendElement(id
);
86 id
.AssignLiteral("generic-trigger-squeeze-thumbstick");
87 profile
.AppendElement(id
);
89 case gfx::VRControllerType::PicoGaze
:
90 id
.AssignLiteral("pico-gaze");
91 profile
.AppendElement(id
);
92 id
.AssignLiteral("generic-button");
93 profile
.AppendElement(id
);
95 case gfx::VRControllerType::PicoG2
:
96 id
.AssignLiteral("pico-g2");
97 profile
.AppendElement(id
);
98 id
.AssignLiteral("generic-trigger-touchpad");
99 profile
.AppendElement(id
);
101 case gfx::VRControllerType::PicoNeo2
:
102 id
.AssignLiteral("pico-neo2");
103 profile
.AppendElement(id
);
104 id
.AssignLiteral("generic-trigger-squeeze-thumbstick");
105 profile
.AppendElement(id
);
108 NS_WARNING("Unsupported XR input source profile.\n");
114 XRInputSource::XRInputSource(nsISupports
* aParent
)
118 mSelectAction(ActionState::ActionState_Released
),
119 mSqueezeAction(ActionState::ActionState_Released
) {}
121 XRInputSource::~XRInputSource() {
122 mTargetRaySpace
= nullptr;
123 mGripSpace
= nullptr;
125 mozilla::DropJSObjects(this);
128 JSObject
* XRInputSource::WrapObject(JSContext
* aCx
,
129 JS::Handle
<JSObject
*> aGivenProto
) {
130 return XRInputSource_Binding::Wrap(aCx
, this, aGivenProto
);
133 XRHandedness
XRInputSource::Handedness() { return mHandedness
; }
135 XRTargetRayMode
XRInputSource::TargetRayMode() { return mTargetRayMode
; }
137 XRSpace
* XRInputSource::TargetRaySpace() { return mTargetRaySpace
; }
139 XRSpace
* XRInputSource::GetGripSpace() { return mGripSpace
; }
141 void XRInputSource::GetProfiles(nsTArray
<nsString
>& aResult
) {
142 aResult
= mProfiles
.Clone();
145 Gamepad
* XRInputSource::GetGamepad() { return mGamepad
; }
147 void XRInputSource::Setup(XRSession
* aSession
, uint32_t aIndex
) {
148 MOZ_ASSERT(aSession
);
149 gfx::VRDisplayClient
* displayClient
= aSession
->GetDisplayClient();
150 if (!displayClient
) {
151 MOZ_ASSERT(displayClient
);
154 const gfx::VRDisplayInfo
& displayInfo
= displayClient
->GetDisplayInfo();
155 const gfx::VRControllerState
& controllerState
=
156 displayInfo
.mControllerState
[aIndex
];
157 MOZ_ASSERT(controllerState
.controllerName
[0] != '\0');
159 mProfiles
= GetInputSourceProfile(controllerState
.type
);
160 mHandedness
= XRHandedness::None
;
161 switch (controllerState
.hand
) {
162 case GamepadHand::_empty
:
163 mHandedness
= XRHandedness::None
;
165 case GamepadHand::Left
:
166 mHandedness
= XRHandedness::Left
;
168 case GamepadHand::Right
:
169 mHandedness
= XRHandedness::Right
;
172 MOZ_ASSERT(false && "Unknown GamepadHand type.");
176 RefPtr
<XRNativeOrigin
> nativeOriginTargetRay
= nullptr;
177 mTargetRayMode
= XRTargetRayMode::Tracked_pointer
;
178 switch (controllerState
.targetRayMode
) {
179 case gfx::TargetRayMode::Gaze
:
180 mTargetRayMode
= XRTargetRayMode::Gaze
;
181 nativeOriginTargetRay
= new XRNativeOriginViewer(displayClient
);
183 case gfx::TargetRayMode::TrackedPointer
:
184 mTargetRayMode
= XRTargetRayMode::Tracked_pointer
;
185 // We use weak pointers of poses in XRNativeOriginTracker to sync their
187 nativeOriginTargetRay
=
188 new XRNativeOriginTracker(&controllerState
.targetRayPose
);
190 case gfx::TargetRayMode::Screen
:
191 mTargetRayMode
= XRTargetRayMode::Screen
;
194 MOZ_ASSERT(false && "Undefined TargetRayMode type.");
198 mTargetRaySpace
= new XRInputSpace(aSession
->GetParentObject(), aSession
,
199 nativeOriginTargetRay
, aIndex
);
201 const uint32_t gamepadHandleValue
=
202 displayInfo
.mDisplayID
* gfx::kVRControllerMaxCount
+ aIndex
;
204 const GamepadHandle gamepadHandle
{gamepadHandleValue
, GamepadHandleKind::VR
};
207 new Gamepad(mParent
, NS_ConvertASCIItoUTF16(""), -1, gamepadHandle
,
208 GamepadMappingType::Xr_standard
, controllerState
.hand
,
209 displayInfo
.mDisplayID
, controllerState
.numButtons
,
210 controllerState
.numAxes
, controllerState
.numHaptics
, 0, 0);
214 CreateGripSpace(aSession
, controllerState
);
218 void XRInputSource::SetGamepadIsConnected(bool aConnected
,
219 XRSession
* aSession
) {
220 mGamepad
->SetConnected(aConnected
);
221 MOZ_ASSERT(aSession
);
224 if (mSelectAction
!= ActionState::ActionState_Released
) {
225 DispatchEvent(u
"selectend"_ns
, aSession
);
226 mSelectAction
= ActionState::ActionState_Released
;
228 if (mSqueezeAction
!= ActionState::ActionState_Released
) {
229 DispatchEvent(u
"squeezeend"_ns
, aSession
);
230 mSqueezeAction
= ActionState::ActionState_Released
;
235 void XRInputSource::Update(XRSession
* aSession
) {
236 MOZ_ASSERT(aSession
&& mIndex
>= 0 && mGamepad
);
238 gfx::VRDisplayClient
* displayClient
= aSession
->GetDisplayClient();
239 if (!displayClient
) {
240 MOZ_ASSERT(displayClient
);
243 const gfx::VRDisplayInfo
& displayInfo
= displayClient
->GetDisplayInfo();
244 const gfx::VRControllerState
& controllerState
=
245 displayInfo
.mControllerState
[mIndex
];
246 MOZ_ASSERT(controllerState
.controllerName
[0] != '\0');
248 // OculusVR and OpenVR controllers need to wait until
249 // update functions to assign GamepadCapabilityFlags::Cap_GripSpacePosition
252 CreateGripSpace(aSession
, controllerState
);
255 // Update button values.
256 nsTArray
<RefPtr
<GamepadButton
>> buttons
;
257 mGamepad
->GetButtons(buttons
);
258 for (uint32_t i
= 0; i
< buttons
.Length(); ++i
) {
259 const bool pressed
= controllerState
.buttonPressed
& (1ULL << i
);
260 const bool touched
= controllerState
.buttonTouched
& (1ULL << i
);
262 if (buttons
[i
]->Pressed() != pressed
|| buttons
[i
]->Touched() != touched
||
263 buttons
[i
]->Value() != controllerState
.triggerValue
[i
]) {
264 mGamepad
->SetButton(i
, pressed
, touched
, controllerState
.triggerValue
[i
]);
267 // Update axis values.
268 nsTArray
<double> axes
;
269 mGamepad
->GetAxes(axes
);
270 for (uint32_t i
= 0; i
< axes
.Length(); ++i
) {
271 if (axes
[i
] != controllerState
.axisValue
[i
]) {
272 mGamepad
->SetAxis(i
, controllerState
.axisValue
[i
]);
276 // We define 0.85f and 0.15f based on our current finding
277 // for better experience, we can adjust these values if we need.
278 const float completeThreshold
= 0.90f
;
279 const float startThreshold
= 0.85f
;
280 const float endThreshold
= 0.15f
;
281 const uint32_t selectIndex
= 0;
282 const uint32_t squeezeIndex
= 1;
284 // Checking selectstart, select, selectend
285 if (buttons
.Length() > selectIndex
) {
286 if (controllerState
.selectActionStartFrameId
>
287 controllerState
.selectActionStopFrameId
) {
288 if (mSelectAction
== ActionState::ActionState_Released
&&
289 controllerState
.triggerValue
[selectIndex
] > endThreshold
) {
290 DispatchEvent(u
"selectstart"_ns
, aSession
);
291 mSelectAction
= ActionState::ActionState_Pressing
;
292 } else if (mSelectAction
== ActionState::ActionState_Pressing
&&
293 controllerState
.triggerValue
[selectIndex
] >
295 mSelectAction
= ActionState::ActionState_Pressed
;
296 } else if (mSelectAction
== ActionState::ActionState_Pressed
&&
297 controllerState
.triggerValue
[selectIndex
] < startThreshold
) {
298 DispatchEvent(u
"select"_ns
, aSession
);
299 mSelectAction
= ActionState::ActionState_Releasing
;
300 } else if (mSelectAction
<= ActionState::ActionState_Releasing
&&
301 controllerState
.triggerValue
[selectIndex
] < endThreshold
) {
302 // For a select btn which only has pressed and unpressed status.
303 if (mSelectAction
== ActionState::ActionState_Pressed
) {
304 DispatchEvent(u
"select"_ns
, aSession
);
306 DispatchEvent(u
"selectend"_ns
, aSession
);
307 mSelectAction
= ActionState::ActionState_Released
;
309 } else if (mSelectAction
<= ActionState::ActionState_Releasing
) {
310 // For a select btn which only has pressed and unpressed status.
311 if (mSelectAction
== ActionState::ActionState_Pressed
) {
312 DispatchEvent(u
"select"_ns
, aSession
);
314 DispatchEvent(u
"selectend"_ns
, aSession
);
315 mSelectAction
= ActionState::ActionState_Released
;
319 // Checking squeezestart, squeeze, squeezeend
320 if (buttons
.Length() > squeezeIndex
) {
321 if (controllerState
.squeezeActionStartFrameId
>
322 controllerState
.squeezeActionStopFrameId
) {
323 if (mSqueezeAction
== ActionState::ActionState_Released
&&
324 controllerState
.triggerValue
[squeezeIndex
] > endThreshold
) {
325 DispatchEvent(u
"squeezestart"_ns
, aSession
);
326 mSqueezeAction
= ActionState::ActionState_Pressing
;
327 } else if (mSqueezeAction
== ActionState::ActionState_Pressing
&&
328 controllerState
.triggerValue
[squeezeIndex
] >
330 mSqueezeAction
= ActionState::ActionState_Pressed
;
331 } else if (mSqueezeAction
== ActionState::ActionState_Pressed
&&
332 controllerState
.triggerValue
[squeezeIndex
] < startThreshold
) {
333 DispatchEvent(u
"squeeze"_ns
, aSession
);
334 mSqueezeAction
= ActionState::ActionState_Releasing
;
335 } else if (mSqueezeAction
<= ActionState::ActionState_Releasing
&&
336 controllerState
.triggerValue
[squeezeIndex
] < endThreshold
) {
337 // For a squeeze btn which only has pressed and unpressed status.
338 if (mSqueezeAction
== ActionState::ActionState_Pressed
) {
339 DispatchEvent(u
"squeeze"_ns
, aSession
);
341 DispatchEvent(u
"squeezeend"_ns
, aSession
);
342 mSqueezeAction
= ActionState::ActionState_Released
;
344 } else if (mSqueezeAction
<= ActionState::ActionState_Releasing
) {
345 // For a squeeze btn which only has pressed and unpressed status.
346 if (mSqueezeAction
== ActionState::ActionState_Pressed
) {
347 DispatchEvent(u
"squeeze"_ns
, aSession
);
349 DispatchEvent(u
"squeezeend"_ns
, aSession
);
350 mSqueezeAction
= ActionState::ActionState_Released
;
355 int32_t XRInputSource::GetIndex() { return mIndex
; }
357 void XRInputSource::DispatchEvent(const nsAString
& aEvent
,
358 XRSession
* aSession
) {
359 if (!GetParentObject() || !aSession
) {
362 // Create a XRFrame for its callbacks
363 RefPtr
<XRFrame
> frame
= new XRFrame(GetParentObject(), aSession
);
364 frame
->StartInputSourceEvent();
366 XRInputSourceEventInit init
;
367 init
.mBubbles
= false;
368 init
.mCancelable
= false;
370 init
.mInputSource
= this;
372 RefPtr
<XRInputSourceEvent
> event
=
373 XRInputSourceEvent::Constructor(aSession
, aEvent
, init
);
375 event
->SetTrusted(true);
376 aSession
->DispatchEvent(*event
);
377 frame
->EndInputSourceEvent();
380 void XRInputSource::CreateGripSpace(
381 XRSession
* aSession
, const gfx::VRControllerState
& controllerState
) {
382 MOZ_ASSERT(!mGripSpace
);
383 MOZ_ASSERT(aSession
&& mIndex
>= 0 && mGamepad
);
384 if (mTargetRayMode
== XRTargetRayMode::Tracked_pointer
&&
385 controllerState
.flags
& GamepadCapabilityFlags::Cap_GripSpacePosition
) {
386 RefPtr
<XRNativeOrigin
> nativeOriginGrip
= nullptr;
387 nativeOriginGrip
= new XRNativeOriginTracker(&controllerState
.pose
);
388 mGripSpace
= new XRInputSpace(aSession
->GetParentObject(), aSession
,
389 nativeOriginGrip
, mIndex
);
391 mGripSpace
= nullptr;
396 } // namespace mozilla