-
-
Save startergo/ba2f4d733b9e9452283cc7f359d1ad25 to your computer and use it in GitHub Desktop.
A set of shell functions used to view and edit EDIDs.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # EDIDUtil.sh v5.12 | |
| # by joevt Jun 27/2020 | |
| #========================================================================================= | |
| # Modify EDID | |
| getarrstart () { | |
| # bash arrays start at 0 | |
| # zsh arrays start at 1 (applies only to [] syntax) but this can be changed with "setopt ksh_arrays" | |
| # zsh arrays start at 0 when using ${arr:x:x} syntax | |
| local arr=(1 0) | |
| arrstart=${arr[1]} | |
| } | |
| getarrstart | |
| replacebytes () { | |
| local bytepos=$(($1*2)) | |
| local thebytes=$2 | |
| local thelen=${#thebytes} | |
| [[ -n $3 ]] && thelen=$(($3*2)) | |
| theedid=${theedid:0:$bytepos}${thebytes}${theedid:$bytepos+thelen} | |
| } | |
| repairchecksums () { | |
| local blockoffset=0 | |
| while (( blockoffset*2 < ${#theedid} )); do | |
| local blocktag=${theedid:$blockoffset*2:2} | |
| if [[ $blocktag = 70 ]]; then | |
| local sectionsize=$((0x${theedid:$blockoffset*2+4:2})) | |
| replacebytes $(($blockoffset+$sectionsize+5)) $(printf "%02x" $(( -($(echo -n ${theedid:$blockoffset*2+2:$sectionsize*2+8} | sed "s/../+0x&/g")) & 0xff ))) | |
| fi | |
| replacebytes $(($blockoffset+127)) $(printf "%02x" $(( -($(echo -n ${theedid:$blockoffset*2:254} | sed "s/../+0x&/g")) & 0xff ))) | |
| ((blockoffset+=128)) | |
| done | |
| } | |
| deleteextensionblock () { | |
| local blockoffset=$(($1*128)) | |
| if (( blockoffset*2 < ${#theedid} )); then | |
| (( debug )) && echo ": deleteextensionblock $blockoffset" 1>&2 | |
| replacebytes $blockoffset "" 128 | |
| replacebytes 126 $(printf "%02x" $((0x${theedid:252:2}-1))) | |
| processedid | |
| repairchecksums | |
| else | |
| echo "Block at $blockoffset doesn't exist" | |
| fi | |
| } | |
| deleteextensionblocktype () { | |
| local blocktype="$1" | |
| local blockoffset=128 | |
| local blocknum=1 | |
| while (( blockoffset*2 < ${#theedid} )); do | |
| local blocktag=${theedid:$blockoffset*2:2} | |
| (( debug )) && echo ": deleteextensionblocktype $blocknum) $blockoffset $blocktag" 1>&2 | |
| if (( 0x$blocktag == $blocktype )); then | |
| deleteextensionblock $blocknum | |
| else | |
| ((blockoffset+=128)) | |
| ((blocknum++)) | |
| fi | |
| done | |
| } | |
| adddisplayidextensionblock () { | |
| local version="$1" | |
| local producttype="$2" | |
| (( debug )) && echo ": adddisplayidextensionblock $version $producttype" 1>&2 | |
| #echo adddisplayidextensionblock $version $producttype | |
| replacebytes 126 $(printf "%02x" $((0x${theedid:252:2}+1))) | |
| theedid+=$(printf "70%02x79%02x%0248x" $((0x${version//./})) "$producttype" 0) | |
| repairchecksums | |
| } | |
| addctaextensionblock () { | |
| local version="$1" | |
| (( debug )) && echo ": addctaextensionblock $version" 1>&2 | |
| #echo addctaextensionblock $version $producttype | |
| replacebytes 126 $(printf "%02x" $((0x${theedid:252:2}+1))) | |
| theedid+=$(printf "02%02x0400%0248x" $((${version})) 0) | |
| repairchecksums | |
| } | |
| detailed_block () { | |
| local detailedblock=$1 | |
| # other parameters are optional | |
| local detailedblockoffset=$2 | |
| local willbereplaced=$3 | |
| [[ -z $willbereplaced ]] && willbereplaced=1 | |
| local dodump=$dodump | |
| if (( $# == 1 )); then | |
| dodump=1 | |
| fi | |
| if [[ ${detailedblock:0:4} = 0000 ]]; then | |
| case ${detailedblock} in | |
| 000000000000000000000000000000000000) (( dodump )) && printf "Empty" ;; | |
| 000000100000000000000000000000000000) (( dodump )) && printf "Dummy block" ;; | |
| *) | |
| case ${detailedblock:6:2} in | |
| 0*) (( dodump )) && printf "Manufacturer-specified data" ;; | |
| 10) (( dodump )) && printf "Dummy block" ;; | |
| f7) (( dodump )) && printf "Established timings III" ;; | |
| f8) (( dodump )) && printf "CVT 3-byte timing codes" ;; | |
| f9) (( dodump )) && printf "Color management data" ;; | |
| fa) (( dodump )) && printf "More standard timings" ;; | |
| fb) (( dodump )) && printf "Color point" ;; | |
| fc) (( dodump )) && printf "Monitor name" ;; | |
| fd) (( dodump )) && printf "Display range limits" ;; | |
| fe) (( dodump )) && printf "ASCII string" ;; | |
| ff) (( dodump )) && printf "Serial number" ;; | |
| *) (( dodump )) && printf "Reserved(${detailedblock:6:2})" ;; | |
| esac | |
| (( dodump )) && printf ": $detailedblock" | |
| esac | |
| case ${detailedblock:6:2} in | |
| fd) | |
| local minV=$((((0x${detailedblock:8:2} >> 0) & 1) * 255 + 0x${detailedblock:10:2})) | |
| local maxV=$((((0x${detailedblock:8:2} >> 1) & 1) * 255 + 0x${detailedblock:12:2})) | |
| local minH=$((((0x${detailedblock:8:2} >> 2) & 1) * 255 + 0x${detailedblock:14:2})) | |
| local maxH=$((((0x${detailedblock:8:2} >> 3) & 1) * 255 + 0x${detailedblock:16:2})) | |
| local maxP=$((0x${detailedblock:18:2} * 10)) | |
| local timingtype="?" | |
| case $((0x${detailedblock:20:2})) in | |
| 0) timingtype="default GTF" ;; | |
| 1) timingtype="limits only" ;; | |
| 2) timingtype="Secondary GTF" ;; | |
| 4) timingtype="CVT" ;; | |
| *) timingtype="Reserved($((0x${detailedblock:20:2})))" ;; | |
| esac | |
| (( dodump )) && printf " = %d-%dHz %d-%dkHz %dMHz (%s:%s)\n" $minV $maxV $minH $maxH $maxP $timingtype ${detailedblock:22} | |
| ;; | |
| *) (( dodump )) && echo | |
| ;; | |
| esac | |
| else | |
| local MHz=00$((0x${detailedblock:2:2}${detailedblock:0:2})) | |
| local hmm=$((0x${detailedblock:28:1}${detailedblock:24:2})) | |
| local vmm=$((0x${detailedblock:29:1}${detailedblock:26:2})) | |
| local hactive=$((0x${detailedblock:8:1}${detailedblock:4:2})) | |
| local hfront=$((((0x${detailedblock:22:1} >> 2) << 8) + 0x${detailedblock:16:2})) | |
| local hpulse=$((((0x${detailedblock:22:1} & 3) << 8) + 0x${detailedblock:18:2})) | |
| local hblank=$((0x${detailedblock:9:1}${detailedblock:6:2})) | |
| local vactive=$((0x${detailedblock:14:1}${detailedblock:10:2})) | |
| local vfront=$((((0x${detailedblock:23:1} >> 2) << 4) + 0x${detailedblock:20:1})) | |
| local vpulse=$((((0x${detailedblock:23:1} & 3) << 4) + 0x${detailedblock:21:1})) | |
| local vblank=$((0x${detailedblock:15:1}${detailedblock:12:2})) | |
| local hborder=$((0x${detailedblock:30:2})) | |
| local vborder=$((0x${detailedblock:32:2})) | |
| local hsync='-' | |
| local vsync='-' | |
| local interlaced='' | |
| local hback=$((hblank - hfront - hpulse)) | |
| local vback=$((vblank - vfront - vpulse)) | |
| (( 0x${detailedblock:34:2} >> 7 )) && interlaced='i' | |
| (( 0x${detailedblock:34:2} & 2 )) && hsync='+' | |
| (( 0x${detailedblock:34:2} & 4 )) && vsync='+' | |
| local kHz='000' | |
| local Hz='000' | |
| if (( hactive + hblank )); then | |
| kHz=000$(((10#$MHz * 100000 / (hactive+hblank) + 5)/10)) | |
| fi | |
| if (( (vactive+vblank)*(hactive+hblank) )); then | |
| Hz=000$(((10#$MHz * 100000000 / ((vactive+vblank)*(hactive+hblank)) + 5)/10)) | |
| fi | |
| local timingstring=$( | |
| printf "%dx%d%s@%d.%sHz %d.%skHz %d.%sMHz h(%d %d %d %s) v(%d %d %d %s)" \ | |
| $hactive $vactive "$interlaced" \ | |
| $((10#$Hz/1000)) ${Hz: -3} $((10#$kHz/1000)) ${kHz: -3} $((10#$MHz/100)) ${MHz: -2} \ | |
| $hfront $hpulse $hback "$hsync" \ | |
| $vfront $vpulse $vback "$vsync" | |
| ) | |
| (( debug )) && echo ":(( $willbereplaced == 0 ))" 1>&2 | |
| if (( willbereplaced == 0 )); then | |
| thetimings+=("$timingstring") | |
| thetimingsoffsets+=("$detailedblockoffset") | |
| thetimingshex+=("$detailedblock") | |
| fi | |
| (( dodump )) && printf "Detailed timing: %s = %s\n" "$detailedblock" "$timingstring" | |
| fi | |
| } | |
| one_detailed_block () { | |
| (( debug )) && echo ": one_detailed_block $@" 1>&2 | |
| local detailedblockoffset=$1 | |
| local willbereplaced=$2 | |
| (( dodump )) && echo -n " ${detailedblockoffset}) $detailedblockstring" | |
| detailed_block ${theedid:$1*2:36} $detailedblockoffset $willbereplaced | |
| } | |
| detailed_blocks () { | |
| (( debug )) && echo ": detailed_blocks $@" 1>&2 | |
| for param in detailedblockoffset lastdetailedblockoffset dodump doreplacedescriptoroffset newdescriptor; do | |
| (( debug )) && echo ": param $param=$1" 1>&2 | |
| eval "local $param=\"$1\""; shift | |
| done | |
| (( dodump )) && echo " Descriptors:" | |
| while (( detailedblockoffset <= lastdetailedblockoffset )); do | |
| (( debug )) && echo one_detailed_block $detailedblockoffset $(( detailedblockoffset == doreplacedescriptoroffset )) 1>&2 | |
| one_detailed_block $detailedblockoffset $(( detailedblockoffset == doreplacedescriptoroffset )) | |
| if (( detailedblockoffset == doreplacedescriptoroffset )); then | |
| replacebytes $detailedblockoffset $newdescriptor | |
| (( dodump )) && echo " changed:" | |
| one_detailed_block $detailedblockoffset 0 | |
| fi | |
| ((detailedblockoffset+=18)) | |
| done | |
| } | |
| colorc () { | |
| local ccc=$1 | |
| # [0..1023] | |
| # [0..9990] | |
| # [10005..19995] add leading zeros (ignore the 1) and rounding | |
| local rx=$(( | |
| ( | |
| ( | |
| (0x${theedid:54+ccc*2:2} << 2) | ((0x${theedid:50:4} >> (14-ccc*2)) & 3) | |
| ) * 10000 / 1024 | |
| ) + 10005 | |
| )); | |
| echo -n 0.${rx:1:3} # output 3 digits (ignoring the leading 1) | |
| } | |
| dumptype1timingdescriptor () { | |
| local type1timing=$1 | |
| local MHz=00$((0x${type1timing:4:2}${type1timing:2:2}${type1timing:0:2} + 1)) | |
| local preferred='' | |
| (( 0x${type1timing:6:2} >> 7 )) && preferred=' preferred' | |
| local support3D=' 3D:undefined' | |
| case $(( (0x${type1timing:6:2} >> 5) & 3 )) in | |
| 0) support3D='' ;; | |
| 1) support3D=' stereo' ;; | |
| 2) support3D=' stereo/mono' ;; | |
| 3) support3D=' 3D:reserved' ;; | |
| esac | |
| local interlaced='' | |
| (( (0x${type1timing:6:2} >> 4) & 1 )) && interlaced='i' | |
| local aspect=' aspect:reserved' | |
| case $(( 0x${type1timing:6:2} & 15 )) in | |
| 0) aspect=' 1:1' ;; | |
| 1) aspect=' 5:4' ;; | |
| 2) aspect=' 4:3' ;; | |
| 3) aspect=' 15:9' ;; | |
| 4) aspect=' 16:9' ;; | |
| 5) aspect=' 16:10' ;; | |
| 6) aspect=' 64:27' ;; | |
| 7) aspect=' 256:135' ;; | |
| 8) aspect=' aspect:undefined' ;; | |
| esac | |
| local hactive=$((0x${type1timing:10:2}${type1timing:8:2} + 1)) | |
| local hblank=$((0x${type1timing:14:2}${type1timing:12:2} + 1)) | |
| local hfront=$(((0x${type1timing:18:2}${type1timing:16:2} & 32767) + 1)) | |
| local hsync='-' | |
| (( 0x${type1timing:18:2} >> 7 )) && hsync='+' | |
| local hpulse=$((0x${type1timing:22:2}${type1timing:20:2} + 1)) | |
| local hback=$((hblank - hfront - hpulse)) | |
| local vactive=$((0x${type1timing:26:2}${type1timing:24:2} + 1)) | |
| local vblank=$((0x${type1timing:30:2}${type1timing:28:2} + 1)) | |
| local vfront=$(((0x${type1timing:34:2}${type1timing:32:2} & 32767) + 1)) | |
| local vsync='-' | |
| (( 0x${type1timing:34:2} >> 7 )) && vsync='+' | |
| local vpulse=$((0x${type1timing:38:2}${type1timing:36:2} + 1)) | |
| local vback=$((vblank - vfront - vpulse)) | |
| local kHz=000$(((10#$MHz * 100000 / (hactive+hblank) + 5)/10)) | |
| local Hz=000$(((10#$MHz * 100000000 / ((vactive+vblank)*(hactive+hblank)) + 5)/10)) | |
| printf "%dx%d%s@%d.%sHz %d.%skHz %d.%sMHz h(%d %d %d %s) v(%d %d %d %s)%s%s%s" \ | |
| $hactive $vactive "$interlaced" \ | |
| $((10#$Hz/1000)) ${Hz: -3} $((10#$kHz/1000)) ${kHz: -3} $((10#$MHz/100)) ${MHz: -2} \ | |
| $hfront $hpulse $hback "$hsync" \ | |
| $vfront $vpulse $vback "$vsync" \ | |
| "$support3D" "$aspect" "$preferred" | |
| } | |
| dumptileddisplaytopologyblock () { | |
| local tiledblock=$1 | |
| local revision=$((0x${tiledblock:2:2} & 7)) # high 5 bits should be 0 | |
| local length=$((0x${tiledblock:4:2})) # should be 22 | |
| local capabilities=$((0x${tiledblock:6:2})) | |
| local enclosuretype=$(( (capabilities >> 7) & 1 )) | |
| local tilebezelinformationdescriptoravailable=$(( (capabilities >> 6) & 1 )) | |
| # bit 5 reserved | |
| local behaviorsome=$(( (capabilities >> 3) & 3 )) | |
| local behavioronly=$(( (capabilities >> 0) & 7 )) | |
| tileCountH=$(( 0x${tiledblock:8:1} + ((0x${tiledblock:12:2} >> 2) & 0x30) + 1 )) | |
| tileCountV=$(( 0x${tiledblock:9:1} + ((0x${tiledblock:12:2} >> 0) & 0x30) + 1 )) | |
| local tileLocationH=$(( 0x${tiledblock:10:1} + ((0x${tiledblock:12:2} << 2) & 0x30) )) | |
| local tileLocationV=$(( 0x${tiledblock:11:1} + ((0x${tiledblock:12:2} << 4) & 0x30) )) | |
| tileSizeH=$(( 0x${tiledblock:16:2}${tiledblock:14:2} + 1 )) | |
| tileSizeV=$(( 0x${tiledblock:20:2}${tiledblock:18:2} + 1 )) | |
| # depends on tilebezelinformationdescriptoravailable | |
| local pixelmultiplier=$(( 0x${tiledblock:22:2} )) | |
| local bezelsizetop=$(( 0x${tiledblock:24:2} )) | |
| local bezelsizebottom=$(( 0x${tiledblock:26:2} )) | |
| local bezelsizeleft=$(( 0x${tiledblock:28:2} )) | |
| local bezelsizeright=$(( 0x${tiledblock:30:2} )) | |
| local vendorid=$(echo -n ${tiledblock:32:6} | xxd -p -r) | |
| local productid=$((0x${tiledblock:40:2}${tiledblock:38:2})) | |
| local serialnumber=$((0x${tiledblock:48:2}${tiledblock:46:2}${tiledblock:44:2}${tiledblock:42:2})) | |
| if (( dodump )); then | |
| printf "%dx%d @ (%d,%d) of (%dx%d) vendor:%s product:0x%04x serial:%d" \ | |
| $tileSizeH $tileSizeV \ | |
| $tileLocationH $tileLocationV \ | |
| $tileCountH $tileCountV \ | |
| $vendorid $productid $serialnumber | |
| printf ' enclosure:' | |
| case $enclosuretype in | |
| 0) printf 'multiple' ;; | |
| 1) printf 'single' ;; | |
| esac | |
| printf ' some:' | |
| case $behaviorsome in | |
| 0) printf 'undescribed' ;; | |
| 1) printf 'location' ;; | |
| *) printf 'reserved'$behaviorsome ;; | |
| esac | |
| printf ' one:' | |
| case $behavioronly in | |
| 0) printf 'undescribed' ;; | |
| 1) printf 'location' ;; | |
| 2) printf 'scaled' ;; | |
| 3) printf 'cloned' ;; | |
| *) printf 'reserved'$behavioronly ;; | |
| esac | |
| if (( tilebezelinformationdescriptoravailable )); then | |
| printf " bezel(t,l,b,r):(%d,%d,%d,%d)x%d" \ | |
| $bezelsizetop \ | |
| $bezelsizeleft \ | |
| $bezelsizebottom \ | |
| $bezelsizeright \ | |
| $pixelmultiplier | |
| fi | |
| fi | |
| } | |
| flagsstring () { | |
| local label=$1 | |
| local bitfirst=$2 | |
| local bitlast=$3 | |
| local bitdirection=1 | |
| local theflags="$4" | |
| local result="" | |
| if [[ -z $theflags ]]; then | |
| result="missing" | |
| else | |
| shift 4 | |
| theflags=$((0x$theflags)) | |
| (( bitlast < bitfirst )) && bitdirection=-1 | |
| result="" | |
| local thebit=$bitfirst | |
| while (( 1 )); do | |
| if (( theflags & (2 ** thebit) )); then | |
| [[ -n $result ]] && result+="," | |
| [[ -z $1 ]] && result+="reserved$thebit" || result+="$1" | |
| fi | |
| (( $# )) && shift | |
| (( thebit == bitlast )) && break | |
| (( thebit = thebit + bitdirection )) | |
| done | |
| fi | |
| [[ -n $result ]] && echo -n " $label:$result" | |
| } | |
| dumpdisplayinterfacefeaturesdata () { | |
| local theblock=$1 | |
| local revision=$((0x${theblock:2:2} & 7)) # high 5 bits should be 0 | |
| local length=$((0x${theblock:4:2})) # should be 9+N | |
| local colorDepthsRGB=$(flagsstring "RGB" 0 7 ${theblock:6:2} 6 8 10 12 14 16) | |
| local colorDepthsYCbCr444=$(flagsstring "444" 0 7 ${theblock:8:2} 6 8 10 12 14 16) | |
| local colorDepthsYCbCr422=$(flagsstring "422" 0 7 ${theblock:10:2} 8 10 12 14 16) | |
| local colorDepthsYCbCr420=$(flagsstring "420" 0 7 ${theblock:12:2} 8 10 12 14 16) | |
| local minRateYCbCr420string=" 420(MHz):missing" | |
| if [[ -n ${theblock:14:2} ]]; then | |
| local minRateYCbCr420=$((0x${theblock:14:2} * 7425)) # / 100 | |
| (( minRateYCbCr420 > 0 )) && minRateYCbCr420string=" 420:≥$(( minRateYCbCr420/100 )).${minRateYCbCr420: -2}MHz" || minRateYCbCr420string="" | |
| fi | |
| local audio=$(flagsstring "audio(kHz)" 7 0 ${theblock:16:2} "32" "44.1" "48") | |
| local colorspace_eotf_combinations1=$(flagsstring "colorspace/eotf#1" 0 7 ${theblock:18:2} \ | |
| "sRGB" \ | |
| "BT.601" \ | |
| "BT.709/BT.1886" \ | |
| "Adobe RGB" \ | |
| "DCI-P3" \ | |
| "BT.2020" \ | |
| "BT.2020/SMPTE ST 2084") | |
| local colorspace_eotf_combinations2=$(flagsstring "colorspace/eotf#2" 0 7 ${theblock:20:2}) | |
| local additional_colorspace_eotf="missing" | |
| if [[ -n ${theblock:22:2} ]]; then | |
| local num_additional_colorspace_eotf=$((0x${theblock:22:2} & 7)) | |
| additional_colorspace_eotf="" | |
| local cendx="" | |
| for (( cendx = 0; cendx < num_additional_colorspace_eotf; cendx++ )); do | |
| (( cendx > 0 )) && additional_colorspace_eotf+="," | |
| local colorspace="" | |
| local eotf="" | |
| if [[ -z ${theblock:24+$cendx*2:2} ]]; then | |
| additional_colorspace_eotf+="missing" | |
| else | |
| case $((0x${theblock:24+$cendx*2:2} >> 4)) in | |
| 0) colorspace="Undefined" ;; | |
| 1) colorspace="sRGB" ;; | |
| 2) colorspace="BT.601" ;; | |
| 3) colorspace="BT.709" ;; | |
| 4) colorspace="Adobe RGB" ;; | |
| 5) colorspace="DCI-P3" ;; | |
| 6) colorspace="BT.2020" ;; | |
| 7) colorspace="Custom" ;; | |
| *) colorspace="Reserved$((0x${theblock:24+$cendx*2:2} >> 4))" ;; | |
| esac | |
| case $((0x${theblock:24+$cendx*2:2} & 15)) in | |
| 0) eotf="Undefined" ;; | |
| 1) eotf="sRGB" ;; | |
| 2) eotf="BT.601" ;; | |
| 3) eotf="BT.1886" ;; | |
| 4) eotf="Adobe RGB" ;; | |
| 5) eotf="DCI-P3" ;; | |
| 6) eotf="BT.2020" ;; | |
| 7) eotf="Gamma function" ;; | |
| 8) eotf="SMPTE ST 2084" ;; | |
| 9) eotf="Hybrid Log" ;; | |
| 10) eotf="Custom" ;; | |
| *) eotf="Reserved$((0x${theblock:24+$cendx*2:2} & 15))" ;; | |
| esac | |
| [[ $colorspace = $eotf ]] && additional_colorspace_eotf+="$colorspace" || additional_colorspace_eotf+="$colorspace/$eotf" | |
| fi | |
| done | |
| fi | |
| local lengtherror="" | |
| (( length != 9 + num_additional_colorspace_eotf )) && lengtherror+=" ($length != $((9 + num_additional_colorspace_eotf)))" | |
| [[ -n $additional_colorspace_eotf ]] && additional_colorspace_eotf=" colorspace/eotf#n:$additional_colorspace_eotf" | |
| if (( dodump )); then | |
| printf "revision:%d%s%s%s%s%s%s%s%s%s%s" \ | |
| $revision \ | |
| $colorDepthsRGB \ | |
| $colorDepthsYCbCr444 \ | |
| $colorDepthsYCbCr422 \ | |
| $colorDepthsYCbCr420 \ | |
| $minRateYCbCr420string \ | |
| $audio \ | |
| $colorspace_eotf_combinations1 \ | |
| $colorspace_eotf_combinations2 \ | |
| $additional_colorspace_eotf \ | |
| $lengtherror | |
| fi | |
| } | |
| processedid () { | |
| (( debug )) && echo ": processedid $@" 1>&2 | |
| local ctablocktypetodelete=9999 | |
| local displayidblocktypetodelete=9999 | |
| local newdisplayidblocks=() | |
| local currentnewdisplayidblock=0 | |
| local countAdded=0 | |
| while (( $# )); do | |
| local param="$1"; shift | |
| eval "local $param=1" | |
| case "$param" in | |
| doreplacedescriptor) | |
| local doreplacedescriptoroffset="$1"; shift | |
| local newdescriptor="$1"; shift ;; | |
| doadddisplayidblock) | |
| local doadddisplayidblockoffset="$1"; shift | |
| if (( doadddisplayidblockoffset == 0 )); then | |
| doadddisplayidblockoffset=$EndOfLastDisplayIDblock | |
| fi | |
| (( debug )) && echo ": doadddisplayidblock $doadddisplayidblockoffset" 1>&2 | |
| while (( $# )); do | |
| (( debug )) && echo ": doadddisplayidblock param: $1" 1>&2 | |
| newdisplayidblocks+=("$1"); shift | |
| done | |
| ;; | |
| dodeletedisplayidblock) | |
| local deletedisplayidblockoffset="$1"; shift ;; | |
| dodeletedisplayidblockblocktype) | |
| local displayidblocktypetodelete="$1"; shift ;; | |
| dodeletectablocktype) | |
| ctablocktypetodelete="$1"; shift ;; | |
| dosetpreferredisnative) | |
| preferredvalue="$1"; shift ;; | |
| esac | |
| done | |
| # Keep a list of found DisplayID blocks. | |
| DisplayIDblocks=() | |
| # Keep a list of found timings and their offsets. | |
| thetimings=() | |
| thetimingsoffsets=() | |
| thetimingshex=() | |
| HasAudio=0 | |
| HasTile=0 | |
| # Remember the end of the last DisplayID block (-1 means no DisplayID blocks exist. | |
| # It is also used by adddisplayidblock for inserting new DisplayID blocks at the first available location. | |
| EndOfLastDisplayIDblock=-1 | |
| (( debug )) && echo ": parse 1" 1>&2 | |
| theproductid=$((0x${theedid:22:2}${theedid:20:2})) | |
| thevendorid=$((0x${theedid:16:4})) | |
| (( debug )) && echo ": parse 2" 1>&2 | |
| thevendorcode="$(printf "%06x" $((((thevendorid&0x7c00)<<6)+((thevendorid&0x3e0)<<3)+(thevendorid&0x1f)+0x404040)) | xxd -p -r)" | |
| (( debug )) && echo ": parse 2.1" 1>&2 | |
| theweekofmanufacture=$((0x${theedid:32:2})) | |
| (( debug )) && echo ": parse 2.2" 1>&2 | |
| theyearofmanufacture=$((0x${theedid:34:2}+1990)) | |
| (( debug )) && echo ": parse 3" 1>&2 | |
| thevendordir="DisplayVendorID-$(printf "%x" $thevendorid)" | |
| theproductfile="${thevendordir}/DisplayProductID-$(printf "%x" $theproductid)" | |
| themanufacturefile="${thevendordir}/DisplayYearManufacture-$theyearofmanufacture-DisplayWeekManufacture-$theweekofmanufacture" | |
| thefilenamebase="$(printf "EDID_%s_%x_%x" "$thevendorcode" $thevendorid $theproductid)" | |
| (( debug )) && echo ": parse 4" 1>&2 | |
| theEDIDversion=$((0x${theedid:0x12*2:2})).$((0x${theedid:0x13*2:2})) # 1.3 or 1.4 | |
| isdigital=$(( 0x${theedid:0x14*2:1} > 7 )) # 0 or 1 | |
| featuresupportbyte=$((0x${theedid:0x18*2:2})) | |
| featuresupport=$(( (featuresupportbyte >> 3) & 3 )) | |
| preferredisnative=$(( (featuresupportbyte >> 1) & 1 )) | |
| iscontinuous=$(( (featuresupportbyte >> 0) & 1 )) | |
| (( dodump )) && echo "0) EDID $theEDIDversion" | |
| (( dodump )) && printf " Vendor ID: %s %d = 0x%x\n" $thevendorcode $thevendorid $thevendorid | |
| (( dodump )) && printf " Product ID: %d = 0x%x\n" $theproductid $theproductid | |
| if [[ ${theedid:24:8} != 00000000 ]]; then | |
| (( dodump )) && printf " Serial Number: %d" $((0x${theedid:30:2}${theedid:28:2}${theedid:26:2}${theedid:24:2})) | |
| if (( doclearserialnumber )); then | |
| replacebytes 12 00000000 | |
| (( dodump )) && printf " changed: unspecified" | |
| fi | |
| (( dodump )) && echo | |
| fi | |
| if [[ ${theedid:32:4} != 0000 ]]; then | |
| if (( dodump )); then | |
| case ${theedid:32:4} in | |
| 00*) printf " Made in year $theyearofmanufacture" ;; | |
| ff*) printf " Model year: $theyearofmanufacture" ;; | |
| *) printf " Made in week $theweekofmanufacture of $theyearofmanufacture" | |
| esac | |
| fi | |
| if (( docleardate )); then | |
| replacebytes 16 0000 | |
| (( dodump )) && printf " changed: unspecified" | |
| fi | |
| (( dodump )) && echo | |
| fi | |
| (( dodump )) && printf " " | |
| if [[ $theEDIDversion < "1.4" ]]; then | |
| (( dodump )) && | |
| case $featuresupport in | |
| 0) echo "Monochrome or Grayscale" ;; | |
| 1) echo "RGB color" ;; | |
| 2) echo "Non-RGB color" ;; | |
| 3) echo "Undefined" ;; | |
| esac | |
| else | |
| (( dodump )) && | |
| case $isdigital$featuresupport in | |
| 00) echo "Monochrome or Grayscale" ;; | |
| 01) echo "RGB color" ;; | |
| 02) echo "Non-RGB color" ;; | |
| 03) echo "Undefined" ;; | |
| 10) echo "RGB 4:4:4" ;; | |
| 11) echo "RGB 4:4:4 + YCrCb 4:4:4" ;; | |
| 12) echo "RGB 4:4:4 + YCrCb 4:2:2" ;; | |
| 13) echo "RGB 4:4:4 + YCrCb 4:4:4 + YCrCb 4:2:2" ;; | |
| esac | |
| if (( do444 )); then | |
| local newfeaturesupportbyte=$(((featuresupportbyte & ~0x18) | (0 << 3) )) | |
| replacebytes 24 $(printf "%02x" $newfeaturesupportbyte) | |
| (( dodump )) && | |
| case $isdigital in | |
| 0) echo ' changed: Monochrome or Grayscale' ;; | |
| 1) echo ' changed: RGB 4:4:4' ;; | |
| esac | |
| fi | |
| if (( do422 )); then | |
| local newfeaturesupportbyte=$(((featuresupportbyte & ~0x18) | (3 << 3) )) | |
| replacebytes 24 $(printf "%02x" $newfeaturesupportbyte) | |
| (( dodump )) && | |
| case $isdigital in | |
| 0) echo ' changed: Undefined' ;; | |
| 1) echo ' changed: RGB 4:4:4 + YCrCb 4:4:4 + YCrCb 4:2:2' ;; | |
| esac | |
| fi | |
| fi | |
| (( dodump )) && | |
| case $preferredisnative in | |
| 1) echo " Preferred Timing Mode is native pixel format and preferred refresh rate" ;; | |
| esac | |
| if (( dosetpreferredisnative && (preferredvalue != preferredisnative) )); then | |
| local newfeaturesupportbyte=$(((featuresupportbyte & ~2) | (preferredvalue << 1) )) | |
| replacebytes 24 $(printf "%02x" $newfeaturesupportbyte) | |
| (( dodump )) && echo ' changed' | |
| fi | |
| (( dodump )) && | |
| case $iscontinuous in | |
| 0) echo " Display is non-continuous frequency" ;; | |
| 1) echo " Display is continuous frequency" ;; | |
| esac | |
| if [[ ${theedid:50:20} != 00000000000000000000 ]]; then | |
| (( dodump )) && printf " Color characteristics: R(%s,%s) G(%s,%s) B(%s,%s) W(%s,%s)" $(colorc 0) $(colorc 1) $(colorc 2) $(colorc 3) $(colorc 4) $(colorc 5) $(colorc 6) $(colorc 7) | |
| if (( doclearchromaticity )); then | |
| replacebytes 25 00000000000000000000 | |
| (( dodump )) && printf " changed: unspecified" | |
| fi | |
| (( dodump )) && echo | |
| fi | |
| if [[ ${theedid:70:4} != 0000 ]]; then | |
| (( dodump )) && printf " Established timings: %s" ${theedid:70:4} | |
| if (( doclearestablishedtimings )); then | |
| replacebytes 35 0000 | |
| (( dodump )) && printf " changed: unspecified" | |
| fi | |
| (( dodump )) && echo | |
| fi | |
| if [[ ${theedid:74:2} != 00 ]]; then | |
| (( dodump )) && printf " Manufacturer's timings: %s" ${theedid:74:2} | |
| if (( doclearmanufacturerstimings )); then | |
| replacebytes 37 00 | |
| (( dodump )) && printf " changed: unspecified" | |
| fi | |
| (( dodump )) && echo | |
| fi | |
| if [[ ${theedid:76:32} != 01010101010101010101010101010101 ]]; then | |
| (( dodump )) && printf " Standard timings: %s" ${theedid:76:32} | |
| if (( doclearstandardtimings )); then | |
| replacebytes 38 01010101010101010101010101010101 | |
| (( dodump )) && printf " changed: unspecified" | |
| fi | |
| (( dodump )) && echo | |
| fi | |
| detailed_blocks 54 108 "$dodump" "$doreplacedescriptoroffset" "$newdescriptor" | |
| local blockoffset=128 | |
| while (( blockoffset*2 < ${#theedid} )); do | |
| (( debug )) && echo ": extension block $blockoffset" 1>&2 | |
| local theblock=${theedid:$blockoffset*2:254} | |
| (( debug )) && echo ": extension theblock = $theblock" 1>&2 | |
| local blocktag=${theblock:0:2} | |
| (( debug )) && echo ": blocktag = $blocktag" 1>&2 | |
| if [[ $blocktag = 02 ]]; then | |
| CTAversion=$((0x${theblock:2:2})) | |
| (( debug )) && echo ": CTAversion = $CTAversion" 1>&2 | |
| detailedTimingDescriptorsOffset=$((0x${theblock:4:2})) | |
| if (( CTAversion > 1 )); then | |
| (( dodump )) && echo "$blockoffset) CTA-861 extension block with new version $CTAversion" | |
| YCbCrSupportbyte=$((0x${theblock:6:2})) | |
| YCbCrSupport=$((( YCbCrSupportbyte >> 4) & 3)) | |
| (( dodump )) && | |
| case $YCbCrSupport in | |
| 0) echo " No YCbCr support" ;; | |
| 1) echo " YCbCr 4:2:2" ;; | |
| 2) echo " YCbCr 4:4:4" ;; | |
| 3) echo " YCbCr 4:4:4, YCbCr 4:2:2" ;; | |
| esac | |
| if (( do444 )); then | |
| newYCbCrSupportbyte=$(((YCbCrSupportbyte & ~0x30) | (0 << 4) )) | |
| replacebytes $((blockoffset+3)) $(printf "%02x" $newYCbCrSupportbyte) | |
| (( dodump )) && echo ' changed: No YCbCr support' | |
| fi | |
| if (( do422 )); then | |
| newYCbCrSupportbyte=$(((YCbCrSupportbyte & ~0x30) | (3 << 4) )) | |
| replacebytes $((blockoffset+3)) $(printf "%02x" $newYCbCrSupportbyte) | |
| (( dodump )) && echo ' changed: YCbCr 4:4:4, YCbCr 4:2:2' | |
| fi | |
| BasicAudioSupport=$((( YCbCrSupportbyte >> 6) & 1)) | |
| (( dodump )) && | |
| case $BasicAudioSupport in | |
| 1) echo " Basic audio support" ;; | |
| esac | |
| if (( CTAversion > 2 )); then | |
| CTAdatablockoffset=4 | |
| (( dodump && (CTAdatablockoffset < detailedTimingDescriptorsOffset) )) && echo " CTA data blocks:" | |
| while (( CTAdatablockoffset < detailedTimingDescriptorsOffset )); do | |
| CTAdatablocklength=$(((0x${theblock:$CTAdatablockoffset*2:2} & 0x1f) + 1)) | |
| CTAdatablock=${theblock:$CTAdatablockoffset*2:$CTAdatablocklength * 2} | |
| CTAtagcode=$((0x${CTAdatablock:0:2} >> 5)) | |
| if (( CTAtagcode == 7 )); then | |
| CTAtagcode=$((0x${CTAdatablock:2:2} + 700)) | |
| fi | |
| if (( dodump )); then | |
| echo -n " $((blockoffset + CTAdatablockoffset))) " | |
| case $CTAtagcode in | |
| 0) printf "Reserved" ;; | |
| 1) printf "Audio" ;; | |
| 2) printf "Video" ;; | |
| 3) printf "Vendor-specific" ;; | |
| 4) printf "Speaker allocation" ;; | |
| 5) printf "Display transfer characteristic" ;; | |
| 6) printf "Reserved" ;; | |
| 700) printf "Video capability" ;; | |
| 701) printf "Vendor-specific video" ;; | |
| 702) printf "VESA display device" ;; | |
| 703) printf "VESA video timing block" ;; | |
| 704) printf "Reserved for HDMI video" ;; | |
| 705) printf "Colorimetry" ;; | |
| 706) printf "HDR static metadata" ;; | |
| 707) printf "HDR dynamic metadata" ;; | |
| 713) printf "Video format preference" ;; | |
| 714) printf "YCbCr 4:2:0 video" ;; | |
| 715) printf "YCbCr 4:2:0 capability map" ;; | |
| 716) printf "Reserved for CTA miscellaneous audio fields" ;; | |
| 717) printf "Vendor-specific audio" ;; | |
| 718) printf "Reserved for HDMI audio" ;; | |
| 719) printf "Room configuration" ;; | |
| 720) printf "Speaker location" ;; | |
| 732) printf "InfoFrames" ;; | |
| *) | |
| if (( CTAtagcode <= 712 )); then | |
| printf "Reserved for video-related" | |
| elif (( CTAtagcode <= 731 )); then | |
| printf "Reserved for audio-related" | |
| else | |
| printf "Reserved" | |
| fi | |
| printf " (e${CTAtagcode: -2})" | |
| ;; | |
| esac | |
| echo -n ": $CTAdatablock" | |
| fi | |
| case $CTAtagcode in | |
| 1) | |
| (( dodump )) && echo | |
| HasAudio=1 | |
| ;; | |
| 3) | |
| IEEEOUI=${CTAdatablock:6:2}${CTAdatablock:4:2}${CTAdatablock:2:2} | |
| (( dodump )) && echo -n " = $IEEEOUI:" | |
| case $IEEEOUI in | |
| 000c03) | |
| (( dodump )) && echo " HDMI Licensing, LLC -> H14b VSDB" | |
| H14bByte=${CTAdatablock:12:2} | |
| if [[ -n $H14bByte ]]; then | |
| H14bByte=$((0x${H14bByte})) | |
| H14b=$((( H14bByte >> 3) & 1)) | |
| (( dodump )) && | |
| case $H14b in | |
| 0) echo " Support YCbCr 4:4:4 - No" ;; | |
| 1) echo " Support YCbCr 4:4:4 - Yes" ;; | |
| esac | |
| if (( do444 )); then | |
| newH14bByte=$(((H14bByte & ~0x08) | (0 << 3) )) # 0: No (RGB only), 1: Yes (YCbCr 4:4:4) | |
| replacebytes $((blockoffset+CTAdatablockoffset+6)) $(printf "%02x" $newH14bByte) | |
| (( dodump )) && echo " changed: Support YCbCr 4:4:4 - No" | |
| fi | |
| if (( do422 )); then | |
| newH14bByte=$(((H14bByte & ~0x08) | (1 << 3) )) # 0: No (RGB only), 1: Yes (YCbCr 4:4:4) | |
| replacebytes $((blockoffset+CTAdatablockoffset+6)) $(printf "%02x" $newH14bByte) | |
| (( dodump )) && echo " changed: Support YCbCr 4:4:4 - Yes" | |
| fi | |
| fi | |
| ;; | |
| c45dd8) | |
| (( dodump )) && echo " HDMI Forum -> HF-VSDB" | |
| HFByte=$((0x${CTAdatablock:14:2})) | |
| HF=$((( HFByte >> 0) & 7)) | |
| (( dodump )) && | |
| case $HF in | |
| 0) echo " 4:2:0 10/12/16 bpc - No" ;; | |
| 1) echo " 4:2:0 10 bpc - Yes" ;; | |
| 2) echo " 4:2:0 12 bpc - Yes" ;; | |
| 3) echo " 4:2:0 10/12 bpc - Yes" ;; | |
| 4) echo " 4:2:0 16 bpc - Yes" ;; | |
| 5) echo " 4:2:0 10/16 bpc - Yes" ;; | |
| 6) echo " 4:2:0 12/16 bpc - Yes" ;; | |
| 7) echo " 4:2:0 10/12/16 bpc - Yes" ;; | |
| esac | |
| if (( do444 )); then | |
| newHFByte=$(((HFByte & ~0x07) | (0 << 0) )) | |
| replacebytes $((blockoffset+CTAdatablockoffset+7)) $(printf "%02x" $newHFByte) | |
| (( dodump )) && echo " changed: 4:2:0 10/12/16 bpc - No" | |
| fi | |
| if (( do422 )); then | |
| newHFByte=$(((HFByte & ~0x07) | (7 << 0) )) | |
| replacebytes $((blockoffset+CTAdatablockoffset+7)) $(printf "%02x" $newHFByte) | |
| (( dodump )) && echo " changed: 4:2:0 10/12/16 bpc - Yes" | |
| fi | |
| ;; | |
| *) | |
| (( dodump )) && echo " Unknown OUI" | |
| ;; | |
| esac | |
| ;; | |
| *) | |
| (( dodump )) && echo | |
| ;; | |
| esac | |
| if (( CTAtagcode == ctablocktypetodelete )); then | |
| ((detailedTimingDescriptorsOffset-=CTAdatablocklength)) | |
| replacebytes $((blockoffset+2)) $(printf "%02x" $detailedTimingDescriptorsOffset) | |
| local nextctablockoffset=$((CTAdatablockoffset+CTAdatablocklength)) | |
| replacebytes $((blockoffset+CTAdatablockoffset)) ${theedid:(blockoffset+nextctablockoffset)*2:(127-nextctablockoffset)*2}${CTAdatablock//?/0} | |
| theblock=${theedid:$blockoffset*2:254} | |
| CTAdatablocklength=0 | |
| (( dodump )) && echo " changed: deleted" | |
| fi | |
| ((CTAdatablockoffset+=CTAdatablocklength)) | |
| done | |
| fi | |
| else | |
| (( dodump )) && echo "$blockoffset) CTA-861 extension block with old version $CTAversion" | |
| fi | |
| detailed_blocks $((blockoffset+detailedTimingDescriptorsOffset)) $((blockoffset + 127 - 18)) "$dodump" "$doreplacedescriptoroffset" "$newdescriptor" | |
| elif [[ $blocktag = 70 ]]; then | |
| (( debug )) && echo ": parsing DisplayID extension block" 1>&2 | |
| # 00 edid tag 70 | |
| # | |
| # 01 1 DisplayID version | |
| # 02 2 DisplayID len 0x79 | |
| # 03 3 DisplayID product type | |
| # 04 4 DisplayID nextcount | |
| # | |
| # 05 00 DisplayID block #1 | |
| # 06 01 DisplayID block #1 revision | |
| # 07 02 DisplayID block #1 length | |
| # | |
| # 7E 5 displayID checksum | |
| # | |
| # 7F EDID checksum | |
| DisplayIDversion=${theblock:2:1}.${theblock:3:1} | |
| # maximum length of DisplayID section in an EDID extension block is 121 = 0x79 bytes. | |
| # this is the length of all the DisplayID blocks | |
| DisplayIDlength=$((0x${theblock:4:2})) | |
| DisplayIDproducttype=$((0x${theblock:6:2})) | |
| DisplayIDextcount=$((0x${theblock:8:2})) | |
| DisplayIDblockoffset=5 | |
| if (( doadddisplayidblockoffset == -1 )); then | |
| doadddisplayidblockoffset=$((blockoffset + DisplayIDblockoffset)) | |
| fi | |
| local newdisplayidblock="" | |
| local newDisplayIDblocklength=0 | |
| local lastDisplayIDblockoffset=0 | |
| while (( DisplayIDblockoffset < DisplayIDlength + 5 )); do | |
| if (( blockoffset + DisplayIDblockoffset == doadddisplayidblockoffset )); then | |
| (( debug )) && echo ": found $doadddisplayidblockoffset" 1>&2 | |
| # concatenate all the new blocks that will fit | |
| while (( currentnewdisplayidblock < ${#newdisplayidblocks[@]} )); do | |
| local onedisplayidblock=${newdisplayidblocks[currentnewdisplayidblock+arrstart]} | |
| (( debug )) && echo ": testing $onedisplayidblock" 1>&2 | |
| if (( ${#onedisplayidblock}/2 > 0x79 )); then | |
| echo "Error: DisplayID block is too large: ${onedisplayidblock}" 1>&2 | |
| else | |
| if (( (${#newdisplayidblock} + ${#onedisplayidblock})/2 > 0x7e - DisplayIDblockoffset )); then | |
| (( debug )) && echo ": overflow" 1>&2 | |
| break | |
| fi | |
| newdisplayidblock+="${onedisplayidblock}" | |
| ((countAdded++)) | |
| (( debug )) && echo ": $countAdded: concetenate result: $newdisplayidblock" 1>&2 | |
| fi | |
| ((currentnewdisplayidblock++)) | |
| done | |
| newdisplayidblocks=("${newdisplayidblocks[@]:$currentnewdisplayidblock}") | |
| currentnewdisplayidblock=0 | |
| newDisplayIDblocklength=$((${#newdisplayidblock}/2)) | |
| if (( countAdded == 0 )); then | |
| # none fit here, try in the next block | |
| doadddisplayidblockoffset=-1 | |
| fi | |
| fi | |
| # stop when the remaining bytes are 0 | |
| local DisplayIDremains=${theblock:$DisplayIDblockoffset * 2:($DisplayIDlength + 5 - $DisplayIDblockoffset)*2} | |
| if [ -z ${DisplayIDremains//0/} ]; then | |
| break | |
| fi | |
| DisplayIDblocklength=$((0x${theblock:$DisplayIDblockoffset * 2 + 4:2} + 3)) | |
| # blocks that won't fit are inserted into a list that will be added later | |
| if (( DisplayIDblockoffset + DisplayIDblocklength + newDisplayIDblocklength > 0x7e )); then | |
| DisplayIDblock=${theblock:$DisplayIDblockoffset*2:$DisplayIDblocklength * 2} | |
| newdisplayidblocks+=("$DisplayIDblock") | |
| if (( lastDisplayIDblockoffset == 0)); then | |
| lastDisplayIDblockoffset=$DisplayIDblockoffset | |
| fi | |
| fi | |
| ((DisplayIDblockoffset+=DisplayIDblocklength)) | |
| done | |
| if (( lastDisplayIDblockoffset == 0)); then | |
| lastDisplayIDblockoffset=$DisplayIDblockoffset | |
| fi | |
| (( dodump )) && printf "%s) DisplayID extension block: version %s, type %d\n" $blockoffset $DisplayIDversion $DisplayIDproducttype | |
| DisplayIDblockoffset=5 | |
| while (( DisplayIDblockoffset < DisplayIDlength + 5 )); do | |
| if (( blockoffset + DisplayIDblockoffset == doadddisplayidblockoffset )); then | |
| replacebytes $((doadddisplayidblockoffset)) "$newdisplayidblock${theblock:$DisplayIDblockoffset*2:($lastDisplayIDblockoffset-$DisplayIDblockoffset)*2}" | |
| doadddisplayidblockoffset=-1 | |
| ((lastDisplayIDblockoffset+=newDisplayIDblocklength)) | |
| if (( DisplayIDlength + 5 < lastDisplayIDblockoffset )); then | |
| DisplayIDlength=$((lastDisplayIDblockoffset - 5)) | |
| replacebytes $((blockoffset+2)) $(printf "%02x" $DisplayIDlength) | |
| fi | |
| theblock=${theedid:$blockoffset*2:254} | |
| fi | |
| DisplayIDblocklength=$((0x${theblock:$DisplayIDblockoffset * 2 + 4:2} + 3)) | |
| (( dodump )) && echo -n " $((blockoffset + DisplayIDblockoffset)))" | |
| DisplayIDblock=${theblock:$DisplayIDblockoffset*2:$DisplayIDblocklength * 2} | |
| DisplayIDtagcode=${DisplayIDblock:0:2} | |
| # stop when the remaining bytes are 0 | |
| local DisplayIDremains=${theblock:$DisplayIDblockoffset * 2:($DisplayIDlength + 5 - $DisplayIDblockoffset)*2} | |
| if [ -z ${DisplayIDremains//0/} ]; then | |
| (( dodump )) && echo | |
| break | |
| fi | |
| if (( dodump )); then | |
| printf " " | |
| case $DisplayIDtagcode in | |
| # DisplayID 1.3 | |
| 00) printf "Product identification" ;; | |
| 01) printf "Display parameters" ;; | |
| 02) printf "Color characteristics" ;; | |
| 03) printf "Type 1 detailed timing" ;; | |
| 04) printf "Type 2 detailed timing" ;; | |
| 05) printf "Type 3 short timing" ;; | |
| 06) printf "Type 4 DMT timing" ;; | |
| 07) printf "VESA timings" ;; | |
| 08) printf "CTA timings" ;; | |
| 09) printf "Video timing range" ;; | |
| 0a) printf "Product serial number" ;; | |
| 0b) printf "General purpose ASCII string" ;; | |
| 0c) printf "Display device data" ;; | |
| 0d) printf "Interface power sequencing" ;; | |
| 0e) printf "Transfer characterisitics" ;; | |
| 0f) printf "Display interface" ;; | |
| 10) printf "Stereo display interface" ;; | |
| 12) printf "Tiled display topology" ;; | |
| # DisplayID 2.0 | |
| 20) printf "Product ID data" ;; | |
| 21) printf "Display parameters data" ;; | |
| 22) printf "Type 7 timing - detailed timing data" ;; | |
| 23) printf "Type 8 timing - enumerated timing code data" ;; | |
| 24) printf "Type 9 timing - formula-based timing data" ;; | |
| 25) printf "Dynamic video timing range limits data" ;; | |
| 26) printf "Display interface features data" ;; | |
| 27) printf "Stereo display interface data" ;; | |
| 28) printf "Tiled display topology data" ;; | |
| 29) printf "ContainerID data" ;; | |
| # 2Ah .. 7Dh RESERVED for Additional VESA-defined Data Blocks | |
| 7e) printf "2.0 Vendor-specific data" ;; | |
| 7f) printf "1.3 Vendor-specific data" ;; | |
| 81) printf "CTA DisplayID data" ;; | |
| # 82h .. FFh RESERVED | |
| *) | |
| if (( 0x$DisplayIDtagcode <= 0x1f )); then | |
| printf "Reserved for legacy" | |
| elif (( 0x$DisplayIDtagcode <= 0x7d )); then | |
| printf "Reserved for VESA" | |
| elif (( 0x$DisplayIDtagcode <= 7f )); then | |
| printf "Reserved" | |
| else | |
| printf "Reserved for external standards" | |
| fi | |
| printf " (${DisplayIDtagcode})" | |
| ;; | |
| esac | |
| echo -n ": $DisplayIDblock" | |
| fi | |
| local willberemoved=$(( (blockoffset + DisplayIDblockoffset == deletedisplayidblockoffset) || (0x$DisplayIDtagcode == displayidblocktypetodelete) )) | |
| case $DisplayIDtagcode in | |
| 03) | |
| local timing1offset=3 | |
| (( dodump == 1 && DisplayIDblocklength > 23 )) && echo | |
| while ((timing1offset < DisplayIDblocklength )); do | |
| local type1timing=${DisplayIDblock:$timing1offset*2:40} | |
| if (( dodump )); then | |
| (( DisplayIDblocklength > 23 )) && echo -n " $((blockoffset+DisplayIDblockoffset+timing1offset))) $type1timing" | |
| fi | |
| local type1timingtext="$(dumptype1timingdescriptor $type1timing)" | |
| if (( willberemoved == 0 )); then | |
| thetimings+=("$type1timingtext") | |
| thetimingsoffsets+=("$(( blockoffset + DisplayIDblockoffset + timing1offset ))") | |
| thetimingshex+=("$type1timing") | |
| fi | |
| (( dodump )) && echo " = $type1timingtext" | |
| ((timing1offset+=20)) | |
| done | |
| ;; | |
| 12) | |
| HasTile=1 | |
| (( dodump )) && printf " = " | |
| dumptileddisplaytopologyblock $DisplayIDblock | |
| (( dodump )) && echo | |
| ;; | |
| 26) (( dodump )) && printf " = " | |
| dumpdisplayinterfacefeaturesdata $DisplayIDblock | |
| (( dodump )) && echo | |
| ;; | |
| *) | |
| (( dodump )) && echo | |
| ;; | |
| esac | |
| if (( willberemoved )); then | |
| (( debug )) && echo ":" replacebytes $((blockoffset + DisplayIDblockoffset)) "${theblock:($DisplayIDblockoffset + $DisplayIDblocklength)*2:($lastDisplayIDblockoffset - $DisplayIDblockoffset - $DisplayIDblocklength)*2}${DisplayIDblock//?/0}" 1>&2 | |
| (( debug )) && echo ":" $DisplayIDblockoffset $DisplayIDblocklength $lastDisplayIDblockoffset ${DisplayIDblock//?/0} 1>&2 | |
| replacebytes $((blockoffset + DisplayIDblockoffset)) "${theblock:($DisplayIDblockoffset + $DisplayIDblocklength)*2:($lastDisplayIDblockoffset - $DisplayIDblockoffset - $DisplayIDblocklength)*2}${DisplayIDblock//?/0}" | |
| ((lastDisplayIDblockoffset-=DisplayIDblocklength)) | |
| theblock=${theedid:$blockoffset*2:254} | |
| (( dodump )) && echo " changed: deleted" | |
| deletedisplayidblockoffset=0 | |
| else | |
| DisplayIDblocks+=("$DisplayIDblock") | |
| (( dodump && (countAdded > 0) )) && echo " changed: added $countAdded" | |
| ((DisplayIDblockoffset+=DisplayIDblocklength)) | |
| EndOfLastDisplayIDblock=$((blockoffset + DisplayIDblockoffset)) | |
| fi | |
| done | |
| else | |
| (( dodump )) && echo "$blockoffset) Extension block: type:$blocktag: $theblock" | |
| fi | |
| ((blockoffset+=128)) | |
| if (( (blockoffset*2 == ${#theedid}) && (doadddisplayidblockoffset == -1) && (${#newdisplayidblocks[@]} > 0) )); then | |
| adddisplayidextensionblock ${DisplayIDversion:=1.3} ${DisplayIDproducttype:=0} | |
| fi | |
| done | |
| (( dodump )) && echo "$blockoffset) End" | |
| # post processing | |
| local timingndx=0 | |
| local tileTimingsCurrent="" | |
| for ((timingndx = 0 ; timingndx < ${#thetimings[@]} ; timingndx++)); do | |
| local thetiming="${thetimings[timingndx+arrstart]}" | |
| local thewidth=${thetiming%%x*} | |
| local theheight=${thetiming#*x} | |
| theheight=${theheight%%@*} | |
| local thetimingrefresh=${thetiming#*@} | |
| thetimingrefresh=${thetimingrefresh%%Hz*} | |
| thetimingrefresh=0000${thetimingrefresh/./} | |
| thetimingrefresh=${thetimingrefresh: -7} | |
| if (( thewidth > maxresH )); then | |
| maxresH=$thewidth | |
| maxresV=$theheight | |
| fi | |
| if (( HasTile )); then | |
| if [[ "$thetiming" =~ "${tileSizeH}x${tileSizeV}@([0-9.]+)Hz.*" ]]; then | |
| tileTimingsCurrent+="$thetimingrefresh $thetiming\n" | |
| fi | |
| fi | |
| done | |
| if (( HasTile )); then | |
| if [[ -n $tileTimingsCurrent ]]; then | |
| tileTimings=$(echo "$tileTimingsCurrent" | sort -r) | |
| tileRefresh=$(((10#${tileTimings%% *} + 50)/100)) # round to 1 decimal | |
| if (( tileRefresh % 10 )); then | |
| tileRefresh=$((tileRefresh / 10)).${tileRefresh: -1} | |
| else | |
| tileRefresh=$((tileRefresh / 10)) | |
| fi | |
| fi | |
| fi | |
| } | |
| createdetailedtiming () { | |
| # currently supports only "Normal Display" with "Digital Separate Sync" | |
| for param in MHz hactive hfront hpulse hback vactive vfront vpulse vback hsync vsync hmm vmm hborder vborder; do | |
| eval "local $param=\"$1\""; (( $# )) && shift | |
| done | |
| local interlaced=0 | |
| [[ -n ${vactive//[^i]} ]] && interlaced=1 | |
| vactive=${vactive//[^0-9]} | |
| [[ "$hsync" = '+' ]] && hsync=1 || hsync=0 | |
| [[ "$vsync" = '+' ]] && vsync=1 || vsync=0 | |
| local MHzint=$(((10#$(printf "0${MHz}.000" | sed -E "s/^([0-9]*)\.([0-9])\.*([0-9])\.*([0-9]).*/\1\2\3\4/g") + 5) / 10)) | |
| local hblank=$((hfront + hpulse + hback)) | |
| local vblank=$((vfront + vpulse + vback)) | |
| printf "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x" \ | |
| $((MHzint & 255)) $((MHzint >> 8)) \ | |
| $((hactive & 255)) \ | |
| $((hblank & 255)) \ | |
| $(((hactive >> 8 << 4) + (hblank >> 8))) \ | |
| $((vactive & 255)) \ | |
| $((vblank & 255)) \ | |
| $(((vactive >> 8 << 4) + (vblank >> 8))) \ | |
| $((hfront & 255)) \ | |
| $((hpulse & 255)) \ | |
| $((((vfront & 15) << 4) + (vpulse & 15))) \ | |
| $(((hfront >> 8 << 6) + (hpulse >> 8 << 4) + (vfront >> 4 << 2) + (vpulse >> 4))) \ | |
| $((${hmm:=0} & 255)) \ | |
| $((${vmm:=0} & 255)) \ | |
| $(((${hmm:=0} >> 8 << 4) + (${vmm:=0} >> 8))) \ | |
| ${hborder:=0} \ | |
| ${vborder:=0} \ | |
| $(((interlaced << 7) + 0x18 + (vsync << 2) + (hsync << 2))) | |
| } | |
| createtype1timingdescriptor () { | |
| # currently supports only "Normal Display" with "Digital Separate Sync" | |
| # no 3D, no interlaced | |
| for param in MHz hactive hfront hpulse hback vactive vfront vpulse vback hsync vsync preferred; do | |
| eval "local $param=\"$1\""; (( $# )) && shift | |
| done | |
| local interlaced=0 | |
| [[ -n ${vactive//[^i]} ]] && interlaced=1 | |
| vactive=${vactive//[^0-9]} | |
| [[ "$hsync" = '+' ]] && hsync=1 || hsync=0 | |
| [[ "$vsync" = '+' ]] && vsync=1 || vsync=0 | |
| local MHzint=$(((10#$(printf "0${MHz}.000" | sed -E "s/^([0-9]*)\.([0-9])\.*([0-9])\.*([0-9]).*/\1\2\3\4/g") + 5) / 10)) | |
| local hblank=$((hfront + hpulse + hback)) | |
| local vblank=$((vfront + vpulse + vback)) | |
| local aspect=8 | |
| case $(((hactive * 10000 / vactive + 5)/10)) in | |
| 1000) aspect=0 ;; | |
| 1250) aspect=1 ;; | |
| 1333) aspect=2 ;; | |
| 1667) aspect=3 ;; | |
| 1778) aspect=4 ;; | |
| 1600) aspect=5 ;; | |
| 2370) aspect=6 ;; | |
| 1896) aspect=7 ;; | |
| esac | |
| ((MHzint-=1)) | |
| ((hactive-=1)) | |
| ((hblank-=1)) | |
| ((hfront-=1)) | |
| ((hpulse-=1)) | |
| ((vactive-=1)) | |
| ((vblank-=1)) | |
| ((vfront-=1)) | |
| ((vpulse-=1)) | |
| printf "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x" \ | |
| $((MHzint & 255)) $(((MHzint >> 8) & 255)) $((MHzint >> 16)) \ | |
| $(((${preferred:=0} << 7) + (interlaced << 4) + aspect)) \ | |
| $((hactive & 255)) $((hactive >> 8)) \ | |
| $((hblank & 255)) $((hblank >> 8)) \ | |
| $((hfront & 255)) $((((hfront >> 8) & 127) + (hsync << 7))) \ | |
| $((hpulse & 255)) $((hpulse >> 8)) \ | |
| $((vactive & 255)) $((vactive >> 8)) \ | |
| $((vblank & 255)) $((vblank >> 8)) \ | |
| $((vfront & 255)) $((((vfront >> 8) & 127) + (vsync << 7))) \ | |
| $((vpulse & 255)) $((vpulse >> 8)) | |
| } | |
| createtype1timingblock () { | |
| local type1timingblock="" | |
| while (( $# )); do | |
| type1timingblock+=$1 | |
| shift | |
| done | |
| printf "0300%02x%s" $((${#type1timingblock}/2)) "$type1timingblock" | |
| } | |
| addchromasubsampling () { | |
| processedid do422 dodeletectablocktype 715 # = YCbCr 4:2:0 capability map | |
| repairchecksums | |
| } | |
| removechromasubsampling () { | |
| processedid do444 dodeletectablocktype 715 # = YCbCr 4:2:0 capability map | |
| repairchecksums | |
| } | |
| deletectablocktype () { | |
| processedid dodeletectablocktype "$1" | |
| } | |
| replacedescriptor () { | |
| processedid doreplacedescriptor "$1" "$2" | |
| repairchecksums | |
| } | |
| cleardate () { | |
| processedid docleardate | |
| repairchecksums | |
| } | |
| clearserialnumber () { | |
| processedid doclearserialnumber | |
| repairchecksums | |
| } | |
| setpreferredisnative () { | |
| processedid dosetpreferredisnative "$1" | |
| repairchecksums | |
| } | |
| clearchromaticity () { | |
| processedid doclearchromaticity | |
| repairchecksums | |
| } | |
| clearestablishedtimings () { | |
| processedid doclearestablishedtimings | |
| repairchecksums | |
| } | |
| clearmanufacturerstimings () { | |
| processedid doclearmanufacturerstimings | |
| repairchecksums | |
| } | |
| clearstandardtimings () { | |
| processedid doclearstandardtimings | |
| repairchecksums | |
| } | |
| deletedisplayidblock () { | |
| processedid dodeletedisplayidblock "$1" | |
| repairchecksums | |
| } | |
| deletedisplayidblocktype () { | |
| processedid dodeletedisplayidblockblocktype "$1" | |
| repairchecksums | |
| } | |
| adddisplayidblock () { | |
| processedid doadddisplayidblock "$@" | |
| repairchecksums | |
| } | |
| dumpedid () { | |
| processedid dodump | |
| } | |
| dumpedidall () { | |
| local saveedid="$theedid" | |
| local thefolderpath="$1" | |
| local i="" | |
| for ((i = 1 ; i <= ${#edids[@]} ; i++)); do | |
| useedidnum $i | |
| dumpedid > "${thefolderpath:=.}/${thefilenamebase}_dumpedid.txt" | |
| done | |
| useedidstring "$saveedid" | |
| } | |
| createdummydescriptor () { | |
| echo -n "000000100000000000000000000000000000" | |
| } | |
| createemptydescriptor () { | |
| echo -n "000000000000000000000000000000000000" | |
| } | |
| #========================================================================================= | |
| # Use EDID | |
| clearedidinfo () { | |
| tileTimings="" | |
| tileSizeH="" | |
| tileSizeV="" | |
| tileRefresh="" | |
| maxresH=0 | |
| maxresV=0 | |
| } | |
| useedidstring () { | |
| clearedidinfo | |
| (( debug )) && echo ": useedidstring $1" 1>&2 | |
| theedid="$1" | |
| processedid | |
| } | |
| useedidnum () { | |
| clearedidinfo | |
| theedid=${edids[arrstart - 1 + $1]} | |
| processedid | |
| thefilenamebase="${thefilenamebase}_$i" | |
| } | |
| #========================================================================================= | |
| # Get EDID | |
| clearedids () { | |
| edids=() | |
| paths=() | |
| } | |
| clearedids | |
| applypatches () { | |
| thefilename="$1" | |
| sourcename="$2" | |
| if [[ -f "${thefilename}" ]]; then | |
| if /usr/libexec/PlistBuddy -c 'Print :edid-patches' "${thefilename}" > /dev/null 2>&1; then | |
| local index=0 | |
| while [ -n ""$(/usr/libexec/PlistBuddy -c "Print :edid-patches:$index:offset" "${thefilename}" 2> /dev/null) ]; do | |
| local theoffset=$(/usr/libexec/PlistBuddy -c "Print :edid-patches:$index:offset" "${thefilename}") | |
| local thedata=$(/usr/libexec/PlistBuddy -c "Print :edid-patches:$index:data" "${thefilename}" | xxd -p -c 99999) | |
| thedata=${thedata%0a} # remove extra linefeed that was added by PlistBuddy | |
| theedid=${theedid:0:$theoffset*2}${thedata}${theedid:$theoffset*2 + ${#thedata}} | |
| ((index+=1)) | |
| done | |
| if (( ${#theedid} % 256 == 0 )); then | |
| if [[ -n $sourcename ]]; then | |
| addedid "${thefilename}:edid-patches of ($sourcename)" "$theedid" | |
| else | |
| addedid "${thefilename}:edid-patches" "$theedid" | |
| fi | |
| else | |
| theedid="" | |
| fi | |
| fi | |
| fi | |
| } | |
| addedid () { | |
| local saveedid="$theedid" | |
| local thepath="$1" | |
| useedidstring "$2" | |
| local isnew=1 | |
| local i="" | |
| for ((i = 0 ; i < ${#edids[@]} ; i++)); do | |
| if [[ $theedid = ${edids[i+arrstart]} ]]; then | |
| if [[ ! "$(printf "_\n%s\n_" "${paths[i+arrstart]}")" =~ $(printf ".*\n%s\n.*" "${thepath}") ]]; then | |
| paths[i+arrstart]="$(printf "%s\n%s" "${paths[i+arrstart]}" "$thepath")" | |
| fi | |
| isnew=0 | |
| break | |
| fi | |
| done | |
| if (( isnew )); then | |
| (( debug )) && echo ": isnew" 1>&2 | |
| edids+=("$theedid") | |
| paths+=("$thepath") | |
| local newndx=${#edids[@]} | |
| for thedir in "/System" ""; do | |
| local fulldir="$thedir/Library/Displays/Contents/Resources/Overrides" | |
| for thefilename in "$fulldir/$themanufacturefile" "$fulldir/$theproductfile"; do | |
| (( debug )) && echo ": checking $thefilename" 1>&2 | |
| if [[ -f "${thefilename}" ]]; then | |
| loadoverridefile "${thefilename}" | |
| [[ -n $lastoverrideedid ]] && theedid=$lastoverrideedid | |
| applypatches "${thefilename}" "$newndx" | |
| fi | |
| done | |
| for thefilename in "$fulldir/${themanufacturefile}.mtdd" "$fulldir/${theproductfile}.mtdd"; do | |
| (( debug )) && echo ": checking $thefilename" 1>&2 | |
| if [[ -f "${thefilename}" ]]; then | |
| loadmtddfile "${thefilename}" | |
| fi | |
| done | |
| done | |
| fi | |
| useedidstring "$saveedid" | |
| } | |
| loadoverridefile () { | |
| while (( $# )); do | |
| local thefilename="$1" | |
| shift | |
| # lastoverrideedid is not local | |
| lastoverrideedid=$(/usr/libexec/PlistBuddy -c 'Print :IODisplayEDID' "${thefilename}" 2> /dev/null | xxd -p -c 99999) | |
| lastoverrideedid=${lastoverrideedid%0a} # remove extra linefeed that was added by PlistBuddy | |
| [[ -n $lastoverrideedid ]] && addedid "${thefilename}:IODisplayEDID" "$lastoverrideedid" | |
| done | |
| } | |
| loadmtddfile () { | |
| while (( $# )); do | |
| local thefilename="$1" | |
| shift | |
| local theedid=$(/usr/libexec/PlistBuddy -c 'Print :display:overlay' "${thefilename}" 2> /dev/null | xxd -p -c 99999) | |
| theedid=${theedid%0a} # remove extra linefeed that was added by PlistBuddy | |
| [[ -n $theedid ]] && addedid "${thefilename}:overlay" "$theedid" | |
| done | |
| } | |
| loadswitchresxfile () { | |
| while (( $# )); do | |
| local thefilename="$1" | |
| shift | |
| local theedid=$(sed -n -E -e "/^ *< ([0-9A-F ]+) >/s//\1/p" "${thefilename}" | xxd -r -p | xxd -p -c 99999) | |
| addedid "${thefilename}:switchresx" "$theedid" | |
| done | |
| } | |
| loadmamhexfile () { | |
| while (( $# )); do | |
| local thefilename="$1" | |
| shift | |
| local theedid=$(sed -n -E -e "/^:[0-9A-F]{8}([0-9A-F]{64})[0-9A-F]{2}.?$/s//\1/p" "${thefilename}" | xxd -p -r | xxd -p -c 99999) | |
| addedid "${thefilename}:Monitor Asset Manager.hex" "$theedid" | |
| done | |
| } | |
| loadmaminffile () { | |
| while (( $# )); do | |
| local thefilename="$1" | |
| shift | |
| local theedid=$(sed -n -E -e '/^HKR,EDID_OVERRIDE,".+",0x01((,0x[0-9A-F]{2})*).?$/s//\1/;s/,0x//gp' "${thefilename}" | xxd -p -r | xxd -p -c 99999) | |
| addedid "${thefilename}:Monitor Asset Manager.hex" "$theedid" | |
| done | |
| } | |
| loadmamdatfile () { | |
| while (( $# )); do | |
| local thefilename="$1" | |
| shift | |
| local theedid=$(sed -n -E -e '/^[0-9A-F]{2} \|(( [0-9A-F]{2})*).?$/s//\1/p' "${thefilename}" | xxd -r -p | xxd -p -c 99999) | |
| addedid "${thefilename}:Monitor Asset Manager.dat" "$theedid" | |
| done | |
| } | |
| numstrings=0 | |
| loadstring () { | |
| local sourcename="$2" | |
| if [[ -z $sourcename ]]; then | |
| ((numstrings++)) | |
| sourcename="string:$numstrings" | |
| fi | |
| addedid "$sourcename" "$1" | |
| } | |
| loadbinfile () { | |
| while (( $# )); do | |
| local thefilename="$1" | |
| shift | |
| addedid "$thefilename" ""$(xxd -p -c 99999 < "$thefilename") | |
| done | |
| } | |
| loadhexfile () { | |
| while (( $# )); do | |
| local thefilename="$1" | |
| shift | |
| addedid "$thefilename" ""$(xxd -r < "$thefilename" | xxd -p -c 99999) | |
| done | |
| } | |
| loadoneediditem () { | |
| local ediditem="$1" | |
| local thepath="${ediditem% = <*}" | |
| local theedid="${ediditem##* = <}" | |
| local theedid="${theedid%>}" | |
| (( debug )) && echo ":" addedid "${thepath}" "${theedid}" 1>&2 | |
| addedid "${thepath}" "${theedid}" | |
| } | |
| loadagdcfile () { | |
| while (( $# )); do | |
| local thefilename="$1" | |
| shift | |
| IFS=$'\n' | |
| for ediditem in $( | |
| perl -e ' | |
| $thepath=""; while (<>) { | |
| if ( m|^(?:\[\d+\] )?IOService:(/.*)| ) { $thepath = $1 } | |
| if ( /^[#|]* ?EDID Dump (Port \d+) - Start( ##)?/ ) { $theport = $1; $edid = "" } | |
| if ( m|^( )?/\*? ...: \*?/ ?(.*)| ) { $edid .= $2 } | |
| if ( /^(## )?EDID Dump (Port \d+) - End( ##)?/ and length $edid > 0 ) { $edid =~ s/0x(..)(, *)?/$1/g; print "'"${thefilename}"':" . $thepath . "/" . $theport . " = <" . $edid . ">\n" } | |
| } | |
| ' < "${thefilename}" | |
| ); do | |
| (( debug )) && echo ":" loadoneediditem "${ediditem}" 1>&2 | |
| loadoneediditem "${ediditem}" | |
| done | |
| done | |
| } | |
| loadmamfile () { | |
| while (( $# )); do | |
| local thefilename="$1" | |
| shift | |
| IFS=$'\n' | |
| for ediditem in $( | |
| perl -e ' | |
| $thepath=""; while (<>) { | |
| if ( m|^(Monitor( #.*\S)?)\s*$| ) { $thepath = $1 } | |
| if ( /^Raw data.?$/ ) { $edid = "" } | |
| if ( m| *(([0-9A-F]{2},){31}[0-9A-F]{2},).?$| ) { $edid .= $1 } | |
| if ( m| *(([0-9A-F]{2},){31}[0-9A-F]{2})[^,]*$| ) { $edid .= $1 . ","; $edid =~ s/(..),/$1/g; print "'"${thefilename}"':" . $thepath . " = <" . lc $edid . ">\n" } | |
| } | |
| ' < "${thefilename}" | |
| ); do | |
| (( debug )) && echo ":" loadoneediditem "${ediditem}" 1>&2 | |
| loadoneediditem "${ediditem}" | |
| done | |
| done | |
| } | |
| loadagdc () { | |
| local thefilename="$1" | |
| if [[ -z $thefilename ]]; then | |
| thefilename=`mktemp /tmp/local_AGDCDiagnose.XXXXXX` || exit 1 | |
| fi | |
| /System/Library/Extensions/AppleGraphicsControl.kext/Contents/MacOS/AGDCDiagnose -a > "$thefilename" 2>&1 | |
| loadagdcfile "$thefilename" | |
| } | |
| loadioregfile () { | |
| while (( $# )); do | |
| local thefilename="$1" | |
| shift | |
| IFS=$'\n' | |
| for ediditem in $( | |
| perl -e ' | |
| $thepath=""; while (<>) { | |
| if ( /^([ |]*)\+\-o (.+) </ ) { $indent = (length $1) / 2; $name = $2; $thepath =~ s|^((/[^/]*){$indent}).*|$1/$name| } | |
| if ( /^[ |]*"([^"]+)" = <(00ffffffffffff00.*)>/i ) { print "'"${thefilename}"'" . ":" . $thepath . "/" . $1 . " = <" . $2 . ">\n" } | |
| } | |
| ' < "${thefilename}" | |
| ); do | |
| loadoneediditem "${ediditem}" | |
| done | |
| done | |
| } | |
| loadioreg () { | |
| local tmpfilename=`mktemp /tmp/local_ioreg.XXXXXX` || exit 1 | |
| ioreg -lw0 > "$tmpfilename" | |
| loadioregfile "$tmpfilename" | |
| } | |
| listedids () { | |
| local saveedid="$theedid" | |
| local i="" | |
| for ((i = 1 ; i <= ${#edids[@]} ; i++)); do | |
| useedidnum $i | |
| echo "$i)" | |
| echo "vendor:$thevendorid ($thevendorcode) product:$theproductid" | |
| echo "override product name:$theproductfile" | |
| echo "override date name:$themanufacturefile" | |
| echo "strings:"$(echo -n "$theedid" | xxd -p -r | strings - ) | |
| echo "theedid=$theedid" | |
| echo "sources:" | |
| echo "${paths[i+arrstart-1]}" | |
| echo | |
| done | |
| useedidstring "$saveedid" | |
| } | |
| #========================================================================================= | |
| # Files from EDID | |
| edidbin () { | |
| echo -n "$theedid" | xxd -p -r | |
| } | |
| edidbinall () { | |
| local saveedid="$theedid" | |
| local thefolderpath="$1" | |
| local i="" | |
| for ((i = 1 ; i <= ${#edids[@]} ; i++)); do | |
| useedidnum $i | |
| edidbin > "${thefolderpath:=.}/${thefilenamebase}.bin" | |
| done | |
| useedidstring "$saveedid" | |
| } | |
| decode () { | |
| local tmpfilename=`mktemp /tmp/edidbin.XXXXXX` || exit 1 | |
| edidbin > "$tmpfilename" | |
| edid-decode -C --skip-sha "$tmpfilename" | |
| } | |
| decodeall () { | |
| local saveedid="$theedid" | |
| local thefolderpath="$1" | |
| local i="" | |
| for ((i = 1 ; i <= ${#edids[@]} ; i++)); do | |
| useedidnum $i | |
| decode > "${thefolderpath:=.}/${thefilenamebase}_edid-decode.txt" | |
| done | |
| useedidstring "$saveedid" | |
| } | |
| agdcdevicedump () { | |
| # Indent the Device Dump section of a AGDCDiagnose file. | |
| # Note that some DICT sizes seem to be greater than the number of lines included in the dump. | |
| local thefilename="$1" | |
| perl -e ' | |
| sub dict { | |
| my $indent = $_[0]; | |
| my $lines = $_[1]; | |
| while ($lines-- != 0) { | |
| my $theline = ""; | |
| do { | |
| $theline = <>; | |
| } until ($theline !~ /^$/); | |
| die if ($theline =~ /\-\-END Device Dump\-\-/); | |
| print $indent.$theline; | |
| dict($indent."\t", $1) if ($theline =~ /^.*[\t ]DICT[\t ]+(\d+)\n$/); | |
| } | |
| } | |
| while (<>) { if ( /\-\-BEGIN Device Dump\-\-/ ) { print "----------------\n"; eval { dict("", -1); } } } | |
| ' < "$thefilename" | |
| } | |
| updateoverride () { | |
| local thefilename="$1" | |
| plutil -replace IODisplayEDID -data "$(echo -n $theedid | xxd -p -r | base64)" "${thefilename}" | |
| plutil -replace DisplayProductID -integer $((0x${theedid:22:2}${theedid:20:2})) "${thefilename}" | |
| plutil -replace DisplayVendorID -integer $((0x${theedid:16:4})) "${thefilename}" | |
| } | |
| makeoverride () { | |
| local noedid=0 | |
| if [[ $1 == '-noedid' ]]; then | |
| noedid=1 | |
| shift | |
| fi | |
| local thefilename="${theproductfile}" | |
| if [[ $1 == '-m' ]]; then | |
| thefilename="$themanufacturefile" | |
| shift | |
| fi | |
| if [[ -z $1 ]]; then | |
| [[ -d "${thevendordir}" ]] || mkdir "${thevendordir}" | |
| else | |
| thefilename="$1" | |
| fi | |
| [[ -f "${thefilename}" ]] && rm "${thefilename}" | |
| /usr/libexec/PlistBuddy \ | |
| -c "Add :DisplayProductID integer ${theproductid}" \ | |
| -c "Add :DisplayVendorID integer ${thevendorid}" \ | |
| -c 'Add :IODisplayEDID data ""' \ | |
| -c 'Add :DisplayPixelDimensions data ""' \ | |
| "${thefilename}" > /dev/null | |
| plutil -replace 'IODisplayEDID' -data "$(echo -n $theedid | xxd -p -r | base64)" "${thefilename}" | |
| # the hex is big endian | |
| plutil -replace 'DisplayPixelDimensions' -data "$(printf "%08x%08x" $maxresH $maxresV | xxd -p -r | base64)" "${thefilename}" | |
| if (( noedid )); then | |
| plutil -remove 'IODisplayEDID' "${thefilename}" | |
| fi | |
| # // IOGraphicsLibInternal.h flags for IOGFlags in override file | |
| # enum { | |
| # // disable any use of scaled modes, | |
| # kOvrFlagDisableScaling = 0x00000001, | |
| # // remove driver modes, | |
| # kOvrFlagDisableNonScaled = 0x00000002, | |
| # // disable scaled modes made up by the system (just use the override list) | |
| # kOvrFlagDisableGenerated = 0x00000004 | |
| # }; | |
| } | |
| installoverride () { | |
| local thedir="/System" | |
| local dstfile="$theproductfile" | |
| local thefilename="" | |
| while (( $# )); do | |
| if [[ $1 == '-l' ]]; then | |
| thedir="" | |
| elif [[ $1 == '-m' ]]; then | |
| dstfile="$themanufacturefile" | |
| else | |
| thefilename="$1" | |
| fi | |
| shift | |
| done | |
| [[ -n thefilename ]] && thefilename="${dstfile}" | |
| mount | grep ' on / ' | grep -q 'read-only' && sudo mount -uw / | |
| local fulldir="$thedir/Library/Displays/Contents/Resources/Overrides" | |
| [[ -d "${fulldir}/${thevendordir}" ]] || sudo mkdir -p "${fulldir}/${thevendordir}" | |
| sudo cp "${thefilename}" "${fulldir}/${dstfile}" | |
| echo "# Installed file: ${fulldir}/${dstfile}" | |
| if [[ "$(basename ${dstfile})" =~ "DisplayProductID-.*" ]]; then | |
| [[ -f ${fulldir}/$themanufacturefile ]] && echo "# Warning: alternative override exists at ${fulldir}/$themanufacturefile" | |
| else | |
| [[ -f ${fulldir}/$theproductfile ]] && echo "# Warning: alternative override exists at ${fulldir}/$theproductfile" | |
| fi | |
| } | |
| makemtdd () { | |
| local donew=0 | |
| local thefilename="" | |
| while (( $# )); do | |
| case "$1" in | |
| "-n") donew=1 ;; | |
| *) thefilename="$1" ;; | |
| esac | |
| shift | |
| done | |
| if [[ -z ${thefilename} ]]; then | |
| [[ -d "${thevendordir}" ]] || mkdir "${thevendordir}" | |
| thefilename="${theproductfile}.mtdd" | |
| fi | |
| [[ -f "${thefilename}" ]] && rm "${thefilename}" | |
| /usr/libexec/PlistBuddy \ | |
| -c 'Add display dict' \ | |
| -c 'Add :display:linkmode string multi-cable' \ | |
| -c 'Add :display:overlay data ""' \ | |
| -c "Add :display:streamcount integer $((tileCountH * tileCountV))" \ | |
| -c "Add :display:tileinfo string ($tileCountH,$tileCountV)" \ | |
| -c "Add :productid string $(printf "0x%04x" $theproductid)" \ | |
| -c "Add :serial integer 1" \ | |
| -c "Add :vendorid string $(printf "0x%04x" $thevendorid)" \ | |
| -c "Add :version string 1.3" \ | |
| "${thefilename}" > /dev/null | |
| if (( donew )); then | |
| plutil -insert 'display.backendtiming' -xml "<array>$(echo "$tileTimings" | perl -pe's/^[0-9]+ ([0-9]+)x([0-9]+)@([0-9.]+)Hz [0-9.]+kHz ([0-9]+)\.([0-9]+)MHz h\(([0-9]+) ([0-9]+) ([0-9]+) ([-+])\) v\(([0-9]+) ([0-9]+) ([0-9]+) ([-+])\).*/"<string>$1,$6,$7,".($1+$6+$7+$8)."x$2,$10,$11,".($2+$10+$11+$12)."\@$4${5}0000,$9$13<\/string>"/e')<string>*</string></array>" "${thefilename}" | |
| plutil -insert 'display.frontendtiming' -xml "<array>$(echo "$tileTimings" | perl -pe's/^[0-9]+ ([0-9]+)x([0-9]+)@([0-9.]+)Hz [0-9.]+kHz ([0-9]+)\.([0-9]+)MHz h\(([0-9]+) ([0-9]+) ([0-9]+) ([-+])\) v\(([0-9]+) ([0-9]+) ([0-9]+) ([-+])\).*/"<string>".($1*2).",".($6*2).",".($7*2).",".($1+$6+$7+$8)."x$2,$10,$11,".($2+$10+$11+$12)."\@".("$4${5}0000" * 2).",$9$13<\/string>"/e')</array>" "${thefilename}" | |
| else | |
| plutil -insert 'display.backendtiming' -string "${tileSizeH}x${tileSizeV}@${tileRefresh}" "${thefilename}" | |
| plutil -insert 'display.frontendtiming' -string "$((tileSizeH * 2))x${tileSizeV}@${tileRefresh}" "${thefilename}" | |
| fi | |
| plutil -replace 'display.overlay' -data "$(echo -n $theedid | xxd -p -r | base64)" "${thefilename}" | |
| if (( HasAudio )); then | |
| plutil -insert 'display.audio' -string '(1,1)' "${thefilename}" | |
| fi | |
| echo "# Created mtdd file: ${thefilename}" | |
| } | |
| makepatch () { | |
| local thefilename="$1" | |
| if [[ -z ${thefilename} ]]; then | |
| [[ -d "${thevendordir}" ]] || mkdir "${thevendordir}" | |
| thefilename="${theproductfile}" | |
| fi | |
| [[ -f "${thefilename}" ]] && rm "${thefilename}" | |
| /usr/libexec/PlistBuddy \ | |
| -c "Add :DisplayProductID integer ${theproductid}" \ | |
| -c "Add :DisplayVendorID integer ${thevendorid}" \ | |
| -c 'Add edid-patches array' \ | |
| "${thefilename}" > /dev/null | |
| local i="" | |
| for ((i = 0 ; i < ${#patches[@]} ; i++)); do | |
| local thepatch="${patches[i+arrstart]}" | |
| local offset=$((${thepatch%:*})) | |
| local data=${thepatch#*:} | |
| plutil -insert "edid-patches.$i" -xml "<dict> | |
| <key>offset</key> | |
| <integer>$offset</integer> | |
| <key>data</key> | |
| <data>$(echo -n $data | xxd -p -r | base64)</data> | |
| </dict>" "${thefilename}" | |
| done | |
| echo "# Created patch file: ${thefilename}" | |
| } | |
| makemtddall () { | |
| local saveedid="$theedid" | |
| local thefolderpath="$1" | |
| local i="" | |
| for ((i = 1 ; i <= ${#edids[@]} ; i++)); do | |
| useedidnum $i | |
| [[ -d "${thefolderpath:=.}/${thevendordir}" ]] || mkdir "${thefolderpath}/${thevendordir}" | |
| makemtdd "${thefolderpath}/$theproductfile".mtdd | |
| done | |
| useedidstring "$saveedid" | |
| } | |
| translateoui () { | |
| local oui=$1 | |
| if [[ $oui =~ "([0-9]+)-([0-9]+)-([0-9]+)" ]]; then | |
| oui=$(eval $(echo -n "$oui" | sed -E '/([0-9]+)-([0-9]+)-([0-9]+)/s//printf "%02X%02X%02X" \1 \2 \3/')) | |
| else | |
| oui=${oui//-/} | |
| oui=${oui//0x/} | |
| oui=$(printf "%06X" $((0x$oui))) | |
| fi | |
| for thetype in oui cid; do | |
| [[ -f ~/${thetype}.txt ]] || curl -s "http://standards-oui.ieee.org/${thetype}/${thetype}.txt" > ~/${thetype}.txt | |
| local thetranslate="$(sed -nE '/^'$oui'[ ]+\(base 16\)*(.*)/s//\1/p' ~/${thetype}.txt | tr -d '\t\r')" | |
| [[ -n $thetranslate ]] && echo "$thetype: 0x$oui = $thetranslate" | |
| done | |
| } | |
| translatevendor () { | |
| local thevendorid=$(($1)) | |
| local vendorascii=$(printf "%06x" $((((thevendorid&0x7c00)<<6)+((thevendorid&0x3e0)<<3)+(thevendorid&0x1f)+0x404040)) | xxd -p -r) | |
| if [[ ! -f ~/pnp_id_list.txt ]]; then | |
| curl -s "https://uefi.org/pnp_id_list" | \ | |
| sed -E '/(<\/?)(header|footer)/s//\1span/' | \ | |
| tidy -wrap 0 -raw -utf8 -q | \ | |
| sed -nE ' | |
| /<tbody/,/<\/tbody/ { | |
| /<tr/,/<\/tr/ { | |
| /<tr.*/ { s/// ; h ; } | |
| /<td.*title.*>(.*)<\/td>/ { s//3:\1/ ; H ; } | |
| /<td.*pnp-id.*>(.*)<\/td>/ { s//2:\1/ ; H ; } | |
| /<td.*pnp-approved-on-date.*>(.*)<\/span><\/td>/ { s//1:\1/ ; H ; } | |
| /<\/tr/ { | |
| g | |
| # make field 1 first | |
| s/(.*)(\n1:.*)/\2\1/ | |
| # swap field 2 and 3 if they are reversed | |
| s/(\n3:.*)(\n2:.*)/\2\1/ | |
| # remove field numbers, replace linefeed with space | |
| s/\n[1-3]:/ /g | |
| # remove leading space | |
| s/^ // | |
| s/&/\&/g | |
| s/ / /g | |
| s/ *$// | |
| s/ / /g | |
| p | |
| } | |
| } | |
| } | |
| ' | \ | |
| sort -f -k 3 -k 2 -k 1 > ~/pnp_id_list.txt | |
| fi | |
| printf "0x%04x = %s = %s\n" $thevendorid "$vendorascii" "$(sed -nE '/[0-9/]+ '"$vendorascii"' (.*)/s//\1/p' ~/pnp_id_list.txt)" | |
| # $(sed -nE '/^'$oui'[ ]+\(base 16\)*(.*)/s//\1/p' ~/oui.txt | tr -d '\t\r') | |
| } | |
| #========================================================================================= | |
| edidhelp () { | |
| echo ' | |
| Commands: | |
| Get EDID | |
| loadagdc [dstfilepath] | |
| loadioreg | |
| loadstring hexstring [sourcename] | |
| loadbinfile filepath... | |
| loadhexfile filepath... # reverses xxd | |
| loadagdcfile filepath... | |
| loadioregfile filepath... | |
| loadoverridefile filepath... | |
| loadmtddfile filepath... | |
| loadswitchresxfile filepath... | |
| loadmamfile filepath... # Monitor Asset Manager.txt | |
| loadmamhexfile filepath... # Monitor Asset Manager.hex | |
| loadmaminffile filepath... # Monitor Asset Manager.inf | |
| loadmamdatfile filepath... # Monitor Asset Manager.dat | |
| listedids | |
| clearedids | |
| Use EDID | |
| useedidnum numberfromlist | |
| useedidstring lowercasehexstring | |
| Modify EDID | |
| repairchecksums | |
| replacebytes bytepos lowercasehexstring [numbytestoreplace] | |
| deleteextensionblock blocknumber | |
| deleteextensionblocktype blocktype(decimal or 0xhex) | |
| adddisplayidextensionblock version producttype | |
| addctaextensionblock version | |
| removechromasubsampling | |
| addchromasubsampling | |
| cleardate | |
| clearserialnumber | |
| setpreferredisnative preferredvalue(0/1) | |
| clearchromaticity | |
| clearestablishedtimings | |
| clearmanufacturerstimings | |
| clearstandardtimings | |
| deletectablocktype ctablocktype(decimal; +700 for extended) | |
| createdetailedtiming MHz hactive hfront hpulse hback vactive[i=interlaced] vfront vpulse vback hsync(+/-) vsync(+/-) [hmm] [vmm] [hborder] [vborder] | |
| createdummydescriptor | |
| createemptydescriptor | |
| replacedescriptor offset newdescriptor | |
| createtype1timingdescriptor MHz hactive hfront hpulse hback vactive[i=interlaced] vfront vpulse vback hsync(+/-) vsync(+/-) [preferred(1/0)] | |
| createtype1timingblock descriptors... | |
| dumptype1timingdescriptor 20_byte_descriptor | |
| deletedisplayidblock offset | |
| deletedisplayidblocktype displayidblocktype(decimal or 0xhex) | |
| adddisplayidblock offset newdisplayidblock... # -1 = insert before first DisplayID block; 0 = insert after last DisplayID block | |
| applypatches filepath [sourcename] | |
| Files from EDID | |
| makemtdd [-n] [filepath] | |
| The edid needs to be manually edited. | |
| clearserialnumber | |
| cleardate | |
| #clearchromaticity | |
| #clearestablishedtimings | |
| #clearstandardtimings | |
| #clearmanufacturerstimings | |
| #removechromasubsampling | |
| deletedisplayidblocktype 3 # remove type 1 DisplayID timings | |
| deletedisplayidblocktype 0x12 # remove tile topology | |
| # Add the deleted type 1 DisplayID timings, except replace the tile timing (backend timing) with a full size timing (frontend timing). | |
| # Frontend timing in this example doubles the MHz and horizontal numbers of the backend timing. | |
| adddisplayidblock 0 $(createtype1timingblock 9aa00104ff0ea0002f8021006f083e0003000500 $(createtype1timingdescriptor 1264.02 3840 96 64 20 2160 2 4 20 '+' '-' 1 )) | |
| makepatch [filepath] | |
| Creates an override file with edid-patches array from shell array variabled called patches. e.g. patches=("94:0818900a" "304:7f00080000000000000000") | |
| makemtddall [folderpath] | |
| makeoverride [-noedid] [-m | dstfilepath] | |
| updateoverride filepath | |
| installoverride [-l] [-m] [srcfilepath] | |
| decode | |
| decodeall [folderpath] | |
| edidbin | |
| edidbinall [folderpath] | |
| dumpedid | |
| dumpedidall [folderpath] | |
| agdcdevicedump filepath | |
| Miscellaneous functions | |
| translateoui oui # AGDCDiagnose DisplayPort registers and EDIDs may have an oui. | |
| Variables | |
| dodump # Set to 1 to dump the EDID while changes are made to the EDID. | |
| debug # Set to 1 to output debugging info (uses stderr) | |
| DisplayIDblocks # An array of DisplayID blocks that are used in the EDID. Save these using something like saveDisplayIDblocks=($DisplayIDblocks) before modifying the EDID. | |
| thetimings # a list of detailed timings (printf "%s\n" "${thetimings[@]}") | |
| theproductfile # the name of an override file in the form of DisplayVendorID-%x/DisplayProductID-%x | |
| themanufacturefile # the name of an override file in the form of DisplayVendorID-%x/DisplayYearManufacture-%d-DisplayWeekManufacture-%d | |
| ... | |
| Help | |
| edidhelp | |
| ' | |
| } | |
| #edidhelp | |
| #========================================================================================= |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment