Files
ft/Client/Packages/io.github.hatayama.uloopmcp/Editor/Api/McpTools/SimulateKeyboard/KeyboardKeyState.cs

120 lines
3.8 KiB
C#

#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<Key> _heldKeys = new();
private static readonly HashSet<Key> _transientKeys = new();
public static bool IsKeyHeld(Key key) => _heldKeys.Contains(key);
public static IReadOnlyCollection<Key> 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