Axel Hajslund Damgaard's Website

Here I write about a little bit of everything that interests me.

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:

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.