Skip to content

Commit a839860

Browse files
authored
Add support for "select" voice command with OpenXR (#10661)
* Add support for "select" voice command with OpenXR * Update MicrosoftOpenXRGGVHand.cs
1 parent d092fa9 commit a839860

File tree

3 files changed

+176
-0
lines changed

3 files changed

+176
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using Microsoft.MixedReality.Toolkit.Input;
5+
using Microsoft.MixedReality.Toolkit.Utilities;
6+
using Microsoft.MixedReality.Toolkit.XRSDK.Input;
7+
using UnityEngine;
8+
9+
namespace Microsoft.MixedReality.Toolkit.XRSDK.OpenXR
10+
{
11+
/// <summary>
12+
/// A GGV (Gaze, Gesture, and Voice) hand instance for OpenXR.
13+
/// Used only for the purposes of acting on the select keyword detected by HoloLens 2.
14+
/// </summary>
15+
[MixedRealityController(
16+
SupportedControllerType.GGVHand,
17+
new[] { Handedness.Left, Handedness.Right, Handedness.None })]
18+
internal class MicrosoftOpenXRGGVHand : GenericXRSDKController
19+
{
20+
public MicrosoftOpenXRGGVHand(
21+
TrackingState trackingState,
22+
Handedness controllerHandedness,
23+
IMixedRealityInputSource inputSource = null,
24+
MixedRealityInteractionMapping[] interactions = null)
25+
: base(trackingState, controllerHandedness, inputSource, interactions, new SimpleHandDefinition(controllerHandedness))
26+
{ }
27+
28+
internal void UpdateVoiceState(bool isPressed)
29+
{
30+
MixedRealityInteractionMapping interactionMapping = null;
31+
32+
for (int i = 0; i < Interactions?.Length; i++)
33+
{
34+
MixedRealityInteractionMapping currentInteractionMapping = Interactions[i];
35+
36+
if (currentInteractionMapping.AxisType == AxisType.Digital && currentInteractionMapping.InputType == DeviceInputType.Select)
37+
{
38+
interactionMapping = currentInteractionMapping;
39+
break;
40+
}
41+
}
42+
43+
if (interactionMapping == null)
44+
{
45+
return;
46+
}
47+
48+
interactionMapping.BoolData = isPressed;
49+
50+
// If our value changed raise it.
51+
if (interactionMapping.Changed)
52+
{
53+
// Raise input system event if it's enabled
54+
if (interactionMapping.BoolData)
55+
{
56+
CoreServices.InputSystem?.RaiseOnInputDown(InputSource, ControllerHandedness, interactionMapping.MixedRealityInputAction);
57+
}
58+
else
59+
{
60+
CoreServices.InputSystem?.RaiseOnInputUp(InputSource, ControllerHandedness, interactionMapping.MixedRealityInputAction);
61+
}
62+
}
63+
}
64+
}
65+
}

Assets/MRTK/Providers/OpenXR/Scripts/MicrosoftOpenXRGGVHand.cs.meta

+11
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Assets/MRTK/Providers/OpenXR/Scripts/OpenXRDeviceManager.cs

+100
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#if MSFT_OPENXR && WINDOWS_UWP
1717
using Microsoft.MixedReality.OpenXR;
1818
using Microsoft.MixedReality.Toolkit.Windows.Input;
19+
using Windows.UI.Input.Spatial;
1920
#endif // MSFT_OPENXR && WINDOWS_UWP
2021

2122
namespace Microsoft.MixedReality.Toolkit.XRSDK.OpenXR
@@ -80,6 +81,7 @@ public override void Enable()
8081

8182
#if MSFT_OPENXR && WINDOWS_UWP
8283
CreateGestureRecognizers();
84+
SpatialInteractionManager.SourcePressed += SpatialInteractionManager_SourcePressed;
8385
#endif // MSFT_OPENXR && WINDOWS_UWP
8486

8587
base.Enable();
@@ -114,6 +116,26 @@ public override void Update()
114116
base.Update();
115117

116118
CheckForGestures();
119+
120+
if (shouldSendVoiceEvents)
121+
{
122+
MicrosoftOpenXRGGVHand controller = GetOrAddVoiceController();
123+
if (controller != null)
124+
{
125+
// RaiseOnInputDown for "select"
126+
controller.UpdateVoiceState(true);
127+
// RaiseOnInputUp for "select"
128+
controller.UpdateVoiceState(false);
129+
130+
// On WMR, the voice recognizer does not actually register the phrase 'select'
131+
// when you add it to the speech commands profile. Therefore, simulate
132+
// the "select" voice command running to ensure that we get a select voice command
133+
// registered. This is used by FocusProvider to detect when the select pointer is active.
134+
Service?.RaiseSpeechCommandRecognized(controller.InputSource, RecognitionConfidenceLevel.High, TimeSpan.MinValue, DateTime.Now, new SpeechCommands("select", KeyCode.Alpha1, MixedRealityInputAction.None));
135+
}
136+
137+
shouldSendVoiceEvents = false;
138+
}
117139
}
118140

