using System;
using System.Linq;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Layouts;
using UnityEngine.InputSystem.LowLevel;
using UnityEngine.InputSystem.Utilities;
using UnityEngine.InputSystem.XR;
using UnityEngine.XR.OpenXR.Features;
using UnityEngine.XR.OpenXR.Features.Interactions;
#if UNITY_EDITOR
using UnityEditor;
#endif // UNITY_EDITOR
namespace UnityEngine.XR.OpenXR.Input
{
///
/// OpenXR Input related functionality.
///
public static class OpenXRInput
{
[StructLayout(LayoutKind.Explicit)]
private struct SerializedGuid
{
[FieldOffset(0)]
public Guid guid;
[FieldOffset(0)]
public ulong ulong1;
[FieldOffset(8)]
public ulong ulong2;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
internal struct SerializedBinding
{
///
/// Identifier of the action (created with CreateAction) to bind to
///
public ulong actionId;
///
/// OpenXR path to bind too (full path including user path)
///
public string path;
}
///
/// Flags used to indicate which parts of the the input source name is being requested from
///
[Flags]
public enum InputSourceNameFlags
{
///
/// Request the localized name of the user path as part of the input source name
///
UserPath = 1,
///
/// Request the localized name of the interaction profile as part of the input source name
///
InteractionProfile = 2,
///
/// Request the localized name of the component as part of the input source name
///
Component = 4,
///
/// Request all components
///
All = UserPath | InteractionProfile | Component
}
///
/// Dictionary that provides a conversion between InputSystem.ExpectedControlType to OpenXRInteractionFeature.ActionType
///
private static readonly Dictionary ExpectedControlTypeToActionType = new Dictionary
{
// Binary
["Digital"] = OpenXRInteractionFeature.ActionType.Binary,
["Button"] = OpenXRInteractionFeature.ActionType.Binary,
// Axis1D
["Axis"] = OpenXRInteractionFeature.ActionType.Axis1D,
["Integer"] = OpenXRInteractionFeature.ActionType.Axis1D,
["Analog"] = OpenXRInteractionFeature.ActionType.Axis1D,
// Axis2D
["Vector2"] = OpenXRInteractionFeature.ActionType.Axis2D,
["Dpad"] = OpenXRInteractionFeature.ActionType.Axis2D,
["Stick"] = OpenXRInteractionFeature.ActionType.Axis2D,
// Pose
["Pose"] = OpenXRInteractionFeature.ActionType.Pose,
["Vector3"] = OpenXRInteractionFeature.ActionType.Pose,
["Quaternion"] = OpenXRInteractionFeature.ActionType.Pose,
// Haptics
["Haptic"] = OpenXRInteractionFeature.ActionType.Vibrate
};
private const string s_devicePoseActionName = "devicepose";
private const string s_pointerActionName = "pointer";
///
/// Dictionary used to map virtual controls to concrete controls.
///
private static readonly Dictionary kVirtualControlMap = new Dictionary
{
["deviceposition"] = s_devicePoseActionName,
["devicerotation"] = s_devicePoseActionName,
["trackingstate"] = s_devicePoseActionName,
["istracked"] = s_devicePoseActionName,
["pointerposition"] = s_pointerActionName,
["pointerrotation"] = s_pointerActionName
};
#if ENABLE_INPUT_SYSTEM && UNITY_EDITOR
[InitializeOnLoadMethod]
private static void RegisterFeatureLayouts()
{
static void OnFirstFrame()
{
EditorApplication.update -= OnFirstFrame;
// In the editor we need to make sure the OpenXR layouts get registered even if the user doesn't
// navigate to the project settings. The following code will register the base layouts as well
// as any enabled interaction features.
RegisterLayouts();
}
// LoadAssetFromPath is not supported from within InitializeOnLoad. To work around this we register
// an update callback and wait for the first frame before registering our feature layouts.
EditorApplication.update += OnFirstFrame;
}
#endif
internal static void RegisterLayouts()
{
#if ENABLE_INPUT_SYSTEM
InputSystem.InputSystem.RegisterLayout("Haptic");
#if USE_INPUT_SYSTEM_POSE_CONTROL
#if UNITY_FORCE_INPUTSYSTEM_XR_OFF
InputSystem.InputSystem.RegisterLayout("Pose");
#endif //UNITY_FORCE_INPUTSYSTEM_XR_OFF
#else
InputSystem.InputSystem.RegisterLayout("Pose");
#endif //USE_INPUT_SYSTEM_POSE_CONTROL
InputSystem.InputSystem.RegisterLayout();
InputSystem.InputSystem.RegisterLayout(matches: new InputDeviceMatcher()
.WithInterface(XRUtilities.InterfaceMatchAnyVersion)
.WithProduct(@"Head Tracking - OpenXR")
.WithManufacturer(@"OpenXR"));
OpenXRInteractionFeature.RegisterLayouts();
#endif // ENABLE_INPUT_SYSTEM
}
///
/// Validates a given ActionMapConfig to ensure that it is generally set up correctly.
///
/// InteractionFeature the ActionMapConfig belongs to
/// ActionMapConfig to validate
/// True if the action map config is valid
private static bool ValidateActionMapConfig(OpenXRInteractionFeature interactionFeature, OpenXRInteractionFeature.ActionMapConfig actionMapConfig)
{
var valid = true;
if (actionMapConfig.deviceInfos == null || actionMapConfig.deviceInfos.Count == 0)
{
Debug.LogError($"ActionMapConfig contains no `deviceInfos` in InteractionFeature '{interactionFeature.GetType()}'");
valid = false;
}
if (actionMapConfig.actions == null || actionMapConfig.actions.Count == 0)
{
Debug.LogError($"ActionMapConfig contains no `actions` in InteractionFeature '{interactionFeature.GetType()}'");
valid = false;
}
return valid;
}
///
/// Attach all Unity actions to OpenXR
/// Note: this should not be called more than once per session
///
internal static void AttachActionSets()
{
var actionMaps = new List();
var additiveActionMaps = new List();
foreach (var interactionFeature in OpenXRSettings.Instance.features.OfType().Where(f => f.enabled && !f.IsAdditive))
{
var start = actionMaps.Count;
interactionFeature.CreateActionMaps(actionMaps);
for (var index = actionMaps.Count - 1; index >= start; index--)
{
if (!ValidateActionMapConfig(interactionFeature, actionMaps[index]))
actionMaps.RemoveAt(index);
}
}
if (!RegisterDevices(actionMaps, false))
return;
foreach (var feature in OpenXRSettings.Instance.features.OfType().Where(f => f.enabled && f.IsAdditive))
{
//Create action maps for additive profiles and add extra actions to non-additive profiles.
feature.CreateActionMaps(additiveActionMaps);
feature.AddAdditiveActions(actionMaps, additiveActionMaps[additiveActionMaps.Count - 1]);
}
var interactionProfiles = new Dictionary>();
if (!CreateActions(actionMaps, interactionProfiles))
return;
if (additiveActionMaps.Count > 0)
{
RegisterDevices(additiveActionMaps, true);
CreateActions(additiveActionMaps, interactionProfiles);
}
//Support Binding modifications if available
SetDpadBindingCustomValues();
// Suggest bindings
foreach (var kv in interactionProfiles)
{
if (!Internal_SuggestBindings(kv.Key, kv.Value.ToArray(), (uint)kv.Value.Count))
OpenXRRuntime.LogLastError();
}
// Attach actions sets to commit all bindings
if (!Internal_AttachActionSets())
OpenXRRuntime.LogLastError();
}
private static bool RegisterDevices(List actionMaps, bool isAdditive)
{
foreach (var actionMap in actionMaps)
{
foreach (var deviceInfo in actionMap.deviceInfos)
{
var localizedName = actionMap.desiredInteractionProfile == null ? UserPathToDeviceName(deviceInfo.userPath) : actionMap.localizedName;
if (0 == Internal_RegisterDeviceDefinition(deviceInfo.userPath, actionMap.desiredInteractionProfile, isAdditive, (uint)deviceInfo.characteristics, localizedName, actionMap.manufacturer, actionMap.serialNumber))
{
OpenXRRuntime.LogLastError();
return false;
}
}
}
return true;
}
private static bool CreateActions(List actionMaps, Dictionary> interactionProfiles)
{
foreach (var actionMap in actionMaps)
{
string actionMapLocalizedName = SanitizeStringForOpenXRPath(actionMap.localizedName);
var actionSetId = Internal_CreateActionSet(SanitizeStringForOpenXRPath(actionMap.name), actionMapLocalizedName, new SerializedGuid());
if (0 == actionSetId)
{
OpenXRRuntime.LogLastError();
return false;
}
// User paths specified in the deviceInfo
var deviceUserPaths = actionMap.deviceInfos.Select(d => d.userPath).ToList();
foreach (var action in actionMap.actions)
{
// User paths specified in the bindings
var bindingUserPaths = action.bindings.Where(b => b.userPaths != null).SelectMany(b => b.userPaths).Distinct().ToList();
// Combination of all user paths
var allUserPaths = bindingUserPaths.Union(deviceUserPaths).ToArray();
var actionId = Internal_CreateAction(
actionSetId,
SanitizeStringForOpenXRPath(action.name),
action.localizedName,
(uint)action.type,
new SerializedGuid(),
allUserPaths, (uint)allUserPaths.Length, action.isAdditive,
action.usages?.ToArray(), (uint)(action.usages?.Count ?? 0));
if (actionId == 0)
{
OpenXRRuntime.LogLastError();
return false;
}
foreach (var binding in action.bindings)
{
foreach (var userPath in binding.userPaths ?? deviceUserPaths)
{
var interactionProfile = action.isAdditive ? actionMap.desiredInteractionProfile : binding.interactionProfileName ?? actionMap.desiredInteractionProfile;
if (!interactionProfiles.TryGetValue(interactionProfile, out var bindings))
{
bindings = new List();
interactionProfiles[interactionProfile] = bindings;
}
bindings.Add(new SerializedBinding { actionId = actionId, path = userPath + binding.interactionPath });
}
}
}
}
return true;
}
private static void SetDpadBindingCustomValues()
{
var dpadFeature = OpenXRSettings.Instance.GetFeature();
if (dpadFeature != null && dpadFeature.enabled)
{
Internal_SetDpadBindingCustomValues(true, dpadFeature.forceThresholdLeft, dpadFeature.forceThresholdReleaseLeft, dpadFeature.centerRegionLeft, dpadFeature.wedgeAngleLeft, dpadFeature.isStickyLeft);
Internal_SetDpadBindingCustomValues(false, dpadFeature.forceThresholdRight, dpadFeature.forceThresholdReleaseRight, dpadFeature.centerRegionRight, dpadFeature.wedgeAngleRight, dpadFeature.isStickyRight);
}
}
///
/// Sanitize the given character for use as an OpenXR Path
///
/// Character to sanitize
/// The sanitized character or 0 if the character should be excluded
private static char SanitizeCharForOpenXRPath(char c)
{
if (char.IsLower(c) || char.IsDigit(c))
return c;
if (char.IsUpper(c))
return char.ToLower(c);
if (c == '-' || c == '.' || c == '_' || c == '/')
return c;
return (char)0;
}
///
/// OpenXR names can only contain certain characters. see https://www.khronos.org/registry/OpenXR/specs/1.0/html/xrspec.html#well-formed-path-strings
///
/// the string we'll convert to a valid OpenXR path
private static string SanitizeStringForOpenXRPath(string input)
{
if (string.IsNullOrEmpty(input))
return "";
// Find the first character that is not sanitized
var i = 0;
for (; i < input.Length && SanitizeCharForOpenXRPath(input[i]) == input[i]; ++i) ;
// Already clean
if (i == input.Length)
return input;
// Build the rest of the string by sanitizing each character but start with the
// portion of the string we already know is sanitized
var sb = new StringBuilder(input, 0, i, input.Length);
for (; i < input.Length; ++i)
{
var c = SanitizeCharForOpenXRPath(input[i]);
if (c != 0)
sb.Append(c);
}
return sb.ToString();
}
///
/// Gets the name of the control's action handle.
///
/// The input control
/// The name of the action handle.
#if ENABLE_INPUT_SYSTEM
private static string GetActionHandleName(InputControl control)
{
// Extract the name of the action from the control path.
// Example: /EyeTrackingOpenXR/pose/isTracked --> action is pose.
InputControl inputControl = control;
while (inputControl.parent != null && inputControl.parent.parent != null)
{
inputControl = inputControl.parent;
}
string controlName = SanitizeStringForOpenXRPath(inputControl.name);
if (kVirtualControlMap.TryGetValue(controlName, out var virtualControlName))
{
return virtualControlName;
}
return controlName;
}
#endif // ENABLE_INPUT_SYSTEM
///
/// Send a haptic impulse using an action reference
///
/// Action Reference to send haptic impulse through
/// Amplitude of the impulse [0-1]
/// Duration of the impulse [0-] in seconds
/// Optional device to limit haptic impulse to
public static void SendHapticImpulse(InputActionReference actionRef, float amplitude, float duration, InputSystem.InputDevice inputDevice = null) =>
SendHapticImpulse(actionRef, amplitude, 0.0f, duration, inputDevice);
///
/// Send a haptic impulse using an action reference
///
/// Action Reference to send haptic impulse through
/// Amplitude of the impulse [0-1]
/// Frequency of the impulse in hertz (Hz). (Typical frequency values are between 0 and 300Hz) (0 = default). Note that not all runtimes support frequency.
/// Duration of the impulse [0-] in seconds
/// Optional device to limit haptic impulse to
public static void SendHapticImpulse(InputActionReference actionRef, float amplitude, float frequency, float duration, InputSystem.InputDevice inputDevice = null) =>
SendHapticImpulse(actionRef.action, amplitude, frequency, duration, inputDevice);
///
/// Send a haptic impulse using the given action
///
/// Action to send haptic impulse through
/// Amplitude of the impulse [0-1]
/// Duration of the impulse [0-] in seconds
/// Optional device to limit haptic impulse to
public static void SendHapticImpulse(InputAction action, float amplitude, float duration, InputSystem.InputDevice inputDevice = null) =>
SendHapticImpulse(action, amplitude, 0.0f, duration, inputDevice);
///
/// Send a haptic impulse using the given action
///
/// Action to send haptic impulse through
/// Amplitude of the impulse [0-1]
/// Frequency of the impulse in hertz (Hz). (Typical frequency values are between 0 and 300Hz) (0 = default). Note that not all runtimes support frequency.
/// Duration of the impulse [0-] in seconds
/// Optional device to limit haptic impulse to
public static void SendHapticImpulse(InputAction action, float amplitude, float frequency, float duration, InputSystem.InputDevice inputDevice = null)
{
#if ENABLE_INPUT_SYSTEM
if (action == null)
return;
var actionHandle = GetActionHandle(action, inputDevice);
if (actionHandle == 0)
return;
amplitude = Mathf.Clamp(amplitude, 0, 1);
duration = Mathf.Max(duration, 0);
Internal_SendHapticImpulse(GetDeviceId(inputDevice), actionHandle, amplitude, frequency, duration);
#endif // ENABLE_INPUT_SYSTEM
}
///
/// Stop any haptics playing for the given action reference
///
/// Action reference to stop the haptics on.
/// Optional device filter for actions bound to multiple devices.
public static void StopHaptics(InputActionReference actionRef, InputSystem.InputDevice inputDevice = null)
{
#if ENABLE_INPUT_SYSTEM
if (actionRef == null)
return;
StopHaptics(actionRef.action, inputDevice);
#endif // ENABLE_INPUT_SYSTEM
}
///
/// Send a haptic impulse using the given device
///
/// Device to send haptic impulse
/// Amplitude of the impulse [0-1]
/// Frequency of the impulse in hertz (Hz). (Typical frequency values are between 0 and 300Hz) (0 = default). Note that not all runtimes support frequency.
/// Duration of the impulse [0-] in seconds
public static void SendHapticImpulse(XR.InputDevice device, float amplitude, float frequency, float duration)
{
if (!device.isValid)
return;
Internal_SendHapticImpulseNoISX(GetDeviceId(device), amplitude, frequency, duration);
}
///
/// Stop any haptics playing for the given device.
///
/// Device to stop haptics on.
public static void StopHapticImpulse(XR.InputDevice device)
{
if (!device.isValid)
return;
Internal_StopHapticsNoISX(GetDeviceId(device));
}
///
/// Stop any haptics playing for the given action
///
/// Input action to stop haptics for
/// Optional device filter for actions bound to multiple defices
public static void StopHaptics(InputAction inputAction, InputSystem.InputDevice inputDevice = null)
{
#if ENABLE_INPUT_SYSTEM
if (inputAction == null)
return;
var actionHandle = GetActionHandle(inputAction, inputDevice);
if (actionHandle == 0)
return;
Internal_StopHaptics(GetDeviceId(inputDevice), actionHandle);
#endif // ENABLE_INPUT_SYSTEM
}
///
/// Return the name of the input source bound to the given action
///
/// Input Action
/// Index of the input source in the case of multiple bindings.
/// Name of the input source if an input source was found or an empty string if none was found
/// Flags that indicate which parts of the input source name are requested.
/// Optional input device to limit search to
/// True if an input source was found
public static bool TryGetInputSourceName(
InputAction inputAction,
int index,
out string name,
InputSourceNameFlags flags = InputSourceNameFlags.All,
InputSystem.InputDevice inputDevice = null
)
{
name = "";
#if ENABLE_INPUT_SYSTEM
if (index < 0)
return false;
var actionHandle = GetActionHandle(inputAction, inputDevice);
if (actionHandle == 0)
return false;
return Internal_TryGetInputSourceName(GetDeviceId(inputDevice), actionHandle, (uint)index, (uint)flags, out name);
#else
return false;
#endif
}
///
/// Return the active state of the given action
///
/// Input Action
/// True if the given action has any bindings that are active
public static bool GetActionIsActive(InputAction inputAction)
{
#if ENABLE_INPUT_SYSTEM
if (inputAction != null &&
inputAction.controls.Count > 0 &&
inputAction.controls[0].device != null)
{
for (var index = 0; index < inputAction.controls.Count; ++index)
{
var deviceId = GetDeviceId(inputAction.controls[index].device);
if (deviceId == 0)
continue;
var controlName = GetActionHandleName(inputAction.controls[index]);
if (Internal_GetActionIsActive(deviceId, controlName))
return true;
}
}
#endif // ENABLE_INPUT_SYSTEM
return false;
}
///
/// Return the active state of the given action
///
/// InputDevice to get active state for
/// InputFeatureUsage to get active state for
/// True if the given action has any bindings that are active
public static bool GetActionIsActive(XR.InputDevice device, InputFeatureUsage usage)
=> GetActionIsActive(device, usage.name);
///
/// Return the active state of the given action
///
/// InputDevice to get active state for
/// InputFeatureUsage name to get active state for
/// True if the given action has any bindings that are active
public static bool GetActionIsActive(XR.InputDevice device, string usageName)
{
var deviceId = GetDeviceId(device);
if (deviceId == 0)
return false;
return Internal_GetActionIsActiveNoISX(deviceId, usageName);
}
///
/// Set InputAction to be used for controller late latching (Vulkan Only Feature). Only support one inputAction for each left and right controller.
/// See Controller Samples MarkLateLatchNode.cs for example code and usages.
///
/// Source InputAction - Pose Type
/// True if the given action is a valid pose action can be late latched
public static bool TrySetControllerLateLatchAction(InputAction inputAction)
{
#if ENABLE_INPUT_SYSTEM
//only allow one binding per action for LateLatching
if (inputAction == null || inputAction.controls.Count != 1)
return false;
if (inputAction.controls[0].device == null)
return false;
var deviceId = GetDeviceId(inputAction.controls[0].device);
if (deviceId == 0)
return false;
var actionHandle = GetActionHandle(inputAction);
if (actionHandle == 0)
return false;
return Internal_TrySetControllerLateLatchAction(deviceId, actionHandle);
#else
return false;
#endif
}
///
/// Set InputAction to be used for controller late latching (Vulkan Only
/// Feature). Only support one input action for each left and right
/// controller. See Controller XRInput Samples MarkLateLatchNodeXRInput.cs
/// for example code and usages.
///
/// Source device
/// Source usage - attached to a Pose Type
/// True if the given action is a valid pose action can be late latched
public static bool TrySetControllerLateLatchAction(XR.InputDevice device, InputFeatureUsage usage)
=> TrySetControllerLateLatchAction(device, usage.name);
///
/// Set InputAction to be used for controller late latching (Vulkan Only
/// Feature). Only support one input action for each left and right
/// controller. See Controller XRInput Samples MarkLateLatchNodeXRInput.cs
/// for example code and usages.
///
/// Source device
/// Source usage name - attached to a Pose Type
/// True if the given action is a valid pose action can be late latched
public static bool TrySetControllerLateLatchAction(XR.InputDevice device, string usageName)
{
var deviceId = GetDeviceId(device);
if (deviceId == 0)
return false;
var actionHandle = GetActionHandle(device, usageName);
if (actionHandle == 0)
return false;
return Internal_TrySetControllerLateLatchAction(deviceId, actionHandle);
}
[StructLayout(LayoutKind.Explicit, Size = k_Size)]
private struct GetInternalDeviceIdCommand : IInputDeviceCommandInfo
{
private static FourCC Type => new FourCC('X', 'R', 'D', 'I');
private const int k_BaseCommandSizeSize = 8;
private const int k_Size = k_BaseCommandSizeSize + sizeof(uint);
[FieldOffset(0)] private InputDeviceCommand baseCommand;
[FieldOffset(8)] public readonly uint deviceId;
public FourCC typeStatic => Type;
public static GetInternalDeviceIdCommand Create() =>
new GetInternalDeviceIdCommand { baseCommand = new InputDeviceCommand(Type, k_Size) };
}
///
/// Returns the OpenXR action handle for the given input device and usage.
///
/// Device to find action handle for.
/// Usage to find action handle for.
/// OpenXR handle that is associated with the given device and usage, or 0 if not found
public static ulong GetActionHandle(XR.InputDevice device, InputFeatureUsage usage)
=> GetActionHandle(device, usage.name);
///
/// Returns the OpenXR action handle for the given input device and usage name.
///
/// Device to find action handle for.
/// Usage name to find action handle for.
/// OpenXR handle that is associated with the given device and usage, or 0 if not found
public static ulong GetActionHandle(XR.InputDevice device, string usageName)
{
var deviceId = GetDeviceId(device);
if (deviceId == 0)
return 0;
return Internal_GetActionIdNoISX(deviceId, usageName);
}
///
/// Returns the OpenXR action handle for the given input action
///
/// Source InputAction
/// Optional InputDevice to filter by
/// OpenXR handle that is associated with the given InputAction or 0 if not found
public static ulong GetActionHandle(InputAction inputAction, InputSystem.InputDevice inputDevice = null)
{
#if ENABLE_INPUT_SYSTEM
if (inputAction == null || inputAction.controls.Count == 0)
return 0;
foreach (var control in inputAction.controls)
{
if (inputDevice != null && control.device != inputDevice || control.device == null)
continue;
var deviceId = GetDeviceId(control.device);
if (deviceId == 0)
continue;
var controlName = GetActionHandleName(control);
// Populate the action handles list and make sure we dont overflow
var xrAction = Internal_GetActionId(deviceId, controlName);
if (xrAction != 0)
return xrAction;
}
#endif // ENABLE_INPUT_SYSTEM
return 0;
}
///
/// Return the OpenXR device identifier for the given input device
///
/// Input device to return identifier for
/// Identifier the OpenXR plugin uses for this device. If a device could not be found an invalid device id of 0 will be returned
#if ENABLE_INPUT_SYSTEM
private static uint GetDeviceId(InputSystem.InputDevice inputDevice)
{
if (inputDevice == null)
return 0;
var command = GetInternalDeviceIdCommand.Create();
var result = inputDevice.ExecuteCommand(ref command);
return result == 0 ? 0 : command.deviceId;
}
#endif // ENABLE_INPUT_SYSTEM
static uint GetDeviceId(XR.InputDevice inputDevice)
=> Internal_GetDeviceId(inputDevice.characteristics, inputDevice.name);
///
/// Convert a user path into a device name
///
/// User Path
/// Device name that represents the given user path
private static string UserPathToDeviceName(string userPath)
{
// Build the device name from the user path
var parts = userPath.Split('/', '_');
var nameBuilder = new StringBuilder("OXR");
foreach (var part in parts)
{
if (part.Length == 0)
continue;
var sanitizedPart = SanitizeStringForOpenXRPath(part);
nameBuilder.Append(char.ToUpper(sanitizedPart[0]));
nameBuilder.Append(sanitizedPart.Substring(1));
}
return nameBuilder.ToString();
}
/////////////////////////////////////////////////////////////////////////////////////////////
private const string Library = "UnityOpenXR";
[DllImport(Library, EntryPoint = "OpenXRInputProvider_SetDpadBindingCustomValues", CallingConvention = CallingConvention.Cdecl)]
private static extern void Internal_SetDpadBindingCustomValues([MarshalAs(UnmanagedType.I1)] bool isLeft, float forceThreshold, float forceThresholdReleased, float centerRegion, float wedgeAngle, [MarshalAs(UnmanagedType.I1)] bool isSticky);
[DllImport(Library, EntryPoint = "OpenXRInputProvider_SendHapticImpulse", CallingConvention = CallingConvention.Cdecl)]
private static extern void Internal_SendHapticImpulse(uint deviceId, ulong actionId, float amplitude, float frequency, float duration);
[DllImport(Library, EntryPoint = "OpenXRInputProvider_SendHapticImpulseNoISX", CallingConvention = CallingConvention.Cdecl)]
static extern void Internal_SendHapticImpulseNoISX(uint deviceId, float amplitude, float frequency, float duration);
[DllImport(Library, EntryPoint = "OpenXRInputProvider_StopHaptics", CallingConvention = CallingConvention.Cdecl)]
private static extern void Internal_StopHaptics(uint deviceId, ulong actionId);
[DllImport(Library, EntryPoint = "OpenXRInputProvider_StopHapticsNoISX", CallingConvention = CallingConvention.Cdecl)]
static extern void Internal_StopHapticsNoISX(uint deviceId);
[DllImport(Library, EntryPoint = "OpenXRInputProvider_GetActionIdByControl")]
private static extern ulong Internal_GetActionId(uint deviceId, string name);
[DllImport(Library, EntryPoint = "OpenXRInputProvider_GetActionIdByUsageName", CharSet = CharSet.Ansi)]
static extern ulong Internal_GetActionIdNoISX(uint deviceId, string usageName);
[DllImport(Library, EntryPoint = "OpenXRInputProvider_TryGetInputSourceName", CharSet = CharSet.Ansi)]
[return: MarshalAs(UnmanagedType.U1)]
private static extern bool Internal_TryGetInputSourceNamePtr(uint deviceId, ulong actionId, uint index, uint flags, out IntPtr outName);
internal static bool Internal_TryGetInputSourceName(uint deviceId, ulong actionId, uint index, uint flags, out string outName)
{
if (!Internal_TryGetInputSourceNamePtr(deviceId, actionId, index, flags, out var outNamePtr))
{
outName = "";
return false;
}
outName = Marshal.PtrToStringAnsi(outNamePtr);
return true;
}
[DllImport(Library, EntryPoint = "OpenXRInputProvider_TrySetControllerLateLatchAction")]
[return: MarshalAs(UnmanagedType.U1)]
private static extern bool Internal_TrySetControllerLateLatchAction(uint deviceId, ulong actionId);
[DllImport(Library, EntryPoint = "OpenXRInputProvider_GetActionIsActive")]
[return: MarshalAs(UnmanagedType.U1)]
private static extern bool Internal_GetActionIsActive(uint deviceId, string name);
[DllImport(Library, EntryPoint = "OpenXRInputProvider_GetActionIsActiveNoISX", CharSet = CharSet.Ansi)]
[return: MarshalAs(UnmanagedType.U1)]
static extern bool Internal_GetActionIsActiveNoISX(uint deviceId, string name);
[DllImport(Library, EntryPoint = "OpenXRInputProvider_RegisterDeviceDefinition", CharSet = CharSet.Ansi)]
private static extern ulong Internal_RegisterDeviceDefinition(string userPath, string interactionProfile, [MarshalAs(UnmanagedType.I1)] bool isAdditive, uint characteristics, string name, string manufacturer, string serialNumber);
[DllImport(Library, EntryPoint = "OpenXRInputProvider_CreateActionSet", CharSet = CharSet.Ansi)]
private static extern ulong Internal_CreateActionSet(string name, string localizedName, SerializedGuid guid);
[DllImport(Library, EntryPoint = "OpenXRInputProvider_CreateAction", CharSet = CharSet.Ansi)]
private static extern ulong Internal_CreateAction(ulong actionSetId, string name, string localizedName, uint actionType, SerializedGuid guid, string[] userPaths, uint userPathCount, [MarshalAs(UnmanagedType.I1)] bool isAdditive, string[] usages, uint usageCount);
[DllImport(Library, EntryPoint = "OpenXRInputProvider_SuggestBindings", CharSet = CharSet.Ansi)]
[return: MarshalAs(UnmanagedType.U1)]
internal static extern bool Internal_SuggestBindings(string interactionProfile, SerializedBinding[] serializedBindings, uint serializedBindingCount);
[DllImport(Library, EntryPoint = "OpenXRInputProvider_AttachActionSets", CharSet = CharSet.Ansi)]
[return: MarshalAs(UnmanagedType.U1)]
internal static extern bool Internal_AttachActionSets();
[DllImport(Library, EntryPoint = "OpenXRInputProvider_GetDeviceId", CharSet = CharSet.Ansi)]
static extern uint Internal_GetDeviceId(InputDeviceCharacteristics characteristics, string name);
}
}