nixpkgs/nixos/modules/services/hardware/display.nix
2024-07-18 16:57:40 +03:00

194 lines
6.8 KiB
Nix

{ 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/<filename>`.
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'
<insert your base64 encoded EDID file here `base64 < /sys/class/drm/card0-.../edid`>
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 `<NAME>.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/<name>.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/<filename>`.
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:
<xres>x<yres>[M][R][-<bpp>][@<refresh>][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 ];
})
];
}