diff --git a/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml
index 83f2d1022514..5c29f98b28df 100644
--- a/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml
+++ b/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml
@@ -62,6 +62,15 @@
services.infnoise.
+
+
+ persistent-evdev,
+ a daemon to add virtual proxy devices that mirror a physical
+ input device but persist even if the underlying hardware is
+ hot-plugged. Available as
+ services.persistent-evdev.
+
+
diff --git a/nixos/doc/manual/release-notes/rl-2211.section.md b/nixos/doc/manual/release-notes/rl-2211.section.md
index 85adae269c31..624bde2c83d7 100644
--- a/nixos/doc/manual/release-notes/rl-2211.section.md
+++ b/nixos/doc/manual/release-notes/rl-2211.section.md
@@ -29,6 +29,7 @@ In addition to numerous new and upgraded packages, this release has the followin
- [infnoise](https://github.com/leetronics/infnoise), a hardware True Random Number Generator dongle.
Available as [services.infnoise](options.html#opt-services.infnoise.enable).
+- [persistent-evdev](https://github.com/aiberia/persistent-evdev), a daemon to add virtual proxy devices that mirror a physical input device but persist even if the underlying hardware is hot-plugged. Available as [services.persistent-evdev](#opt-services.persistent-evdev.enable).
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 7b1054262f59..d67602a26761 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -606,6 +606,7 @@
./services/misc/packagekit.nix
./services/misc/paperless.nix
./services/misc/parsoid.nix
+ ./services/misc/persistent-evdev.nix
./services/misc/plex.nix
./services/misc/plikd.nix
./services/misc/podgrab.nix
diff --git a/nixos/modules/services/misc/persistent-evdev.nix b/nixos/modules/services/misc/persistent-evdev.nix
new file mode 100644
index 000000000000..401d20010b12
--- /dev/null
+++ b/nixos/modules/services/misc/persistent-evdev.nix
@@ -0,0 +1,60 @@
+{ config, lib, pkgs, ... }:
+
+let
+ cfg = config.services.persistent-evdev;
+ settingsFormat = pkgs.formats.json {};
+
+ configFile = settingsFormat.generate "persistent-evdev-config" {
+ cache = "/var/cache/persistent-evdev";
+ devices = lib.mapAttrs (virt: phys: "/dev/input/by-id/${phys}") cfg.devices;
+ };
+in
+{
+ options.services.persistent-evdev = {
+ enable = lib.mkEnableOption "virtual input devices that persist even if the backing device is hotplugged";
+
+ devices = lib.mkOption {
+ default = {};
+ type = with lib.types; attrsOf str;
+ description = ''
+ A set of virtual proxy device labels with backing physical device ids.
+
+ Physical devices should already exist in /dev/input/by-id/.
+ Proxy devices will be automatically given a uinput- prefix.
+
+ See the
+ project page for example configuration of virtual devices with libvirt
+ and remember to add uinput-* devices to the qemu
+ cgroup_device_acl list (see ).
+ '';
+ example = lib.literalExpression ''
+ {
+ persist-mouse0 = "usb-Logitech_G403_Prodigy_Gaming_Mouse_078738533531-event-if01";
+ persist-mouse1 = "usb-Logitech_G403_Prodigy_Gaming_Mouse_078738533531-event-mouse";
+ persist-mouse2 = "usb-Logitech_G403_Prodigy_Gaming_Mouse_078738533531-if01-event-kbd";
+ persist-keyboard0 = "usb-Microsoft_NaturalĀ®_Ergonomic_Keyboard_4000-event-kbd";
+ persist-keyboard1 = "usb-Microsoft_NaturalĀ®_Ergonomic_Keyboard_4000-if01-event-kbd";
+ }
+ '';
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+
+ systemd.services.persistent-evdev = {
+ documentation = [ "https://github.com/aiberia/persistent-evdev/blob/master/README.md" ];
+ description = "Persistent evdev proxy";
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = {
+ Restart = "on-failure";
+ ExecStart = "${pkgs.persistent-evdev}/bin/persistent-evdev.py ${configFile}";
+ CacheDirectory = "persistent-evdev";
+ };
+ };
+
+ services.udev.packages = [ pkgs.persistent-evdev ];
+ };
+
+ meta.maintainers = with lib.maintainers; [ lodi ];
+}
diff --git a/pkgs/servers/persistent-evdev/default.nix b/pkgs/servers/persistent-evdev/default.nix
new file mode 100644
index 000000000000..c666e9c8d799
--- /dev/null
+++ b/pkgs/servers/persistent-evdev/default.nix
@@ -0,0 +1,42 @@
+{ lib, buildPythonPackage, fetchFromGitHub, python3Packages }:
+
+buildPythonPackage rec {
+ pname = "persistent-evdev";
+ version = "unstable-2022-05-07";
+
+ src = fetchFromGitHub {
+ owner = "aiberia";
+ repo = pname;
+ rev = "52bf246464e09ef4e6f2e1877feccc7b9feba164";
+ sha256 = "d0i6DL/qgDELet4ew2lyVqzd9TApivRxL3zA3dcsQXY=";
+ };
+
+ propagatedBuildInputs = with python3Packages; [
+ evdev pyudev
+ ];
+
+ postPatch = ''
+ patchShebangs bin/persistent-evdev.py
+ '';
+
+ dontBuild = true;
+
+ installPhase = ''
+ mkdir -p $out/bin
+ cp bin/persistent-evdev.py $out/bin
+
+ mkdir -p $out/etc/udev/rules.d
+ cp udev/60-persistent-input-uinput.rules $out/etc/udev/rules.d
+ '';
+
+ # has no tests
+ doCheck = false;
+
+ meta = with lib; {
+ homepage = "https://github.com/aiberia/persistent-evdev";
+ description = "Persistent virtual input devices for qemu/libvirt/evdev hotplug support";
+ license = licenses.mit;
+ maintainers = [ maintainers.lodi ];
+ platforms = platforms.linux;
+ };
+}
diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix
index bd878b1669ca..4c9ef168932f 100644
--- a/pkgs/top-level/all-packages.nix
+++ b/pkgs/top-level/all-packages.nix
@@ -4757,6 +4757,8 @@ with pkgs;
evdevremapkeys = callPackage ../tools/inputmethods/evdevremapkeys { };
+ persistent-evdev = python3Packages.callPackage ../servers/persistent-evdev { };
+
evscript = callPackage ../tools/inputmethods/evscript { };
gebaar-libinput = callPackage ../tools/inputmethods/gebaar-libinput { stdenv = gcc10StdenvCompat; };