test
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e5769f689f15dce4a9f92aff27659c54
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,228 @@
|
||||
#if UNITY_EDITOR || UNITY_ANDROID || PACKAGE_DOCS_GENERATION
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using UnityEngine.InputSystem.Android.LowLevel;
|
||||
using UnityEngine.InputSystem.Utilities;
|
||||
|
||||
namespace UnityEngine.InputSystem.Android.LowLevel
|
||||
{
|
||||
/// <summary>
|
||||
/// Enum used to identity the axis type in the Android motion input event. See <see cref="AndroidGameControllerState.axis"/>.
|
||||
/// See https://developer.android.com/reference/android/view/MotionEvent#constants_1 for more details.
|
||||
/// </summary>
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1027:MarkEnumsWithFlags", Justification = "False positive")]
|
||||
public enum AndroidAxis
|
||||
{
|
||||
/// <summary>
|
||||
/// X axis of a motion event.
|
||||
/// </summary>
|
||||
X = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Y axis of a motion event.
|
||||
/// </summary>
|
||||
Y = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Pressure axis of a motion event.
|
||||
/// </summary>
|
||||
Pressure = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Size axis of a motion event.
|
||||
/// </summary>
|
||||
Size = 3,
|
||||
|
||||
/// <summary>
|
||||
/// TouchMajor axis of a motion event.
|
||||
/// </summary>
|
||||
TouchMajor = 4,
|
||||
|
||||
/// <summary>
|
||||
/// TouchMinor axis of a motion event.
|
||||
/// </summary>
|
||||
TouchMinor = 5,
|
||||
|
||||
/// <summary>
|
||||
/// ToolMajor axis of a motion event.
|
||||
/// </summary>
|
||||
ToolMajor = 6,
|
||||
|
||||
/// <summary>
|
||||
/// ToolMinor axis of a motion event.
|
||||
/// </summary>
|
||||
ToolMinor = 7,
|
||||
|
||||
/// <summary>
|
||||
/// Orientation axis of a motion event.
|
||||
/// </summary>
|
||||
Orientation = 8,
|
||||
|
||||
/// <summary>
|
||||
/// Vertical Scroll of a motion event.
|
||||
/// </summary>
|
||||
Vscroll = 9,
|
||||
|
||||
/// <summary>
|
||||
/// Horizontal Scroll axis of a motion event.
|
||||
/// </summary>
|
||||
Hscroll = 10,
|
||||
|
||||
/// <summary>
|
||||
/// Z axis of a motion event.
|
||||
/// </summary>
|
||||
Z = 11,
|
||||
|
||||
/// <summary>
|
||||
/// X Rotation axis of a motion event.
|
||||
/// </summary>
|
||||
Rx = 12,
|
||||
|
||||
/// <summary>
|
||||
/// Y Rotation axis of a motion event.
|
||||
/// </summary>
|
||||
Ry = 13,
|
||||
|
||||
/// <summary>
|
||||
/// Z Rotation axis of a motion event.
|
||||
/// </summary>
|
||||
Rz = 14,
|
||||
|
||||
/// <summary>
|
||||
/// Hat X axis of a motion event.
|
||||
/// </summary>
|
||||
HatX = 15,
|
||||
|
||||
/// <summary>
|
||||
/// Hat Y axis of a motion event.
|
||||
/// </summary>
|
||||
HatY = 16,
|
||||
|
||||
/// <summary>
|
||||
/// Left Trigger axis of a motion event.
|
||||
/// </summary>
|
||||
Ltrigger = 17,
|
||||
|
||||
/// <summary>
|
||||
/// Right Trigger axis of a motion event.
|
||||
/// </summary>
|
||||
Rtrigger = 18,
|
||||
|
||||
/// <summary>
|
||||
/// Throttle axis of a motion event.
|
||||
/// </summary>
|
||||
Throttle = 19,
|
||||
|
||||
/// <summary>
|
||||
/// Rudder axis of a motion event.
|
||||
/// </summary>
|
||||
Rudder = 20,
|
||||
|
||||
/// <summary>
|
||||
/// Wheel axis of a motion event.
|
||||
/// </summary>
|
||||
Wheel = 21,
|
||||
|
||||
/// <summary>
|
||||
/// Gas axis of a motion event.
|
||||
/// </summary>
|
||||
Gas = 22,
|
||||
|
||||
/// <summary>
|
||||
/// Break axis of a motion event.
|
||||
/// </summary>
|
||||
Brake = 23,
|
||||
|
||||
/// <summary>
|
||||
/// Distance axis of a motion event.
|
||||
/// </summary>
|
||||
Distance = 24,
|
||||
|
||||
/// <summary>
|
||||
/// Tilt axis of a motion event.
|
||||
/// </summary>
|
||||
Tilt = 25,
|
||||
|
||||
/// <summary>
|
||||
/// Generic 1 axis of a motion event.
|
||||
/// </summary>
|
||||
Generic1 = 32,
|
||||
|
||||
/// <summary>
|
||||
/// Generic 2 axis of a motion event.
|
||||
/// </summary>
|
||||
Generic2 = 33,
|
||||
|
||||
/// <summary>
|
||||
/// Generic 3 axis of a motion event.
|
||||
/// </summary>
|
||||
Generic3 = 34,
|
||||
|
||||
/// <summary>
|
||||
/// Generic 4 axis of a motion event.
|
||||
/// </summary>
|
||||
Generic4 = 35,
|
||||
|
||||
/// <summary>
|
||||
/// Generic 5 axis of a motion event.
|
||||
/// </summary>
|
||||
Generic5 = 36,
|
||||
|
||||
/// <summary>
|
||||
/// Generic 6 axis of a motion event.
|
||||
/// </summary>
|
||||
Generic6 = 37,
|
||||
|
||||
/// <summary>
|
||||
/// Generic 7 axis of a motion event.
|
||||
/// </summary>
|
||||
Generic7 = 38,
|
||||
|
||||
/// <summary>
|
||||
/// Generic 8 axis of a motion event.
|
||||
/// </summary>
|
||||
Generic8 = 39,
|
||||
|
||||
/// <summary>
|
||||
/// Generic 9 axis of a motion event.
|
||||
/// </summary>
|
||||
Generic9 = 40,
|
||||
|
||||
/// <summary>
|
||||
/// Generic 10 axis of a motion event.
|
||||
/// </summary>
|
||||
Generic10 = 41,
|
||||
|
||||
/// <summary>
|
||||
/// Generic 11 axis of a motion event.
|
||||
/// </summary>
|
||||
Generic11 = 42,
|
||||
|
||||
/// <summary>
|
||||
/// Generic 12 axis of a motion event.
|
||||
/// </summary>
|
||||
Generic12 = 43,
|
||||
|
||||
/// <summary>
|
||||
/// Generic 13 axis of a motion event.
|
||||
/// </summary>
|
||||
Generic13 = 44,
|
||||
|
||||
/// <summary>
|
||||
/// Generic 14 axis of a motion event.
|
||||
/// </summary>
|
||||
Generic14 = 45,
|
||||
|
||||
/// <summary>
|
||||
/// Generic 15 axis of a motion event.
|
||||
/// </summary>
|
||||
Generic15 = 46,
|
||||
|
||||
/// <summary>
|
||||
/// Generic 16 axis of a motion event.
|
||||
/// </summary>
|
||||
Generic16 = 47,
|
||||
}
|
||||
}
|
||||
#endif // UNITY_EDITOR || UNITY_ANDROID
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 15fa4fb6e47739e43b36a70fce024b6f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,234 @@
|
||||
#if UNITY_EDITOR || UNITY_ANDROID || PACKAGE_DOCS_GENERATION
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using UnityEngine.InputSystem.Layouts;
|
||||
using UnityEngine.InputSystem.LowLevel;
|
||||
using UnityEngine.InputSystem.Android.LowLevel;
|
||||
using UnityEngine.InputSystem.DualShock;
|
||||
using UnityEngine.InputSystem.Utilities;
|
||||
|
||||
namespace UnityEngine.InputSystem.Android.LowLevel
|
||||
{
|
||||
/// <summary>
|
||||
/// Default state layout for Android game controller.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public unsafe struct AndroidGameControllerState : IInputStateTypeInfo
|
||||
{
|
||||
public const int MaxAxes = 48;
|
||||
public const int MaxButtons = 220;
|
||||
|
||||
public class Variants
|
||||
{
|
||||
public const string Gamepad = "Gamepad";
|
||||
public const string Joystick = "Joystick";
|
||||
public const string DPadAxes = "DpadAxes";
|
||||
public const string DPadButtons = "DpadButtons";
|
||||
}
|
||||
|
||||
internal const uint kAxisOffset = sizeof(uint) * (uint)((MaxButtons + 31) / 32);
|
||||
|
||||
public static FourCC kFormat = new FourCC('A', 'G', 'C', ' ');
|
||||
|
||||
[InputControl(name = "dpad", layout = "Dpad", bit = (uint)AndroidKeyCode.DpadUp, sizeInBits = 4, variants = Variants.DPadButtons)]
|
||||
[InputControl(name = "dpad/up", bit = (uint)AndroidKeyCode.DpadUp, variants = Variants.DPadButtons)]
|
||||
[InputControl(name = "dpad/down", bit = (uint)AndroidKeyCode.DpadDown, variants = Variants.DPadButtons)]
|
||||
[InputControl(name = "dpad/left", bit = (uint)AndroidKeyCode.DpadLeft, variants = Variants.DPadButtons)]
|
||||
[InputControl(name = "dpad/right", bit = (uint)AndroidKeyCode.DpadRight, variants = Variants.DPadButtons)]
|
||||
[InputControl(name = "buttonSouth", bit = (uint)AndroidKeyCode.ButtonA, variants = Variants.Gamepad)]
|
||||
[InputControl(name = "buttonWest", bit = (uint)AndroidKeyCode.ButtonX, variants = Variants.Gamepad)]
|
||||
[InputControl(name = "buttonNorth", bit = (uint)AndroidKeyCode.ButtonY, variants = Variants.Gamepad)]
|
||||
[InputControl(name = "buttonEast", bit = (uint)AndroidKeyCode.ButtonB, variants = Variants.Gamepad)]
|
||||
[InputControl(name = "leftStickPress", bit = (uint)AndroidKeyCode.ButtonThumbl, variants = Variants.Gamepad)]
|
||||
[InputControl(name = "rightStickPress", bit = (uint)AndroidKeyCode.ButtonThumbr, variants = Variants.Gamepad)]
|
||||
[InputControl(name = "leftShoulder", bit = (uint)AndroidKeyCode.ButtonL1, variants = Variants.Gamepad)]
|
||||
[InputControl(name = "rightShoulder", bit = (uint)AndroidKeyCode.ButtonR1, variants = Variants.Gamepad)]
|
||||
[InputControl(name = "start", bit = (uint)AndroidKeyCode.ButtonStart, variants = Variants.Gamepad)]
|
||||
[InputControl(name = "select", bit = (uint)AndroidKeyCode.ButtonSelect, variants = Variants.Gamepad)]
|
||||
public fixed uint buttons[(MaxButtons + 31) / 32];
|
||||
|
||||
[InputControl(name = "dpad", layout = "Dpad", offset = (uint)AndroidAxis.HatX * sizeof(float) + kAxisOffset, format = "VEC2", sizeInBits = 64, variants = Variants.DPadAxes)]
|
||||
[InputControl(name = "dpad/right", offset = 0, bit = 0, sizeInBits = 32, format = "FLT", parameters = "clamp=3,clampConstant=0,clampMin=0,clampMax=1", variants = Variants.DPadAxes)]
|
||||
[InputControl(name = "dpad/left", offset = 0, bit = 0, sizeInBits = 32, format = "FLT", parameters = "clamp=3,clampConstant=0,clampMin=-1,clampMax=0,invert", variants = Variants.DPadAxes)]
|
||||
[InputControl(name = "dpad/down", offset = ((uint)AndroidAxis.HatY - (uint)AndroidAxis.HatX) * sizeof(float), bit = 0, sizeInBits = 32, format = "FLT", parameters = "clamp=3,clampConstant=0,clampMin=0,clampMax=1", variants = Variants.DPadAxes)]
|
||||
[InputControl(name = "dpad/up", offset = ((uint)AndroidAxis.HatY - (uint)AndroidAxis.HatX) * sizeof(float), bit = 0, sizeInBits = 32, format = "FLT", parameters = "clamp=3,clampConstant=0,clampMin=-1,clampMax=0,invert", variants = Variants.DPadAxes)]
|
||||
[InputControl(name = "leftTrigger", offset = (uint)AndroidAxis.Brake * sizeof(float) + kAxisOffset, parameters = "clamp=1,clampMin=0,clampMax=1.0", variants = Variants.Gamepad)]
|
||||
[InputControl(name = "rightTrigger", offset = (uint)AndroidAxis.Gas * sizeof(float) + kAxisOffset, parameters = "clamp=1,clampMin=0,clampMax=1.0", variants = Variants.Gamepad)]
|
||||
[InputControl(name = "leftStick", variants = Variants.Gamepad)]
|
||||
[InputControl(name = "leftStick/y", variants = Variants.Gamepad, parameters = "invert")]
|
||||
[InputControl(name = "leftStick/up", variants = Variants.Gamepad, parameters = "invert,clamp=1,clampMin=-1.0,clampMax=0.0")]
|
||||
[InputControl(name = "leftStick/down", variants = Variants.Gamepad, parameters = "invert=false,clamp=1,clampMin=0,clampMax=1.0")]
|
||||
////FIXME: state for this control is not contiguous
|
||||
[InputControl(name = "rightStick", offset = (uint)AndroidAxis.Z * sizeof(float) + kAxisOffset, sizeInBits = ((uint)AndroidAxis.Rz - (uint)AndroidAxis.Z + 1) * sizeof(float) * 8, variants = Variants.Gamepad)]
|
||||
[InputControl(name = "rightStick/x", variants = Variants.Gamepad)]
|
||||
[InputControl(name = "rightStick/y", offset = ((uint)AndroidAxis.Rz - (uint)AndroidAxis.Z) * sizeof(float), variants = Variants.Gamepad, parameters = "invert")]
|
||||
[InputControl(name = "rightStick/up", offset = ((uint)AndroidAxis.Rz - (uint)AndroidAxis.Z) * sizeof(float), variants = Variants.Gamepad, parameters = "invert,clamp=1,clampMin=-1.0,clampMax=0.0")]
|
||||
[InputControl(name = "rightStick/down", offset = ((uint)AndroidAxis.Rz - (uint)AndroidAxis.Z) * sizeof(float), variants = Variants.Gamepad, parameters = "invert=false,clamp=1,clampMin=0,clampMax=1.0")]
|
||||
public fixed float axis[MaxAxes];
|
||||
|
||||
public FourCC format
|
||||
{
|
||||
get { return kFormat; }
|
||||
}
|
||||
|
||||
public AndroidGameControllerState WithButton(AndroidKeyCode code, bool value = true)
|
||||
{
|
||||
fixed(uint* buttonsPtr = buttons)
|
||||
{
|
||||
if (value)
|
||||
buttonsPtr[(int)code / 32] |= 1U << ((int)code % 32);
|
||||
else
|
||||
buttonsPtr[(int)code / 32] &= ~(1U << ((int)code % 32));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public AndroidGameControllerState WithAxis(AndroidAxis axis, float value)
|
||||
{
|
||||
fixed(float* axisPtr = this.axis)
|
||||
{
|
||||
axisPtr[(int)axis] = value;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
// See https://developer.android.com/reference/android/view/InputDevice.html for input source values
|
||||
internal enum AndroidInputSource
|
||||
{
|
||||
Keyboard = 257,
|
||||
Dpad = 513,
|
||||
Gamepad = 1025,
|
||||
Touchscreen = 4098,
|
||||
Mouse = 8194,
|
||||
Stylus = 16386,
|
||||
Trackball = 65540,
|
||||
Touchpad = 1048584,
|
||||
Joystick = 16777232
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
internal struct AndroidDeviceCapabilities
|
||||
{
|
||||
public string deviceDescriptor;
|
||||
public int productId;
|
||||
public int vendorId;
|
||||
public bool isVirtual;
|
||||
public AndroidAxis[] motionAxes;
|
||||
public AndroidInputSource inputSources;
|
||||
|
||||
public string ToJson()
|
||||
{
|
||||
return JsonUtility.ToJson(this);
|
||||
}
|
||||
|
||||
public static AndroidDeviceCapabilities FromJson(string json)
|
||||
{
|
||||
if (json == null)
|
||||
throw new ArgumentNullException(nameof(json));
|
||||
return JsonUtility.FromJson<AndroidDeviceCapabilities>(json);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return
|
||||
$"deviceDescriptor = {deviceDescriptor}, productId = {productId}, vendorId = {vendorId}, isVirtual = {isVirtual}, motionAxes = {(motionAxes == null ? "<null>" : String.Join(",", motionAxes.Select(i => i.ToString()).ToArray()))}, inputSources = {inputSources}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace UnityEngine.InputSystem.Android
|
||||
{
|
||||
////FIXME: This is messy! Clean up!
|
||||
/// <summary>
|
||||
/// Gamepad on Android. Comprises all types of gamepads supported on Android.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Most of the gamepads:
|
||||
/// - ELAN PLAYSTATION(R)3 Controller
|
||||
/// - My-Power CO.,LTD. PS(R) Controller Adaptor
|
||||
/// (Following tested and work with: Nvidia Shield TV (OS 9); Galaxy Note 20 Ultra (OS 11); Galaxy S9+ (OS 10.0))
|
||||
/// - Sony Interactive Entertainment Wireless (PS4 DualShock) (Also tested and DOES NOT WORK with Galaxy S9 (OS 8.0); Galaxy S8 (OS 7.0); Xiaomi Mi Note2 (OS 8.0))
|
||||
/// - Xbox Wireless Controller (Xbox One) (Also tested and works on Samsung Galaxy S8 (OS 7.0); Xiaomi Mi Note2 (OS 8.0))
|
||||
/// - NVIDIA Controller v01.03/v01.04
|
||||
/// - (Add more)
|
||||
/// map buttons in the following way:
|
||||
/// Left Stick -> AXIS_X(0) / AXIS_Y(1)
|
||||
/// Right Stick -> AXIS_Z (11) / AXIS_RZ(14)
|
||||
/// Right Thumb -> KEYCODE_BUTTON_THUMBR(107)
|
||||
/// Left Thumb -> KEYCODE_BUTTON_THUMBL(106)
|
||||
/// L1 (Left shoulder) -> KEYCODE_BUTTON_L1(102)
|
||||
/// R1 (Right shoulder) -> KEYCODE_BUTTON_R1(103)
|
||||
/// L2 (Left trigger) -> AXIS_BRAKE(23)
|
||||
/// R2 (Right trigger) -> AXIS_GAS(22)
|
||||
/// X -> KEYCODE_BUTTON_X(99)
|
||||
/// Y -> KEYCODE_BUTTON_Y(100)
|
||||
/// B -> KEYCODE_BUTTON_B(97)
|
||||
/// A -> KEYCODE_BUTTON_A(96)
|
||||
/// DPAD -> AXIS_HAT_X(15),AXIS_HAT_Y(16) or KEYCODE_DPAD_LEFT(21), KEYCODE_DPAD_RIGHT(22), KEYCODE_DPAD_UP(19), KEYCODE_DPAD_DOWN(20),
|
||||
/// Note: On Nvidia Shield Console, L2/R2 additionally invoke key events for AXIS_LTRIGGER, AXIS_RTRIGGER (in addition to AXIS_BRAKE, AXIS_GAS)
|
||||
/// If you connect gamepad to a phone for L2/R2 only AXIS_BRAKE/AXIS_GAS come. AXIS_LTRIGGER, AXIS_RTRIGGER are not invoked.
|
||||
/// That's why we map triggers only to AXIS_BRAKE/AXIS_GAS
|
||||
/// Nvidia Shield also reports KEYCODE_BACK instead of KEYCODE_BUTTON_SELECT, so Options(XboxOne Controller)/View(DualShock)/Select buttons do not work
|
||||
/// PS4 controller is officially supported from Android 10 and higher (https://playstation.com/en-us/support/hardware/ps4-pair-dualshock-4-wireless-with-sony-xperia-and-android)
|
||||
/// However, some devices with older OS have fixed PS4 controller support on their drivers this leads to following situation:
|
||||
/// Some gamepads on Android devices (with same Android number version) might have different mappings
|
||||
/// For ex., Dualshock, on NVidia Shield Console (OS 8.0) all buttons correctly map according to rules in AndroidGameControllerState
|
||||
/// when clicking left shoulder it will go to AndroidKeyCode.ButtonL1, rightShoulder -> AndroidKeyCode.ButtonR1, etc
|
||||
/// But, on Samsung Galaxy S9 (OS 8.0), the mapping is different (Same for Xiaomi Mi Note2 (OS 8.0), Samsung Galaxy S8 (OS 7.0))
|
||||
/// when clicking left shoulder it will go to AndroidKeyCode.ButtonY, rightShoulder -> AndroidKeyCode.ButtonZ, etc
|
||||
/// So even though Android version is 8.0 in both cases, Dualshock will only correctly work on NVidia Shield Console
|
||||
/// It's obvious that this depends on the driver and not Android OS, thus we can only assume Samsung in this case doesn't properly support Dualshock in their drivers
|
||||
/// While we can do custom mapping for Samsung, we can never now when will they try to update the driver for Dualshock or some other gamepad
|
||||
/// </remarks>
|
||||
[InputControlLayout(stateType = typeof(AndroidGameControllerState), variants = AndroidGameControllerState.Variants.Gamepad)]
|
||||
public class AndroidGamepad : Gamepad
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generic controller with Dpad axes
|
||||
/// </summary>
|
||||
[InputControlLayout(stateType = typeof(AndroidGameControllerState), hideInUI = true,
|
||||
variants = AndroidGameControllerState.Variants.Gamepad + InputControlLayout.VariantSeparator + AndroidGameControllerState.Variants.DPadAxes)]
|
||||
public class AndroidGamepadWithDpadAxes : AndroidGamepad
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generic controller with Dpad buttons
|
||||
/// </summary>
|
||||
[InputControlLayout(stateType = typeof(AndroidGameControllerState), hideInUI = true,
|
||||
variants = AndroidGameControllerState.Variants.Gamepad + InputControlLayout.VariantSeparator + AndroidGameControllerState.Variants.DPadButtons)]
|
||||
public class AndroidGamepadWithDpadButtons : AndroidGamepad
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Joystick on Android.
|
||||
/// </summary>
|
||||
[InputControlLayout(stateType = typeof(AndroidGameControllerState), variants = AndroidGameControllerState.Variants.Joystick)]
|
||||
public class AndroidJoystick : Joystick
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A PlayStation DualShock 4 controller connected to an Android device.
|
||||
/// </summary>
|
||||
[InputControlLayout(stateType = typeof(AndroidGameControllerState), displayName = "Android DualShock 4 Gamepad",
|
||||
variants = AndroidGameControllerState.Variants.Gamepad + InputControlLayout.VariantSeparator + AndroidGameControllerState.Variants.DPadAxes)]
|
||||
public class DualShock4GamepadAndroid : DualShockGamepad
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A PlayStation DualShock 4 controller connected to an Android device.
|
||||
/// </summary>
|
||||
[InputControlLayout(stateType = typeof(AndroidGameControllerState), displayName = "Android Xbox One Controller",
|
||||
variants = AndroidGameControllerState.Variants.Gamepad + InputControlLayout.VariantSeparator + AndroidGameControllerState.Variants.DPadAxes)]
|
||||
public class XboxOneGamepadAndroid : XInput.XInputController
|
||||
{
|
||||
}
|
||||
}
|
||||
#endif // UNITY_EDITOR || UNITY_ANDROID
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a7c6472eb810ba445be34c69325e4fea
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ede17f8b7e9dfcf42b560545a8102eeb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,293 @@
|
||||
#if UNITY_EDITOR || UNITY_ANDROID || PACKAGE_DOCS_GENERATION
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.InteropServices;
|
||||
using UnityEngine.InputSystem.Android.LowLevel;
|
||||
using UnityEngine.InputSystem.LowLevel;
|
||||
using UnityEngine.InputSystem.Utilities;
|
||||
using UnityEngine.InputSystem.Layouts;
|
||||
using UnityEngine.InputSystem.Processors;
|
||||
|
||||
////TODO: make all the sensor class types internal
|
||||
|
||||
namespace UnityEngine.InputSystem.Android.LowLevel
|
||||
{
|
||||
internal enum AndroidSensorType
|
||||
{
|
||||
None = 0,
|
||||
Accelerometer = 1,
|
||||
MagneticField = 2,
|
||||
Orientation = 3, // Was deprecated in API 8 https://developer.android.com/reference/android/hardware/Sensor#TYPE_ORIENTATION
|
||||
Gyroscope = 4,
|
||||
Light = 5,
|
||||
Pressure = 6,
|
||||
Temperature = 7, // Was deprecated in API 14 https://developer.android.com/reference/android/hardware/Sensor#TYPE_TEMPERATURE
|
||||
Proximity = 8,
|
||||
Gravity = 9,
|
||||
LinearAcceleration = 10,
|
||||
RotationVector = 11,
|
||||
RelativeHumidity = 12,
|
||||
AmbientTemperature = 13,
|
||||
MagneticFieldUncalibrated = 14,
|
||||
GameRotationVector = 15,
|
||||
GyroscopeUncalibrated = 16,
|
||||
SignificantMotion = 17,
|
||||
StepDetector = 18,
|
||||
StepCounter = 19,
|
||||
GeomagneticRotationVector = 20,
|
||||
HeartRate = 21,
|
||||
Pose6DOF = 28,
|
||||
StationaryDetect = 29,
|
||||
MotionDetect = 30,
|
||||
HeartBeat = 31,
|
||||
LowLatencyOffBodyDetect = 34,
|
||||
AccelerometerUncalibrated = 35,
|
||||
HingeAngle = 36
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
internal struct AndroidSensorCapabilities
|
||||
{
|
||||
public AndroidSensorType sensorType;
|
||||
|
||||
public string ToJson()
|
||||
{
|
||||
return JsonUtility.ToJson(this);
|
||||
}
|
||||
|
||||
public static AndroidSensorCapabilities FromJson(string json)
|
||||
{
|
||||
if (json == null)
|
||||
throw new ArgumentNullException(nameof(json));
|
||||
return JsonUtility.FromJson<AndroidSensorCapabilities>(json);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"type = {sensorType.ToString()}";
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal unsafe struct AndroidSensorState : IInputStateTypeInfo
|
||||
{
|
||||
public static FourCC kFormat = new FourCC('A', 'S', 'S', ' ');
|
||||
|
||||
////FIXME: Sensors to check if values matches old system
|
||||
// Accelerometer - OK
|
||||
// MagneticField - no alternative in old system
|
||||
// Gyroscope - OK
|
||||
// Light - no alternative in old system
|
||||
// Pressure - no alternative in old system
|
||||
// Proximity - no alternative in old system
|
||||
// Gravity - OK
|
||||
// LinearAcceleration - need to check
|
||||
// RotationVector - OK
|
||||
// RelativeHumidity - no alternative in old system
|
||||
// AmbientTemperature - no alternative in old system
|
||||
// GameRotationVector - no alternative in old system
|
||||
// StepCounter - no alternative in old system
|
||||
// GeomagneticRotationVector - no alternative in old system
|
||||
// HeartRate - no alternative in old system
|
||||
|
||||
[InputControl(name = "acceleration", layout = "Vector3", processors = "AndroidCompensateDirection", variants = "Accelerometer")]
|
||||
[InputControl(name = "magneticField", layout = "Vector3", variants = "MagneticField")]
|
||||
// Note: Using CompensateDirection instead of AndroidCompensateDirection, because we don't need to normalize velocity
|
||||
[InputControl(name = "angularVelocity", layout = "Vector3", processors = "CompensateDirection", variants = "Gyroscope")]
|
||||
[InputControl(name = "lightLevel", layout = "Axis", variants = "Light")]
|
||||
[InputControl(name = "atmosphericPressure", layout = "Axis", variants = "Pressure")]
|
||||
[InputControl(name = "distance", layout = "Axis", variants = "Proximity")]
|
||||
[InputControl(name = "gravity", layout = "Vector3", processors = "AndroidCompensateDirection", variants = "Gravity")]
|
||||
[InputControl(name = "acceleration", layout = "Vector3", processors = "AndroidCompensateDirection", variants = "LinearAcceleration")]
|
||||
[InputControl(name = "attitude", layout = "Quaternion", processors = "AndroidCompensateRotation", variants = "RotationVector")]
|
||||
[InputControl(name = "relativeHumidity", layout = "Axis", variants = "RelativeHumidity")]
|
||||
[InputControl(name = "ambientTemperature", layout = "Axis", variants = "AmbientTemperature")]
|
||||
[InputControl(name = "attitude", layout = "Quaternion", processors = "AndroidCompensateRotation", variants = "GameRotationVector")]
|
||||
[InputControl(name = "stepCounter", layout = "Integer", variants = "StepCounter")]
|
||||
[InputControl(name = "rotation", layout = "Quaternion", processors = "AndroidCompensateRotation", variants = "GeomagneticRotationVector")]
|
||||
[InputControl(name = "rate", layout = "Axis", variants = "HeartRate")]
|
||||
[InputControl(name = "angle", layout = "Axis", variants = nameof(AndroidSensorType.HingeAngle))]
|
||||
public fixed float data[16];
|
||||
|
||||
public AndroidSensorState WithData(params float[] data)
|
||||
{
|
||||
if (data == null)
|
||||
throw new ArgumentNullException(nameof(data));
|
||||
|
||||
for (var i = 0; i < data.Length && i < 16; i++)
|
||||
this.data[i] = data[i];
|
||||
|
||||
// Fill the rest with zeroes
|
||||
for (var i = data.Length; i < 16; i++)
|
||||
this.data[i] = 0.0f;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public FourCC format => kFormat;
|
||||
}
|
||||
|
||||
[DesignTimeVisible(false)]
|
||||
internal class AndroidCompensateDirectionProcessor : CompensateDirectionProcessor
|
||||
{
|
||||
// Taken from platforms\android-<API>\arch-arm\usr\include\android\sensor.h
|
||||
private const float kSensorStandardGravity = 9.80665f;
|
||||
|
||||
private const float kAccelerationMultiplier = -1.0f / kSensorStandardGravity;
|
||||
|
||||
public override Vector3 Process(Vector3 vector, InputControl control)
|
||||
{
|
||||
return base.Process(vector * kAccelerationMultiplier, control);
|
||||
}
|
||||
}
|
||||
|
||||
[DesignTimeVisible(false)]
|
||||
internal class AndroidCompensateRotationProcessor : CompensateRotationProcessor
|
||||
{
|
||||
public override Quaternion Process(Quaternion value, InputControl control)
|
||||
{
|
||||
// https://developer.android.com/reference/android/hardware/SensorEvent#values
|
||||
// "...The rotation vector represents the orientation of the device as a combination of an angle and an axis, in which the device has rotated through an angle theta around an axis <x, y, z>."
|
||||
// "...The three elements of the rotation vector are < x * sin(theta / 2), y* sin(theta / 2), z* sin(theta / 2)>, such that the magnitude of the rotation vector is equal to sin(theta / 2), and the direction of the rotation vector is equal to the direction of the axis of rotation."
|
||||
// "...The three elements of the rotation vector are equal to the last three components of a unit quaternion < cos(theta / 2), x* sin(theta/ 2), y* sin(theta / 2), z* sin(theta/ 2)>."
|
||||
//
|
||||
// In other words, axis + rotation is combined into Vector3, to recover the quaternion from it, we must compute 4th component as 1 - sqrt(x*x + y*y + z*z)
|
||||
var sinRho2 = value.x * value.x + value.y * value.y + value.z * value.z;
|
||||
value.w = (sinRho2 < 1.0f) ? Mathf.Sqrt(1.0f - sinRho2) : 0.0f;
|
||||
|
||||
return base.Process(value, control);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace UnityEngine.InputSystem.Android
|
||||
{
|
||||
/// <summary>
|
||||
/// Accelerometer device on Android.
|
||||
/// </summary>
|
||||
/// <seealso href="https://developer.android.com/reference/android/hardware/Sensor#TYPE_ACCELEROMETER"/>
|
||||
[InputControlLayout(stateType = typeof(AndroidSensorState), variants = "Accelerometer", hideInUI = true)]
|
||||
public class AndroidAccelerometer : Accelerometer
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Magnetic field sensor device on Android.
|
||||
/// </summary>
|
||||
/// <seealso href="https://developer.android.com/reference/android/hardware/Sensor#TYPE_MAGNETIC_FIELD"/>
|
||||
[InputControlLayout(stateType = typeof(AndroidSensorState), variants = "MagneticField", hideInUI = true)]
|
||||
public class AndroidMagneticFieldSensor : MagneticFieldSensor
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gyroscope device on android.
|
||||
/// </summary>
|
||||
/// <seealso href="https://developer.android.com/reference/android/hardware/Sensor#TYPE_GYROSCOPE"/>
|
||||
[InputControlLayout(stateType = typeof(AndroidSensorState), variants = "Gyroscope", hideInUI = true)]
|
||||
public class AndroidGyroscope : Gyroscope
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Light sensor device on Android.
|
||||
/// </summary>
|
||||
/// <seealso href="https://developer.android.com/reference/android/hardware/Sensor#TYPE_LIGHT"/>
|
||||
[InputControlLayout(stateType = typeof(AndroidSensorState), variants = "Light", hideInUI = true)]
|
||||
public class AndroidLightSensor : LightSensor
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pressure sensor device on Android.
|
||||
/// </summary>
|
||||
/// <seealso href="https://developer.android.com/reference/android/hardware/Sensor#TYPE_PRESSURE"/>
|
||||
[InputControlLayout(stateType = typeof(AndroidSensorState), variants = "Pressure", hideInUI = true)]
|
||||
public class AndroidPressureSensor : PressureSensor
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Proximity sensor type on Android.
|
||||
/// </summary>
|
||||
/// <seealso href="https://developer.android.com/reference/android/hardware/Sensor#TYPE_PROXIMITY"/>
|
||||
[InputControlLayout(stateType = typeof(AndroidSensorState), variants = "Proximity", hideInUI = true)]
|
||||
public class AndroidProximity : ProximitySensor
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gravity sensor device on Android.
|
||||
/// </summary>
|
||||
/// <seealso href="https://developer.android.com/reference/android/hardware/Sensor#TYPE_GRAVITY"/>
|
||||
[InputControlLayout(stateType = typeof(AndroidSensorState), variants = "Gravity", hideInUI = true)]
|
||||
public class AndroidGravitySensor : GravitySensor
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Linear acceleration sensor device on Android.
|
||||
/// </summary>
|
||||
/// <seealso href="https://developer.android.com/reference/android/hardware/Sensor#TYPE_LINEAR_ACCELERATION"/>
|
||||
[InputControlLayout(stateType = typeof(AndroidSensorState), variants = "LinearAcceleration", hideInUI = true)]
|
||||
public class AndroidLinearAccelerationSensor : LinearAccelerationSensor
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotation vector sensor device on Android.
|
||||
/// </summary>
|
||||
/// <seealso href="https://developer.android.com/reference/android/hardware/Sensor#TYPE_ROTATION_VECTOR"/>
|
||||
[InputControlLayout(stateType = typeof(AndroidSensorState), variants = "RotationVector", hideInUI = true)]
|
||||
public class AndroidRotationVector : AttitudeSensor
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Relative humidity sensor device on Android.
|
||||
/// </summary>
|
||||
/// <seealso href="https://developer.android.com/reference/android/hardware/Sensor#TYPE_RELATIVE_HUMIDITY"/>
|
||||
[InputControlLayout(stateType = typeof(AndroidSensorState), variants = "RelativeHumidity", hideInUI = true)]
|
||||
public class AndroidRelativeHumidity : HumiditySensor
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ambient temperature sensor device on Android.
|
||||
/// </summary>
|
||||
/// <seealso href="https://developer.android.com/reference/android/hardware/Sensor#TYPE_AMBIENT_TEMPERATURE"/>
|
||||
[InputControlLayout(stateType = typeof(AndroidSensorState), variants = "AmbientTemperature", hideInUI = true)]
|
||||
public class AndroidAmbientTemperature : AmbientTemperatureSensor
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Game rotation vector sensor device on Android.
|
||||
/// </summary>
|
||||
/// <seealso href="https://developer.android.com/reference/android/hardware/Sensor#TYPE_GAME_ROTATION_VECTOR"/>
|
||||
[InputControlLayout(stateType = typeof(AndroidSensorState), variants = "GameRotationVector", hideInUI = true)]
|
||||
public class AndroidGameRotationVector : AttitudeSensor
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Step counter sensor device on Android.
|
||||
/// </summary>
|
||||
/// <seealso href="https://developer.android.com/reference/android/hardware/Sensor#TYPE_STEP_COUNTER"/>
|
||||
[InputControlLayout(stateType = typeof(AndroidSensorState), variants = "StepCounter", hideInUI = true)]
|
||||
public class AndroidStepCounter : StepCounter
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hinge angle sensor device on Android.
|
||||
/// This sensor is usually available on foldable devices.
|
||||
/// </summary>
|
||||
/// <seealso href="https://developer.android.com/reference/android/hardware/Sensor#TYPE_HINGE_ANGLE"/>
|
||||
[InputControlLayout(stateType = typeof(AndroidSensorState), variants = nameof(AndroidSensorType.HingeAngle), hideInUI = true)]
|
||||
public class AndroidHingeAngle : HingeAngle
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 830105192d35a17469c56711468d1e8f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,174 @@
|
||||
#if UNITY_EDITOR || UNITY_ANDROID
|
||||
using System.Linq;
|
||||
using UnityEngine.InputSystem.Layouts;
|
||||
using UnityEngine.InputSystem.LowLevel;
|
||||
using UnityEngine.InputSystem.Android.LowLevel;
|
||||
|
||||
namespace UnityEngine.InputSystem.Android
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes custom android devices.
|
||||
/// You can use 'adb shell dumpsys input' from terminal to output information about all input devices.
|
||||
/// </summary>
|
||||
#if UNITY_DISABLE_DEFAULT_INPUT_PLUGIN_INITIALIZATION
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
class AndroidSupport
|
||||
{
|
||||
internal const string kAndroidInterface = "Android";
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
InputSystem.RegisterLayout<AndroidGamepad>(
|
||||
matches: new InputDeviceMatcher()
|
||||
.WithInterface(kAndroidInterface)
|
||||
.WithDeviceClass("AndroidGameController"));
|
||||
InputSystem.RegisterLayout<AndroidJoystick>(
|
||||
matches: new InputDeviceMatcher()
|
||||
.WithInterface(kAndroidInterface)
|
||||
.WithDeviceClass("AndroidGameController"));
|
||||
InputSystem.RegisterLayout<DualShock4GamepadAndroid>();
|
||||
InputSystem.RegisterLayout<XboxOneGamepadAndroid>();
|
||||
|
||||
////TODO: capability matching does not yet support bitmasking so these remain handled by OnFindLayoutForDevice for now
|
||||
InputSystem.RegisterLayout<AndroidGamepadWithDpadAxes>();
|
||||
InputSystem.RegisterLayout<AndroidGamepadWithDpadButtons>();
|
||||
|
||||
InputSystem.RegisterProcessor<AndroidCompensateDirectionProcessor>();
|
||||
InputSystem.RegisterProcessor<AndroidCompensateRotationProcessor>();
|
||||
|
||||
// Add sensors
|
||||
InputSystem.RegisterLayout<AndroidAccelerometer>(
|
||||
matches: new InputDeviceMatcher()
|
||||
.WithInterface(kAndroidInterface)
|
||||
.WithDeviceClass("AndroidSensor")
|
||||
.WithCapability("sensorType", AndroidSensorType.Accelerometer));
|
||||
InputSystem.RegisterLayout<AndroidMagneticFieldSensor>(
|
||||
matches: new InputDeviceMatcher()
|
||||
.WithInterface(kAndroidInterface)
|
||||
.WithDeviceClass("AndroidSensor")
|
||||
.WithCapability("sensorType", AndroidSensorType.MagneticField));
|
||||
InputSystem.RegisterLayout<AndroidGyroscope>(
|
||||
matches: new InputDeviceMatcher()
|
||||
.WithInterface(kAndroidInterface)
|
||||
.WithDeviceClass("AndroidSensor")
|
||||
.WithCapability("sensorType", AndroidSensorType.Gyroscope));
|
||||
InputSystem.RegisterLayout<AndroidLightSensor>(
|
||||
matches: new InputDeviceMatcher()
|
||||
.WithInterface(kAndroidInterface)
|
||||
.WithDeviceClass("AndroidSensor")
|
||||
.WithCapability("sensorType", AndroidSensorType.Light));
|
||||
InputSystem.RegisterLayout<AndroidPressureSensor>(
|
||||
matches: new InputDeviceMatcher()
|
||||
.WithInterface(kAndroidInterface)
|
||||
.WithDeviceClass("AndroidSensor")
|
||||
.WithCapability("sensorType", AndroidSensorType.Pressure));
|
||||
InputSystem.RegisterLayout<AndroidProximity>(
|
||||
matches: new InputDeviceMatcher()
|
||||
.WithInterface(kAndroidInterface)
|
||||
.WithDeviceClass("AndroidSensor")
|
||||
.WithCapability("sensorType", AndroidSensorType.Proximity));
|
||||
InputSystem.RegisterLayout<AndroidGravitySensor>(
|
||||
matches: new InputDeviceMatcher()
|
||||
.WithInterface(kAndroidInterface)
|
||||
.WithDeviceClass("AndroidSensor")
|
||||
.WithCapability("sensorType", AndroidSensorType.Gravity));
|
||||
InputSystem.RegisterLayout<AndroidLinearAccelerationSensor>(
|
||||
matches: new InputDeviceMatcher()
|
||||
.WithInterface(kAndroidInterface)
|
||||
.WithDeviceClass("AndroidSensor")
|
||||
.WithCapability("sensorType", AndroidSensorType.LinearAcceleration));
|
||||
InputSystem.RegisterLayout<AndroidRotationVector>(
|
||||
matches: new InputDeviceMatcher()
|
||||
.WithInterface(kAndroidInterface)
|
||||
.WithDeviceClass("AndroidSensor")
|
||||
.WithCapability("sensorType", AndroidSensorType.RotationVector));
|
||||
InputSystem.RegisterLayout<AndroidRelativeHumidity>(
|
||||
matches: new InputDeviceMatcher()
|
||||
.WithInterface(kAndroidInterface)
|
||||
.WithDeviceClass("AndroidSensor")
|
||||
.WithCapability("sensorType", AndroidSensorType.RelativeHumidity));
|
||||
InputSystem.RegisterLayout<AndroidAmbientTemperature>(
|
||||
matches: new InputDeviceMatcher()
|
||||
.WithInterface(kAndroidInterface)
|
||||
.WithDeviceClass("AndroidSensor")
|
||||
.WithCapability("sensorType", AndroidSensorType.AmbientTemperature));
|
||||
InputSystem.RegisterLayout<AndroidGameRotationVector>(
|
||||
matches: new InputDeviceMatcher()
|
||||
.WithInterface(kAndroidInterface)
|
||||
.WithDeviceClass("AndroidSensor")
|
||||
.WithCapability("sensorType", AndroidSensorType.GameRotationVector));
|
||||
InputSystem.RegisterLayout<AndroidStepCounter>(
|
||||
matches: new InputDeviceMatcher()
|
||||
.WithInterface(kAndroidInterface)
|
||||
.WithDeviceClass("AndroidSensor")
|
||||
.WithCapability("sensorType", AndroidSensorType.StepCounter));
|
||||
InputSystem.RegisterLayout<AndroidHingeAngle>(
|
||||
matches: new InputDeviceMatcher()
|
||||
.WithInterface(kAndroidInterface)
|
||||
.WithDeviceClass("AndroidSensor")
|
||||
.WithCapability("sensorType", AndroidSensorType.HingeAngle));
|
||||
|
||||
InputSystem.onFindLayoutForDevice += OnFindLayoutForDevice;
|
||||
}
|
||||
|
||||
internal static string OnFindLayoutForDevice(ref InputDeviceDescription description,
|
||||
string matchedLayout, InputDeviceExecuteCommandDelegate executeCommandDelegate)
|
||||
{
|
||||
// If we already have a matching layout, someone registered a better match.
|
||||
// We only want to act as a fallback.
|
||||
if (!string.IsNullOrEmpty(matchedLayout) && matchedLayout != "AndroidGamepad" && matchedLayout != "AndroidJoystick")
|
||||
return null;
|
||||
|
||||
if (description.interfaceName != "Android" || string.IsNullOrEmpty(description.capabilities))
|
||||
return null;
|
||||
|
||||
////TODO: these should just be Controller and Sensor; the interface is already Android
|
||||
switch (description.deviceClass)
|
||||
{
|
||||
case "AndroidGameController":
|
||||
{
|
||||
var caps = AndroidDeviceCapabilities.FromJson(description.capabilities);
|
||||
|
||||
// Note: Gamepads have both AndroidInputSource.Gamepad and AndroidInputSource.Joystick in input source, while
|
||||
// Joysticks don't have AndroidInputSource.Gamepad in their input source
|
||||
if ((caps.inputSources & AndroidInputSource.Gamepad) != AndroidInputSource.Gamepad)
|
||||
return "AndroidJoystick";
|
||||
|
||||
if (caps.motionAxes == null)
|
||||
return "AndroidGamepadWithDpadButtons";
|
||||
|
||||
// Vendor Ids, Product Ids can be found here http://www.linux-usb.org/usb.ids
|
||||
const int kVendorMicrosoft = 0x045e;
|
||||
const int kVendorSony = 0x054c;
|
||||
|
||||
// Tested with controllers: PS4 DualShock; XboxOne; Nvidia Shield
|
||||
// Tested on devices: Shield console Android 9; Galaxy s9+ Android 10
|
||||
if (caps.motionAxes.Contains(AndroidAxis.Z) &&
|
||||
caps.motionAxes.Contains(AndroidAxis.Rz) &&
|
||||
caps.motionAxes.Contains(AndroidAxis.HatX) &&
|
||||
caps.motionAxes.Contains(AndroidAxis.HatY))
|
||||
{
|
||||
if (caps.vendorId == kVendorMicrosoft)
|
||||
return "XboxOneGamepadAndroid";
|
||||
if (caps.vendorId == kVendorSony)
|
||||
return "DualShock4GamepadAndroid";
|
||||
}
|
||||
|
||||
|
||||
// Fallback to generic gamepads
|
||||
if (caps.motionAxes.Contains(AndroidAxis.HatX) &&
|
||||
caps.motionAxes.Contains(AndroidAxis.HatY))
|
||||
return "AndroidGamepadWithDpadAxes";
|
||||
|
||||
return "AndroidGamepadWithDpadButtons";
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // UNITY_EDITOR || UNITY_ANDROID
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0c8442e8094ce974598eacc0302ddabb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6eefa91e7a89647338245ee3818141d1
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,137 @@
|
||||
using UnityEngine.InputSystem.Controls;
|
||||
using UnityEngine.InputSystem.Layouts;
|
||||
using UnityEngine.Scripting;
|
||||
|
||||
////TODO: speaker, touchpad
|
||||
|
||||
////TODO: move gyro here
|
||||
|
||||
namespace UnityEngine.InputSystem.DualShock
|
||||
{
|
||||
/// <summary>
|
||||
/// A Sony DualShock/DualSense controller.
|
||||
/// </summary>
|
||||
[InputControlLayout(displayName = "PlayStation Controller")]
|
||||
public class DualShockGamepad : Gamepad, IDualShockHaptics
|
||||
{
|
||||
/// <summary>
|
||||
/// Button that is triggered when the touchbar on the controller is pressed down.
|
||||
/// </summary>
|
||||
/// <value>Control representing the touchbar button.</value>
|
||||
[InputControl(name = "buttonWest", displayName = "Square", shortDisplayName = "Square")]
|
||||
[InputControl(name = "buttonNorth", displayName = "Triangle", shortDisplayName = "Triangle")]
|
||||
[InputControl(name = "buttonEast", displayName = "Circle", shortDisplayName = "Circle")]
|
||||
[InputControl(name = "buttonSouth", displayName = "Cross", shortDisplayName = "Cross")]
|
||||
[InputControl]
|
||||
public ButtonControl touchpadButton { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// The right side button in the middle section of the controller. Equivalent to
|
||||
/// <see cref="Gamepad.startButton"/>.
|
||||
/// </summary>
|
||||
/// <value>Same as <see cref="Gamepad.startButton"/>.</value>
|
||||
[InputControl(name = "start", displayName = "Options")]
|
||||
public ButtonControl optionsButton { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// The left side button in the middle section of the controller. Equivalent to
|
||||
/// <see cref="Gamepad.selectButton"/>
|
||||
/// </summary>
|
||||
/// <value>Same as <see cref="Gamepad.selectButton"/>.</value>
|
||||
[InputControl(name = "select", displayName = "Share")]
|
||||
public ButtonControl shareButton { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// The left shoulder button.
|
||||
/// </summary>
|
||||
/// <value>Equivalent to <see cref="Gamepad.leftShoulder"/>.</value>
|
||||
[InputControl(name = "leftShoulder", displayName = "L1", shortDisplayName = "L1")]
|
||||
public ButtonControl L1 { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// The right shoulder button.
|
||||
/// </summary>
|
||||
/// <value>Equivalent to <see cref="Gamepad.rightShoulder"/>.</value>
|
||||
[InputControl(name = "rightShoulder", displayName = "R1", shortDisplayName = "R1")]
|
||||
public ButtonControl R1 { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// The left trigger button.
|
||||
/// </summary>
|
||||
/// <value>Equivalent to <see cref="Gamepad.leftTrigger"/>.</value>
|
||||
[InputControl(name = "leftTrigger", displayName = "L2", shortDisplayName = "L2")]
|
||||
public ButtonControl L2 { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// The right trigger button.
|
||||
/// </summary>
|
||||
/// <value>Equivalent to <see cref="Gamepad.rightTrigger"/>.</value>
|
||||
[InputControl(name = "rightTrigger", displayName = "R2", shortDisplayName = "R2")]
|
||||
public ButtonControl R2 { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// The left stick press button.
|
||||
/// </summary>
|
||||
/// <value>Equivalent to <see cref="Gamepad.leftStickButton"/>.</value>
|
||||
[InputControl(name = "leftStickPress", displayName = "L3", shortDisplayName = "L3")]
|
||||
public ButtonControl L3 { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// The right stick press button.
|
||||
/// </summary>
|
||||
/// <value>Equivalent to <see cref="Gamepad.rightStickButton"/>.</value>
|
||||
[InputControl(name = "rightStickPress", displayName = "R3", shortDisplayName = "R3")]
|
||||
public ButtonControl R3 { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// The last used/added DualShock controller.
|
||||
/// </summary>
|
||||
/// <value>Equivalent to <see cref="Gamepad.leftTrigger"/>.</value>
|
||||
public new static DualShockGamepad current { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// If the controller is connected over HID, returns <see cref="HID.HID.HIDDeviceDescriptor"/> data parsed from <see cref="InputDeviceDescription.capabilities"/>.
|
||||
/// </summary>
|
||||
internal HID.HID.HIDDeviceDescriptor hidDescriptor { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void MakeCurrent()
|
||||
{
|
||||
base.MakeCurrent();
|
||||
current = this;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnRemoved()
|
||||
{
|
||||
base.OnRemoved();
|
||||
if (current == this)
|
||||
current = null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void FinishSetup()
|
||||
{
|
||||
base.FinishSetup();
|
||||
|
||||
touchpadButton = GetChildControl<ButtonControl>("touchpadButton");
|
||||
optionsButton = startButton;
|
||||
shareButton = selectButton;
|
||||
|
||||
L1 = leftShoulder;
|
||||
R1 = rightShoulder;
|
||||
L2 = leftTrigger;
|
||||
R2 = rightTrigger;
|
||||
L3 = leftStickButton;
|
||||
R3 = rightStickButton;
|
||||
|
||||
if (m_Description.capabilities != null && m_Description.interfaceName == "HID")
|
||||
hidDescriptor = HID.HID.HIDDeviceDescriptor.FromJson(m_Description.capabilities);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void SetLightBarColor(Color color)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: df88ce51eaa14f2f8975023f7f45d2d9
|
||||
timeCreated: 1511133490
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c7225419e3d446c5982afd5ba9cce319
|
||||
timeCreated: 1517282565
|
@@ -0,0 +1,73 @@
|
||||
using UnityEngine.InputSystem.Layouts;
|
||||
|
||||
namespace UnityEngine.InputSystem.DualShock
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds support for PS4 DualShock controllers.
|
||||
/// </summary>
|
||||
#if UNITY_DISABLE_DEFAULT_INPUT_PLUGIN_INITIALIZATION
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
static class DualShockSupport
|
||||
{
|
||||
public static void Initialize()
|
||||
{
|
||||
InputSystem.RegisterLayout<DualShockGamepad>();
|
||||
|
||||
// HID version for platforms where we pick up the controller as a raw HID.
|
||||
// This works without any PS4-specific drivers but does not support the full
|
||||
// range of capabilities of the controller (the HID format is undocumented
|
||||
// and only partially understood).
|
||||
//
|
||||
// NOTE: We match by PID and VID here as that is the most reliable way. The product
|
||||
// and manufacturer strings we get from APIs often return inconsistent results
|
||||
// or none at all. E.g. when connected via Bluetooth on OSX, the DualShock will
|
||||
// not return anything from IOHIDDevice_GetProduct() and IOHIDevice_GetManufacturer()
|
||||
// even though it will report the expected results when plugged in via USB.
|
||||
#if UNITY_STANDALONE_WIN || UNITY_STANDALONE_OSX || UNITY_WSA || UNITY_EDITOR
|
||||
InputSystem.RegisterLayout<DualSenseGamepadHID>(
|
||||
matches: new InputDeviceMatcher()
|
||||
.WithInterface("HID")
|
||||
.WithCapability("vendorId", 0x54C) // Sony Entertainment.
|
||||
.WithCapability("productId", 0xDF2)); // Dual Sense Edge
|
||||
InputSystem.RegisterLayout<DualSenseGamepadHID>(
|
||||
matches: new InputDeviceMatcher()
|
||||
.WithInterface("HID")
|
||||
.WithCapability("vendorId", 0x54C) // Sony Entertainment.
|
||||
.WithCapability("productId", 0xCE6)); // Dual Sense
|
||||
InputSystem.RegisterLayout<DualShock4GamepadHID>(
|
||||
matches: new InputDeviceMatcher()
|
||||
.WithInterface("HID")
|
||||
.WithCapability("vendorId", 0x54C) // Sony Entertainment.
|
||||
.WithCapability("productId", 0x9CC)); // Wireless controller.
|
||||
InputSystem.RegisterLayoutMatcher<DualShock4GamepadHID>(
|
||||
new InputDeviceMatcher()
|
||||
.WithInterface("HID")
|
||||
.WithCapability("vendorId", 0x54C) // Sony Entertainment.
|
||||
.WithCapability("productId", 0x5C4)); // Wireless controller.
|
||||
|
||||
// Just to make sure, also set up a matcher that goes by strings so that we cover
|
||||
// all bases.
|
||||
InputSystem.RegisterLayoutMatcher<DualShock4GamepadHID>(
|
||||
new InputDeviceMatcher()
|
||||
.WithInterface("HID")
|
||||
.WithManufacturerContains("Sony")
|
||||
.WithProduct("Wireless Controller"));
|
||||
|
||||
InputSystem.RegisterLayout<DualShock3GamepadHID>(
|
||||
matches: new InputDeviceMatcher()
|
||||
.WithInterface("HID")
|
||||
.WithCapability("vendorId", 0x54C) // Sony Entertainment.
|
||||
.WithCapability("productId", 0x268)); // PLAYSTATION(R)3 Controller.
|
||||
|
||||
InputSystem.RegisterLayoutMatcher<DualShock3GamepadHID>(
|
||||
new InputDeviceMatcher()
|
||||
.WithInterface("HID")
|
||||
.WithManufacturerContains("Sony")
|
||||
.WithProduct("PLAYSTATION(R)3 Controller", supportRegex: false));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fa26399d579948dd90c6ff298cdb0570
|
||||
timeCreated: 1511133448
|
@@ -0,0 +1,17 @@
|
||||
using UnityEngine.InputSystem.Haptics;
|
||||
|
||||
namespace UnityEngine.InputSystem.DualShock
|
||||
{
|
||||
/// <summary>
|
||||
/// Extended haptics interface for DualShock controllers.
|
||||
/// </summary>
|
||||
public interface IDualShockHaptics : IDualMotorRumble
|
||||
{
|
||||
/// <summary>
|
||||
/// Set the color of the light bar on the back of the controller.
|
||||
/// </summary>
|
||||
/// <param name="color">Color to use for the light bar. Alpha component is ignored. Also,
|
||||
/// RBG values are clamped into [0..1] range.</param>
|
||||
void SetLightBarColor(Color color);
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0e235e01f7e6408f87e899ac373c85d1
|
||||
timeCreated: 1517282788
|
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ef4b66ea93dd243a783c32ab1da39fc4
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,213 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using UnityEngine.InputSystem.LowLevel;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
////REVIEW: this *really* should be renamed to TouchPolling or something like that
|
||||
|
||||
////REVIEW: Should this auto-enable itself when the API is used? Problem with this is that it means the first touch inputs will get missed
|
||||
//// as by the time the API is polled, we're already into the first frame.
|
||||
|
||||
////TODO: gesture support
|
||||
////TODO: high-frequency touch support
|
||||
|
||||
////REVIEW: have TouchTap, TouchSwipe, etc. wrapper MonoBehaviours like LeanTouch?
|
||||
|
||||
////TODO: as soon as we can break the API, remove the EnhancedTouchSupport class altogether and rename UnityEngine.InputSystem.EnhancedTouch to TouchPolling
|
||||
|
||||
////FIXME: does not survive domain reloads
|
||||
|
||||
namespace UnityEngine.InputSystem.EnhancedTouch
|
||||
{
|
||||
/// <summary>
|
||||
/// API to control enhanced touch facilities like <see cref="Touch"/> that are not
|
||||
/// enabled by default.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Enhanced touch support provides automatic finger tracking and touch history recording.
|
||||
/// It is an API designed for polling, i.e. for querying touch state directly in methods
|
||||
/// such as <c>MonoBehaviour.Update</c>. Enhanced touch support cannot be used in combination
|
||||
/// with <see cref="InputAction"/>s though both can be used side-by-side.
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// public class MyBehavior : MonoBehaviour
|
||||
/// {
|
||||
/// protected void OnEnable()
|
||||
/// {
|
||||
/// EnhancedTouchSupport.Enable();
|
||||
/// }
|
||||
///
|
||||
/// protected void OnDisable()
|
||||
/// {
|
||||
/// EnhancedTouchSupport.Disable();
|
||||
/// }
|
||||
///
|
||||
/// protected void Update()
|
||||
/// {
|
||||
/// var activeTouches = Touch.activeTouches;
|
||||
/// for (var i = 0; i < activeTouches.Count; ++i)
|
||||
/// Debug.Log("Active touch: " + activeTouches[i]);
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// </remarks>
|
||||
/// <seealso cref="Touch"/>
|
||||
/// <seealso cref="Finger"/>
|
||||
public static class EnhancedTouchSupport
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether enhanced touch support is currently enabled.
|
||||
/// </summary>
|
||||
/// <value>True if EnhancedTouch support has been enabled.</value>
|
||||
public static bool enabled => s_Enabled > 0;
|
||||
|
||||
private static int s_Enabled;
|
||||
private static InputSettings.UpdateMode s_UpdateMode;
|
||||
|
||||
/// <summary>
|
||||
/// Enable enhanced touch support.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Calling this method is necessary to enable the functionality provided
|
||||
/// by <see cref="Touch"/> and <see cref="Finger"/>. These APIs add extra
|
||||
/// processing to touches and are thus disabled by default.
|
||||
///
|
||||
/// Calls to <c>Enable</c> and <see cref="Disable"/> balance each other out.
|
||||
/// If <c>Enable</c> is called repeatedly, it will take as many calls to
|
||||
/// <see cref="Disable"/> to disable the system again.
|
||||
/// </remarks>
|
||||
public static void Enable()
|
||||
{
|
||||
++s_Enabled;
|
||||
if (s_Enabled > 1)
|
||||
return;
|
||||
|
||||
InputSystem.onDeviceChange += OnDeviceChange;
|
||||
InputSystem.onBeforeUpdate += Touch.BeginUpdate;
|
||||
InputSystem.onSettingsChange += OnSettingsChange;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
AssemblyReloadEvents.beforeAssemblyReload += OnBeforeDomainReload;
|
||||
#endif
|
||||
|
||||
SetUpState();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disable enhanced touch support.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method only undoes a single call to <see cref="Enable"/>.
|
||||
/// </remarks>
|
||||
public static void Disable()
|
||||
{
|
||||
if (!enabled)
|
||||
return;
|
||||
--s_Enabled;
|
||||
if (s_Enabled > 0)
|
||||
return;
|
||||
|
||||
InputSystem.onDeviceChange -= OnDeviceChange;
|
||||
InputSystem.onBeforeUpdate -= Touch.BeginUpdate;
|
||||
InputSystem.onSettingsChange -= OnSettingsChange;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
AssemblyReloadEvents.beforeAssemblyReload -= OnBeforeDomainReload;
|
||||
#endif
|
||||
|
||||
TearDownState();
|
||||
}
|
||||
|
||||
internal static void Reset()
|
||||
{
|
||||
Touch.s_GlobalState.touchscreens = default;
|
||||
Touch.s_GlobalState.playerState.Destroy();
|
||||
Touch.s_GlobalState.playerState = default;
|
||||
#if UNITY_EDITOR
|
||||
Touch.s_GlobalState.editorState.Destroy();
|
||||
Touch.s_GlobalState.editorState = default;
|
||||
#endif
|
||||
s_Enabled = 0;
|
||||
}
|
||||
|
||||
private static void SetUpState()
|
||||
{
|
||||
Touch.s_GlobalState.playerState.updateMask = InputUpdateType.Dynamic | InputUpdateType.Manual | InputUpdateType.Fixed;
|
||||
#if UNITY_EDITOR
|
||||
Touch.s_GlobalState.editorState.updateMask = InputUpdateType.Editor;
|
||||
#endif
|
||||
|
||||
s_UpdateMode = InputSystem.settings.updateMode;
|
||||
|
||||
foreach (var device in InputSystem.devices)
|
||||
OnDeviceChange(device, InputDeviceChange.Added);
|
||||
}
|
||||
|
||||
internal static void TearDownState()
|
||||
{
|
||||
foreach (var device in InputSystem.devices)
|
||||
OnDeviceChange(device, InputDeviceChange.Removed);
|
||||
|
||||
Touch.s_GlobalState.playerState.Destroy();
|
||||
#if UNITY_EDITOR
|
||||
Touch.s_GlobalState.editorState.Destroy();
|
||||
#endif
|
||||
|
||||
Touch.s_GlobalState.playerState = default;
|
||||
#if UNITY_EDITOR
|
||||
Touch.s_GlobalState.editorState = default;
|
||||
#endif
|
||||
}
|
||||
|
||||
private static void OnDeviceChange(InputDevice device, InputDeviceChange change)
|
||||
{
|
||||
switch (change)
|
||||
{
|
||||
case InputDeviceChange.Added:
|
||||
{
|
||||
if (device is Touchscreen touchscreen)
|
||||
Touch.AddTouchscreen(touchscreen);
|
||||
break;
|
||||
}
|
||||
|
||||
case InputDeviceChange.Removed:
|
||||
{
|
||||
if (device is Touchscreen touchscreen)
|
||||
Touch.RemoveTouchscreen(touchscreen);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnSettingsChange()
|
||||
{
|
||||
var currentUpdateMode = InputSystem.settings.updateMode;
|
||||
if (s_UpdateMode == currentUpdateMode)
|
||||
return;
|
||||
TearDownState();
|
||||
SetUpState();
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private static void OnBeforeDomainReload()
|
||||
{
|
||||
// We need to release NativeArrays we're holding before losing track of them during domain reloads.
|
||||
Touch.s_GlobalState.playerState.Destroy();
|
||||
Touch.s_GlobalState.editorState.Destroy();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
[Conditional("DEVELOPMENT_BUILD")]
|
||||
[Conditional("UNITY_EDITOR")]
|
||||
internal static void CheckEnabled()
|
||||
{
|
||||
if (!enabled)
|
||||
throw new InvalidOperationException("EnhancedTouch API is not enabled; call EnhancedTouchSupport.Enable()");
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3cf42e49077e442fd8b9dc29c328d345
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,266 @@
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using UnityEngine.InputSystem.LowLevel;
|
||||
using UnityEngine.InputSystem.Utilities;
|
||||
|
||||
namespace UnityEngine.InputSystem.EnhancedTouch
|
||||
{
|
||||
/// <summary>
|
||||
/// A source of touches (<see cref="Touch"/>).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Each <see cref="Touchscreen"/> has a limited number of fingers it supports corresponding to the total number of concurrent
|
||||
/// touches supported by the screen. Unlike a <see cref="Touch"/>, a <see cref="Finger"/> will stay the same and valid for the
|
||||
/// lifetime of its <see cref="Touchscreen"/>.
|
||||
///
|
||||
/// Note that a Finger does not represent an actual physical finger in the world. That is, the same Finger instance might be used,
|
||||
/// for example, for a touch from the index finger at one point and then for a touch from the ring finger. Each Finger simply
|
||||
/// corresponds to the Nth touch on the given screen.
|
||||
/// </remarks>
|
||||
/// <seealso cref="Touch"/>
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable",
|
||||
Justification = "Holds on to internally managed memory which should not be disposed by the user.")]
|
||||
public class Finger
|
||||
{
|
||||
// This class stores pretty much all the data that is kept by the enhanced touch system. All
|
||||
// the finger and history tracking is found here.
|
||||
|
||||
/// <summary>
|
||||
/// The screen that the finger is associated with.
|
||||
/// </summary>
|
||||
/// <value>Touchscreen associated with the touch.</value>
|
||||
public Touchscreen screen { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Index of the finger on <see cref="screen"/>. Each finger corresponds to the Nth touch on a screen.
|
||||
/// </summary>
|
||||
public int index { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the finger is currently touching the screen.
|
||||
/// </summary>
|
||||
public bool isActive => currentTouch.valid;
|
||||
|
||||
/// <summary>
|
||||
/// The current position of the finger on the screen or <c>default(Vector2)</c> if there is no
|
||||
/// ongoing touch.
|
||||
/// </summary>
|
||||
public Vector2 screenPosition
|
||||
{
|
||||
get
|
||||
{
|
||||
////REVIEW: should this work off of currentTouch instead of lastTouch?
|
||||
var touch = lastTouch;
|
||||
if (!touch.valid)
|
||||
return default;
|
||||
return touch.screenPosition;
|
||||
}
|
||||
}
|
||||
|
||||
////REVIEW: should lastTouch and currentTouch have accumulated deltas? would that be confusing?
|
||||
|
||||
/// <summary>
|
||||
/// The last touch that happened on the finger or <c>default(Touch)</c> (with <see cref="Touch.valid"/> being
|
||||
/// false) if no touch has been registered on the finger yet.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A given touch will be returned from this property for as long as no new touch has been started. As soon as a
|
||||
/// new touch is registered on the finger, the property switches to the new touch.
|
||||
/// </remarks>
|
||||
public Touch lastTouch
|
||||
{
|
||||
get
|
||||
{
|
||||
var count = m_StateHistory.Count;
|
||||
if (count == 0)
|
||||
return default;
|
||||
return new Touch(this, m_StateHistory[count - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The currently ongoing touch for the finger or <c>default(Touch)</c> (with <see cref="Touch.valid"/> being false)
|
||||
/// if no touch is currently in progress on the finger.
|
||||
/// </summary>
|
||||
public Touch currentTouch
|
||||
{
|
||||
get
|
||||
{
|
||||
var touch = lastTouch;
|
||||
if (!touch.valid)
|
||||
return default;
|
||||
if (touch.isInProgress)
|
||||
return touch;
|
||||
// Ended touches stay current in the frame they ended in.
|
||||
if (touch.updateStepCount == InputUpdate.s_UpdateStepCount)
|
||||
return touch;
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The full touch history of the finger.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The history is capped at <see cref="Touch.maxHistoryLengthPerFinger"/>. Once full, newer touch records will start
|
||||
/// overwriting older entries. Note that this means that a given touch will not trace all the way back to its beginning
|
||||
/// if it runs past the max history size.
|
||||
/// </remarks>
|
||||
public TouchHistory touchHistory => new TouchHistory(this, m_StateHistory);
|
||||
|
||||
internal readonly InputStateHistory<TouchState> m_StateHistory;
|
||||
|
||||
internal Finger(Touchscreen screen, int index, InputUpdateType updateMask)
|
||||
{
|
||||
this.screen = screen;
|
||||
this.index = index;
|
||||
|
||||
// Set up history recording.
|
||||
m_StateHistory = new InputStateHistory<TouchState>(screen.touches[index])
|
||||
{
|
||||
historyDepth = Touch.maxHistoryLengthPerFinger,
|
||||
extraMemoryPerRecord = UnsafeUtility.SizeOf<Touch.ExtraDataPerTouchState>(),
|
||||
onRecordAdded = OnTouchRecorded,
|
||||
onShouldRecordStateChange = ShouldRecordTouch,
|
||||
updateMask = updateMask,
|
||||
};
|
||||
m_StateHistory.StartRecording();
|
||||
|
||||
// record the current state if touch is already in progress
|
||||
if (screen.touches[index].isInProgress)
|
||||
m_StateHistory.RecordStateChange(screen.touches[index], screen.touches[index].value);
|
||||
}
|
||||
|
||||
private static unsafe bool ShouldRecordTouch(InputControl control, double time, InputEventPtr eventPtr)
|
||||
{
|
||||
// We only want to record changes that come from events. We ignore internal state
|
||||
// changes that Touchscreen itself generates. This includes the resetting of deltas.
|
||||
if (!eventPtr.valid)
|
||||
return false;
|
||||
var eventType = eventPtr.type;
|
||||
if (eventType != StateEvent.Type && eventType != DeltaStateEvent.Type)
|
||||
return false;
|
||||
|
||||
// Direct memory access for speed.
|
||||
var currentTouchState = (TouchState*)((byte*)control.currentStatePtr + control.stateBlock.byteOffset);
|
||||
|
||||
// Touchscreen will record a button down and button up on a TouchControl when a tap occurs.
|
||||
// We only want to record the button down, not the button up.
|
||||
if (currentTouchState->isTapRelease)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private unsafe void OnTouchRecorded(InputStateHistory.Record record)
|
||||
{
|
||||
var recordIndex = record.recordIndex;
|
||||
var touchHeader = m_StateHistory.GetRecordUnchecked(recordIndex);
|
||||
var touchState = (TouchState*)touchHeader->statePtrWithoutControlIndex; // m_StateHistory is bound to a single TouchControl.
|
||||
touchState->updateStepCount = InputUpdate.s_UpdateStepCount;
|
||||
|
||||
// Invalidate activeTouches.
|
||||
Touch.s_GlobalState.playerState.haveBuiltActiveTouches = false;
|
||||
|
||||
// Record the extra data we maintain for each touch.
|
||||
var extraData = (Touch.ExtraDataPerTouchState*)((byte*)touchHeader + m_StateHistory.bytesPerRecord -
|
||||
UnsafeUtility.SizeOf<Touch.ExtraDataPerTouchState>());
|
||||
extraData->uniqueId = ++Touch.s_GlobalState.playerState.lastId;
|
||||
|
||||
// We get accumulated deltas from Touchscreen. Store the accumulated
|
||||
// value and "unaccumulate" the value we store on delta.
|
||||
extraData->accumulatedDelta = touchState->delta;
|
||||
if (touchState->phase != TouchPhase.Began)
|
||||
{
|
||||
// Inlined (instead of just using record.previous) for speed. Bypassing
|
||||
// the safety checks here.
|
||||
if (recordIndex != m_StateHistory.m_HeadIndex)
|
||||
{
|
||||
var previousRecordIndex = recordIndex == 0 ? m_StateHistory.historyDepth - 1 : recordIndex - 1;
|
||||
var previousTouchHeader = m_StateHistory.GetRecordUnchecked(previousRecordIndex);
|
||||
var previousTouchState = (TouchState*)previousTouchHeader->statePtrWithoutControlIndex;
|
||||
touchState->delta -= previousTouchState->delta;
|
||||
touchState->beganInSameFrame = previousTouchState->beganInSameFrame &&
|
||||
previousTouchState->updateStepCount == touchState->updateStepCount;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
touchState->beganInSameFrame = true;
|
||||
}
|
||||
|
||||
// Trigger callback.
|
||||
switch (touchState->phase)
|
||||
{
|
||||
case TouchPhase.Began:
|
||||
DelegateHelpers.InvokeCallbacksSafe(ref Touch.s_GlobalState.onFingerDown, this, "Touch.onFingerDown");
|
||||
break;
|
||||
case TouchPhase.Moved:
|
||||
DelegateHelpers.InvokeCallbacksSafe(ref Touch.s_GlobalState.onFingerMove, this, "Touch.onFingerMove");
|
||||
break;
|
||||
case TouchPhase.Ended:
|
||||
case TouchPhase.Canceled:
|
||||
DelegateHelpers.InvokeCallbacksSafe(ref Touch.s_GlobalState.onFingerUp, this, "Touch.onFingerUp");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe Touch FindTouch(uint uniqueId)
|
||||
{
|
||||
Debug.Assert(uniqueId != default, "0 is not a valid ID");
|
||||
foreach (var record in m_StateHistory)
|
||||
{
|
||||
if (((Touch.ExtraDataPerTouchState*)record.GetUnsafeExtraMemoryPtrUnchecked())->uniqueId == uniqueId)
|
||||
return new Touch(this, record);
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
internal unsafe TouchHistory GetTouchHistory(Touch touch)
|
||||
{
|
||||
Debug.Assert(touch.finger == this);
|
||||
|
||||
// If the touch is not pointing to our history, it's probably a touch we copied for
|
||||
// activeTouches. We know the unique ID of the touch so go and try to find the touch
|
||||
// in our history.
|
||||
var touchRecord = touch.m_TouchRecord;
|
||||
if (touchRecord.owner != m_StateHistory)
|
||||
{
|
||||
touch = FindTouch(touch.uniqueId);
|
||||
if (!touch.valid)
|
||||
return default;
|
||||
}
|
||||
|
||||
var touchId = touch.touchId;
|
||||
var startIndex = touch.m_TouchRecord.index;
|
||||
|
||||
// If the current touch isn't the beginning of the touch, search back through the
|
||||
// history for all touches belonging to the same contact.
|
||||
var count = 0;
|
||||
if (touch.phase != TouchPhase.Began)
|
||||
{
|
||||
for (var previousRecord = touch.m_TouchRecord.previous; previousRecord.valid; previousRecord = previousRecord.previous)
|
||||
{
|
||||
var touchState = (TouchState*)previousRecord.GetUnsafeMemoryPtr();
|
||||
|
||||
// Stop if the touch doesn't belong to the same contact.
|
||||
if (touchState->touchId != touchId)
|
||||
break;
|
||||
++count;
|
||||
|
||||
// Stop if we've found the beginning of the touch.
|
||||
if (touchState->phase == TouchPhase.Began)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (count == 0)
|
||||
return default;
|
||||
|
||||
// We don't want to include the touch we started with.
|
||||
--startIndex;
|
||||
|
||||
return new TouchHistory(this, m_StateHistory, startIndex, count);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 71f3c0e630f0148b496f565843b5b272
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6261dd2a83757498188548c57e2492bf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,111 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.InputSystem.LowLevel;
|
||||
|
||||
namespace UnityEngine.InputSystem.EnhancedTouch
|
||||
{
|
||||
/// <summary>
|
||||
/// A fixed-size buffer of <see cref="Touch"/> records used to trace the history of touches.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This struct provides access to a recorded list of touches.
|
||||
/// </remarks>
|
||||
public struct TouchHistory : IReadOnlyList<Touch>
|
||||
{
|
||||
private readonly InputStateHistory<TouchState> m_History;
|
||||
private readonly Finger m_Finger;
|
||||
private readonly int m_Count;
|
||||
private readonly int m_StartIndex;
|
||||
private readonly uint m_Version;
|
||||
|
||||
internal TouchHistory(Finger finger, InputStateHistory<TouchState> history, int startIndex = -1, int count = -1)
|
||||
{
|
||||
m_Finger = finger;
|
||||
m_History = history;
|
||||
m_Version = history.version;
|
||||
m_Count = count >= 0 ? count : m_History.Count;
|
||||
m_StartIndex = startIndex >= 0 ? startIndex : m_History.Count - 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumerate touches in the history. Goes from newest records to oldest.
|
||||
/// </summary>
|
||||
/// <returns>Enumerator over the touches in the history.</returns>
|
||||
public IEnumerator<Touch> GetEnumerator()
|
||||
{
|
||||
return new Enumerator(this);
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Number of history records available.
|
||||
/// </summary>
|
||||
public int Count => m_Count;
|
||||
|
||||
/// <summary>
|
||||
/// Return a history record by index. Indexing starts at 0 == newest to <see cref="Count"/> - 1 == oldest.
|
||||
/// </summary>
|
||||
/// <param name="index">Index of history record.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is less than 0 or >= <see cref="Count"/>.</exception>
|
||||
public Touch this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
CheckValid();
|
||||
if (index < 0 || index >= Count)
|
||||
throw new ArgumentOutOfRangeException(
|
||||
$"Index {index} is out of range for history with {Count} entries", nameof(index));
|
||||
|
||||
// History records oldest-first but we index newest-first.
|
||||
return new Touch(m_Finger, m_History[m_StartIndex - index]);
|
||||
}
|
||||
}
|
||||
|
||||
internal void CheckValid()
|
||||
{
|
||||
if (m_Finger == null || m_History == null)
|
||||
throw new InvalidOperationException("Touch history not initialized");
|
||||
if (m_History.version != m_Version)
|
||||
throw new InvalidOperationException(
|
||||
"Touch history is no longer valid; the recorded history has been changed");
|
||||
}
|
||||
|
||||
private class Enumerator : IEnumerator<Touch>
|
||||
{
|
||||
private readonly TouchHistory m_Owner;
|
||||
private int m_Index;
|
||||
|
||||
internal Enumerator(TouchHistory owner)
|
||||
{
|
||||
m_Owner = owner;
|
||||
m_Index = -1;
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (m_Index >= m_Owner.Count - 1)
|
||||
return false;
|
||||
++m_Index;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
m_Index = -1;
|
||||
}
|
||||
|
||||
public Touch Current => m_Owner[m_Index];
|
||||
|
||||
object IEnumerator.Current => Current;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4cc0c6a7fa1c14a518fec64497f4937f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,411 @@
|
||||
using System;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using UnityEngine.InputSystem.Controls;
|
||||
using UnityEngine.InputSystem.LowLevel;
|
||||
using UnityEngine.InputSystem.Utilities;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
using UnityEngine.InputSystem.Editor;
|
||||
#endif
|
||||
|
||||
////TODO: add pressure support
|
||||
|
||||
////REVIEW: extend this beyond simulating from Pointers only? theoretically, we could simulate from any means of generating positions and presses
|
||||
|
||||
////REVIEW: I think this is a workable first attempt but overall, not a sufficient take on input simulation. ATM this uses InputState.Change
|
||||
//// to shove input directly into Touchscreen. Also, it uses state change notifications to set off the simulation. The latter leads
|
||||
//// to touch input potentially changing multiple times in response to a single pointer event. And the former leads to the simulated
|
||||
//// touch input not being visible at the event level -- which leaves Touch and Finger slightly unhappy, for example.
|
||||
//// I think being able to cycle simulated input fully through the event loop would result in a setup that is both simpler and more robust.
|
||||
//// Also, it would allow *disabling* the source devices as long as we don't disable them in the backend, too.
|
||||
//// Finally, the fact that we spin off input *from* events here and feed that into InputState.Change() by passing the event along
|
||||
//// means that places that make sure we process input only once (e.g. binding composites which will remember the event ID they have
|
||||
//// been triggered from) may reject the simulated input when they have already seen the non-simulated input (which may be okay
|
||||
//// behavior).
|
||||
|
||||
namespace UnityEngine.InputSystem.EnhancedTouch
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a <see cref="Touchscreen"/> with input simulated from other types of <see cref="Pointer"/> devices (e.g. <see cref="Mouse"/>
|
||||
/// or <see cref="Pen"/>).
|
||||
/// </summary>
|
||||
[AddComponentMenu("Input/Debug/Touch Simulation")]
|
||||
[ExecuteInEditMode]
|
||||
[HelpURL(InputSystem.kDocUrl + "/manual/Touch.html#touch-simulation")]
|
||||
#if UNITY_EDITOR
|
||||
[InitializeOnLoad]
|
||||
#endif
|
||||
public class TouchSimulation : MonoBehaviour, IInputStateChangeMonitor
|
||||
{
|
||||
public Touchscreen simulatedTouchscreen { get; private set; }
|
||||
|
||||
public static TouchSimulation instance => s_Instance;
|
||||
|
||||
public static void Enable()
|
||||
{
|
||||
if (instance == null)
|
||||
{
|
||||
////TODO: find instance
|
||||
var hiddenGO = new GameObject();
|
||||
hiddenGO.SetActive(false);
|
||||
hiddenGO.hideFlags = HideFlags.HideAndDontSave;
|
||||
s_Instance = hiddenGO.AddComponent<TouchSimulation>();
|
||||
instance.gameObject.SetActive(true);
|
||||
}
|
||||
instance.enabled = true;
|
||||
}
|
||||
|
||||
public static void Disable()
|
||||
{
|
||||
if (instance != null)
|
||||
instance.enabled = false;
|
||||
}
|
||||
|
||||
public static void Destroy()
|
||||
{
|
||||
Disable();
|
||||
|
||||
if (s_Instance != null)
|
||||
{
|
||||
Destroy(s_Instance.gameObject);
|
||||
s_Instance = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected void AddPointer(Pointer pointer)
|
||||
{
|
||||
if (pointer == null)
|
||||
throw new ArgumentNullException(nameof(pointer));
|
||||
|
||||
// Ignore if already added.
|
||||
if (m_Pointers.ContainsReference(m_NumPointers, pointer))
|
||||
return;
|
||||
|
||||
// Add to list.
|
||||
ArrayHelpers.AppendWithCapacity(ref m_Pointers, ref m_NumPointers, pointer);
|
||||
ArrayHelpers.Append(ref m_CurrentPositions, default(Vector2));
|
||||
ArrayHelpers.Append(ref m_CurrentDisplayIndices, default(int));
|
||||
|
||||
InputSystem.DisableDevice(pointer, keepSendingEvents: true);
|
||||
}
|
||||
|
||||
protected void RemovePointer(Pointer pointer)
|
||||
{
|
||||
if (pointer == null)
|
||||
throw new ArgumentNullException(nameof(pointer));
|
||||
|
||||
// Ignore if not added.
|
||||
var pointerIndex = m_Pointers.IndexOfReference(pointer, m_NumPointers);
|
||||
if (pointerIndex == -1)
|
||||
return;
|
||||
|
||||
// Cancel all ongoing touches from the pointer.
|
||||
for (var i = 0; i < m_Touches.Length; ++i)
|
||||
{
|
||||
var button = m_Touches[i];
|
||||
if (button != null && button.device != pointer)
|
||||
continue;
|
||||
|
||||
UpdateTouch(i, pointerIndex, TouchPhase.Canceled);
|
||||
}
|
||||
|
||||
// Remove from list.
|
||||
m_Pointers.EraseAtWithCapacity(ref m_NumPointers, pointerIndex);
|
||||
ArrayHelpers.EraseAt(ref m_CurrentPositions, pointerIndex);
|
||||
ArrayHelpers.EraseAt(ref m_CurrentDisplayIndices, pointerIndex);
|
||||
|
||||
// Re-enable the device (only in case it's still added to the system).
|
||||
if (pointer.added)
|
||||
InputSystem.EnableDevice(pointer);
|
||||
}
|
||||
|
||||
private unsafe void OnEvent(InputEventPtr eventPtr, InputDevice device)
|
||||
{
|
||||
if (device == simulatedTouchscreen)
|
||||
{
|
||||
// Avoid processing events queued by this simulation device
|
||||
return;
|
||||
}
|
||||
|
||||
var pointerIndex = m_Pointers.IndexOfReference(device, m_NumPointers);
|
||||
if (pointerIndex < 0)
|
||||
return;
|
||||
|
||||
var eventType = eventPtr.type;
|
||||
if (eventType != StateEvent.Type && eventType != DeltaStateEvent.Type)
|
||||
return;
|
||||
|
||||
////REVIEW: should we have specialized paths for MouseState and PenState here? (probably can only use for StateEvents)
|
||||
|
||||
Pointer pointer = m_Pointers[pointerIndex];
|
||||
|
||||
// Read pointer position.
|
||||
var positionControl = pointer.position;
|
||||
var positionStatePtr = positionControl.GetStatePtrFromStateEventUnchecked(eventPtr, eventType);
|
||||
if (positionStatePtr != null)
|
||||
m_CurrentPositions[pointerIndex] = positionControl.ReadValueFromState(positionStatePtr);
|
||||
|
||||
// Read display index.
|
||||
var displayIndexControl = pointer.displayIndex;
|
||||
var displayIndexStatePtr = displayIndexControl.GetStatePtrFromStateEventUnchecked(eventPtr, eventType);
|
||||
if (displayIndexStatePtr != null)
|
||||
m_CurrentDisplayIndices[pointerIndex] = displayIndexControl.ReadValueFromState(displayIndexStatePtr);
|
||||
|
||||
// End touches for which buttons are no longer pressed.
|
||||
////REVIEW: There must be a better way to do this
|
||||
for (var i = 0; i < m_Touches.Length; ++i)
|
||||
{
|
||||
var button = m_Touches[i];
|
||||
if (button == null || button.device != device)
|
||||
continue;
|
||||
|
||||
var buttonStatePtr = button.GetStatePtrFromStateEventUnchecked(eventPtr, eventType);
|
||||
if (buttonStatePtr == null)
|
||||
{
|
||||
// Button is not contained in event. If we do have a position update, issue
|
||||
// a move on the button's corresponding touch. This makes us deal with delta
|
||||
// events that only update pointer positions.
|
||||
if (positionStatePtr != null)
|
||||
UpdateTouch(i, pointerIndex, TouchPhase.Moved, eventPtr);
|
||||
}
|
||||
else if (button.ReadValueFromState(buttonStatePtr) < (ButtonControl.s_GlobalDefaultButtonPressPoint * ButtonControl.s_GlobalDefaultButtonReleaseThreshold))
|
||||
UpdateTouch(i, pointerIndex, TouchPhase.Ended, eventPtr);
|
||||
}
|
||||
|
||||
// Add/update touches for buttons that are pressed.
|
||||
foreach (var control in eventPtr.EnumerateControls(InputControlExtensions.Enumerate.IgnoreControlsInDefaultState, device))
|
||||
{
|
||||
if (!control.isButton)
|
||||
continue;
|
||||
|
||||
// Check if it's pressed.
|
||||
var buttonStatePtr = control.GetStatePtrFromStateEventUnchecked(eventPtr, eventType);
|
||||
Debug.Assert(buttonStatePtr != null, "Button returned from EnumerateControls() must be found in event");
|
||||
var value = 0f;
|
||||
control.ReadValueFromStateIntoBuffer(buttonStatePtr, UnsafeUtility.AddressOf(ref value), 4);
|
||||
if (value <= ButtonControl.s_GlobalDefaultButtonPressPoint)
|
||||
continue; // Not in default state but also not pressed.
|
||||
|
||||
// See if we have an ongoing touch for the button.
|
||||
var touchIndex = m_Touches.IndexOfReference(control);
|
||||
if (touchIndex < 0)
|
||||
{
|
||||
// No, so add it.
|
||||
touchIndex = m_Touches.IndexOfReference((ButtonControl)null);
|
||||
if (touchIndex >= 0) // If negative, we're at max touch count and can't add more.
|
||||
{
|
||||
m_Touches[touchIndex] = (ButtonControl)control;
|
||||
UpdateTouch(touchIndex, pointerIndex, TouchPhase.Began, eventPtr);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Yes, so update it.
|
||||
UpdateTouch(touchIndex, pointerIndex, TouchPhase.Moved, eventPtr);
|
||||
}
|
||||
}
|
||||
|
||||
eventPtr.handled = true;
|
||||
}
|
||||
|
||||
private void OnDeviceChange(InputDevice device, InputDeviceChange change)
|
||||
{
|
||||
// If someone removed our simulated touchscreen, disable touch simulation.
|
||||
if (device == simulatedTouchscreen && change == InputDeviceChange.Removed)
|
||||
{
|
||||
Disable();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (change)
|
||||
{
|
||||
case InputDeviceChange.Added:
|
||||
{
|
||||
if (device is Pointer pointer)
|
||||
{
|
||||
if (device is Touchscreen)
|
||||
return; ////TODO: decide what to do
|
||||
|
||||
AddPointer(pointer);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case InputDeviceChange.Removed:
|
||||
{
|
||||
if (device is Pointer pointer)
|
||||
RemovePointer(pointer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void OnEnable()
|
||||
{
|
||||
if (simulatedTouchscreen != null)
|
||||
{
|
||||
if (!simulatedTouchscreen.added)
|
||||
InputSystem.AddDevice(simulatedTouchscreen);
|
||||
}
|
||||
else
|
||||
{
|
||||
simulatedTouchscreen = InputSystem.GetDevice("Simulated Touchscreen") as Touchscreen;
|
||||
if (simulatedTouchscreen == null)
|
||||
simulatedTouchscreen = InputSystem.AddDevice<Touchscreen>("Simulated Touchscreen");
|
||||
}
|
||||
|
||||
if (m_Touches == null)
|
||||
m_Touches = new ButtonControl[simulatedTouchscreen.touches.Count];
|
||||
|
||||
if (m_TouchIds == null)
|
||||
m_TouchIds = new int[simulatedTouchscreen.touches.Count];
|
||||
|
||||
foreach (var device in InputSystem.devices)
|
||||
OnDeviceChange(device, InputDeviceChange.Added);
|
||||
|
||||
if (m_OnDeviceChange == null)
|
||||
m_OnDeviceChange = OnDeviceChange;
|
||||
if (m_OnEvent == null)
|
||||
m_OnEvent = OnEvent;
|
||||
|
||||
InputSystem.onDeviceChange += m_OnDeviceChange;
|
||||
InputSystem.onEvent += m_OnEvent;
|
||||
}
|
||||
|
||||
protected void OnDisable()
|
||||
{
|
||||
if (simulatedTouchscreen != null && simulatedTouchscreen.added)
|
||||
InputSystem.RemoveDevice(simulatedTouchscreen);
|
||||
|
||||
// Re-enable all pointers we disabled.
|
||||
for (var i = 0; i < m_NumPointers; ++i)
|
||||
InputSystem.EnableDevice(m_Pointers[i]);
|
||||
|
||||
m_Pointers.Clear(m_NumPointers);
|
||||
m_Touches.Clear();
|
||||
|
||||
m_NumPointers = 0;
|
||||
m_LastTouchId = 0;
|
||||
|
||||
InputSystem.onDeviceChange -= m_OnDeviceChange;
|
||||
InputSystem.onEvent -= m_OnEvent;
|
||||
}
|
||||
|
||||
private unsafe void UpdateTouch(int touchIndex, int pointerIndex, TouchPhase phase, InputEventPtr eventPtr = default)
|
||||
{
|
||||
Vector2 position = m_CurrentPositions[pointerIndex];
|
||||
Debug.Assert(m_CurrentDisplayIndices[pointerIndex] <= byte.MaxValue, "Display index was larger than expected");
|
||||
byte displayIndex = (byte)m_CurrentDisplayIndices[pointerIndex];
|
||||
|
||||
// We need to partially set TouchState in a similar way that the Native side would do, but deriving that
|
||||
// data from the Pointer events.
|
||||
// The handling of the remaining fields is done by the Touchscreen.OnStateEvent() callback.
|
||||
var touch = new TouchState
|
||||
{
|
||||
phase = phase,
|
||||
position = position,
|
||||
displayIndex = displayIndex
|
||||
};
|
||||
|
||||
if (phase == TouchPhase.Began)
|
||||
{
|
||||
touch.startTime = eventPtr.valid ? eventPtr.time : InputState.currentTime;
|
||||
touch.startPosition = position;
|
||||
touch.touchId = ++m_LastTouchId;
|
||||
m_TouchIds[touchIndex] = m_LastTouchId;
|
||||
}
|
||||
else
|
||||
{
|
||||
touch.touchId = m_TouchIds[touchIndex];
|
||||
}
|
||||
|
||||
//NOTE: Processing these events still happen in the current frame.
|
||||
InputSystem.QueueStateEvent(simulatedTouchscreen, touch);
|
||||
|
||||
if (phase.IsEndedOrCanceled())
|
||||
{
|
||||
m_Touches[touchIndex] = null;
|
||||
}
|
||||
}
|
||||
|
||||
[NonSerialized] private int m_NumPointers;
|
||||
[NonSerialized] private Pointer[] m_Pointers;
|
||||
[NonSerialized] private Vector2[] m_CurrentPositions;
|
||||
[NonSerialized] private int[] m_CurrentDisplayIndices;
|
||||
[NonSerialized] private ButtonControl[] m_Touches;
|
||||
[NonSerialized] private int[] m_TouchIds;
|
||||
|
||||
[NonSerialized] private int m_LastTouchId;
|
||||
[NonSerialized] private Action<InputDevice, InputDeviceChange> m_OnDeviceChange;
|
||||
[NonSerialized] private Action<InputEventPtr, InputDevice> m_OnEvent;
|
||||
|
||||
internal static TouchSimulation s_Instance;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
static TouchSimulation()
|
||||
{
|
||||
// We're a MonoBehaviour so our cctor may get called as part of the MonoBehaviour being
|
||||
// created. We don't want to trigger InputSystem initialization from there so delay-execute
|
||||
// the code here.
|
||||
EditorApplication.delayCall +=
|
||||
() =>
|
||||
{
|
||||
InputSystem.onSettingsChange += OnSettingsChanged;
|
||||
InputSystem.onBeforeUpdate += ReEnableAfterDomainReload;
|
||||
};
|
||||
}
|
||||
|
||||
private static void ReEnableAfterDomainReload()
|
||||
{
|
||||
OnSettingsChanged();
|
||||
InputSystem.onBeforeUpdate -= ReEnableAfterDomainReload;
|
||||
}
|
||||
|
||||
private static void OnSettingsChanged()
|
||||
{
|
||||
if (InputEditorUserSettings.simulateTouch)
|
||||
Enable();
|
||||
else
|
||||
Disable();
|
||||
}
|
||||
|
||||
[CustomEditor(typeof(TouchSimulation))]
|
||||
private class TouchSimulationEditor : UnityEditor.Editor
|
||||
{
|
||||
public void OnDisable()
|
||||
{
|
||||
new InputComponentEditorAnalytic(InputSystemComponent.TouchSimulation).Send();
|
||||
}
|
||||
}
|
||||
|
||||
#endif // UNITY_EDITOR
|
||||
|
||||
////TODO: Remove IInputStateChangeMonitor from this class when we can break the API
|
||||
void IInputStateChangeMonitor.NotifyControlStateChanged(InputControl control, double time, InputEventPtr eventPtr, long monitorIndex)
|
||||
{
|
||||
}
|
||||
|
||||
void IInputStateChangeMonitor.NotifyTimerExpired(InputControl control, double time, long monitorIndex, int timerIndex)
|
||||
{
|
||||
}
|
||||
|
||||
// Disable warnings about unused parameters.
|
||||
#pragma warning disable CA1801
|
||||
|
||||
////TODO: [Obsolete]
|
||||
protected void InstallStateChangeMonitors(int startIndex = 0)
|
||||
{
|
||||
}
|
||||
|
||||
////TODO: [Obsolete]
|
||||
protected void OnSourceControlChangedValue(InputControl control, double time, InputEventPtr eventPtr,
|
||||
long sourceDeviceAndButtonIndex)
|
||||
{
|
||||
}
|
||||
|
||||
////TODO: [Obsolete]
|
||||
protected void UninstallStateChangeMonitors(int startIndex = 0)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e31057ef324a04478bb4f7d469b3cdc0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a71161af1440e4b1fbbefefb3aaffce0
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8806f46699a44c39a6a66c5d07772871
|
||||
timeCreated: 1510872623
|
@@ -0,0 +1,233 @@
|
||||
#if UNITY_EDITOR
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEditor.IMGUI.Controls;
|
||||
using UnityEngine.InputSystem.Layouts;
|
||||
using UnityEngine.InputSystem.LowLevel;
|
||||
|
||||
////TODO: use two columns for treeview and separate name and value
|
||||
|
||||
namespace UnityEngine.InputSystem.HID.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// A window that dumps a raw HID descriptor in a tree view.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Not specific to InputDevices of type <see cref="HID"/> so that it can work with
|
||||
/// any <see cref="InputDevice"/> created for a device using the "HID" interface.
|
||||
/// </remarks>
|
||||
internal class HIDDescriptorWindow : EditorWindow, ISerializationCallbackReceiver
|
||||
{
|
||||
public static void CreateOrShowExisting(int deviceId, InputDeviceDescription deviceDescription)
|
||||
{
|
||||
// See if we have an existing window for the device and if so pop it
|
||||
// in front.
|
||||
if (s_OpenWindows != null)
|
||||
{
|
||||
for (var i = 0; i < s_OpenWindows.Count; ++i)
|
||||
{
|
||||
var existingWindow = s_OpenWindows[i];
|
||||
if (existingWindow.m_DeviceId == deviceId)
|
||||
{
|
||||
existingWindow.Show();
|
||||
existingWindow.Focus();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No, so create a new one.
|
||||
var window = CreateInstance<HIDDescriptorWindow>();
|
||||
window.InitializeWith(deviceId, deviceDescription);
|
||||
window.minSize = new Vector2(270, 200);
|
||||
window.Show();
|
||||
window.titleContent = new GUIContent("HID Descriptor");
|
||||
}
|
||||
|
||||
public void Awake()
|
||||
{
|
||||
AddToList();
|
||||
}
|
||||
|
||||
public void OnDestroy()
|
||||
{
|
||||
RemoveFromList();
|
||||
}
|
||||
|
||||
public void OnGUI()
|
||||
{
|
||||
if (!m_Initialized)
|
||||
InitializeWith(m_DeviceId, m_DeviceDescription);
|
||||
|
||||
GUILayout.BeginHorizontal(EditorStyles.toolbar);
|
||||
GUILayout.Label(m_Label, GUILayout.MinWidth(100), GUILayout.ExpandWidth(true));
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
var rect = EditorGUILayout.GetControlRect(GUILayout.ExpandHeight(true));
|
||||
m_TreeView.OnGUI(rect);
|
||||
}
|
||||
|
||||
private void InitializeWith(int deviceId, InputDeviceDescription deviceDescription)
|
||||
{
|
||||
m_DeviceId = deviceId;
|
||||
m_DeviceDescription = deviceDescription;
|
||||
m_Initialized = true;
|
||||
|
||||
// Set up tree view for HID descriptor.
|
||||
var hidDescriptor = HID.ReadHIDDeviceDescriptor(ref m_DeviceDescription,
|
||||
(ref InputDeviceCommand command) => InputRuntime.s_Instance.DeviceCommand(m_DeviceId, ref command));
|
||||
if (m_TreeViewState == null)
|
||||
m_TreeViewState = new TreeViewState();
|
||||
m_TreeView = new HIDDescriptorTreeView(m_TreeViewState, hidDescriptor);
|
||||
m_TreeView.SetExpanded(1, true);
|
||||
|
||||
m_Label = new GUIContent(
|
||||
$"HID Descriptor for '{deviceDescription.manufacturer} {deviceDescription.product}'");
|
||||
}
|
||||
|
||||
[NonSerialized] private bool m_Initialized;
|
||||
[NonSerialized] private HIDDescriptorTreeView m_TreeView;
|
||||
[NonSerialized] private GUIContent m_Label;
|
||||
|
||||
[SerializeField] private int m_DeviceId;
|
||||
[SerializeField] private InputDeviceDescription m_DeviceDescription;
|
||||
[SerializeField] private TreeViewState m_TreeViewState;
|
||||
|
||||
private void AddToList()
|
||||
{
|
||||
if (s_OpenWindows == null)
|
||||
s_OpenWindows = new List<HIDDescriptorWindow>();
|
||||
if (!s_OpenWindows.Contains(this))
|
||||
s_OpenWindows.Add(this);
|
||||
}
|
||||
|
||||
private void RemoveFromList()
|
||||
{
|
||||
if (s_OpenWindows != null)
|
||||
s_OpenWindows.Remove(this);
|
||||
}
|
||||
|
||||
private static List<HIDDescriptorWindow> s_OpenWindows;
|
||||
|
||||
private class HIDDescriptorTreeView : TreeView
|
||||
{
|
||||
private HID.HIDDeviceDescriptor m_Descriptor;
|
||||
|
||||
public HIDDescriptorTreeView(TreeViewState state, HID.HIDDeviceDescriptor descriptor)
|
||||
: base(state)
|
||||
{
|
||||
m_Descriptor = descriptor;
|
||||
Reload();
|
||||
}
|
||||
|
||||
protected override TreeViewItem BuildRoot()
|
||||
{
|
||||
var id = 0;
|
||||
|
||||
var root = new TreeViewItem
|
||||
{
|
||||
id = id++,
|
||||
depth = -1
|
||||
};
|
||||
|
||||
var item = BuildDeviceItem(m_Descriptor, ref id);
|
||||
root.AddChild(item);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private TreeViewItem BuildDeviceItem(HID.HIDDeviceDescriptor device, ref int id)
|
||||
{
|
||||
var item = new TreeViewItem
|
||||
{
|
||||
id = id++,
|
||||
depth = 0,
|
||||
displayName = "Device"
|
||||
};
|
||||
|
||||
AddChild(item, string.Format("Vendor ID: 0x{0:X}", device.vendorId), ref id);
|
||||
AddChild(item, string.Format("Product ID: 0x{0:X}", device.productId), ref id);
|
||||
AddChild(item, string.Format("Usage Page: 0x{0:X} ({1})", (uint)device.usagePage, device.usagePage), ref id);
|
||||
AddChild(item, string.Format("Usage: 0x{0:X}", device.usage), ref id);
|
||||
AddChild(item, "Input Report Size: " + device.inputReportSize, ref id);
|
||||
AddChild(item, "Output Report Size: " + device.outputReportSize, ref id);
|
||||
AddChild(item, "Feature Report Size: " + device.featureReportSize, ref id);
|
||||
|
||||
// Elements.
|
||||
if (device.elements != null)
|
||||
{
|
||||
var elementCount = device.elements.Length;
|
||||
var elements = AddChild(item, elementCount + " Elements", ref id);
|
||||
for (var i = 0; i < elementCount; ++i)
|
||||
BuildElementItem(i, elements, device.elements[i], ref id);
|
||||
}
|
||||
else
|
||||
AddChild(item, "0 Elements", ref id);
|
||||
|
||||
////TODO: collections
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
private TreeViewItem BuildElementItem(int index, TreeViewItem parent, HID.HIDElementDescriptor element, ref int id)
|
||||
{
|
||||
var item = AddChild(parent, string.Format("Element {0} ({1})", index, element.reportType), ref id);
|
||||
|
||||
string usagePageString = HID.UsagePageToString(element.usagePage);
|
||||
string usageString = HID.UsageToString(element.usagePage, element.usage);
|
||||
|
||||
AddChild(item, string.Format("Usage Page: 0x{0:X} ({1})", (uint)element.usagePage, usagePageString), ref id);
|
||||
if (usageString != null)
|
||||
AddChild(item, string.Format("Usage: 0x{0:X} ({1})", element.usage, usageString), ref id);
|
||||
else
|
||||
AddChild(item, string.Format("Usage: 0x{0:X}", element.usage), ref id);
|
||||
|
||||
AddChild(item, "Report Type: " + element.reportType, ref id);
|
||||
AddChild(item, "Report ID: " + element.reportId, ref id);
|
||||
AddChild(item, "Report Size in Bits: " + element.reportSizeInBits, ref id);
|
||||
AddChild(item, "Report Bit Offset: " + element.reportOffsetInBits, ref id);
|
||||
AddChild(item, "Collection Index: " + element.collectionIndex, ref id);
|
||||
AddChild(item, string.Format("Unit: {0:X}", element.unit), ref id);
|
||||
AddChild(item, string.Format("Unit Exponent: {0:X}", element.unitExponent), ref id);
|
||||
AddChild(item, "Logical Min: " + element.logicalMin, ref id);
|
||||
AddChild(item, "Logical Max: " + element.logicalMax, ref id);
|
||||
AddChild(item, "Physical Min: " + element.physicalMin, ref id);
|
||||
AddChild(item, "Physical Max: " + element.physicalMax, ref id);
|
||||
AddChild(item, "Has Null State?: " + element.hasNullState, ref id);
|
||||
AddChild(item, "Has Preferred State?: " + element.hasPreferredState, ref id);
|
||||
AddChild(item, "Is Array?: " + element.isArray, ref id);
|
||||
AddChild(item, "Is Non-Linear?: " + element.isNonLinear, ref id);
|
||||
AddChild(item, "Is Relative?: " + element.isRelative, ref id);
|
||||
AddChild(item, "Is Constant?: " + element.isConstant, ref id);
|
||||
AddChild(item, "Is Wrapping?: " + element.isWrapping, ref id);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
private TreeViewItem AddChild(TreeViewItem parent, string displayName, ref int id)
|
||||
{
|
||||
var item = new TreeViewItem
|
||||
{
|
||||
id = id++,
|
||||
depth = parent.depth + 1,
|
||||
displayName = displayName
|
||||
};
|
||||
|
||||
parent.AddChild(item);
|
||||
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnBeforeSerialize()
|
||||
{
|
||||
}
|
||||
|
||||
public void OnAfterDeserialize()
|
||||
{
|
||||
AddToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // UNITY_EDITOR
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ae42a3986cf84fe2aa1df4aa984dd806
|
||||
timeCreated: 1513554255
|
@@ -0,0 +1,480 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
////TODO: array support
|
||||
////TODO: delimiter support
|
||||
////TODO: designator support
|
||||
|
||||
#pragma warning disable CS0649
|
||||
namespace UnityEngine.InputSystem.HID
|
||||
{
|
||||
/// <summary>
|
||||
/// Turns binary HID descriptors into <see cref="HID.HIDDeviceDescriptor"/> instances.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For information about the format, see the <a href="http://www.usb.org/developers/hidpage/HID1_11.pdf">
|
||||
/// Device Class Definition for Human Interface Devices</a> section 6.2.2.
|
||||
/// </remarks>
|
||||
internal static class HIDParser
|
||||
{
|
||||
/// <summary>
|
||||
/// Parse a HID report descriptor as defined by section 6.2.2 of the
|
||||
/// <a href="http://www.usb.org/developers/hidpage/HID1_11.pdf">HID
|
||||
/// specification</a> and add the elements and collections from the
|
||||
/// descriptor to the given <paramref name="deviceDescriptor"/>.
|
||||
/// </summary>
|
||||
/// <param name="buffer">Buffer containing raw HID report descriptor.</param>
|
||||
/// <param name="deviceDescriptor">HID device descriptor to complete with the information
|
||||
/// from the report descriptor. Elements and collections will get added to this descriptor.</param>
|
||||
/// <returns>True if the report descriptor was successfully parsed.</returns>
|
||||
/// <remarks>
|
||||
/// Will also set <see cref="HID.HIDDeviceDescriptor.inputReportSize"/>,
|
||||
/// <see cref="HID.HIDDeviceDescriptor.outputReportSize"/>, and
|
||||
/// <see cref="HID.HIDDeviceDescriptor.featureReportSize"/>.
|
||||
/// </remarks>
|
||||
public static unsafe bool ParseReportDescriptor(byte[] buffer, ref HID.HIDDeviceDescriptor deviceDescriptor)
|
||||
{
|
||||
if (buffer == null)
|
||||
throw new ArgumentNullException(nameof(buffer));
|
||||
|
||||
fixed(byte* bufferPtr = buffer)
|
||||
{
|
||||
return ParseReportDescriptor(bufferPtr, buffer.Length, ref deviceDescriptor);
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe static bool ParseReportDescriptor(byte* bufferPtr, int bufferLength, ref HID.HIDDeviceDescriptor deviceDescriptor)
|
||||
{
|
||||
// Item state.
|
||||
var localItemState = new HIDItemStateLocal();
|
||||
var globalItemState = new HIDItemStateGlobal();
|
||||
|
||||
// Lists where we accumulate the data from the HID items.
|
||||
var reports = new List<HIDReportData>();
|
||||
var elements = new List<HID.HIDElementDescriptor>();
|
||||
var collections = new List<HID.HIDCollectionDescriptor>();
|
||||
var currentCollection = -1;
|
||||
|
||||
// Parse the linear list of items.
|
||||
var endPtr = bufferPtr + bufferLength;
|
||||
var currentPtr = bufferPtr;
|
||||
while (currentPtr < endPtr)
|
||||
{
|
||||
var firstByte = *currentPtr;
|
||||
|
||||
////TODO
|
||||
if (firstByte == 0xFE)
|
||||
throw new NotImplementedException("long item support");
|
||||
|
||||
// Read item header.
|
||||
var itemSize = (byte)(firstByte & 0x3);
|
||||
var itemTypeAndTag = (byte)(firstByte & 0xFC);
|
||||
++currentPtr;
|
||||
|
||||
// Process item.
|
||||
switch (itemTypeAndTag)
|
||||
{
|
||||
// ------------ Global Items --------------
|
||||
// These set item state permanently until it is reset.
|
||||
|
||||
// Usage Page
|
||||
case (int)HIDItemTypeAndTag.UsagePage:
|
||||
globalItemState.usagePage = ReadData(itemSize, currentPtr, endPtr);
|
||||
break;
|
||||
|
||||
// Report Count
|
||||
case (int)HIDItemTypeAndTag.ReportCount:
|
||||
globalItemState.reportCount = ReadData(itemSize, currentPtr, endPtr);
|
||||
break;
|
||||
|
||||
// Report Size
|
||||
case (int)HIDItemTypeAndTag.ReportSize:
|
||||
globalItemState.reportSize = ReadData(itemSize, currentPtr, endPtr);
|
||||
break;
|
||||
|
||||
// Report ID
|
||||
case (int)HIDItemTypeAndTag.ReportID:
|
||||
globalItemState.reportId = ReadData(itemSize, currentPtr, endPtr);
|
||||
break;
|
||||
|
||||
// Logical Minimum
|
||||
case (int)HIDItemTypeAndTag.LogicalMinimum:
|
||||
globalItemState.logicalMinimum = ReadData(itemSize, currentPtr, endPtr);
|
||||
break;
|
||||
|
||||
// Logical Maximum
|
||||
case (int)HIDItemTypeAndTag.LogicalMaximum:
|
||||
globalItemState.logicalMaximum = ReadData(itemSize, currentPtr, endPtr);
|
||||
break;
|
||||
|
||||
// Physical Minimum
|
||||
case (int)HIDItemTypeAndTag.PhysicalMinimum:
|
||||
globalItemState.physicalMinimum = ReadData(itemSize, currentPtr, endPtr);
|
||||
break;
|
||||
|
||||
// Physical Maximum
|
||||
case (int)HIDItemTypeAndTag.PhysicalMaximum:
|
||||
globalItemState.physicalMaximum = ReadData(itemSize, currentPtr, endPtr);
|
||||
break;
|
||||
|
||||
// Unit Exponent
|
||||
case (int)HIDItemTypeAndTag.UnitExponent:
|
||||
globalItemState.unitExponent = ReadData(itemSize, currentPtr, endPtr);
|
||||
break;
|
||||
|
||||
// Unit
|
||||
case (int)HIDItemTypeAndTag.Unit:
|
||||
globalItemState.unit = ReadData(itemSize, currentPtr, endPtr);
|
||||
break;
|
||||
|
||||
// ------------ Local Items --------------
|
||||
// These set the state for the very next elements to be generated.
|
||||
|
||||
// Usage
|
||||
case (int)HIDItemTypeAndTag.Usage:
|
||||
localItemState.SetUsage(ReadData(itemSize, currentPtr, endPtr));
|
||||
break;
|
||||
|
||||
// Usage Minimum
|
||||
case (int)HIDItemTypeAndTag.UsageMinimum:
|
||||
localItemState.usageMinimum = ReadData(itemSize, currentPtr, endPtr);
|
||||
break;
|
||||
|
||||
// Usage Maximum
|
||||
case (int)HIDItemTypeAndTag.UsageMaximum:
|
||||
localItemState.usageMaximum = ReadData(itemSize, currentPtr, endPtr);
|
||||
break;
|
||||
|
||||
// ------------ Main Items --------------
|
||||
// These emit things into the descriptor based on the local and global item state.
|
||||
|
||||
// Collection
|
||||
case (int)HIDItemTypeAndTag.Collection:
|
||||
|
||||
// Start new collection.
|
||||
var parentCollection = currentCollection;
|
||||
currentCollection = collections.Count;
|
||||
collections.Add(new HID.HIDCollectionDescriptor
|
||||
{
|
||||
type = (HID.HIDCollectionType)ReadData(itemSize, currentPtr, endPtr),
|
||||
parent = parentCollection,
|
||||
usagePage = globalItemState.GetUsagePage(0, ref localItemState),
|
||||
usage = localItemState.GetUsage(0),
|
||||
firstChild = elements.Count
|
||||
});
|
||||
|
||||
HIDItemStateLocal.Reset(ref localItemState);
|
||||
break;
|
||||
|
||||
// EndCollection
|
||||
case (int)HIDItemTypeAndTag.EndCollection:
|
||||
if (currentCollection == -1)
|
||||
return false;
|
||||
|
||||
// Close collection.
|
||||
var collection = collections[currentCollection];
|
||||
collection.childCount = elements.Count - collection.firstChild;
|
||||
collections[currentCollection] = collection;
|
||||
|
||||
// Switch back to parent collection (if any).
|
||||
currentCollection = collection.parent;
|
||||
|
||||
HIDItemStateLocal.Reset(ref localItemState);
|
||||
break;
|
||||
|
||||
// Input/Output/Feature
|
||||
case (int)HIDItemTypeAndTag.Input:
|
||||
case (int)HIDItemTypeAndTag.Output:
|
||||
case (int)HIDItemTypeAndTag.Feature:
|
||||
|
||||
// Determine report type.
|
||||
var reportType = itemTypeAndTag == (int)HIDItemTypeAndTag.Input
|
||||
? HID.HIDReportType.Input
|
||||
: itemTypeAndTag == (int)HIDItemTypeAndTag.Output
|
||||
? HID.HIDReportType.Output
|
||||
: HID.HIDReportType.Feature;
|
||||
|
||||
// Find report.
|
||||
var reportIndex = HIDReportData.FindOrAddReport(globalItemState.reportId, reportType, reports);
|
||||
var report = reports[reportIndex];
|
||||
|
||||
// If we have a report ID, then reports start with an 8 byte report ID.
|
||||
// Shift our offsets accordingly.
|
||||
if (report.currentBitOffset == 0 && globalItemState.reportId.HasValue)
|
||||
report.currentBitOffset = 8;
|
||||
|
||||
// Add elements to report.
|
||||
var reportCount = globalItemState.reportCount.GetValueOrDefault(1);
|
||||
var flags = ReadData(itemSize, currentPtr, endPtr);
|
||||
for (var i = 0; i < reportCount; ++i)
|
||||
{
|
||||
var element = new HID.HIDElementDescriptor
|
||||
{
|
||||
usage = localItemState.GetUsage(i) & 0xFFFF, // Mask off usage page, if set.
|
||||
usagePage = globalItemState.GetUsagePage(i, ref localItemState),
|
||||
reportType = reportType,
|
||||
reportSizeInBits = globalItemState.reportSize.GetValueOrDefault(8),
|
||||
reportOffsetInBits = report.currentBitOffset,
|
||||
reportId = globalItemState.reportId.GetValueOrDefault(1),
|
||||
flags = (HID.HIDElementFlags)flags,
|
||||
logicalMin = globalItemState.logicalMinimum.GetValueOrDefault(0),
|
||||
logicalMax = globalItemState.logicalMaximum.GetValueOrDefault(0),
|
||||
physicalMin = globalItemState.GetPhysicalMin(),
|
||||
physicalMax = globalItemState.GetPhysicalMax(),
|
||||
unitExponent = globalItemState.unitExponent.GetValueOrDefault(0),
|
||||
unit = globalItemState.unit.GetValueOrDefault(0),
|
||||
};
|
||||
report.currentBitOffset += element.reportSizeInBits;
|
||||
elements.Add(element);
|
||||
}
|
||||
reports[reportIndex] = report;
|
||||
|
||||
HIDItemStateLocal.Reset(ref localItemState);
|
||||
break;
|
||||
}
|
||||
|
||||
if (itemSize == 3)
|
||||
currentPtr += 4;
|
||||
else
|
||||
currentPtr += itemSize;
|
||||
}
|
||||
|
||||
deviceDescriptor.elements = elements.ToArray();
|
||||
deviceDescriptor.collections = collections.ToArray();
|
||||
|
||||
// Set usage and usage page on device descriptor to what's
|
||||
// on the toplevel application collection.
|
||||
foreach (var collection in collections)
|
||||
{
|
||||
if (collection.parent == -1 && collection.type == HID.HIDCollectionType.Application)
|
||||
{
|
||||
deviceDescriptor.usage = collection.usage;
|
||||
deviceDescriptor.usagePage = collection.usagePage;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private unsafe static int ReadData(int itemSize, byte* currentPtr, byte* endPtr)
|
||||
{
|
||||
if (itemSize == 0)
|
||||
return 0;
|
||||
|
||||
// Read byte.
|
||||
if (itemSize == 1)
|
||||
{
|
||||
if (currentPtr >= endPtr)
|
||||
return 0;
|
||||
return *currentPtr;
|
||||
}
|
||||
|
||||
// Read short.
|
||||
if (itemSize == 2)
|
||||
{
|
||||
if (currentPtr + 2 >= endPtr)
|
||||
return 0;
|
||||
var data1 = *currentPtr;
|
||||
var data2 = *(currentPtr + 1);
|
||||
return (data2 << 8) | data1;
|
||||
}
|
||||
|
||||
// Read int.
|
||||
if (itemSize == 3) // Item size 3 means 4 bytes!
|
||||
{
|
||||
if (currentPtr + 4 >= endPtr)
|
||||
return 0;
|
||||
|
||||
var data1 = *currentPtr;
|
||||
var data2 = *(currentPtr + 1);
|
||||
var data3 = *(currentPtr + 2);
|
||||
var data4 = *(currentPtr + 3);
|
||||
|
||||
return (data4 << 24) | (data3 << 24) | (data2 << 8) | data1;
|
||||
}
|
||||
|
||||
Debug.Assert(false, "Should not reach here");
|
||||
return 0;
|
||||
}
|
||||
|
||||
private struct HIDReportData
|
||||
{
|
||||
public int reportId;
|
||||
public HID.HIDReportType reportType;
|
||||
public int currentBitOffset;
|
||||
|
||||
public static int FindOrAddReport(int? reportId, HID.HIDReportType reportType, List<HIDReportData> reports)
|
||||
{
|
||||
var id = 1;
|
||||
if (reportId.HasValue)
|
||||
id = reportId.Value;
|
||||
|
||||
for (var i = 0; i < reports.Count; ++i)
|
||||
{
|
||||
if (reports[i].reportId == id && reports[i].reportType == reportType)
|
||||
return i;
|
||||
}
|
||||
|
||||
reports.Add(new HIDReportData
|
||||
{
|
||||
reportId = id,
|
||||
reportType = reportType
|
||||
});
|
||||
|
||||
return reports.Count - 1;
|
||||
}
|
||||
}
|
||||
|
||||
// All types and tags with size bits (low order two bits) masked out (i.e. being 0).
|
||||
private enum HIDItemTypeAndTag
|
||||
{
|
||||
Input = 0x80,
|
||||
Output = 0x90,
|
||||
Feature = 0xB0,
|
||||
Collection = 0xA0,
|
||||
EndCollection = 0xC0,
|
||||
UsagePage = 0x04,
|
||||
LogicalMinimum = 0x14,
|
||||
LogicalMaximum = 0x24,
|
||||
PhysicalMinimum = 0x34,
|
||||
PhysicalMaximum = 0x44,
|
||||
UnitExponent = 0x54,
|
||||
Unit = 0x64,
|
||||
ReportSize = 0x74,
|
||||
ReportID = 0x84,
|
||||
ReportCount = 0x94,
|
||||
Push = 0xA4,
|
||||
Pop = 0xB4,
|
||||
Usage = 0x08,
|
||||
UsageMinimum = 0x18,
|
||||
UsageMaximum = 0x28,
|
||||
DesignatorIndex = 0x38,
|
||||
DesignatorMinimum = 0x48,
|
||||
DesignatorMaximum = 0x58,
|
||||
StringIndex = 0x78,
|
||||
StringMinimum = 0x88,
|
||||
StringMaximum = 0x98,
|
||||
Delimiter = 0xA8,
|
||||
}
|
||||
|
||||
// State that needs to be defined for each main item separately.
|
||||
// See section 6.2.2.8
|
||||
private struct HIDItemStateLocal
|
||||
{
|
||||
public int? usage;
|
||||
public int? usageMinimum;
|
||||
public int? usageMaximum;
|
||||
public int? designatorIndex;
|
||||
public int? designatorMinimum;
|
||||
public int? designatorMaximum;
|
||||
public int? stringIndex;
|
||||
public int? stringMinimum;
|
||||
public int? stringMaximum;
|
||||
|
||||
public List<int> usageList;
|
||||
|
||||
// Wipe state but preserve usageList allocation.
|
||||
public static void Reset(ref HIDItemStateLocal state)
|
||||
{
|
||||
var usageList = state.usageList;
|
||||
state = new HIDItemStateLocal();
|
||||
if (usageList != null)
|
||||
{
|
||||
usageList.Clear();
|
||||
state.usageList = usageList;
|
||||
}
|
||||
}
|
||||
|
||||
// Usage can be set repeatedly to provide an enumeration of usages.
|
||||
public void SetUsage(int value)
|
||||
{
|
||||
if (usage.HasValue)
|
||||
{
|
||||
if (usageList == null)
|
||||
usageList = new List<int>();
|
||||
usageList.Add(usage.Value);
|
||||
}
|
||||
usage = value;
|
||||
}
|
||||
|
||||
// Get usage for Nth element in [0-reportCount] list.
|
||||
public int GetUsage(int index)
|
||||
{
|
||||
// If we have minimum and maximum usage, interpolate between that.
|
||||
if (usageMinimum.HasValue && usageMaximum.HasValue)
|
||||
{
|
||||
var min = usageMinimum.Value;
|
||||
var max = usageMaximum.Value;
|
||||
|
||||
var range = max - min;
|
||||
if (range < 0)
|
||||
return 0;
|
||||
if (index >= range)
|
||||
return max;
|
||||
return min + index;
|
||||
}
|
||||
|
||||
// If we have a list of usages, index into that.
|
||||
if (usageList != null && usageList.Count > 0)
|
||||
{
|
||||
var usageCount = usageList.Count;
|
||||
if (index >= usageCount)
|
||||
return usage.Value;
|
||||
|
||||
return usageList[index];
|
||||
}
|
||||
|
||||
if (usage.HasValue)
|
||||
return usage.Value;
|
||||
|
||||
////TODO: min/max
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// State that is carried over from main item to main item.
|
||||
// See section 6.2.2.7
|
||||
private struct HIDItemStateGlobal
|
||||
{
|
||||
public int? usagePage;
|
||||
public int? logicalMinimum;
|
||||
public int? logicalMaximum;
|
||||
public int? physicalMinimum;
|
||||
public int? physicalMaximum;
|
||||
public int? unitExponent;
|
||||
public int? unit;
|
||||
public int? reportSize;
|
||||
public int? reportCount;
|
||||
public int? reportId;
|
||||
|
||||
public HID.UsagePage GetUsagePage(int index, ref HIDItemStateLocal localItemState)
|
||||
{
|
||||
if (!usagePage.HasValue)
|
||||
{
|
||||
var usage = localItemState.GetUsage(index);
|
||||
return (HID.UsagePage)(usage >> 16);
|
||||
}
|
||||
|
||||
return (HID.UsagePage)usagePage.Value;
|
||||
}
|
||||
|
||||
public int GetPhysicalMin()
|
||||
{
|
||||
if (physicalMinimum == null || physicalMaximum == null ||
|
||||
(physicalMinimum.Value == 0 && physicalMaximum.Value == 0))
|
||||
return logicalMinimum.GetValueOrDefault(0);
|
||||
return physicalMinimum.Value;
|
||||
}
|
||||
|
||||
public int GetPhysicalMax()
|
||||
{
|
||||
if (physicalMinimum == null || physicalMaximum == null ||
|
||||
(physicalMinimum.Value == 0 && physicalMaximum.Value == 0))
|
||||
return logicalMaximum.GetValueOrDefault(0);
|
||||
return physicalMaximum.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a736049645e8c4781aedf679c2ee6be6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,154 @@
|
||||
using System.Linq;
|
||||
using UnityEngine.InputSystem.Utilities;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
using UnityEngine.InputSystem.Editor;
|
||||
using UnityEngine.InputSystem.HID.Editor;
|
||||
#endif
|
||||
|
||||
namespace UnityEngine.InputSystem.HID
|
||||
{
|
||||
using ShouldCreateHIDCallback = System.Func<HID.HIDDeviceDescriptor, bool?>;
|
||||
|
||||
/// <summary>
|
||||
/// Adds support for generic HID devices to the input system.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Even without this module, HIDs can be used on platforms where we
|
||||
/// support HID has a native backend (Windows and OSX, at the moment).
|
||||
/// However, each supported HID requires a layout specifically targeting
|
||||
/// it as a product.
|
||||
///
|
||||
/// What this module adds is the ability to turn any HID with usable
|
||||
/// controls into an InputDevice. It will make a best effort to figure
|
||||
/// out a suitable class for the device and will use the HID elements
|
||||
/// present in the HID report descriptor to populate the device.
|
||||
///
|
||||
/// If there is an existing product-specific layout for a HID, it will
|
||||
/// take precedence and HIDSupport will leave the device alone.
|
||||
/// </remarks>
|
||||
public static class HIDSupport
|
||||
{
|
||||
/// <summary>
|
||||
/// A pair of HID usage page and HID usage number.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Used to describe a HID usage for the <see cref="supportedHIDUsages"/> property.
|
||||
/// </remarks>
|
||||
public struct HIDPageUsage
|
||||
{
|
||||
/// <summary>
|
||||
/// The usage page.
|
||||
/// </summary>
|
||||
public HID.UsagePage page;
|
||||
|
||||
/// <summary>
|
||||
/// A number specifying the usage on the usage page.
|
||||
/// </summary>
|
||||
public int usage;
|
||||
|
||||
/// <summary>
|
||||
/// Create a HIDPageUsage struct by specifying a page and usage.
|
||||
/// </summary>
|
||||
public HIDPageUsage(HID.UsagePage page, int usage)
|
||||
{
|
||||
this.page = page;
|
||||
this.usage = usage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a HIDPageUsage struct from the GenericDesktop usage page by specifying the usage.
|
||||
/// </summary>
|
||||
public HIDPageUsage(HID.GenericDesktop usage)
|
||||
{
|
||||
page = HID.UsagePage.GenericDesktop;
|
||||
this.usage = (int)usage;
|
||||
}
|
||||
}
|
||||
|
||||
private static HIDPageUsage[] s_SupportedHIDUsages;
|
||||
|
||||
/// <summary>
|
||||
/// An array of HID usages the input is configured to support.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The input system will only create <see cref="InputDevice"/>s for HIDs with usages
|
||||
/// listed in this array. Any other HID will be ignored. This saves the input system from
|
||||
/// spending resources on creating layouts and devices for HIDs which are not supported or
|
||||
/// not usable for game input.
|
||||
///
|
||||
/// By default, this includes only <see cref="HID.GenericDesktop.Joystick"/>,
|
||||
/// <see cref="HID.GenericDesktop.Gamepad"/> and <see cref="HID.GenericDesktop.MultiAxisController"/>,
|
||||
/// but you can set this property to include any other HID usages.
|
||||
///
|
||||
/// Note that currently on macOS, the only HID usages which can be enabled are
|
||||
/// <see cref="HID.GenericDesktop.Joystick"/>, <see cref="HID.GenericDesktop.Gamepad"/>,
|
||||
/// <see cref="HID.GenericDesktop.MultiAxisController"/>, <see cref="HID.GenericDesktop.TabletPCControls"/>,
|
||||
/// and <see cref="HID.GenericDesktop.AssistiveControl"/>.
|
||||
/// </remarks>
|
||||
public static ReadOnlyArray<HIDPageUsage> supportedHIDUsages
|
||||
{
|
||||
get => s_SupportedHIDUsages;
|
||||
set
|
||||
{
|
||||
s_SupportedHIDUsages = value.ToArray();
|
||||
|
||||
// Add HIDs we now support.
|
||||
InputSystem.s_Manager.AddAvailableDevicesThatAreNowRecognized();
|
||||
|
||||
// Remove HIDs we no longer support.
|
||||
for (var i = 0; i < InputSystem.devices.Count; ++i)
|
||||
{
|
||||
var device = InputSystem.devices[i];
|
||||
if (device is HID hid && !s_SupportedHIDUsages.Contains(new HIDPageUsage(hid.hidDescriptor.usagePage, hid.hidDescriptor.usage)))
|
||||
{
|
||||
// Remove the entire generated layout. This will also remove all devices based on it.
|
||||
InputSystem.RemoveLayout(device.layout);
|
||||
--i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add support for generic HIDs to InputSystem.
|
||||
/// </summary>
|
||||
#if UNITY_DISABLE_DEFAULT_INPUT_PLUGIN_INITIALIZATION
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
static void Initialize()
|
||||
{
|
||||
s_SupportedHIDUsages = new[]
|
||||
{
|
||||
new HIDPageUsage(HID.GenericDesktop.Joystick),
|
||||
new HIDPageUsage(HID.GenericDesktop.Gamepad),
|
||||
new HIDPageUsage(HID.GenericDesktop.MultiAxisController),
|
||||
};
|
||||
|
||||
InputSystem.RegisterLayout<HID>();
|
||||
InputSystem.onFindLayoutForDevice += HID.OnFindLayoutForDevice;
|
||||
|
||||
// Add toolbar button to any devices using the "HID" interface. Opens
|
||||
// a windows to browse the HID descriptor of the device.
|
||||
#if UNITY_EDITOR
|
||||
InputDeviceDebuggerWindow.onToolbarGUI +=
|
||||
device =>
|
||||
{
|
||||
if (device.description.interfaceName == HID.kHIDInterface)
|
||||
{
|
||||
if (GUILayout.Button(s_HIDDescriptor, EditorStyles.toolbarButton))
|
||||
{
|
||||
HIDDescriptorWindow.CreateOrShowExisting(device.deviceId, device.description);
|
||||
}
|
||||
}
|
||||
};
|
||||
#endif
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private static readonly GUIContent s_HIDDescriptor = new GUIContent("HID Descriptor");
|
||||
#endif
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4f0b0cfb7c484e43823c1a038c97a5f3
|
||||
timeCreated: 1511133984
|
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 96500a17645c67442a0ccdca007e6d28
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,6 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine.Scripting;
|
||||
|
||||
[assembly: InternalsVisibleTo("UnityEngine.InputForUIVisualizer")]
|
||||
[assembly: InternalsVisibleTo("Unity.InputSystem.Tests")]
|
||||
[assembly: AlwaysLinkAssembly]
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 65c861c7ef9bee2479c728694e87ea45
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,139 @@
|
||||
#if UNITY_EDITOR && ENABLE_INPUT_SYSTEM && UNITY_2023_2_OR_NEWER
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine.InputSystem.Editor;
|
||||
|
||||
namespace UnityEngine.InputSystem.Plugins.InputForUI
|
||||
{
|
||||
// Unlike InputSystemProvider we want the verifier to register itself directly on domain reload in editor.
|
||||
[InitializeOnLoad]
|
||||
internal class InputActionAssetVerifier : ProjectWideActionsAsset.IInputActionAssetVerifier
|
||||
{
|
||||
public enum ReportPolicy
|
||||
{
|
||||
ReportAll,
|
||||
SuppressChildErrors
|
||||
}
|
||||
|
||||
// Note: This is intentionally not a constant to avoid dead code warning in tests while this remains
|
||||
// as a setting type of value.
|
||||
public static ReportPolicy DefaultReportPolicy = ReportPolicy.SuppressChildErrors;
|
||||
|
||||
static InputActionAssetVerifier()
|
||||
{
|
||||
// Register an InputActionAsset verifier for this plugin.
|
||||
ProjectWideActionsAsset.RegisterInputActionAssetVerifier(() => new InputActionAssetVerifier());
|
||||
|
||||
InputSystemProvider.SetOnRegisterActions((asset) => { ProjectWideActionsAsset.Verify(asset); });
|
||||
}
|
||||
|
||||
#region ProjectWideActionsAsset.IInputActionAssetVerifier
|
||||
|
||||
public void Verify(InputActionAsset asset,
|
||||
ProjectWideActionsAsset.IReportInputActionAssetVerificationErrors reporter)
|
||||
{
|
||||
// Note that we never cache this to guarantee we have the current configuration.
|
||||
var config = InputSystemProvider.Configuration.GetDefaultConfiguration();
|
||||
Verify(asset, ref config, reporter);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private struct Context
|
||||
{
|
||||
const string errorSuffix = "The Input System's runtime UI integration relies on certain required input action definitions, some of which are missing. This means some runtime UI input may not work correctly. See <a href=\"https://docs.unity3d.com/Packages/com.unity.inputsystem@latest/index.html?subfolder=/manual/UISupport.html#required-actions-for-ui\">Input System Manual - UI Support</a> for guidance on required actions for UI integration or see <a href=\"https://docs.unity3d.com/Packages/com.unity.inputsystem@latest/index.html?subfolder=/manual/ProjectWideActions.html#the-default-actions\">how to revert to defaults</a>.";
|
||||
|
||||
public Context(InputActionAsset asset,
|
||||
ProjectWideActionsAsset.IReportInputActionAssetVerificationErrors reporter,
|
||||
ReportPolicy policy)
|
||||
{
|
||||
this.asset = asset;
|
||||
this.missingPaths = new HashSet<string>();
|
||||
this.reporter = reporter;
|
||||
this.policy = policy;
|
||||
}
|
||||
|
||||
private string GetAssetReference()
|
||||
{
|
||||
var path = AssetDatabase.GetAssetPath(asset);
|
||||
return path ?? asset.name;
|
||||
}
|
||||
|
||||
private void ActionMapWarning(string actionMap, string problem)
|
||||
{
|
||||
reporter.Report($"InputActionMap with path '{actionMap}' in asset \"{GetAssetReference()}\" {problem}. {errorSuffix}");
|
||||
}
|
||||
|
||||
private void ActionWarning(string actionNameOrId, string problem)
|
||||
{
|
||||
reporter.Report($"InputAction with path '{actionNameOrId}' in asset \"{GetAssetReference()}\" {problem}. {errorSuffix}");
|
||||
}
|
||||
|
||||
public void Verify(string actionNameOrId, InputActionType actionType, string expectedControlType)
|
||||
{
|
||||
var action = asset.FindAction(actionNameOrId);
|
||||
if (action == null)
|
||||
{
|
||||
const string kCouldNotBeFound = "could not be found";
|
||||
|
||||
// Check if the map (if any) exists
|
||||
var noMapOrMapExists = true;
|
||||
var index = actionNameOrId.IndexOf('/');
|
||||
if (index > 0)
|
||||
{
|
||||
var path = actionNameOrId.Substring(0, index);
|
||||
if (asset.FindActionMap(path) == null)
|
||||
{
|
||||
if (missingPaths == null)
|
||||
missingPaths = new HashSet<string>(1);
|
||||
if (missingPaths.Add(path))
|
||||
ActionMapWarning(path, kCouldNotBeFound);
|
||||
noMapOrMapExists = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!noMapOrMapExists && policy == ReportPolicy.SuppressChildErrors)
|
||||
return;
|
||||
|
||||
ActionWarning(actionNameOrId, kCouldNotBeFound);
|
||||
}
|
||||
else if (action.bindings.Count == 0)
|
||||
ActionWarning(actionNameOrId, "do not have any configured bindings");
|
||||
else if (action.type != actionType)
|
||||
ActionWarning(actionNameOrId, $"has 'type' set to '{nameof(InputActionType)}.{action.type}', but '{nameof(InputActionType)}.{actionType}' was expected");
|
||||
else if (!string.IsNullOrEmpty(expectedControlType) && !string.IsNullOrEmpty(action.expectedControlType) && action.expectedControlType != expectedControlType)
|
||||
ActionWarning(actionNameOrId, $"has 'expectedControlType' set to '{action.expectedControlType}', but '{expectedControlType}' was expected");
|
||||
}
|
||||
|
||||
private readonly InputActionAsset asset;
|
||||
private readonly ProjectWideActionsAsset.IReportInputActionAssetVerificationErrors reporter;
|
||||
|
||||
private HashSet<string> missingPaths; // Avoids generating multiple warnings around missing map
|
||||
private ReportPolicy policy;
|
||||
}
|
||||
|
||||
private static void Verify(InputActionAsset asset, ref InputSystemProvider.Configuration config,
|
||||
ProjectWideActionsAsset.IReportInputActionAssetVerificationErrors reporter)
|
||||
{
|
||||
// Note:
|
||||
// PWA has initial state check true for "Point" action, DefaultActions do not, does it matter?
|
||||
//
|
||||
// Additionally note that "Submit" and "Cancel" are indirectly expected to be of Button action type.
|
||||
// This is not available in UI configuration, but InputActionRebindingExtensions suggests this.
|
||||
//
|
||||
// Additional "LeftClick" has initial state check set in PWA, but not "MiddleClick" and "RightClick".
|
||||
// Is this intentional? Are requirements different?
|
||||
var context = new Context(asset, reporter, DefaultReportPolicy);
|
||||
context.Verify(actionNameOrId: config.PointAction, actionType: InputActionType.PassThrough, expectedControlType: nameof(Vector2));
|
||||
context.Verify(actionNameOrId: config.MoveAction, actionType: InputActionType.PassThrough, expectedControlType: nameof(Vector2));
|
||||
context.Verify(actionNameOrId: config.SubmitAction, actionType: InputActionType.Button, expectedControlType: "Button");
|
||||
context.Verify(actionNameOrId: config.CancelAction, actionType: InputActionType.Button, expectedControlType: "Button");
|
||||
context.Verify(actionNameOrId: config.LeftClickAction, actionType: InputActionType.PassThrough, expectedControlType: "Button");
|
||||
context.Verify(actionNameOrId: config.MiddleClickAction, actionType: InputActionType.PassThrough, expectedControlType: "Button");
|
||||
context.Verify(actionNameOrId: config.RightClickAction, actionType: InputActionType.PassThrough, expectedControlType: "Button");
|
||||
context.Verify(actionNameOrId: config.ScrollWheelAction, actionType: InputActionType.PassThrough, expectedControlType: nameof(Vector2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // UNITY_EDITOR && ENABLE_INPUT_SYSTEM && UNITY_2023_2_OR_NEWER
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1fe35221cd5f4ca9b9df947d34e7e1f0
|
||||
timeCreated: 1712929907
|
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "Unity.InputSystem.ForUI",
|
||||
"references": [
|
||||
"Unity.InputSystem"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 09dcc9c0126b6b34ab2a877b8f2277f5
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d6965b068653ebe45986cb10b38854d1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1c597fa0f94a3d54b9c0e85f56e5f821
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user