diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
index 03efa87b74..393d187924 100644
--- a/.config/dotnet-tools.json
+++ b/.config/dotnet-tools.json
@@ -3,10 +3,11 @@
"isRoot": true,
"tools": {
"csharpier": {
- "version": "0.29.2",
+ "version": "0.30.6",
"commands": [
"dotnet-csharpier"
- ]
+ ],
+ "rollForward": false
}
}
}
\ No newline at end of file
diff --git a/Silk.NET.sln b/Silk.NET.sln
index 17850fdc6f..364d408733 100644
--- a/Silk.NET.sln
+++ b/Silk.NET.sln
@@ -102,6 +102,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Windowing", "Windowing", "{
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Silk.NET.Windowing", "sources\Windowing\Windowing\Silk.NET.Windowing.csproj", "{EF07CBB5-D253-4CA9-A5DA-8B3DF2B0DF8E}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Input", "Input", "{33ED9765-8C36-4A9D-95E8-AF037FE104B3}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Silk.NET.Input", "sources\Input\Input\Silk.NET.Input.csproj", "{49A42CE3-94C5-4239-B0FC-F1FF8D7AAADA}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Input", "Input", "{4E0EF53A-76BC-4729-8E3B-4768E86E357E}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Silk.NET.Input.UnitTests", "tests\Input\Input\Silk.NET.Input.UnitTests.csproj", "{00B9B6E6-776E-480C-B3ED-D6420C5B4E8E}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -168,6 +176,14 @@ Global
{EF07CBB5-D253-4CA9-A5DA-8B3DF2B0DF8E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EF07CBB5-D253-4CA9-A5DA-8B3DF2B0DF8E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EF07CBB5-D253-4CA9-A5DA-8B3DF2B0DF8E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {49A42CE3-94C5-4239-B0FC-F1FF8D7AAADA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {49A42CE3-94C5-4239-B0FC-F1FF8D7AAADA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {49A42CE3-94C5-4239-B0FC-F1FF8D7AAADA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {49A42CE3-94C5-4239-B0FC-F1FF8D7AAADA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {00B9B6E6-776E-480C-B3ED-D6420C5B4E8E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {00B9B6E6-776E-480C-B3ED-D6420C5B4E8E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {00B9B6E6-776E-480C-B3ED-D6420C5B4E8E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {00B9B6E6-776E-480C-B3ED-D6420C5B4E8E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -200,6 +216,10 @@ Global
{F16C0AB9-DE7E-4C09-9EE9-DAA8B8E935A6} = {EC4D7B06-D277-4411-BD7B-71A6D37683F0}
{FE4414F8-5370-445D-9F24-C3AD3223F299} = {DD29EA8F-B1A6-45AA-8D2E-B38DA56D9EF6}
{EF07CBB5-D253-4CA9-A5DA-8B3DF2B0DF8E} = {FE4414F8-5370-445D-9F24-C3AD3223F299}
+ {33ED9765-8C36-4A9D-95E8-AF037FE104B3} = {DD29EA8F-B1A6-45AA-8D2E-B38DA56D9EF6}
+ {49A42CE3-94C5-4239-B0FC-F1FF8D7AAADA} = {33ED9765-8C36-4A9D-95E8-AF037FE104B3}
+ {4E0EF53A-76BC-4729-8E3B-4768E86E357E} = {A5578D12-9E77-4647-8C22-0DBD17760BFF}
+ {00B9B6E6-776E-480C-B3ED-D6420C5B4E8E} = {4E0EF53A-76BC-4729-8E3B-4768E86E357E}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {78D2CF6A-60A1-43E3-837B-00B73C9DA384}
diff --git a/docs/silk.net/diagnostics/ST0001.md b/docs/silk.net/diagnostics/ST0001.md
new file mode 100644
index 0000000000..f04906d857
--- /dev/null
+++ b/docs/silk.net/diagnostics/ST0001.md
@@ -0,0 +1,21 @@
+# ST0001 - ProcessClass failure
+
+## Overview
+
+This internal error was raised by SilkTouch when failing to generate an implementation for a binding at source
+generation time. It provided details regarding the exception that led to the entire native API class failing to have its
+implementation generated.
+
+| Attribute | Value |
+|--------------------|----------------------|
+| Diagnostic ID | ST0001 |
+| Title | ProcessClass failure |
+| Category | SilkTouch.Internal |
+| Default Severity | Error |
+| Enabled by Default | Yes |
+
+Example message: `ProcessClass failed. Exception: '...'`
+
+## Explanation & Solutions
+
+This functionality is no longer supported in 3.0, where this diagnostic is never raised.
diff --git a/docs/silk.net/diagnostics/ST0002.md b/docs/silk.net/diagnostics/ST0002.md
new file mode 100644
index 0000000000..89742a49a7
--- /dev/null
+++ b/docs/silk.net/diagnostics/ST0002.md
@@ -0,0 +1,21 @@
+# ST0002 - MethodClass failure
+
+## Overview
+
+This internal error was raised by SilkTouch when failing to generate an implementation for a binding at source
+generation time. It provided details regarding the exception that led to a specific native API method failing to have
+its implementation generated.
+
+| Attribute | Value |
+|--------------------|---------------------|
+| Diagnostic ID | ST0002 |
+| Title | MethodClass failure |
+| Category | SilkTouch.Internal |
+| Default Severity | Error |
+| Enabled by Default | Yes |
+
+Example message: `MethodClass failed. Exception: '...'`
+
+## Explanation & Solutions
+
+This functionality is no longer supported in 3.0, where this diagnostic is never raised.
diff --git a/docs/silk.net/diagnostics/ST0003.md b/docs/silk.net/diagnostics/ST0003.md
new file mode 100644
index 0000000000..d722f5f6d7
--- /dev/null
+++ b/docs/silk.net/diagnostics/ST0003.md
@@ -0,0 +1,20 @@
+# ST0003 - Silk.NET.Core is Missing
+
+## Overview
+
+This internal diagnostic was raised by SilkTouch when failing to generate an implementation for bindings at source
+generation time due to the binding project missing a reference to Silk.NET.Core.
+
+| Attribute | Value |
+|--------------------|---------------------|
+| Diagnostic ID | ST0003 |
+| Title | MethodClass failure |
+| Category | SilkTouch.Internal |
+| Default Severity | Info |
+| Enabled by Default | Yes |
+
+Example message: `Silk.NET.Core is missing from references. You should use SilkTouch with Silk.NET.Core`
+
+## Explanation & Solutions
+
+This functionality is no longer supported in 3.0, where this diagnostic is never raised.
diff --git a/docs/silk.net/diagnostics/ST0004.md b/docs/silk.net/diagnostics/ST0004.md
new file mode 100644
index 0000000000..4680d3faa3
--- /dev/null
+++ b/docs/silk.net/diagnostics/ST0004.md
@@ -0,0 +1,20 @@
+# ST0004 - Build Info
+
+## Overview
+
+This internal diagnostic was raised by SilkTouch when configured to do so. It provided diagnostic information relating
+to the performance and characteristics of SilkTouch's internals.
+
+| Attribute | Value |
+|--------------------|--------------------|
+| Diagnostic ID | ST0004 |
+| Title | Build Info |
+| Category | SilkTouch.Internal |
+| Default Severity | Warning |
+| Enabled by Default | Yes |
+
+Example message: `GCSlotCount: '127'. Time: '6437ms'`
+
+## Explanation & Solutions
+
+This functionality is no longer supported in 3.0, where this diagnostic is never raised.
diff --git a/docs/silk.net/diagnostics/ST0005.md b/docs/silk.net/diagnostics/ST0005.md
new file mode 100644
index 0000000000..8a7c730766
--- /dev/null
+++ b/docs/silk.net/diagnostics/ST0005.md
@@ -0,0 +1,15 @@
+# ST0005 - Intentionally Unstable API
+
+## Overview
+
+This diagnostic is raised when trying to use a Silk.NET API that has been marked with the `Experimental` attribute due
+to its API and/or ABI being unstable. When this diagnostic ID is used, it indicates that it is intentional that this is
+the case and that this API is extremely unlikely to ever graduate to a stable, versioned API.
+
+## Explanation & Solutions
+
+Typically, APIs meeting this description are internal APIs and are not intended for use outside of the assembly they're
+defined in. As a result, where this diagnostic is raised, you should cease use of this API or at least only continue if
+you can guarantee that you will never update Silk.NET ever again and that your downstream consumers, if applicable, will
+lock their version to the same version referenced by your project. However, please reconsider use of the API if this is
+the case.
diff --git a/eng/build/Silk.NET.NUKE.csproj b/eng/build/Silk.NET.NUKE.csproj
index 1f3ae0857e..2a1f1a1c28 100644
--- a/eng/build/Silk.NET.NUKE.csproj
+++ b/eng/build/Silk.NET.NUKE.csproj
@@ -18,4 +18,10 @@
+
+
+
+ Directory.Build\tests\Input\Input\Silk.NET.Input.UnitTests.csproj
+
+
diff --git a/sources/Input/Input/Button.cs b/sources/Input/Input/Button.cs
new file mode 100644
index 0000000000..1ae8875c65
--- /dev/null
+++ b/sources/Input/Input/Button.cs
@@ -0,0 +1,24 @@
+namespace Silk.NET.Input;
+
+///
+/// Represents a button the user can push.
+///
+/// The name of the button.
+/// Whether the user is pushing the button.
+///
+/// The pressure with which the user is pushing the button, where 0.0 is the smallest measurable pressure and
+/// 1.0 is the largest measurable pressure.
+///
+///
+/// The button type (e.g. , , etc).
+///
+public readonly record struct Button(T Name, bool IsDown, float Pressure)
+ where T : unmanaged, Enum
+{
+ ///
+ /// Collapses this struct into just its value.
+ ///
+ /// The button state.
+ /// The value.
+ public static implicit operator bool(Button state) => state.IsDown;
+}
diff --git a/sources/Input/Input/ButtonChangedEvent.cs b/sources/Input/Input/ButtonChangedEvent.cs
new file mode 100644
index 0000000000..b030762606
--- /dev/null
+++ b/sources/Input/Input/ButtonChangedEvent.cs
@@ -0,0 +1,21 @@
+using System.Diagnostics;
+
+namespace Silk.NET.Input;
+
+///
+/// Contains information pertaining to a button state change (e.g. press, depress, etc).
+///
+/// The device on which the button being pressed or depressed resides.
+///
+/// The timestamp (as retrieved from ) at which the event occurred.
+///
+/// The new state of the button being pressed or depressed.
+/// The previous state of the button.
+/// The button type e.g. , , etc.
+public readonly record struct ButtonChangedEvent(
+ IButtonDevice Device,
+ long Timestamp,
+ Button Button,
+ Button Previous
+)
+ where T : unmanaged, Enum;
diff --git a/sources/Input/Input/ButtonReadOnlyList.cs b/sources/Input/Input/ButtonReadOnlyList.cs
new file mode 100644
index 0000000000..f93504197c
--- /dev/null
+++ b/sources/Input/Input/ButtonReadOnlyList.cs
@@ -0,0 +1,43 @@
+using System.Collections;
+
+namespace Silk.NET.Input;
+
+///
+/// An implementation of providing utility APIs for getting a
+/// given a button name , that is optimised for storing s with the
+/// given button name type using the most memory-efficient mechanism available.
+///
+///
+/// The button type (e.g. , , etc).
+///
+public struct ButtonReadOnlyList : IReadOnlyList>
+ where T : unmanaged, Enum
+{
+ private InputReadOnlyList> _list;
+
+ internal ButtonReadOnlyList(InputReadOnlyList> list) => _list = list;
+
+ ///
+ /// Creates an from a .
+ ///
+ /// The list to copy.
+ public ButtonReadOnlyList(IReadOnlyList> other) =>
+ InputMarshal.Clone(other).List.AsButtonList();
+
+ ///
+ /// Gets the state for the button with the given name.
+ ///
+ /// The button name.
+ public Button this[T name] => InputMarshal.GetButtonState(_list, name);
+
+ ///
+ public IEnumerator> GetEnumerator() => _list.GetEnumerator();
+
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
+ ///
+ public int Count => _list.Count;
+
+ ///
+ public Button this[int index] => _list[index];
+}
diff --git a/sources/Input/Input/ConnectionEvent.cs b/sources/Input/Input/ConnectionEvent.cs
new file mode 100644
index 0000000000..2da787cc70
--- /dev/null
+++ b/sources/Input/Input/ConnectionEvent.cs
@@ -0,0 +1,13 @@
+using System.Diagnostics;
+
+namespace Silk.NET.Input;
+
+///
+/// Contains information pertaining to a device connection or disconnection event.
+///
+/// The device that has disconnected or connected.
+///
+/// The timestamp (as retrieved from ) at which the event occurred.
+///
+/// Whether the device has connected (true ) or disconnected (false ).
+public readonly record struct ConnectionEvent(IInputDevice Device, long Timestamp, bool IsConnected);
\ No newline at end of file
diff --git a/sources/Input/Input/CursorModes.cs b/sources/Input/Input/CursorModes.cs
new file mode 100644
index 0000000000..a5ce021ea8
--- /dev/null
+++ b/sources/Input/Input/CursorModes.cs
@@ -0,0 +1,57 @@
+namespace Silk.NET.Input;
+
+///
+/// Enumerates the modes in which a mouse cursor can operate.
+///
+///
+/// implementations for implementations typically have two
+/// :
+///
+/// -
+///
Bounded
+///
+/// An that is bounded to the desktop environment i.e. the
+/// are not infinite and reflect the total screen space that is available to the
+/// running application in window coordinates. This is typically the sum of all monitor resolutions, with the positions
+/// being defined using an implementation-defined mechanism. The window bounds operate in this same coordinate space.
+/// It is highly unlikely that you will be unable to determine the individual points for multiple mice on this target,
+/// as desktop environments typically aggregate all movement from all mice into a single .
+/// This target is used for every cursor mode except .
+///
+///
+/// -
+///
Unbounded
+///
+/// An that is unbounded and operates in an arbitrary coordinate space. This target is used
+/// for raw mouse mode and points on this target represent the net mouse movement from a mouse. Implementations
+/// are more likely to be able to give multiple s for each mouse when this target is used. This
+/// target is used when the cursor mode is enabled. will
+/// represent an infinitely large unbounded target.
+///
+///
+///
+///
+[Flags]
+public enum CursorModes
+{
+ ///
+ /// The cursor is visible to the user and operating within the bounds of the desktop environment . The
+ /// coordinates received are in desktop coordinates, operating in the same coordinate space as the window
+ /// position/size.
+ ///
+ Normal = 1 << 0,
+
+ ///
+ /// The cursor is visible to the user but is constrained to the window's client area . The coordinates
+ /// received are in desktop coordinates, operating in the same coordinate space as the window position/size.
+ /// The bounded to the desktop environment is used.
+ ///
+ Confined = 1 << 1,
+
+ ///
+ /// The cursor is invisible to the user and is unconstrained/unbounded . The coordinates received are
+ /// arbitrary values that have no bounds representing the net mouse movement since entering into this cursor mode.
+ /// The unbounded is used. This is the equivalent of raw mouse mode .
+ ///
+ Unbounded = 1 << 2,
+}
\ No newline at end of file
diff --git a/sources/Input/Input/CursorStyles.cs b/sources/Input/Input/CursorStyles.cs
new file mode 100644
index 0000000000..65ecfc6f55
--- /dev/null
+++ b/sources/Input/Input/CursorStyles.cs
@@ -0,0 +1,61 @@
+namespace Silk.NET.Input;
+
+///
+/// Enumerates the cursor styles with which the desktop environment should render the cursor.
+///
+[Flags]
+public enum CursorStyles
+{
+ ///
+ /// The cursor should be rendered using its default image.
+ ///
+ Default,
+
+ ///
+ /// The cursor should be rendered using an arrow cursor image.
+ ///
+ Arrow = 1 << 0,
+
+ ///
+ /// The cursor should be rendered using an I-beam cursor image, which is used to show where the text cursor appears
+ /// when the mouse is clicked.
+ ///
+ IBeam = 1 << 1,
+
+ ///
+ /// The cursor should be rendered using a crosshair cursor image.
+ ///
+ Crosshair = 1 << 2,
+
+ ///
+ /// The cursor should be rendered using a hand cursor image, typically used when hovering over a web link.
+ ///
+ Hand = 1 << 3,
+
+ ///
+ /// The cursor should be rendered using a two-headed horizontal sizing cursor image.
+ ///
+ HResize = 1 << 4,
+
+ ///
+ /// The cursor should be rendered using a two-headed vertical sizing cursor image.
+ ///
+ VResize = 1 << 5,
+
+ ///
+ /// The cursor should not be rendered.
+ ///
+ ///
+ /// When is used, the cursor ceases to exist anyway. As such, while the
+ /// property may not reflect this (as it is retained across changes to
+ /// and just ignored when is used),
+ /// can be implied as being when
+ /// is used.
+ ///
+ Hidden = 1 << 6,
+
+ ///
+ /// The cursor should be rendered using a custom application-provided image.
+ ///
+ Custom = 1 << 7,
+}
\ No newline at end of file
diff --git a/sources/Input/Input/CustomCursor.cs b/sources/Input/Input/CustomCursor.cs
new file mode 100644
index 0000000000..8780f934af
--- /dev/null
+++ b/sources/Input/Input/CustomCursor.cs
@@ -0,0 +1,22 @@
+namespace Silk.NET.Input;
+
+///
+/// Represents a custom image for a mouse cursor.
+///
+public readonly ref struct CustomCursor
+{
+ ///
+ /// The number of pixels in the X axis.
+ ///
+ public int Width { get; init; }
+
+ ///
+ /// The number of pixels in the Y axis.
+ ///
+ public int Height { get; init; }
+
+ ///
+ /// The row-major 32-bit RGBA pixel data (i.e. 8 bytes for each colour component).
+ ///
+ public ReadOnlySpan Data { get; init; } // Rgba32
+}
\ No newline at end of file
diff --git a/sources/Input/Input/DualReadOnlyList.cs b/sources/Input/Input/DualReadOnlyList.cs
new file mode 100644
index 0000000000..9639703823
--- /dev/null
+++ b/sources/Input/Input/DualReadOnlyList.cs
@@ -0,0 +1,39 @@
+using System.Collections;
+
+namespace Silk.NET.Input;
+
+///
+/// Represents a list that has exactly two elements.
+///
+/// The element type.
+public readonly struct DualReadOnlyList : IReadOnlyList
+{
+ ///
+ /// The first/leftmost element.
+ ///
+ public readonly T Left;
+
+ ///
+ /// The second/rightmost element.
+ ///
+ public readonly T Right;
+
+ ///
+ public IEnumerator GetEnumerator()
+ {
+ yield return Left;
+ yield return Right;
+ }
+
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
+ ///
+ public int Count => 2;
+
+ ///
+ public T this[int index] => index switch {
+ 0 => Left,
+ 1 => Right,
+ _ => throw new IndexOutOfRangeException()
+ };
+}
\ No newline at end of file
diff --git a/sources/Input/Input/GamepadState.cs b/sources/Input/Input/GamepadState.cs
new file mode 100644
index 0000000000..05d0d9bb2a
--- /dev/null
+++ b/sources/Input/Input/GamepadState.cs
@@ -0,0 +1,24 @@
+using System.Numerics;
+
+namespace Silk.NET.Input;
+
+///
+/// Contains user input received from an .
+///
+public class GamepadState
+{
+ ///
+ /// Gets the gamepad button state denoting the buttons being pressed or depressed.
+ ///
+ public ButtonReadOnlyList Buttons { get; }
+
+ ///
+ /// Gets the state of the twin sticks on the gamepad.
+ ///
+ public DualReadOnlyList Thumbsticks { get; }
+
+ ///
+ /// Gets the state of the triggers on the gamepad.
+ ///
+ public DualReadOnlyList Triggers { get; }
+}
\ No newline at end of file
diff --git a/sources/Input/Input/GamepadThumbstickMoveEvent.cs b/sources/Input/Input/GamepadThumbstickMoveEvent.cs
new file mode 100644
index 0000000000..b2ffeef3a3
--- /dev/null
+++ b/sources/Input/Input/GamepadThumbstickMoveEvent.cs
@@ -0,0 +1,17 @@
+using System.Diagnostics;
+using System.Numerics;
+
+namespace Silk.NET.Input;
+
+///
+/// Contains information pertaining to the movement of a thumbstick.
+///
+/// The gamepad on which the thumbstick resides.
+///
+/// The timestamp (as retrieved from ) at which the event occurred.
+///
+///
+/// The new position of the thumbstick, where each axis is between -1.0 and 1.0 .
+///
+/// The change in as a result of this event.
+public readonly record struct GamepadThumbstickMoveEvent(IGamepad Gamepad, long Timestamp, Vector2 Value, Vector2 Delta);
\ No newline at end of file
diff --git a/sources/Input/Input/GamepadTriggerMoveEvent.cs b/sources/Input/Input/GamepadTriggerMoveEvent.cs
new file mode 100644
index 0000000000..0cbca61581
--- /dev/null
+++ b/sources/Input/Input/GamepadTriggerMoveEvent.cs
@@ -0,0 +1,17 @@
+using System.Diagnostics;
+
+namespace Silk.NET.Input;
+
+///
+/// Contains information pertaining to the movement of a trigger.
+///
+/// The gamepad on which the trigger resides.
+///
+/// The timestamp (as retrieved from ) at which the event occurred.
+///
+/// The index of the trigger that has moved.
+///
+/// The new value of the trigger, between 0.0 (fully depressed) and 1.0 (fully pressed).
+///
+/// The change in as a result of this event.
+public readonly record struct GamepadTriggerMoveEvent(IGamepad Gamepad, long Timestamp, int Axis, float Value, float Delta);
\ No newline at end of file
diff --git a/sources/Input/Input/Gamepads.cs b/sources/Input/Input/Gamepads.cs
new file mode 100644
index 0000000000..ce56d20079
--- /dev/null
+++ b/sources/Input/Input/Gamepads.cs
@@ -0,0 +1,43 @@
+namespace Silk.NET.Input;
+
+///
+/// Represents a collection of s from which input events can be received.
+///
+public sealed class Gamepads : InputContextDeviceList, IGamepadInputHandler
+{
+ internal Gamepads(InputContext ctx)
+ : base(ctx) { }
+
+ ///
+ /// Raised when state pertaining to a pushable button on the gamepad changes (e.g. button up, button down).
+ ///
+ public event Action>? ButtonChanged;
+
+ ///
+ /// Raised when a thumbstick on the gamepad moves.
+ ///
+ public event Action? ThumbstickMove;
+
+ ///
+ /// Raised when a trigger on the gamepad moves.
+ ///
+ public event Action? TriggerMove;
+
+ internal void HandleButtonChanged(ButtonChangedEvent @event) =>
+ ButtonChanged?.Invoke(@event);
+
+ void IButtonInputHandler.HandleButtonChanged(
+ ButtonChangedEvent @event
+ ) => HandleButtonChanged(@event);
+
+ internal void HandleThumbstickMove(GamepadThumbstickMoveEvent @event) =>
+ ThumbstickMove?.Invoke(@event);
+
+ void IGamepadInputHandler.HandleThumbstickMove(GamepadThumbstickMoveEvent @event) =>
+ HandleThumbstickMove(@event);
+
+ internal void HandleTriggerMove(GamepadTriggerMoveEvent @event) => TriggerMove?.Invoke(@event);
+
+ void IGamepadInputHandler.HandleTriggerMove(GamepadTriggerMoveEvent @event) =>
+ HandleTriggerMove(@event);
+}
diff --git a/sources/Input/Input/IButtonDevice.cs b/sources/Input/Input/IButtonDevice.cs
new file mode 100644
index 0000000000..70b88af9d3
--- /dev/null
+++ b/sources/Input/Input/IButtonDevice.cs
@@ -0,0 +1,17 @@
+namespace Silk.NET.Input;
+
+///
+/// Represents an input device that has buttons.
+///
+/// The type of buttons the input device has.
+public interface IButtonDevice : IInputDevice
+ where T : unmanaged, Enum
+{
+ ///
+ /// Gets the current button state for this device.
+ ///
+ ///
+ /// Only updated when is called.
+ ///
+ ButtonReadOnlyList State { get; }
+}
diff --git a/sources/Input/Input/IButtonInputHandler.cs b/sources/Input/Input/IButtonInputHandler.cs
new file mode 100644
index 0000000000..0d02d1d675
--- /dev/null
+++ b/sources/Input/Input/IButtonInputHandler.cs
@@ -0,0 +1,15 @@
+namespace Silk.NET.Input;
+
+///
+/// An that also receives events.
+///
+/// The device's button type.
+public interface IButtonInputHandler : IInputHandler
+ where T : unmanaged, Enum
+{
+ ///
+ /// Called when a button's state changes (e.g. button down, button up).
+ ///
+ /// The event details.
+ void HandleButtonChanged(ButtonChangedEvent @event);
+}
diff --git a/sources/Input/Input/ICursorConfiguration.cs b/sources/Input/Input/ICursorConfiguration.cs
new file mode 100644
index 0000000000..0d0209d4e5
--- /dev/null
+++ b/sources/Input/Input/ICursorConfiguration.cs
@@ -0,0 +1,45 @@
+namespace Silk.NET.Input;
+
+///
+/// Configuration for the behaviour of a mouse cursor.
+///
+public interface ICursorConfiguration
+{
+ ///
+ /// Gets a bitmask denoting the supported values for .
+ ///
+ CursorModes SupportedModes { get; }
+
+ ///
+ /// Gets or sets the current cursor mode. Only one bit shall be set at a time.
+ ///
+ ///
+ /// Note that this property affects the in use, see the
+ /// documentation for more info.
+ ///
+ CursorModes Mode { get; set; }
+
+ ///
+ /// Gets a bitmask denoting the supported values for .
+ ///
+ CursorStyles SupportedStyles { get; }
+
+ ///
+ /// Gets or sets the current cursor style. Only one bit shall be set at a time.
+ /// shall use the provided.
+ ///
+ ///
+ /// When is used, the cursor ceases to exist anyway. As such, while the
+ /// property may not reflect this (as it is retained across changes to
+ /// and just ignored when is used),
+ /// can be implied as being when
+ /// is used.
+ ///
+ CursorStyles Style { get; set; }
+
+ ///
+ /// Gets or sets the current custom cursor image. This has no effect if is not
+ /// used, but the value is stored nonetheless for use when that is the case.
+ ///
+ CustomCursor Image { get; set; }
+}
\ No newline at end of file
diff --git a/sources/Input/Input/IGamepad.cs b/sources/Input/Input/IGamepad.cs
new file mode 100644
index 0000000000..1dc37823b6
--- /dev/null
+++ b/sources/Input/Input/IGamepad.cs
@@ -0,0 +1,20 @@
+namespace Silk.NET.Input;
+
+///
+/// Represents a gamepad that follows a typical layout.
+///
+public interface IGamepad : IButtonDevice
+{
+ ///
+ /// Gets the device state.
+ ///
+ ///
+ /// Only updated when is called.
+ ///
+ new GamepadState State { get; }
+ ButtonReadOnlyList IButtonDevice.State => State.Buttons;
+ ///
+ /// Gets a collection enumerating the vibration motors available to the application to enable haptics.
+ ///
+ IReadOnlyList VibrationMotors { get; }
+}
\ No newline at end of file
diff --git a/sources/Input/Input/IGamepadInputHandler.cs b/sources/Input/Input/IGamepadInputHandler.cs
new file mode 100644
index 0000000000..b1bf488d8d
--- /dev/null
+++ b/sources/Input/Input/IGamepadInputHandler.cs
@@ -0,0 +1,19 @@
+namespace Silk.NET.Input;
+
+///
+/// An that also receives input.
+///
+public interface IGamepadInputHandler : IButtonInputHandler
+{
+ ///
+ /// Called when one of the twin sticks moves.
+ ///
+ /// The event details.
+ void HandleThumbstickMove(GamepadThumbstickMoveEvent @event);
+
+ ///
+ /// Called when one of the two triggers moves.
+ ///
+ /// The event details.
+ void HandleTriggerMove(GamepadTriggerMoveEvent @event);
+}
\ No newline at end of file
diff --git a/sources/Input/Input/IInputBackend.cs b/sources/Input/Input/IInputBackend.cs
new file mode 100644
index 0000000000..e67ce94d0f
--- /dev/null
+++ b/sources/Input/Input/IInputBackend.cs
@@ -0,0 +1,49 @@
+namespace Silk.NET.Input;
+
+///
+/// Represents an input backend capable of receiving human input from Human Input Devices (HIDs).
+///
+///
+/// The onus is on the user to coordinate using this type across threads, as the input backend is not thread safe
+/// In addition, certain backends may have (unavoidable) restrictions on what thread can be called
+/// on - the user is responsible for respecting these threading rules as well.
+///
+public interface IInputBackend : IDisposable
+{
+ ///
+ /// Gets a rough human-readable description of the input backend. Its value is not intrinsically meaningful.
+ ///
+ string Name { get; }
+
+ ///
+ /// Gets a globally-unique integral identifier for this device.
+ ///
+ nint Id { get; }
+
+ ///
+ /// Get a list containing all the connected devices available from this input backend.
+ ///
+ ///
+ /// When a device is disconnected, its shall no longer function and will not be
+ /// enumerated by this list. When a device is connected, an with that physical device ID
+ /// shall be added to this list. In addition, upon connection any past objects previously
+ /// enumerated by this list on this instance shall also regain function if the device
+ /// being added to this list shares the same physical device ID as those previous instances. All such previous
+ /// instances shall be equatable to one another and to the instance added to this list.
+ /// An implementation is welcome to reuse old objects, but this is strictly implementation-defined. A device not
+ /// being present in the (checked using s
+ /// implementation) list is sufficient evidence that a device has been
+ /// disconnected.
+ ///
+ IReadOnlyList Devices { get; }
+
+ ///
+ /// Polls and updates the state of the objects connected using this backend, sending
+ /// input events to the given to reflect the human input received.
+ ///
+ ///
+ /// The value of the State properties on each device must not change until this method is called.
+ ///
+ /// The input handler.
+ void Update(IInputHandler? handler = null);
+}
diff --git a/sources/Input/Input/IInputDevice.cs b/sources/Input/Input/IInputDevice.cs
new file mode 100644
index 0000000000..a53a47d750
--- /dev/null
+++ b/sources/Input/Input/IInputDevice.cs
@@ -0,0 +1,36 @@
+namespace Silk.NET.Input;
+
+///
+/// Represents a connected Human Input Device (HID).
+///
+///
+/// All devices originate from a backend.
+///
+/// An object shall be equatable to any such object retrieved from the same backend where
+/// is equal.
+///
+/// objects must not store any managed state, and if there is a requirement for this in a
+/// future extension of this API then this must be defined in such a way that the state storage and lifetime is
+/// user-controlled. While objects are equatable based on s, if a physical
+/// device disconnects and reconnects the does not provide a guarantee that the same object
+/// will be returned (primarily because doing so would require the to keep track of every
+/// object it's ever created), rather a "compatible" one that acts identically to the original object. This is
+/// completely benign if the object is nothing but a wrapper to the backend anyway. If there is unmanaged state (e.g. a
+/// handle to a device that must be explicitly closed upon disconnection), then it is expected that even in the event of
+/// reconnection, old objects (e.g. created with a now-disposed handle) shall still work for the newly-reconnected
+/// device. A common way this could be implemented is storing the handles in the
+/// implementation instead in the form of a mapping of physical device IDs ( ) to those handles. This
+/// solves the object lifetime problem while also not adding undue complications to user code.
+///
+public interface IInputDevice : IEquatable
+{
+ ///
+ /// Gets a globally-unique integral identifier for this device.
+ ///
+ nint Id { get; }
+
+ ///
+ /// Gets a rough human-readable description of the input device. Its value is not intrinsically meaningful.
+ ///
+ string Name { get; }
+}
\ No newline at end of file
diff --git a/sources/Input/Input/IInputHandler.cs b/sources/Input/Input/IInputHandler.cs
new file mode 100644
index 0000000000..3a7c7bbccc
--- /dev/null
+++ b/sources/Input/Input/IInputHandler.cs
@@ -0,0 +1,15 @@
+namespace Silk.NET.Input;
+
+///
+/// Represents a handler of human input. Implementations of this type will receive a method call for each distinctive
+/// HID event received in the order they were received, to the best of the backend's ability. All visible changes to
+/// device state correspond to a method call using this interface.
+///
+public interface IInputHandler
+{
+ ///
+ /// Called when an disconnects from the application.
+ ///
+ /// The event details.
+ void HandleDeviceConnectionChanged(ConnectionEvent @event);
+}
\ No newline at end of file
diff --git a/sources/Input/Input/IJoystick.cs b/sources/Input/Input/IJoystick.cs
new file mode 100644
index 0000000000..df5bb9b3b3
--- /dev/null
+++ b/sources/Input/Input/IJoystick.cs
@@ -0,0 +1,16 @@
+namespace Silk.NET.Input;
+
+///
+/// Represents a joystick with axes, buttons, and hats.
+///
+public interface IJoystick : IButtonDevice
+{
+ ///
+ /// Gets the device state.
+ ///
+ ///
+ /// Only updated when is called.
+ ///
+ new JoystickState State { get; }
+ ButtonReadOnlyList IButtonDevice.State => State.Buttons;
+}
\ No newline at end of file
diff --git a/sources/Input/Input/IJoystickInputHandler.cs b/sources/Input/Input/IJoystickInputHandler.cs
new file mode 100644
index 0000000000..5dca7202d1
--- /dev/null
+++ b/sources/Input/Input/IJoystickInputHandler.cs
@@ -0,0 +1,19 @@
+namespace Silk.NET.Input;
+
+///
+/// An that also receives input.
+///
+public interface IJoystickInputHandler : IButtonInputHandler
+{
+ ///
+ /// Called when an axis on the joystick moves.
+ ///
+ /// The event details.
+ void HandleAxisMove(JoystickAxisMoveEvent @event);
+
+ ///
+ /// Called when a hat on the joystick moves.
+ ///
+ /// The event details.
+ void HandleHatMove(JoystickHatMoveEvent @event);
+}
\ No newline at end of file
diff --git a/sources/Input/Input/IKeyboard.cs b/sources/Input/Input/IKeyboard.cs
new file mode 100644
index 0000000000..5dcbf4896c
--- /dev/null
+++ b/sources/Input/Input/IKeyboard.cs
@@ -0,0 +1,51 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace Silk.NET.Input;
+
+///
+/// Represents a keyboard device.
+///
+public interface IKeyboard : IButtonDevice
+{
+ ///
+ /// Gets the device state.
+ ///
+ ///
+ /// Only updated when is called.
+ ///
+ new KeyboardState State { get; }
+
+ ButtonReadOnlyList IButtonDevice.State => State.Keys;
+
+ ///
+ /// Gets or sets the current text on the clipboard.
+ ///
+ string? ClipboardText { get; set; }
+
+ ///
+ /// Attempts to get a user-displayable string in the user's locale for the key at the physical position represented
+ /// by in the user's current keyboard layout.
+ ///
+ /// The physical key name. Consult documentation for more info.
+ /// The user-displayable name of the key.
+ /// Whether the name was successfully retrieved.
+ bool TryGetKeyName(KeyName key, [NotNullWhen(true)] out string? name);
+
+ ///
+ /// Begins recording keyboard input. Without / , there is no
+ /// guarantee that will be raised as this might require displaying
+ /// a concept/touchscreen keyboard on certain platforms (e.g. phones). It is recommended that you call
+ /// when you'd like to capture text input (e.g. in a text box), followed by
+ /// when you have completed collecting such input.
+ ///
+ void BeginInput();
+
+ ///
+ /// Concludes recording keyboard input. Without / , there is no
+ /// guarantee that will be raised as this might require displaying
+ /// a concept/touchscreen keyboard on certain platforms (e.g. phones). It is recommended that you call
+ /// when you'd like to capture text input (e.g. in a text box), followed by
+ /// when you have completed collecting such input.
+ ///
+ string? EndInput();
+}
diff --git a/sources/Input/Input/IKeyboardInputHandler.cs b/sources/Input/Input/IKeyboardInputHandler.cs
new file mode 100644
index 0000000000..6ca22ec632
--- /dev/null
+++ b/sources/Input/Input/IKeyboardInputHandler.cs
@@ -0,0 +1,23 @@
+namespace Silk.NET.Input;
+
+///
+/// An that also receives events.
+///
+public interface IKeyboardInputHandler : IButtonInputHandler
+{
+ ///
+ /// Called when a key is pressed or depressed.
+ ///
+ /// The event details.
+ void HandleKeyChanged(KeyChangedEvent @event);
+
+ ///
+ /// Called when a character is typed.
+ ///
+ ///
+ /// Ensure you have called to start receiving text, after which events will be
+ /// sent for each character until is called.
+ ///
+ /// The event details.
+ void HandleKeyChar(KeyCharEvent @event);
+}
\ No newline at end of file
diff --git a/sources/Input/Input/IMotor.cs b/sources/Input/Input/IMotor.cs
new file mode 100644
index 0000000000..f8875e7149
--- /dev/null
+++ b/sources/Input/Input/IMotor.cs
@@ -0,0 +1,13 @@
+namespace Silk.NET.Input;
+
+///
+/// Represents a vibration motor.
+///
+public interface IMotor
+{
+ ///
+ /// Gets or sets the speed at which the motor is operating, where 0.0 represents no vibration and 1.0
+ /// represents the maximum amount of vibration.
+ ///
+ float Speed { get; set; }
+}
\ No newline at end of file
diff --git a/sources/Input/Input/IMouse.cs b/sources/Input/Input/IMouse.cs
new file mode 100644
index 0000000000..e71c8d7162
--- /dev/null
+++ b/sources/Input/Input/IMouse.cs
@@ -0,0 +1,34 @@
+using System.Numerics;
+
+namespace Silk.NET.Input;
+
+///
+/// Represents a mouse - a type of pointer device.
+///
+public interface IMouse : IPointerDevice
+{
+ ///
+ /// Gets the device state.
+ ///
+ ///
+ /// Only updated when is called.
+ ///
+ new MouseState State { get; }
+
+ PointerState IPointerDevice.State => State;
+
+ ///
+ /// Gets the cursor configuration of the mouse.
+ ///
+ ///
+ /// This will determine which points shall lie on.
+ ///
+ ICursorConfiguration Cursor { get; }
+
+ ///
+ /// Attempts to set the position of the mouse.
+ ///
+ /// The position of the mouse in window coordinates.
+ /// Whether the requested position has been applied.
+ bool TrySetPosition(Vector2 position);
+}
\ No newline at end of file
diff --git a/sources/Input/Input/IMouseInputHandler.cs b/sources/Input/Input/IMouseInputHandler.cs
new file mode 100644
index 0000000000..71d02ad1c6
--- /dev/null
+++ b/sources/Input/Input/IMouseInputHandler.cs
@@ -0,0 +1,13 @@
+namespace Silk.NET.Input;
+
+///
+/// An that receives input from an .
+///
+public interface IMouseInputHandler : IButtonInputHandler
+{
+ ///
+ /// Called when the user scrolls using the scroll wheel.
+ ///
+ /// The event details.
+ void HandleScroll(MouseScrollEvent @event);
+}
\ No newline at end of file
diff --git a/sources/Input/Input/IPointerDevice.cs b/sources/Input/Input/IPointerDevice.cs
new file mode 100644
index 0000000000..de4e803f28
--- /dev/null
+++ b/sources/Input/Input/IPointerDevice.cs
@@ -0,0 +1,20 @@
+namespace Silk.NET.Input;
+
+///
+/// Represents a device with which the user can point at a target.
+///
+public interface IPointerDevice : IButtonDevice
+{
+ ///
+ /// Gets the device state.
+ ///
+ ///
+ /// Only updated when is called.
+ ///
+ new PointerState State { get; }
+ ButtonReadOnlyList IButtonDevice.State => State.Buttons;
+ ///
+ /// Gets the targets at which the user can point with their pointer.
+ ///
+ IReadOnlyList Targets { get; }
+}
\ No newline at end of file
diff --git a/sources/Input/Input/IPointerInputHandler.cs b/sources/Input/Input/IPointerInputHandler.cs
new file mode 100644
index 0000000000..f4bd67c0fc
--- /dev/null
+++ b/sources/Input/Input/IPointerInputHandler.cs
@@ -0,0 +1,26 @@
+namespace Silk.NET.Input;
+
+///
+/// An that also receives events.
+///
+public interface IPointerInputHandler : IButtonInputHandler
+{
+ ///
+ /// Called when the properties of a target at which the user can point using the pointer change. This includes the
+ /// addition and removal of targets.
+ ///
+ /// The event details.
+ void HandleTargetChanged(PointerTargetChangedEvent @event);
+
+ ///
+ /// Called when the user adds, removes, or changes a point at which they're pointing at a target.
+ ///
+ /// The event details.
+ void HandlePointChanged(PointChangedEvent @event);
+
+ ///
+ /// Called when the user changes the pressure with which they're gripping the pointer device.
+ ///
+ /// The event details.
+ void HandleGripChanged(PointerGripChangedEvent @event);
+}
\ No newline at end of file
diff --git a/sources/Input/Input/IPointerTarget.cs b/sources/Input/Input/IPointerTarget.cs
new file mode 100644
index 0000000000..5bf3595bda
--- /dev/null
+++ b/sources/Input/Input/IPointerTarget.cs
@@ -0,0 +1,41 @@
+using Silk.NET.Maths;
+
+namespace Silk.NET.Input;
+
+///
+/// Represents a target at which the user can point using their pointer device.
+///
+public interface IPointerTarget
+{
+ ///
+ /// The boundary in which positions of points on this target shall fall. For ,
+ /// shall represent the lack of a lower bound on a particular axis. For
+ /// For , shall represent the lack of a lower bound
+ /// on a particular axis. 0 represents an unused axis that axis is 0 on both
+ /// and .
+ ///
+ Box3D Bounds { get; }
+
+ ///
+ /// Gets the number of points with which the given pointer is pointing at this target.
+ ///
+ /// The number of points.
+ ///
+ /// A single "logical" pointer device may have many points, and can optionally represent multiple physical pointers
+ /// as a single logical device - this is the case where a backend supports multiple mice to control an
+ /// cursor on its "raw mouse input" target, but combines these all to a single point on its "windowed" target. This
+ /// is also true for touch input - a touch screen is represented as a single touch device,
+ /// where each finger is its own point.
+ ///
+ int GetPointCount(IPointerDevice pointer);
+
+ ///
+ /// Gets a point with which the given pointer is pointing at this target.
+ ///
+ /// The pointer device.
+ ///
+ /// The index of the point, between 0 and the number sourced from .
+ ///
+ /// The point at the given index with which the given pointer device is pointing at the target.
+ TargetPoint GetPoint(IPointerDevice pointer, int point);
+}
\ No newline at end of file
diff --git a/sources/Input/Input/Implementations/SDL3/InputWindowExtensions.cs b/sources/Input/Input/Implementations/SDL3/InputWindowExtensions.cs
new file mode 100644
index 0000000000..9d2f55ce37
--- /dev/null
+++ b/sources/Input/Input/Implementations/SDL3/InputWindowExtensions.cs
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+// ReSharper disable CheckNamespace
+namespace Silk.NET.Input;
+
+///
+/// Contains extensions for creating input backends and contexts from s.
+///
+public static partial class InputWindowExtensions
+{
+ public static partial IInputBackend CreateInputBackend(this INativeWindow window)
+ {
+ throw new NotImplementedException();
+ }
+}
diff --git a/sources/Input/Input/Implementations/SDL3/SdlGamepad.cs b/sources/Input/Input/Implementations/SDL3/SdlGamepad.cs
new file mode 100644
index 0000000000..64cd37ba8a
--- /dev/null
+++ b/sources/Input/Input/Implementations/SDL3/SdlGamepad.cs
@@ -0,0 +1,17 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Silk.NET.Input.SDL3;
+
+internal class SdlGamepad : IGamepad
+{
+ public bool Equals(IInputDevice? other) => throw new NotImplementedException();
+
+ public IntPtr Id => throw new NotImplementedException();
+
+ public string Name => throw new NotImplementedException();
+
+ public GamepadState State => throw new NotImplementedException();
+
+ public IReadOnlyList VibrationMotors => throw new NotImplementedException();
+}
diff --git a/sources/Input/Input/Implementations/SDL3/SdlInputBackend.cs b/sources/Input/Input/Implementations/SDL3/SdlInputBackend.cs
new file mode 100644
index 0000000000..24d0a8fdd9
--- /dev/null
+++ b/sources/Input/Input/Implementations/SDL3/SdlInputBackend.cs
@@ -0,0 +1,47 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Silk.NET.SDL;
+
+namespace Silk.NET.Input.SDL3;
+
+internal class SdlInputBackend : IInputBackend
+{
+ public unsafe SdlInputBackend()
+ {
+ var ptr = new EventFilter(OnEvent);
+ Sdl.AddEventWatch(ptr, nullptr);
+ Id = (nint)ptr.Handle;
+ }
+
+ private unsafe byte OnEvent(void* arg0, Event* arg1)
+ {
+ throw new NotImplementedException();
+ }
+
+ public string Name =>
+ $"Silk.NET.Input Reference Implementation using SDL3 ({Sdl.GetPlatform().ReadToString()})";
+
+ public nint Id { get; }
+
+ public IReadOnlyList Devices => throw new NotImplementedException();
+
+ public void Update(IInputHandler? handler = null) => throw new NotImplementedException();
+
+ private unsafe void ReleaseUnmanagedResources()
+ {
+ Sdl.RemoveEventWatch(
+ new EventFilter((delegate* unmanaged)(void*)Id),
+ nullptr
+ );
+ SilkMarshal.Free((Ptr)Id);
+ }
+
+ public void Dispose()
+ {
+ ReleaseUnmanagedResources();
+ GC.SuppressFinalize(this);
+ }
+
+ ~SdlInputBackend() => ReleaseUnmanagedResources();
+}
diff --git a/sources/Input/Input/Implementations/SDL3/SdlJoystick.cs b/sources/Input/Input/Implementations/SDL3/SdlJoystick.cs
new file mode 100644
index 0000000000..b4ecb54675
--- /dev/null
+++ b/sources/Input/Input/Implementations/SDL3/SdlJoystick.cs
@@ -0,0 +1,15 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Silk.NET.Input.SDL3;
+
+internal class SdlJoystick : IJoystick
+{
+ public bool Equals(IInputDevice? other) => throw new NotImplementedException();
+
+ public IntPtr Id => throw new NotImplementedException();
+
+ public string Name => throw new NotImplementedException();
+
+ public JoystickState State => throw new NotImplementedException();
+}
diff --git a/sources/Input/Input/Implementations/SDL3/SdlKeyboard.cs b/sources/Input/Input/Implementations/SDL3/SdlKeyboard.cs
new file mode 100644
index 0000000000..9bd7de6d81
--- /dev/null
+++ b/sources/Input/Input/Implementations/SDL3/SdlKeyboard.cs
@@ -0,0 +1,30 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics.CodeAnalysis;
+
+namespace Silk.NET.Input.SDL3;
+
+internal class SdlKeyboard : IKeyboard
+{
+ public bool Equals(IInputDevice? other) => throw new NotImplementedException();
+
+ public IntPtr Id => throw new NotImplementedException();
+
+ public string Name => throw new NotImplementedException();
+
+ public KeyboardState State => throw new NotImplementedException();
+
+ public string? ClipboardText
+ {
+ get => throw new NotImplementedException();
+ set => throw new NotImplementedException();
+ }
+
+ public bool TryGetKeyName(KeyName key, [NotNullWhen(true)] out string? name) =>
+ throw new NotImplementedException();
+
+ public void BeginInput() => throw new NotImplementedException();
+
+ public string? EndInput() => throw new NotImplementedException();
+}
diff --git a/sources/Input/Input/Implementations/SDL3/SdlMotor.cs b/sources/Input/Input/Implementations/SDL3/SdlMotor.cs
new file mode 100644
index 0000000000..3e551826a6
--- /dev/null
+++ b/sources/Input/Input/Implementations/SDL3/SdlMotor.cs
@@ -0,0 +1,13 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Silk.NET.Input.SDL3;
+
+internal class SdlMotor : IMotor
+{
+ public float Speed
+ {
+ get => throw new NotImplementedException();
+ set => throw new NotImplementedException();
+ }
+}
diff --git a/sources/Input/Input/Implementations/SDL3/SdlMouse.cs b/sources/Input/Input/Implementations/SDL3/SdlMouse.cs
new file mode 100644
index 0000000000..25ac5cdb4f
--- /dev/null
+++ b/sources/Input/Input/Implementations/SDL3/SdlMouse.cs
@@ -0,0 +1,23 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Numerics;
+
+namespace Silk.NET.Input.SDL3;
+
+internal class SdlMouse : IMouse
+{
+ public bool Equals(IInputDevice? other) => throw new NotImplementedException();
+
+ public IntPtr Id => throw new NotImplementedException();
+
+ public string Name => throw new NotImplementedException();
+
+ public MouseState State => throw new NotImplementedException();
+
+ public ICursorConfiguration Cursor => throw new NotImplementedException();
+
+ public bool TrySetPosition(Vector2 position) => throw new NotImplementedException();
+
+ public IReadOnlyList Targets => throw new NotImplementedException();
+}
diff --git a/sources/Input/Input/Implementations/SDL3/SdlPen.cs b/sources/Input/Input/Implementations/SDL3/SdlPen.cs
new file mode 100644
index 0000000000..d061bc86ca
--- /dev/null
+++ b/sources/Input/Input/Implementations/SDL3/SdlPen.cs
@@ -0,0 +1,17 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Silk.NET.Input.SDL3;
+
+internal class SdlPen : IPointerDevice
+{
+ public bool Equals(IInputDevice? other) => throw new NotImplementedException();
+
+ public IntPtr Id => throw new NotImplementedException();
+
+ public string Name => throw new NotImplementedException();
+
+ public PointerState State => throw new NotImplementedException();
+
+ public IReadOnlyList Targets => throw new NotImplementedException();
+}
diff --git a/sources/Input/Input/Implementations/SDL3/SdlTouchScreen.cs b/sources/Input/Input/Implementations/SDL3/SdlTouchScreen.cs
new file mode 100644
index 0000000000..7219f1a1d9
--- /dev/null
+++ b/sources/Input/Input/Implementations/SDL3/SdlTouchScreen.cs
@@ -0,0 +1,17 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Silk.NET.Input.SDL3;
+
+internal class SdlTouchScreen : IPointerDevice
+{
+ public bool Equals(IInputDevice? other) => throw new NotImplementedException();
+
+ public IntPtr Id => throw new NotImplementedException();
+
+ public string Name => throw new NotImplementedException();
+
+ public PointerState State => throw new NotImplementedException();
+
+ public IReadOnlyList Targets => throw new NotImplementedException();
+}
diff --git a/sources/Input/Input/InputContext.cs b/sources/Input/Input/InputContext.cs
new file mode 100644
index 0000000000..402735e702
--- /dev/null
+++ b/sources/Input/Input/InputContext.cs
@@ -0,0 +1,241 @@
+using System.Collections;
+
+namespace Silk.NET.Input;
+
+///
+/// Represents an "input context" containing multiple s from which
+/// s, their state, and their events are aggregated and laid-out in a user-friendly fashion.
+///
+///
+/// The onus is on the user to coordinate using this type across threads, as the input backend is not thread safe
+/// In addition, certain backends may have (unavoidable) restrictions on what thread can be called
+/// on - the user is responsible for respecting these threading rules as well.
+///
+public class InputContext
+ : IJoystickInputHandler,
+ IGamepadInputHandler,
+ IMouseInputHandler,
+ IPointerInputHandler,
+ IKeyboardInputHandler,
+ IList
+{
+ // These are lazy-initialized as they contain their own device lists in addition to the device list stored here and
+ // the device lists stored in each of the backends. You could argue having this many duplicated lists is inefficient
+ // and you'd be absolutely right, but realistically: how many devices will the average user have connected to their
+ // PC? If you're worried about your game's memory consumption, you're probably not looking at the small lists that
+ // input allocates... This way we can also provide sane/consistent indices.
+ private Pointers? _pointers;
+ private Keyboards? _keyboards;
+ private Gamepads? _gamepads;
+ private Joysticks? _joysticks;
+ private List _backends = [];
+ private List? _devices;
+
+ ///
+ /// Gets the s enumerated by the s attached to this context.
+ ///
+ public Pointers Pointers => _pointers ??= new Pointers(this);
+
+ ///
+ /// Gets the s enumerated by the s attached to this context.
+ ///
+ public Keyboards Keyboards => _keyboards ??= new Keyboards(this);
+
+ ///
+ /// Gets the s enumerated by the s attached to this context.
+ ///
+ public Gamepads Gamepads => _gamepads ??= new Gamepads(this);
+
+ ///
+ /// Gets the s enumerated by the s attached to this context.
+ ///
+ public Joysticks Joysticks => _joysticks ??= new Joysticks(this);
+
+ ///
+ /// Gets the s enumerated by the s attached to this context.
+ ///
+ public IReadOnlyList Devices
+ {
+ get
+ {
+ if (_devices is not null)
+ {
+ return _devices;
+ }
+
+ foreach (var backend in Backends)
+ {
+ _devices ??= new List(backend.Devices.Count);
+ _devices.AddRange(backend.Devices);
+ }
+
+ return _devices ??= [];
+ }
+ }
+
+ ///
+ /// Gets a list denoting the attached to this context.
+ ///
+ public IList Backends => this;
+
+ ///
+ /// Raised when a device is added or removed from the list of connected .
+ ///
+ public event Action? ConnectionChanged;
+
+ ///
+ /// Polls and updates the state of the objects connected to each
+ /// attached to this context, raising appropriate events for each state change.
+ ///
+ ///
+ /// This calls for each attached to this context.
+ ///
+ public void Update()
+ {
+ foreach (var backend in Backends)
+ {
+ backend.Update(this);
+ }
+
+ _pointers?.HandleUpdate();
+ }
+
+ private void HandleBackendRemoval(IInputBackend backend)
+ {
+ foreach (var device in backend.Devices)
+ {
+ HandleDeviceConnectionChanged(new ConnectionEvent(device, 0, false));
+ }
+ }
+
+ private void HandleBackendAddition(IInputBackend backend)
+ {
+ foreach (var device in backend.Devices)
+ {
+ HandleDeviceConnectionChanged(new ConnectionEvent(device, 0, true));
+ }
+ }
+
+ private void HandleDeviceConnectionChanged(ConnectionEvent e)
+ {
+ _pointers?.HandleDeviceConnectionChanged(e);
+ _joysticks?.HandleDeviceConnectionChanged(e);
+ _gamepads?.HandleDeviceConnectionChanged(e);
+ _keyboards?.HandleDeviceConnectionChanged(e);
+ if (_devices is null)
+ {
+ return;
+ }
+
+ if (e.IsConnected)
+ {
+ _devices?.Add(e.Device);
+ }
+ else
+ {
+ _devices?.Remove(e.Device);
+ }
+ }
+
+ void IButtonInputHandler.HandleButtonChanged(
+ ButtonChangedEvent @event
+ ) => _joysticks?.HandleButtonChanged(@event);
+
+ void IJoystickInputHandler.HandleAxisMove(JoystickAxisMoveEvent @event) =>
+ _joysticks?.HandleAxisMove(@event);
+
+ void IJoystickInputHandler.HandleHatMove(JoystickHatMoveEvent @event) =>
+ _joysticks?.HandleHatMove(@event);
+
+ void IGamepadInputHandler.HandleThumbstickMove(GamepadThumbstickMoveEvent @event) =>
+ _gamepads?.HandleThumbstickMove(@event);
+
+ void IGamepadInputHandler.HandleTriggerMove(GamepadTriggerMoveEvent @event) =>
+ _gamepads?.HandleTriggerMove(@event);
+
+ void IButtonInputHandler.HandleButtonChanged(
+ ButtonChangedEvent @event
+ ) => _pointers?.HandleButtonChanged(@event);
+
+ void IMouseInputHandler.HandleScroll(MouseScrollEvent @event) =>
+ _pointers?.HandleScroll(@event);
+
+ void IPointerInputHandler.HandleTargetChanged(PointerTargetChangedEvent @event) =>
+ _pointers?.HandleTargetChanged(@event);
+
+ void IPointerInputHandler.HandlePointChanged(PointChangedEvent @event) =>
+ _pointers?.HandlePointChanged(@event);
+
+ void IPointerInputHandler.HandleGripChanged(PointerGripChangedEvent @event) =>
+ _pointers?.HandleGripChanged(@event);
+
+ void IButtonInputHandler.HandleButtonChanged(ButtonChangedEvent @event) =>
+ _keyboards?.HandleButtonChanged(@event);
+
+ void IKeyboardInputHandler.HandleKeyChanged(KeyChangedEvent @event) =>
+ _keyboards?.HandleKeyChanged(@event);
+
+ void IKeyboardInputHandler.HandleKeyChar(KeyCharEvent @event) =>
+ _keyboards?.HandleKeyChar(@event);
+
+ void IInputHandler.HandleDeviceConnectionChanged(ConnectionEvent @event)
+ {
+ HandleDeviceConnectionChanged(@event);
+ ConnectionChanged?.Invoke(@event);
+ }
+
+ IEnumerator IEnumerable.GetEnumerator() =>
+ _backends.GetEnumerator();
+
+ IEnumerator IEnumerable.GetEnumerator() => _backends.GetEnumerator();
+
+ void ICollection.Add(IInputBackend item)
+ {
+ HandleBackendAddition(item);
+ _backends.Add(item);
+ }
+
+ void ICollection.Clear()
+ {
+ foreach (var backend in Backends)
+ {
+ HandleBackendRemoval(backend);
+ }
+ }
+
+ bool ICollection.Contains(IInputBackend item) => _backends.Contains(item);
+
+ void ICollection.CopyTo(IInputBackend[] array, int arrayIndex) =>
+ _backends.CopyTo(array, arrayIndex);
+
+ bool ICollection.Remove(IInputBackend item)
+ {
+ HandleBackendRemoval(item);
+ return _backends.Remove(item);
+ }
+
+ int ICollection.Count => _backends.Count;
+
+ bool ICollection.IsReadOnly => false;
+
+ int IList.IndexOf(IInputBackend item) => _backends.IndexOf(item);
+
+ void IList.Insert(int index, IInputBackend item)
+ {
+ HandleBackendAddition(item);
+ _backends.Insert(index, item);
+ }
+
+ void IList.RemoveAt(int index)
+ {
+ var backend = _backends[index];
+ HandleBackendRemoval(backend);
+ _backends.RemoveAt(index);
+ }
+
+ IInputBackend IList.this[int index]
+ {
+ get => _backends[index];
+ set => _backends[index] = value;
+ }
+}
diff --git a/sources/Input/Input/InputContextDeviceList.cs b/sources/Input/Input/InputContextDeviceList.cs
new file mode 100644
index 0000000000..1ae312526b
--- /dev/null
+++ b/sources/Input/Input/InputContextDeviceList.cs
@@ -0,0 +1,61 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections;
+using System.Diagnostics.CodeAnalysis;
+
+namespace Silk.NET.Input;
+
+///
+/// An internal class that represents a list of that are assignable to
+/// . The backing list is lazily initialized.
+///
+/// The device type.
+///
+/// This type is not intended for public consumption and has no API/ABI stability guarantees.
+///
+[Experimental(
+ "ST0005",
+ UrlFormat = "https://dotnet.github.io/Silk.NET/docs/v3/silk.net/diagnostics/{0}"
+)]
+public abstract class InputContextDeviceList : IReadOnlyList, IInputHandler
+{
+ private readonly InputContext _ctx;
+ private List? _list;
+
+ internal InputContextDeviceList(InputContext ctx) => _ctx = ctx;
+
+ private List List => _list ??= _ctx.Devices.OfType().ToList();
+
+ ///
+ public IEnumerator GetEnumerator() => throw new NotImplementedException();
+
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
+ ///
+ public int Count => List.Count;
+
+ ///
+ public T this[int index] => List[index];
+
+ void IInputHandler.HandleDeviceConnectionChanged(ConnectionEvent @event) =>
+ HandleDeviceConnectionChanged(@event);
+
+ ///
+ protected internal virtual void HandleDeviceConnectionChanged(ConnectionEvent @event)
+ {
+ if (_list is null || @event.Device is not T t)
+ {
+ return;
+ }
+
+ if (@event.IsConnected)
+ {
+ _list.Add(t);
+ }
+ else
+ {
+ _list.Remove(t);
+ }
+ }
+}
diff --git a/sources/Input/Input/InputMarshal.cs b/sources/Input/Input/InputMarshal.cs
new file mode 100644
index 0000000000..0d9731e8e7
--- /dev/null
+++ b/sources/Input/Input/InputMarshal.cs
@@ -0,0 +1,636 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections;
+using System.Diagnostics;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Silk.NET.Input;
+
+///
+/// Contains utilities for creating and manipulating s. This is a very unsafe set of
+/// APIs that are extremely prone to misuse, and therefore is not recommended to be consumed by anyone other than input
+/// backends.
+///
+///
+/// This class is ABI/API stable, but new APIs that obsolete the old ones may be added at any time as more efficient
+/// implementations are discovered.
+///
+// NOTE: Not experimental so that we don't eliminate the prospects of third-party implementations.
+public static class InputMarshal
+{
+ ///
+ /// A wrapper class denoting ownership of a . This is used to attempt to stop
+ /// misuse of these methods, but of course it's fairly trivial to work around this for a user determined to do
+ /// terrible things.
+ ///
+ /// The list element type.
+ public struct ListOwner
+ {
+ internal ListOwner(InputReadOnlyList list) => List = list;
+
+ ///
+ /// Gets the list owned by this owner.
+ ///
+ public InputReadOnlyList List { get; }
+ }
+
+ internal class ButtonList(uint[] binary, Dictionary>? other)
+ : IReadOnlyList>
+ where T : unmanaged, Enum
+ {
+ private Dictionary>? _other = other;
+
+ public ButtonList()
+ : this(new uint[(GetButtonListCount() + 32 - 1) / 32], null) { }
+
+ public ButtonList Clone() =>
+ new([.. binary], _other is null ? null : new Dictionary>(_other));
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)] // <-- generic specialisation
+ public IEnumerator> GetEnumerator() =>
+ typeof(T) == typeof(KeyName) ? GetKeyNameEnumerator() : GetButtonEnumerator();
+
+ private IEnumerator> GetKeyNameEnumerator()
+ {
+ var idx = 0;
+ var bit = 0;
+ // To determine the gaps, run the GetButtonCount unit test. The equality check is the LHS from the output +
+ // 1, and the assignment is the RHS - 1. Example output below:
+ // 0 (Unknown), 4 (A)
+ // 129 (VolumeDown), 133 (KeypadComma)
+ // 164 (ExtendSelect), 176 (Keypad00)
+ // 221 (KeypadHexadecimal), 224 (ControlLeft)
+ // 231 (SuperRight), 257 (Mode)
+ // 286 (ApplicationBookmarks), 501 (SoftLeft)
+ for (var cur = (int)KeyName.A; cur <= (int)KeyName.EndCall; cur++)
+ {
+ switch (cur)
+ {
+ case (int)KeyName.VolumeDown + 1:
+ cur = (int)KeyName.KeypadComma - 1;
+ continue;
+ case (int)KeyName.ExtendSelect + 1:
+ cur = (int)KeyName.Keypad00 - 1;
+ continue;
+ case (int)KeyName.KeypadHexadecimal + 1:
+ cur = (int)KeyName.ControlLeft - 1;
+ continue;
+ case (int)KeyName.SuperRight + 1:
+ cur = (int)KeyName.Mode - 1;
+ continue;
+ case (int)KeyName.ApplicationBookmarks + 1:
+ cur = (int)KeyName.SoftLeft - 1;
+ continue;
+ }
+
+ var ret = ElementAt((T)(object)(KeyName)cur, idx, bit);
+ (idx, bit) = BitIterate(idx, bit);
+ yield return ret;
+ }
+ }
+
+ private IEnumerator> GetButtonEnumerator()
+ {
+ var max = GetButtonListCount();
+ int idx = 0,
+ bit = 0;
+ for (var i = 1; i <= max; i++)
+ {
+ var ret = ElementAt(SilkMarshal.ConstCast(i), idx, bit);
+ (idx, bit) = BitIterate(idx, bit);
+ yield return ret;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private Button ElementAt(T name, int idx, int bit)
+ {
+ var ret = new Button(name, false, 0);
+ var isBinaryDown = BitOperations.PopCount(binary[idx] & (1U << (7 - bit))) > 0;
+ if (isBinaryDown)
+ {
+ ret = ret with { IsDown = true, Pressure = 1 };
+ }
+ else
+ {
+ _other?.TryGetValue(name, out ret);
+ }
+
+ return ret;
+ }
+
+ [MethodImpl(
+ MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization
+ )]
+ private static (int, int) BitIterate(int idx, int bit) =>
+ ++bit == 32 ? (++idx, 0) : (idx, bit);
+
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
+ public int Count => GetButtonListCount();
+
+ [MethodImpl(
+ MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization
+ )]
+ internal static T IndexName(int index)
+ {
+ var name = index;
+ if (typeof(T) == typeof(KeyName))
+ {
+ // To determine the gaps, run the GetButtonCount unit test. The condition is to check whether name
+ // is greater than the LHS, and if so add the RHS less the LHS less 1. Example output:
+ // 0 (Unknown), 4 (A)
+ // 129 (VolumeDown), 133 (KeypadComma)
+ // 164 (ExtendSelect), 176 (Keypad00)
+ // 221 (KeypadHexadecimal), 224 (ControlLeft)
+ // 231 (SuperRight), 257 (Mode)
+ // 286 (ApplicationBookmarks), 501 (SoftLeft)
+ name += 4;
+ if (name > 129)
+ {
+ name += 133 - 129 - 1;
+ }
+
+ if (name > 164)
+ {
+ name += 176 - 164 - 1;
+ }
+
+ if (name > 221)
+ {
+ name += 224 - 221 - 1;
+ }
+
+ if (name > 231)
+ {
+ name += 257 - 231 - 1;
+ }
+
+ if (name > 286)
+ {
+ name += 501 - 286 - 1;
+ }
+ }
+ else
+ {
+ // To account for Unknown = 0.
+ name++;
+ }
+
+ return SilkMarshal.ConstCast(name);
+ }
+
+ [MethodImpl(
+ MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization
+ )]
+ internal static int NameIndex(T name)
+ {
+ var index = SilkMarshal.ConstCast(name);
+ if (typeof(T) == typeof(KeyName))
+ {
+ // To determine the gaps, run the GetButtonCount unit test. The condition is to check whether name
+ // is greater than the LHS, and if so subtract the RHS less the LHS less 1. Note that the conditions
+ // should be in reverse order i.e. from the last output line to the first output line. Example output:
+ // 0 (Unknown), 4 (A)
+ // 129 (VolumeDown), 133 (KeypadComma)
+ // 164 (ExtendSelect), 176 (Keypad00)
+ // 221 (KeypadHexadecimal), 224 (ControlLeft)
+ // 231 (SuperRight), 257 (Mode)
+ // 286 (ApplicationBookmarks), 501 (SoftLeft)
+ if (index > 286)
+ {
+ index -= 501 - 286 - 1;
+ }
+
+ if (index > 231)
+ {
+ index -= 257 - 231 - 1;
+ }
+
+ if (index > 221)
+ {
+ index -= 224 - 221 - 1;
+ }
+
+ if (index > 164)
+ {
+ index -= 176 - 164 - 1;
+ }
+
+ if (index > 129)
+ {
+ index -= 133 - 129 - 1;
+ }
+ index -= 4;
+ }
+ else
+ {
+ // To account for Unknown = 0.
+ index--;
+ }
+
+ return index;
+ }
+
+ public Button this[int index]
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+ ArgumentOutOfRangeException.ThrowIfNegative(index);
+ ArgumentOutOfRangeException.ThrowIfGreaterThan(index, Count);
+ return ElementAt(
+ IndexName(index),
+ Math.DivRem(index, 32, out var remainder),
+ remainder
+ );
+ }
+ }
+
+ public Button this[T name]
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+ var index = NameIndex(name);
+ if (index >= 0 && index < GetButtonListCount())
+ {
+ return ElementAt(name, Math.DivRem(index, 32, out var remainder), remainder);
+ }
+
+ Throw();
+ return default;
+ [StackTraceHidden]
+ static void Throw() => throw new ArgumentOutOfRangeException(nameof(name));
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Set(Button btn, bool isBinary)
+ {
+ if (btn.IsDown && isBinary)
+ {
+ binary[Math.DivRem(NameIndex(btn.Name), 32, out var bit)] |= 1U << (7 - bit);
+ _other?.Remove(btn.Name);
+ }
+ else
+ {
+ binary[Math.DivRem(NameIndex(btn.Name), 32, out var bit)] &= ~(1U << (7 - bit));
+ }
+
+ if (!isBinary)
+ {
+ (_other ??= [])[btn.Name] = btn;
+ }
+ }
+ }
+
+ ///
+ /// Gets the reported by s created with
+ /// for the given .
+ ///
+ /// The button name type.
+ ///
+ /// The number of buttons that will be in a button list created with , or -1 if
+ /// is not a supported button name type.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
+ public static int GetButtonListCount()
+ {
+ if (typeof(T) == typeof(JoystickButton))
+ {
+ return (int)JoystickButton.DPadLeft;
+ }
+
+ if (typeof(T) == typeof(PointerButton))
+ {
+ return (int)PointerButton.Button32;
+ }
+
+ if (typeof(T) == typeof(KeyName))
+ {
+ // To determine the ranges, run the GetButtonCount unit test. The RHS of the subtraction statements below
+ // are the RHS of the line output, and the LHS is the LHS of the following line in its output. There is a
+ // final addition that is the number of preceding additions to account for the boundary values. Example
+ // output from that test:
+ // 0 (Unknown), 4 (A)
+ // 129 (VolumeDown), 133 (KeypadComma)
+ // 164 (ExtendSelect), 176 (Keypad00)
+ // 221 (KeypadHexadecimal), 224 (ControlLeft)
+ // 231 (SuperRight), 257 (Mode)
+ // 286 (ApplicationBookmarks), 501 (SoftLeft)
+ // ReSharper disable once ArrangeRedundantParentheses <-- stylistic choice
+ return ((int)KeyName.VolumeDown - (int)KeyName.A)
+ + ((int)KeyName.ExtendSelect - (int)KeyName.KeypadComma)
+ + ((int)KeyName.KeypadHexadecimal - (int)KeyName.Keypad00)
+ + ((int)KeyName.SuperRight - (int)KeyName.ControlLeft)
+ + ((int)KeyName.ApplicationBookmarks - (int)KeyName.Mode)
+ + ((int)KeyName.EndCall - (int)KeyName.SoftLeft)
+ + 6;
+ }
+
+ return -1;
+ }
+
+ ///
+ /// Creates a wrapping the given button .
+ ///
+ /// The list.
+ /// The button name type.
+ /// The button list.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)] // <-- generic specialisation
+ public static ButtonReadOnlyList AsButtonList(this InputReadOnlyList> list)
+ where T : unmanaged, Enum => new(list);
+
+ ///
+ /// Creates a new for the given , optionally with the
+ /// given where is applicable for this
+ /// .
+ ///
+ /// The capacity.
+ /// The element type.
+ /// The list.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)] // <-- generic specialisation
+ public static ListOwner CreateList(int capacity = 0)
+ {
+ if (typeof(T) == typeof(Button))
+ {
+ return (ListOwner)
+ (object)
+ new ListOwner>(
+ new InputReadOnlyList>((object)new ButtonList())
+ );
+ }
+
+ if (typeof(T) == typeof(Button))
+ {
+ return (ListOwner)
+ (object)
+ new ListOwner>(
+ new InputReadOnlyList>(
+ (object)new ButtonList()
+ )
+ );
+ }
+
+ if (typeof(T) == typeof(Button))
+ {
+ return (ListOwner)
+ (object)
+ new ListOwner>(
+ new InputReadOnlyList>(
+ (object)new ButtonList()
+ )
+ );
+ }
+
+ return new ListOwner(new InputReadOnlyList((object)new List(capacity)));
+ }
+
+ ///
+ /// Creates a new from the given . This is
+ /// equivalent to , but returns a
+ /// instead.
+ ///
+ /// The elements to populate the list with.
+ ///
+ ///
+ public static ListOwner Clone(IReadOnlyList other)
+ {
+ // ReSharper disable once InvertIf <-- starting to really dislike this as it duplicates code
+ if (other is InputReadOnlyList irl)
+ {
+ if (typeof(T) == typeof(Button))
+ {
+ return new ListOwner(
+ new InputReadOnlyList(Unsafe.As>(irl.Data).Clone())
+ );
+ }
+
+ if (typeof(T) == typeof(Button))
+ {
+ return new ListOwner(
+ new InputReadOnlyList(Unsafe.As>(irl.Data).Clone())
+ );
+ }
+
+ if (typeof(T) == typeof(Button))
+ {
+ return new ListOwner(
+ new InputReadOnlyList(
+ Unsafe.As>(irl.Data).Clone()
+ )
+ );
+ }
+ }
+
+ if (typeof(T) == typeof(Button))
+ {
+ return new ListOwner(
+ new InputReadOnlyList(CloneButtonList((IReadOnlyList>)other))
+ );
+ }
+
+ if (typeof(T) == typeof(Button))
+ {
+ return new ListOwner(
+ new InputReadOnlyList(
+ CloneButtonList((IReadOnlyList>)other)
+ )
+ );
+ }
+
+ // ReSharper disable once ConvertIfStatementToReturnStatement <-- stylistic choice
+ if (typeof(T) == typeof(Button))
+ {
+ return new ListOwner(
+ new InputReadOnlyList(
+ CloneButtonList((IReadOnlyList>)other)
+ )
+ );
+ }
+
+ return new ListOwner(new InputReadOnlyList((object)new List(other)));
+ static ButtonList CloneButtonList(IReadOnlyList> list)
+ where TEnum : unmanaged, Enum
+ {
+ var ret = new ButtonList();
+ foreach (var button in list)
+ {
+ ret.Set(
+ button,
+ (button.IsDown && button.Pressure >= 1.0)
+ || (!button.IsDown && button.Pressure <= 0.0)
+ );
+ }
+
+ return ret;
+ }
+ }
+
+ ///
+ /// Sets the button state in the given button list.
+ ///
+ /// The list to update.
+ /// The new state of the button.
+ ///
+ /// Whether the of can only be 1.0 when
+ /// is true , and 0.0 when is
+ /// false .
+ ///
+ /// The button type.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)] // <-- generic specialisation
+ public static void SetButtonState(ListOwner> list, Button value, bool isBinary)
+ where T : unmanaged, Enum
+ {
+ if (
+ typeof(T) == typeof(KeyName)
+ || typeof(T) == typeof(JoystickButton)
+ || typeof(T) == typeof(PointerButton)
+ )
+ {
+ Unsafe.As>(list.List.Data).Set(value, isBinary);
+ return;
+ }
+
+ var underlying = GetUnderlyingList(list)!;
+ for (var i = 0; i < underlying.Count; i++)
+ {
+ // ReSharper disable once InvertIf <-- this literally results in more lines of code!!!!!
+ if (underlying[i].Name.Equals(value.Name))
+ {
+ underlying[i] = value;
+ return;
+ }
+ }
+
+ underlying.Add(value);
+ }
+
+ ///
+ /// Attempts to retrieve the underlying implementation, provided that
+ /// for the given is implemented as a sequential list
+ /// with individually addressable and a variable number of elements.
+ ///
+ /// The list.
+ /// The list element type.
+ ///
+ /// The list, or null if the optimised implementation of cannot be
+ /// expressed as an .
+ ///
+ ///
+ /// Currently, this can be assumed to not null except for the following types:
+ ///
+ /// where T is
+ /// where T is
+ /// where T is
+ ///
+ /// It is a breaking change to change the underlying implementation of the list such that this method returns
+ /// null where it previously did not return null , therefore Silk.NET will only do this in a
+ /// major release. As a result, it is safe to use the ! operator for code targeting a specific major
+ /// release. Ideally, this is also the case for major releases, but the Silk.NET team cannot guarantee this at this
+ /// time.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)] // <-- generic specialisation
+ public static IList? GetUnderlyingList(this ListOwner list) =>
+ typeof(T) == typeof(Button)
+ || typeof(T) == typeof(Button)
+ || typeof(T) == typeof(Button)
+ ? null
+ : Unsafe.As>(list.List.Data);
+
+ // These are APIs defined on InputReadOnlyList or ButtonReadOnlyList but are implemented here to keep the
+ // implementation of the backing list in one file, the hope being that this decreases the likelihood of bugs.
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)] // <-- generic specialisation
+ internal static Button GetButtonState(InputReadOnlyList> list, T name)
+ where T : unmanaged, Enum
+ {
+ if (
+ typeof(T) == typeof(KeyName)
+ || typeof(T) == typeof(JoystickButton)
+ || typeof(T) == typeof(PointerButton)
+ )
+ {
+ return Unsafe.As>(list.Data)[name];
+ }
+
+ var underlying = Unsafe.As>>(list.Data);
+ foreach (var t in underlying)
+ {
+ // ReSharper disable once InvertIf <-- this literally results in more lines of code!!!!!
+ if (t.Name.Equals(name))
+ {
+ return t;
+ }
+ }
+
+ return new Button(name, false, 0);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static int GetListCount(InputReadOnlyList list)
+ {
+ if (typeof(T) == typeof(Button))
+ {
+ return GetButtonListCount();
+ }
+
+ if (typeof(T) == typeof(Button))
+ {
+ return GetButtonListCount();
+ }
+
+ if (typeof(T) == typeof(Button))
+ {
+ return GetButtonListCount();
+ }
+
+ return Unsafe.As>(list.Data).Count;
+ }
+
+ // ReSharper disable NotDisposedResourceIsReturned - Nope, sorry, not adding a reference to JetBrains.Annotations.
+ internal static IEnumerator EnumerateList(InputReadOnlyList list)
+ {
+ if (typeof(T) == typeof(Button))
+ {
+ return (IEnumerator)Unsafe.As>(list.Data).GetEnumerator();
+ }
+
+ if (typeof(T) == typeof(Button))
+ {
+ return (IEnumerator)Unsafe.As>(list.Data).GetEnumerator();
+ }
+
+ if (typeof(T) == typeof(Button))
+ {
+ return (IEnumerator)Unsafe.As>(list.Data).GetEnumerator();
+ }
+
+ return Unsafe.As>(list.Data).GetEnumerator();
+ } // ReSharper restore NotDisposedResourceIsReturned
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static T ElementAt(InputReadOnlyList list, int index)
+ {
+ if (typeof(T) == typeof(Button))
+ {
+ return (T)(object)Unsafe.As>(list.Data)[index];
+ }
+
+ if (typeof(T) == typeof(Button))
+ {
+ return (T)(object)Unsafe.As>(list.Data)[index];
+ }
+
+ if (typeof(T) == typeof(Button))
+ {
+ return (T)(object)Unsafe.As>(list.Data)[index];
+ }
+
+ return Unsafe.As>(list.Data)[index];
+ }
+}
diff --git a/sources/Input/Input/InputReadOnlyList.cs b/sources/Input/Input/InputReadOnlyList.cs
new file mode 100644
index 0000000000..db3f1546a3
--- /dev/null
+++ b/sources/Input/Input/InputReadOnlyList.cs
@@ -0,0 +1,32 @@
+using System.Collections;
+
+namespace Silk.NET.Input;
+
+///
+/// An opaque implementation of that is optimised for storing a Silk.NET.Input
+/// type specified by using the most memory-efficient mechanism available.
+///
+/// The Silk.NET.Input type to store.
+public readonly struct InputReadOnlyList : IReadOnlyList
+{
+ internal object Data { get; }
+
+ internal InputReadOnlyList(object data) => Data = data;
+
+ ///
+ /// Creates an from a .
+ ///
+ /// The list to copy.
+ public InputReadOnlyList(IReadOnlyList other) => this = InputMarshal.Clone(other).List;
+
+ ///
+ public IEnumerator GetEnumerator() => InputMarshal.EnumerateList(this);
+
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
+ ///
+ public int Count => InputMarshal.GetListCount(this);
+
+ ///
+ public T this[int index] => InputMarshal.ElementAt(this, index);
+}
diff --git a/sources/Input/Input/InputWindowExtensions.cs b/sources/Input/Input/InputWindowExtensions.cs
new file mode 100644
index 0000000000..2933bc7527
--- /dev/null
+++ b/sources/Input/Input/InputWindowExtensions.cs
@@ -0,0 +1,55 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Numerics;
+using Silk.NET.Maths;
+
+namespace Silk.NET.Input;
+
+///
+/// Contains extensions for creating input backends and contexts from s.
+///
+public static partial class InputWindowExtensions
+{
+ ///
+ /// Creates an instance of the "reference implementation" of for the given
+ /// , provided that this was also sourced from the "reference implementation" of the
+ /// windowing API.
+ ///
+ ///
+ /// Regarding the threading rules documented on ,
+ /// must only be called on the "main thread," i.e. the same thread that windowing operates on.
+ ///
+ /// The window to create an input backend from.
+ /// The input backend.
+ ///
+ /// If the given is not compatible with the reference implementation for this platform.
+ ///
+ public static partial IInputBackend CreateInputBackend(this INativeWindow window);
+
+ ///
+ /// Creates an that uses the "reference implementation" of
+ /// for the given as its only backend, provided that the was
+ /// also sourced from the "reference implementation" of the windowing API.
+ ///
+ ///
+ /// Regarding the threading rules documented on ,
+ /// must only be called on the "main thread," i.e. the same thread that windowing operates on.
+ ///
+ /// The window to create an input backend from.
+ ///
+ /// The created with the instantiated input backend as its only backend.
+ ///
+ ///
+ /// If the given is not compatible with the reference implementation for this platform.
+ ///
+ public static InputContext CreateInput(this INativeWindow window)
+ {
+ var ret = new InputContext();
+ ret.Backends.Add(window.CreateInputBackend());
+ return ret;
+ }
+}
diff --git a/sources/Input/Input/JoystickAxisMoveEvent.cs b/sources/Input/Input/JoystickAxisMoveEvent.cs
new file mode 100644
index 0000000000..18e8a1f72c
--- /dev/null
+++ b/sources/Input/Input/JoystickAxisMoveEvent.cs
@@ -0,0 +1,15 @@
+using System.Diagnostics;
+
+namespace Silk.NET.Input;
+
+///
+/// Contains information pertaining to the movement of a joystick axis.
+///
+/// The joystick on which the axis being moved resides.
+///
+/// The timestamp (as retrieved from ) at which the event occurred.
+///
+/// The index of the axis being moved.
+/// The new value of the axis, typically between 0.0 and 1.0 .
+/// The change in as a result of this event.
+public readonly record struct JoystickAxisMoveEvent(IJoystick Joystick, long Timestamp, int Axis, float Value, float Delta);
\ No newline at end of file
diff --git a/sources/Input/Input/JoystickButton.cs b/sources/Input/Input/JoystickButton.cs
new file mode 100644
index 0000000000..f76482034e
--- /dev/null
+++ b/sources/Input/Input/JoystickButton.cs
@@ -0,0 +1,109 @@
+namespace Silk.NET.Input;
+
+///
+/// Enumerates the buttons of a joystick.
+///
+public enum JoystickButton
+{
+ ///
+ /// The button was not recognised.
+ ///
+ Unknown,
+
+ ///
+ /// The down-most button of the primary button cluster.
+ ///
+ ButtonDown,
+
+ ///
+ /// The "A" button on Xbox (and similar) controllers. Equivalent to .
+ ///
+ A = ButtonDown,
+
+ ///
+ /// The rightmost button of the primary button cluster.
+ ///
+ ButtonRight,
+
+ ///
+ /// The "B" button on Xbox (and similar) controllers. Equivalent to .
+ ///
+ B = ButtonRight,
+
+ ///
+ /// The leftmost button of the primary button cluster.
+ ///
+ ButtonLeft,
+
+ ///
+ /// The "X" button on Xbox (and similar) controllers. Equivalent to .
+ ///
+ X = ButtonLeft,
+
+ ///
+ /// The upmost button of the primary button cluster.
+ ///
+ ButtonUp,
+
+ ///
+ /// The "Y" button on Xbox (and similar) controllers. Equivalent to .
+ ///
+ Y = ButtonUp,
+
+ ///
+ /// The leftmost bumper/shoulder button.
+ ///
+ LeftBumper,
+
+ ///
+ /// The rightmost bumper/shoulder button.
+ ///
+ RightBumper,
+
+ ///
+ /// The "back" button.
+ ///
+ Back,
+
+ ///
+ /// The "start" button.
+ ///
+ Start,
+
+ ///
+ /// The "home" button.
+ ///
+ Home,
+
+ ///
+ /// The leftmost thumbstick. This button represents the stick being pressed down.
+ ///
+ LeftStick,
+
+ ///
+ /// The rightmost thumbstick. This button represents the stick being pressed down.
+ ///
+ RightStick,
+
+ ///
+ /// The upmost button of the D-Pad button cluster.
+ ///
+ DPadUp,
+
+ ///
+ /// The rightmost button of the D-Pad button cluster.
+ ///
+ DPadRight,
+
+ ///
+ /// The down-most button of the D-Pad button cluster.
+ ///
+ DPadDown,
+
+ ///
+ /// The leftmost button of the D-Pad button cluster.
+ ///
+ DPadLeft,
+
+ // BEFORE ADDING A NEW ITEM MAKE SURE YOU CHANGE LastJoystickButton IN InputMarshal
+}
diff --git a/sources/Input/Input/JoystickHatMoveEvent.cs b/sources/Input/Input/JoystickHatMoveEvent.cs
new file mode 100644
index 0000000000..67b3defd95
--- /dev/null
+++ b/sources/Input/Input/JoystickHatMoveEvent.cs
@@ -0,0 +1,15 @@
+using System.Diagnostics;
+using System.Numerics;
+
+namespace Silk.NET.Input;
+
+///
+/// Contains information pertaining to the movement of a joystick hat.
+///
+/// The joystick on which the hat being moved resides.
+///
+/// The timestamp (as retrieved from ) at which the event occurred.
+///
+/// The position of the hat after this event.
+/// The change in as a result of this event.
+public readonly record struct JoystickHatMoveEvent(IJoystick Joystick, long Timestamp, Vector2 Value, Vector2 Delta);
\ No newline at end of file
diff --git a/sources/Input/Input/JoystickState.cs b/sources/Input/Input/JoystickState.cs
new file mode 100644
index 0000000000..9d80287b7b
--- /dev/null
+++ b/sources/Input/Input/JoystickState.cs
@@ -0,0 +1,24 @@
+using System.Numerics;
+
+namespace Silk.NET.Input;
+
+///
+/// Contains user input received from an .
+///
+public class JoystickState
+{
+ ///
+ /// Gets the state of the joystick axes between -1.0 and 1.0
+ ///
+ public InputReadOnlyList Axes { get; }
+
+ ///
+ /// Gets the joystick button state, denoting which buttons are pressed/depressed.
+ ///
+ public ButtonReadOnlyList Buttons { get; }
+
+ ///
+ /// Gets the state of the joystick hats as vectors between -1.0 and 1.0 .
+ ///
+ public InputReadOnlyList Hats { get; }
+}
\ No newline at end of file
diff --git a/sources/Input/Input/Joysticks.cs b/sources/Input/Input/Joysticks.cs
new file mode 100644
index 0000000000..f576e6f85f
--- /dev/null
+++ b/sources/Input/Input/Joysticks.cs
@@ -0,0 +1,41 @@
+namespace Silk.NET.Input;
+
+///
+/// Represents a collection of s from which input events can be received.
+///
+public sealed class Joysticks : InputContextDeviceList, IJoystickInputHandler
+{
+ internal Joysticks(InputContext ctx)
+ : base(ctx) { }
+
+ ///
+ /// Raised when state pertaining to a pushable button on the joystick changes (e.g. button up, button down).
+ ///
+ public event Action>? ButtonChanged;
+
+ ///
+ /// Raised when a movable axis on the joystick changes position.
+ ///
+ public event Action? AxisMove;
+
+ ///
+ /// Raised when a joystick hat moves.
+ ///
+ public event Action? HatMove;
+
+ internal void HandleButtonChanged(ButtonChangedEvent @event) =>
+ ButtonChanged?.Invoke(@event);
+
+ void IButtonInputHandler.HandleButtonChanged(
+ ButtonChangedEvent @event
+ ) => HandleButtonChanged(@event);
+
+ internal void HandleAxisMove(JoystickAxisMoveEvent @event) => AxisMove?.Invoke(@event);
+
+ void IJoystickInputHandler.HandleAxisMove(JoystickAxisMoveEvent @event) =>
+ HandleAxisMove(@event);
+
+ internal void HandleHatMove(JoystickHatMoveEvent @event) => HatMove?.Invoke(@event);
+
+ void IJoystickInputHandler.HandleHatMove(JoystickHatMoveEvent @event) => HandleHatMove(@event);
+}
diff --git a/sources/Input/Input/KeyChangedEvent.cs b/sources/Input/Input/KeyChangedEvent.cs
new file mode 100644
index 0000000000..3868ceaccd
--- /dev/null
+++ b/sources/Input/Input/KeyChangedEvent.cs
@@ -0,0 +1,16 @@
+using System.Diagnostics;
+
+namespace Silk.NET.Input;
+
+///
+/// Contains information pertaining to a key press state change.
+///
+/// The keyboard on which the key being pressed or depressed resides.
+///
+/// The timestamp (as retrieved from ) at which the event occurred.
+///
+/// The new state of the key being pressed or depressed.
+/// The previous state of the key.
+/// Whether this is an event that has been repeated at an implementation-defined rate.
+/// The active key modifiers at the time the event was raised.
+public readonly record struct KeyChangedEvent(IKeyboard Keyboard, long Timestamp, Button Key, Button Previous, bool IsRepeat, KeyModifiers Modifiers);
\ No newline at end of file
diff --git a/sources/Input/Input/KeyCharEvent.cs b/sources/Input/Input/KeyCharEvent.cs
new file mode 100644
index 0000000000..8e557d06df
--- /dev/null
+++ b/sources/Input/Input/KeyCharEvent.cs
@@ -0,0 +1,13 @@
+using System.Diagnostics;
+
+namespace Silk.NET.Input;
+
+///
+/// Contains information pertaining to a character being typed on a keyboard.
+///
+/// The keyboard with which the end user typed a character.
+///
+/// The timestamp (as retrieved from ) at which the event occurred.
+///
+/// The character that was typed. A null character denotes a backspace.
+public readonly record struct KeyCharEvent(IKeyboard Keyboard, long Timestamp, char? Character);
\ No newline at end of file
diff --git a/sources/Input/Input/KeyModifiers.cs b/sources/Input/Input/KeyModifiers.cs
new file mode 100644
index 0000000000..8b6660641f
--- /dev/null
+++ b/sources/Input/Input/KeyModifiers.cs
@@ -0,0 +1,40 @@
+namespace Silk.NET.Input;
+
+///
+/// A bitmask denoting the modifier keys that can be active when a key press occurs to modify its behaviour.
+///
+[Flags]
+public enum KeyModifiers
+{
+ /// No modifier keys are active.
+ None = 0,
+ /// The left "shift" key.
+ ShiftLeft = 1 << 0,
+
+ /// The right "shift" key.
+ ShiftRight = 1 << 1,
+
+ /// The left "control" key.
+ ControlLeft = 1 << 2,
+
+ /// The right "control" key.
+ ControlRight = 1 << 3,
+
+ /// The left "alt" key.
+ AltLeft = 1 << 4,
+
+ /// The right "alt" key.
+ AltRight = 1 << 5,
+
+ /// The left "super" (e.g. Windows/Start) key.
+ SuperLeft = 1 << 6,
+
+ /// The right "super" (e.g. Windows/Start) key.
+ SuperRight = 1 << 7,
+
+ /// The "num lock" key.
+ NumLock = 1 << 8,
+
+ /// The "caps lock" key.
+ CapsLock = 1 << 9
+}
\ No newline at end of file
diff --git a/sources/Input/Input/KeyName.cs b/sources/Input/Input/KeyName.cs
new file mode 100644
index 0000000000..2def1ba7d7
--- /dev/null
+++ b/sources/Input/Input/KeyName.cs
@@ -0,0 +1,828 @@
+namespace Silk.NET.Input;
+
+///
+/// Enumerates names for physical key positions as defined by the
+/// USB HID Usage Tables published by
+/// the USB-IF. Note that these denote an en-US-centric definition of the keys that reside at each physical position,
+/// and does not take account of keyboard layout. That is, represents the Q key on a QWERTY
+/// keyboard but represents the " key on a Dvorak keyboard. Use to
+/// determine the localised name of a physical key position name ( ) when taking account of the
+/// user's selected keyboard layout.
+///
+public enum KeyName
+{
+ // BEFORE ADDING ANYTHING TO THIS FILE MAKE SURE YOU REALISE THAT InputMarshal RELIES ON ASSUMPTIONS ON THE VALUES
+
+ // These values are from usage page 0x07 (USB keyboard page).
+ ///
+ /// A key that was not recognised.
+ ///
+ Unknown = 0,
+
+ /// The "A" key.
+ A = 4,
+
+ /// The "B" key.
+ B = 5,
+
+ /// The "C" key.
+ C = 6,
+
+ /// The "D" key.
+ D = 7,
+
+ /// The "E" key.
+ E = 8,
+
+ /// The "F" key.
+ F = 9,
+
+ /// The "G" key.
+ G = 10,
+
+ /// The "H" key.
+ H = 11,
+
+ /// The "I" key.
+ I = 12,
+
+ /// The "J" key.
+ J = 13,
+
+ /// The "K" key.
+ K = 14,
+
+ /// The "L" key.
+ L = 15,
+
+ /// The "M" key.
+ M = 16,
+
+ /// The "N" key.
+ N = 17,
+
+ /// The "O" key.
+ O = 18,
+
+ /// The "P" key.
+ P = 19,
+
+ /// The "Q" key.
+ Q = 20,
+
+ /// The "R" key.
+ R = 21,
+
+ /// The "S" key.
+ S = 22,
+
+ /// The "T" key.
+ T = 23,
+
+ /// The "U" key.
+ U = 24,
+
+ /// The "V" key.
+ V = 25,
+
+ /// The "W" key.
+ W = 26,
+
+ /// The "X" key.
+ X = 27,
+
+ /// The "Y" key.
+ Y = 28,
+
+ /// The "Z" key.
+ Z = 29,
+
+ /// The "1" key.
+ Number1 = 30,
+
+ /// The "2" key.
+ Number2 = 31,
+
+ /// The "3" key.
+ Number3 = 32,
+
+ /// The "4" key.
+ Number4 = 33,
+
+ /// The "5" key.
+ Number5 = 34,
+
+ /// The "6" key.
+ Number6 = 35,
+
+ /// The "7" key.
+ Number7 = 36,
+
+ /// The "8" key.
+ Number8 = 37,
+
+ /// The "9" key.
+ Number9 = 38,
+
+ /// The "0" key.
+ Number0 = 39,
+
+ /// The "return" key.
+ Return = 40,
+
+ /// The "escape" key.
+ Escape = 41,
+
+ /// The "backspace" key.
+ Backspace = 42,
+
+ /// The "tab" key.
+ Tab = 43,
+
+ /// The "space" key.
+ Space = 44,
+
+ /// The "minus" key.
+ Minus = 45,
+
+ /// The "equals" key.
+ Equals = 46,
+
+ /// The "left bracket" key.
+ LeftBracket = 47,
+
+ /// The "right bracket" key.
+ RightBracket = 48,
+
+ /// The "backslash" key.
+ Backslash = 49,
+
+ ///
+ /// A key with region-specific meanings.
+ ///
+ ///
+ ///
+ /// American \|
+ /// Belgium µ`£
+ /// Canadian-French <}>
+ /// Danish ’*
+ /// Dutch <>
+ /// French *µ
+ /// German #’
+ /// Italian ù§
+ /// Latin-American }`]
+ /// Norwegian ,*
+ /// Spanish }Ç
+ /// Swedish , *
+ /// Swiss $£
+ /// British #~.
+ ///
+ ///
+ NonUs1 = 50,
+
+ /// The "semicolon" key.
+ Semicolon = 51,
+
+ /// The "apostrophe" key.
+ Apostrophe = 52,
+
+ /// The "grave" key.
+ Grave = 53,
+
+ /// The "comma" key.
+ Comma = 54,
+
+ /// The "period" key.
+ Period = 55,
+
+ /// The "slash" key.
+ Slash = 56,
+
+ /// The "caps lock" key.
+ CapsLock = 57,
+
+ /// The first function key.
+ F1 = 58,
+
+ /// The second function key.
+ F2 = 59,
+
+ /// The third function key.
+ F3 = 60,
+
+ /// The fourth function key.
+ F4 = 61,
+
+ /// The fifth function key.
+ F5 = 62,
+
+ /// The sixth function key.
+ F6 = 63,
+
+ /// The seventh function key.
+ F7 = 64,
+
+ /// The eighth function key.
+ F8 = 65,
+
+ /// The ninth function key.
+ F9 = 66,
+
+ /// The tenth function key.
+ F10 = 67,
+
+ /// The eleventh function key.
+ F11 = 68,
+
+ /// The twelfth function key.
+ F12 = 69,
+
+ /// The "print screen" key.
+ PrintScreen = 70,
+
+ /// The "scroll lock" key.
+ ScrollLock = 71,
+
+ /// The "pause" key.
+ Pause = 72,
+
+ /// The "insert" key.
+ Insert = 73,
+
+ /// The "home" key.
+ Home = 74,
+
+ /// The "page up" key.
+ PageUp = 75,
+
+ /// The "delete" key.
+ Delete = 76,
+
+ /// The "end" key.
+ End = 77,
+
+ /// The "page down" key.
+ PageDown = 78,
+
+ /// The "right" key.
+ Right = 79,
+
+ /// The "left" key.
+ Left = 80,
+
+ /// The "down" key.
+ Down = 81,
+
+ /// The "up" key.
+ Up = 82,
+
+ /// The "num lock clear" key.
+ NumLockClear = 83,
+
+ /// The "divide" key on the keypad.
+ KeypadDivide = 84,
+
+ /// The "multiply" key on the keypad.
+ KeypadMultiply = 85,
+
+ /// The "minus" key on the keypad.
+ KeypadMinus = 86,
+
+ /// The "plus" key on the keypad.
+ KeypadPlus = 87,
+
+ /// The "enter" key on the keypad.
+ KeypadEnter = 88,
+
+ /// The "1" key on the keypad.
+ Keypad1 = 89,
+
+ /// The "2" key on the keypad.
+ Keypad2 = 90,
+
+ /// The "3" key on the keypad.
+ Keypad3 = 91,
+
+ /// The "4" key on the keypad.
+ Keypad4 = 92,
+
+ /// The "5" key on the keypad.
+ Keypad5 = 93,
+
+ /// The "6" key on the keypad.
+ Keypad6 = 94,
+
+ /// The "7" key on the keypad.
+ Keypad7 = 95,
+
+ /// The "8" key on the keypad.
+ Keypad8 = 96,
+
+ /// The "9" key on the keypad.
+ Keypad9 = 97,
+
+ /// The "0" key on the keypad.
+ Keypad0 = 98,
+
+ /// The "period" key on the keypad.
+ KeypadPeriod = 99,
+
+ ///
+ /// A key with region-specific meanings, typically near the Left-Shift key in AT-102 implementations.
+ ///
+ ///
+ /// Belg <\>
+ /// FrCa «°»
+ /// Dan <\>
+ /// Dutch ]|[
+ /// Fren <>
+ /// Ger <|>
+ /// Ital <>
+ /// LatAm <>
+ /// Nor <>
+ /// Span <>
+ /// Swed <|>
+ /// Swiss <\>
+ /// UK \|
+ /// Brazil \|
+ ///
+ NonUs2 = 100,
+
+ /// A key for application-defined functions.
+ Application = 101,
+
+ /// The "power" key.
+ Power = 102,
+
+ /// The "equals" key on the keypad.
+ KeypadEquals = 103,
+
+ /// The thirteenth function key.
+ F13 = 104,
+
+ /// The fourteenth function key.
+ F14 = 105,
+
+ /// The fifteenth function key.
+ F15 = 106,
+
+ /// The sixteenth function key.
+ F16 = 107,
+
+ /// The seventeenth function key.
+ F17 = 108,
+
+ /// The eighteenth function key.
+ F18 = 109,
+
+ /// The nineteenth function key.
+ F19 = 110,
+
+ /// The twentieth function key.
+ F20 = 111,
+
+ /// The twenty-first function key.
+ F21 = 112,
+
+ /// The twenty-second function key.
+ F22 = 113,
+
+ /// The twenty-third function key.
+ F23 = 114,
+
+ /// The twenty-fourth function key.
+ F24 = 115,
+
+ /// The "execute" key.
+ Execute = 116,
+
+ /// The "help" key.
+ Help = 117,
+
+ /// The "menu" key.
+ Menu = 118,
+
+ /// The "select" key.
+ Select = 119,
+
+ /// The "stop" key.
+ Stop = 120,
+
+ /// The "again" key.
+ Again = 121,
+
+ /// The "undo" key.
+ Undo = 122,
+
+ /// The "cut" key.
+ Cut = 123,
+
+ /// The "copy" key.
+ Copy = 124,
+
+ /// The "paste" key.
+ Paste = 125,
+
+ /// The "find" key.
+ Find = 126,
+
+ /// The "mute" key.
+ Mute = 127,
+
+ /// The "volume up" key.
+ VolumeUp = 128,
+
+ /// The "volume down" key.
+ VolumeDown = 129,
+
+ // BEFORE ADDING ANYTHING TO THIS FILE MAKE SURE YOU REALISE THAT InputMarshal RELIES ON ASSUMPTIONS ON THE VALUES
+
+ /// The "comma" key on the keypad.
+ KeypadComma = 133,
+
+ /// The alternative "equals" key on the keypad as typically found on AS-400 keyboards.
+ OtherKeypadEquals = 134,
+
+ /// The first international key.
+ International1 = 135,
+
+ /// The second international key.
+ International2 = 136,
+
+ /// The third international key.
+ International3 = 137,
+
+ /// The fourth international key.
+ International4 = 138,
+
+ /// The fifth international key.
+ International5 = 139,
+
+ /// The sixth international key.
+ International6 = 140,
+
+ /// The seventh international key.
+ International7 = 141,
+
+ /// The eighth international key.
+ International8 = 142,
+
+ /// The ninth international key.
+ International9 = 143,
+
+ /// The first language key.
+ Lang1 = 144,
+
+ /// The second language key.
+ Lang2 = 145,
+
+ /// The third language key.
+ Lang3 = 146,
+
+ /// The fourth language key.
+ Lang4 = 147,
+
+ /// The fifth language key.
+ Lang5 = 148,
+
+ /// The sixth language key.
+ Lang6 = 149,
+
+ /// The seventh language key.
+ Lang7 = 150,
+
+ /// The eighth language key.
+ Lang8 = 151,
+
+ /// The ninth language key.
+ Lang9 = 152,
+
+ /// The alternative "erase" key, for example an Erase-Eaze™ key.
+ AlternativeErase = 153,
+
+ /// The "system request" key.
+ SystemRequest = 154,
+
+ /// The "cancel" key.
+ Cancel = 155,
+
+ /// The "clear" key.
+ Clear = 156,
+
+ /// The "prior" key.
+ Prior = 157,
+
+ /// An alternative "return" key.
+ Return2 = 158,
+
+ /// The "separator" key.
+ Separator = 159,
+
+ /// The "out" key.
+ Out = 160,
+
+ /// The "operation" key.
+ Oper = 161,
+
+ /// The "clear again" key.
+ ClearAgain = 162,
+
+ /// The "cursor select" key.
+ ///
+ /// For more information consult IBM's "3174 Establishment Controller - Terminal User's Reference for Expanded
+ /// Functions" (GA23-03320-02, May 1989)
+ ///
+ CursorSelect = 163,
+
+ /// The "extend select" key.
+ ///
+ /// For more information consult IBM's "3174 Establishment Controller - Terminal User's Reference for Expanded
+ /// Functions" (GA23-03320-02, May 1989)
+ ///
+ ExtendSelect = 164,
+
+ // BEFORE ADDING ANYTHING TO THIS FILE MAKE SURE YOU REALISE THAT InputMarshal RELIES ON ASSUMPTIONS ON THE VALUES
+
+ /// The "00" key on the keypad.
+ Keypad00 = 176,
+
+ /// The "000" key on the keypad.
+ Keypad000 = 177,
+
+ /// The "thousands separator" key.
+ /// Interpreted as a comma for en-US.
+ ThousandsSeparator = 178,
+
+ /// The "decimal separator" key.
+ /// Interpreted as a period for en-US.
+ DecimalSeparator = 179,
+
+ /// The "currency unit" key.
+ /// Interpreted as a dollar sign for en-US.
+ CurrencyUnit = 180,
+
+ /// The "currencySubunit" key.
+ /// Interpreted as a cents symbol for en-US.
+ CurrencySubunit = 181,
+
+ /// The "leftParenthesis" key on the keypad.
+ KeypadLeftParenthesis = 182,
+
+ /// The "rightParenthesis" key on the keypad.
+ KeypadRightParenthesis = 183,
+
+ /// The "leftBrace" key on the keypad.
+ KeypadLeftBrace = 184,
+
+ /// The "rightBrace" key on the keypad.
+ KeypadRightBrace = 185,
+
+ /// The "tab" key on the keypad.
+ KeypadTab = 186,
+
+ /// The "backspace" key on the keypad.
+ KeypadBackspace = 187,
+
+ /// The "a" key on the keypad.
+ KeypadA = 188,
+
+ /// The "b" key on the keypad.
+ KeypadB = 189,
+
+ /// The "c" key on the keypad.
+ KeypadC = 190,
+
+ /// The "d" key on the keypad.
+ KeypadD = 191,
+
+ /// The "e" key on the keypad.
+ KeypadE = 192,
+
+ /// The "f" key on the keypad.
+ KeypadF = 193,
+
+ /// The "xor" key on the keypad.
+ KeypadXor = 194,
+
+ /// The "power" key on the keypad.
+ KeypadPower = 195,
+
+ /// The "percent" key on the keypad.
+ KeypadPercent = 196,
+
+ /// The "less" key on the keypad.
+ KeypadLess = 197,
+
+ /// The "greater" key on the keypad.
+ KeypadGreater = 198,
+
+ /// The "ampersand" key on the keypad.
+ KeypadAmpersand = 199,
+
+ /// The "doubleAmpersand" key on the keypad.
+ KeypadDoubleAmpersand = 200,
+
+ /// The "vertical bar" key on the keypad.
+ KeypadVerticalBar = 201,
+
+ /// The "double vertical bar" key on the keypad.
+ KeypadDoubleVerticalBar = 202,
+
+ /// The "colon" key on the keypad.
+ KeypadColon = 203,
+
+ /// The "hash" key on the keypad.
+ KeypadHash = 204,
+
+ /// The "space" key on the keypad.
+ KeypadSpace = 205,
+
+ /// The "@" key on the keypad.
+ KeypadAt = 206,
+
+ /// The "exclamation" key on the keypad.
+ KeypadExclamation = 207,
+
+ /// The "memory store" key on the keypad.
+ KeypadMemoryStore = 208,
+
+ /// The "memory recall" key on the keypad.
+ KeypadMemoryRecall = 209,
+
+ /// The "memory clear" key on the keypad.
+ KeypadMemoryClear = 210,
+
+ /// The "memory add" key on the keypad.
+ KeypadMemoryAdd = 211,
+
+ /// The "memory subtract" key on the keypad.
+ KeypadMemorySubtract = 212,
+
+ /// The "memory multiply" key on the keypad.
+ KeypadMemoryMultiply = 213,
+
+ /// The "memory divide" key on the keypad.
+ KeypadMemoryDivide = 214,
+
+ /// The "plus/minus" key on the keypad.
+ KeypadPlusMinus = 215,
+
+ /// The "clear" key on the keypad.
+ KeypadClear = 216,
+
+ /// The "clear entry" key on the keypad.
+ KeypadClearEntry = 217,
+
+ /// The "binary" key on the keypad.
+ KeypadBinary = 218,
+
+ /// The "octal" key on the keypad.
+ KeypadOctal = 219,
+
+ /// The "decimal" key on the keypad.
+ KeypadDecimal = 220,
+
+ /// The "hexadecimal" key on the keypad.
+ KeypadHexadecimal = 221,
+
+ // BEFORE ADDING ANYTHING TO THIS FILE MAKE SURE YOU REALISE THAT InputMarshal RELIES ON ASSUMPTIONS ON THE VALUES
+
+ /// The left "control" key.
+ ControlLeft = 224,
+
+ /// The left "shift" key.
+ ShiftLeft = 225,
+
+ /// The left "alt" key.
+ AltLeft = 226,
+
+ /// The left "super" (e.g. Windows/Start) key.
+ SuperLeft = 227,
+
+ /// The right "control" key.
+ ControlRight = 228,
+
+ /// The right "shift" key.
+ ShiftRight = 229,
+
+ /// The right "alt" key.
+ AltRight = 230,
+
+ /// The right "super" (e.g. Windows/Start) key.
+ SuperRight = 231,
+
+ // 232-256..... wtf?
+ // BEFORE ADDING ANYTHING TO THIS FILE MAKE SURE YOU REALISE THAT InputMarshal RELIES ON ASSUMPTIONS ON THE VALUES
+
+ /// The "mode" key.
+ Mode = 257,
+
+ // These values are mapped from usage page 0x0C (USB consumer page).
+ /// The "sleep" key.
+ Sleep = 258,
+
+ /// The "wake" key.
+ Wake = 259,
+
+ /// The "channel increment" key.
+ ChannelIncrement = 260,
+
+ /// The "channel decrement" key.
+ ChannelDecrement = 261,
+
+ /// The "play" media key.
+ MediaPlay = 262,
+
+ /// The "pause" media key.
+ MediaPause = 263,
+
+ /// The "record" media key.
+ MediaRecord = 264,
+
+ /// The "fast forward" media key.
+ MediaFastForward = 265,
+
+ /// The "rewind" media key.
+ MediaRewind = 266,
+
+ /// The "next track" media key.
+ MediaNextTrack = 267,
+
+ /// The "previous track" media key.
+ MediaPreviousTrack = 268,
+
+ /// The "stop" media key.
+ MediaStop = 269,
+
+ /// The "eject" media key.
+ MediaEject = 270,
+
+ /// The "play/pause" media key.
+ MediaPlayPause = 271,
+
+ /// The "select" media key.
+ MediaSelect = 272,
+
+ /// The "new" application key.
+ ApplicationNew = 273,
+
+ /// The "open" application key.
+ ApplicationOpen = 274,
+
+ /// The "close" application key.
+ ApplicationClose = 275,
+
+ /// The "exit" application key.
+ ApplicationExit = 276,
+
+ /// The "save" application key.
+ ApplicationSave = 277,
+
+ /// The "print" application key.
+ ApplicationPrint = 278,
+
+ /// The "properties" application key.
+ ApplicationProperties = 279,
+
+ /// The "search" application key.
+ ApplicationSearch = 280,
+
+ /// The "home" application key.
+ ApplicationHome = 281,
+
+ /// The "back" application key.
+ ApplicationBack = 282,
+
+ /// The "forward" application key.
+ ApplicationForward = 283,
+
+ /// The "stop" application key.
+ ApplicationStop = 284,
+
+ /// The "refresh" application key.
+ ApplicationRefresh = 285,
+
+ /// The "bookmarks" application key.
+ ApplicationBookmarks = 286,
+
+ // BEFORE ADDING ANYTHING TO THIS FILE MAKE SURE YOU REALISE THAT InputMarshal RELIES ON ASSUMPTIONS ON THE VALUES
+
+ // 501-512 is reserved for non-standard (i.e. not from an industry-standard HID page) keys.
+ /// The left soft key e.g. the left button on a mobile phone.
+ /// This is not from an industry-standard HID page.
+ SoftLeft = 501,
+
+ /// The right soft key e.g. the right button on a mobile phone.
+ /// This is not from an industry-standard HID page.
+ SoftRight = 502,
+
+ /// The "call" key.
+ /// This is not from an industry-standard HID page.
+ Call = 503,
+
+ /// The "end call" key.
+ /// This is not from an industry-standard HID page.
+ EndCall = 504,
+
+ // BEFORE ADDING ANYTHING TO THIS FILE MAKE SURE YOU REALISE THAT InputMarshal RELIES ON ASSUMPTIONS ON THE VALUES
+}
diff --git a/sources/Input/Input/KeyboardState.cs b/sources/Input/Input/KeyboardState.cs
new file mode 100644
index 0000000000..a9e28ac478
--- /dev/null
+++ b/sources/Input/Input/KeyboardState.cs
@@ -0,0 +1,23 @@
+namespace Silk.NET.Input;
+
+///
+/// Contains user input received from an .
+///
+public class KeyboardState
+{
+ ///
+ /// Gets the text that has been typed since has been called. This will be cleared
+ /// when is called.
+ ///
+ public InputReadOnlyList? Text { get; }
+
+ ///
+ /// Gets the key state, denoting which keys are pressed on the keyboard.
+ ///
+ public ButtonReadOnlyList Keys { get; }
+
+ ///
+ /// Gets the active modifier keys.
+ ///
+ public KeyModifiers Modifiers { get; }
+}
\ No newline at end of file
diff --git a/sources/Input/Input/Keyboards.cs b/sources/Input/Input/Keyboards.cs
new file mode 100644
index 0000000000..34c59a25c6
--- /dev/null
+++ b/sources/Input/Input/Keyboards.cs
@@ -0,0 +1,33 @@
+namespace Silk.NET.Input;
+
+///
+/// Represents a collection of s from which input events can be received.
+///
+public sealed class Keyboards : InputContextDeviceList, IKeyboardInputHandler
+{
+ internal Keyboards(InputContext ctx)
+ : base(ctx) { }
+
+ ///
+ /// Raised when state pertaining to a pushable key on the keyboard changes (e.g. key up, key down, key repeat).
+ ///
+ public event Action? KeyChanged;
+
+ ///
+ /// Raised when the user types a character using the keyboard.
+ ///
+ public event Action? KeyChar;
+
+ internal void HandleButtonChanged(ButtonChangedEvent @event) { }
+
+ void IButtonInputHandler.HandleButtonChanged(ButtonChangedEvent @event) =>
+ HandleButtonChanged(@event);
+
+ internal void HandleKeyChanged(KeyChangedEvent @event) => KeyChanged?.Invoke(@event);
+
+ void IKeyboardInputHandler.HandleKeyChanged(KeyChangedEvent @event) => HandleKeyChanged(@event);
+
+ internal void HandleKeyChar(KeyCharEvent @event) => KeyChar?.Invoke(@event);
+
+ void IKeyboardInputHandler.HandleKeyChar(KeyCharEvent @event) => HandleKeyChar(@event);
+}
diff --git a/sources/Input/Input/MouseScrollEvent.cs b/sources/Input/Input/MouseScrollEvent.cs
new file mode 100644
index 0000000000..737950479e
--- /dev/null
+++ b/sources/Input/Input/MouseScrollEvent.cs
@@ -0,0 +1,18 @@
+using System.Diagnostics;
+using System.Numerics;
+
+namespace Silk.NET.Input;
+
+///
+/// Contains information pertaining to the user scrolling using a mouse scroll wheel.
+///
+/// The mouse on which the scroll wheel resides.
+///
+/// The timestamp (as retrieved from ) at which the event occurred.
+///
+/// The mouse's active point when the scroll event occurred.
+/// The after the event occurred.
+///
+/// The change in as a result of this event represented as a number of ratchets.
+///
+public readonly record struct MouseScrollEvent(IMouse Mouse, long Timestamp, TargetPoint Point, Vector2 WheelPosition, Vector2 Delta);
\ No newline at end of file
diff --git a/sources/Input/Input/MouseState.cs b/sources/Input/Input/MouseState.cs
new file mode 100644
index 0000000000..fe6a776b0e
--- /dev/null
+++ b/sources/Input/Input/MouseState.cs
@@ -0,0 +1,14 @@
+using System.Numerics;
+
+namespace Silk.NET.Input;
+
+///
+/// Contains user input received from an .
+///
+public class MouseState : PointerState
+{
+ ///
+ /// Gets the current position of the scroll wheel in number of ratchets.
+ ///
+ public Vector2 WheelPosition { get; }
+}
\ No newline at end of file
diff --git a/sources/Input/Input/PointChangedEvent.cs b/sources/Input/Input/PointChangedEvent.cs
new file mode 100644
index 0000000000..cf9ae15e8c
--- /dev/null
+++ b/sources/Input/Input/PointChangedEvent.cs
@@ -0,0 +1,26 @@
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+
+namespace Silk.NET.Input;
+
+///
+/// Contains information pertaining to a change on a ,
+///
+/// The pointer device with which the user is pointing.
+///
+/// The timestamp (as retrieved from ) at which the event occurred.
+///
+///
+/// The previous state for this . If this is a new point (e.g. a finger has only just touched a
+/// touch screen), this shall be null .
+///
+///
+/// The new state for this . If the point is no longer valid (e.g. a finger is no longer
+/// touching a touch screen), this shall be null .
+///
+public readonly record struct PointChangedEvent(
+ IPointerDevice Pointer,
+ long Timestamp,
+ TargetPoint? OldPoint,
+ TargetPoint? NewPoint
+);
diff --git a/sources/Input/Input/PointerButton.cs b/sources/Input/Input/PointerButton.cs
new file mode 100644
index 0000000000..f0874e646e
--- /dev/null
+++ b/sources/Input/Input/PointerButton.cs
@@ -0,0 +1,184 @@
+namespace Silk.NET.Input;
+
+///
+/// Enumerates the buttons available on pointer devices.
+///
+public enum PointerButton
+{
+ ///
+ /// An unrecognised button.
+ ///
+ Unknown,
+
+ ///
+ /// The primary button e.g. left click.
+ ///
+ Primary,
+
+ ///
+ /// The secondary button e.g. right click.
+ ///
+ Secondary,
+
+ ///
+ /// The third button.
+ ///
+ Button3,
+
+ ///
+ /// The middle button i.e. clicking the scroll wheel down. This acts as the third button.
+ ///
+ MiddleButton = Button3,
+
+ ///
+ /// The fourth button.
+ ///
+ Button4,
+
+ ///
+ /// The fifth button.
+ ///
+ Button5,
+
+ ///
+ /// The sixth button.
+ ///
+ Button6,
+
+ ///
+ /// The seventh button.
+ ///
+ Button7,
+
+ ///
+ /// The eighth button.
+ ///
+ Button8,
+
+ ///
+ /// The ninth button.
+ ///
+ Button9,
+
+ ///
+ /// The tenth button.
+ ///
+ Button10,
+
+ ///
+ /// The eleventh button.
+ ///
+ Button11,
+
+ ///
+ /// The twelveth button.
+ ///
+ Button12,
+
+ ///
+ /// The thirteenth button.
+ ///
+ Button13,
+
+ ///
+ /// The fourteenth button.
+ ///
+ Button14,
+
+ ///
+ /// The fifteenth button.
+ ///
+ Button15,
+
+ ///
+ /// The sixteenth button.
+ ///
+ Button16,
+
+ ///
+ /// The seventeenth button.
+ ///
+ Button17,
+
+ ///
+ /// The eighteenth button.
+ ///
+ Button18,
+
+ ///
+ /// The nineteenth button.
+ ///
+ Button19,
+
+ ///
+ /// The twentieth button.
+ ///
+ Button20,
+
+ ///
+ /// The twenty-first button.
+ ///
+ Button21,
+
+ ///
+ /// The twenty-second button.
+ ///
+ Button22,
+
+ ///
+ /// The twenty-third button.
+ ///
+ Button23,
+
+ ///
+ /// The twenty-fourth button.
+ ///
+ Button24,
+
+ ///
+ /// The twenty-fifth button.
+ ///
+ Button25,
+
+ ///
+ /// The twenty-sixth button.
+ ///
+ Button26,
+
+ ///
+ /// The twenty-seventh button.
+ ///
+ Button27,
+
+ ///
+ /// The twenty-eighth button.
+ ///
+ Button28,
+
+ ///
+ /// The twenty-ninth button.
+ ///
+ Button29,
+
+ ///
+ /// The thirtieth button.
+ ///
+ Button30,
+
+ ///
+ /// The eraser tip of a pen pointer device. This acts as the thirtieth button.
+ ///
+ EraserTip = Button30,
+
+ ///
+ /// The thirty-first button.
+ ///
+ Button31,
+
+ ///
+ /// The thirty-second button.
+ ///
+ Button32,
+
+ // BEFORE ADDING MORE BUTTONS, ENSURE YOU CHANGE InputMarshal TO ACCOUNT FOR THE NEW MAX
+}
diff --git a/sources/Input/Input/PointerClickConfiguration.cs b/sources/Input/Input/PointerClickConfiguration.cs
new file mode 100644
index 0000000000..4612da0780
--- /dev/null
+++ b/sources/Input/Input/PointerClickConfiguration.cs
@@ -0,0 +1,19 @@
+namespace Silk.NET.Input;
+
+///
+/// Denotes the configuration for recognising events apart from single
+/// events.
+///
+///
+/// The maximum time in milliseconds between two consecutive clicks to count as a double click.
+///
+///
+/// The maximum distance in pixels between two consecutive clicks to count as a double click.
+///
+public record struct PointerClickConfiguration(int DoubleClickTime, float DoubleClickRange)
+{
+ ///
+ /// Gets the default configuration.
+ ///
+ public static PointerClickConfiguration Default => new(500, 4);
+}
diff --git a/sources/Input/Input/PointerClickEvent.cs b/sources/Input/Input/PointerClickEvent.cs
new file mode 100644
index 0000000000..b5c75eece6
--- /dev/null
+++ b/sources/Input/Input/PointerClickEvent.cs
@@ -0,0 +1,17 @@
+using System.Diagnostics;
+
+namespace Silk.NET.Input;
+
+///
+/// Contains information pertaining to a pointer button being pressed and released (i.e. clicked).
+///
+/// The pointer device on which the button being pressed and released resides.
+///
+/// The timestamp (as retrieved from ) at which the event occurred.
+///
+///
+/// A specific for which the button press occurred, check to
+/// validate if such a point was available.
+///
+/// The button that was pressed and released in succession.
+public readonly record struct PointerClickEvent(IPointerDevice Pointer, long Timestamp, TargetPoint Point, PointerButton Button);
\ No newline at end of file
diff --git a/sources/Input/Input/PointerGripChangedEvent.cs b/sources/Input/Input/PointerGripChangedEvent.cs
new file mode 100644
index 0000000000..079e1e4e8a
--- /dev/null
+++ b/sources/Input/Input/PointerGripChangedEvent.cs
@@ -0,0 +1,18 @@
+using System.Diagnostics;
+
+namespace Silk.NET.Input;
+
+///
+/// Contains information pertaining to the user changing the pressure with which they're applying their grip on the
+/// given pointer device.
+///
+/// The pointer device the user is gripping.
+///
+/// The timestamp (as retrieved from ) at which the event occurred.
+///
+///
+/// The grip pressure being applied to the device, where 0.0 is the lowest amount of pressure measurable by the
+/// device and 1.0 is the maximum amount of pressure measurable by the device.
+///
+/// The change in from its previous value.
+public readonly record struct PointerGripChangedEvent(IPointerDevice Pointer, long Timestamp, float GripPressure, float Delta);
\ No newline at end of file
diff --git a/sources/Input/Input/PointerState.cs b/sources/Input/Input/PointerState.cs
new file mode 100644
index 0000000000..b69372214c
--- /dev/null
+++ b/sources/Input/Input/PointerState.cs
@@ -0,0 +1,23 @@
+namespace Silk.NET.Input;
+
+///
+/// Contains user input state received from an .
+///
+public class PointerState
+{
+ ///
+ /// Gets the captured state of each of the buttons on the device.
+ ///
+ public ButtonReadOnlyList Buttons { get; }
+
+ ///
+ /// Gets the points on the targets at which the user is pointing using the device.
+ ///
+ public InputReadOnlyList Points { get; }
+
+ ///
+ /// Gets the pressure the user is applying to the grip of the pointer device, where 0.0 is the lowest
+ /// measurable pressure and 1.0 is the highest measurable pressure.
+ ///
+ public float GripPressure { get; }
+}
\ No newline at end of file
diff --git a/sources/Input/Input/PointerTargetChangedEvent.cs b/sources/Input/Input/PointerTargetChangedEvent.cs
new file mode 100644
index 0000000000..49a97e5950
--- /dev/null
+++ b/sources/Input/Input/PointerTargetChangedEvent.cs
@@ -0,0 +1,27 @@
+using System.Diagnostics;
+using Silk.NET.Maths;
+
+namespace Silk.NET.Input;
+
+///
+/// Contains information pertaining to changes to a "target" at which the user can point using a pointer device.
+///
+/// The pointer with which the user can point at the given target.
+///
+/// The timestamp (as retrieved from ) at which the event occurred.
+///
+/// The target at which the user can point.
+///
+/// true if this is a newly-added target to ,
+/// false if this target has been removed from the list of available ,
+/// null if there has been no change to the target's validity.
+///
+///
+/// The old of the target. This may be the same as if there
+/// has been no change.
+///
+///
+/// The new of the target. This may be the same as if there
+/// has been no change.
+///
+public readonly record struct PointerTargetChangedEvent(IPointerDevice Pointer, long Timestamp, IPointerTarget Target, bool? IsAdded, Box3D OldBounds, Box3D NewBounds);
\ No newline at end of file
diff --git a/sources/Input/Input/Pointers.cs b/sources/Input/Input/Pointers.cs
new file mode 100644
index 0000000000..f1d5e84181
--- /dev/null
+++ b/sources/Input/Input/Pointers.cs
@@ -0,0 +1,367 @@
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Numerics;
+using System.Runtime.InteropServices;
+
+namespace Silk.NET.Input;
+
+///
+/// Represents a collection of s from which input events can be received.
+///
+public sealed class Pointers
+ : InputContextDeviceList,
+ IMouseInputHandler,
+ IPointerInputHandler
+{
+ private long _doubleClickTime;
+ private float _doubleClickRange;
+ private List? _clicks;
+
+ internal Pointers(InputContext ctx)
+ : base(ctx) => ClickConfiguration = PointerClickConfiguration.Default;
+
+ ///
+ /// Gets or sets the configuration that denotes the behaviour of