This commit is contained in:
2025-01-17 13:10:42 +01:00
commit 4536213c91
15115 changed files with 1442174 additions and 0 deletions

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e5769f689f15dce4a9f92aff27659c54
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 15fa4fb6e47739e43b36a70fce024b6f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a7c6472eb810ba445be34c69325e4fea
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ede17f8b7e9dfcf42b560545a8102eeb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 830105192d35a17469c56711468d1e8f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0c8442e8094ce974598eacc0302ddabb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 6eefa91e7a89647338245ee3818141d1
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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)
{
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: df88ce51eaa14f2f8975023f7f45d2d9
timeCreated: 1511133490

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c7225419e3d446c5982afd5ba9cce319
timeCreated: 1517282565

View File

@@ -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
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: fa26399d579948dd90c6ff298cdb0570
timeCreated: 1511133448

View File

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

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0e235e01f7e6408f87e899ac373c85d1
timeCreated: 1517282788

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ef4b66ea93dd243a783c32ab1da39fc4
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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 &lt; 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()");
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3cf42e49077e442fd8b9dc29c328d345
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 71f3c0e630f0148b496f565843b5b272
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6261dd2a83757498188548c57e2492bf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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()
{
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4cc0c6a7fa1c14a518fec64497f4937f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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)
{
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e31057ef324a04478bb4f7d469b3cdc0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a71161af1440e4b1fbbefefb3aaffce0
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8806f46699a44c39a6a66c5d07772871
timeCreated: 1510872623

View File

@@ -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

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ae42a3986cf84fe2aa1df4aa984dd806
timeCreated: 1513554255

View File

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

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a736049645e8c4781aedf679c2ee6be6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 4f0b0cfb7c484e43823c1a038c97a5f3
timeCreated: 1511133984

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 96500a17645c67442a0ccdca007e6d28
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,6 @@
using System.Runtime.CompilerServices;
using UnityEngine.Scripting;
[assembly: InternalsVisibleTo("UnityEngine.InputForUIVisualizer")]
[assembly: InternalsVisibleTo("Unity.InputSystem.Tests")]
[assembly: AlwaysLinkAssembly]

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 65c861c7ef9bee2479c728694e87ea45
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1fe35221cd5f4ca9b9df947d34e7e1f0
timeCreated: 1712929907

View File

@@ -0,0 +1,15 @@
{
"name": "Unity.InputSystem.ForUI",
"references": [
"Unity.InputSystem"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 09dcc9c0126b6b34ab2a877b8f2277f5
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d6965b068653ebe45986cb10b38854d1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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