{ config, lib, pkgs, ... }: let cfg = config.hardware.display; in { meta.doc = ./display.md; meta.maintainers = with lib.maintainers; [ nazarewk ]; options = { hardware.display.edid.enable = lib.mkOption { type = with lib.types; bool; default = cfg.edid.packages != null; defaultText = lib.literalExpression "config.hardware.display.edid.packages != null"; description = '' Enables handling of EDID files ''; }; hardware.display.edid.packages = lib.mkOption { type = with lib.types; listOf package; default = [ ]; description = '' List of packages containing EDID binary files at `$out/lib/firmware/edid`. Such files will be available for use in `drm.edid_firmware` kernel parameter as `edid/`. You can craft one directly here or use sibling options `linuxhw` and `modelines`. ''; example = lib.literalExpression '' [ (pkgs.runCommand "edid-custom" {} ''' mkdir -p "$out/lib/firmware/edid" base64 -d > "$out/lib/firmware/edid/custom1.bin" <<'EOF' EOF ''') ] ''; apply = list: if list == [ ] then null else (pkgs.buildEnv { name = "firmware-edid"; paths = list; pathsToLink = [ "/lib/firmware/edid" ]; ignoreCollisions = true; }) // { compressFirmware = false; }; }; hardware.display.edid.linuxhw = lib.mkOption { type = with lib.types; attrsOf (listOf str); default = { }; description = '' Exposes EDID files from users-sourced database at https://github.com/linuxhw/EDID Attribute names will be mapped to EDID filenames `.bin`. Attribute values are lists of `awk` regexp patterns that (together) must match exactly one line in either of: - [AnalogDisplay.md](https://raw.githubusercontent.com/linuxhw/EDID/master/AnalogDisplay.md) - [DigitalDisplay.md](https://raw.githubusercontent.com/linuxhw/EDID/master/DigitalDisplay.md) There is no universal way of locating your device config, but here are some practical tips: 1. locate your device: - find your model number (second column) - locate manufacturer (first column) and go through the list manually 2. narrow down results using other columns until there is only one left: - `Name` column - production date (`Made` column) - resolution `Res` - screen diagonal (`Inch` column) - as a last resort use `ID` from the last column ''; example = lib.literalExpression '' { PG278Q_2014 = [ "PG278Q" "2014" ]; } ''; apply = displays: if displays == { } then null else pkgs.linuxhw-edid-fetcher.override { inherit displays; }; }; hardware.display.edid.modelines = lib.mkOption { type = with lib.types; attrsOf str; default = { }; description = '' Attribute set of XFree86 Modelines automatically converted and exposed as `edid/.bin` files in initrd. See for more information: - https://en.wikipedia.org/wiki/XFree86_Modeline ''; example = lib.literalExpression '' { "PG278Q_60" = " 241.50 2560 2608 2640 2720 1440 1443 1448 1481 -hsync +vsync"; "PG278Q_120" = " 497.75 2560 2608 2640 2720 1440 1443 1448 1525 +hsync -vsync"; "U2711_60" = " 241.50 2560 2600 2632 2720 1440 1443 1448 1481 -hsync +vsync"; } ''; apply = modelines: if modelines == { } then null else pkgs.edid-generator.overrideAttrs { clean = true; passthru.config = modelines; modelines = lib.trivial.pipe modelines [ (lib.mapAttrsToList (name: value: lib.throwIfNot (builtins.stringLength name <= 12) "Modeline name must be 12 characters or less" ''Modeline "${name}" ${value}'' )) (builtins.map (line: "${line}\n")) (lib.strings.concatStringsSep "") ]; }; }; hardware.display.outputs = lib.mkOption { type = lib.types.attrsOf (lib.types.submodule ({ options = { edid = lib.mkOption { type = with lib.types; nullOr str; default = null; description = '' An EDID filename to be used for configured display, as in `edid/`. See for more information: - `hardware.display.edid.packages` - https://wiki.archlinux.org/title/Kernel_mode_setting#Forcing_modes_and_EDID ''; }; mode = lib.mkOption { type = with lib.types; nullOr str; default = null; description = '' A `video` kernel parameter (framebuffer mode) configuration for the specific output: x[M][R][-][@][i][m][eDd] See for more information: - https://docs.kernel.org/fb/modedb.html - https://wiki.archlinux.org/title/Kernel_mode_setting#Forcing_modes ''; example = lib.literalExpression '' "e" ''; }; }; })); description = '' Hardware/kernel-level configuration of specific outputs. ''; default = { }; example = lib.literalExpression '' { edid.modelines."PG278Q_60" = "241.50 2560 2608 2640 2720 1440 1443 1448 1481 -hsync +vsync"; outputs."DP-1".edid = "PG278Q_60.bin"; outputs."DP-1".mode = "e"; } ''; }; }; config = lib.mkMerge [ { hardware.display.edid.packages = lib.optional (cfg.edid.modelines != null) cfg.edid.modelines ++ lib.optional (cfg.edid.linuxhw != null) cfg.edid.linuxhw; boot.kernelParams = # forcing video modes lib.trivial.pipe cfg.outputs [ (lib.attrsets.filterAttrs (_: spec: spec.mode != null)) (lib.mapAttrsToList (output: spec: "video=${output}:${spec.mode}")) ] ++ # selecting EDID for displays lib.trivial.pipe cfg.outputs [ (lib.attrsets.filterAttrs (_: spec: spec.edid != null)) (lib.mapAttrsToList (output: spec: "${output}:edid/${spec.edid}")) (builtins.concatStringsSep ",") (p: lib.optional (p != "") "drm.edid_firmware=${p}") ] ; } (lib.mkIf (cfg.edid.packages != null) { # services.udev implements hardware.firmware option services.udev.enable = true; hardware.firmware = [ cfg.edid.packages ]; }) ]; }