119141
/// <inheritdoc />
@@ -140,6 +162,14 @@ public override void Disable()
140162
#endif
141163
navigationGestureRecognizer = null;
142164

165+
SpatialInteractionManager.SourcePressed -= SpatialInteractionManager_SourcePressed;
166+
167+
if (voiceController != null)
168+
{
169+
RemoveControllerFromScene(voiceController);
170+
voiceController = null;
171+
}
172+
143173
base.Disable();
144174
}
145175
#endif // MSFT_OPENXR && WINDOWS_UWP
@@ -529,5 +559,75 @@ private GenericXRSDKController FindMatchingController(GestureHandedness gestureH
529559
#endif // MSFT_OPENXR && WINDOWS_UWP
530560

531561
#endregion Gesture implementation
562+
563+
#region SpatialInteractionManager event and helpers
564+
565+
#if MSFT_OPENXR && WINDOWS_UWP
566+
/// <summary>
567+
/// SDK Interaction Source Pressed Event handler. Used only for voice.
568+
/// </summary>
569+
/// <param name="args">SDK source pressed event arguments</param>
570+
private void SpatialInteractionManager_SourcePressed(SpatialInteractionManager sender, SpatialInteractionSourceEventArgs args)
571+
{
572+
if (args.State.Source.Kind == SpatialInteractionSourceKind.Voice)
573+
{
574+
shouldSendVoiceEvents = true;
575+
}
576+
}
577+
578+
private MicrosoftOpenXRGGVHand voiceController = null;
579+
private bool shouldSendVoiceEvents = false;
580+
581+
private MicrosoftOpenXRGGVHand GetOrAddVoiceController()
582+
{
583+
if (voiceController != null)
584+
{
585+
return voiceController;
586+
}
587+
588+
IMixedRealityInputSource inputSource = Service?.RequestNewGenericInputSource("Mixed Reality Voice", sourceType: InputSourceType.Voice);
589+
MicrosoftOpenXRGGVHand detectedController = new MicrosoftOpenXRGGVHand(TrackingState.NotTracked, Utilities.Handedness.None, inputSource);
590+
591+
if (!detectedController.Enabled)
592+
{
593+
// Controller failed to be setup correctly.
594+
// Return null so we don't raise the source detected.
595+
return null;
596+
}
597+
598+
for (int i = 0; i < detectedController.InputSource?.Pointers?.Length; i++)
599+
{
600+
detectedController.InputSource.Pointers[i].Controller = detectedController;
601+
}
602+
603+
Service?.RaiseSourceDetected(detectedController.InputSource, detectedController);
604+
605+
voiceController = detectedController;
606+
return voiceController;
607+
}
608+
609+
private SpatialInteractionManager spatialInteractionManager = null;
610+
611+
/// <summary>
612+
/// Provides access to the current native SpatialInteractionManager.
613+
/// </summary>
614+
private SpatialInteractionManager SpatialInteractionManager
615+
{
616+
get
617+
{
618+
if (spatialInteractionManager == null)
619+
{
620+
UnityEngine.WSA.Application.InvokeOnUIThread(() =>
621+
{
622+
spatialInteractionManager = SpatialInteractionManager.GetForCurrentView();
623+
}, true);
624+
}
625+
626+
return spatialInteractionManager;
627+
}
628+
}
629+
#endif // MSFT_OPENXR && WINDOWS_UWP
630+
631+
#endregion SpatialInteractionManager events
532632
}
533633
}

0 commit comments

Comments
 (0)