diff --git a/examples/CSharp/OpenGL Demos/AndroidInputDemo/AndroidManifest.xml b/examples/CSharp/OpenGL Demos/AndroidInputDemo/AndroidManifest.xml
index c4f3a7d809..dba50ab426 100644
--- a/examples/CSharp/OpenGL Demos/AndroidInputDemo/AndroidManifest.xml	
+++ b/examples/CSharp/OpenGL Demos/AndroidInputDemo/AndroidManifest.xml	
@@ -1,8 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android" 
-          android:versionCode="1" 
-          android:versionName="1.0" 
-          package="com.companyname.AndroidInputDemo">
-  <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true">
-  </application>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.companyname.AndroidInputDemo">
+	<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
+	<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
+	<application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true"></application>
+	<uses-sdk />
 </manifest>
\ No newline at end of file
diff --git a/examples/CSharp/OpenGL Demos/AndroidInputDemo/MainActivity.cs b/examples/CSharp/OpenGL Demos/AndroidInputDemo/MainActivity.cs
index 145cd47cb6..ddba6dfdd7 100644
--- a/examples/CSharp/OpenGL Demos/AndroidInputDemo/MainActivity.cs	
+++ b/examples/CSharp/OpenGL Demos/AndroidInputDemo/MainActivity.cs	
@@ -34,6 +34,10 @@ public class MainActivity : SilkActivity
         private static int counter = 0;
 
         private static IInputContext input;
+        private static ITouchDevice currentTouchDevice;
+        private static Gesture trackedGestures = Gesture.All;
+        private static DoubleTapBehavior doubleTapBehavior = DoubleTapBehavior.EmitFirstSingleTap;
+        private static MultiGestureHandling multiGestureHandling = MultiGestureHandling.RecognizeBothGestures;
 
         /// <summary>
         /// This is where the application starts.
@@ -69,6 +73,15 @@ private unsafe static void OnLoad()
 
             input.Keyboards[0].KeyChar += KeyChar;
 
+            if (input.PrimaryTouchDevice != null)
+                SetupTouchDevice(input.PrimaryTouchDevice);
+
+            input.ConnectionChanged += (device, connected) =>
+            {
+                if (connected && device is ITouchDevice touchDevice)
+                    SetupTouchDevice(touchDevice);
+            };
+
             Vbo = new BufferObject<float>(Gl, Vertices, BufferTargetARB.ArrayBuffer);
             Vao = new VertexArrayObject<float, uint>(Gl, Vbo, null);
 
@@ -82,12 +95,96 @@ private unsafe static void OnLoad()
                 1.0f, 2.0f);
         }
 
+        private static void GestureRecognizer_Rotate(Vector2 position, float angle)
+        {
+            DebugLog($"Rotate gesture at {position} with rotation {angle} degree");
+        }
+
+        private static void GestureRecognizer_Zoom(Vector2 position, float zoom)
+        {
+            DebugLog($"Zoom gesture at {position} with zoom {zoom}");
+        }
+
+        private static void GestureRecognizer_Hold(Vector2 position)
+        {
+            Gl.ClearColor(1.0f, 0.0f, 0.0f, 1.0f);
+            DebugLog($"Hold gesture at {position}");
+        }
+
+        private static void GestureRecognizer_Swipe(Vector2 direction)
+        {
+            DebugLog($"Swipe gesture with direction {direction}");
+        }
+
+        private static void GestureRecognizer_DoubleTap(Vector2 position)
+        {
+            Gl.ClearColor(0.0f, 0.0f, 1.0f, 1.0f);
+            DebugLog($"Double Tap gesture at {position}");
+        }
+
+        private static void GestureRecognizer_Tap(Vector2 position)
+        {
+            Gl.ClearColor(0.0f, 1.0f, 0.0f, 1.0f);
+            DebugLog($"Tap gesture at {position}");
+        }
+
         private static void KeyChar(IKeyboard arg1, char arg2)
         {
             if (arg2 == 'c')
                 Array.Clear(Vertices);
             if (arg2 == 'k')
                 input.Keyboards[0].EndInput();
+            if (arg2 == 't')
+                ToggleGesture(Gesture.Tap);
+            if (arg2 == 'd')
+                ToggleGesture(Gesture.DoubleTap);
+            if (arg2 == 's')
+                ToggleGesture(Gesture.Swipe);
+            if (arg2 == 'h')
+                ToggleGesture(Gesture.Hold);
+            if (arg2 == 'z')
+                ToggleGesture(Gesture.Zoom);
+            if (arg2 == 'r')
+                ToggleGesture(Gesture.Rotate);
+            if (arg2 == '0')
+                SetDoubleTapBehavior(DoubleTapBehavior.WaitForDoubleTapTimeElapse);
+            if (arg2 == '1')
+                SetDoubleTapBehavior(DoubleTapBehavior.EmitFirstSingleTap);
+            if (arg2 == '2')
+                SetDoubleTapBehavior(DoubleTapBehavior.EmitBothSingleTaps);
+            if (arg2 == '7')
+                SetMultiGestureHandling(MultiGestureHandling.RecognizeBothGestures);
+            if (arg2 == '8')
+                SetMultiGestureHandling(MultiGestureHandling.PrioritizeZoomGesture);
+            if (arg2 == '9')
+                SetMultiGestureHandling(MultiGestureHandling.PrioritizeRotateGesture);
+        }
+
+        private static void ToggleGesture(Gesture gesture)
+        {
+            if (trackedGestures.HasFlag(gesture))
+                trackedGestures &= ~gesture;
+            else
+                trackedGestures |= gesture;
+
+            if (currentTouchDevice?.GestureRecognizer != null)
+                currentTouchDevice.GestureRecognizer.TrackedGestures = trackedGestures;
+        }
+
+        private static void SetDoubleTapBehavior(DoubleTapBehavior doubleTapBehavior)
+        {
+            MainActivity.doubleTapBehavior = doubleTapBehavior;
+
+            if (currentTouchDevice?.GestureRecognizer != null)
+                currentTouchDevice.GestureRecognizer.DoubleTapBehavior = doubleTapBehavior;
+        }
+
+        private static void SetMultiGestureHandling(MultiGestureHandling multiGestureHandling)
+        {
+            MainActivity.multiGestureHandling = multiGestureHandling;
+
+            if (currentTouchDevice?.GestureRecognizer != null)
+                currentTouchDevice.GestureRecognizer.MultiGestureHandling = multiGestureHandling;
         }
 
         private static void MouseDown(IMouse arg1, MouseButton arg2)
@@ -131,5 +228,41 @@ private static void OnClose()
             Vao.Dispose();
             Shader.Dispose();
         }
+
+        private static void SetupTouchDevice(ITouchDevice touchDevice)
+        {
+            if (currentTouchDevice == touchDevice)
+                return;
+
+            TouchGestureRecognizer gestureRecognizer;
+
+            if (currentTouchDevice != null)
+            {
+                gestureRecognizer = currentTouchDevice.GestureRecognizer;
+                gestureRecognizer.Tap -= GestureRecognizer_Tap;
+                gestureRecognizer.DoubleTap -= GestureRecognizer_DoubleTap;
+                gestureRecognizer.Swipe -= GestureRecognizer_Swipe;
+                gestureRecognizer.Hold -= GestureRecognizer_Hold;
+                gestureRecognizer.Zoom -= GestureRecognizer_Zoom;
+                gestureRecognizer.Rotate -= GestureRecognizer_Rotate;
+            }
+
+            currentTouchDevice = touchDevice;
+            gestureRecognizer = currentTouchDevice.GestureRecognizer;
+            gestureRecognizer.TrackedGestures = trackedGestures;
+            gestureRecognizer.DoubleTapBehavior = doubleTapBehavior;
+            gestureRecognizer.MultiGestureHandling = multiGestureHandling;
+            gestureRecognizer.Tap += GestureRecognizer_Tap;
+            gestureRecognizer.DoubleTap += GestureRecognizer_DoubleTap;
+            gestureRecognizer.Swipe += GestureRecognizer_Swipe;
+            gestureRecognizer.Hold += GestureRecognizer_Hold;
+            gestureRecognizer.Zoom += GestureRecognizer_Zoom;
+            gestureRecognizer.Rotate += GestureRecognizer_Rotate;
+        }
+
+        private static void DebugLog(string message)
+        {
+            Android.Util.Log.Debug(nameof(AndroidInputDemo), message);
+        }
     }
