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/dom/Gamepad.h"
15 #include "mozilla/dom/GamepadManager.h"
17 namespace mozilla::dom
{
19 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(XRInputSource
, mParent
, mTargetRaySpace
,
22 // Follow the controller profile ids from
23 // https://github.com/immersive-web/webxr-input-profiles.
24 nsTArray
<nsString
> GetInputSourceProfile(gfx::VRControllerType aType
) {
25 nsTArray
<nsString
> profile
;
29 case gfx::VRControllerType::HTCVive
:
30 id
.AssignLiteral("htc-vive");
31 profile
.AppendElement(id
);
32 id
.AssignLiteral("generic-trigger-squeeze-touchpad");
33 profile
.AppendElement(id
);
35 case gfx::VRControllerType::HTCViveCosmos
:
36 id
.AssignLiteral("htc-vive-cosmos");
37 profile
.AppendElement(id
);
38 id
.AssignLiteral("generic-trigger-squeeze-thumbstick");
39 profile
.AppendElement(id
);
41 case gfx::VRControllerType::HTCViveFocus
:
42 id
.AssignLiteral("htc-vive-focus");
43 profile
.AppendElement(id
);
44 id
.AssignLiteral("generic-trigger-touchpad");
45 profile
.AppendElement(id
);
47 case gfx::VRControllerType::HTCViveFocusPlus
:
48 id
.AssignLiteral("htc-vive-focus-plus");
49 profile
.AppendElement(id
);
50 id
.AssignLiteral("generic-trigger-squeeze-touchpad");
51 profile
.AppendElement(id
);
53 case gfx::VRControllerType::MSMR
:
54 id
.AssignLiteral("microsoft-mixed-reality");
55 profile
.AppendElement(id
);
56 id
.AssignLiteral("generic-trigger-squeeze-touchpad-thumbstick");
57 profile
.AppendElement(id
);
59 case gfx::VRControllerType::ValveIndex
:
60 id
.AssignLiteral("valve-index");
61 profile
.AppendElement(id
);
62 id
.AssignLiteral("generic-trigger-squeeze-touchpad-thumbstick");
63 profile
.AppendElement(id
);
65 case gfx::VRControllerType::OculusGo
:
66 id
.AssignLiteral("oculus-go");
67 profile
.AppendElement(id
);
68 id
.AssignLiteral("generic-trigger-touchpad");
69 profile
.AppendElement(id
);
71 case gfx::VRControllerType::OculusTouch
:
72 id
.AssignLiteral("oculus-touch");
73 profile
.AppendElement(id
);
74 id
.AssignLiteral("generic-trigger-squeeze-thumbstick");
75 profile
.AppendElement(id
);
77 case gfx::VRControllerType::OculusTouch2
:
78 id
.AssignLiteral("oculus-touch-v2");
79 profile
.AppendElement(id
);
80 id
.AssignLiteral("oculus-touch");
81 profile
.AppendElement(id
);
82 id
.AssignLiteral("generic-trigger-squeeze-thumbstick");
83 profile
.AppendElement(id
);
85 case gfx::VRControllerType::OculusTouch3
:
86 id
.AssignLiteral("oculus-touch-v3");
87 profile
.AppendElement(id
);
88 id
.AssignLiteral("oculus-touch-v2");
89 profile
.AppendElement(id
);
90 id
.AssignLiteral("oculus-touch");
91 profile
.AppendElement(id
);
92 id
.AssignLiteral("generic-trigger-squeeze-thumbstick");
93 profile
.AppendElement(id
);
95 case gfx::VRControllerType::PicoGaze
:
96 id
.AssignLiteral("pico-gaze");
97 profile
.AppendElement(id
);
98 id
.AssignLiteral("generic-button");
99 profile
.AppendElement(id
);
101 case gfx::VRControllerType::PicoG2
:
102 id
.AssignLiteral("pico-g2");
103 profile
.AppendElement(id
);
104 id
.AssignLiteral("generic-trigger-touchpad");
105 profile
.AppendElement(id
);
107 case gfx::VRControllerType::PicoNeo2
:
108 id
.AssignLiteral("pico-neo2");
109 profile
.AppendElement(id
);
110 id
.AssignLiteral("generic-trigger-squeeze-thumbstick");
111 profile
.AppendElement(id
);
114 NS_WARNING("Unsupported XR input source profile.\n");
120 XRInputSource::XRInputSource(nsISupports
* aParent
)
124 mSelectAction(ActionState::ActionState_Released
),
125 mSqueezeAction(ActionState::ActionState_Released
) {}
127 XRInputSource::~XRInputSource() {
128 mTargetRaySpace
= nullptr;
129 mGripSpace
= nullptr;
133 JSObject
* XRInputSource::WrapObject(JSContext
* aCx
,
134 JS::Handle
<JSObject
*> aGivenProto
) {
135 return XRInputSource_Binding::Wrap(aCx
, this, aGivenProto
);
138 XRHandedness
XRInputSource::Handedness() { return mHandedness
; }
140 XRTargetRayMode
XRInputSource::TargetRayMode() { return mTargetRayMode
; }
142 XRSpace
* XRInputSource::TargetRaySpace() { return mTargetRaySpace
; }
144 XRSpace
* XRInputSource::GetGripSpace() { return mGripSpace
; }
146 void XRInputSource::GetProfiles(nsTArray
<nsString
>& aResult
) {
147 aResult
= mProfiles
.Clone();
150 Gamepad
* XRInputSource::GetGamepad() { return mGamepad
; }
152 void XRInputSource::Setup(XRSession
* aSession
, uint32_t aIndex
) {
153 MOZ_ASSERT(aSession
);
154 gfx::VRDisplayClient
* displayClient
= aSession
->GetDisplayClient();
155 if (!displayClient
) {
156 MOZ_ASSERT(displayClient
);
159 const gfx::VRDisplayInfo
& displayInfo
= displayClient
->GetDisplayInfo();
160 const gfx::VRControllerState
& controllerState
=
161 displayInfo
.mControllerState
[aIndex
];
162 MOZ_ASSERT(controllerState
.controllerName
[0] != '\0');
164 mProfiles
= GetInputSourceProfile(controllerState
.type
);
165 mHandedness
= XRHandedness::None
;
166 switch (controllerState
.hand
) {
167 case GamepadHand::_empty
:
168 mHandedness
= XRHandedness::None
;
170 case GamepadHand::Left
:
171 mHandedness
= XRHandedness::Left
;
173 case GamepadHand::Right
:
174 mHandedness
= XRHandedness::Right
;
177 MOZ_ASSERT(false && "Unknown GamepadHand type.");
181 RefPtr
<XRNativeOrigin
> nativeOriginTargetRay
= nullptr;
182 mTargetRayMode
= XRTargetRayMode::Tracked_pointer
;
183 switch (controllerState
.targetRayMode
) {
184 case gfx::TargetRayMode::Gaze
:
185 mTargetRayMode
= XRTargetRayMode::Gaze
;
186 nativeOriginTargetRay
= new XRNativeOriginViewer(displayClient
);
188 case gfx::TargetRayMode::TrackedPointer
:
189 mTargetRayMode
= XRTargetRayMode::Tracked_pointer
;
190 // We use weak pointers of poses in XRNativeOriginTracker to sync their
192 nativeOriginTargetRay
=
193 new XRNativeOriginTracker(&controllerState
.targetRayPose
);
195 case gfx::TargetRayMode::Screen
:
196 mTargetRayMode
= XRTargetRayMode::Screen
;
199 MOZ_ASSERT(false && "Undefined TargetRayMode type.");
203 mTargetRaySpace
= new XRInputSpace(aSession
->GetParentObject(), aSession
,
204 nativeOriginTargetRay
, aIndex
);
206 const uint32_t gamepadHandleValue
=
207 displayInfo
.mDisplayID
* gfx::kVRControllerMaxCount
+ aIndex
;
209 const GamepadHandle gamepadHandle
{gamepadHandleValue
, GamepadHandleKind::VR
};
212 new Gamepad(mParent
, NS_ConvertASCIItoUTF16(""), -1, gamepadHandle
,
213 GamepadMappingType::Xr_standard
, controllerState
.hand
,
214 displayInfo
.mDisplayID
, controllerState
.numButtons
,
215 controllerState
.numAxes
, controllerState
.numHaptics
, 0, 0);
219 CreateGripSpace(aSession
, controllerState
);
223 void XRInputSource::SetGamepadIsConnected(bool aConnected
,
224 XRSession
* aSession
) {
225 mGamepad
->SetConnected(aConnected
);
226 MOZ_ASSERT(aSession
);
229 if (mSelectAction
!= ActionState::ActionState_Released
) {
230 DispatchEvent(u
"selectend"_ns
, aSession
);
231 mSelectAction
= ActionState::ActionState_Released
;
233 if (mSqueezeAction
!= ActionState::ActionState_Released
) {
234 DispatchEvent(u
"squeezeend"_ns
, aSession
);
235 mSqueezeAction
= ActionState::ActionState_Released
;
240 void XRInputSource::Update(XRSession
* aSession
) {
241 MOZ_ASSERT(aSession
&& mIndex
>= 0 && mGamepad
);
243 gfx::VRDisplayClient
* displayClient
= aSession
->GetDisplayClient();
244 if (!displayClient
) {
245 MOZ_ASSERT(displayClient
);
248 const gfx::VRDisplayInfo
& displayInfo
= displayClient
->GetDisplayInfo();
249 const gfx::VRControllerState
& controllerState
=
250 displayInfo
.mControllerState
[mIndex
];
251 MOZ_ASSERT(controllerState
.controllerName
[0] != '\0');
253 // OculusVR and OpenVR controllers need to wait until
254 // update functions to assign GamepadCapabilityFlags::Cap_GripSpacePosition
257 CreateGripSpace(aSession
, controllerState
);
260 // Update button values.
261 nsTArray
<RefPtr
<GamepadButton
>> buttons
;
262 mGamepad
->GetButtons(buttons
);
263 for (uint32_t i
= 0; i
< buttons
.Length(); ++i
) {
264 const bool pressed
= controllerState
.buttonPressed
& (1ULL << i
);
265 const bool touched
= controllerState
.buttonTouched
& (1ULL << i
);
267 if (buttons
[i
]->Pressed() != pressed
|| buttons
[i
]->Touched() != touched
||
268 buttons
[i
]->Value() != controllerState
.triggerValue
[i
]) {
269 mGamepad
->SetButton(i
, pressed
, touched
, controllerState
.triggerValue
[i
]);
272 // Update axis values.
273 nsTArray
<double> axes
;
274 mGamepad
->GetAxes(axes
);
275 for (uint32_t i
= 0; i
< axes
.Length(); ++i
) {
276 if (axes
[i
] != controllerState
.axisValue
[i
]) {
277 mGamepad
->SetAxis(i
, controllerState
.axisValue
[i
]);
281 // We define 0.85f and 0.15f based on our current finding
282 // for better experience, we can adjust these values if we need.
283 const float completeThreshold
= 0.90f
;
284 const float startThreshold
= 0.85f
;
285 const float endThreshold
= 0.15f
;
286 const uint32_t selectIndex
= 0;
287 const uint32_t squeezeIndex
= 1;
289 // Checking selectstart, select, selectend
290 if (buttons
.Length() > selectIndex
) {
291 if (controllerState
.selectActionStartFrameId
>
292 controllerState
.selectActionStopFrameId
) {
293 if (mSelectAction
== ActionState::ActionState_Released
&&
294 controllerState
.triggerValue
[selectIndex
] > endThreshold
) {
295 DispatchEvent(u
"selectstart"_ns
, aSession
);
296 mSelectAction
= ActionState::ActionState_Pressing
;
297 } else if (mSelectAction
== ActionState::ActionState_Pressing
&&
298 controllerState
.triggerValue
[selectIndex
] >
300 mSelectAction
= ActionState::ActionState_Pressed
;
301 } else if (mSelectAction
== ActionState::ActionState_Pressed
&&
302 controllerState
.triggerValue
[selectIndex
] < startThreshold
) {
303 DispatchEvent(u
"select"_ns
, aSession
);
304 mSelectAction
= ActionState::ActionState_Releasing
;
305 } else if (mSelectAction
<= ActionState::ActionState_Releasing
&&
306 controllerState
.triggerValue
[selectIndex
] < endThreshold
) {
307 // For a select btn which only has pressed and unpressed status.
308 if (mSelectAction
== ActionState::ActionState_Pressed
) {
309 DispatchEvent(u
"select"_ns
, aSession
);
311 DispatchEvent(u
"selectend"_ns
, aSession
);
312 mSelectAction
= ActionState::ActionState_Released
;
314 } else if (mSelectAction
<= ActionState::ActionState_Releasing
) {
315 // For a select btn which only has pressed and unpressed status.
316 if (mSelectAction
== ActionState::ActionState_Pressed
) {
317 DispatchEvent(u
"select"_ns
, aSession
);
319 DispatchEvent(u
"selectend"_ns
, aSession
);
320 mSelectAction
= ActionState::ActionState_Released
;
324 // Checking squeezestart, squeeze, squeezeend
325 if (buttons
.Length() > squeezeIndex
) {
326 if (controllerState
.squeezeActionStartFrameId
>
327 controllerState
.squeezeActionStopFrameId
) {
328 if (mSqueezeAction
== ActionState::ActionState_Released
&&
329 controllerState
.triggerValue
[squeezeIndex
] > endThreshold
) {
330 DispatchEvent(u
"squeezestart"_ns
, aSession
);
331 mSqueezeAction
= ActionState::ActionState_Pressing
;
332 } else if (mSqueezeAction
== ActionState::ActionState_Pressing
&&
333 controllerState
.triggerValue
[squeezeIndex
] >
335 mSqueezeAction
= ActionState::ActionState_Pressed
;
336 } else if (mSqueezeAction
== ActionState::ActionState_Pressed
&&
337 controllerState
.triggerValue
[squeezeIndex
] < startThreshold
) {
338 DispatchEvent(u
"squeeze"_ns
, aSession
);
339 mSqueezeAction
= ActionState::ActionState_Releasing
;
340 } else if (mSqueezeAction
<= ActionState::ActionState_Releasing
&&
341 controllerState
.triggerValue
[squeezeIndex
] < endThreshold
) {
342 // For a squeeze btn which only has pressed and unpressed status.
343 if (mSqueezeAction
== ActionState::ActionState_Pressed
) {
344 DispatchEvent(u
"squeeze"_ns
, aSession
);
346 DispatchEvent(u
"squeezeend"_ns
, aSession
);
347 mSqueezeAction
= ActionState::ActionState_Released
;
349 } else if (mSqueezeAction
<= ActionState::ActionState_Releasing
) {
350 // For a squeeze btn which only has pressed and unpressed status.
351 if (mSqueezeAction
== ActionState::ActionState_Pressed
) {
352 DispatchEvent(u
"squeeze"_ns
, aSession
);
354 DispatchEvent(u
"squeezeend"_ns
, aSession
);
355 mSqueezeAction
= ActionState::ActionState_Released
;
360 int32_t XRInputSource::GetIndex() { return mIndex
; }
362 void XRInputSource::DispatchEvent(const nsAString
& aEvent
,
363 XRSession
* aSession
) {
364 if (!GetParentObject() || !aSession
) {
367 // Create a XRFrame for its callbacks
368 RefPtr
<XRFrame
> frame
= new XRFrame(GetParentObject(), aSession
);
369 frame
->StartInputSourceEvent();
371 XRInputSourceEventInit init
;
372 init
.mBubbles
= false;
373 init
.mCancelable
= false;
375 init
.mInputSource
= this;
377 RefPtr
<XRInputSourceEvent
> event
=
378 XRInputSourceEvent::Constructor(aSession
, aEvent
, init
);
380 event
->SetTrusted(true);
381 aSession
->DispatchEvent(*event
);
382 frame
->EndInputSourceEvent();
385 void XRInputSource::CreateGripSpace(
386 XRSession
* aSession
, const gfx::VRControllerState
& controllerState
) {
387 MOZ_ASSERT(!mGripSpace
);
388 MOZ_ASSERT(aSession
&& mIndex
>= 0 && mGamepad
);
389 if (mTargetRayMode
== XRTargetRayMode::Tracked_pointer
&&
390 controllerState
.flags
& GamepadCapabilityFlags::Cap_GripSpacePosition
) {
391 RefPtr
<XRNativeOrigin
> nativeOriginGrip
= nullptr;
392 nativeOriginGrip
= new XRNativeOriginTracker(&controllerState
.pose
);
393 mGripSpace
= new XRInputSpace(aSession
->GetParentObject(), aSession
,
394 nativeOriginGrip
, mIndex
);
396 mGripSpace
= nullptr;
400 } // namespace mozilla::dom