123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501 |
- #if UNITY_EDITOR
- 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; }
- }
-
- [StructLayout(LayoutKind.Explicit, Size = 5)]
- 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.
- [FieldOffset(0)] public byte type;
- [FieldOffset(1)] public int length;
- }
-
- [StructLayout(LayoutKind.Explicit)]
- internal unsafe struct HelloMessage : IUnityRemoteMessage
- {
- [FieldOffset(0)] public MessageHeader header;
- [FieldOffset(5)] public uint protocolIdLength;
- [FieldOffset(9)] public fixed char protocolId[11];
- [FieldOffset(20)] 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;
- }
- }
-
- [StructLayout(LayoutKind.Explicit)]
- internal struct OptionsMessage : IUnityRemoteMessage
- {
- [FieldOffset(0)] public MessageHeader header;
- [FieldOffset(5)] public int dimension1;
- [FieldOffset(9)] public int dimension2;
-
- public byte staticType => (byte)MessageType.Options;
- }
-
- [StructLayout(LayoutKind.Explicit)]
- internal struct GoodbyeMessage : IUnityRemoteMessage
- {
- [FieldOffset(0)] public MessageHeader header;
-
- public byte staticType => (byte)MessageType.Goodbye;
- }
-
- [StructLayout(LayoutKind.Explicit)]
- internal struct TouchInputMessage : IUnityRemoteMessage
- {
- [FieldOffset(0)] public MessageHeader header;
- [FieldOffset(5)] public float positionX;
- [FieldOffset(9)] public float positionY;
- [FieldOffset(13)] public ulong frame;
- [FieldOffset(21)] public int id;
- [FieldOffset(25)] public int phase;
- [FieldOffset(29)] public int tapCount;
- [FieldOffset(33)] public float radius;
- [FieldOffset(37)] public float radiusVariance;
- [FieldOffset(41)] public int type;
- [FieldOffset(45)] public float pressure;
- [FieldOffset(49)] public float maximumPossiblePressure;
- [FieldOffset(53)] public float azimuthAngle;
- [FieldOffset(57)] public float altitudeAngle;
-
- public byte staticType => (byte)MessageType.TouchInput;
- }
-
- [StructLayout(LayoutKind.Explicit)]
- internal struct GyroSettingsMessage : IUnityRemoteMessage
- {
- [FieldOffset(0)] public MessageHeader header;
- [FieldOffset(5)] public int enabled;
- [FieldOffset(9)] public float receivedGyroUpdateInternal;
-
- public byte staticType => (byte)MessageType.GyroSettings;
- }
-
- [StructLayout(LayoutKind.Explicit)]
- internal struct GyroInputMessage : IUnityRemoteMessage
- {
- [FieldOffset(0)] public MessageHeader header;
- [FieldOffset(5)] public float rotationRateX;
- [FieldOffset(9)] public float rotationRateY;
- [FieldOffset(13)] public float rotationRateZ;
- [FieldOffset(17)] public float rotationRateUnbiasedX;
- [FieldOffset(21)] public float rotationRateUnbiasedY;
- [FieldOffset(25)] public float rotationRateUnbiasedZ;
- [FieldOffset(29)] public float gravityX;
- [FieldOffset(33)] public float gravityY;
- [FieldOffset(37)] public float gravityZ;
- [FieldOffset(41)] public float userAccelerationX;
- [FieldOffset(45)] public float userAccelerationY;
- [FieldOffset(49)] public float userAccelerationZ;
- [FieldOffset(53)] public float attitudeX;
- [FieldOffset(57)] public float attitudeY;
- [FieldOffset(61)] public float attitudeZ;
- [FieldOffset(65)] public float attitudeW;
-
- public byte staticType => (byte)MessageType.GyroInput;
- }
-
- [StructLayout(LayoutKind.Explicit)]
- internal struct AccelerometerInputMessage : IUnityRemoteMessage
- {
- [FieldOffset(0)] public MessageHeader header;
- [FieldOffset(5)] public float accelerationX;
- [FieldOffset(9)] public float accelerationY;
- [FieldOffset(13)] public float accelerationZ;
- [FieldOffset(17)] 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;
- }
- }
- }
- #endif
|