-}
\ No newline at end of file
+}
diff --git a/src/Input/Silk.NET.Input.Common/Enums/DoubleTapBehavior.cs b/src/Input/Silk.NET.Input.Common/Enums/DoubleTapBehavior.cs
new file mode 100644
index 0000000000..2ddb3493a1
--- /dev/null
+++ b/src/Input/Silk.NET.Input.Common/Enums/DoubleTapBehavior.cs
@@ -0,0 +1,27 @@
+// 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
+{
+    /// <summary>
+    /// Controls the behavior of the double tap gesture tracking.
+    /// </summary>
+    public enum DoubleTapBehavior
+    {
+        /// <summary>
+        /// Always emit the first tap as a single tap.
+        /// </summary>
+        EmitFirstSingleTap,
+
+        /// <summary>
+        /// Always emit the first and second tap as single taps.
+        /// </summary>
+        EmitBothSingleTaps,
+
+        /// <summary>
+        /// Do not emit single taps and wait for the double tap time to elapse.
+        /// The first single tap is only emitted after the time has elapsed.
+        /// </summary>
+        WaitForDoubleTapTimeElapse
+    }
+}
diff --git a/src/Input/Silk.NET.Input.Common/Enums/Gesture.cs b/src/Input/Silk.NET.Input.Common/Enums/Gesture.cs
new file mode 100644
index 0000000000..df887190bb
--- /dev/null
+++ b/src/Input/Silk.NET.Input.Common/Enums/Gesture.cs
@@ -0,0 +1,54 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+
+namespace Silk.NET.Input
+{
+    /// <summary>
+    /// Recognizable touch gesture.
+    /// </summary>
+    [Flags]
+    public enum Gesture
+    {
+        /// <summary>
+        /// No gesture.
+        /// </summary>
+        None = 0,
+
+        /// <summary>
+        /// Tap gesture.
+        /// </summary>
+        Tap = 1,
+
+        /// <summary>
+        /// Double tap gesture.
+        /// </summary>
+        DoubleTap = 2,
+
+        /// <summary>
+        /// Swipe gesture.
+        /// </summary>
+        Swipe = 4,
+
+        /// <summary>
+        /// Long hold gesture.
+        /// </summary>
+        Hold = 8,
+
+        /// <summary>
+        /// Zoom gesture.
+        /// </summary>
+        Zoom = 16,
+
+        /// <summary>
+        /// Rotate gesture.
+        /// </summary>
+        Rotate = 32,
+
+        /// <summary>
+        /// All gestures.
+        /// </summary>
+        All = Tap | DoubleTap | Swipe | Hold | Zoom | Rotate
+    }
+}
diff --git a/src/Input/Silk.NET.Input.Common/Enums/MultiGestureHandling.cs b/src/Input/Silk.NET.Input.Common/Enums/MultiGestureHandling.cs
new file mode 100644
index 0000000000..420266ea52
--- /dev/null
+++ b/src/Input/Silk.NET.Input.Common/Enums/MultiGestureHandling.cs
@@ -0,0 +1,26 @@
+// 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
+{
+    /// <summary>
+    /// Controls the behavior of how multiple two-finger gestures are handled.
+    /// </summary>
+    public enum MultiGestureHandling
+    {
+        /// <summary>
+        /// Only recognize the zoom gesture if both the zoom and the rotate gesture are performed.
+        /// </summary>
+        PrioritizeZoomGesture,
+
+        /// <summary>
+        /// Only recognize the rotate gesture if both the zoom and the rotate gesture are performed.
+        /// </summary>
+        PrioritizeRotateGesture,
+
+        /// <summary>
+        /// Recognize both gestures if they are performed (zoom and rotate).
+        /// </summary>
+        RecognizeBothGestures
+    }
+}
diff --git a/src/Input/Silk.NET.Input.Common/Interfaces/IInputContext.cs b/src/Input/Silk.NET.Input.Common/Interfaces/IInputContext.cs
index 4f32ab9312..c4f20f3234 100644
--- a/src/Input/Silk.NET.Input.Common/Interfaces/IInputContext.cs
+++ b/src/Input/Silk.NET.Input.Common/Interfaces/IInputContext.cs
@@ -44,6 +44,25 @@ public interface IInputContext : IDisposable
         /// </remarks>
         IReadOnlyList<IMouse> Mice { get; }
 
+        /// <summary>
+        /// A list of all available touch devices.
+        /// </summary>
+        /// <remarks>
+        /// On some backends, this list may only contain 1 item. This is most likely because the underlying API doesn't
+        /// support multiple touch devices.
+        /// On some backends, this list might be empty. This is most likely because the underlying API doesn't
+        /// support touch devices.
+        /// </remarks>
+        IReadOnlyList<ITouchDevice> TouchDevices { get; }
+
+        /// <summary>
+        /// The primary touch device if any.
+        /// </summary>
+        /// <remarks>
+        /// On some backends this might just be an educated guess. Or might be null even though there are touch devices.
+        /// </remarks>
+        ITouchDevice? PrimaryTouchDevice { get; }
+
         /// <summary>
         /// A list of all other available input devices.
         /// </summary>
diff --git a/src/Input/Silk.NET.Input.Common/Interfaces/ITouchDevice.cs b/src/Input/Silk.NET.Input.Common/Interfaces/ITouchDevice.cs
new file mode 100644
index 0000000000..2e75d8342b
--- /dev/null
+++ b/src/Input/Silk.NET.Input.Common/Interfaces/ITouchDevice.cs
@@ -0,0 +1,52 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using System.Numerics;
+
+namespace Silk.NET.Input
+{
+    /// <summary>
+    /// An interface representing a touch device.
+    /// </summary>
+    public interface ITouchDevice : IInputDevice
+    {
+        /// <summary>
+        /// The known fingers this touch device has tracked.
+        /// </summary>
+        // ReSharper disable once ReturnTypeCanBeEnumerable.Global
+        IReadOnlyDictionary<long, TouchFinger> Fingers { get; }
+
+        /// <summary>
+        /// A recognizer for gestures.
+        /// </summary>
+        TouchGestureRecognizer GestureRecognizer { get; }
+
+        /// <summary>
+        /// Checks if a specific finger is currently down on the touch surface.
+        /// </summary>
+        /// <param name="index">The finger index to check.</param>
+        /// <returns>Whether or not the finger is pressed down.</returns>
+        bool IsFingerDown(long index);
+
+        /// <summary>
+        /// Called when a finger touches the surface.
+        /// </summary>
+        event Action<ITouchDevice, TouchFinger>? FingerDown;
+
+        /// <summary>
+        /// Called when a finger is lifted from the surface.
+        /// </summary>
+        event Action<ITouchDevice, TouchFinger>? FingerUp;
+
+        /// <summary>
+        /// Called when the finger is moved while on the surface.
+        /// </summary>
+        /// <remarks>
+        /// The last event argument gives the distance in pixels
+        /// the finger has moved since the last event.
+        /// </remarks>
+        event Action<ITouchDevice, TouchFinger, Vector2>? FingerMove;
+    }
+}
diff --git a/src/Input/Silk.NET.Input.Common/Internals/InputContextImplementationBase.cs b/src/Input/Silk.NET.Input.Common/Internals/InputContextImplementationBase.cs
index 1dd7721a8a..8f52530939 100644
--- a/src/Input/Silk.NET.Input.Common/Internals/InputContextImplementationBase.cs
+++ b/src/Input/Silk.NET.Input.Common/Internals/InputContextImplementationBase.cs
@@ -46,6 +46,8 @@ public void Dispose()
     public abstract IReadOnlyList<IJoystick> Joysticks { get; }
     public abstract IReadOnlyList<IKeyboard> Keyboards { get; }
     public abstract IReadOnlyList<IMouse> Mice { get; }
+    public abstract IReadOnlyList<ITouchDevice> TouchDevices { get; }
+    public abstract ITouchDevice? PrimaryTouchDevice { get; }
     public abstract IReadOnlyList<IInputDevice> OtherDevices { get; }
     public abstract event Action<IInputDevice, bool>? ConnectionChanged;
 }
diff --git a/src/Input/Silk.NET.Input.Common/PublicAPI/net5.0/PublicAPI.Unshipped.txt b/src/Input/Silk.NET.Input.Common/PublicAPI/net5.0/PublicAPI.Unshipped.txt
index 862d9e4729..bf305d02f8 100644
--- a/src/Input/Silk.NET.Input.Common/PublicAPI/net5.0/PublicAPI.Unshipped.txt
+++ b/src/Input/Silk.NET.Input.Common/PublicAPI/net5.0/PublicAPI.Unshipped.txt
@@ -1,4 +1,70 @@
 #nullable enable
