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