Continuing the discussion from Night Light not working after upgrade to Fedora 42 (GNOME 48.4, Wayland, Intel GPU):
I’ve found a fix with the help of Claude Opus 4.7:
Script fix-nightlight-edid.sh
#!/usr/bin/env bash
#
# fix-nightlight-edid.sh
# -----------------------
# Repare Night Light (GNOME/Wayland) sur un setup multi-ecrans ou
# DEUX (ou plus) ecrans externes IDENTIQUES ne recoivent pas le filtre.
#
# Cause : des ecrans identiques exposent un EDID binairement identique.
# colord genere alors le meme "device id" pour les deux, le second
# enregistrement echoue ("device id already exists"), et Night Light
# n'a plus de cible gamma sur l'ecran en collision.
#
# Solution : surcharger l'EDID de chaque sortie DisplayPort avec un
# numero de serie unique (+ checksum recalcule), pour TOUS les
# connecteurs susceptibles d'etre utilises par le dock. Ainsi, quel
# que soit le connecteur sur lequel un ecran atterrit au boot, son
# EDID reste unique et colord enregistre chaque ecran separement.
#
# Cible : Fedora / GNOME / Wayland / GPU Intel (i915) ou tout DRM/KMS.
# Necessite : root (sudo), python3, grubby.
#
# Usage :
# chmod +x fix-nightlight-edid.sh
# sudo ./fix-nightlight-edid.sh # applique
# sudo ./fix-nightlight-edid.sh --status # diagnostic seul, sans modif
# sudo ./fix-nightlight-edid.sh --revert # annule le parametre kernel
#
# Idempotent : relancer le script ne casse rien, il regenere proprement.
set -euo pipefail
# --- Configuration ----------------------------------------------------------
# Connecteurs DP a couvrir. Ajuste si ton dock en utilise d'autres.
# (DP-5..DP-8 couvre la majorite des docks USB-C/Thunderbolt.)
CONNECTORS=(DP-5 DP-6 DP-7 DP-8)
# Repertoire ou seront stockes les EDID patches.
FW_DIR="/lib/firmware/edid"
# Carte DRM (card1 sur la plupart des portables Intel recents ;
# le script la detecte automatiquement, ceci n'est qu'un repli).
CARD_FALLBACK="card1"
# --- Helpers ----------------------------------------------------------------
red() { printf '\033[31m%s\033[0m\n' "$*"; }
grn() { printf '\033[32m%s\033[0m\n' "$*"; }
ylw() { printf '\033[33m%s\033[0m\n' "$*"; }
info() { printf ' %s\n' "$*"; }
need_root() {
if [[ $EUID -ne 0 ]]; then
red "Ce script doit etre lance avec sudo."
exit 1
fi
}
detect_card() {
# Trouve la carte qui possede des connecteurs DP connectes.
for c in /sys/class/drm/card*-DP-*/status; do
if [[ "$(cat "$c" 2>/dev/null)" == "connected" ]]; then
basename "$(dirname "$c")" | sed 's/-DP-.*//'
return 0
fi
done
echo "$CARD_FALLBACK"
}
# --- Sous-commandes ---------------------------------------------------------
cmd_status() {
local card; card="$(detect_card)"
ylw "== Diagnostic Night Light / EDID =="
info "Carte DRM detectee : $card"
echo
ylw "Sorties DisplayPort :"
for f in /sys/class/drm/${card}-DP-*/status; do
[[ -e "$f" ]] || continue
local name; name="$(basename "$(dirname "$f")")"
info "$name : $(cat "$f")"
done
echo
ylw "Empreintes EDID des sorties connectees (collision si identiques) :"
for f in /sys/class/drm/${card}-DP-*/edid; do
[[ -s "$f" ]] || continue
info "$(md5sum "$f")"
done
echo
ylw "Devices couleur connus de colord :"
if command -v colormgr >/dev/null; then
colormgr get-devices 2>/dev/null | grep -i "device id" | sed 's/^/ /' || info "(aucun)"
else
info "colormgr absent."
fi
echo
ylw "Parametre kernel actif :"
grep -o "drm.edid_firmware=[^ ]*" /proc/cmdline | sed 's/^/ /' || info "(aucun)"
}
cmd_apply() {
need_root
local card; card="$(detect_card)"
ylw "Carte DRM : $card"
# 1. Trouver une sortie connectee pour servir de modele EDID.
local src=""
for f in /sys/class/drm/${card}-DP-*/edid; do
if [[ -s "$f" ]]; then src="$f"; break; fi
done
if [[ -z "$src" ]]; then
red "Aucune sortie DP connectee avec EDID. Branche les ecrans externes d'abord."
exit 1
fi
grn "EDID modele : $src"
# 2. Generer un EDID patche par connecteur, chacun avec un serial unique.
mkdir -p "$FW_DIR"
python3 - "$src" "$FW_DIR" "${CONNECTORS[@]}" <<'PY'
import sys
src, fw_dir, *connectors = sys.argv[1:]
with open(src, "rb") as f:
base = bytearray(f.read())
# Un serial unique par connecteur (octet 0x0C du bloc de base).
for i, name in enumerate(connectors):
edid = bytearray(base)
edid[12] = 0x21 + i # serial unique, evite 0x00
somme = sum(edid[0:127]) & 0xFF
edid[127] = (256 - somme) & 0xFF # checksum bloc de base
assert sum(edid[0:128]) & 0xFF == 0, f"checksum invalide {name}"
out = f"{fw_dir}/edid-{name}.bin"
with open(out, "wb") as g:
g.write(edid)
print(f" genere {out} (serial 0x{edid[12]:02x})")
PY
# 3. Construire la valeur du parametre kernel : un fichier par connecteur.
local arg="drm.edid_firmware="
local parts=()
for n in "${CONNECTORS[@]}"; do
parts+=("${n}:edid/edid-${n}.bin")
done
arg+="$(IFS=,; echo "${parts[*]}")"
# 4. Nettoyer toute ancienne valeur edid_firmware puis appliquer la nouvelle.
local old
old="$(grep -o 'drm.edid_firmware=[^ ]*' /proc/cmdline || true)"
if [[ -n "$old" ]]; then
info "Suppression ancien parametre : $old"
grubby --update-kernel=ALL --remove-args="$old" >/dev/null || true
fi
grubby --update-kernel=ALL --args="$arg" >/dev/null
grn "Parametre applique :"
info "$arg"
echo
ylw "Reboote pour activer, puis verifie avec :"
info "colormgr get-devices | grep -i 'device id' # doit lister chaque ecran"
}
cmd_revert() {
need_root
local old
old="$(grep -o 'drm.edid_firmware=[^ ]*' /proc/cmdline || true)"
if [[ -z "$old" ]]; then
# Repli : reconstruire la valeur theorique pour la retirer.
local parts=()
for n in "${CONNECTORS[@]}"; do parts+=("${n}:edid/edid-${n}.bin"); done
old="drm.edid_firmware=$(IFS=,; echo "${parts[*]}")"
fi
grubby --update-kernel=ALL --remove-args="$old" >/dev/null || true
grn "Parametre kernel retire. Reboote pour revenir a l'etat d'origine."
info "(Les fichiers $FW_DIR/edid-*.bin restent ; supprime-les a la main si besoin.)"
}
# --- Dispatch ---------------------------------------------------------------
case "${1:-apply}" in
--status|status) cmd_status ;;
--revert|revert) cmd_revert ;;
--apply|apply|"") cmd_apply ;;
*) red "Argument inconnu : $1"; echo "Usage : $0 [--apply|--status|--revert]"; exit 1 ;;
esac
Readme:
# Réparer Night Light sur un setup multi-écrans (GNOME / Wayland / Intel)
Correctif pour le cas où **Night Light (éclairage nocturne) ne s'applique pas
sur des écrans externes identiques** branchés via un dock, sous GNOME Wayland.
Testé sur **Fedora 42, GNOME/Mutter 48, GPU Intel Iris Xe (i915), Wayland**,
avec deux écrans externes identiques + l'écran d'un portable.
---
## Le symptôme
- Night Light est activé (`enabled = true`), la température est réglée, mais
l'écran ne se réchauffe pas.
- Le démon `gsd-color` calcule correctement (`night light mode on, using
temperature of 1700K`) **sans erreur** dans les logs.
- `colormgr get-devices` ne liste **pas** tous les écrans : il en manque un.
- Typiquement, sur deux écrans externes *identiques*, un seul reçoit le filtre
(ou aucun), tandis que l'écran du portable fonctionne.
## La cause racine
Sous Wayland, GNOME applique Night Light via une **rampe gamma par écran**,
pilotée par `colord`. Or `colord` identifie chaque écran par un *device id*
dérivé de son **EDID** (la carte d'identité que le moniteur transmet).
Deux écrans **strictement identiques** transmettent un EDID **binairement
identique** — y compris le numéro de série, que les moniteurs bon marché ne
rendent pas unique. `colord` génère alors le **même device id** pour les deux.
Le second enregistrement échoue silencieusement :
```
Failed to create colord device for '...': device id 'xrandr-DP-8' already exists
```
Résultat : l'écran en collision n'a **aucun device colord**, donc Night Light
n'a aucune cible où appliquer la rampe gamma. Il reste froid.
## La solution
Surcharger l'EDID de chaque sortie DisplayPort avec un **numéro de série
unique** (octet `0x0C` du bloc de base), en recalculant le **checksum** du bloc
(octet `0x7F`, somme des 128 octets ≡ 0 mod 256). Les deux écrans présentent
alors des EDID différents → `colord` les enregistre séparément → Night Light
s'applique sur chacun.
Le correctif est passé au **kernel** via `drm.edid_firmware`, qui charge un
fichier EDID de remplacement par connecteur dès le démarrage.
### Pourquoi patcher TOUS les connecteurs DP
Les docks USB-C/Thunderbolt **ne donnent pas un nom de connecteur stable** : le
même écran physique peut apparaître comme `DP-5` à un boot, `DP-7` à un autre,
selon l'ordre d'énumération. Si on ne patche qu'un seul connecteur, le correctif
saute dès que le nom change.
La parade : patcher `DP-5` à `DP-8` d'un coup, **chacun avec un fichier au
serial différent**. Quel que soit le connecteur où un écran atterrit, son EDID
reste unique, et deux écrans ne peuvent jamais entrer en collision.
> ⚠️ Ne jamais donner le *même* fichier patché à deux connecteurs : on
> recréerait la collision (même serial). Un fichier = un serial = un connecteur.
---
## Utilisation
```bash
chmod +x fix-nightlight-edid.sh
# 1. Diagnostic (lecture seule, aucune modification)
sudo ./fix-nightlight-edid.sh --status
# 2. Appliquer le correctif
sudo ./fix-nightlight-edid.sh
# 3. Redémarrer (le paramètre kernel n'est lu qu'au boot)
sudo reboot
# 4. Vérifier après reboot : chaque écran doit avoir son device id
colormgr get-devices | grep -i "device id"
```
Le script est **idempotent** : le relancer nettoie l'ancien paramètre et
régénère proprement. Pour annuler complètement :
```bash
sudo ./fix-nightlight-edid.sh --revert
sudo reboot
```
### Adapter à un autre dock
Si ton dock utilise d'autres connecteurs que `DP-5`–`DP-8`, repère-les avec
`--status` (cherche les lignes `connected`) et modifie la liste `CONNECTORS`
en tête du script.
---
## Vérifications utiles (manuelles)
```bash
# Quels écrans sont connectés et sur quels connecteurs
for f in /sys/class/drm/card*-DP-*/status; do echo "$f: $(cat $f)"; done
# Collision EDID ? (md5 identiques = collision)
md5sum /sys/class/drm/card*-DP-*/edid
# Le paramètre kernel est-il actif ?
cat /proc/cmdline | grep -o "drm.edid_firmware=[^ ]*"
# Le kernel a-t-il chargé les EDID de remplacement ?
sudo dmesg | grep -i edid
```
---
## Ce que ce correctif ne règle PAS
Ce script traite **uniquement** la collision EDID (écrans identiques non
distingués par colord).
Sous **GNOME 48 / Fedora 42–43**, il existe par ailleurs un bug distinct où
Night Light peut être **instable au démarrage** (s'applique ou non selon le
boot), indépendamment du nombre d'écrans. Il est suivi côté distribution
(Bugzilla Red Hat #2400989) et côté GNOME (gitlab gnome-shell). Contournements
connus le temps qu'un correctif arrive :
- Rebooter jusqu'à ce que l'application gamma reparte.
- Passer par l'écran de connexion GDM (éviter l'autologin) — aide chez certains.
- Extension GNOME *Bedtime Mode* (option « Amber ») : pose un calque ambré
logiciel indépendant du gamma matériel. S'applique partout mais peut
provoquer un léger scintillement selon les configs.
- Filtre « Low Blue Light » intégré à l'OSD du moniteur : 100 % matériel,
insensible à tous ces bugs.
---
## Détails techniques de l'EDID (pour comprendre le patch)
| Offset (bloc de base) | Contenu |
|---|---|
| `0x08`–`0x0B` | ID fabricant + produit |
| `0x0C`–`0x0F` | **Numéro de série** (4 octets) — patché ici (octet `0x0C`) |
| `0x7F` (127) | **Checksum** : la somme des 128 octets du bloc doit valoir 0 (mod 256) |
Un EDID peut faire 128 octets (1 bloc) ou 256 (bloc de base + extension
CTA-861). On ne modifie que le bloc de base, donc seul le checksum à `0x7F` est
recalculé ; l'éventuelle extension reste intacte.