+Silk.NET.Input.DoubleTapBehavior
+Silk.NET.Input.DoubleTapBehavior.EmitBothSingleTaps = 1 -> Silk.NET.Input.DoubleTapBehavior
+Silk.NET.Input.DoubleTapBehavior.EmitFirstSingleTap = 0 -> Silk.NET.Input.DoubleTapBehavior
+Silk.NET.Input.DoubleTapBehavior.WaitForDoubleTapTimeElapse = 2 -> Silk.NET.Input.DoubleTapBehavior
+Silk.NET.Input.Gesture
+Silk.NET.Input.Gesture.All = Silk.NET.Input.Gesture.Tap | Silk.NET.Input.Gesture.DoubleTap | Silk.NET.Input.Gesture.Swipe | Silk.NET.Input.Gesture.Hold | Silk.NET.Input.Gesture.Zoom | Silk.NET.Input.Gesture.Rotate -> Silk.NET.Input.Gesture
+Silk.NET.Input.Gesture.DoubleTap = 2 -> Silk.NET.Input.Gesture
+Silk.NET.Input.Gesture.Hold = 8 -> Silk.NET.Input.Gesture
+Silk.NET.Input.Gesture.None = 0 -> Silk.NET.Input.Gesture
+Silk.NET.Input.Gesture.Rotate = 32 -> Silk.NET.Input.Gesture
+Silk.NET.Input.Gesture.Swipe = 4 -> Silk.NET.Input.Gesture
+Silk.NET.Input.Gesture.Tap = 1 -> Silk.NET.Input.Gesture
+Silk.NET.Input.Gesture.Zoom = 16 -> Silk.NET.Input.Gesture
+Silk.NET.Input.IInputContext.PrimaryTouchDevice.get -> Silk.NET.Input.ITouchDevice?
+Silk.NET.Input.IInputContext.TouchDevices.get -> System.Collections.Generic.IReadOnlyList<Silk.NET.Input.ITouchDevice!>!
+Silk.NET.Input.ITouchDevice
+Silk.NET.Input.ITouchDevice.FingerDown -> System.Action<Silk.NET.Input.ITouchDevice!, Silk.NET.Input.TouchFinger>?
+Silk.NET.Input.ITouchDevice.FingerMove -> System.Action<Silk.NET.Input.ITouchDevice!, Silk.NET.Input.TouchFinger, System.Numerics.Vector2>?
+Silk.NET.Input.ITouchDevice.Fingers.get -> System.Collections.Generic.IReadOnlyDictionary<long, Silk.NET.Input.TouchFinger>!
+Silk.NET.Input.ITouchDevice.FingerUp -> System.Action<Silk.NET.Input.ITouchDevice!, Silk.NET.Input.TouchFinger>?
+Silk.NET.Input.ITouchDevice.GestureRecognizer.get -> Silk.NET.Input.TouchGestureRecognizer!
+Silk.NET.Input.ITouchDevice.IsFingerDown(long index) -> bool
+Silk.NET.Input.MultiGestureHandling
+Silk.NET.Input.MultiGestureHandling.PrioritizeRotateGesture = 1 -> Silk.NET.Input.MultiGestureHandling
+Silk.NET.Input.MultiGestureHandling.PrioritizeZoomGesture = 0 -> Silk.NET.Input.MultiGestureHandling
+Silk.NET.Input.MultiGestureHandling.RecognizeBothGestures = 2 -> Silk.NET.Input.MultiGestureHandling
+Silk.NET.Input.TouchFinger
+Silk.NET.Input.TouchFinger.Down.get -> bool
+Silk.NET.Input.TouchFinger.Index.get -> long
+Silk.NET.Input.TouchFinger.NormalizedPosition.get -> System.Numerics.Vector2
+Silk.NET.Input.TouchFinger.NormalizedSpeed.get -> System.Numerics.Vector2
+Silk.NET.Input.TouchFinger.Position.get -> System.Numerics.Vector2
+Silk.NET.Input.TouchFinger.Speed.get -> System.Numerics.Vector2
+Silk.NET.Input.TouchFinger.TouchFinger() -> void
+Silk.NET.Input.TouchFinger.TouchFinger(long index, System.Numerics.Vector2 position, System.Numerics.Vector2 normalizedPosition, System.Numerics.Vector2 speed, System.Numerics.Vector2 normalizedSpeed, bool down) -> void
+Silk.NET.Input.TouchGestureRecognizer
+Silk.NET.Input.TouchGestureRecognizer.Dispose() -> void
+Silk.NET.Input.TouchGestureRecognizer.DoubleTap -> System.Action<System.Numerics.Vector2>?
+Silk.NET.Input.TouchGestureRecognizer.DoubleTapBehavior.get -> Silk.NET.Input.DoubleTapBehavior
+Silk.NET.Input.TouchGestureRecognizer.DoubleTapBehavior.set -> void
+Silk.NET.Input.TouchGestureRecognizer.DoubleTapRange.get -> float
+Silk.NET.Input.TouchGestureRecognizer.DoubleTapRange.set -> void
+Silk.NET.Input.TouchGestureRecognizer.DoubleTapTime.get -> int
+Silk.NET.Input.TouchGestureRecognizer.DoubleTapTime.set -> void
+Silk.NET.Input.TouchGestureRecognizer.Hold -> System.Action<System.Numerics.Vector2>?
+Silk.NET.Input.TouchGestureRecognizer.HoldTime.get -> int
+Silk.NET.Input.TouchGestureRecognizer.HoldTime.set -> void
+Silk.NET.Input.TouchGestureRecognizer.MultiGestureHandling.get -> Silk.NET.Input.MultiGestureHandling
+Silk.NET.Input.TouchGestureRecognizer.MultiGestureHandling.set -> void
+Silk.NET.Input.TouchGestureRecognizer.Rotate -> System.Action<System.Numerics.Vector2, float>?
+Silk.NET.Input.TouchGestureRecognizer.RotateAngleThreshold.get -> float
+Silk.NET.Input.TouchGestureRecognizer.RotateAngleThreshold.set -> void
+Silk.NET.Input.TouchGestureRecognizer.Swipe -> System.Action<System.Numerics.Vector2>?
+Silk.NET.Input.TouchGestureRecognizer.SwipeMaxSpeed.get -> float
+Silk.NET.Input.TouchGestureRecognizer.SwipeMaxSpeed.set -> void
+Silk.NET.Input.TouchGestureRecognizer.SwipeMinSpeed.get -> float
+Silk.NET.Input.TouchGestureRecognizer.SwipeMinSpeed.set -> void
+Silk.NET.Input.TouchGestureRecognizer.Tap -> System.Action<System.Numerics.Vector2>?
+Silk.NET.Input.TouchGestureRecognizer.TrackedGestures.get -> Silk.NET.Input.Gesture
+Silk.NET.Input.TouchGestureRecognizer.TrackedGestures.set -> void
+Silk.NET.Input.TouchGestureRecognizer.Update() -> void
+Silk.NET.Input.TouchGestureRecognizer.Zoom -> System.Action<System.Numerics.Vector2, float>?
+Silk.NET.Input.TouchGestureRecognizer.ZoomInDistanceThreshold.get -> float
+Silk.NET.Input.TouchGestureRecognizer.ZoomInDistanceThreshold.set -> void
+Silk.NET.Input.TouchGestureRecognizer.ZoomOutDistanceThreshold.get -> float
+Silk.NET.Input.TouchGestureRecognizer.ZoomOutDistanceThreshold.set -> void
 static Silk.NET.Input.GamepadExtensions.LeftThumbstick(this Silk.NET.Input.IGamepad! gamepad) -> Silk.NET.Input.Thumbstick
 static Silk.NET.Input.GamepadExtensions.LeftThumbstickButton(this Silk.NET.Input.IGamepad! gamepad) -> Silk.NET.Input.Button
 static Silk.NET.Input.GamepadExtensions.RightThumbstick(this Silk.NET.Input.IGamepad! gamepad) -> Silk.NET.Input.Thumbstick
diff --git a/src/Input/Silk.NET.Input.Common/PublicAPI/netcoreapp3.1/PublicAPI.Unshipped.txt b/src/Input/Silk.NET.Input.Common/PublicAPI/netcoreapp3.1/PublicAPI.Unshipped.txt
index 862d9e4729..0cae6b2980 100644
--- a/src/Input/Silk.NET.Input.Common/PublicAPI/netcoreapp3.1/PublicAPI.Unshipped.txt
+++ b/src/Input/Silk.NET.Input.Common/PublicAPI/netcoreapp3.1/PublicAPI.Unshipped.txt
@@ -1,4 +1,68 @@
 #nullable enable
+Silk.NET.Input.DoubleTapBehavior
+Silk.NET.Input.DoubleTapBehavior.EmitBothSingleTaps = 1 -> Silk.NET.Input.DoubleTapBehavior
+Silk.NET.Input.DoubleTapBehavior.EmitFirstSingleTap = 0 -> Silk.NET.Input.DoubleTapBehavior
+Silk.NET.Input.DoubleTapBehavior.WaitForDoubleTapTimeElapse = 2 -> Silk.NET.Input.DoubleTapBehavior
+Silk.NET.Input.Gesture
+Silk.NET.Input.Gesture.All = Silk.NET.Input.Gesture.Tap | Silk.NET.Input.Gesture.DoubleTap | Silk.NET.Input.Gesture.Swipe | Silk.NET.Input.Gesture.Hold | Silk.NET.Input.Gesture.Zoom | Silk.NET.Input.Gesture.Rotate -> Silk.NET.Input.Gesture
+Silk.NET.Input.Gesture.DoubleTap = 2 -> Silk.NET.Input.Gesture
+Silk.NET.Input.Gesture.Hold = 8 -> Silk.NET.Input.Gesture
+Silk.NET.Input.Gesture.None = 0 -> Silk.NET.Input.Gesture
+Silk.NET.Input.Gesture.Rotate = 32 -> Silk.NET.Input.Gesture
+Silk.NET.Input.Gesture.Swipe = 4 -> Silk.NET.Input.Gesture
+Silk.NET.Input.Gesture.Tap = 1 -> Silk.NET.Input.Gesture
+Silk.NET.Input.Gesture.Zoom = 16 -> Silk.NET.Input.Gesture
+Silk.NET.Input.IInputContext.PrimaryTouchDevice.get -> Silk.NET.Input.ITouchDevice?
+Silk.NET.Input.IInputContext.TouchDevices.get -> System.Collections.Generic.IReadOnlyList<Silk.NET.Input.ITouchDevice!>!
+Silk.NET.Input.ITouchDevice
+Silk.NET.Input.ITouchDevice.FingerDown -> System.Action<Silk.NET.Input.ITouchDevice!, Silk.NET.Input.TouchFinger>?
+Silk.NET.Input.ITouchDevice.FingerMove -> System.Action<Silk.NET.Input.ITouchDevice!, Silk.NET.Input.TouchFinger, System.Numerics.Vector2>?
+Silk.NET.Input.ITouchDevice.Fingers.get -> System.Collections.Generic.IReadOnlyDictionary<long, Silk.NET.Input.TouchFinger>!
+Silk.NET.Input.ITouchDevice.FingerUp -> System.Action<Silk.NET.Input.ITouchDevice!, Silk.NET.Input.TouchFinger>?
+Silk.NET.Input.ITouchDevice.GestureRecognizer.get -> Silk.NET.Input.TouchGestureRecognizer!
+Silk.NET.Input.ITouchDevice.IsFingerDown(long index) -> bool
+Silk.NET.Input.MultiGestureHandling
+Silk.NET.Input.MultiGestureHandling.PrioritizeRotateGesture = 1 -> Silk.NET.Input.MultiGestureHandling
+Silk.NET.Input.MultiGestureHandling.PrioritizeZoomGesture = 0 -> Silk.NET.Input.MultiGestureHandling
+Silk.NET.Input.MultiGestureHandling.RecognizeBothGestures = 2 -> Silk.NET.Input.MultiGestureHandling
+Silk.NET.Input.TouchFinger
+Silk.NET.Input.TouchFinger.Down.get -> bool
+Silk.NET.Input.TouchFinger.Index.get -> long
+Silk.NET.Input.TouchFinger.NormalizedPosition.get -> System.Numerics.Vector2
+Silk.NET.Input.TouchFinger.NormalizedSpeed.get -> System.Numerics.Vector2
+Silk.NET.Input.TouchFinger.Position.get -> System.Numerics.Vector2
+Silk.NET.Input.TouchFinger.Speed.get -> System.Numerics.Vector2
+Silk.NET.Input.TouchFinger.TouchFinger() -> void
+Silk.NET.Input.TouchFinger.TouchFinger(long index, System.Numerics.Vector2 position, System.Numerics.Vector2 normalizedPosition, System.Numerics.Vector2 speed, System.Numerics.Vector2 normalizedSpeed, bool down) -> void
+Silk.NET.Input.TouchGestureRecognizer
+Silk.NET.Input.TouchGestureRecognizer.Dispose() -> void
+Silk.NET.Input.TouchGestureRecognizer.DoubleTap -> System.Action<System.Numerics.Vector2>?
+Silk.NET.Input.TouchGestureRecognizer.DoubleTapBehavior.get -> Silk.NET.Input.DoubleTapBehavior
+Silk.NET.Input.TouchGestureRecognizer.DoubleTapBehavior.set -> void
+Silk.NET.Input.TouchGestureRecognizer.DoubleTapRange.get -> float
+Silk.NET.Input.TouchGestureRecognizer.DoubleTapRange.set -> void
+Silk.NET.Input.TouchGestureRecognizer.DoubleTapTime.get -> int
+Silk.NET.Input.TouchGestureRecognizer.DoubleTapTime.set -> void
+Silk.NET.Input.TouchGestureRecognizer.Hold -> System.Action<System.Numerics.Vector2>?
+Silk.NET.Input.TouchGestureRecognizer.HoldTime.get -> int
+Silk.NET.Input.TouchGestureRecognizer.HoldTime.set -> void
+Silk.NET.Input.TouchGestureRecognizer.MultiGestureHandling.get -> Silk.NET.Input.MultiGestureHandling
+Silk.NET.Input.TouchGestureRecognizer.MultiGestureHandling.set -> void
+Silk.NET.Input.TouchGestureRecognizer.Rotate -> System.Action<System.Numerics.Vector2, float>?
+Silk.NET.Input.TouchGestureRecognizer.RotateAngleThreshold.get -> float
+Silk.NET.Input.TouchGestureRecognizer.RotateAngleThreshold.set -> void
+Silk.NET.Input.TouchGestureRecognizer.Swipe -> System.Action<System.Numerics.Vector2>?
+Silk.NET.Input.TouchGestureRecognizer.SwipeMaxSpeed.get -> float
+Silk.NET.Input.TouchGestureRecognizer.SwipeMaxSpeed.set -> void
+Silk.NET.Input.TouchGestureRecognizer.SwipeMinSpeed.get -> float
+Silk.NET.Input.TouchGestureRecognizer.SwipeMinSpeed.set -> void
+Silk.NET.Input.TouchGestureRecognizer.Tap -> System.Action<System.Numerics.Vector2>?
+Silk.NET.Input.TouchGestureRecognizer.TrackedGestures.get -> Silk.NET.Input.Gesture
+Silk.NET.Input.TouchGestureRecognizer.TrackedGestures.set -> void
+Silk.NET.Input.TouchGestureRecognizer.Update() -> void
+Silk.NET.Input.TouchGestureRecognizer.Zoom -> System.Action<System.Numerics.Vector2, System.Numerics.Vector2>?
+Silk.NET.Input.TouchGestureRecognizer.ZoomDistanceThreshold.get -> float
+Silk.NET.Input.TouchGestureRecognizer.ZoomDistanceThreshold.set -> void
 static Silk.NET.Input.GamepadExtensions.LeftThumbstick(this Silk.NET.Input.IGamepad! gamepad) -> Silk.NET.Input.Thumbstick
 static Silk.NET.Input.GamepadExtensions.LeftThumbstickButton(this Silk.NET.Input.IGamepad! gamepad) -> Silk.NET.Input.Button
 static Silk.NET.Input.GamepadExtensions.RightThumbstick(this Silk.NET.Input.IGamepad! gamepad) -> Silk.NET.Input.Thumbstick
