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