{ config, lib, pkgs, ... }: with lib; let cfg = config.hidutil; # convert hex to int with nix! hexToDec = hex: (builtins.fromTOML "a = 0x${removePrefix "0x" hex}").a; in { ###### interface options = { hidutil = { enable = mkEnableOption "Enable key remapping powered by hidutil"; remapKeys = mkOption { type = types.listOf (types.submodule { options = { VendorID = mkOption { type = types.nullOr types.str; default = null; }; ProductID = mkOption { type = types.nullOr types.str; default = null; }; LocationID = mkOption { type = types.nullOr types.str; default = null; }; UserKeyMapping = mkOption { type = types.listOf (types.submodule { options = { HIDKeyboardModifierMappingSrc = mkOption { type = types.int; }; HIDKeyboardModifierMappingDst = mkOption { type = types.int; }; }; }); }; }; }); default = []; }; }; }; ###### implementation config = mkIf cfg.enable { launchd.user.agents = builtins.listToAttrs ( lib.remove "skip" ( lib.forEach cfg.remapKeys ( entry: (if (entry.LocationID != null) then "skip" else { name = "activateUserKeyMapping-${entry.VendorID}:${entry.ProductID}"; value.serviceConfig = { Disabled = if (entry.VendorID != "5ac" && entry.ProductID != "281") then false else true; Label = "org.nixos.activateUserKeyMapping-${entry.VendorID}:${entry.ProductID}"; ProgramArguments = [ "${pkgs.XPCEventStreamHandler}/bin/xpc_set_event_stream_handler" "${pkgs.writeScript "hidutil" '' #!/usr/bin/env bash osascript -e 'display notification "Load UserKeyMapping for ${entry.VendorID}:${entry.ProductID}" with title "hidutil"' hidutil property --matching '{"VendorID":0x${entry.VendorID},"ProductID":0x${entry.ProductID}' --set '{"UserKeyMapping":${builtins.toJSON entry.UserKeyMapping}}' > /dev/null ''}" ]; LaunchEvents = { "com.apple.iokit.matching" = { "com.apple.device-attach" = { IOMatchLaunchStream = true; IOProviderClass = "IOUSBDevice"; idVendor = hexToDec entry.VendorID; idProduct = hexToDec entry.ProductID; }; }; }; }; }) ) ) ); system.activationScripts.keyboard.text = '' # Configuring keyboard echo "configuring keyboard..." >&2 ${lib.concatStringsSep "\n" (lib.forEach cfg.remapKeys (entry: (if entry.LocationID != null then "hidutil property --matching '{\"LocationID\":0x${entry.LocationID}}' --set '{\"UserKeyMapping\":${builtins.toJSON entry.UserKeyMapping}}' > /dev/null" else "hidutil property --matching '{\"VendorID\":0x${entry.VendorID},\"ProductID\":0x${entry.ProductID}}' --set '{\"UserKeyMapping\":${builtins.toJSON entry.UserKeyMapping}}' > /dev/null" )))} ''; }; }