diff --git a/src/Input/Silk.NET.Input.Common/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt b/src/Input/Silk.NET.Input.Common/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt
index 862d9e4729..0cae6b2980 100644
--- a/src/Input/Silk.NET.Input.Common/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt
+++ b/src/Input/Silk.NET.Input.Common/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt
@@ -1,4 +1,68 @@
 #nullable enable
+Silk.NET.Input.DoubleTapBehavior
+Silk.NET.Input.DoubleTapBehavior.EmitBothSingleTaps = 1 -> Silk.NET.Input.DoubleTapBehavior
+Silk.NET.Input.DoubleTapBehavior.EmitFirstSingleTap = 0 -> Silk.NET.Input.DoubleTapBehavior
+Silk.NET.Input.DoubleTapBehavior.WaitForDoubleTapTimeElapse = 2 -> Silk.NET.Input.DoubleTapBehavior
+Silk.NET.Input.Gesture
+Silk.NET.Input.Gesture.All = Silk.NET.Input.Gesture.Tap | Silk.NET.Input.Gesture.DoubleTap | Silk.NET.Input.Gesture.Swipe | Silk.NET.Input.Gesture.Hold | Silk.NET.Input.Gesture.Zoom | Silk.NET.Input.Gesture.Rotate -> Silk.NET.Input.Gesture
+Silk.NET.Input.Gesture.DoubleTap = 2 -> Silk.NET.Input.Gesture
+Silk.NET.Input.Gesture.Hold = 8 -> Silk.NET.Input.Gesture
+Silk.NET.Input.Gesture.None = 0 -> Silk.NET.Input.Gesture
+Silk.NET.Input.Gesture.Rotate = 32 -> Silk.NET.Input.Gesture
+Silk.NET.Input.Gesture.Swipe = 4 -> Silk.NET.Input.Gesture
+Silk.NET.Input.Gesture.Tap = 1 -> Silk.NET.Input.Gesture
+Silk.NET.Input.Gesture.Zoom = 16 -> Silk.NET.Input.Gesture
+Silk.NET.Input.IInputContext.PrimaryTouchDevice.get -> Silk.NET.Input.ITouchDevice?
+Silk.NET.Input.IInputContext.TouchDevices.get -> System.Collections.Generic.IReadOnlyList<Silk.NET.Input.ITouchDevice!>!
+Silk.NET.Input.ITouchDevice
+Silk.NET.Input.ITouchDevice.FingerDown -> System.Action<Silk.NET.Input.ITouchDevice!, Silk.NET.Input.TouchFinger>?
+Silk.NET.Input.ITouchDevice.FingerMove -> System.Action<Silk.NET.Input.ITouchDevice!, Silk.NET.Input.TouchFinger, System.Numerics.Vector2>?
+Silk.NET.Input.ITouchDevice.Fingers.get -> System.Collections.Generic.IReadOnlyDictionary<long, Silk.NET.Input.TouchFinger>!
+Silk.NET.Input.ITouchDevice.FingerUp -> System.Action<Silk.NET.Input.ITouchDevice!, Silk.NET.Input.TouchFinger>?
+Silk.NET.Input.ITouchDevice.GestureRecognizer.get -> Silk.NET.Input.TouchGestureRecognizer!
+Silk.NET.Input.ITouchDevice.IsFingerDown(long index) -> bool
+Silk.NET.Input.MultiGestureHandling
+Silk.NET.Input.MultiGestureHandling.PrioritizeRotateGesture = 1 -> Silk.NET.Input.MultiGestureHandling
+Silk.NET.Input.MultiGestureHandling.PrioritizeZoomGesture = 0 -> Silk.NET.Input.MultiGestureHandling
+Silk.NET.Input.MultiGestureHandling.RecognizeBothGestures = 2 -> Silk.NET.Input.MultiGestureHandling
+Silk.NET.Input.TouchFinger
+Silk.NET.Input.TouchFinger.Down.get -> bool
+Silk.NET.Input.TouchFinger.Index.get -> long
+Silk.NET.Input.TouchFinger.NormalizedPosition.get -> System.Numerics.Vector2
+Silk.NET.Input.TouchFinger.NormalizedSpeed.get -> System.Numerics.Vector2
+Silk.NET.Input.TouchFinger.Position.get -> System.Numerics.Vector2
+Silk.NET.Input.TouchFinger.Speed.get -> System.Numerics.Vector2
+Silk.NET.Input.TouchFinger.TouchFinger() -> void
+Silk.NET.Input.TouchFinger.TouchFinger(long index, System.Numerics.Vector2 position, System.Numerics.Vector2 normalizedPosition, System.Numerics.Vector2 speed, System.Numerics.Vector2 normalizedSpeed, bool down) -> void
+Silk.NET.Input.TouchGestureRecognizer
+Silk.NET.Input.TouchGestureRecognizer.Dispose() -> void
+Silk.NET.Input.TouchGestureRecognizer.DoubleTap -> System.Action<System.Numerics.Vector2>?
+Silk.NET.Input.TouchGestureRecognizer.DoubleTapBehavior.get -> Silk.NET.Input.DoubleTapBehavior
+Silk.NET.Input.TouchGestureRecognizer.DoubleTapBehavior.set -> void
+Silk.NET.Input.TouchGestureRecognizer.DoubleTapRange.get -> float
+Silk.NET.Input.TouchGestureRecognizer.DoubleTapRange.set -> void
+Silk.NET.Input.TouchGestureRecognizer.DoubleTapTime.get -> int
+Silk.NET.Input.TouchGestureRecognizer.DoubleTapTime.set -> void
+Silk.NET.Input.TouchGestureRecognizer.Hold -> System.Action<System.Numerics.Vector2>?
+Silk.NET.Input.TouchGestureRecognizer.HoldTime.get -> int
+Silk.NET.Input.TouchGestureRecognizer.HoldTime.set -> void
+Silk.NET.Input.TouchGestureRecognizer.MultiGestureHandling.get -> Silk.NET.Input.MultiGestureHandling
+Silk.NET.Input.TouchGestureRecognizer.MultiGestureHandling.set -> void
+Silk.NET.Input.TouchGestureRecognizer.Rotate -> System.Action<System.Numerics.Vector2, float>?
+Silk.NET.Input.TouchGestureRecognizer.RotateAngleThreshold.get -> float
+Silk.NET.Input.TouchGestureRecognizer.RotateAngleThreshold.set -> void
+Silk.NET.Input.TouchGestureRecognizer.Swipe -> System.Action<System.Numerics.Vector2>?
+Silk.NET.Input.TouchGestureRecognizer.SwipeMaxSpeed.get -> float
+Silk.NET.Input.TouchGestureRecognizer.SwipeMaxSpeed.set -> void
+Silk.NET.Input.TouchGestureRecognizer.SwipeMinSpeed.get -> float
+Silk.NET.Input.TouchGestureRecognizer.SwipeMinSpeed.set -> void
+Silk.NET.Input.TouchGestureRecognizer.Tap -> System.Action<System.Numerics.Vector2>?
+Silk.NET.Input.TouchGestureRecognizer.TrackedGestures.get -> Silk.NET.Input.Gesture
+Silk.NET.Input.TouchGestureRecognizer.TrackedGestures.set -> void
+Silk.NET.Input.TouchGestureRecognizer.Update() -> void
+Silk.NET.Input.TouchGestureRecognizer.Zoom -> System.Action<System.Numerics.Vector2, System.Numerics.Vector2>?
+Silk.NET.Input.TouchGestureRecognizer.ZoomDistanceThreshold.get -> float
+Silk.NET.Input.TouchGestureRecognizer.ZoomDistanceThreshold.set -> void
 static Silk.NET.Input.GamepadExtensions.LeftThumbstick(this Silk.NET.Input.IGamepad! gamepad) -> Silk.NET.Input.Thumbstick
 static Silk.NET.Input.GamepadExtensions.LeftThumbstickButton(this Silk.NET.Input.IGamepad! gamepad) -> Silk.NET.Input.Button
 static Silk.NET.Input.GamepadExtensions.RightThumbstick(this Silk.NET.Input.IGamepad! gamepad) -> Silk.NET.Input.Thumbstick
