289 lines
12 KiB
C#
289 lines
12 KiB
C#
#if UNITY_EDITOR || UNITY_STANDALONE_LINUX
|
|
using System;
|
|
using UnityEngine.InputSystem.LowLevel;
|
|
using UnityEngine.InputSystem.Utilities;
|
|
using System.Text;
|
|
using UnityEngine.InputSystem.Layouts;
|
|
|
|
namespace UnityEngine.InputSystem.Linux
|
|
{
|
|
[Serializable]
|
|
internal class SDLLayoutBuilder
|
|
{
|
|
[SerializeField] private string m_ParentLayout;
|
|
[SerializeField] private SDLDeviceDescriptor m_Descriptor;
|
|
|
|
internal static string OnFindLayoutForDevice(ref InputDeviceDescription description, string matchedLayout,
|
|
InputDeviceExecuteCommandDelegate executeCommandDelegate)
|
|
{
|
|
if (description.interfaceName != LinuxSupport.kInterfaceName)
|
|
return null;
|
|
|
|
if (string.IsNullOrEmpty(description.capabilities))
|
|
return null;
|
|
|
|
// Try to parse the SDL descriptor.
|
|
SDLDeviceDescriptor deviceDescriptor;
|
|
try
|
|
{
|
|
deviceDescriptor = SDLDeviceDescriptor.FromJson(description.capabilities);
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
Debug.LogError($"{exception} while trying to parse descriptor for SDL device: {description.capabilities}");
|
|
return null;
|
|
}
|
|
|
|
if (deviceDescriptor == null)
|
|
return null;
|
|
|
|
string layoutName;
|
|
if (string.IsNullOrEmpty(description.manufacturer))
|
|
{
|
|
layoutName = $"{SanitizeName(description.interfaceName)}::{SanitizeName(description.product)}";
|
|
}
|
|
else
|
|
{
|
|
layoutName =
|
|
$"{SanitizeName(description.interfaceName)}::{SanitizeName(description.manufacturer)}::{SanitizeName(description.product)}";
|
|
}
|
|
|
|
var layout = new SDLLayoutBuilder { m_Descriptor = deviceDescriptor, m_ParentLayout = matchedLayout };
|
|
InputSystem.RegisterLayoutBuilder(() => layout.Build(), layoutName, matchedLayout);
|
|
|
|
return layoutName;
|
|
}
|
|
|
|
private static string SanitizeName(string originalName)
|
|
{
|
|
var stringLength = originalName.Length;
|
|
var sanitizedName = new StringBuilder(stringLength);
|
|
for (var i = 0; i < stringLength; i++)
|
|
{
|
|
var letter = originalName[i];
|
|
if (char.IsUpper(letter) || char.IsLower(letter) || char.IsDigit(letter))
|
|
sanitizedName.Append(letter);
|
|
}
|
|
return sanitizedName.ToString();
|
|
}
|
|
|
|
private static bool IsAxis(SDLFeatureDescriptor feature, SDLAxisUsage axis)
|
|
{
|
|
return feature.featureType == JoystickFeatureType.Axis
|
|
&& feature.usageHint == (int)axis;
|
|
}
|
|
|
|
private static void BuildStickFeature(ref InputControlLayout.Builder builder, SDLFeatureDescriptor xFeature, SDLFeatureDescriptor yFeature)
|
|
{
|
|
int byteOffset;
|
|
if (xFeature.offset <= yFeature.offset)
|
|
byteOffset = xFeature.offset;
|
|
else
|
|
byteOffset = yFeature.offset;
|
|
|
|
const string stickName = "Stick";
|
|
builder.AddControl(stickName)
|
|
.WithLayout("Stick")
|
|
.WithByteOffset((uint)byteOffset)
|
|
.WithSizeInBits((uint)xFeature.featureSize * 8 + (uint)yFeature.featureSize * 8)
|
|
.WithUsages(CommonUsages.Primary2DMotion);
|
|
|
|
builder.AddControl(stickName + "/x")
|
|
.WithFormat(InputStateBlock.FormatInt)
|
|
.WithByteOffset(0)
|
|
.WithSizeInBits((uint)xFeature.featureSize * 8)
|
|
.WithParameters("clamp=1,clampMin=-1,clampMax=1,scale,scaleFactor=65538");
|
|
|
|
builder.AddControl(stickName + "/y")
|
|
.WithFormat(InputStateBlock.FormatInt)
|
|
.WithByteOffset(4)
|
|
.WithSizeInBits((uint)xFeature.featureSize * 8)
|
|
.WithParameters("clamp=1,clampMin=-1,clampMax=1,scale,scaleFactor=65538,invert");
|
|
|
|
builder.AddControl(stickName + "/up")
|
|
.WithParameters("clamp=1,clampMin=-1,clampMax=0,scale,scaleFactor=65538,invert");
|
|
|
|
builder.AddControl(stickName + "/down")
|
|
.WithParameters("clamp=1,clampMin=0,clampMax=1,scale,scaleFactor=65538,invert=false");
|
|
|
|
builder.AddControl(stickName + "/left")
|
|
.WithParameters("clamp=1,clampMin=-1,clampMax=0,scale,scaleFactor=65538,invert");
|
|
|
|
builder.AddControl(stickName + "/right")
|
|
.WithParameters("clamp=1,clampMin=0,clampMax=1,scale,scaleFactor=65538");
|
|
}
|
|
|
|
private static bool IsHatX(SDLFeatureDescriptor feature)
|
|
{
|
|
return feature.featureType == JoystickFeatureType.Hat
|
|
&& (feature.usageHint == (int)SDLAxisUsage.Hat0X
|
|
|| feature.usageHint == (int)SDLAxisUsage.Hat1X
|
|
|| feature.usageHint == (int)SDLAxisUsage.Hat2X
|
|
|| feature.usageHint == (int)SDLAxisUsage.Hat3X);
|
|
}
|
|
|
|
private static bool IsHatY(SDLFeatureDescriptor feature)
|
|
{
|
|
return feature.featureType == JoystickFeatureType.Hat
|
|
&& (feature.usageHint == (int)SDLAxisUsage.Hat0Y
|
|
|| feature.usageHint == (int)SDLAxisUsage.Hat1Y
|
|
|| feature.usageHint == (int)SDLAxisUsage.Hat2Y
|
|
|| feature.usageHint == (int)SDLAxisUsage.Hat3Y);
|
|
}
|
|
|
|
private static int HatNumber(SDLFeatureDescriptor feature)
|
|
{
|
|
Debug.Assert(feature.featureType == JoystickFeatureType.Hat);
|
|
return 1 + (feature.usageHint - (int)SDLAxisUsage.Hat0X) / 2;
|
|
}
|
|
|
|
private static void BuildHatFeature(ref InputControlLayout.Builder builder, SDLFeatureDescriptor xFeature, SDLFeatureDescriptor yFeature)
|
|
{
|
|
Debug.Assert(xFeature.offset < yFeature.offset, "Order of features must be X followed by Y");
|
|
|
|
var hat = HatNumber(xFeature);
|
|
var hatName = hat > 1 ? $"Hat{hat}" : "Hat";
|
|
|
|
builder.AddControl(hatName)
|
|
.WithLayout("Dpad")
|
|
.WithByteOffset((uint)xFeature.offset)
|
|
.WithSizeInBits((uint)xFeature.featureSize * 8 + (uint)yFeature.featureSize * 8)
|
|
.WithUsages(CommonUsages.Hatswitch);
|
|
|
|
builder.AddControl(hatName + "/up")
|
|
.WithFormat(InputStateBlock.FormatInt)
|
|
.WithParameters("scale,scaleFactor=2147483647,clamp,clampMin=-1,clampMax=0,invert")
|
|
.WithByteOffset(4)
|
|
.WithBitOffset(0)
|
|
.WithSizeInBits((uint)yFeature.featureSize * 8);
|
|
|
|
builder.AddControl(hatName + "/down")
|
|
.WithFormat(InputStateBlock.FormatInt)
|
|
.WithParameters("scale,scaleFactor=2147483647,clamp,clampMin=0,clampMax=1")
|
|
.WithByteOffset(4)
|
|
.WithBitOffset(0)
|
|
.WithSizeInBits((uint)yFeature.featureSize * 8);
|
|
|
|
builder.AddControl(hatName + "/left")
|
|
.WithFormat(InputStateBlock.FormatInt)
|
|
.WithParameters("scale,scaleFactor=2147483647,clamp,clampMin=-1,clampMax=0,invert")
|
|
.WithByteOffset(0)
|
|
.WithBitOffset(0)
|
|
.WithSizeInBits((uint)xFeature.featureSize * 8);
|
|
|
|
builder.AddControl(hatName + "/right")
|
|
.WithFormat(InputStateBlock.FormatInt)
|
|
.WithParameters("scale,scaleFactor=2147483647,clamp,clampMin=0,clampMax=1")
|
|
.WithByteOffset(0)
|
|
.WithBitOffset(0)
|
|
.WithSizeInBits((uint)xFeature.featureSize * 8);
|
|
}
|
|
|
|
internal InputControlLayout Build()
|
|
{
|
|
var builder = new InputControlLayout.Builder
|
|
{
|
|
stateFormat = new FourCC('L', 'J', 'O', 'Y'),
|
|
extendsLayout = m_ParentLayout
|
|
};
|
|
|
|
for (var i = 0; i < m_Descriptor.controls.LengthSafe(); i++)
|
|
{
|
|
var feature = m_Descriptor.controls[i];
|
|
switch (feature.featureType)
|
|
{
|
|
case JoystickFeatureType.Axis:
|
|
{
|
|
var usage = (SDLAxisUsage)feature.usageHint;
|
|
var featureName = LinuxSupport.GetAxisNameFromUsage(usage);
|
|
var parameters = "scale,scaleFactor=65538,clamp=1,clampMin=-1,clampMax=1";
|
|
|
|
// If X is followed by Y, build a stick out of the two.
|
|
if (IsAxis(feature, SDLAxisUsage.X) && i + 1 < m_Descriptor.controls.Length)
|
|
{
|
|
var nextFeature = m_Descriptor.controls[i + 1];
|
|
if (IsAxis(nextFeature, SDLAxisUsage.Y))
|
|
{
|
|
BuildStickFeature(ref builder, feature, nextFeature);
|
|
++i;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (IsAxis(feature, SDLAxisUsage.Y))
|
|
parameters += ",invert";
|
|
|
|
var control = builder.AddControl(featureName)
|
|
.WithLayout("Analog")
|
|
.WithByteOffset((uint)feature.offset)
|
|
.WithFormat(InputStateBlock.FormatInt)
|
|
.WithParameters(parameters);
|
|
|
|
if (IsAxis(feature, SDLAxisUsage.RotateZ))
|
|
control.WithUsages(CommonUsages.Twist);
|
|
break;
|
|
}
|
|
|
|
case JoystickFeatureType.Ball:
|
|
{
|
|
//TODO
|
|
break;
|
|
}
|
|
|
|
case JoystickFeatureType.Button:
|
|
{
|
|
var usage = (SDLButtonUsage)feature.usageHint;
|
|
var featureName = LinuxSupport.GetButtonNameFromUsage(usage);
|
|
if (featureName != null)
|
|
{
|
|
builder.AddControl(featureName)
|
|
.WithLayout("Button")
|
|
.WithByteOffset((uint)feature.offset)
|
|
.WithBitOffset((uint)feature.bit)
|
|
.WithFormat(InputStateBlock.FormatBit);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case JoystickFeatureType.Hat:
|
|
{
|
|
var usage = (SDLAxisUsage)feature.usageHint;
|
|
var featureName = LinuxSupport.GetAxisNameFromUsage(usage);
|
|
var parameters = "scale,scaleFactor=2147483647,clamp=1,clampMin=-1,clampMax=1";
|
|
|
|
if (i + 1 < m_Descriptor.controls.Length)
|
|
{
|
|
var nextFeature = m_Descriptor.controls[i + 1];
|
|
if (IsHatY(nextFeature) && HatNumber(feature) == HatNumber(nextFeature))
|
|
{
|
|
BuildHatFeature(ref builder, feature, nextFeature);
|
|
++i;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (IsHatY(feature))
|
|
parameters += ",invert";
|
|
|
|
builder.AddControl(featureName)
|
|
.WithLayout("Analog")
|
|
.WithByteOffset((uint)feature.offset)
|
|
.WithFormat(InputStateBlock.FormatInt)
|
|
.WithParameters(parameters);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
throw new NotImplementedException(
|
|
$"SDLLayoutBuilder.Build: Trying to build an SDL device with an unknown feature of type {feature.featureType}.");
|
|
}
|
|
}
|
|
}
|
|
|
|
return builder.Build();
|
|
}
|
|
}
|
|
}
|
|
#endif // UNITY_EDITOR || UNITY_STANDALONE_LINUX
|