SDLDeviceBuilder.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. #if UNITY_EDITOR || UNITY_STANDALONE_LINUX
  2. using System;
  3. using UnityEngine.InputSystem.LowLevel;
  4. using UnityEngine.InputSystem.Utilities;
  5. using System.Text;
  6. using UnityEngine.InputSystem.Layouts;
  7. namespace UnityEngine.InputSystem.Linux
  8. {
  9. [Serializable]
  10. internal class SDLLayoutBuilder
  11. {
  12. [SerializeField] private string m_ParentLayout;
  13. [SerializeField] private SDLDeviceDescriptor m_Descriptor;
  14. internal static string OnFindLayoutForDevice(ref InputDeviceDescription description, string matchedLayout,
  15. InputDeviceExecuteCommandDelegate executeCommandDelegate)
  16. {
  17. if (description.interfaceName != LinuxSupport.kInterfaceName)
  18. return null;
  19. if (string.IsNullOrEmpty(description.capabilities))
  20. return null;
  21. // Try to parse the SDL descriptor.
  22. SDLDeviceDescriptor deviceDescriptor;
  23. try
  24. {
  25. deviceDescriptor = SDLDeviceDescriptor.FromJson(description.capabilities);
  26. }
  27. catch (Exception exception)
  28. {
  29. Debug.LogError($"{exception} while trying to parse descriptor for SDL device: {description.capabilities}");
  30. return null;
  31. }
  32. if (deviceDescriptor == null)
  33. return null;
  34. string layoutName;
  35. if (string.IsNullOrEmpty(description.manufacturer))
  36. {
  37. layoutName = $"{SanitizeName(description.interfaceName)}::{SanitizeName(description.product)}";
  38. }
  39. else
  40. {
  41. layoutName =
  42. $"{SanitizeName(description.interfaceName)}::{SanitizeName(description.manufacturer)}::{SanitizeName(description.product)}";
  43. }
  44. var layout = new SDLLayoutBuilder { m_Descriptor = deviceDescriptor, m_ParentLayout = matchedLayout };
  45. InputSystem.RegisterLayoutBuilder(() => layout.Build(), layoutName, matchedLayout);
  46. return layoutName;
  47. }
  48. private static string SanitizeName(string originalName)
  49. {
  50. var stringLength = originalName.Length;
  51. var sanitizedName = new StringBuilder(stringLength);
  52. for (var i = 0; i < stringLength; i++)
  53. {
  54. var letter = originalName[i];
  55. if (char.IsUpper(letter) || char.IsLower(letter) || char.IsDigit(letter))
  56. sanitizedName.Append(letter);
  57. }
  58. return sanitizedName.ToString();
  59. }
  60. private static bool IsAxis(SDLFeatureDescriptor feature, SDLAxisUsage axis)
  61. {
  62. return feature.featureType == JoystickFeatureType.Axis
  63. && feature.usageHint == (int)axis;
  64. }
  65. private static void BuildStickFeature(ref InputControlLayout.Builder builder, SDLFeatureDescriptor xFeature, SDLFeatureDescriptor yFeature)
  66. {
  67. int byteOffset;
  68. if (xFeature.offset <= yFeature.offset)
  69. byteOffset = xFeature.offset;
  70. else
  71. byteOffset = yFeature.offset;
  72. const string stickName = "Stick";
  73. builder.AddControl(stickName)
  74. .WithLayout("Stick")
  75. .WithByteOffset((uint)byteOffset)
  76. .WithSizeInBits((uint)xFeature.featureSize * 8 + (uint)yFeature.featureSize * 8)
  77. .WithUsages(CommonUsages.Primary2DMotion);
  78. builder.AddControl(stickName + "/x")
  79. .WithFormat(InputStateBlock.FormatInt)
  80. .WithByteOffset(0)
  81. .WithSizeInBits((uint)xFeature.featureSize * 8)
  82. .WithParameters("clamp=1,clampMin=-1,clampMax=1,scale,scaleFactor=65538");
  83. builder.AddControl(stickName + "/y")
  84. .WithFormat(InputStateBlock.FormatInt)
  85. .WithByteOffset(4)
  86. .WithSizeInBits((uint)xFeature.featureSize * 8)
  87. .WithParameters("clamp=1,clampMin=-1,clampMax=1,scale,scaleFactor=65538,invert");
  88. builder.AddControl(stickName + "/up")
  89. .WithParameters("clamp=1,clampMin=-1,clampMax=0,scale,scaleFactor=65538,invert");
  90. builder.AddControl(stickName + "/down")
  91. .WithParameters("clamp=1,clampMin=0,clampMax=1,scale,scaleFactor=65538,invert=false");
  92. builder.AddControl(stickName + "/left")
  93. .WithParameters("clamp=1,clampMin=-1,clampMax=0,scale,scaleFactor=65538,invert");
  94. builder.AddControl(stickName + "/right")
  95. .WithParameters("clamp=1,clampMin=0,clampMax=1,scale,scaleFactor=65538");
  96. }
  97. private static bool IsHatX(SDLFeatureDescriptor feature)
  98. {
  99. return feature.featureType == JoystickFeatureType.Hat
  100. && (feature.usageHint == (int)SDLAxisUsage.Hat0X
  101. || feature.usageHint == (int)SDLAxisUsage.Hat1X
  102. || feature.usageHint == (int)SDLAxisUsage.Hat2X
  103. || feature.usageHint == (int)SDLAxisUsage.Hat3X);
  104. }
  105. private static bool IsHatY(SDLFeatureDescriptor feature)
  106. {
  107. return feature.featureType == JoystickFeatureType.Hat
  108. && (feature.usageHint == (int)SDLAxisUsage.Hat0Y
  109. || feature.usageHint == (int)SDLAxisUsage.Hat1Y
  110. || feature.usageHint == (int)SDLAxisUsage.Hat2Y
  111. || feature.usageHint == (int)SDLAxisUsage.Hat3Y);
  112. }
  113. private static int HatNumber(SDLFeatureDescriptor feature)
  114. {
  115. Debug.Assert(feature.featureType == JoystickFeatureType.Hat);
  116. return 1 + (feature.usageHint - (int)SDLAxisUsage.Hat0X) / 2;
  117. }
  118. private static void BuildHatFeature(ref InputControlLayout.Builder builder, SDLFeatureDescriptor xFeature, SDLFeatureDescriptor yFeature)
  119. {
  120. Debug.Assert(xFeature.offset < yFeature.offset, "Order of features must be X followed by Y");
  121. var hat = HatNumber(xFeature);
  122. var hatName = hat > 1 ? $"Hat{hat}" : "Hat";
  123. builder.AddControl(hatName)
  124. .WithLayout("Dpad")
  125. .WithByteOffset((uint)xFeature.offset)
  126. .WithSizeInBits((uint)xFeature.featureSize * 8 + (uint)yFeature.featureSize * 8)
  127. .WithUsages(CommonUsages.Hatswitch);
  128. builder.AddControl(hatName + "/up")
  129. .WithFormat(InputStateBlock.FormatInt)
  130. .WithParameters("scale,scaleFactor=2147483647,clamp,clampMin=-1,clampMax=0,invert")
  131. .WithByteOffset(4)
  132. .WithBitOffset(0)
  133. .WithSizeInBits((uint)yFeature.featureSize * 8);
  134. builder.AddControl(hatName + "/down")
  135. .WithFormat(InputStateBlock.FormatInt)
  136. .WithParameters("scale,scaleFactor=2147483647,clamp,clampMin=0,clampMax=1")
  137. .WithByteOffset(4)
  138. .WithBitOffset(0)
  139. .WithSizeInBits((uint)yFeature.featureSize * 8);
  140. builder.AddControl(hatName + "/left")
  141. .WithFormat(InputStateBlock.FormatInt)
  142. .WithParameters("scale,scaleFactor=2147483647,clamp,clampMin=-1,clampMax=0,invert")
  143. .WithByteOffset(0)
  144. .WithBitOffset(0)
  145. .WithSizeInBits((uint)xFeature.featureSize * 8);
  146. builder.AddControl(hatName + "/right")
  147. .WithFormat(InputStateBlock.FormatInt)
  148. .WithParameters("scale,scaleFactor=2147483647,clamp,clampMin=0,clampMax=1")
  149. .WithByteOffset(0)
  150. .WithBitOffset(0)
  151. .WithSizeInBits((uint)xFeature.featureSize * 8);
  152. }
  153. internal InputControlLayout Build()
  154. {
  155. var builder = new InputControlLayout.Builder
  156. {
  157. stateFormat = new FourCC('L', 'J', 'O', 'Y'),
  158. extendsLayout = m_ParentLayout
  159. };
  160. for (var i = 0; i < m_Descriptor.controls.LengthSafe(); i++)
  161. {
  162. var feature = m_Descriptor.controls[i];
  163. switch (feature.featureType)
  164. {
  165. case JoystickFeatureType.Axis:
  166. {
  167. var usage = (SDLAxisUsage)feature.usageHint;
  168. var featureName = LinuxSupport.GetAxisNameFromUsage(usage);
  169. var parameters = "scale,scaleFactor=65538,clamp=1,clampMin=-1,clampMax=1";
  170. // If X is followed by Y, build a stick out of the two.
  171. if (IsAxis(feature, SDLAxisUsage.X) && i + 1 < m_Descriptor.controls.Length)
  172. {
  173. var nextFeature = m_Descriptor.controls[i + 1];
  174. if (IsAxis(nextFeature, SDLAxisUsage.Y))
  175. {
  176. BuildStickFeature(ref builder, feature, nextFeature);
  177. ++i;
  178. continue;
  179. }
  180. }
  181. if (IsAxis(feature, SDLAxisUsage.Y))
  182. parameters += ",invert";
  183. var control = builder.AddControl(featureName)
  184. .WithLayout("Analog")
  185. .WithByteOffset((uint)feature.offset)
  186. .WithFormat(InputStateBlock.FormatInt)
  187. .WithParameters(parameters);
  188. if (IsAxis(feature, SDLAxisUsage.RotateZ))
  189. control.WithUsages(CommonUsages.Twist);
  190. break;
  191. }
  192. case JoystickFeatureType.Ball:
  193. {
  194. //TODO
  195. break;
  196. }
  197. case JoystickFeatureType.Button:
  198. {
  199. var usage = (SDLButtonUsage)feature.usageHint;
  200. var featureName = LinuxSupport.GetButtonNameFromUsage(usage);
  201. if (featureName != null)
  202. {
  203. builder.AddControl(featureName)
  204. .WithLayout("Button")
  205. .WithByteOffset((uint)feature.offset)
  206. .WithBitOffset((uint)feature.bit)
  207. .WithFormat(InputStateBlock.FormatBit);
  208. }
  209. break;
  210. }
  211. case JoystickFeatureType.Hat:
  212. {
  213. var usage = (SDLAxisUsage)feature.usageHint;
  214. var featureName = LinuxSupport.GetAxisNameFromUsage(usage);
  215. var parameters = "scale,scaleFactor=2147483647,clamp=1,clampMin=-1,clampMax=1";
  216. if (i + 1 < m_Descriptor.controls.Length)
  217. {
  218. var nextFeature = m_Descriptor.controls[i + 1];
  219. if (IsHatY(nextFeature) && HatNumber(feature) == HatNumber(nextFeature))
  220. {
  221. BuildHatFeature(ref builder, feature, nextFeature);
  222. ++i;
  223. continue;
  224. }
  225. }
  226. if (IsHatY(feature))
  227. parameters += ",invert";
  228. builder.AddControl(featureName)
  229. .WithLayout("Analog")
  230. .WithByteOffset((uint)feature.offset)
  231. .WithFormat(InputStateBlock.FormatInt)
  232. .WithParameters(parameters);
  233. break;
  234. }
  235. default:
  236. {
  237. throw new NotImplementedException(
  238. $"SDLLayoutBuilder.Build: Trying to build an SDL device with an unknown feature of type {feature.featureType}.");
  239. }
  240. }
  241. }
  242. return builder.Build();
  243. }
  244. }
  245. }
  246. #endif // UNITY_EDITOR || UNITY_STANDALONE_LINUX