From 4472524197f96f4d644f1fdb4b62c7eff21ae10c Mon Sep 17 00:00:00 2001 From: Imran Rahman Date: Fri, 14 Aug 2026 22:19:18 +0600 Subject: [PATCH 1/4] add mouse movements via gamepad left joystick --- .../Helpers/UI/XBoxGamepadController.cs | 48 +++++++++++++++++++ src/Files.App/MainWindow.xaml.cs | 3 ++ 2 files changed, 51 insertions(+) create mode 100644 src/Files.App/Helpers/UI/XBoxGamepadController.cs diff --git a/src/Files.App/Helpers/UI/XBoxGamepadController.cs b/src/Files.App/Helpers/UI/XBoxGamepadController.cs new file mode 100644 index 000000000000..0baae432bfee --- /dev/null +++ b/src/Files.App/Helpers/UI/XBoxGamepadController.cs @@ -0,0 +1,48 @@ +using System; +using System.Runtime.InteropServices; +using Windows.Gaming.Input; +using Windows.Win32; +using Microsoft.UI.Dispatching; + +namespace Files.App.Helpers +{ + public sealed class XBoxGamepadController + { + [DllImport("user32.dll")] + private static extern bool SetCursorPos(int x, int y); + + private readonly DispatcherQueueTimer _timer; + private readonly float _deadzone = 0.15f; + private readonly float _sensitivity = 12f; + + public XBoxGamepadController(DispatcherQueue dispatcherQueue) + { + _timer = dispatcherQueue.CreateTimer(); + _timer.Interval = TimeSpan.FromMilliseconds(16); + _timer.Tick += OnTick; + _timer.Start(); + } + + private void OnTick(object? sender, object e) + { + var gamepads = Gamepad.Gamepads; + if (gamepads.Count == 0) + return; + + var gamepad = gamepads[0]; + var reading = gamepad.GetCurrentReading(); + + var leftThumbX = reading.LeftThumbstickX; + var leftThumbY = reading.LeftThumbstickY; + + if (Math.Abs(leftThumbX) < _deadzone) leftThumbX = 0; + if (Math.Abs(leftThumbY) < _deadzone) leftThumbY = 0; + + PInvoke.GetCursorPos(out var cursorPos); + var dx = (int)Math.Round(leftThumbX * _sensitivity); + var dy = (int)Math.Round(-leftThumbY * _sensitivity); + + SetCursorPos(cursorPos.X + dx, cursorPos.Y + dy); + } + } +} diff --git a/src/Files.App/MainWindow.xaml.cs b/src/Files.App/MainWindow.xaml.cs index b5bf7fd00115..fdb329b387a1 100644 --- a/src/Files.App/MainWindow.xaml.cs +++ b/src/Files.App/MainWindow.xaml.cs @@ -28,6 +28,9 @@ public MainWindow() { InitializeComponent(); + var gamepadCursor = new XBoxGamepadController(DispatcherQueue); + + WindowHandle = WinUIEx.WindowExtensions.GetWindowHandle(this); MinHeight = 316; MinWidth = 416; From 3c8223cd6fa100d7439014f80c0467fccf89c975 Mon Sep 17 00:00:00 2001 From: Imran Rahman Date: Sat, 15 Aug 2026 11:00:37 +0600 Subject: [PATCH 2/4] Add Controls for Xbox A,B,X, Right Joystick --- .../Helpers/UI/XBoxGamepadController.cs | 192 +++++++++++++++++- 1 file changed, 191 insertions(+), 1 deletion(-) diff --git a/src/Files.App/Helpers/UI/XBoxGamepadController.cs b/src/Files.App/Helpers/UI/XBoxGamepadController.cs index 0baae432bfee..0ef2f6979a07 100644 --- a/src/Files.App/Helpers/UI/XBoxGamepadController.cs +++ b/src/Files.App/Helpers/UI/XBoxGamepadController.cs @@ -11,10 +11,68 @@ public sealed class XBoxGamepadController [DllImport("user32.dll")] private static extern bool SetCursorPos(int x, int y); + [DllImport("user32.dll")] + private static extern uint SendInput(uint nInputs, ref INPUT pInputs, int cbSize); + + [DllImport("user32.dll")] + private static extern uint SendInput(uint nInputs, ref KEYINPUT pInputs, int cbSize); + + + [StructLayout(LayoutKind.Sequential)] + private struct KEYINPUT + { + public uint type; + public KEYBDINPUT ki; + } + + [StructLayout(LayoutKind.Sequential)] + private struct KEYBDINPUT + { + public ushort wVk; + public ushort wScan; + public uint dwFlags; + public long time; + public nint dwExtraInfo; + } + + [StructLayout(LayoutKind.Sequential)] + private struct INPUT + { + public uint type; + public MOUSEINPUT mi; + } + + [StructLayout(LayoutKind.Sequential)] + private struct MOUSEINPUT + { + public int dx; + public int dy; + public uint mouseData; + public uint dwFlags; + public uint time; + public IntPtr dwExtraInfo; + } + + private const uint INPUT_MOUSE = 0; + private const uint MOUSEEVENTF_LEFTDOWN = 0x0002; + private const uint MOUSEEVENTF_LEFTUP = 0x0004; + private const uint MOUSEEVENTF_RIGHTDOWN = 0x0008; + private const uint MOUSEEVENTF_RIGHTUP = 0x0010; + private const uint MOUSEEVENTF_WHEEL = 0x0800; + + private const uint INPUT_KEYBOARD = 1; + private const uint KEYEVENTF_KEYDOWN = 0x0000; + private const uint KEYEVENTF_KEYUP = 0x0002; + + private readonly DispatcherQueueTimer _timer; private readonly float _deadzone = 0.15f; private readonly float _sensitivity = 12f; + private bool _wasAPressed; + private bool _wasXPressed; + private bool _wasBPressed; + public XBoxGamepadController(DispatcherQueue dispatcherQueue) { _timer = dispatcherQueue.CreateTimer(); @@ -41,8 +99,140 @@ private void OnTick(object? sender, object e) PInvoke.GetCursorPos(out var cursorPos); var dx = (int)Math.Round(leftThumbX * _sensitivity); var dy = (int)Math.Round(-leftThumbY * _sensitivity); - SetCursorPos(cursorPos.X + dx, cursorPos.Y + dy); + + var isAPressed = (reading.Buttons & GamepadButtons.A) == GamepadButtons.A; + if (isAPressed && !_wasAPressed) + { + SimulateLeftMouseClick(); + } + _wasAPressed = isAPressed; + + var isXPressed = (reading.Buttons & GamepadButtons.X) == GamepadButtons.X; + if (isXPressed && !_wasXPressed) + { + SimulateRightMouseClick(); + } + _wasXPressed = isXPressed; + + var isBPressed = (reading.Buttons & GamepadButtons.B) == GamepadButtons.B; + if (isBPressed && !_wasBPressed) + { + HandleBackOrCancel(); + } + _wasBPressed = isBPressed; + + var rightThumbY = reading.RightThumbstickY; + if (Math.Abs(rightThumbY) > _deadzone) + { + var wheelDelta = (int)Math.Round(-rightThumbY * 120f); + if (wheelDelta != 0) + { + SimulateMouseWheel(wheelDelta); + } + } } + + private static void HandleBackOrCancel() + { + if (MainWindow.Instance.Content is not Microsoft.UI.Xaml.Controls.Frame rootFrame) + return; + + var currentPage = rootFrame.Content as Microsoft.UI.Xaml.Controls.Page; + if (currentPage is null) + return; + + var pageContext = Ioc.Default.GetRequiredService(); + var shell = pageContext.ShellPage; + + if (shell is null) + return; + + if (shell.CanNavigateBackward) + { + shell.Back_Click(); + return; + } + + SimulateKeyPress(Windows.System.VirtualKey.Escape); + } + + // Key Input Simulation + private static void SimulateKeyPress(Windows.System.VirtualKey key) + { + var down = new KEYINPUT + { + type = INPUT_KEYBOARD, + ki = new KEYBDINPUT + { + wVk = (ushort)key, + dwFlags = KEYEVENTF_KEYDOWN + } + }; + + SendInput(1, ref down, Marshal.SizeOf()); + + var up = new KEYINPUT + { + type = INPUT_KEYBOARD, + ki = new KEYBDINPUT + { + wVk = (ushort)key, + dwFlags = KEYEVENTF_KEYUP + } + }; + + SendInput(1, ref up, Marshal.SizeOf()); + } + + + // Mouse Action Simulation + private static void SimulateLeftMouseClick() + { + var inputDown = new INPUT + { + type = INPUT_MOUSE, + mi = new MOUSEINPUT + { + dwFlags = MOUSEEVENTF_LEFTDOWN + } + }; + + SendInput(1, ref inputDown, Marshal.SizeOf()); + inputDown.mi.dwFlags = MOUSEEVENTF_LEFTUP; + SendInput(1, ref inputDown, Marshal.SizeOf()); + + } + + private static void SimulateRightMouseClick() + { + var inputDown = new INPUT + { + type = INPUT_MOUSE, + mi = new MOUSEINPUT + { + dwFlags = MOUSEEVENTF_RIGHTDOWN + } + }; + + SendInput(1, ref inputDown, Marshal.SizeOf()); + inputDown.mi.dwFlags = MOUSEEVENTF_RIGHTUP; + SendInput(1, ref inputDown, Marshal.SizeOf()); + } + + private static void SimulateMouseWheel(int delta) + { + var input = new INPUT + { + type = INPUT_MOUSE, + mi = new MOUSEINPUT + { + mouseData = unchecked((uint)(delta)), + dwFlags = MOUSEEVENTF_WHEEL + } + }; + + SendInput(1, ref input, Marshal.SizeOf()); + } } } From 9c0ed423fa4d1967c169354e18ad930acc4ce0a4 Mon Sep 17 00:00:00 2001 From: Imran Rahman Date: Mon, 17 Aug 2026 16:23:27 +0600 Subject: [PATCH 3/4] add gamepad dpad mapping to arrow keys --- .../Helpers/UI/XBoxGamepadController.cs | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/Files.App/Helpers/UI/XBoxGamepadController.cs b/src/Files.App/Helpers/UI/XBoxGamepadController.cs index 0ef2f6979a07..209da4a545c6 100644 --- a/src/Files.App/Helpers/UI/XBoxGamepadController.cs +++ b/src/Files.App/Helpers/UI/XBoxGamepadController.cs @@ -73,6 +73,11 @@ private struct MOUSEINPUT private bool _wasXPressed; private bool _wasBPressed; + private bool _wasDPadDownPressed; + private bool _wasDPadUpPressed; + private bool _wasDPadLeftPressed; + private bool _wasDPadRightPressed; + public XBoxGamepadController(DispatcherQueue dispatcherQueue) { _timer = dispatcherQueue.CreateTimer(); @@ -122,6 +127,26 @@ private void OnTick(object? sender, object e) } _wasBPressed = isBPressed; + + var isDDownPressed = (reading.Buttons & GamepadButtons.DPadDown) == GamepadButtons.DPadDown; + if (isDDownPressed && !_wasDPadDownPressed) + SimulateKeyPress(Windows.System.VirtualKey.Down); + _wasDPadDownPressed = isDDownPressed; + + var isDUpPressed = (reading.Buttons & GamepadButtons.DPadUp) == GamepadButtons.DPadUp; + if (isDUpPressed && !_wasDPadUpPressed) + SimulateKeyPress(Windows.System.VirtualKey.Up); + _wasDPadUpPressed = isDUpPressed; + + var isDLeftPressed = (reading.Buttons & GamepadButtons.DPadLeft) == GamepadButtons.DPadLeft; + if (isDLeftPressed && !_wasDPadLeftPressed) + SimulateKeyPress(Windows.System.VirtualKey.Left); + _wasDPadLeftPressed = isDLeftPressed; + + var isDRightPressed = (reading.Buttons & GamepadButtons.DPadRight) == GamepadButtons.DPadRight; + if (isDRightPressed && !_wasDPadRightPressed) + SimulateKeyPress(Windows.System.VirtualKey.Right); + var rightThumbY = reading.RightThumbstickY; if (Math.Abs(rightThumbY) > _deadzone) { @@ -201,7 +226,6 @@ private static void SimulateLeftMouseClick() SendInput(1, ref inputDown, Marshal.SizeOf()); inputDown.mi.dwFlags = MOUSEEVENTF_LEFTUP; SendInput(1, ref inputDown, Marshal.SizeOf()); - } private static void SimulateRightMouseClick() From 3b882201ffe3f8a6b480704a981b97e53a957c39 Mon Sep 17 00:00:00 2001 From: Imran Rahman Date: Mon, 17 Aug 2026 16:47:37 +0600 Subject: [PATCH 4/4] code style formatting --- .../UI/{XBoxGamepadController.cs => XBoxGamepadHelper.cs} | 8 +++----- src/Files.App/MainWindow.xaml.cs | 3 +-- 2 files changed, 4 insertions(+), 7 deletions(-) rename src/Files.App/Helpers/UI/{XBoxGamepadController.cs => XBoxGamepadHelper.cs} (98%) diff --git a/src/Files.App/Helpers/UI/XBoxGamepadController.cs b/src/Files.App/Helpers/UI/XBoxGamepadHelper.cs similarity index 98% rename from src/Files.App/Helpers/UI/XBoxGamepadController.cs rename to src/Files.App/Helpers/UI/XBoxGamepadHelper.cs index 209da4a545c6..f97ce2821706 100644 --- a/src/Files.App/Helpers/UI/XBoxGamepadController.cs +++ b/src/Files.App/Helpers/UI/XBoxGamepadHelper.cs @@ -6,7 +6,7 @@ namespace Files.App.Helpers { - public sealed class XBoxGamepadController + public sealed class XBoxGamepadHelper { [DllImport("user32.dll")] private static extern bool SetCursorPos(int x, int y); @@ -78,7 +78,7 @@ private struct MOUSEINPUT private bool _wasDPadLeftPressed; private bool _wasDPadRightPressed; - public XBoxGamepadController(DispatcherQueue dispatcherQueue) + public XBoxGamepadHelper(DispatcherQueue dispatcherQueue) { _timer = dispatcherQueue.CreateTimer(); _timer.Interval = TimeSpan.FromMilliseconds(16); @@ -91,7 +91,7 @@ private void OnTick(object? sender, object e) var gamepads = Gamepad.Gamepads; if (gamepads.Count == 0) return; - + var gamepad = gamepads[0]; var reading = gamepad.GetCurrentReading(); @@ -152,9 +152,7 @@ private void OnTick(object? sender, object e) { var wheelDelta = (int)Math.Round(-rightThumbY * 120f); if (wheelDelta != 0) - { SimulateMouseWheel(wheelDelta); - } } } diff --git a/src/Files.App/MainWindow.xaml.cs b/src/Files.App/MainWindow.xaml.cs index fdb329b387a1..a4166c46b5a7 100644 --- a/src/Files.App/MainWindow.xaml.cs +++ b/src/Files.App/MainWindow.xaml.cs @@ -28,8 +28,7 @@ public MainWindow() { InitializeComponent(); - var gamepadCursor = new XBoxGamepadController(DispatcherQueue); - + var GamepadHelper = new XBoxGamepadHelper(DispatcherQueue); WindowHandle = WinUIEx.WindowExtensions.GetWindowHandle(this); MinHeight = 316;