forza-trigger: rewrite

This commit is contained in:
2026-05-04 01:39:32 -04:00
parent 09175cd0dc
commit 6501fe2ddb
4 changed files with 366 additions and 1447 deletions

View File

@@ -171,7 +171,6 @@
}; };
}; };
# PS5 DualSense adaptive triggers in Forza Horizon 4 / 5. # PS5 DualSense adaptive triggers in Forza Horizon 4 / 5.
services.forzaTrigger.enable = true; services.forzaTrigger.enable = true;
} }

View File

@@ -10,7 +10,7 @@
# Forza emits a fixed-format UDP telemetry stream ("Data Out") at 60 Hz on a # Forza emits a fixed-format UDP telemetry stream ("Data Out") at 60 Hz on a
# user-configured port. We listen on that port, parse each packet via fdp # user-configured port. We listen on that port, parse each packet via fdp
# (nettrom/forza_motorsport, MIT), and drive the PS5 DualSense's adaptive # (nettrom/forza_motorsport, MIT), and drive the PS5 DualSense's adaptive
# triggers via pydualsense (PyPI, MIT) which talks HID over hidraw. # triggers via dualsense-controller (PyPI, MIT) which talks HID over hidraw.
# #
# Setup on the user side, once enabled here: # Setup on the user side, once enabled here:
# - plug the DualSense in over USB and disable Steam Input for the # - plug the DualSense in over USB and disable Steam Input for the
@@ -23,11 +23,11 @@
let let
cfg = config.services.forzaTrigger; cfg = config.services.forzaTrigger;
pythonPackages = import ./python-packages.nix { inherit lib pkgs; }; pythonPackages = import ./python-packages.nix { inherit lib pkgs; };
inherit (pythonPackages) pydualsense fdp; inherit (pythonPackages) dualsense-controller fdp;
forzaTrigger = pkgs.writers.writePython3Bin "forza-trigger" { forzaTrigger = pkgs.writers.writePython3Bin "forza-trigger" {
libraries = [ libraries = [
pydualsense dualsense-controller
fdp fdp
]; ];
# The wrapped binary doesn't need style enforcement — readability of # The wrapped binary doesn't need style enforcement — readability of

File diff suppressed because it is too large Load Diff

View File

@@ -12,65 +12,64 @@ let
py = python.pkgs; py = python.pkgs;
in in
rec { rec {
# CFFI bindings to libhidapi (flok/hidapi-cffi on PyPI). pydualsense's # DualSense controller library (yesbotics/dualsense-controller-python).
# `import hidapi` resolves to this — nixpkgs' python3Packages.hidapi is the # Bundles its own hidapi-cffi binding; replaces the previous pydualsense +
# Cython wrapper from trezor/cython-hidapi which exposes a different # hidapi-usb pair. The library's transport model writes the HID output
# `import hid` API and can't satisfy pydualsense. # report on every input report tick BUT only when an output state actually
hidapi-usb = py.buildPythonPackage rec { # changed since the last write — so we get on-demand HID writes for free
pname = "hidapi-usb"; # without disabling any background thread (the thing pydualsense made us
version = "0.3.2"; # work around at ~250 Hz). BT CRC32 is computed correctly inside the
format = "setuptools"; # library's Bt31OutReport.to_bytes.
dualsense-controller = py.buildPythonPackage rec {
pname = "dualsense-controller";
version = "0.3.1";
pyproject = true;
# PyPI's project URL slug uses a hyphen (`hidapi-usb`) but the sdist file # PyPI normalizes the sdist filename to underscore form (PEP 625) but
# itself is PEP-625-normalized to an underscore (`hidapi_usb-…`). Stock # the project URL slug is dasherized. fetchPypi assumes they match;
# fetchPypi assumes they match they don't here, so fetch by direct URL. # passing the underscored pname matches the on-disk filename.
src = pkgs.fetchurl { src = py.fetchPypi {
url = "https://files.pythonhosted.org/packages/55/80/960ae94b615e26a7d1aeebe8e9fefda2f25608bf1016f9aec268b328c35e/hidapi_usb-${version}.tar.gz"; pname = "dualsense_controller";
hash = "sha256-oxp+2i+qqYd1uwiS2Dh8/PzO62iYQQXpR936MnDIFk0="; inherit version;
hash = "sha256-yy1MQeRPqaLvoXaAigQd3gPFsFLbwKqrD4mP2zQqcFw=";
}; };
propagatedBuildInputs = [ py.cffi ]; nativeBuildInputs = [ py.poetry-core ];
# Upstream's hidapi.py walks a tuple of soname strings via ffi.dlopen() # Upstream's pyproject pins pyee ^11 and cffi ^1.15. nixpkgs ships
# until one resolves. Pin the two Linux hidraw entries to absolute store # pyee 13 and cffi 2. The library only uses stable APIs — pyee
# paths so the wrapped Python in our writePython3Bin closure finds them # `EventEmitter`/`on`/`emit` and cffi `FFI`/`cdef`/`dlopen`/`new`/
# without LD_LIBRARY_PATH wrapping. The libusb / iohidmanager / dylib / # `buffer` — that work unchanged on the newer versions. Relax both.
# dll entries are dead code on Linux. --replace-fail makes a rename in pythonRelaxDeps = [
"pyee"
"cffi"
];
propagatedBuildInputs = with py; [
pyee
cffi
deprecated
];
# Same hidapi-binding pattern as our previous hidapi-usb derivation.
# The library walks a tuple of soname strings via ffi.dlopen() until
# one resolves; pin the two Linux hidraw entries to absolute store
# paths so the wrapped Python in writePython3Bin doesn't need
# LD_LIBRARY_PATH wrapping. The libusb / iohidmanager / dylib / dll
# entries are dead code on Linux. --replace-fail makes a rename in
# upstream's tuple a loud build error rather than a silent ImportError # upstream's tuple a loud build error rather than a silent ImportError
# at runtime. # at runtime.
postPatch = '' postPatch = ''
substituteInPlace hidapi.py \ substituteInPlace src/dualsense_controller/core/hidapi/hidapi.py \
--replace-fail "'libhidapi-hidraw.so'," "'${pkgs.hidapi}/lib/libhidapi-hidraw.so'," \ --replace-fail "'libhidapi-hidraw.so'," "'${pkgs.hidapi}/lib/libhidapi-hidraw.so'," \
--replace-fail "'libhidapi-hidraw.so.0'," "'${pkgs.hidapi}/lib/libhidapi-hidraw.so.0'," --replace-fail "'libhidapi-hidraw.so.0'," "'${pkgs.hidapi}/lib/libhidapi-hidraw.so.0',"
''; '';
pythonImportsCheck = [ "hidapi" ]; pythonImportsCheck = [ "dualsense_controller" ];
meta = { meta = {
description = "CFFI wrapper for hidapi (used by pydualsense)"; description = "Use DualSense Controller with Python";
homepage = "https://github.com/flok/hidapi-cffi"; homepage = "https://github.com/yesbotics/dualsense-controller-python";
license = lib.licenses.bsd3;
};
};
pydualsense = py.buildPythonPackage rec {
pname = "pydualsense";
version = "0.7.5";
format = "pyproject";
src = py.fetchPypi {
inherit pname version;
hash = "sha256-YgX8AJE4f8p7geKT3xlCD0Mlh1GcyHpBz4rEIqdwKgs=";
};
nativeBuildInputs = [ py.poetry-core ];
propagatedBuildInputs = [ hidapi-usb ];
pythonImportsCheck = [ "pydualsense" ];
meta = {
description = "Control your PS5 DualSense controller from Python";
homepage = "https://github.com/flok/pydualsense";
license = lib.licenses.mit; license = lib.licenses.mit;
}; };
}; };