diff --git a/src/Input/Silk.NET.Input.Common/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt b/src/Input/Silk.NET.Input.Common/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt
index 862d9e4729..bf305d02f8 100644
--- a/src/Input/Silk.NET.Input.Common/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt
+++ b/src/Input/Silk.NET.Input.Common/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt
@@ -1,4 +1,70 @@
 #nullable enable
+Silk.NET.Input.DoubleTapBehavior
+Silk.NET.Input.DoubleTapBehavior.EmitBothSingleTaps = 1 -> Silk.NET.Input.DoubleTapBehavior
+Silk.NET.Input.DoubleTapBehavior.EmitFirstSingleTap = 0 -> Silk.NET.Input.DoubleTapBehavior
+Silk.NET.Input.DoubleTapBehavior.WaitForDoubleTapTimeElapse = 2 -> Silk.NET.Input.DoubleTapBehavior
+Silk.NET.Input.Gesture
+Silk.NET.Input.Gesture.All = Silk.NET.Input.Gesture.Tap | Silk.NET.Input.Gesture.DoubleTap | Silk.NET.Input.Gesture.Swipe | Silk.NET.Input.Gesture.Hold | Silk.NET.Input.Gesture.Zoom | Silk.NET.Input.Gesture.Rotate -> Silk.NET.Input.Gesture
+Silk.NET.Input.Gesture.DoubleTap = 2 -> Silk.NET.Input.Gesture
+Silk.NET.Input.Gesture.Hold = 8 -> Silk.NET.Input.Gesture
+Silk.NET.Input.Gesture.None = 0 -> Silk.NET.Input.Gesture
+Silk.NET.Input.Gesture.Rotate = 32 -> Silk.NET.Input.Gesture
+Silk.NET.Input.Gesture.Swipe = 4 -> Silk.NET.Input.Gesture
+Silk.NET.Input.Gesture.Tap = 1 -> Silk.NET.Input.Gesture
+Silk.NET.Input.Gesture.Zoom = 16 -> Silk.NET.Input.Gesture
+Silk.NET.Input.IInputContext.PrimaryTouchDevice.get -> Silk.NET.Input.ITouchDevice?
+Silk.NET.Input.IInputContext.TouchDevices.get -> System.Collections.Generic.IReadOnlyList<Silk.NET.Input.ITouchDevice!>!
+Silk.NET.Input.ITouchDevice
+Silk.NET.Input.ITouchDevice.FingerDown -> System.Action<Silk.NET.Input.ITouchDevice!, Silk.NET.Input.TouchFinger>?
+Silk.NET.Input.ITouchDevice.FingerMove -> System.Action<Silk.NET.Input.ITouchDevice!, Silk.NET.Input.TouchFinger, System.Numerics.Vector2>?
+Silk.NET.Input.ITouchDevice.Fingers.get -> System.Collections.Generic.IReadOnlyDictionary<long, Silk.NET.Input.TouchFinger>!
+Silk.NET.Input.ITouchDevice.FingerUp -> System.Action<Silk.NET.Input.ITouchDevice!, Silk.NET.Input.TouchFinger>?
+Silk.NET.Input.ITouchDevice.GestureRecognizer.get -> Silk.NET.Input.TouchGestureRecognizer!
+Silk.NET.Input.ITouchDevice.IsFingerDown(long index) -> bool
+Silk.NET.Input.MultiGestureHandling
+Silk.NET.Input.MultiGestureHandling.PrioritizeRotateGesture = 1 -> Silk.NET.Input.MultiGestureHandling
+Silk.NET.Input.MultiGestureHandling.PrioritizeZoomGesture = 0 -> Silk.NET.Input.MultiGestureHandling
+Silk.NET.Input.MultiGestureHandling.RecognizeBothGestures = 2 -> Silk.NET.Input.MultiGestureHandling
+Silk.NET.Input.TouchFinger
+Silk.NET.Input.TouchFinger.Down.get -> bool
+Silk.NET.Input.TouchFinger.Index.get -> long
+Silk.NET.Input.TouchFinger.NormalizedPosition.get -> System.Numerics.Vector2
+Silk.NET.Input.TouchFinger.NormalizedSpeed.get -> System.Numerics.Vector2
+Silk.NET.Input.TouchFinger.Position.get -> System.Numerics.Vector2
+Silk.NET.Input.TouchFinger.Speed.get -> System.Numerics.Vector2
+Silk.NET.Input.TouchFinger.TouchFinger() -> void
+Silk.NET.Input.TouchFinger.TouchFinger(long index, System.Numerics.Vector2 position, System.Numerics.Vector2 normalizedPosition, System.Numerics.Vector2 speed, System.Numerics.Vector2 normalizedSpeed, bool down) -> void
+Silk.NET.Input.TouchGestureRecognizer
+Silk.NET.Input.TouchGestureRecognizer.Dispose() -> void
+Silk.NET.Input.TouchGestureRecognizer.DoubleTap -> System.Action<System.Numerics.Vector2>?
+Silk.NET.Input.TouchGestureRecognizer.DoubleTapBehavior.get -> Silk.NET.Input.DoubleTapBehavior
+Silk.NET.Input.TouchGestureRecognizer.DoubleTapBehavior.set -> void
+Silk.NET.Input.TouchGestureRecognizer.DoubleTapRange.get -> float
+Silk.NET.Input.TouchGestureRecognizer.DoubleTapRange.set -> void
+Silk.NET.Input.TouchGestureRecognizer.DoubleTapTime.get -> int
+Silk.NET.Input.TouchGestureRecognizer.DoubleTapTime.set -> void
+Silk.NET.Input.TouchGestureRecognizer.Hold -> System.Action<System.Numerics.Vector2>?
+Silk.NET.Input.TouchGestureRecognizer.HoldTime.get -> int
+Silk.NET.Input.TouchGestureRecognizer.HoldTime.set -> void
+Silk.NET.Input.TouchGestureRecognizer.MultiGestureHandling.get -> Silk.NET.Input.MultiGestureHandling
+Silk.NET.Input.TouchGestureRecognizer.MultiGestureHandling.set -> void
+Silk.NET.Input.TouchGestureRecognizer.Rotate -> System.Action<System.Numerics.Vector2, float>?
+Silk.NET.Input.TouchGestureRecognizer.RotateAngleThreshold.get -> float
+Silk.NET.Input.TouchGestureRecognizer.RotateAngleThreshold.set -> void
+Silk.NET.Input.TouchGestureRecognizer.Swipe -> System.Action<System.Numerics.Vector2>?
+Silk.NET.Input.TouchGestureRecognizer.SwipeMaxSpeed.get -> float
+Silk.NET.Input.TouchGestureRecognizer.SwipeMaxSpeed.set -> void
+Silk.NET.Input.TouchGestureRecognizer.SwipeMinSpeed.get -> float
+Silk.NET.Input.TouchGestureRecognizer.SwipeMinSpeed.set -> void
+Silk.NET.Input.TouchGestureRecognizer.Tap -> System.Action<System.Numerics.Vector2>?
+Silk.NET.Input.TouchGestureRecognizer.TrackedGestures.get -> Silk.NET.Input.Gesture
+Silk.NET.Input.TouchGestureRecognizer.TrackedGestures.set -> void
+Silk.NET.Input.TouchGestureRecognizer.Update() -> void
+Silk.NET.Input.TouchGestureRecognizer.Zoom -> System.Action<System.Numerics.Vector2, float>?
+Silk.NET.Input.TouchGestureRecognizer.ZoomInDistanceThreshold.get -> float
+Silk.NET.Input.TouchGestureRecognizer.ZoomInDistanceThreshold.set -> void
+Silk.NET.Input.TouchGestureRecognizer.ZoomOutDistanceThreshold.get -> float
+Silk.NET.Input.TouchGestureRecognizer.ZoomOutDistanceThreshold.set -> void
 static Silk.NET.Input.GamepadExtensions.LeftThumbstick(this Silk.NET.Input.IGamepad! gamepad) -> Silk.NET.Input.Thumbstick
 static Silk.NET.Input.GamepadExtensions.LeftThumbstickButton(this Silk.NET.Input.IGamepad! gamepad) -> Silk.NET.Input.Button
 static Silk.NET.Input.GamepadExtensions.RightThumbstick(this Silk.NET.Input.IGamepad! gamepad) -> Silk.NET.Input.Thumbstick
