Maze / Library /PackageCache /com.unity.inputsystem@1.6.1 /InputSystem /Plugins /UnityRemote /UnityRemoteSupport.cs
| using System; | |
| using System.Runtime.InteropServices; | |
| using UnityEditor; | |
| using UnityEngine.InputSystem.LowLevel; | |
| namespace UnityEngine.InputSystem | |
| { | |
| /// <summary> | |
| /// Adds support for processing input-related messages sent from the <c>Unite Remote</c> app. | |
| /// </summary> | |
| /// <remarks> | |
| /// A hook in the Unity runtime allows us to observe messages received from the remote (see Modules/GenericRemoteEditor). | |
| /// We get the binary blob of each message and a shot at processing the message instead of | |
| /// the native code doing it. | |
| /// </remarks> | |
| internal static class UnityRemoteSupport | |
| { | |
| public static bool isConnected => s_State.connected; | |
| public static void Initialize() | |
| { | |
| InputRuntime.s_Instance.onUnityRemoteMessage = ProcessMessageFromUnityRemote; | |
| InputSystem.onSettingsChange += () => | |
| { | |
| if (InputSystem.settings.IsFeatureEnabled(InputFeatureNames.kDisableUnityRemoteSupport)) | |
| { | |
| InputRuntime.s_Instance.onUnityRemoteMessage = null; | |
| if (s_State.connected) | |
| Disconnect(); | |
| } | |
| else | |
| InputRuntime.s_Instance.onUnityRemoteMessage = ProcessMessageFromUnityRemote; | |
| }; | |
| } | |
| private static unsafe bool ProcessMessageFromUnityRemote(IntPtr messageData) | |
| { | |
| var messageHeader = (MessageHeader*)messageData; | |
| switch (messageHeader->type) | |
| { | |
| case (byte)MessageType.Hello: | |
| if (s_State.connected) | |
| break; | |
| // Install handlers. | |
| s_State.deviceChangeHandler = OnDeviceChange; | |
| s_State.deviceCommandHandler = OnDeviceCommand; ////REVIEW: We really should have a way of installing a handler just for a specific device. | |
| InputSystem.onDeviceChange += s_State.deviceChangeHandler; | |
| InputSystem.onDeviceCommand += s_State.deviceCommandHandler; | |
| // Add devices. | |
| s_State.touchscreen = InputSystem.AddDevice<Touchscreen>(); | |
| s_State.touchscreen.m_DeviceFlags |= InputDevice.DeviceFlags.Remote; | |
| s_State.accelerometer = InputSystem.AddDevice<Accelerometer>(); | |
| s_State.accelerometer.m_DeviceFlags |= InputDevice.DeviceFlags.Remote; | |
| // Gryo etc. added only when we receive GyroSettingsMessage. | |
| s_State.connected = true; | |
| Debug.Log("Unity Remote connected to input!"); | |
| break; | |
| case (byte)MessageType.Goodbye: | |
| if (!s_State.connected) | |
| break; | |
| Disconnect(); | |
| Debug.Log("Unity Remote disconnected from input!"); | |
| break; | |
| case (byte)MessageType.Options: | |
| var optionsMessage = (OptionsMessage*)messageData; | |
| s_State.screenSize = DetermineScreenSize(optionsMessage->dimension1, optionsMessage->dimension2); | |
| break; | |
| case (byte)MessageType.TouchInput: | |
| if (s_State.touchscreen == null) | |
| break; | |
| // Android Remote seems to not be sending the last two fields (azimuthAngle and attitudeAngle). | |
| if (messageHeader->length < 56) | |
| break; | |
| var touchMessage = (TouchInputMessage*)messageData; | |
| var phase = TouchPhase.None; | |
| switch (touchMessage->phase) | |
| { | |
| case (int)UnityEngine.TouchPhase.Began: phase = TouchPhase.Began; break; | |
| case (int)UnityEngine.TouchPhase.Canceled: phase = TouchPhase.Canceled; break; | |
| case (int)UnityEngine.TouchPhase.Ended: phase = TouchPhase.Ended; break; | |
| case (int)UnityEngine.TouchPhase.Moved: phase = TouchPhase.Moved; break; | |
| // Ignore stationary. | |
| } | |
| if (phase == default) | |
| break; | |
| InputSystem.QueueStateEvent(s_State.touchscreen, new TouchState | |
| { | |
| touchId = touchMessage->id + 1, | |
| phase = phase, | |
| position = MapRemoteTouchCoordinatesToLocal(new Vector2(touchMessage->positionX, touchMessage->positionY)), | |
| radius = new Vector2(touchMessage->radius, touchMessage->radius), | |
| pressure = touchMessage->pressure | |
| }); | |
| break; | |
| case (byte)MessageType.GyroSettings: | |
| var gyroSettingsMessage = (GyroSettingsMessage*)messageData; | |
| if (!s_State.gyroInitialized) | |
| { | |
| // Message itself indicates presence of a gyro. Add the devices. | |
| s_State.gyroscope = InputSystem.AddDevice<Gyroscope>(); | |
| s_State.attitude = InputSystem.AddDevice<AttitudeSensor>(); | |
| s_State.gravity = InputSystem.AddDevice<GravitySensor>(); | |
| s_State.linearAcceleration = InputSystem.AddDevice<LinearAccelerationSensor>(); | |
| s_State.gyroscope.m_DeviceFlags |= InputDevice.DeviceFlags.Remote; | |
| s_State.attitude.m_DeviceFlags |= InputDevice.DeviceFlags.Remote; | |
| s_State.gravity.m_DeviceFlags |= InputDevice.DeviceFlags.Remote; | |
| s_State.linearAcceleration.m_DeviceFlags |= InputDevice.DeviceFlags.Remote; | |
| s_State.gyroInitialized = true; | |
| } | |
| // Disable them if they are not currently enabled. | |
| if (gyroSettingsMessage->enabled == 0) | |
| { | |
| InputSystem.DisableDevice(s_State.gyroscope); | |
| InputSystem.DisableDevice(s_State.attitude); | |
| InputSystem.DisableDevice(s_State.gravity); | |
| InputSystem.DisableDevice(s_State.linearAcceleration); | |
| } | |
| else | |
| { | |
| s_State.gyroEnabled = true; | |
| } | |
| s_State.gyroUpdateInterval = gyroSettingsMessage->receivedGyroUpdateInternal; | |
| break; | |
| case (byte)MessageType.GyroInput: | |
| var gyroInputMessage = (GyroInputMessage*)messageData; | |
| if (s_State.attitude != null && s_State.attitude.enabled) | |
| { | |
| InputSystem.QueueStateEvent(s_State.attitude, new AttitudeState | |
| { | |
| attitude = new Quaternion(gyroInputMessage->attitudeX, gyroInputMessage->attitudeY, gyroInputMessage->attitudeZ, | |
| gyroInputMessage->attitudeW) | |
| }); | |
| } | |
| if (s_State.gyroscope != null && s_State.gyroscope.enabled) | |
| { | |
| InputSystem.QueueStateEvent(s_State.gyroscope, new GyroscopeState | |
| { | |
| angularVelocity = new Vector3(gyroInputMessage->rotationRateX, gyroInputMessage->rotationRateY, | |
| gyroInputMessage->rotationRateZ) | |
| }); | |
| } | |
| if (s_State.gravity != null && s_State.gravity.enabled) | |
| { | |
| InputSystem.QueueStateEvent(s_State.gravity, new GravityState | |
| { | |
| gravity = new Vector3(gyroInputMessage->gravityX, gyroInputMessage->gravityY, | |
| gyroInputMessage->gravityZ) | |
| }); | |
| } | |
| if (s_State.linearAcceleration != null && s_State.linearAcceleration.enabled) | |
| { | |
| InputSystem.QueueStateEvent(s_State.linearAcceleration, new LinearAccelerationState | |
| { | |
| acceleration = new Vector3(gyroInputMessage->userAccelerationX, gyroInputMessage->userAccelerationY, | |
| gyroInputMessage->userAccelerationZ) | |
| }); | |
| } | |
| break; | |
| case (byte)MessageType.AccelerometerInput: | |
| if (s_State.accelerometer == null) | |
| break; | |
| var accelerometerMessage = (AccelerometerInputMessage*)messageData; | |
| InputSystem.QueueStateEvent(s_State.accelerometer, new AccelerometerState | |
| { | |
| acceleration = new Vector3(accelerometerMessage->accelerationX, accelerometerMessage->accelerationY, | |
| accelerometerMessage->accelerationZ) | |
| }); | |
| break; | |
| } | |
| return false; | |
| } | |
| private static void Disconnect() | |
| { | |
| InputSystem.RemoveDevice(s_State.touchscreen); | |
| InputSystem.RemoveDevice(s_State.accelerometer); | |
| if (s_State.gyroscope != null) | |
| InputSystem.RemoveDevice(s_State.gyroscope); | |
| if (s_State.attitude != null) | |
| InputSystem.RemoveDevice(s_State.attitude); | |
| if (s_State.gravity != null) | |
| InputSystem.RemoveDevice(s_State.gravity); | |
| if (s_State.linearAcceleration != null) | |
| InputSystem.RemoveDevice(s_State.linearAcceleration); | |
| ResetGlobalState(); | |
| } | |
| private static void OnDeviceChange(InputDevice device, InputDeviceChange change) | |
| { | |
| switch (change) | |
| { | |
| case InputDeviceChange.Removed: | |
| // Deal with someone manually removing one of our devices. | |
| if (device == s_State.accelerometer) | |
| s_State.accelerometer = null; | |
| else if (device == s_State.attitude) | |
| s_State.accelerometer = null; | |
| else if (device == s_State.gravity) | |
| s_State.gravity = null; | |
| else if (device == s_State.gyroscope) | |
| s_State.gyroscope = null; | |
| else if (device == s_State.touchscreen) | |
| s_State.touchscreen = null; | |
| else if (device == s_State.linearAcceleration) | |
| s_State.linearAcceleration = null; | |
| break; | |
| case InputDeviceChange.Enabled: | |
| case InputDeviceChange.Disabled: | |
| // If it's any of our devices that make up the remote gyro, | |
| // send a message to the remote. | |
| if (device == s_State.attitude || device == s_State.gravity || device == s_State.gyroscope || | |
| device == s_State.linearAcceleration) | |
| { | |
| SyncGyroEnabledInRemote(); | |
| } | |
| break; | |
| } | |
| } | |
| private static unsafe long? OnDeviceCommand(InputDevice device, InputDeviceCommand* command) | |
| { | |
| if (device != s_State.attitude && device != s_State.gyroscope && device != s_State.gravity && | |
| device != s_State.linearAcceleration) | |
| return null; | |
| if (command->type == SetSamplingFrequencyCommand.Type) | |
| { | |
| s_State.gyroUpdateInterval = ((SetSamplingFrequencyCommand*)command)->frequency; | |
| InputRuntime.s_Instance.SetUnityRemoteGyroUpdateInterval(s_State.gyroUpdateInterval); | |
| return InputDeviceCommand.GenericSuccess; | |
| } | |
| if (command->type == QuerySamplingFrequencyCommand.Type) | |
| { | |
| ((QuerySamplingFrequencyCommand*)command)->frequency = s_State.gyroUpdateInterval; | |
| return InputDeviceCommand.GenericSuccess; | |
| } | |
| return InputDeviceCommand.GenericFailure; | |
| } | |
| private static void SyncGyroEnabledInRemote() | |
| { | |
| var enabled = (s_State.attitude?.enabled ?? false) || (s_State.gravity?.enabled ?? false) || | |
| (s_State.gyroscope?.enabled ?? false) || (s_State.linearAcceleration?.enabled ?? false); | |
| if (enabled != s_State.gyroEnabled) | |
| { | |
| s_State.gyroEnabled = enabled; | |
| InputRuntime.s_Instance.SetUnityRemoteGyroEnabled(enabled); | |
| } | |
| } | |
| // This is taken from HandleOptionsMessage() in GenericRemote.cpp. | |
| private static Vector2 DetermineScreenSize(int dimension1, int dimension2) | |
| { | |
| const float kMaxPixels = 640 * 480; // limit the resolution to VGA | |
| float screenPixels = dimension1 * dimension2; | |
| var divider = (int)Mathf.Ceil(Mathf.Sqrt(screenPixels / kMaxPixels)); | |
| if (divider == 0) | |
| return default; | |
| var hdim1 = dimension1 / divider; | |
| var hdim2 = dimension2 / divider; | |
| // GetConfigValue is private. Reflect around it. | |
| var getConfigValueMethod = typeof(EditorSettings).GetMethod("GetConfigValue"); | |
| if (getConfigValueMethod != null && "Normal".Equals(getConfigValueMethod.Invoke(null, new[] { "UnityRemoteResolution" }))) | |
| { | |
| hdim1 = dimension1; | |
| hdim2 = dimension2; | |
| } | |
| if (hdim1 >= 1 && hdim2 >= 1) | |
| return new Vector2(dimension1, dimension2); | |
| return default; | |
| } | |
| private static Vector2 MapRemoteTouchCoordinatesToLocal(Vector2 position) | |
| { | |
| var screenSizeRemote = s_State.screenSize; | |
| var screenSizeLocal = InputRuntime.s_Instance.screenSize; | |
| return new Vector2( | |
| position.x / screenSizeRemote.x * screenSizeLocal.x, | |
| position.y = position.y / screenSizeRemote.y * screenSizeLocal.y); | |
| } | |
| // See Editor/Src/RemoteInput/GenericRemote.cpp | |
| internal enum MessageType : byte | |
| { | |
| Invalid = 0, | |
| Hello = 1, | |
| Options = 2, | |
| GyroSettings = 3, | |
| DeviceOrientation = 4, | |
| DeviceFeatures = 5, | |
| TouchInput = 10, | |
| AccelerometerInput = 11, | |
| TrackBallInput = 12, | |
| Key = 13, | |
| GyroInput = 14, | |
| MousePresence = 15, | |
| JoystickInput = 16, | |
| JoystickNames = 17, | |
| WebCamDeviceList = 20, | |
| WebCamStream = 21, | |
| LocationServiceData = 30, | |
| CompassData = 31, | |
| Goodbye = 32, | |
| Reserved = 255, | |
| } | |
| internal interface IUnityRemoteMessage | |
| { | |
| byte staticType { get; } | |
| } | |
| [] | |
| internal struct MessageHeader | |
| { | |
| // Unfortunately, the header has an odd 5 byte length and everything | |
| // coming after it is misaligned. Reason is that native reads is as a stream | |
| // and wants to pack tightly. | |
| [] public byte type; | |
| [] public int length; | |
| } | |
| [] | |
| internal unsafe struct HelloMessage : IUnityRemoteMessage | |
| { | |
| [] public MessageHeader header; | |
| [] public uint protocolIdLength; | |
| [] public fixed char protocolId[11]; | |
| [] public int protocolVersion; | |
| public byte staticType => (byte)MessageType.Hello; | |
| public static HelloMessage Create() | |
| { | |
| var msg = default(HelloMessage); | |
| msg.protocolIdLength = 11; | |
| msg.protocolId[0] = 'U'; | |
| msg.protocolId[1] = 'n'; | |
| msg.protocolId[2] = 'i'; | |
| msg.protocolId[3] = 't'; | |
| msg.protocolId[4] = 'y'; | |
| msg.protocolId[5] = 'R'; | |
| msg.protocolId[6] = 'e'; | |
| msg.protocolId[7] = 'm'; | |
| msg.protocolId[8] = 'o'; | |
| msg.protocolId[9] = 't'; | |
| msg.protocolId[10] = 'e'; | |
| msg.protocolVersion = 0; | |
| return msg; | |
| } | |
| } | |
| [] | |
| internal struct OptionsMessage : IUnityRemoteMessage | |
| { | |
| [] public MessageHeader header; | |
| [] public int dimension1; | |
| [] public int dimension2; | |
| public byte staticType => (byte)MessageType.Options; | |
| } | |
| [] | |
| internal struct GoodbyeMessage : IUnityRemoteMessage | |
| { | |
| [] public MessageHeader header; | |
| public byte staticType => (byte)MessageType.Goodbye; | |
| } | |
| [] | |
| internal struct TouchInputMessage : IUnityRemoteMessage | |
| { | |
| [] public MessageHeader header; | |
| [] public float positionX; | |
| [] public float positionY; | |
| [] public ulong frame; | |
| [] public int id; | |
| [] public int phase; | |
| [] public int tapCount; | |
| [] public float radius; | |
| [] public float radiusVariance; | |
| [] public int type; | |
| [] public float pressure; | |
| [] public float maximumPossiblePressure; | |
| [] public float azimuthAngle; | |
| [] public float altitudeAngle; | |
| public byte staticType => (byte)MessageType.TouchInput; | |
| } | |
| [] | |
| internal struct GyroSettingsMessage : IUnityRemoteMessage | |
| { | |
| [] public MessageHeader header; | |
| [] public int enabled; | |
| [] public float receivedGyroUpdateInternal; | |
| public byte staticType => (byte)MessageType.GyroSettings; | |
| } | |
| [] | |
| internal struct GyroInputMessage : IUnityRemoteMessage | |
| { | |
| [] public MessageHeader header; | |
| [] public float rotationRateX; | |
| [] public float rotationRateY; | |
| [] public float rotationRateZ; | |
| [] public float rotationRateUnbiasedX; | |
| [] public float rotationRateUnbiasedY; | |
| [] public float rotationRateUnbiasedZ; | |
| [] public float gravityX; | |
| [] public float gravityY; | |
| [] public float gravityZ; | |
| [] public float userAccelerationX; | |
| [] public float userAccelerationY; | |
| [] public float userAccelerationZ; | |
| [] public float attitudeX; | |
| [] public float attitudeY; | |
| [] public float attitudeZ; | |
| [] public float attitudeW; | |
| public byte staticType => (byte)MessageType.GyroInput; | |
| } | |
| [] | |
| internal struct AccelerometerInputMessage : IUnityRemoteMessage | |
| { | |
| [] public MessageHeader header; | |
| [] public float accelerationX; | |
| [] public float accelerationY; | |
| [] public float accelerationZ; | |
| [] public float deltaTime; | |
| public byte staticType => (byte)MessageType.AccelerometerInput; | |
| } | |
| private struct State | |
| { | |
| public bool connected; | |
| public bool gyroInitialized; | |
| public bool gyroEnabled; | |
| public float gyroUpdateInterval; | |
| public Vector2 screenSize; | |
| public Action<InputDevice, InputDeviceChange> deviceChangeHandler; | |
| public InputDeviceCommandDelegate deviceCommandHandler; | |
| // Devices that we create for receiving input from the remote. | |
| public Touchscreen touchscreen; | |
| public Accelerometer accelerometer; | |
| public Gyroscope gyroscope; | |
| public AttitudeSensor attitude; | |
| public GravitySensor gravity; | |
| public LinearAccelerationSensor linearAcceleration; | |
| } | |
| private static State s_State; | |
| ////TODO: hook this into Hakan's new cleanup mechanism | |
| internal static void ResetGlobalState() | |
| { | |
| if (s_State.deviceChangeHandler != null) | |
| InputSystem.onDeviceChange -= s_State.deviceChangeHandler; | |
| if (s_State.deviceCommandHandler != null) | |
| InputSystem.onDeviceCommand -= s_State.deviceCommandHandler; | |
| s_State = default; | |
| } | |
| } | |
| } | |