A game about forced loneliness, made by TACStudios
1#if UNITY_EDITOR 2using System; 3using System.Runtime.InteropServices; 4using UnityEditor; 5using UnityEngine.InputSystem.LowLevel; 6 7namespace UnityEngine.InputSystem 8{ 9 /// <summary> 10 /// Adds support for processing input-related messages sent from the <c>Unite Remote</c> app. 11 /// </summary> 12 /// <remarks> 13 /// A hook in the Unity runtime allows us to observe messages received from the remote (see Modules/GenericRemoteEditor). 14 /// We get the binary blob of each message and a shot at processing the message instead of 15 /// the native code doing it. 16 /// </remarks> 17 internal static class UnityRemoteSupport 18 { 19 public static bool isConnected => s_State.connected; 20 21 public static void Initialize() 22 { 23 InputRuntime.s_Instance.onUnityRemoteMessage = ProcessMessageFromUnityRemote; 24 25 InputSystem.onSettingsChange += () => 26 { 27 if (InputSystem.settings.IsFeatureEnabled(InputFeatureNames.kDisableUnityRemoteSupport)) 28 { 29 InputRuntime.s_Instance.onUnityRemoteMessage = null; 30 if (s_State.connected) 31 Disconnect(); 32 } 33 else 34 InputRuntime.s_Instance.onUnityRemoteMessage = ProcessMessageFromUnityRemote; 35 }; 36 } 37 38 private static unsafe bool ProcessMessageFromUnityRemote(IntPtr messageData) 39 { 40 var messageHeader = (MessageHeader*)messageData; 41 42 switch (messageHeader->type) 43 { 44 case (byte)MessageType.Hello: 45 if (s_State.connected) 46 break; 47 48 // Install handlers. 49 s_State.deviceChangeHandler = OnDeviceChange; 50 s_State.deviceCommandHandler = OnDeviceCommand; ////REVIEW: We really should have a way of installing a handler just for a specific device. 51 InputSystem.onDeviceChange += s_State.deviceChangeHandler; 52 InputSystem.onDeviceCommand += s_State.deviceCommandHandler; 53 54 // Add devices. 55 s_State.touchscreen = InputSystem.AddDevice<Touchscreen>(); 56 s_State.touchscreen.m_DeviceFlags |= InputDevice.DeviceFlags.Remote; 57 s_State.accelerometer = InputSystem.AddDevice<Accelerometer>(); 58 s_State.accelerometer.m_DeviceFlags |= InputDevice.DeviceFlags.Remote; 59 // Gryo etc. added only when we receive GyroSettingsMessage. 60 61 s_State.connected = true; 62 Debug.Log("Unity Remote connected to input!"); 63 break; 64 65 case (byte)MessageType.Goodbye: 66 if (!s_State.connected) 67 break; 68 Disconnect(); 69 Debug.Log("Unity Remote disconnected from input!"); 70 break; 71 72 case (byte)MessageType.Options: 73 var optionsMessage = (OptionsMessage*)messageData; 74 s_State.screenSize = DetermineScreenSize(optionsMessage->dimension1, optionsMessage->dimension2); 75 break; 76 77 case (byte)MessageType.TouchInput: 78 if (s_State.touchscreen == null) 79 break; 80 // Android Remote seems to not be sending the last two fields (azimuthAngle and attitudeAngle). 81 if (messageHeader->length < 56) 82 break; 83 var touchMessage = (TouchInputMessage*)messageData; 84 var phase = TouchPhase.None; 85 switch (touchMessage->phase) 86 { 87 case (int)UnityEngine.TouchPhase.Began: phase = TouchPhase.Began; break; 88 case (int)UnityEngine.TouchPhase.Canceled: phase = TouchPhase.Canceled; break; 89 case (int)UnityEngine.TouchPhase.Ended: phase = TouchPhase.Ended; break; 90 case (int)UnityEngine.TouchPhase.Moved: phase = TouchPhase.Moved; break; 91 // Ignore stationary. 92 } 93 if (phase == default) 94 break; 95 InputSystem.QueueStateEvent(s_State.touchscreen, new TouchState 96 { 97 touchId = touchMessage->id + 1, 98 phase = phase, 99 position = MapRemoteTouchCoordinatesToLocal(new Vector2(touchMessage->positionX, touchMessage->positionY)), 100 radius = new Vector2(touchMessage->radius, touchMessage->radius), 101 pressure = touchMessage->pressure 102 }); 103 break; 104 105 case (byte)MessageType.GyroSettings: 106 var gyroSettingsMessage = (GyroSettingsMessage*)messageData; 107 if (!s_State.gyroInitialized) 108 { 109 // Message itself indicates presence of a gyro. Add the devices. 110 s_State.gyroscope = InputSystem.AddDevice<Gyroscope>(); 111 s_State.attitude = InputSystem.AddDevice<AttitudeSensor>(); 112 s_State.gravity = InputSystem.AddDevice<GravitySensor>(); 113 s_State.linearAcceleration = InputSystem.AddDevice<LinearAccelerationSensor>(); 114 s_State.gyroscope.m_DeviceFlags |= InputDevice.DeviceFlags.Remote; 115 s_State.attitude.m_DeviceFlags |= InputDevice.DeviceFlags.Remote; 116 s_State.gravity.m_DeviceFlags |= InputDevice.DeviceFlags.Remote; 117 s_State.linearAcceleration.m_DeviceFlags |= InputDevice.DeviceFlags.Remote; 118 119 s_State.gyroInitialized = true; 120 } 121 // Disable them if they are not currently enabled. 122 if (gyroSettingsMessage->enabled == 0) 123 { 124 InputSystem.DisableDevice(s_State.gyroscope); 125 InputSystem.DisableDevice(s_State.attitude); 126 InputSystem.DisableDevice(s_State.gravity); 127 InputSystem.DisableDevice(s_State.linearAcceleration); 128 } 129 else 130 { 131 s_State.gyroEnabled = true; 132 } 133 s_State.gyroUpdateInterval = gyroSettingsMessage->receivedGyroUpdateInternal; 134 break; 135 136 case (byte)MessageType.GyroInput: 137 var gyroInputMessage = (GyroInputMessage*)messageData; 138 if (s_State.attitude != null && s_State.attitude.enabled) 139 { 140 InputSystem.QueueStateEvent(s_State.attitude, new AttitudeState 141 { 142 attitude = new Quaternion(gyroInputMessage->attitudeX, gyroInputMessage->attitudeY, gyroInputMessage->attitudeZ, 143 gyroInputMessage->attitudeW) 144 }); 145 } 146 if (s_State.gyroscope != null && s_State.gyroscope.enabled) 147 { 148 InputSystem.QueueStateEvent(s_State.gyroscope, new GyroscopeState 149 { 150 angularVelocity = new Vector3(gyroInputMessage->rotationRateX, gyroInputMessage->rotationRateY, 151 gyroInputMessage->rotationRateZ) 152 }); 153 } 154 if (s_State.gravity != null && s_State.gravity.enabled) 155 { 156 InputSystem.QueueStateEvent(s_State.gravity, new GravityState 157 { 158 gravity = new Vector3(gyroInputMessage->gravityX, gyroInputMessage->gravityY, 159 gyroInputMessage->gravityZ) 160 }); 161 } 162 if (s_State.linearAcceleration != null && s_State.linearAcceleration.enabled) 163 { 164 InputSystem.QueueStateEvent(s_State.linearAcceleration, new LinearAccelerationState 165 { 166 acceleration = new Vector3(gyroInputMessage->userAccelerationX, gyroInputMessage->userAccelerationY, 167 gyroInputMessage->userAccelerationZ) 168 }); 169 } 170 break; 171 172 case (byte)MessageType.AccelerometerInput: 173 if (s_State.accelerometer == null) 174 break; 175 var accelerometerMessage = (AccelerometerInputMessage*)messageData; 176 InputSystem.QueueStateEvent(s_State.accelerometer, new AccelerometerState 177 { 178 acceleration = new Vector3(accelerometerMessage->accelerationX, accelerometerMessage->accelerationY, 179 accelerometerMessage->accelerationZ) 180 }); 181 break; 182 } 183 184 return false; 185 } 186 187 private static void Disconnect() 188 { 189 InputSystem.RemoveDevice(s_State.touchscreen); 190 InputSystem.RemoveDevice(s_State.accelerometer); 191 if (s_State.gyroscope != null) 192 InputSystem.RemoveDevice(s_State.gyroscope); 193 if (s_State.attitude != null) 194 InputSystem.RemoveDevice(s_State.attitude); 195 if (s_State.gravity != null) 196 InputSystem.RemoveDevice(s_State.gravity); 197 if (s_State.linearAcceleration != null) 198 InputSystem.RemoveDevice(s_State.linearAcceleration); 199 200 ResetGlobalState(); 201 } 202 203 private static void OnDeviceChange(InputDevice device, InputDeviceChange change) 204 { 205 switch (change) 206 { 207 case InputDeviceChange.Removed: 208 // Deal with someone manually removing one of our devices. 209 if (device == s_State.accelerometer) 210 s_State.accelerometer = null; 211 else if (device == s_State.attitude) 212 s_State.accelerometer = null; 213 else if (device == s_State.gravity) 214 s_State.gravity = null; 215 else if (device == s_State.gyroscope) 216 s_State.gyroscope = null; 217 else if (device == s_State.touchscreen) 218 s_State.touchscreen = null; 219 else if (device == s_State.linearAcceleration) 220 s_State.linearAcceleration = null; 221 break; 222 223 case InputDeviceChange.Enabled: 224 case InputDeviceChange.Disabled: 225 // If it's any of our devices that make up the remote gyro, 226 // send a message to the remote. 227 if (device == s_State.attitude || device == s_State.gravity || device == s_State.gyroscope || 228 device == s_State.linearAcceleration) 229 { 230 SyncGyroEnabledInRemote(); 231 } 232 break; 233 } 234 } 235 236 private static unsafe long? OnDeviceCommand(InputDevice device, InputDeviceCommand* command) 237 { 238 if (device != s_State.attitude && device != s_State.gyroscope && device != s_State.gravity && 239 device != s_State.linearAcceleration) 240 return null; 241 242 if (command->type == SetSamplingFrequencyCommand.Type) 243 { 244 s_State.gyroUpdateInterval = ((SetSamplingFrequencyCommand*)command)->frequency; 245 InputRuntime.s_Instance.SetUnityRemoteGyroUpdateInterval(s_State.gyroUpdateInterval); 246 return InputDeviceCommand.GenericSuccess; 247 } 248 249 if (command->type == QuerySamplingFrequencyCommand.Type) 250 { 251 ((QuerySamplingFrequencyCommand*)command)->frequency = s_State.gyroUpdateInterval; 252 return InputDeviceCommand.GenericSuccess; 253 } 254 255 return InputDeviceCommand.GenericFailure; 256 } 257 258 private static void SyncGyroEnabledInRemote() 259 { 260 var enabled = (s_State.attitude?.enabled ?? false) || (s_State.gravity?.enabled ?? false) || 261 (s_State.gyroscope?.enabled ?? false) || (s_State.linearAcceleration?.enabled ?? false); 262 if (enabled != s_State.gyroEnabled) 263 { 264 s_State.gyroEnabled = enabled; 265 InputRuntime.s_Instance.SetUnityRemoteGyroEnabled(enabled); 266 } 267 } 268 269 // This is taken from HandleOptionsMessage() in GenericRemote.cpp. 270 private static Vector2 DetermineScreenSize(int dimension1, int dimension2) 271 { 272 const float kMaxPixels = 640 * 480; // limit the resolution to VGA 273 float screenPixels = dimension1 * dimension2; 274 var divider = (int)Mathf.Ceil(Mathf.Sqrt(screenPixels / kMaxPixels)); 275 276 if (divider == 0) 277 return default; 278 279 var hdim1 = dimension1 / divider; 280 var hdim2 = dimension2 / divider; 281 282 // GetConfigValue is private. Reflect around it. 283 var getConfigValueMethod = typeof(EditorSettings).GetMethod("GetConfigValue"); 284 if (getConfigValueMethod != null && "Normal".Equals(getConfigValueMethod.Invoke(null, new[] { "UnityRemoteResolution" }))) 285 { 286 hdim1 = dimension1; 287 hdim2 = dimension2; 288 } 289 290 if (hdim1 >= 1 && hdim2 >= 1) 291 return new Vector2(dimension1, dimension2); 292 293 return default; 294 } 295 296 private static Vector2 MapRemoteTouchCoordinatesToLocal(Vector2 position) 297 { 298 var screenSizeRemote = s_State.screenSize; 299 var screenSizeLocal = InputRuntime.s_Instance.screenSize; 300 301 return new Vector2( 302 position.x / screenSizeRemote.x * screenSizeLocal.x, 303 position.y = position.y / screenSizeRemote.y * screenSizeLocal.y); 304 } 305 306 // See Editor/Src/RemoteInput/GenericRemote.cpp 307 308 internal enum MessageType : byte 309 { 310 Invalid = 0, 311 312 Hello = 1, 313 Options = 2, 314 GyroSettings = 3, 315 DeviceOrientation = 4, 316 DeviceFeatures = 5, 317 318 TouchInput = 10, 319 AccelerometerInput = 11, 320 TrackBallInput = 12, 321 Key = 13, 322 GyroInput = 14, 323 MousePresence = 15, 324 JoystickInput = 16, 325 JoystickNames = 17, 326 327 WebCamDeviceList = 20, 328 WebCamStream = 21, 329 330 LocationServiceData = 30, 331 CompassData = 31, 332 333 Goodbye = 32, 334 335 Reserved = 255, 336 } 337 338 internal interface IUnityRemoteMessage 339 { 340 byte staticType { get; } 341 } 342 343 [StructLayout(LayoutKind.Explicit, Size = 5)] 344 internal struct MessageHeader 345 { 346 // Unfortunately, the header has an odd 5 byte length and everything 347 // coming after it is misaligned. Reason is that native reads is as a stream 348 // and wants to pack tightly. 349 [FieldOffset(0)] public byte type; 350 [FieldOffset(1)] public int length; 351 } 352 353 [StructLayout(LayoutKind.Explicit)] 354 internal unsafe struct HelloMessage : IUnityRemoteMessage 355 { 356 [FieldOffset(0)] public MessageHeader header; 357 [FieldOffset(5)] public uint protocolIdLength; 358 [FieldOffset(9)] public fixed char protocolId[11]; 359 [FieldOffset(20)] public int protocolVersion; 360 361 public byte staticType => (byte)MessageType.Hello; 362 363 public static HelloMessage Create() 364 { 365 var msg = default(HelloMessage); 366 msg.protocolIdLength = 11; 367 msg.protocolId[0] = 'U'; 368 msg.protocolId[1] = 'n'; 369 msg.protocolId[2] = 'i'; 370 msg.protocolId[3] = 't'; 371 msg.protocolId[4] = 'y'; 372 msg.protocolId[5] = 'R'; 373 msg.protocolId[6] = 'e'; 374 msg.protocolId[7] = 'm'; 375 msg.protocolId[8] = 'o'; 376 msg.protocolId[9] = 't'; 377 msg.protocolId[10] = 'e'; 378 msg.protocolVersion = 0; 379 return msg; 380 } 381 } 382 383 [StructLayout(LayoutKind.Explicit)] 384 internal struct OptionsMessage : IUnityRemoteMessage 385 { 386 [FieldOffset(0)] public MessageHeader header; 387 [FieldOffset(5)] public int dimension1; 388 [FieldOffset(9)] public int dimension2; 389 390 public byte staticType => (byte)MessageType.Options; 391 } 392 393 [StructLayout(LayoutKind.Explicit)] 394 internal struct GoodbyeMessage : IUnityRemoteMessage 395 { 396 [FieldOffset(0)] public MessageHeader header; 397 398 public byte staticType => (byte)MessageType.Goodbye; 399 } 400 401 [StructLayout(LayoutKind.Explicit)] 402 internal struct TouchInputMessage : IUnityRemoteMessage 403 { 404 [FieldOffset(0)] public MessageHeader header; 405 [FieldOffset(5)] public float positionX; 406 [FieldOffset(9)] public float positionY; 407 [FieldOffset(13)] public ulong frame; 408 [FieldOffset(21)] public int id; 409 [FieldOffset(25)] public int phase; 410 [FieldOffset(29)] public int tapCount; 411 [FieldOffset(33)] public float radius; 412 [FieldOffset(37)] public float radiusVariance; 413 [FieldOffset(41)] public int type; 414 [FieldOffset(45)] public float pressure; 415 [FieldOffset(49)] public float maximumPossiblePressure; 416 [FieldOffset(53)] public float azimuthAngle; 417 [FieldOffset(57)] public float altitudeAngle; 418 419 public byte staticType => (byte)MessageType.TouchInput; 420 } 421 422 [StructLayout(LayoutKind.Explicit)] 423 internal struct GyroSettingsMessage : IUnityRemoteMessage 424 { 425 [FieldOffset(0)] public MessageHeader header; 426 [FieldOffset(5)] public int enabled; 427 [FieldOffset(9)] public float receivedGyroUpdateInternal; 428 429 public byte staticType => (byte)MessageType.GyroSettings; 430 } 431 432 [StructLayout(LayoutKind.Explicit)] 433 internal struct GyroInputMessage : IUnityRemoteMessage 434 { 435 [FieldOffset(0)] public MessageHeader header; 436 [FieldOffset(5)] public float rotationRateX; 437 [FieldOffset(9)] public float rotationRateY; 438 [FieldOffset(13)] public float rotationRateZ; 439 [FieldOffset(17)] public float rotationRateUnbiasedX; 440 [FieldOffset(21)] public float rotationRateUnbiasedY; 441 [FieldOffset(25)] public float rotationRateUnbiasedZ; 442 [FieldOffset(29)] public float gravityX; 443 [FieldOffset(33)] public float gravityY; 444 [FieldOffset(37)] public float gravityZ; 445 [FieldOffset(41)] public float userAccelerationX; 446 [FieldOffset(45)] public float userAccelerationY; 447 [FieldOffset(49)] public float userAccelerationZ; 448 [FieldOffset(53)] public float attitudeX; 449 [FieldOffset(57)] public float attitudeY; 450 [FieldOffset(61)] public float attitudeZ; 451 [FieldOffset(65)] public float attitudeW; 452 453 public byte staticType => (byte)MessageType.GyroInput; 454 } 455 456 [StructLayout(LayoutKind.Explicit)] 457 internal struct AccelerometerInputMessage : IUnityRemoteMessage 458 { 459 [FieldOffset(0)] public MessageHeader header; 460 [FieldOffset(5)] public float accelerationX; 461 [FieldOffset(9)] public float accelerationY; 462 [FieldOffset(13)] public float accelerationZ; 463 [FieldOffset(17)] public float deltaTime; 464 465 public byte staticType => (byte)MessageType.AccelerometerInput; 466 } 467 468 private struct State 469 { 470 public bool connected; 471 public bool gyroInitialized; 472 public bool gyroEnabled; 473 public float gyroUpdateInterval; 474 public Vector2 screenSize; 475 476 public Action<InputDevice, InputDeviceChange> deviceChangeHandler; 477 public InputDeviceCommandDelegate deviceCommandHandler; 478 479 // Devices that we create for receiving input from the remote. 480 public Touchscreen touchscreen; 481 public Accelerometer accelerometer; 482 public Gyroscope gyroscope; 483 public AttitudeSensor attitude; 484 public GravitySensor gravity; 485 public LinearAccelerationSensor linearAcceleration; 486 } 487 488 private static State s_State; 489 490 ////TODO: hook this into Hakan's new cleanup mechanism 491 internal static void ResetGlobalState() 492 { 493 if (s_State.deviceChangeHandler != null) 494 InputSystem.onDeviceChange -= s_State.deviceChangeHandler; 495 if (s_State.deviceCommandHandler != null) 496 InputSystem.onDeviceCommand -= s_State.deviceCommandHandler; 497 s_State = default; 498 } 499 } 500} 501#endif