diff --git a/Ryujinx.Input/HLE/InputManager.cs b/Ryujinx.Input/HLE/InputManager.cs
index 277e8ec22..699e521d8 100644
--- a/Ryujinx.Input/HLE/InputManager.cs
+++ b/Ryujinx.Input/HLE/InputManager.cs
@@ -6,6 +6,7 @@ namespace Ryujinx.Input.HLE
     {
         public IGamepadDriver KeyboardDriver { get; private set; }
         public IGamepadDriver GamepadDriver { get; private set; }
+        public IGamepadDriver MouseDriver { get; private set; }
 
         public InputManager(IGamepadDriver keyboardDriver, IGamepadDriver gamepadDriver)
         {
@@ -13,10 +14,27 @@ namespace Ryujinx.Input.HLE
             GamepadDriver = gamepadDriver;
         }
 
+        public void SetMouseDriver(IGamepadDriver mouseDriver)
+        {
+            MouseDriver?.Dispose();
+
+            MouseDriver = mouseDriver;
+        }
+
         public NpadManager CreateNpadManager()
         {
             return new NpadManager(KeyboardDriver, GamepadDriver);
         }
+        
+        public TouchScreenManager CreateTouchScreenManager()
+        {
+            if (MouseDriver == null)
+            {
+                throw new InvalidOperationException("Mouse Driver has not been initialized.");
+            }
+
+            return new TouchScreenManager(MouseDriver.GetGamepad("0") as IMouse);
+        }
 
         protected virtual void Dispose(bool disposing)
         {
@@ -24,6 +42,7 @@ namespace Ryujinx.Input.HLE
             {
                 KeyboardDriver?.Dispose();
                 GamepadDriver?.Dispose();
+                MouseDriver?.Dispose();
             }
         }
 
diff --git a/Ryujinx.Input/HLE/TouchScreenManager.cs b/Ryujinx.Input/HLE/TouchScreenManager.cs
new file mode 100644
index 000000000..ffa8eeacc
--- /dev/null
+++ b/Ryujinx.Input/HLE/TouchScreenManager.cs
@@ -0,0 +1,57 @@
+using Ryujinx.HLE;
+using Ryujinx.HLE.HOS.Services.Hid;
+using System;
+
+namespace Ryujinx.Input.HLE
+{
+    public class TouchScreenManager : IDisposable
+    {
+        private readonly IMouse _mouse;
+        private Switch _device;
+
+        public TouchScreenManager(IMouse mouse)
+        {
+            _mouse = mouse;
+        }
+
+        public void Initialize(Switch device)
+        {
+            _device = device;
+        }
+
+        public bool Update(bool isFocused, float aspectRatio = 0)
+        {
+            if (!isFocused)
+            {
+                _device.Hid.Touchscreen.Update();
+
+                return false;
+            }
+
+            if (aspectRatio > 0)
+            {
+                var snapshot = IMouse.GetMouseStateSnapshot(_mouse);
+                var touchPosition = IMouse.GetTouchPosition(snapshot.Position, _mouse.ClientSize, aspectRatio);
+
+                TouchPoint currentPoint = new TouchPoint
+                {
+                    X = (uint)touchPosition.X,
+                    Y = (uint)touchPosition.Y,
+
+                    // Placeholder values till more data is acquired
+                    DiameterX = 10,
+                    DiameterY = 10,
+                    Angle = 90
+                };
+
+                _device.Hid.Touchscreen.Update(currentPoint);
+
+                return true;
+            }
+
+            return false;
+        }
+
+        public void Dispose() { }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Input/IMouse.cs b/Ryujinx.Input/IMouse.cs
new file mode 100644
index 000000000..37de0229e
--- /dev/null
+++ b/Ryujinx.Input/IMouse.cs
@@ -0,0 +1,100 @@
+using System.Drawing;
+using System.Numerics;
+
+namespace Ryujinx.Input
+{
+    /// <summary>
+    /// Represent an emulated mouse.
+    /// </summary>
+    public interface IMouse : IGamepad
+    {
+        private const int SwitchPanelWidth = 1280;
+        private const int SwitchPanelHeight = 720;
+
+        /// <summary>
+        /// Check if a given button is pressed on the mouse.
+        /// </summary>
+        /// <param name="button">The button</param>
+        /// <returns>True if the given button is pressed on the mouse</returns>
+        bool IsButtonPressed(MouseButton button);
+
+        /// <summary>
+        /// Get the position of the mouse in the client.
+        /// </summary>
+        Vector2 GetPosition();
+
+        /// <summary>
+        /// Get the client size.
+        /// </summary>
+        Size ClientSize { get; }
+
+        /// <summary>
+        /// Get the button states of the mouse.
+        /// </summary>
+        bool[] Buttons { get; }
+
+        /// <summary>
+        /// Get a snaphost of the state of a mouse.
+        /// </summary>
+        /// <param name="mouse">The mouse to do a snapshot of</param>
+        /// <returns>A snaphost of the state of the mouse.</returns>
+        public static MouseStateSnapshot GetMouseStateSnapshot(IMouse mouse)
+        {
+            var position = mouse.GetPosition();
+            bool[] buttons = new bool[(int)MouseButton.Count];
+
+            mouse.Buttons.CopyTo(buttons, 0);
+
+            return new MouseStateSnapshot(buttons, position);
+        }
+
+        /// <summary>
+        /// Get the touch position of a mouse position relative to the app's view
+        /// </summary>
+        /// <param name="mousePosition">The position of the mouse in the client</param>
+        /// <param name="clientSize">The size of the client</param>
+        /// <param name="aspectRatio">The aspect ratio of the view</param>
+        /// <returns>A snaphost of the state of the mouse.</returns>
+        public static Vector2 GetTouchPosition(Vector2 mousePosition, Size clientSize, float aspectRatio)
+        {
+            float mouseX = mousePosition.X;
+            float mouseY = mousePosition.Y;
+
+            float aspectWidth = SwitchPanelHeight * aspectRatio;
+
+            int screenWidth = clientSize.Width;
+            int screenHeight = clientSize.Height;
+
+            if (clientSize.Width > clientSize.Height * aspectWidth / SwitchPanelHeight)
+            {
+                screenWidth = (int)(clientSize.Height * aspectWidth) / SwitchPanelHeight;
+            }
+            else
+            {
+                screenHeight = (clientSize.Width * SwitchPanelHeight) / (int)aspectWidth;
+            }
+
+            int startX = (clientSize.Width - screenWidth) >> 1;
+            int startY = (clientSize.Height - screenHeight) >> 1;
+
+            int endX = startX + screenWidth;
+            int endY = startY + screenHeight;
+
+            if (mouseX >= startX &&
+                mouseY >= startY &&
+                mouseX < endX &&
+                mouseY < endY)
+            {
+                int screenMouseX = (int)mouseX - startX;
+                int screenMouseY = (int)mouseY - startY;
+
+                mouseX = (screenMouseX * (int)aspectWidth) / screenWidth;
+                mouseY = (screenMouseY * SwitchPanelHeight) / screenHeight;
+
+                return new Vector2(mouseX, mouseY);
+            }
+
+            return new Vector2();
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Input/MouseButton.cs b/Ryujinx.Input/MouseButton.cs
new file mode 100644
index 000000000..ab7642167
--- /dev/null
+++ b/Ryujinx.Input/MouseButton.cs
@@ -0,0 +1,16 @@
+namespace Ryujinx.Input
+{
+    public enum MouseButton : byte
+    {
+        Button1,
+        Button2,
+        Button3,
+        Button4,
+        Button5,
+        Button6,
+        Button7,
+        Button8,
+        Button9,
+        Count
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Input/MouseStateSnapshot.cs b/Ryujinx.Input/MouseStateSnapshot.cs
new file mode 100644
index 000000000..4fbfeebd1
--- /dev/null
+++ b/Ryujinx.Input/MouseStateSnapshot.cs
@@ -0,0 +1,34 @@
+using System.Numerics;
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.Input
+{
+    /// <summary>
+    /// A snapshot of a <see cref="IMouse"/>.
+    /// </summary>
+    public class MouseStateSnapshot
+    {
+        private bool[] _buttonState;
+
+        public Vector2 Position { get; }
+
+        /// <summary>
+        /// Create a new <see cref="MouseStateSnapshot"/>.
+        /// </summary>
+        /// <param name="buttonState">The keys state</param>
+        public MouseStateSnapshot(bool[] buttonState, Vector2 position)
+        {
+            _buttonState = buttonState;
+
+            Position = position;
+        }
+
+        /// <summary>
+        /// Check if a given button is pressed.
+        /// </summary>
+        /// <param name="button">The button</param>
+        /// <returns>True if the given button is pressed</returns>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public bool IsPressed(MouseButton button) => _buttonState[(int)button];
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx/Input/GTK3/GTK3Mouse.cs b/Ryujinx/Input/GTK3/GTK3Mouse.cs
new file mode 100644
index 000000000..eb0c8c9a0
--- /dev/null
+++ b/Ryujinx/Input/GTK3/GTK3Mouse.cs
@@ -0,0 +1,84 @@
+using Ryujinx.Common.Configuration.Hid;
+using System;
+using System.Drawing;
+using System.Numerics;
+
+namespace Ryujinx.Input.GTK3
+{
+    public class GTK3Mouse : IMouse
+    {
+        private GTK3MouseDriver _driver;
+
+        public GamepadFeaturesFlag Features => throw new NotImplementedException();
+
+        public string Id => "0";
+
+        public string Name => "GTKMouse";
+
+        public bool IsConnected => true;
+
+        public bool[] Buttons => _driver.PressedButtons;
+
+        public GTK3Mouse(GTK3MouseDriver driver)
+        {
+            _driver = driver;
+        }
+
+        public Size ClientSize => _driver.GetClientSize();
+
+        public Vector2 GetPosition()
+        {
+            return _driver.CurrentPosition;
+        }
+
+        public GamepadStateSnapshot GetMappedStateSnapshot()
+        {
+            throw new NotImplementedException();
+        }
+
+        public Vector3 GetMotionData(MotionInputId inputId)
+        {
+            throw new NotImplementedException();
+        }
+
+        public GamepadStateSnapshot GetStateSnapshot()
+        {
+            throw new NotImplementedException();
+        }
+
+        public (float, float) GetStick(StickInputId inputId)
+        {
+            throw new NotImplementedException();
+        }
+
+        public bool IsButtonPressed(MouseButton button)
+        {
+            return _driver.IsButtonPressed(button);
+        }
+
+        public bool IsPressed(GamepadButtonInputId inputId)
+        {
+            throw new NotImplementedException();
+        }
+
+        public void Rumble(float lowFrequency, float highFrequency, uint durationMs)
+        {
+            throw new NotImplementedException();
+        }
+
+        public void SetConfiguration(InputConfig configuration)
+        {
+            throw new NotImplementedException();
+        }
+
+        public void SetTriggerThreshold(float triggerThreshold)
+        {
+            throw new NotImplementedException();
+        }
+
+        public void Dispose()
+        {
+            _driver = null;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx/Input/GTK3/GTK3MouseDriver.cs b/Ryujinx/Input/GTK3/GTK3MouseDriver.cs
new file mode 100644
index 000000000..015f58176
--- /dev/null
+++ b/Ryujinx/Input/GTK3/GTK3MouseDriver.cs
@@ -0,0 +1,97 @@
+using Gdk;
+using Gtk;
+using System;
+using System.Numerics;
+using Size = System.Drawing.Size;
+
+namespace Ryujinx.Input.GTK3
+{
+    public class GTK3MouseDriver : IGamepadDriver
+    {
+        private Widget _widget;
+        private bool _isDisposed;
+
+        public bool[] PressedButtons { get; }
+        
+        public Vector2 CurrentPosition { get; private set; }
+
+        public GTK3MouseDriver(Widget parent)
+        {
+            _widget = parent;
+
+            _widget.MotionNotifyEvent += Parent_MotionNotifyEvent;
+            _widget.ButtonPressEvent += Parent_ButtonPressEvent;
+            _widget.ButtonReleaseEvent += Parent_ButtonReleaseEvent;
+
+            PressedButtons = new bool[(int)MouseButton.Count];
+        }
+
+        [GLib.ConnectBefore]
+        private void Parent_ButtonReleaseEvent(object o, ButtonReleaseEventArgs args)
+        {
+            PressedButtons[args.Event.Button - 1] = false;
+        }
+
+        [GLib.ConnectBefore]
+        private void Parent_ButtonPressEvent(object o, ButtonPressEventArgs args)
+        {
+            PressedButtons[args.Event.Button - 1] = true;
+        }
+
+        [GLib.ConnectBefore]
+        private void Parent_MotionNotifyEvent(object o, MotionNotifyEventArgs args)
+        {
+            if (args.Event.Device.InputSource == InputSource.Mouse)
+            {
+                CurrentPosition = new Vector2((float)args.Event.X, (float)args.Event.Y);
+            }
+        }
+
+        public bool IsButtonPressed(MouseButton button)
+        {
+            return PressedButtons[(int) button];
+        }
+
+        public Size GetClientSize()
+        {
+            return new Size(_widget.AllocatedWidth, _widget.AllocatedHeight);
+        }
+
+        public string DriverName => "GTK3";
+        
+        public event Action<string> OnGamepadConnected
+        {
+            add    { }
+            remove { }
+        }
+
+        public event Action<string> OnGamepadDisconnected
+        {
+            add    { }
+            remove { }
+        }
+
+        public ReadOnlySpan<string> GamepadsIds => new[] {"0"};
+        
+        public IGamepad GetGamepad(string id)
+        {
+            return new GTK3Mouse(this);
+        }
+
+        public void Dispose()
+        {
+            if (_isDisposed)
+            {
+                return;
+            }
+
+            _isDisposed = true;
+
+            _widget.MotionNotifyEvent -= Parent_MotionNotifyEvent;
+            _widget.ButtonPressEvent -= Parent_ButtonPressEvent;
+            _widget.ButtonReleaseEvent -= Parent_ButtonReleaseEvent;
+
+            _widget = null;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx/Ui/RendererWidgetBase.cs b/Ryujinx/Ui/RendererWidgetBase.cs
index 00882ba0c..4ba87a1b1 100644
--- a/Ryujinx/Ui/RendererWidgetBase.cs
+++ b/Ryujinx/Ui/RendererWidgetBase.cs
@@ -8,6 +8,7 @@ using Ryujinx.Configuration;
 using Ryujinx.Graphics.GAL;
 using Ryujinx.HLE.HOS.Services.Hid;
 using Ryujinx.Input;
+using Ryujinx.Input.GTK3;
 using Ryujinx.Input.HLE;
 using Ryujinx.Ui.Widgets;
 using System;
@@ -28,6 +29,7 @@ namespace Ryujinx.Ui
 
         public ManualResetEvent WaitEvent { get; set; }
         public NpadManager NpadManager { get; }
+        public TouchScreenManager TouchScreenManager { get; }
         public Switch Device { get; private set; }
         public IRenderer Renderer { get; private set; }
 
@@ -37,10 +39,6 @@ namespace Ryujinx.Ui
         private bool _isStopped;
         private bool _isFocused;
 
-        private double _mouseX;
-        private double _mouseY;
-        private bool _mousePressed;
-
         private bool _toggleFullscreen;
         private bool _toggleDockedMode;
 
@@ -69,8 +67,12 @@ namespace Ryujinx.Ui
 
         public RendererWidgetBase(InputManager inputManager, GraphicsDebugLevel glLogLevel)
         {
+            var mouseDriver = new GTK3MouseDriver(this);
+
             _inputManager = inputManager;
+            _inputManager.SetMouseDriver(mouseDriver);
             NpadManager = _inputManager.CreateNpadManager();
+            TouchScreenManager = _inputManager.CreateTouchScreenManager();
             _keyboardInterface = (IKeyboard)_inputManager.KeyboardDriver.GetGamepad("0");
 
             WaitEvent = new ManualResetEvent(false);
@@ -145,37 +147,8 @@ namespace Ryujinx.Ui
             _isFocused = ParentWindow.State.HasFlag(Gdk.WindowState.Focused);
         }
 
-        protected override bool OnButtonPressEvent(EventButton evnt)
-        {
-            _mouseX = evnt.X;
-            _mouseY = evnt.Y;
-
-            if (evnt.Button == 1)
-            {
-                _mousePressed = true;
-            }
-
-            return false;
-        }
-
-        protected override bool OnButtonReleaseEvent(EventButton evnt)
-        {
-            if (evnt.Button == 1)
-            {
-                _mousePressed = false;
-            }
-
-            return false;
-        }
-
         protected override bool OnMotionNotifyEvent(EventMotion evnt)
         {
-            if (evnt.Device.InputSource == InputSource.Mouse)
-            {
-                _mouseX = evnt.X;
-                _mouseY = evnt.Y;
-            }
-
             if (_hideCursorOnIdle)
             {
                 _lastCursorMoveTime = Stopwatch.GetTimestamp();
@@ -300,6 +273,7 @@ namespace Ryujinx.Ui
             Renderer?.Window.SetSize(_windowWidth, _windowHeight);
 
             NpadManager.Initialize(device, ConfigurationState.Instance.Hid.InputConfig, ConfigurationState.Instance.Hid.EnableKeyboard);
+            TouchScreenManager.Initialize(device);
         }
 
         public void Render()
@@ -412,6 +386,7 @@ namespace Ryujinx.Ui
 
         public void Exit()
         {
+            TouchScreenManager?.Dispose();
             NpadManager?.Dispose();
 
             if (_isStopped)
@@ -507,60 +482,14 @@ namespace Ryujinx.Ui
             bool hasTouch = false;
 
             // Get screen touch position from left mouse click
-            // OpenTK always captures mouse events, even if out of focus, so check if window is focused.
-            if (_isFocused && _mousePressed)
+            if (_isFocused && (_inputManager.MouseDriver as GTK3MouseDriver).IsButtonPressed(MouseButton.Button1))
             {
-                float aspectWidth = SwitchPanelHeight * ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat();
-
-                int screenWidth = AllocatedWidth;
-                int screenHeight = AllocatedHeight;
-
-                if (AllocatedWidth > AllocatedHeight * aspectWidth / SwitchPanelHeight)
-                {
-                    screenWidth = (int)(AllocatedHeight * aspectWidth) / SwitchPanelHeight;
-                }
-                else
-                {
-                    screenHeight = (AllocatedWidth * SwitchPanelHeight) / (int)aspectWidth;
-                }
-
-                int startX = (AllocatedWidth - screenWidth) >> 1;
-                int startY = (AllocatedHeight - screenHeight) >> 1;
-
-                int endX = startX + screenWidth;
-                int endY = startY + screenHeight;
-
-                if (_mouseX >= startX &&
-                    _mouseY >= startY &&
-                    _mouseX < endX &&
-                    _mouseY < endY)
-                {
-                    int screenMouseX = (int)_mouseX - startX;
-                    int screenMouseY = (int)_mouseY - startY;
-
-                    int mX = (screenMouseX * (int)aspectWidth) / screenWidth;
-                    int mY = (screenMouseY * SwitchPanelHeight) / screenHeight;
-
-                    TouchPoint currentPoint = new TouchPoint
-                    {
-                        X = (uint)mX,
-                        Y = (uint)mY,
-
-                        // Placeholder values till more data is acquired
-                        DiameterX = 10,
-                        DiameterY = 10,
-                        Angle = 90
-                    };
-
-                    hasTouch = true;
-
-                    Device.Hid.Touchscreen.Update(currentPoint);
-                }
+                hasTouch = TouchScreenManager.Update(true, ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat());
             }
 
             if (!hasTouch)
             {
-                Device.Hid.Touchscreen.Update();
+                TouchScreenManager.Update(false);
             }
 
             Device.Hid.DebugPad.Update();
@@ -568,7 +497,6 @@ namespace Ryujinx.Ui
             return true;
         }
 
-
         [Flags]
         private enum KeyboardHotkeyState
         {