// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using Microsoft.MixedReality.Toolkit.Utilities; using System; using System.Collections.Generic; using System.Linq; using Unity.Profiling; using UnityEngine; using UInput = UnityEngine.Input; namespace Microsoft.MixedReality.Toolkit.Input.UnityInput { /// /// Manages joysticks using unity input system. /// [MixedRealityDataProvider( typeof(IMixedRealityInputSystem), (SupportedPlatforms)(-1), // All platforms supported by Unity "Unity Joystick Manager")] public class UnityJoystickManager : BaseInputDeviceManager { /// /// Constructor. /// /// The instance that loaded the data provider. /// The instance that receives data from this provider. /// Friendly name of the service. /// Service priority. Used to determine order of instantiation. /// The service's configuration profile. [Obsolete("This constructor is obsolete (registrar parameter is no longer required) and will be removed in a future version of the Microsoft Mixed Reality Toolkit.")] public UnityJoystickManager( IMixedRealityServiceRegistrar registrar, IMixedRealityInputSystem inputSystem, string name = null, uint priority = DefaultPriority, BaseMixedRealityProfile profile = null) : this(inputSystem, name, priority, profile) { Registrar = registrar; } /// /// Constructor. /// /// The instance that receives data from this provider. /// Friendly name of the service. /// Service priority. Used to determine order of instantiation. /// The service's configuration profile. public UnityJoystickManager( IMixedRealityInputSystem inputSystem, string name = null, uint priority = DefaultPriority, BaseMixedRealityProfile profile = null) : base(inputSystem, name, priority, profile) { } private const float DeviceRefreshInterval = 3.0f; protected readonly Dictionary ActiveControllers = new Dictionary(); private float deviceRefreshTimer; private string[] lastDeviceList; private static readonly ProfilerMarker UpdatePerfMarker = new ProfilerMarker("[MRTK] UnityJoystickManager.Update"); /// public override void Update() { using (UpdatePerfMarker.Auto()) { base.Update(); deviceRefreshTimer += Time.unscaledDeltaTime; if (deviceRefreshTimer >= DeviceRefreshInterval) { deviceRefreshTimer = 0.0f; RefreshDevices(); } foreach (var controller in ActiveControllers) { controller.Value?.UpdateController(); } } } /// public override void Disable() { base.Disable(); foreach (var genericJoystick in ActiveControllers) { if (genericJoystick.Value != null) { Service?.RaiseSourceLost(genericJoystick.Value.InputSource, genericJoystick.Value); } } ActiveControllers.Clear(); } private static readonly ProfilerMarker GetActiveControllersPerfMarker = new ProfilerMarker("[MRTK] UnityJoystickManager.GetActiveControllers"); /// public override IMixedRealityController[] GetActiveControllers() { using (GetActiveControllersPerfMarker.Auto()) { IMixedRealityController[] controllers = ActiveControllers.Values.ToArray(); return controllers; } } private static readonly ProfilerMarker RefreshDevicesPerfMarker = new ProfilerMarker("[MRTK] UnityJoystickManager.RefreshDevices"); private void RefreshDevices() { using (RefreshDevicesPerfMarker.Auto()) { var joystickNames = UInput.GetJoystickNames(); if (joystickNames.Length <= 0) { return; } if (lastDeviceList != null && joystickNames.Length == lastDeviceList.Length) { for (int i = 0; i < lastDeviceList.Length; i++) { if (joystickNames[i].Equals(lastDeviceList[i])) { continue; } if (ActiveControllers.ContainsKey(lastDeviceList[i])) { var controller = GetOrAddController(lastDeviceList[i]); if (controller != null) { Service?.RaiseSourceLost(controller.InputSource, controller); } RemoveController(lastDeviceList[i]); } } } for (var i = 0; i < joystickNames.Length; i++) { if (string.IsNullOrEmpty(joystickNames[i])) { continue; } if (!ActiveControllers.ContainsKey(joystickNames[i])) { var controller = GetOrAddController(joystickNames[i]); if (controller != null) { Service?.RaiseSourceDetected(controller.InputSource, controller); } } } lastDeviceList = joystickNames; } } private static readonly ProfilerMarker GetOrAddControllerPerfMarker = new ProfilerMarker("[MRTK] UnityJoystickManager.GetOrAddController"); /// /// Gets or adds a controller using the joystick name provided. /// /// The name of the joystick from Unity's Input.GetJoystickNames /// A new controller reference. protected virtual GenericJoystickController GetOrAddController(string joystickName) { using (GetOrAddControllerPerfMarker.Auto()) { if (ActiveControllers.ContainsKey(joystickName)) { var controller = ActiveControllers[joystickName]; Debug.Assert(controller != null); return controller; } Type controllerType; switch (GetCurrentControllerType(joystickName)) { default: return null; case SupportedControllerType.GenericUnity: controllerType = typeof(GenericJoystickController); break; case SupportedControllerType.Xbox: controllerType = typeof(XboxController); break; } IMixedRealityInputSource inputSource = Service?.RequestNewGenericInputSource($"{controllerType.Name} Controller", sourceType: InputSourceType.Controller); GenericJoystickController detectedController = Activator.CreateInstance(controllerType, TrackingState.NotTracked, Handedness.None, inputSource, null) as GenericJoystickController; if (detectedController == null || !detectedController.Enabled) { // Controller failed to be setup correctly. Debug.LogError($"Failed to create {controllerType.Name} controller"); // Return null so we don't raise the source detected. return null; } ActiveControllers.Add(joystickName, detectedController); return detectedController; } } /// /// Removes a controller using the joystick name provided. /// /// The name of the joystick from Unity's Input.GetJoystickNames protected virtual void RemoveController(string joystickName) { ActiveControllers.Remove(joystickName); } /// /// Gets the current controller type for the joystick name provided. /// /// The name of the joystick from Unity's Input.GetJoystickNames /// The supported controller type protected virtual SupportedControllerType GetCurrentControllerType(string joystickName) { // todo: this should be using an allow list, not a disallow list if (string.IsNullOrEmpty(joystickName) || joystickName.Contains("OpenVR") || // This catches input sources from legacy OpenVR joystickName.Contains("OpenXR") || // This catches input sources from OpenXR Plugin joystickName.Contains("Oculus") || // This catches controllers from Oculus XR Plugin joystickName.Contains("Hand - ") || // This catches HoloLens hands from Windows XR Plugin joystickName.Contains("Spatial")) // This catches controllers from Windows XR Plugin and all input sources from legacy WMR { return 0; } if (joystickName.ToLower().Contains("xbox")) { return SupportedControllerType.Xbox; } Debug.Log($"{joystickName} does not have a defined controller type, falling back to generic controller type"); return SupportedControllerType.GenericUnity; } } }