// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE.Host;
namespace SharpEmu.Libs.Pad;
/// Cross-platform input state supplied by the SDL game window.
public static class HostWindowInput
{
private static readonly object Gate = new();
private static readonly HashSet PressedKeys = new();
private static bool _focused;
private static bool _gamepadConnected;
private static string? _gamepadName;
private static HostGamepadState _gamepadState;
private static IHostGamepadOutput? _gamepadOutput;
private static readonly WindowInputSource Source = new();
public static void Connect(IHostGamepadOutput? gamepadOutput = null)
{
lock (Gate)
{
_focused = true;
_gamepadOutput = gamepadOutput;
PressedKeys.Clear();
}
HostWindowInputSource.Set(Source);
}
public static void Disconnect()
{
lock (Gate)
{
_focused = false;
_gamepadConnected = false;
_gamepadName = null;
_gamepadState = default;
_gamepadOutput = null;
PressedKeys.Clear();
}
HostWindowInputSource.Clear(Source);
}
public static void SetFocused(bool focused)
{
lock (Gate)
{
_focused = focused;
if (!focused)
{
PressedKeys.Clear();
}
}
}
public static void SetKey(int virtualKey, bool down)
{
lock (Gate)
{
if (down)
{
PressedKeys.Add(virtualKey);
}
else
{
PressedKeys.Remove(virtualKey);
}
}
}
public static void SetGamepad(string? name, HostGamepadState state)
{
lock (Gate)
{
_gamepadConnected = state.Connected;
_gamepadName = name;
_gamepadState = state;
}
}
public static void ClearGamepad()
{
lock (Gate)
{
_gamepadConnected = false;
_gamepadName = null;
_gamepadState = default;
}
}
internal static byte ToStickByte(short value)
{
var normalized = value + 32768;
return (byte)Math.Clamp((normalized * 255 + 32767) / 65535, 0, 255);
}
internal static byte ToTriggerByte(short value) =>
(byte)Math.Clamp(value * 255 / 32767, 0, 255);
private sealed class WindowInputSource : IHostWindowInputSource
{
public bool HasKeyboardFocus
{
get
{
lock (Gate)
{
return _focused;
}
}
}
public bool IsKeyDown(int virtualKey)
{
lock (Gate)
{
return PressedKeys.Contains(virtualKey);
}
}
public int GetGamepadStates(Span destination)
{
lock (Gate)
{
if (!_gamepadConnected || destination.IsEmpty)
{
return 0;
}
destination[0] = _gamepadState;
return 1;
}
}
public string? DescribeConnectedGamepad()
{
lock (Gate)
{
return _gamepadConnected ? _gamepadName ?? "SDL gamepad" : null;
}
}
public void SetRumble(byte largeMotor, byte smallMotor)
{
IHostGamepadOutput? output;
lock (Gate)
{
output = _gamepadOutput;
}
output?.SetRumble(largeMotor, smallMotor);
}
public void SetTriggerRumble(byte? leftTrigger, byte? rightTrigger)
{
IHostGamepadOutput? output;
lock (Gate)
{
output = _gamepadOutput;
}
output?.SetTriggerRumble(leftTrigger, rightTrigger);
}
public void SetAdaptiveTriggerEffect(
HostAdaptiveTriggerEffect? leftTrigger,
HostAdaptiveTriggerEffect? rightTrigger)
{
IHostGamepadOutput? output;
lock (Gate)
{
output = _gamepadOutput;
}
output?.SetAdaptiveTriggerEffect(leftTrigger, rightTrigger);
}
public void SetLightbar(byte red, byte green, byte blue)
{
IHostGamepadOutput? output;
lock (Gate)
{
output = _gamepadOutput;
}
output?.SetLightbar(red, green, blue);
}
public void ResetLightbar()
{
IHostGamepadOutput? output;
lock (Gate)
{
output = _gamepadOutput;
}
output?.ResetLightbar();
}
}
}
public interface IHostGamepadOutput
{
void SetRumble(byte largeMotor, byte smallMotor);
void SetTriggerRumble(byte? leftTrigger, byte? rightTrigger);
void SetAdaptiveTriggerEffect(
HostAdaptiveTriggerEffect? leftTrigger,
HostAdaptiveTriggerEffect? rightTrigger);
void SetLightbar(byte red, byte green, byte blue);
void ResetLightbar();
}