// 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;
}
}
}