This commit is contained in:
2025-01-17 13:10:42 +01:00
commit 4536213c91
15115 changed files with 1442174 additions and 0 deletions

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 857f018a2ed885449804cf82b4fec116
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,188 @@
using System.Collections;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.LowLevel;
using UnityEngine.Scripting;
using UnityEngine.TestTools;
#if UNITY_EDITOR
using UnityEngine.InputSystem.Editor;
#endif
// Disable irrelevant warning about there not being underscores in method names.
#pragma warning disable CA1707
// These tests are the only ones that we put *in* the package. The rest of our tests live in Assets/Tests and run separately
// from our CI and not through upm-ci. This also means that IntegrationTests is the only thing we put on trunk through our
// verified package.
//
// Rationale:
// (1) Our APIVerificationTests have extra package requirements and thus need a custom package manifest.json. This will not
// work with upm-ci.
// (2) The tests we have in Assets/Tests exercise the input system in isolation. Having these run on trunk in addition to our
// CI in the input system repo adds little value while adding extra execution time to trunk QV runs. This is unlike
// the integration tests here which add value to trunk by making sure the input system is intact all the way through
// to the native input module.
// (3) If we added everything in Assets/Tests to the package, we would add more stuff to user projects that has no value to users.
//
// NOTE: The tests here are necessary to pass the requirement imposed by upm-ci that a package MUST have tests in it.
public class IntegrationTests
{
[Preserve]
public static void PreserveMethods()
{
// Workaround a bug in com.unity.test-framework.utp-reporter
// Due Stripping set to to High System.ComponentModel.StringConverter ctor is stripped, making first test Integration_CanSendAndReceiveEvents to fail
// MissingMethodException: Constructor on type 'System.ComponentModel.StringConverter' not found.
//at System.RuntimeType.CreateInstanceImpl(System.Reflection.BindingFlags bindingAttr, System.Reflection.Binder binder, System.Object[] args, System.Globalization.CultureInfo culture, System.Object[] activationAttributes, System.Threading.StackCrawlMark & stackMark)[0x001f0] in < b6074dacdf2142f38da4050b03a225bb >:0
//at System.Activator.CreateInstance(System.Type type, System.Reflection.BindingFlags bindingAttr, System.Reflection.Binder binder, System.Object[] args, System.Globalization.CultureInfo culture, System.Object[] activationAttributes)[0x00095] in < b6074dacdf2142f38da4050b03a225bb >:0
//at System.Activator.CreateInstance(System.Type type, System.Reflection.BindingFlags bindingAttr, System.Reflection.Binder binder, System.Object[] args, System.Globalization.CultureInfo culture)[0x00000] in < b6074dacdf2142f38da4050b03a225bb >:0
//at System.SecurityUtils.SecureCreateInstance(System.Type type, System.Object[] args, System.Boolean allowNonPublic)[0x0003a] in < 43cff77c8e644fb3bd45df5f20310d13 >:0
//at System.SecurityUtils.SecureCreateInstance(System.Type type)[0x00000] in < 43cff77c8e644fb3bd45df5f20310d13 >:0
//at System.ComponentModel.ReflectTypeDescriptionProvider.CreateInstance(System.Type objectType, System.Type callingType)[0x0001a] in < 43cff77c8e644fb3bd45df5f20310d13 >:0
//at System.ComponentModel.ReflectTypeDescriptionProvider.SearchIntrinsicTable(System.Collections.Hashtable table, System.Type callingType)[0x0015d] in < 43cff77c8e644fb3bd45df5f20310d13 >:0
//at System.ComponentModel.ReflectTypeDescriptionProvider + ReflectedTypeData.GetConverter(System.Object instance)[0x000fc] in < 43cff77c8e644fb3bd45df5f20310d13 >:0
//at System.ComponentModel.ReflectTypeDescriptionProvider.GetConverter(System.Type type, System.Object instance)[0x00008] in < 43cff77c8e644fb3bd45df5f20310d13 >:0
//at System.ComponentModel.TypeDescriptor + TypeDescriptionNode + DefaultTypeDescriptor.System.ComponentModel.ICustomTypeDescriptor.GetConverter()[0x00016] in < 43cff77c8e644fb3bd45df5f20310d13 >:0
//at System.ComponentModel.TypeDescriptor.GetConverter(System.Type type)[0x0000b] in < 43cff77c8e644fb3bd45df5f20310d13 >:0
//at Newtonsoft.Json.Serialization.JsonTypeReflector.CanTypeDescriptorConvertString(System.Type type, System.ComponentModel.TypeConverter & typeConverter)[0x00000] in < 489f342f5a6b4cd3856aec7b3d5c47e7 >:0
//at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.TryConvertToString(System.Object value, System.Type type, System.String & s)[0x00000] in < 489f342f5a6b4cd3856aec7b3d5c47e7 >:0
//at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.GetPropertyName(Newtonsoft.Json.JsonWriter writer, System.Object name, Newtonsoft.Json.Serialization.JsonContract contract, System.Boolean & escape)[0x00127] in < 489f342f5a6b4cd3856aec7b3d5c47e7 >:0
//at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeDictionary(Newtonsoft.Json.JsonWriter writer, System.Collections.IDictionary values, Newtonsoft.Json.Serialization.JsonDictionaryContract contract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract collectionContract, Newtonsoft.Json.Serialization.JsonProperty containerProperty)[0x000c6] in < 489f342f5a6b4cd3856aec7b3d5c47e7 >:0
//at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeValue(Newtonsoft.Json.JsonWriter writer, System.Object value, Newtonsoft.Json.Serialization.JsonContract valueContract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerProperty)[0x0013d] in < 489f342f5a6b4cd3856aec7b3d5c47e7 >:0
//at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.Serialize(Newtonsoft.Json.JsonWriter jsonWriter, System.Object value, System.Type objectType)[0x00079] in < 489f342f5a6b4cd3856aec7b3d5c47e7 >:0
//at Newtonsoft.Json.JsonSerializer.SerializeInternal(Newtonsoft.Json.JsonWriter jsonWriter, System.Object value, System.Type objectType)[0x0023a] in < 489f342f5a6b4cd3856aec7b3d5c47e7 >:0
//at Newtonsoft.Json.JsonSerializer.Serialize(Newtonsoft.Json.JsonWriter jsonWriter, System.Object value, System.Type objectType)[0x00000] in < 489f342f5a6b4cd3856aec7b3d5c47e7 >:0
//at Newtonsoft.Json.JsonConvert.SerializeObjectInternal(System.Object value, System.Type type, Newtonsoft.Json.JsonSerializer jsonSerializer)[0x00028] in < 489f342f5a6b4cd3856aec7b3d5c47e7 >:0
//at Newtonsoft.Json.JsonConvert.SerializeObject(System.Object value, System.Type type, Newtonsoft.Json.Formatting formatting, Newtonsoft.Json.JsonSerializerSettings settings)[0x0000e] in < 489f342f5a6b4cd3856aec7b3d5c47e7 >:0
//at Newtonsoft.Json.JsonConvert.SerializeObject(System.Object value, Newtonsoft.Json.Formatting formatting, Newtonsoft.Json.JsonSerializerSettings settings)[0x00000] in < 489f342f5a6b4cd3856aec7b3d5c47e7 >:0
//at Newtonsoft.Json.JsonConvert.SerializeObject(System.Object value, Newtonsoft.Json.Formatting formatting)[0x00000] in < 489f342f5a6b4cd3856aec7b3d5c47e7 >:0
//at Unity.TestProtocol.UnityTestProtocolMessageBuilder.BuildMessage(System.Collections.Specialized.OrderedDictionary fields)[0x00001] in < eea339da6b5e4d4bb255bfef95601890 >:0
//at Unity.TestProtocol.UnityTestProtocolMessageBuilder.Serialize(Unity.TestProtocol.Message message)[0x0006c] in < eea339da6b5e4d4bb255bfef95601890 >:0
//at Unity.TestFramework.UTPReporter.TestResultToUtpMessage.Send(NUnit.Framework.Interfaces.IXmlNodeBuilder xmlNodeBuilder)[0x0002d] in F:\Projects\InputSystem\Library\PackageCache\com.unity.test - framework.utp - reporter@0.1.3 - preview.18\Runtime\TestResultsHandler.cs:65
//at Unity.TestFramework.UTPReporter.TestResultToUtpMessage.TestStarted(NUnit.Framework.Interfaces.ITest testStartedResult)[0x00001] in F:\Projects\InputSystem\Library\PackageCache\com.unity.test - framework.utp - reporter@0.1.3 - preview.18\Runtime\TestResultsHandler.cs:50
//at UnityEngine.TestRunner.Utils.TestRunCallbackListener +<> c__DisplayClass5_0.< TestStarted > b__0(UnityEngine.TestRunner.ITestRunCallback callback)[0x00000] in F:\Projects\InputSystem\Library\PackageCache\com.unity.test - framework@1.1.14\UnityEngine.TestRunner\Utils\TestRunCallbackListener.cs:55
//at UnityEngine.TestRunner.Utils.TestRunCallbackListener.InvokeAllCallbacks(System.Action`1[T] invoker)[0x0002d] in F:\Projects\InputSystem\Library\PackageCache\com.unity.test - framework@1.1.14\UnityEngine.TestRunner\Utils\TestRunCallbackListener.cs:38
var dummy = new System.ComponentModel.StringConverter();
}
[SetUp]
public virtual void Setup()
{
// The standalone player can go out of focus when running tests. This makes the devices added during tests set
// as disabled. Which in turn makes the InputSystem update not process input state changes for devices.
// By ignoring focus, we can protect ourselves against this and always update the device state in the standalone
// test players
InputSystem.settings.backgroundBehavior = InputSettings.BackgroundBehavior.IgnoreFocus;
}
[TearDown]
public virtual void TearDown()
{
InputSystem.settings.backgroundBehavior = default;
}
[Test]
[Category("Integration")]
public void Integration_CanSendAndReceiveEvents()
{
var keyboard = InputSystem.AddDevice<Keyboard>();
try
{
InputSystem.QueueStateEvent(keyboard, new KeyboardState(Key.A));
InputSystem.Update();
Assert.That(keyboard.aKey.isPressed, Is.True);
}
finally
{
InputSystem.RemoveDevice(keyboard);
}
}
#if UNITY_EDITOR
[Test]
[Category("Integration")]
public void Integration_CanChangeInputBackendPlayerSettingInEditor()
{
// Save current player settings so we can restore them.
var oldEnabled = EditorPlayerSettingHelpers.oldSystemBackendsEnabled;
var newEnabled = EditorPlayerSettingHelpers.newSystemBackendsEnabled;
// Enable new and disable old.
EditorPlayerSettingHelpers.newSystemBackendsEnabled = true;
EditorPlayerSettingHelpers.oldSystemBackendsEnabled = false;
Assert.That(EditorPlayerSettingHelpers.newSystemBackendsEnabled, Is.True);
Assert.That(EditorPlayerSettingHelpers.oldSystemBackendsEnabled, Is.False);
// Enable old and disable new.
EditorPlayerSettingHelpers.newSystemBackendsEnabled = false;
EditorPlayerSettingHelpers.oldSystemBackendsEnabled = true;
Assert.That(EditorPlayerSettingHelpers.newSystemBackendsEnabled, Is.False);
Assert.That(EditorPlayerSettingHelpers.oldSystemBackendsEnabled, Is.True);
// Enable both.
EditorPlayerSettingHelpers.newSystemBackendsEnabled = true;
EditorPlayerSettingHelpers.oldSystemBackendsEnabled = true;
Assert.That(EditorPlayerSettingHelpers.newSystemBackendsEnabled, Is.True);
Assert.That(EditorPlayerSettingHelpers.oldSystemBackendsEnabled, Is.True);
// Restore previous settings.
EditorPlayerSettingHelpers.oldSystemBackendsEnabled = oldEnabled;
EditorPlayerSettingHelpers.newSystemBackendsEnabled = newEnabled;
}
#endif
#if UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN
[UnityTest]
[Category("Integration")]
[Ignore("Unstable due to 1252825")]
public IEnumerator WindowsInput_RemoteDesktopMouseMovements_AreDetected()
{
var mouse = InputSystem.GetDevice<Mouse>();
var currentPosition = mouse.position.ReadValue();
yield return new WaitForSeconds(0.1f);
Assert.AreEqual(currentPosition, mouse.position.ReadValue(), "Expected mouse position to not change when no input was sent. Please do not move the mouse during this test.");
WinUserInput.SendRDPMouseMoveEvent(10, 10);
yield return new WaitForSeconds(0.1f);
Assert.AreNotEqual(currentPosition, mouse.position.ReadValue(), "Expected mouse position to have moved when sending RDP/absolute values.");
currentPosition = mouse.position.ReadValue();
WinUserInput.SendRDPMouseMoveEvent(100, 100);
yield return new WaitForSeconds(0.1f);
Assert.AreNotEqual(currentPosition, mouse.position.ReadValue(), "Expected mouse position to have moved when sending RDP/absolute values.");
}
[UnityTest]
[Category("Integration")]
[Ignore("Unstable due to 1252825")]
public IEnumerator WindowsInput_MouseMovements_AreDetected()
{
var mouse = InputSystem.GetDevice<Mouse>();
var currentPosition = mouse.position.ReadValue();
yield return new WaitForSeconds(0.1f);
Assert.AreEqual(currentPosition, mouse.position.ReadValue(), "Expected mouse position to not change when no input was sent. Please do not move the mouse during this test.");
WinUserInput.SendMouseMoveEvent(10, 10);
yield return new WaitForSeconds(0.1f);
Assert.AreNotEqual(currentPosition, mouse.position.ReadValue(), "Expected mouse position to have moved when sending relative values.");
currentPosition = mouse.position.ReadValue();
WinUserInput.SendMouseMoveEvent(100, 100);
yield return new WaitForSeconds(0.1f);
Assert.AreNotEqual(currentPosition, mouse.position.ReadValue(), "Expected mouse position to have moved when sending relative values.");
}
#endif // UNITY_2019_3_OR_NEWER && (UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN)
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 96d7b220d8769944f9aca48b3218daaa
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,18 @@
{
"name": "Unity.InputSystem.IntegrationTests",
"references": [
"GUID:75469ad4d38634e559750d17036d5f7c",
"GUID:dc04f38471c3a459fb4d31124ee9127d"
],
"optionalUnityReferences": [
"TestAssemblies"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": false,
"defineConstraints": ["UNITY_INCLUDE_TESTS"],
"versionDefines": []
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 3ae5734e49d646442ad24f37762ebdd2
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e349610bc4b1546dd94c6dab37414d2b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,10 @@
using System.Reflection;
using System.Runtime.CompilerServices;
// Keep this in sync with "Packages/com.unity.inputsystem/package.json".
// NOTE: Unfortunately, System.Version doesn't use semantic versioning so we can't include
// "-preview" suffixes here.
[assembly: AssemblyVersion("1.11.2")]
[assembly: InternalsVisibleTo("Unity.InputSystem.Tests.Editor")]
[assembly: InternalsVisibleTo("Unity.InputSystem.Tests")]
[assembly: InternalsVisibleTo("Unity.InputSystem.IntegrationTests")]

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 674b72d5417404b9eac446749e3e2d5e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,216 @@
#if UNITY_EDITOR
using System;
using System.IO;
using UnityEditor;
namespace UnityEngine.InputSystem
{
/// Provides convenience functions for creating and managing assets for test purposes.
/// Note that all returned paths are converted to Unix paths when running on Windows
/// for consistency and to avoid mixed path names.
public static class AssetDatabaseUtils
{
private const string kAssetPath = "Assets";
private const string kTestPath = "TestFiles";
private const string kMetaExtension = ".meta";
private const string kDefaultAssetExtension = "asset";
// Perform an operation equivalent to a file delete operation outside of Unity Editor.
// Note that meta file is also removed to avoid generating warnings about non-clean delete.
public static void ExternalDeleteFileOrDirectory(string path)
{
FileUtil.DeleteFileOrDirectory(path);
FileUtil.DeleteFileOrDirectory(path + kMetaExtension);
}
// Perform an operation equivalent to a file move operation outside of Unity Editor.
// Note that meta file is also moved to avoid generating warnings about non-clean move.
public static void ExternalMoveFileOrDirectory(string source, string dest)
{
FileUtil.MoveFileOrDirectory(source, dest);
FileUtil.MoveFileOrDirectory(source + kMetaExtension, dest + kMetaExtension);
}
// Create an asset at the given path containing the given text content.
private static T CreateAssetAtPath<T>(string path, string content) where T : UnityEngine.Object
{
Debug.Assert(!File.Exists(path));
T obj;
try
{
CreateDirectories(Path.GetDirectoryName(path));
File.WriteAllText(path, content);
AssetDatabase.ImportAsset(path);
obj = AssetDatabase.LoadAssetAtPath<T>(path);
if (obj == null)
throw new Exception($"Failed to create asset at \"{path}\"");
}
catch (Exception)
{
AssetDatabase.DeleteAsset(path);
throw;
}
return obj;
}
private static string SanitizePath(string path)
{
return path?.Replace("\\", "/");
}
private static void CreateRootDirectory()
{
CreateDirectories(RootPath());
}
// Creates all directories (including intermediate) defined in path.
private static string CreateDirectories(string path)
{
if (Directory.Exists(path))
return SanitizePath(path);
var parentFolder = kAssetPath;
path = path.Replace("\\", "/"); // Make sure we only get '/' separators.
var directories = path.Split('/');
if (directories[0] != kAssetPath)
throw new ArgumentException(path);
for (var i = 1; i < directories.Length; ++i)
{
var guid = AssetDatabase.CreateFolder(parentFolder, directories[i]);
if (guid == string.Empty)
throw new Exception("Failed to create path \"" + path + "\"");
parentFolder = SanitizePath(Path.Combine(parentFolder, directories[i]));
}
AssetDatabase.Refresh();
return SanitizePath(path);
}
// Creates a random test directory within asset folder that is automatically removed after test run.
public static string CreateDirectory()
{
return CreateDirectories(RandomDirectoryPath());
}
// Creates an asset in the given directory path with an explicit or random file name containing the
// given content or the default content based on type.
public static T CreateAsset<T>(string directoryPath, string filename = null, string content = null) where T : UnityEngine.Object
{
Debug.Assert(directoryPath == null || directoryPath.Contains(RootPath()));
Debug.Assert(filename == null || !filename.Contains("/"));
if (directoryPath == null)
directoryPath = RootPath();
string path;
if (filename != null)
{
path = SanitizePath(Path.Combine(directoryPath, filename));
if (File.Exists(path))
throw new Exception($"File already exists: {path}");
}
else
{
path = RandomAssetFilePath(directoryPath, AssetFileExtensionFromType(typeof(T)));
}
return CreateAsset<T>(path: path, content: content);
}
// Creates an asset at the given path containing the specified content.
// If path is null, a unique random file name is assigned, if content is null the default content based
// on type (extension) is used.
public static T CreateAsset<T>(string path = null, string content = null) where T : UnityEngine.Object
{
if (path == null)
path = RandomAssetFilePath(RootPath(), AssetFileExtensionFromType(typeof(T)));
if (content == null)
content = DefaultContentFromType(typeof(T));
return CreateAssetAtPath<T>(path, content);
}
public static void Restore()
{
var root = RootPath();
// Delete all files in test folder
if (!Directory.Exists(root))
return;
foreach (var asset in AssetDatabase.FindAssets("", new string[] { root }))
{
var path = AssetDatabase.GUIDToAssetPath(asset);
AssetDatabase.DeleteAsset(path);
}
AssetDatabase.DeleteAsset(root);
}
private static string RandomName()
{
const double scale = int.MaxValue;
double r = UnityEngine.Random.value;
return "Test_" + (int)(Math.Floor(r * scale));
}
private static string RandomAssetFilePath<T>(string directoryPath = null)
{
return RandomAssetFilePath(directoryPath, AssetFileExtensionFromType(typeof(T)));
}
private static string RandomAssetFilePath(string directoryPath = null, string extension = null)
{
// Default to using test files root path
if (directoryPath == null)
directoryPath = RootPath();
// Default to default extension
if (extension == null)
extension = kDefaultAssetExtension;
string path;
do
{
path = SanitizePath(Path.Combine(directoryPath, RandomName() + "." + extension)); // EDIT
}
while (File.Exists(path));
return path;
}
private static string RootPath()
{
return SanitizePath(Path.Combine(kAssetPath, kTestPath));
}
public static string RandomDirectoryPath()
{
string path;
do
{
path = Path.Combine(RootPath(), RandomName());
}
while (File.Exists(path));
return SanitizePath(path);
}
private static string AssetFileExtensionFromType(Type type)
{
if (type == typeof(InputActionAsset))
return InputActionAsset.Extension;
return kDefaultAssetExtension;
}
private static string DefaultContentFromType(Type type)
{
if (type == typeof(InputActionAsset))
return "{}";
return string.Empty;
}
}
}
#endif // UNITY_EDITOR

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a1bc613b5fab4dd4997810700b46dbf7
timeCreated: 1708676790

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 81128cc364d234170845119461c41970
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,489 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using UnityEngine.InputSystem.LowLevel;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine.Analytics;
using UnityEngine.InputSystem.Layouts;
using UnityEngine.InputSystem.Utilities;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace UnityEngine.InputSystem
{
/// <summary>
/// An implementation of <see cref="IInputRuntime"/> for use during tests.
/// </summary>
/// <remarks>
/// This class is only available in the editor and in development players.
///
/// The test runtime replaces the services usually supplied by <see cref="UnityEngineInternal.Input.NativeInputSystem"/>.
/// </remarks>
/// <seealso cref="InputTestFixture.runtime"/>
internal class InputTestRuntime : IInputRuntime, IDisposable
{
public unsafe delegate long DeviceCommandCallback(int deviceId, InputDeviceCommand* command);
~InputTestRuntime()
{
Dispose();
}
public int AllocateDeviceId()
{
var result = m_NextDeviceId;
++m_NextDeviceId;
return result;
}
public unsafe void Update(InputUpdateType type)
{
if (!onShouldRunUpdate.Invoke(type))
return;
lock (m_Lock)
{
if (type == InputUpdateType.Dynamic && !dontAdvanceUnscaledGameTimeNextDynamicUpdate)
{
unscaledGameTime += 1 / 30f;
dontAdvanceUnscaledGameTimeNextDynamicUpdate = false;
}
if (m_NewDeviceDiscoveries != null && m_NewDeviceDiscoveries.Count > 0)
{
if (onDeviceDiscovered != null)
foreach (var entry in m_NewDeviceDiscoveries)
onDeviceDiscovered(entry.Key, entry.Value);
m_NewDeviceDiscoveries.Clear();
}
onBeforeUpdate?.Invoke(type);
// Advance time *after* onBeforeUpdate so that events generated from onBeforeUpdate
// don't get bumped into the following update.
if (type == InputUpdateType.Dynamic && !dontAdvanceTimeNextDynamicUpdate)
{
currentTime += advanceTimeEachDynamicUpdate;
dontAdvanceTimeNextDynamicUpdate = false;
}
if (onUpdate != null)
{
var buffer = new InputEventBuffer(
(InputEvent*)NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(m_EventBuffer),
m_EventCount, m_EventWritePosition, m_EventBuffer.Length);
try
{
onUpdate(type, ref buffer);
}
catch (Exception e)
{
// Same order as in NativeInputRuntime
Debug.LogException(e);
Debug.LogError($"{e.GetType().Name} during event processing of {type} update; resetting event buffer");
// Rethrow exception for test runtime to enable us to assert against it in tests.
m_EventCount = 0;
m_EventWritePosition = 0;
throw;
}
m_EventCount = buffer.eventCount;
m_EventWritePosition = (int)buffer.sizeInBytes;
if (NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(buffer.data) !=
NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(m_EventBuffer))
m_EventBuffer = buffer.data;
}
else
{
m_EventCount = 0;
m_EventWritePosition = 0;
}
}
}
public unsafe void QueueEvent(InputEvent* eventPtr)
{
var eventSize = eventPtr->sizeInBytes;
var alignedEventSize = eventSize.AlignToMultipleOf(4);
lock (m_Lock)
{
eventPtr->eventId = m_NextEventId;
eventPtr->handled = false;
++m_NextEventId;
// Enlarge buffer, if we have to.
if ((m_EventWritePosition + alignedEventSize) > m_EventBuffer.Length)
{
var newBufferSize = m_EventBuffer.Length + Mathf.Max((int)alignedEventSize, 1024);
var newBuffer = new NativeArray<byte>(newBufferSize, Allocator.Persistent);
UnsafeUtility.MemCpy(newBuffer.GetUnsafePtr(), m_EventBuffer.GetUnsafePtr(), m_EventWritePosition);
m_EventBuffer.Dispose();
m_EventBuffer = newBuffer;
}
// Copy event.
UnsafeUtility.MemCpy((byte*)m_EventBuffer.GetUnsafePtr() + m_EventWritePosition, eventPtr, eventSize);
m_EventWritePosition += (int)alignedEventSize;
++m_EventCount;
}
}
public unsafe void SetCanRunInBackground(int deviceId)
{
SetDeviceCommandCallback(deviceId,
(id, command) =>
{
if (command->type == QueryCanRunInBackground.Type)
{
((QueryCanRunInBackground*)command)->canRunInBackground = true;
return InputDeviceCommand.GenericSuccess;
}
return InputDeviceCommand.GenericFailure;
});
}
public void SetDeviceCommandCallback(InputDevice device, DeviceCommandCallback callback)
{
SetDeviceCommandCallback(device.deviceId, callback);
}
public void SetDeviceCommandCallback(int deviceId, DeviceCommandCallback callback)
{
lock (m_Lock)
{
if (m_DeviceCommandCallbacks == null)
m_DeviceCommandCallbacks = new List<KeyValuePair<int, DeviceCommandCallback>>();
else
{
for (var i = 0; i < m_DeviceCommandCallbacks.Count; ++i)
{
if (m_DeviceCommandCallbacks[i].Key == deviceId)
{
m_DeviceCommandCallbacks[i] = new KeyValuePair<int, DeviceCommandCallback>(deviceId, callback);
return;
}
}
}
m_DeviceCommandCallbacks.Add(new KeyValuePair<int, DeviceCommandCallback>(deviceId, callback));
}
}
public void SetDeviceCommandCallback<TCommand>(int deviceId, TCommand result)
where TCommand : struct, IInputDeviceCommandInfo
{
bool? receivedCommand = null;
unsafe
{
SetDeviceCommandCallback(deviceId,
(id, commandPtr) =>
{
if (commandPtr->type == result.typeStatic)
{
Assert.That(receivedCommand.HasValue, Is.False);
receivedCommand = true;
UnsafeUtility.MemCpy(commandPtr, UnsafeUtility.AddressOf(ref result),
UnsafeUtility.SizeOf<TCommand>());
return InputDeviceCommand.GenericSuccess;
}
return InputDeviceCommand.GenericFailure;
});
}
}
public unsafe long DeviceCommand(int deviceId, InputDeviceCommand* commandPtr)
{
lock (m_Lock)
{
if (commandPtr->type == QueryPairedUserAccountCommand.Type)
{
foreach (var pairing in userAccountPairings)
{
if (pairing.deviceId != deviceId)
continue;
var queryPairedUser = (QueryPairedUserAccountCommand*)commandPtr;
queryPairedUser->handle = pairing.userHandle;
queryPairedUser->name = pairing.userName;
queryPairedUser->id = pairing.userId;
return (long)QueryPairedUserAccountCommand.Result.DevicePairedToUserAccount;
}
}
var result = InputDeviceCommand.GenericFailure;
if (m_DeviceCommandCallbacks != null)
foreach (var entry in m_DeviceCommandCallbacks)
{
if (entry.Key == deviceId)
{
result = entry.Value(deviceId, commandPtr);
if (result >= 0)
return result;
}
}
return result;
}
}
public void InvokePlayerFocusChanged(bool newFocusState)
{
m_HasFocus = newFocusState;
onPlayerFocusChanged?.Invoke(newFocusState);
}
public void PlayerFocusLost()
{
InvokePlayerFocusChanged(false);
}
public void PlayerFocusGained()
{
InvokePlayerFocusChanged(true);
}
public int ReportNewInputDevice(string deviceDescriptor, int deviceId = InputDevice.InvalidDeviceId)
{
lock (m_Lock)
{
if (deviceId == InputDevice.InvalidDeviceId)
deviceId = AllocateDeviceId();
if (m_NewDeviceDiscoveries == null)
m_NewDeviceDiscoveries = new List<KeyValuePair<int, string>>();
m_NewDeviceDiscoveries.Add(new KeyValuePair<int, string>(deviceId, deviceDescriptor));
return deviceId;
}
}
public int ReportNewInputDevice(InputDeviceDescription description, int deviceId = InputDevice.InvalidDeviceId,
ulong userHandle = 0, string userName = null, string userId = null)
{
deviceId = ReportNewInputDevice(description.ToJson(), deviceId);
// If we have user information, automatically set up
if (userHandle != 0)
AssociateInputDeviceWithUser(deviceId, userHandle, userName, userId);
return deviceId;
}
public int ReportNewInputDevice<TDevice>(int deviceId = InputDevice.InvalidDeviceId,
ulong userHandle = 0, string userName = null, string userId = null)
where TDevice : InputDevice
{
return ReportNewInputDevice(
new InputDeviceDescription {deviceClass = typeof(TDevice).Name, interfaceName = "Test"}, deviceId,
userHandle, userName, userId);
}
public unsafe void ReportInputDeviceRemoved(int deviceId)
{
var removeEvent = DeviceRemoveEvent.Create(deviceId);
var removeEventPtr = UnsafeUtility.AddressOf(ref removeEvent);
QueueEvent((InputEvent*)removeEventPtr);
}
public void ReportInputDeviceRemoved(InputDevice device)
{
if (device == null)
throw new ArgumentNullException(nameof(device));
ReportInputDeviceRemoved(device.deviceId);
}
public void AssociateInputDeviceWithUser(int deviceId, ulong userHandle, string userName = null, string userId = null)
{
var existingIndex = -1;
for (var i = 0; i < userAccountPairings.Count; ++i)
if (userAccountPairings[i].deviceId == deviceId)
{
existingIndex = i;
break;
}
if (userHandle == 0)
{
if (existingIndex != -1)
userAccountPairings.RemoveAt(existingIndex);
}
else if (existingIndex != -1)
{
userAccountPairings[existingIndex] =
new PairedUser
{
deviceId = deviceId,
userHandle = userHandle,
userName = userName,
userId = userId,
};
}
else
{
userAccountPairings.Add(
new PairedUser
{
deviceId = deviceId,
userHandle = userHandle,
userName = userName,
userId = userId,
});
}
}
public void AssociateInputDeviceWithUser(InputDevice device, ulong userHandle, string userName = null, string userId = null)
{
AssociateInputDeviceWithUser(device.deviceId, userHandle, userName, userId);
}
public struct PairedUser
{
public int deviceId;
public ulong userHandle;
public string userName;
public string userId;
}
public InputUpdateDelegate onUpdate { get; set; }
public Action<InputUpdateType> onBeforeUpdate { get; set; }
public Func<InputUpdateType, bool> onShouldRunUpdate { get; set; }
#if UNITY_EDITOR
public Action onPlayerLoopInitialization { get; set; }
#endif
public Action<int, string> onDeviceDiscovered { get; set; }
public Action onShutdown { get; set; }
public Action<bool> onPlayerFocusChanged { get; set; }
public bool isPlayerFocused => m_HasFocus;
public float pollingFrequency { get; set; }
public double currentTime { get; set; }
public double currentTimeForFixedUpdate { get; set; }
public float unscaledGameTime { get; set; } = 1;
public bool dontAdvanceUnscaledGameTimeNextDynamicUpdate { get; set; }
public double advanceTimeEachDynamicUpdate { get; set; } = 1.0 / 60;
public bool dontAdvanceTimeNextDynamicUpdate { get; set; }
public bool runInBackground { get; set; } = false;
public Vector2 screenSize { get; set; } = new Vector2(1024, 768);
public ScreenOrientation screenOrientation { set; get; } = ScreenOrientation.Portrait;
public bool normalizeScrollWheelDelta { get; set; } = true;
public float scrollWheelDeltaPerTick { get; set; } = 1.0f;
public List<PairedUser> userAccountPairings
{
get
{
if (m_UserPairings == null)
m_UserPairings = new List<PairedUser>();
return m_UserPairings;
}
}
public void Dispose()
{
m_EventBuffer.Dispose();
GC.SuppressFinalize(this);
}
public double currentTimeOffsetToRealtimeSinceStartup
{
get => m_CurrentTimeOffsetToRealtimeSinceStartup;
set
{
m_CurrentTimeOffsetToRealtimeSinceStartup = value;
InputRuntime.s_CurrentTimeOffsetToRealtimeSinceStartup = value;
}
}
public bool isInBatchMode { get; set; }
#if UNITY_EDITOR
public bool isInPlayMode { get; set; } = true;
public bool isPaused { get; set; }
public bool isEditorActive { get; set; } = true;
public Func<IntPtr, bool> onUnityRemoteMessage
{
get => m_UnityRemoteMessageHandler;
set => m_UnityRemoteMessageHandler = value;
}
public bool? unityRemoteGyroEnabled;
public float? unityRemoteGyroUpdateInterval;
public void SetUnityRemoteGyroEnabled(bool value)
{
unityRemoteGyroEnabled = value;
}
public void SetUnityRemoteGyroUpdateInterval(float interval)
{
unityRemoteGyroUpdateInterval = interval;
}
public Action<PlayModeStateChange> onPlayModeChanged { get; set; }
public Action onProjectChange { get; set; }
#endif
public int eventCount => m_EventCount;
internal const int kDefaultEventBufferSize = 1024 * 512;
private bool m_HasFocus = true;
private int m_NextDeviceId = 1;
private int m_NextEventId = 1;
internal int m_EventCount;
private int m_EventWritePosition;
private NativeArray<byte> m_EventBuffer = new NativeArray<byte>(kDefaultEventBufferSize, Allocator.Persistent);
private List<PairedUser> m_UserPairings;
private List<KeyValuePair<int, string>> m_NewDeviceDiscoveries;
private List<KeyValuePair<int, DeviceCommandCallback>> m_DeviceCommandCallbacks;
private object m_Lock = new object();
private double m_CurrentTimeOffsetToRealtimeSinceStartup;
private Func<IntPtr, bool> m_UnityRemoteMessageHandler;
#if UNITY_ANALYTICS || UNITY_EDITOR
public Action<string, int, int> onRegisterAnalyticsEvent { get; set; }
public Action<string, object> onSendAnalyticsEvent { get; set; }
public void SendAnalytic(InputAnalytics.IInputAnalytic analytic)
{
#if UNITY_2023_2_OR_NEWER
// Mimic editor analytics for Unity 2023.2+ invoking TryGatherData to send
var analyticInfoAttribute = analytic.GetType().GetCustomAttributes(
typeof(AnalyticInfoAttribute), true).FirstOrDefault() as AnalyticInfoAttribute;
var info = analytic.info;
#if UNITY_EDITOR
// Registration handled by framework
#else
onRegisterAnalyticsEvent?.Invoke(info.Name, info.MaxEventsPerHour, info.MaxNumberOfElements); // only to avoid writing two tests per Unity version (registration handled by framework)
#endif
if (analytic.TryGatherData(out var data, out var ex) && data != null && analyticInfoAttribute != null)
onSendAnalyticsEvent?.Invoke(analyticInfoAttribute.eventName, data);
else if (ex != null)
throw ex; // rethrow for visibility in test scope
#else
var info = analytic.info;
onRegisterAnalyticsEvent?.Invoke(info.Name, info.MaxEventsPerHour, info.MaxNumberOfElements);
if (analytic.TryGatherData(out var data, out var error))
onSendAnalyticsEvent?.Invoke(info.Name, data);
else
throw error; // For visibility in tests
#endif // UNITY_2023_2_OR_NEWER
}
#endif // UNITY_ANALYTICS || UNITY_EDITOR
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 073f4378d59364199ae19d0486842726
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,57 @@
using System;
using UnityEditor;
namespace UnityEngine.InputSystem
{
// Utility allowing access to object T as well as a dispose delegate to clean-up any resources associated with it.
// Useful with the 'using' keyword to provide scoped RAII-like cleanup of objects in tests without having a
// dedicated fixture handling the clean-up.
internal sealed class ScopedDisposable<T> : IDisposable
where T : UnityEngine.Object
{
private Action<T> m_Dispose;
public ScopedDisposable(T obj, Action<T> dispose)
{
value = obj;
m_Dispose = dispose;
}
public T value
{
get;
private set;
}
public void Dispose()
{
if (m_Dispose == null)
return;
if (value != null)
m_Dispose(value);
m_Dispose = null;
value = null;
}
}
// Convenience API for scoped objects.
internal static class Scoped
{
public static ScopedDisposable<T> Object<T>(T obj) where T : UnityEngine.Object
{
#if UNITY_EDITOR
return new ScopedDisposable<T>(obj, UnityEngine.Object.DestroyImmediate);
#else
return new ScopedDisposable<T>(obj, UnityEngine.Object.Destroy);
#endif
}
#if UNITY_EDITOR
public static ScopedDisposable<T> Asset<T>(T obj) where T : UnityEngine.Object
{
return new ScopedDisposable<T>(obj, (o) => AssetDatabase.DeleteAsset(AssetDatabase.GetAssetPath(o)));
}
#endif
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5cd4530e3c9047dea0f1cc17dccbf6ec
timeCreated: 1708678710

View File

@@ -0,0 +1,25 @@
{
"name": "Unity.InputSystem.TestFramework",
"references": [
"Unity.InputSystem",
"UnityEngine.TestRunner",
"UnityEditor.TestRunner"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": true,
"overrideReferences": true,
"precompiledReferences": [
"nunit.framework.dll"
],
"autoReferenced": false,
"defineConstraints": ["UNITY_TESTS_FRAMEWORK"],
"versionDefines": [
{
"name": "com.unity.test-framework",
"expression": "",
"define": "UNITY_TESTS_FRAMEWORK"
}
],
"versionDefines": []
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: dc04f38471c3a459fb4d31124ee9127d
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,492 @@
#if UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN
using System;
using System.Runtime.InteropServices;
/// <summary>
/// Used to send fake input events to Windows via user32.dll.
/// </summary>
internal static class WinUserInput
{
[DllImport("user32.dll")]
public static extern uint SendInput(
uint nInputs,
[MarshalAs(UnmanagedType.LPArray), In] INPUT[] pInputs,
int cbSize);
[StructLayout(LayoutKind.Sequential)]
public struct INPUT
{
public InputType type;
public InputUnion U;
public static int Size
{
get { return Marshal.SizeOf(typeof(INPUT)); }
}
}
[StructLayout(LayoutKind.Explicit)]
public struct InputUnion
{
[FieldOffset(0)]
public MOUSEINPUT mi;
[FieldOffset(0)]
public KEYBDINPUT ki;
[FieldOffset(0)]
public HARDWAREINPUT hi;
}
[StructLayout(LayoutKind.Sequential)]
public struct MOUSEINPUT
{
public int dx;
public int dy;
public MouseEventDataXButtons mouseData;
public MOUSEEVENTF dwFlags;
public uint time;
public UIntPtr dwExtraInfo;
}
public enum InputType : uint
{
INPUT_MOUSE,
INPUT_KEYBOARD,
INPUT_HARDWARE
}
[Flags]
public enum MouseEventDataXButtons : uint
{
Nothing = 0x00000000,
XBUTTON1 = 0x00000001,
XBUTTON2 = 0x00000002
}
[Flags]
public enum MOUSEEVENTF : uint
{
ABSOLUTE = 0x8000,
HWHEEL = 0x01000,
MOVE = 0x0001,
MOVE_NOCOALESCE = 0x2000,
LEFTDOWN = 0x0002,
LEFTUP = 0x0004,
RIGHTDOWN = 0x0008,
RIGHTUP = 0x0010,
MIDDLEDOWN = 0x0020,
MIDDLEUP = 0x0040,
VIRTUALDESK = 0x4000,
WHEEL = 0x0800,
XDOWN = 0x0080,
XUP = 0x0100
}
[StructLayout(LayoutKind.Sequential)]
public struct KEYBDINPUT
{
public VirtualKeyShort wVk;
public ScanCodeShort wScan;
public KEYEVENTF dwFlags;
public int time;
public UIntPtr dwExtraInfo;
}
[Flags]
public enum KEYEVENTF : uint
{
EXTENDEDKEY = 0x0001,
KEYUP = 0x0002,
SCANCODE = 0x0008,
UNICODE = 0x0004
}
public enum VirtualKeyShort : short
{
LBUTTON = 0x01,
RBUTTON = 0x02,
CANCEL = 0x03,
MBUTTON = 0x04,
XBUTTON1 = 0x05,
XBUTTON2 = 0x06,
BACK = 0x08,
TAB = 0x09,
CLEAR = 0x0C,
RETURN = 0x0D,
SHIFT = 0x10,
CONTROL = 0x11,
MENU = 0x12,
PAUSE = 0x13,
CAPITAL = 0x14,
KANA = 0x15,
HANGUL = 0x15,
JUNJA = 0x17,
FINAL = 0x18,
HANJA = 0x19,
KANJI = 0x19,
ESCAPE = 0x1B,
CONVERT = 0x1C,
NONCONVERT = 0x1D,
ACCEPT = 0x1E,
MODECHANGE = 0x1F,
SPACE = 0x20,
PRIOR = 0x21,
NEXT = 0x22,
END = 0x23,
HOME = 0x24,
LEFT = 0x25,
UP = 0x26,
RIGHT = 0x27,
DOWN = 0x28,
SELECT = 0x29,
PRINT = 0x2A,
EXECUTE = 0x2B,
SNAPSHOT = 0x2C,
INSERT = 0x2D,
DELETE = 0x2E,
HELP = 0x2F,
KEY_0 = 0x30,
KEY_1 = 0x31,
KEY_2 = 0x32,
KEY_3 = 0x33,
KEY_4 = 0x34,
KEY_5 = 0x35,
KEY_6 = 0x36,
KEY_7 = 0x37,
KEY_8 = 0x38,
KEY_9 = 0x39,
KEY_A = 0x41,
KEY_B = 0x42,
KEY_C = 0x43,
KEY_D = 0x44,
KEY_E = 0x45,
KEY_F = 0x46,
KEY_G = 0x47,
KEY_H = 0x48,
KEY_I = 0x49,
KEY_J = 0x4A,
KEY_K = 0x4B,
KEY_L = 0x4C,
KEY_M = 0x4D,
KEY_N = 0x4E,
KEY_O = 0x4F,
KEY_P = 0x50,
KEY_Q = 0x51,
KEY_R = 0x52,
KEY_S = 0x53,
KEY_T = 0x54,
KEY_U = 0x55,
KEY_V = 0x56,
KEY_W = 0x57,
KEY_X = 0x58,
KEY_Y = 0x59,
KEY_Z = 0x5A,
LWIN = 0x5B,
RWIN = 0x5C,
APPS = 0x5D,
SLEEP = 0x5F,
NUMPAD0 = 0x60,
NUMPAD1 = 0x61,
NUMPAD2 = 0x62,
NUMPAD3 = 0x63,
NUMPAD4 = 0x64,
NUMPAD5 = 0x65,
NUMPAD6 = 0x66,
NUMPAD7 = 0x67,
NUMPAD8 = 0x68,
NUMPAD9 = 0x69,
MULTIPLY = 0x6A,
ADD = 0x6B,
SEPARATOR = 0x6C,
SUBTRACT = 0x6D,
DECIMAL = 0x6E,
DIVIDE = 0x6F,
F1 = 0x70,
F2 = 0x71,
F3 = 0x72,
F4 = 0x73,
F5 = 0x74,
F6 = 0x75,
F7 = 0x76,
F8 = 0x77,
F9 = 0x78,
F10 = 0x79,
F11 = 0x7A,
F12 = 0x7B,
F13 = 0x7C,
F14 = 0x7D,
F15 = 0x7E,
F16 = 0x7F,
F17 = 0x80,
F18 = 0x81,
F19 = 0x82,
F20 = 0x83,
F21 = 0x84,
F22 = 0x85,
F23 = 0x86,
F24 = 0x87,
NUMLOCK = 0x90,
SCROLL = 0x91,
LSHIFT = 0xA0,
RSHIFT = 0xA1,
LCONTROL = 0xA2,
RCONTROL = 0xA3,
LMENU = 0xA4,
RMENU = 0xA5,
BROWSER_BACK = 0xA6,
BROWSER_FORWARD = 0xA7,
BROWSER_REFRESH = 0xA8,
BROWSER_STOP = 0xA9,
BROWSER_SEARCH = 0xAA,
BROWSER_FAVORITES = 0xAB,
BROWSER_HOME = 0xAC,
VOLUME_MUTE = 0xAD,
VOLUME_DOWN = 0xAE,
VOLUME_UP = 0xAF,
MEDIA_NEXT_TRACK = 0xB0,
MEDIA_PREV_TRACK = 0xB1,
MEDIA_STOP = 0xB2,
MEDIA_PLAY_PAUSE = 0xB3,
LAUNCH_MAIL = 0xB4,
LAUNCH_MEDIA_SELECT = 0xB5,
LAUNCH_APP1 = 0xB6,
LAUNCH_APP2 = 0xB7,
OEM_1 = 0xBA,
OEM_PLUS = 0xBB,
OEM_COMMA = 0xBC,
OEM_MINUS = 0xBD,
OEM_PERIOD = 0xBE,
OEM_2 = 0xBF,
OEM_3 = 0xC0,
OEM_4 = 0xDB,
OEM_5 = 0xDC,
OEM_6 = 0xDD,
OEM_7 = 0xDE,
OEM_8 = 0xDF,
OEM_102 = 0xE2,
PROCESSKEY = 0xE5,
PACKET = 0xE7,
ATTN = 0xF6,
CRSEL = 0xF7,
EXSEL = 0xF8,
EREOF = 0xF9,
PLAY = 0xFA,
ZOOM = 0xFB,
NONAME = 0xFC,
PA1 = 0xFD,
OEM_CLEAR = 0xFE
}
public enum ScanCodeShort : short
{
LBUTTON = 0,
RBUTTON = 0,
CANCEL = 70,
MBUTTON = 0,
XBUTTON1 = 0,
XBUTTON2 = 0,
BACK = 14,
TAB = 15,
CLEAR = 76,
RETURN = 28,
SHIFT = 42,
CONTROL = 29,
MENU = 56,
PAUSE = 0,
CAPITAL = 58,
KANA = 0,
HANGUL = 0,
JUNJA = 0,
FINAL = 0,
HANJA = 0,
KANJI = 0,
ESCAPE = 1,
CONVERT = 0,
NONCONVERT = 0,
ACCEPT = 0,
MODECHANGE = 0,
SPACE = 57,
PRIOR = 73,
NEXT = 81,
END = 79,
HOME = 71,
LEFT = 75,
UP = 72,
RIGHT = 77,
DOWN = 80,
SELECT = 0,
PRINT = 0,
EXECUTE = 0,
SNAPSHOT = 84,
INSERT = 82,
DELETE = 83,
HELP = 99,
KEY_0 = 11,
KEY_1 = 2,
KEY_2 = 3,
KEY_3 = 4,
KEY_4 = 5,
KEY_5 = 6,
KEY_6 = 7,
KEY_7 = 8,
KEY_8 = 9,
KEY_9 = 10,
KEY_A = 30,
KEY_B = 48,
KEY_C = 46,
KEY_D = 32,
KEY_E = 18,
KEY_F = 33,
KEY_G = 34,
KEY_H = 35,
KEY_I = 23,
KEY_J = 36,
KEY_K = 37,
KEY_L = 38,
KEY_M = 50,
KEY_N = 49,
KEY_O = 24,
KEY_P = 25,
KEY_Q = 16,
KEY_R = 19,
KEY_S = 31,
KEY_T = 20,
KEY_U = 22,
KEY_V = 47,
KEY_W = 17,
KEY_X = 45,
KEY_Y = 21,
KEY_Z = 44,
LWIN = 91,
RWIN = 92,
APPS = 93,
SLEEP = 95,
NUMPAD0 = 82,
NUMPAD1 = 79,
NUMPAD2 = 80,
NUMPAD3 = 81,
NUMPAD4 = 75,
NUMPAD5 = 76,
NUMPAD6 = 77,
NUMPAD7 = 71,
NUMPAD8 = 72,
NUMPAD9 = 73,
MULTIPLY = 55,
ADD = 78,
SEPARATOR = 0,
SUBTRACT = 74,
DECIMAL = 83,
DIVIDE = 53,
F1 = 59,
F2 = 60,
F3 = 61,
F4 = 62,
F5 = 63,
F6 = 64,
F7 = 65,
F8 = 66,
F9 = 67,
F10 = 68,
F11 = 87,
F12 = 88,
F13 = 100,
F14 = 101,
F15 = 102,
F16 = 103,
F17 = 104,
F18 = 105,
F19 = 106,
F20 = 107,
F21 = 108,
F22 = 109,
F23 = 110,
F24 = 118,
NUMLOCK = 69,
SCROLL = 70,
LSHIFT = 42,
RSHIFT = 54,
LCONTROL = 29,
RCONTROL = 29,
LMENU = 56,
RMENU = 56,
BROWSER_BACK = 106,
BROWSER_FORWARD = 105,
BROWSER_REFRESH = 103,
BROWSER_STOP = 104,
BROWSER_SEARCH = 101,
BROWSER_FAVORITES = 102,
BROWSER_HOME = 50,
VOLUME_MUTE = 32,
VOLUME_DOWN = 46,
VOLUME_UP = 48,
MEDIA_NEXT_TRACK = 25,
MEDIA_PREV_TRACK = 16,
MEDIA_STOP = 36,
MEDIA_PLAY_PAUSE = 34,
LAUNCH_MAIL = 108,
LAUNCH_MEDIA_SELECT = 109,
LAUNCH_APP1 = 107,
LAUNCH_APP2 = 33,
OEM_1 = 39,
OEM_PLUS = 13,
OEM_COMMA = 51,
OEM_MINUS = 12,
OEM_PERIOD = 52,
OEM_2 = 53,
OEM_3 = 41,
OEM_4 = 26,
OEM_5 = 43,
OEM_6 = 27,
OEM_7 = 40,
OEM_8 = 0,
OEM_102 = 86,
PROCESSKEY = 0,
PACKET = 0,
ATTN = 0,
CRSEL = 0,
EXSEL = 0,
EREOF = 93,
PLAY = 0,
ZOOM = 98,
NONAME = 0,
PA1 = 0,
OEM_CLEAR = 0,
}
[StructLayout(LayoutKind.Sequential)]
public struct HARDWAREINPUT
{
public int uMsg;
public short wParamL;
public short wParamH;
}
/// <summary>
/// Emulate an event sent over RDP.
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
public static void SendRDPMouseMoveEvent(int x, int y)
{
var mouseInput = new INPUT { type = InputType.INPUT_MOUSE };
mouseInput.U.mi.dx = x;
mouseInput.U.mi.dy = y;
mouseInput.U.mi.dwFlags = MOUSEEVENTF.ABSOLUTE | MOUSEEVENTF.VIRTUALDESK | MOUSEEVENTF.MOVE;
SendInput(1, new INPUT[] { mouseInput }, INPUT.Size);
}
/// <summary>
/// Emulate a normal mouse move event with relative coordinates.
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
public static void SendMouseMoveEvent(int x, int y)
{
var mouseInput = new INPUT { type = InputType.INPUT_MOUSE };
mouseInput.U.mi.dx = x;
mouseInput.U.mi.dy = y;
mouseInput.U.mi.dwFlags = MOUSEEVENTF.MOVE;
SendInput(1, new INPUT[] { mouseInput }, INPUT.Size);
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e2c8138bd42a9a041bb4b1398ae819d8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: