Using more than 80 joystick buttons in Linux and Wine
March 28, 2026
Table of Contents
Introduction
By default, Linux only allows for a maximum of 80 buttons per joystick device. This is fine for most purposes, but for flight simulation many joysticks have more than 80 buttons, with some having 128.
The limit is not due to a technical limitation, but instead due to an arbitrary #define in include/linux/mod_devicetable.h and include/uapi/linux/input-event-codes.h:
// include/linux/mod_devicetable.h
#define INPUT_DEVICE_ID_KEY_MAX 0x2ff
// include/uapi/linux/input-event-codes.h
#define KEY_MAX 0x2ff
Increasing the limit in the kernel
A patch has already been made, but it unfortunately didn’t implemented in the kernel. Link to patch.
To use this patch on NixOS patches can be easily applied with boot.kernelPatches. To do this, first create a patch file:
//input-key-max.patch
diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h
index 8d764aab29de..35eb59ae1f19 100644
--- a/include/linux/mod_devicetable.h
+++ b/include/linux/mod_devicetable.h
@@ -311,7 +311,7 @@ struct pcmcia_device_id {
/* Input */
#define INPUT_DEVICE_ID_EV_MAX 0x1f
#define INPUT_DEVICE_ID_KEY_MIN_INTERESTING 0x71
-#define INPUT_DEVICE_ID_KEY_MAX 0x2ff
+#define INPUT_DEVICE_ID_KEY_MAX 0x4ff
#define INPUT_DEVICE_ID_REL_MAX 0x0f
#define INPUT_DEVICE_ID_ABS_MAX 0x3f
#define INPUT_DEVICE_ID_MSC_MAX 0x07
diff --git a/include/uapi/linux/input-event-codes.h b/include/uapi/linux/input-event-codes.h
index b6a835d37826..ad1b9bed3828 100644
--- a/include/uapi/linux/input-event-codes.h
+++ b/include/uapi/linux/input-event-codes.h
@@ -774,7 +774,7 @@
/* We avoid low common keys in module aliases so they don't get huge. */
#define KEY_MIN_INTERESTING KEY_MUTE
-#define KEY_MAX 0x2ff
+#define KEY_MAX 0x4ff
#define KEY_CNT (KEY_MAX+1)
Patching the kernel on NixOS is done by adding the patch as an attribute set to the boot.kernelPatches list like so:
boot.kernelPatches = [
{
name = "input-key-max";
patch = ./input-key-max.patch;
}
];
If you open jstest-gtk, it should now correctly show all buttons (unless you have more than 512 buttons).
Increasing the limit in Wine
Although the kernel now exposes all of the buttons, most programs that use joysticks use the values from the linux headers they were built with, which only uses a maximum of 80 buttons. You therefore have to rebuild wine with the new linux headers.
It might seem tempting to just patch linuxHeaders with an overlay.
This will recompile all packages that depend on linuxHeaders, and it would be the ideal solution.
However, this will cause many programs that don’t use joystick inputs to needlessly rebuild, taking literal days to compile on my machine.
The better way is to build a standalone version of wine without messing with the system packages.
In order to do that, first chose a directory to build it in (eg. ~/app/wine_patched/).
First, copy input-key-max.patch to the wine directory, and create a nix file to patch linuxHeaders like so:
# patched-headers.nix
{ pkgs }:
pkgs.linuxHeaders.overrideAttrs (old: {
patches = (old.patches or [ ]) ++ [
./input-key-max.patch
];
})
You then patch wine with patched-headers.nix:
# wine-custom.nix
{ pkgs }:
let
patchedHeaders = import ./patched-headers.nix { inherit pkgs; };
in
pkgs.wineWow64Packages.staging.overrideAttrs (old: {
buildInputs = (old.buildInputs or [ ]) ++ [ patchedHeaders ];
nativeBuildInputs = (old.nativeBuildInputs or [ ]) ++ [ patchedHeaders ];
})
And finally make a flake to tie it all together:
# flake.nix
{
description = "Wine with patched linux headers";
inputs.nixpkgs.url = "github:NixOS/nixpkgs";
outputs =
{ self, nixpkgs }:
let
system = "x86_64-linux";
pkgs = import nixpkgs { inherit system; };
in
{
packages.${system}.wine-custom = import ./wine-custom.nix { inherit pkgs; };
};
}
Running nix build will now compile Wine with the patched headers, and put the patched wine executeable in ./result/bin/wine.
To use the patched wine in Lutris, you set the Wine version in Runner options to “Custom”, and the custom wine executeable to /path/to/build/result/bin/wine.
In order to get it working properly, I had to set the following options:
- Disable Lutris Runtime: True
- Prefer system libraries: True
- Environment variables: “WINEDEBUG=dinput,+joystick”
- DLL overrides: “wbemprox=n”
For me, it looks like it doesn’t work in the Wine control panel, but it works in DCS. Your mileage may vary with the custom options.