diff --git a/src/Input/Silk.NET.Input.Common/Structs/TouchFinger.cs b/src/Input/Silk.NET.Input.Common/Structs/TouchFinger.cs
new file mode 100644
index 0000000000..810b3c8e0b
--- /dev/null
+++ b/src/Input/Silk.NET.Input.Common/Structs/TouchFinger.cs
@@ -0,0 +1,62 @@
+// 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
+{
+    /// <summary>
+    /// Represents a touch finger.
+    /// </summary>
+    public readonly struct TouchFinger
+    {
+        /// <summary>
+        /// The index of the finger.
+        /// </summary>
+        public long Index { get; }
+
+        /// <summary>
+        /// The last known position of the finger.
+        /// </summary>
+        public Vector2 Position { get; }
+
+        /// <summary>
+        /// The last known normalized position (0..1) of the finger.
+        /// </summary>
+        public Vector2 NormalizedPosition { get; }
+
+        /// <summary>
+        /// The last known speed of the finger.
+        /// </summary>
+        public Vector2 Speed { get; }
+
+        /// <summary>
+        /// The last known normalized speed (-1..1) of the finger.
+        /// </summary>
+        public Vector2 NormalizedSpeed { get; }
+
+        /// <summary>
+        /// Finger down on the touch surface.
+        /// </summary>
+        public bool Down { get; }
+
+        /// <summary>
+        /// Creates a new instance of the touch finger struct.
+        /// </summary>
+        /// <param name="index">The index of the finger.</param>
+        /// <param name="position">The position of the finger.</param>
+        /// <param name="normalizedPosition">The normalized position of the finger.</param>
+        /// <param name="speed">The speed of the finger.</param>
+        /// <param name="normalizedSpeed">The normalized speed of the finger.</param>
+        /// <param name="down">Boolean which is true if the finger is down.</param>
+        public TouchFinger(long index, Vector2 position, Vector2 normalizedPosition, Vector2 speed, Vector2 normalizedSpeed, bool down)
+        {
+            Index = index;
+            Position = position;
+            NormalizedPosition = normalizedPosition;
+            Speed = speed;
+            NormalizedSpeed = normalizedSpeed;
+            Down = down;
+        }
+    }
+}
diff --git a/src/Input/Silk.NET.Input.Common/TouchGestureRecognizer.cs b/src/Input/Silk.NET.Input.Common/TouchGestureRecognizer.cs
new file mode 100644
index 0000000000..9cda15de85
--- /dev/null
+++ b/src/Input/Silk.NET.Input.Common/TouchGestureRecognizer.cs
@@ -0,0 +1,414 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.ComponentModel;
+using System.Numerics;
+
+namespace Silk.NET.Input
+{
+    /// <summary>
+    /// Touch gesture tracking.
+    /// </summary>
+    public sealed class TouchGestureRecognizer : IDisposable
+    {
+        private readonly ITouchDevice _device;
+        private long? _firstFingerIndex;
+        private DateTime? _firstFingerLastMoveTime;
+        private long? _secondFingerIndex;
+        private DateTime? _firstTapTime;
+        private Vector2? _firstTapPosition;
+        private Vector2? _firstTapNormalizedPosition;
+        private float _initialFingerDistance = 0.0f;
+        private float _initialNormalizedFingerDistance = 0.0f;
+        private float _initialFingerAngle = 0.0f;
+        private bool _gestureHandled = false;
+
+        internal TouchGestureRecognizer(ITouchDevice device)
+        {
+            _device = device;
+            device.FingerDown += Device_FingerDown;
+            device.FingerUp += Device_FingerUp;
+            device.FingerMove += Device_FingerMove;
+        }
+
+        private void Device_FingerDown(ITouchDevice touchDevice, TouchFinger finger)
+        {
+            if (_firstFingerIndex is null)
+            {
+                _gestureHandled = false;
+                _firstFingerIndex = finger.Index;
+                _firstFingerLastMoveTime = DateTime.Now;
+            }
+            else if (_secondFingerIndex is null)
+            {
+                _secondFingerIndex = finger.Index;
+                var firstFinger = _device.Fingers[_firstFingerIndex.Value];
+
+                _initialFingerDistance = (finger.Position - firstFinger.Position).Length();
+                _initialNormalizedFingerDistance = (finger.NormalizedPosition - firstFinger.NormalizedPosition).Length();
+                _initialFingerAngle = (float)(Math.Atan2(finger.NormalizedPosition.Y - firstFinger.NormalizedPosition.Y,
+                    finger.NormalizedPosition.X - firstFinger.NormalizedPosition.X) * 180.0 / Math.PI);
+            }
+        }
+
+        private void Device_FingerUp(ITouchDevice touchDevice, TouchFinger finger)
+        {
+            if (_gestureHandled)
+            {
+                _firstFingerIndex = null;
+                _secondFingerIndex = null;
+                return;
+            }
+
+            if (finger.Index == _firstFingerIndex)
+            {
+                bool secondTap = _firstTapTime != null;
+                // Double tap is considered if it is tracked, this is the second tap and it was in time.
+                bool doubleTap = secondTap && DoubleTap != null && TrackedGestures.HasFlag(Gesture.DoubleTap) &&
+                    (DateTime.Now - _firstTapTime!.Value).TotalMilliseconds <= DoubleTapTime;
+
+                if (doubleTap && _firstTapNormalizedPosition != null &&
+                    (Math.Abs(finger.NormalizedPosition.X - _firstTapNormalizedPosition.Value.X) > DoubleTapRange ||
+                     Math.Abs(finger.NormalizedPosition.Y - _firstTapNormalizedPosition.Value.Y) > DoubleTapRange))
+                {
+                    // Second tap was out of range
+                    doubleTap = false;
+                }
+
+                switch (DoubleTapBehavior)
+                {
+                    case DoubleTapBehavior.EmitFirstSingleTap:
+                        if (doubleTap)
+                        {
+                            DoubleTap?.Invoke(finger.Position);
+                        }
+                        else if (TrackedGestures.HasFlag(Gesture.Tap))
+                        {
+                            Tap?.Invoke(finger.Position);
+                        }
+                        break;
+                    case DoubleTapBehavior.EmitBothSingleTaps:
+                        if (TrackedGestures.HasFlag(Gesture.Tap))
+                        {
+                            Tap?.Invoke(finger.Position);
+                        }
+                        if (doubleTap)
+                        {
+                            DoubleTap?.Invoke(finger.Position);
+                        }
+                        break;
+                    case DoubleTapBehavior.WaitForDoubleTapTimeElapse:
+                        if (doubleTap)
+                        {
+                            DoubleTap?.Invoke(finger.Position);
+                        }
+                        else if (secondTap && TrackedGestures.HasFlag(Gesture.Tap))
+                        {
+                            Tap?.Invoke(_firstTapPosition ?? finger.Position);
+                            Tap?.Invoke(finger.Position);
+                        }
+                        break;
+                }
+
+                if (secondTap && (TrackedGestures & (Gesture.Tap | Gesture.DoubleTap)) != 0)
+                    _gestureHandled = true;
+
+                _firstTapTime = doubleTap ? null : DateTime.Now;
+                _firstTapPosition = doubleTap ? null : finger.Position;
+                _firstTapNormalizedPosition = doubleTap ? null : finger.NormalizedPosition;
+                _firstFingerIndex = null;
+                _secondFingerIndex = null;
+                _initialFingerDistance = 0.0f;
+                _initialNormalizedFingerDistance = 0.0f;
+                _initialFingerAngle = 0.0f;
+            }
+            else if (finger.Index == _secondFingerIndex)
+            {
+                _secondFingerIndex = null;
+                _initialFingerDistance = 0.0f;
+                _initialNormalizedFingerDistance = 0.0f;
+                _initialFingerAngle = 0.0f;
+                _gestureHandled = true;
+            }
+        }
+
+        private void Device_FingerMove(ITouchDevice touchDevice, TouchFinger finger, Vector2 delta)
+        {
+            if (finger.Index == _firstFingerIndex)
+            {
+                _firstFingerLastMoveTime = DateTime.Now;
+
+                if (!_gestureHandled &&
+                    Swipe != null &&
+                    TrackedGestures.HasFlag(Gesture.Swipe) &&
+                    _secondFingerIndex is null &&
+                    ((Math.Abs(finger.NormalizedSpeed.X) >= SwipeMinSpeed &&
+                    Math.Abs(finger.NormalizedSpeed.X) <= SwipeMaxSpeed) ||
+                    (Math.Abs(finger.NormalizedSpeed.Y) >= SwipeMinSpeed &&
+                    Math.Abs(finger.NormalizedSpeed.Y) <= SwipeMaxSpeed)))
+                {
+                    _gestureHandled = true;
+                    _firstFingerIndex = null;
+                    Swipe(finger.NormalizedSpeed);
+                    return;
+                }
+
+                if (_secondFingerIndex != null)
+                {
+                    CheckTwoFingerGestures();
+                }
+            }
+            else
+            {
+                if (_firstFingerIndex != null && _secondFingerIndex is null)
+                {
+                    _secondFingerIndex = finger.Index;
+                    var firstFinger = _device.Fingers[_firstFingerIndex.Value];
+
+                    _initialFingerDistance = (finger.Position - firstFinger.Position).Length();
+                    _initialNormalizedFingerDistance = (finger.NormalizedPosition - firstFinger.NormalizedPosition).Length();
+                    _initialFingerAngle = (float) (Math.Atan2(finger.NormalizedPosition.Y - firstFinger.NormalizedPosition.Y,
+                        finger.NormalizedPosition.X - firstFinger.NormalizedPosition.X) * 180.0 / Math.PI);
+                }
+
+                CheckTwoFingerGestures();
+            }
+        }
+
+        private void CheckTwoFingerGestures()
+        {
+            if (Zoom is null && Rotate is null)
+                return; // Don't bother
+
+            TouchFinger? firstFinger = _firstFingerIndex != null && _device.Fingers.TryGetValue(_firstFingerIndex.Value, out var finger) ? finger : null;
+            TouchFinger? secondFinger = _secondFingerIndex != null && _device.Fingers.TryGetValue(_secondFingerIndex.Value, out finger) ? finger : null;
+
+            if (firstFinger != null && secondFinger != null)
+            {
+                var multiGestureHandling = MultiGestureHandling;
+                if (Rotate is null)
+                    multiGestureHandling = MultiGestureHandling.PrioritizeZoomGesture;
+                else if (Zoom is null)
+                    multiGestureHandling = MultiGestureHandling.PrioritizeRotateGesture;
+                Action? zoomInvoker = null;
+
+                if (Zoom != null && TrackedGestures.HasFlag(Gesture.Zoom))
+                {
+                    var normalizedFingerDistance = (secondFinger.Value.NormalizedPosition - firstFinger.Value.NormalizedPosition).Length();
+                    var normalizedDistance = normalizedFingerDistance - _initialNormalizedFingerDistance;
+
+                    if ((normalizedDistance >= 0.0f && normalizedDistance >= ZoomInDistanceThreshold) ||
+                        (normalizedDistance < 0.0f && -normalizedDistance >= ZoomOutDistanceThreshold))
+                    {
+                        var firstFingerPosition = firstFinger.Value.Position;
+                        var fingerDistance = (secondFinger.Value.Position - firstFingerPosition).Length() - _initialFingerDistance;
+
+                        zoomInvoker = () => Zoom(firstFingerPosition, fingerDistance);
+
+                        if (multiGestureHandling == MultiGestureHandling.PrioritizeZoomGesture)
+                        {
+                            _gestureHandled = true;
+                            zoomInvoker();
+                            return;
+                        }
+                    }
+                }
+                if (Rotate != null && TrackedGestures.HasFlag(Gesture.Rotate))
+                {
+                    var firstFingerPosition = firstFinger.Value.NormalizedPosition;
+                    var secondFingerPosition = secondFinger.Value.NormalizedPosition;
+                    var fingerAngle = (float)(Math.Atan2(secondFingerPosition.Y - firstFingerPosition.Y,
+                            secondFingerPosition.X - firstFingerPosition.X) * 180.0 / Math.PI);
+                    var angle = CalculateAngleDifference(fingerAngle, _initialFingerAngle);
+
+                    if (Math.Abs(angle) >= RotateAngleThreshold)
+                    {
+                        _gestureHandled = true;
+
+                        if (zoomInvoker != null && multiGestureHandling == MultiGestureHandling.RecognizeBothGestures)
+                            zoomInvoker();
+
+                        Rotate(firstFinger.Value.Position, angle);
+                    }
+                }
+            }
+        }
+
+        private static float CalculateAngleDifference(float angle1, float angle2)
+        {
+            // Ensure angles are in the range [0, 360)
+            angle1 = (angle1 + 360.0f) % 360.0f;
+            angle2 = (angle2 + 360.0f) % 360.0f;
+
+            // Calculate the signed angle difference
+            // This is always in the range (-360, 360)
+            return angle2 - angle1;
+        }
+
+        /// <inheritdoc />
+        public void Dispose()
+        {
+            _device.FingerDown -= Device_FingerDown;
+            _device.FingerUp -= Device_FingerUp;
+            _device.FingerMove -= Device_FingerMove;
+        }
+
+        /// <summary>
+        /// General gesture recognition update for time-based recognition aspects.
+        /// </summary>
+        public void Update()
+        {
+            if (DoubleTap != null &&
+                DoubleTapBehavior == DoubleTapBehavior.WaitForDoubleTapTimeElapse &&
+                _firstFingerIndex is null &&
+                _firstTapTime != null &&
+                (DateTime.Now - _firstTapTime.Value).TotalMilliseconds >= DoubleTapTime)
+            {
+                _gestureHandled = true;
+
+                if (Tap != null && TrackedGestures.HasFlag(Gesture.Tap) && _firstTapPosition != null)
+                {
+                    Tap(_firstTapPosition.Value);
+                }
+
+                _firstTapTime = null;
+                _firstTapPosition = null;
+                _firstTapNormalizedPosition = null;
+                _firstFingerIndex = null;
+                _secondFingerIndex = null;
+                _initialFingerDistance = 0.0f;
+                _initialNormalizedFingerDistance = 0.0f;
+                _initialFingerAngle = 0.0f;
+            }
+            else if (Hold != null &&
+                TrackedGestures.HasFlag(Gesture.Hold) &&
+                _firstFingerIndex != null && _firstFingerLastMoveTime != null && _secondFingerIndex is null &&
+                (DateTime.Now - _firstFingerLastMoveTime.Value).TotalMilliseconds >= HoldTime &&
+                _device.Fingers.TryGetValue(_firstFingerIndex.Value, out var finger))
+            {
+                _gestureHandled = true;
+                Hold(finger.Position);
+                _firstFingerIndex = null;
+            }
+        }
+
+        /// <summary>
+        /// The tracked gestures.
+        /// </summary>
+        [DefaultValue(Gesture.All)]
+        public Gesture TrackedGestures { get; set; } = Gesture.All;
+
+        /// <summary>
+        /// THe behavior to handle multiple two-finger gestures.
+        /// </summary>
+        [DefaultValue(MultiGestureHandling.RecognizeBothGestures)]
+        public MultiGestureHandling MultiGestureHandling { get; set; } = MultiGestureHandling.RecognizeBothGestures;
+
+        /// <summary>
+        /// The behavior for tracking double taps.
+        /// </summary>
+        [DefaultValue(DoubleTapBehavior.EmitFirstSingleTap)]
+        public DoubleTapBehavior DoubleTapBehavior { get; set; } = DoubleTapBehavior.EmitFirstSingleTap;
+
+        /// <summary>
+        /// The maximum time in milliseconds between two
+        /// consecutive taps to count as a double tap.
+        /// </summary>
+        [DefaultValue(500)]
+        public int DoubleTapTime { get; set; } = 500;
+
+        /// <summary>
+        /// The maximum distance in normalized distance (0..1) between two
+        /// consecutive taps to count as a double tap.
+        /// </summary>
+        [DefaultValue(0.05f)]
+        public float DoubleTapRange { get; set; } = 0.05f;
+
+        /// <summary>
+        /// The minimum finger speed in normalized distance (0..1) per second to count as a swipe gesture.
+        /// </summary>
+        [DefaultValue(0.15f)]
+        public float SwipeMinSpeed { get; set; } = 0.15f;
+
+        /// <summary>
+        /// The maximum finger speed in normalized distance (0..1) per second to count as a swipe gesture.
+        /// </summary>
+        [DefaultValue(1000.0f)]
+        public float SwipeMaxSpeed { get; set; } = 1000.0f;
+
+        /// <summary>
+        /// The minimum time in milliseconds a finger must be pressed on the surface to count as a hold gesture.
+        /// </summary>
+        [DefaultValue(1000)]
+        public int HoldTime { get; set; } = 1000;
+
+        /// <summary>
+        /// Distance threshold as a normalized value (0..1) for zoom in gesture tracking.
+        /// </summary>
+        [DefaultValue(0.15f)]
+        public float ZoomInDistanceThreshold { get; set; } = 0.15f;
+
+        /// <summary>
+        /// Distance threshold as a normalized value (0..1) for zoom out in gesture tracking.
+        /// </summary>
+        [DefaultValue(0.15f)]
+        public float ZoomOutDistanceThreshold { get; set; } = 0.1f;
+
+        /// <summary>
+        /// Angle threshold in degrees for rotate gesture tracking.
+        /// </summary>
+        [DefaultValue(9.0f)]
+        public float RotateAngleThreshold { get; set; } = 9.0f;
+
+        /// <summary>
+        /// Tap gesture.
+        /// </summary>
+        /// <remarks>
+        /// The event argument gives the finger position of the tap in pixel coordinates.
+        /// </remarks>
+        public event Action<Vector2>? Tap;
+
+        /// <summary>
+        /// Double tap gesture.
+        /// </summary>
+        /// <remarks>
+        /// The event argument gives the finger position of the second tap in pixel coordinates.
+        /// </remarks>
+        public event Action<Vector2>? DoubleTap;
+
+        /// <summary>
+        /// Swipe gesture.
+        /// </summary>
+        /// <remarks>
+        /// The event argument gives the swipe direction as a normalized 2D vector.
+        /// </remarks>
+        public event Action<Vector2>? Swipe;
+
+        /// <summary>
+        /// Long hold gesture.
+        /// </summary>
+        /// <remarks>
+        /// The event argument gives the finger position at the end of the hold in pixel coordinates.
+        /// </remarks>
+        public event Action<Vector2>? Hold;
+
+        /// <summary>
+        /// Zoom gesture.
+        /// </summary>
+        /// <remarks>
+        /// The first event argument gives the first finger position in pixel coordinates and the second
+        /// event argument the total distance change in pixels of the two fingers in relation to the initial finger distance.
+        /// </remarks>
+        public event Action<Vector2, float>? Zoom;
+
+        /// <summary>
+        /// Rotate gesture.
+        /// </summary>
+        /// <remarks>
+        /// The first event argument gives the first finger position in pixel coordinates and the second
+        /// event argument gives the total angle delta in degress in relation to the initial finger angle.
+        /// </remarks>
+        public event Action<Vector2, float>? Rotate;
+    }
+}
diff --git a/src/Input/Silk.NET.Input.Glfw/GlfwInputContext.cs b/src/Input/Silk.NET.Input.Glfw/GlfwInputContext.cs
index 9e7a2f9f56..d5919a4b5b 100644
--- a/src/Input/Silk.NET.Input.Glfw/GlfwInputContext.cs
+++ b/src/Input/Silk.NET.Input.Glfw/GlfwInputContext.cs
@@ -7,7 +7,6 @@
 using Silk.NET.Input.Internals;
 using Silk.NET.Windowing;
 using Silk.NET.Windowing.Glfw;
