#if ULOOPMCP_HAS_INPUT_SYSTEM #nullable enable using System.Collections.Generic; using UnityEditor; using UnityEngine.InputSystem; using UnityEngine.InputSystem.LowLevel; namespace io.github.hatayama.uLoopMCP { // Tool instances are created fresh per invocation, so held-key state // between KeyDown/KeyUp must be held statically. [InitializeOnLoad] internal static class KeyboardKeyState { private static readonly HashSet _heldKeys = new(); private static readonly HashSet _transientKeys = new(); public static bool IsKeyHeld(Key key) => _heldKeys.Contains(key); public static IReadOnlyCollection HeldKeys => _heldKeys; static KeyboardKeyState() { // Guard against duplicate subscriptions on domain reload EditorApplication.playModeStateChanged -= OnPlayModeStateChanged; EditorApplication.playModeStateChanged += OnPlayModeStateChanged; } public static void SetKeyDown(Key key) { _heldKeys.Add(key); } public static void SetKeyUp(Key key) { _heldKeys.Remove(key); } public static void RegisterTransientKey(Key key) { _transientKeys.Add(key); } public static void UnregisterTransientKey(Key key) { _transientKeys.Remove(key); } public static void Clear() { _heldKeys.Clear(); _transientKeys.Clear(); } // Keyboard keys are stored as a bitfield, so StateEvent.From captures // the entire keyboard state. To support simultaneous key holds, we write // ALL currently held keys into every event — not just the target key. public static void SetKeyState(Keyboard keyboard, Key key, bool pressed) { InputUpdateType updateType = InputUpdateTypeResolver.Resolve(); using (StateEvent.From(keyboard, out InputEventPtr eventPtr)) { foreach (Key heldKey in _heldKeys) { keyboard[heldKey].WriteValueIntoEvent(1f, eventPtr); } foreach (Key transientKey in _transientKeys) { keyboard[transientKey].WriteValueIntoEvent(1f, eventPtr); } keyboard[key].WriteValueIntoEvent(pressed ? 1f : 0f, eventPtr); // Updating player state directly avoids editor focus-dependent routing. InputState.Change(keyboard, eventPtr, updateType); } } public static void ReleaseAllKeys() { Keyboard keyboard = Keyboard.current; if (keyboard == null) { _heldKeys.Clear(); _transientKeys.Clear(); return; } // Single event with all keys released using (StateEvent.From(keyboard, out InputEventPtr eventPtr)) { foreach (Key key in _heldKeys) { keyboard[key].WriteValueIntoEvent(0f, eventPtr); } foreach (Key key in _transientKeys) { keyboard[key].WriteValueIntoEvent(0f, eventPtr); } InputUpdateType updateType = InputUpdateTypeResolver.Resolve(); InputState.Change(keyboard, eventPtr, updateType); } _heldKeys.Clear(); _transientKeys.Clear(); } private static void OnPlayModeStateChanged(PlayModeStateChange state) { if (state == PlayModeStateChange.ExitingPlayMode) { ReleaseAllKeys(); SimulateKeyboardOverlayState.Clear(); } } } } #endif