-using Silk.NET.Windowing.Internals;
 
 namespace Silk.NET.Input.Glfw
 {
@@ -94,6 +93,9 @@ public override unsafe void CoreDispose()
         public override IReadOnlyList<IKeyboard> Keyboards { get; }
         public override IReadOnlyList<IMouse> Mice { get; }
         public override IReadOnlyList<IInputDevice> OtherDevices { get; } = Array.Empty<IInputDevice>();
+        public override IReadOnlyList<ITouchDevice> TouchDevices { get; } = Array.Empty<ITouchDevice>();
+        public override ITouchDevice? PrimaryTouchDevice { get; } = null;
+
         public override event Action<IInputDevice, bool>? ConnectionChanged;
     }
 }
diff --git a/src/Input/Silk.NET.Input.Sdl/SdlInputContext.cs b/src/Input/Silk.NET.Input.Sdl/SdlInputContext.cs
index ef9b429a43..58c50eb270 100644
--- a/src/Input/Silk.NET.Input.Sdl/SdlInputContext.cs
+++ b/src/Input/Silk.NET.Input.Sdl/SdlInputContext.cs
@@ -3,9 +3,9 @@
 
 using System;
 using System.Collections.Generic;
+using System.Linq;
 using Silk.NET.Input.Internals;
 using Silk.NET.SDL;
-using Silk.NET.Windowing;
 using Silk.NET.Windowing.Sdl;
 
 namespace Silk.NET.Input.Sdl
@@ -32,6 +32,14 @@ public SdlInputContext(SdlView view) : base(view)
             );
             Keyboards = new IKeyboard[] {new SdlKeyboard(this)};
             Mice = new IMouse[] {new SdlMouse(this)};
+            int numTouchDevices = Sdl.GetNumTouchDevices();
+            SdlTouchDevices = new Dictionary<long, SdlTouchDevice>(numTouchDevices);
+            for (int i = 0; i < numTouchDevices; ++i)
+            {
+                long touchId = Sdl.GetTouchDevice(i);
+                SdlTouchDevices.Add(touchId, new SdlTouchDevice(this, touchId, Sdl.GetTouchDeviceType(touchId), i));
+            }
+            TouchDevices = new ReadOnlyCollectionListAdapter<SdlTouchDevice>(SdlTouchDevices.Values);
         }
 
         // Public properties
@@ -39,11 +47,14 @@ public SdlInputContext(SdlView view) : base(view)
         public override IReadOnlyList<IJoystick> Joysticks { get; }
         public override IReadOnlyList<IKeyboard> Keyboards { get; }
         public override IReadOnlyList<IMouse> Mice { get; }
+        public override IReadOnlyList<ITouchDevice> TouchDevices { get; }
+        public override ITouchDevice? PrimaryTouchDevice => TouchDevices.FirstOrDefault(td => td.IsConnected) ?? SdlTouchDevices.FirstOrDefault(td => td.Value.TouchId == SdlTouchDevices.Count).Value;
         public override IReadOnlyList<IInputDevice> OtherDevices { get; } = Array.Empty<IInputDevice>();
 
         // Implementation-specific properties
         public Dictionary<int, SdlGamepad> SdlGamepads { get; }
         public Dictionary<int, SdlJoystick> SdlJoysticks { get; }
+        public Dictionary<long, SdlTouchDevice> SdlTouchDevices { get; }
 
         public SDL.Sdl Sdl => _sdlView.Sdl;
         public override nint Handle => Window.Handle;
@@ -238,11 +249,25 @@ public override void ProcessEvents()
                     case EventType.Fingerdown:
                     case EventType.Fingerup:
                     case EventType.Fingermotion:
-                    case EventType.Dollargesture:
-                    case EventType.Dollarrecord:
-                    case EventType.Multigesture:
                     {
-                        // TODO touch input
+                        if (SdlTouchDevices.TryGetValue(@event.Tfinger.TouchId, out var td))
+                        {
+                            if (!td.IsConnected)
+                            {
+                                td.IsConnected = true;
+                                ConnectionChanged?.Invoke(td, true);
+                            }
+
+                            td.DoEvent(@event);
+                        }
+                        else
+                        {
+                            var touchId = @event.Tfinger.TouchId;
+                            var touchDevice = new SdlTouchDevice(this, touchId, Sdl.GetTouchDeviceType(touchId), SdlTouchDevices.Count) { IsConnected = true };
+                            SdlTouchDevices.Add(touchId, touchDevice);
+                            ConnectionChanged?.Invoke(touchDevice, true);
+                            touchDevice.DoEvent(@event);
+                        }
                         break;
                     }
                     default:
@@ -267,6 +292,10 @@ public override void ProcessEvents()
             {
                 gp.Update();
             }
+            foreach (var td in SdlTouchDevices.Values)
+            {
+                td.Update();
+            }
 
             // There's actually nowhere here that will raise an SDL error that we cause.
             // Sdl.ThrowError();
@@ -313,6 +342,11 @@ public override void CoreDispose()
             {
                 joy.Dispose();
             }
+
+            foreach (var td in SdlTouchDevices.Values)
+            {
+                td.Dispose();
+            }
         }
 
         public void ChangeConnection(IInputDevice device, bool connected)
diff --git a/src/Input/Silk.NET.Input.Sdl/SdlMouse.cs b/src/Input/Silk.NET.Input.Sdl/SdlMouse.cs
index e6a9fb20fe..46a554a95f 100644
--- a/src/Input/Silk.NET.Input.Sdl/SdlMouse.cs
+++ b/src/Input/Silk.NET.Input.Sdl/SdlMouse.cs
@@ -63,6 +63,7 @@ public void Update()
                 _scrollWheels[0] = default;
             }
             _wheelChanged = false;
+            HandleUpdate();
         }
 
         public void DoEvent(Event @event)
diff --git a/src/Input/Silk.NET.Input.Sdl/SdlTouchDevice.cs b/src/Input/Silk.NET.Input.Sdl/SdlTouchDevice.cs
new file mode 100644
index 0000000000..cf75594fc3
--- /dev/null
+++ b/src/Input/Silk.NET.Input.Sdl/SdlTouchDevice.cs
@@ -0,0 +1,115 @@
+using System;
+using System.Collections.Generic;
+using System.Numerics;
+using Silk.NET.SDL;
+
+namespace Silk.NET.Input.Sdl
+{
+    internal class SdlTouchDevice : ITouchDevice, ISdlDevice, IDisposable
+    {
+        private readonly SdlInputContext _ctx;
+        private readonly Dictionary<long, TouchFinger> _fingers = new Dictionary<long, TouchFinger>();
+        private readonly Dictionary<long, DateTime> _fingerEventTimes = new Dictionary<long, DateTime>();
+
+        /// <summary>
+        /// Id of the touch device.
+        /// </summary>
+        public long TouchId { get; }
+
+        /// <summary>
+        /// Type of the touch device.
+        /// </summary>
+        public TouchDeviceType TouchDeviceType { get; }
+
+        public SdlTouchDevice(SdlInputContext ctx, long touchId, TouchDeviceType touchDeviceType, int index)
+        {
+            _ctx = ctx;
+            TouchId = touchId;
+            TouchDeviceType = touchDeviceType;
+            Index = index;
+            GestureRecognizer = new TouchGestureRecognizer(this);
+        }
+
+        public string Name => GetTouchDeviceName();
+        public int Index { get; }
+        public bool IsConnected { get; set; } = false;
+
+        public IReadOnlyDictionary<long, TouchFinger> Fingers => _fingers;
+
+        public TouchGestureRecognizer GestureRecognizer { get; }
+
+        public bool IsFingerDown(long index) => _fingers.TryGetValue(index, out var finger) && finger.Down;
+        public event Action<ITouchDevice, TouchFinger>? FingerDown;
+        public event Action<ITouchDevice, TouchFinger>? FingerUp;
+        public event Action<ITouchDevice, TouchFinger, Vector2>? FingerMove;
+
+        public void Update()
+        {
+            GestureRecognizer.Update();
+        }
+
+        public unsafe void DoEvent(Event @event)
+        {
+            var window = _ctx.Sdl.GetWindowFromID(@event.Tfinger.WindowID);
+            int windowWidth = 1;
+            int windowHeight = 1;
+            _ctx.Sdl.GetWindowSize(window, ref windowWidth, ref windowHeight);
+            Vector2 windowSize = new Vector2(windowWidth, windowHeight);
+            var normalizedPosition = new Vector2(@event.Tfinger.X, @event.Tfinger.Y);
+            var position = normalizedPosition * windowSize;
+
+            switch ((EventType) @event.Type)
+            {
+                case EventType.Fingerdown:
+                {
+                    var finger = new TouchFinger(@event.Tfinger.FingerId,
+                        position, normalizedPosition, Vector2.Zero, Vector2.Zero, true);
+                    FingerDown?.Invoke(this, finger);
+                    _fingers[finger.Index] = finger;
+                    _fingerEventTimes[finger.Index] = DateTime.Now;
+                    break;
+                }
+                case EventType.Fingerup:
+                {
+                    var finger = new TouchFinger(@event.Tfinger.FingerId,
+                        position, normalizedPosition, Vector2.Zero, Vector2.Zero, false);
+                    FingerUp?.Invoke(this, finger);
+                    _fingers.Remove(finger.Index);
+                    _fingerEventTimes.Remove(finger.Index);
+                    break;
+                }
+                case EventType.Fingermotion:
+                {
+                    var distance = new Vector2(@event.Tfinger.Dx, @event.Tfinger.Dy);
+                    var time = (DateTime.Now - _fingerEventTimes[@event.Tfinger.FingerId]).TotalSeconds;
+                    var normalizedSpeed = distance / (float) (time == 0.0 ? double.Epsilon : time);
+                    var speed = normalizedSpeed * windowSize;
+                    var finger = new TouchFinger(@event.Tfinger.FingerId,
+                        position, normalizedPosition, speed, normalizedSpeed,
+                        true);
+                    FingerMove?.Invoke(this, finger, distance * windowSize);
+                    _fingers[finger.Index] = finger;
+                    _fingerEventTimes[finger.Index] = DateTime.Now;
+                    break;
+                }
+            }
+        }
+
+        public void Dispose()
+        {
+            GestureRecognizer.Dispose();
+        }
+
+        private string GetTouchDeviceName()
+        {
+            try
+            {
+                return _ctx.Sdl.GetTouchNameS(Index);
+            }
+            catch
+            {
+                return "Silk.NET Touch Device (via SDL)";
+            }
+        }
+    }
+}