rgb-fix.sh
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
Show hidden characters
#!/usr/bin/env bash
function error_exit {
echo "Error: $1" >&2
exit 1
}
function check_success {
if [ $? -ne 0 ]; then
error_exit "Command failed: $1"
fi
}
set -e
ORIGINAL_PLIST="/Library/Preferences/com.apple.windowserver.displays.plist"
TEMP_DIR="$HOME/Downloads"
TEMP_PLIST="$TEMP_DIR/com.apple.windowserver.displays.plist"
sudo chflags nouchg "$ORIGINAL_PLIST" || true
echo "Step 1: File unlocked if necessary."
sudo cp "$ORIGINAL_PLIST" "$TEMP_PLIST"
sudo chown "$USER" "$TEMP_PLIST"
check_success "Copying the plist file"
echo "Step 2: File copied successfully."
set +e
num=$(python3 -c "
import plistlib, subprocess, json, sys
file = '$TEMP_PLIST'
with open(file, 'rb') as f:
data = plistlib.load(f)
displays = data['DisplayAnyUserSets']['Configs'][0]['DisplayConfig']
sp_json = json.loads(subprocess.check_output(
['system_profiler', 'SPDisplaysDataType', '-json'],
stderr=subprocess.DEVNULL
))
sp_displays = []
for gpu in sp_json.get('SPDisplaysDataType', []):
sp_displays.extend(gpu.get('spdisplays_ndrvs', []))
name_by_res = {}
for d in sp_displays:
pixels = d.get('_spdisplays_pixels', '')
name = d.get('_name', '')
if pixels and name:
name_by_res[pixels.replace(' ', '')] = name
print(len(displays))
print('Connected displays:', file=sys.stderr)
for idx, disp in enumerate(displays):
info = disp.get('CurrentInfo', disp.get('UnmirrorInfo', {}))
w = info.get('Wide', 0)
h = info.get('High', 0)
scale = info.get('Scale', 1)
hz = info.get('Hz', 0)
pw, ph = int(w * scale), int(h * scale)
name = name_by_res.get(f'{pw}x{ph}', f'Display {idx}')
print(f'{idx + 1}: {name} ({pw}x{ph} @ {hz}Hz)', file=sys.stderr)
")
if [ -z "$num" ] || [ "$num" -eq 0 ]; then
error_exit "Unable to parse number of displays from plist."
fi
read -p "Enter the number of the display to update (1-${num}): " choice
if ! [[ "$choice" =~ ^[0-9]+$ ]] || [ "$choice" -lt 1 ] || [ "$choice" -gt "$num" ]; then
error_exit "Invalid choice."
fi
i=$((choice - 1))
uuid=$(python3 -c "
import plistlib
with open('$TEMP_PLIST', 'rb') as f:
data = plistlib.load(f)
print(data['DisplayAnyUserSets']['Configs'][0]['DisplayConfig'][$i]['UUID'])
" 2>/dev/null)
if [ -z "$uuid" ]; then
error_exit "Unable to retrieve UUID for the selected display."
fi
echo "Modifying ${uuid}..."
read -p "Enter the bit depth (8 or 10): " bit_depth_choice
if ! [[ "$bit_depth_choice" == "8" || "$bit_depth_choice" == "10" ]]; then
error_exit "Invalid bit depth. Please enter 8 or 10."
fi
python3 -c "
import plistlib, sys file = '$TEMP_PLIST'
uuid = '$uuid'
bit_depth = int('$bit_depth_choice')
link_desc = {
'BitDepth': bit_depth,
'EOTF': 0,
'PixelEncoding': 0, # 0 = RGB
'Range': 1 # 1 = Full Range
}
with open(file, 'rb') as f:
data = plistlib.load(f)
modified_count = 0
if 'DisplayAnyUserSets' in data and 'Configs' in data['DisplayAnyUserSets']:
for config in data['DisplayAnyUserSets']['Configs']:
if 'DisplayConfig' in config:
for disp in config['DisplayConfig']:
if 'UUID' in disp and disp['UUID'] == uuid:
disp['LinkDescription'] = link_desc.copy()
modified_count += 1
if 'DisplaySets' in data and 'Configs' in data['DisplaySets']:
for config in data['DisplaySets']['Configs']:
if 'DisplayConfig' in config:
for disp in config['DisplayConfig']:
if 'UUID' in disp and disp['UUID'] == uuid:
disp['LinkDescription'] = link_desc.copy()
modified_count += 1
print(f'Modified {modified_count} display config entries for UUID {uuid}')
with open(file, 'wb') as f:
plistlib.dump(data, f, fmt=plistlib.FMT_BINARY)
"
check_success "Modifying plist with Python"
echo "Step 4: Plist modified successfully."
plutil -lint "$TEMP_PLIST"
check_success "Validating plist"
echo "Step 6: Plist validated successfully."
if [ ! -f "${ORIGINAL_PLIST}_backup" ]; then
sudo cp "$ORIGINAL_PLIST" "${ORIGINAL_PLIST}_backup"
check_success "Backing up original file"
echo "Step 7: Original file backed up successfully."
else
echo "Step 7: Backup already exists, skipping (preserving original backup)."
fi
if [ -f "$HOME/Library/Preferences/com.apple.windowserver.displays.plist" ]; then
if [ ! -f "$HOME/Library/Preferences/com.apple.windowserver.displays.plist_backup" ]; then
mv "$HOME/Library/Preferences/com.apple.windowserver.displays.plist" "$HOME/Library/Preferences/com.apple.windowserver.displays.plist_backup"
check_success "Backing up user preferences"
echo "Step 8: User preferences backed up successfully."
else
rm "$HOME/Library/Preferences/com.apple.windowserver.displays.plist"
echo "Step 8: User preferences removed (backup already exists)."
fi
fi
for file in "$HOME/Library/Preferences/ByHost/com.apple.windowserver.displays."*.plist; do
if [ -f "$file" ] && [[ "$file" != *_backup ]]; then
if [ ! -f "${file}_backup" ]; then
mv "$file" "${file}_backup"
check_success "Backing up ByHost file"
echo "Step 9: ByHost file backed up successfully."
else
rm "$file"
echo "Step 9: ByHost file removed (backup already exists)."
fi
fi
done
sudo chown "root" "$TEMP_PLIST"
sudo mv "$TEMP_PLIST" "$ORIGINAL_PLIST"
check_success "Copying modified plist back"
echo "Step 10: Modified file copied back successfully."
if command -v SetFile &>/dev/null; then
sudo SetFile -a T "$ORIGINAL_PLIST" 2>/dev/null || true
fi
sudo chflags uchg "$ORIGINAL_PLIST"
check_success "Locking the file"
echo "Step 11: File locked successfully."
echo ""
echo "=== Verification ==="
link_count=$(python3 -c "
import plistlib
with open('$ORIGINAL_PLIST', 'rb') as f:
data = plistlib.load(f)
count = 0
for section in ['DisplayAnyUserSets', 'DisplaySets']:
if section in data and 'Configs' in data[section]:
for config in data[section]['Configs']:
for disp in config.get('DisplayConfig', []):
if disp.get('UUID') == '$uuid' and 'LinkDescription' in disp:
count += 1
print(f'{section}: BitDepth={disp[\"LinkDescription\"].get(\"BitDepth\")}, PixelEncoding={disp[\"LinkDescription\"].get(\"PixelEncoding\")}, Range={disp[\"LinkDescription\"].get(\"Range\")}')
print(f'Total entries modified: {count}')
")
echo "$link_count"
echo ""
read -p "Changes applied. Reboot required. Do you want to reboot now? (Y/N): " reboot_choice
case "$reboot_choice" in
y | Y) sudo shutdown -r now ;;
n | N) echo "Reboot canceled. Please reboot manually for changes to take effect." ;;
*) echo "Invalid choice. Reboot canceled. Please reboot manually." ;;
esac
verify.sh
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
Show hidden characters
#!/usr/bin/env bash
set -e
ORIGINAL_PLIST="/Library/Preferences/com.apple.windowserver.displays.plist"
TEMP_DIR="$(mktemp -d)" TEMP_PLIST="$TEMP_DIR/com.apple.windowserver.displays.plist"
trap 'rm -rf "$TEMP_DIR"' EXIT
sudo cp "$ORIGINAL_PLIST" "$TEMP_PLIST"
sudo chown "$USER" "$TEMP_PLIST"
python3 <<PYEOF
import plistlib, subprocess, json file = '$TEMP_PLIST'
with open(file, 'rb') as f:
data = plistlib.load(f)
sp_json = json.loads(subprocess.check_output(
['system_profiler', 'SPDisplaysDataType', '-json'],
stderr=subprocess.DEVNULL
))
sp_displays = []
for gpu in sp_json.get('SPDisplaysDataType', []):
sp_displays.extend(gpu.get('spdisplays_ndrvs', []))
name_by_res = {}
for d in sp_displays:
pixels = d.get('_spdisplays_pixels', '')
name = d.get('_name', '')
if pixels and name:
key = pixels.replace(' ', '')
name_by_res[key] = name
pixel_encoding_map = {0: 'RGB', 1: 'YCbCr 4:2:2', 2: 'YCbCr 4:4:4'}
range_map = {0: 'Limited', 1: 'Full'}
eotf_map = {0: 'SDR', 1: 'HDR'}
for section in ['DisplayAnyUserSets', 'DisplaySets']:
if section not in data or 'Configs' not in data[section]:
continue
configs = data[section]['Configs']
if not configs:
continue
config = configs[0]
displays = config.get('DisplayConfig', [])
print(f'\n=== {section} ({len(displays)} display(s)) ===')
for idx, disp in enumerate(displays):
uuid = disp.get('UUID', 'N/A')
link = disp.get('LinkDescription', {})
info = disp.get('CurrentInfo', disp.get('UnmirrorInfo', {}))
w = info.get('Wide', 0)
h = info.get('High', 0)
scale = info.get('Scale', 1)
hz = info.get('Hz', 0)
pixel_w = int(w * scale)
pixel_h = int(h * scale)
res_key = f'{pixel_w}x{pixel_h}'
name = name_by_res.get(res_key, f'Display {idx}')
print(f'\n [{idx + 1}] {name} ({pixel_w}x{pixel_h} @ {hz}Hz)')
print(f' UUID: {uuid}')
if not link:
print(' LinkDescription: (not set)')
continue
bit_depth = link.get('BitDepth', 'N/A')
pixel_enc = link.get('PixelEncoding', 'N/A')
rng = link.get('Range', 'N/A')
eotf = link.get('EOTF', 'N/A')
pixel_label = pixel_encoding_map.get(pixel_enc, str(pixel_enc))
range_label = range_map.get(rng, str(rng))
eotf_label = eotf_map.get(eotf, str(eotf))
print(f' BitDepth: {bit_depth}')
print(f' PixelEncoding: {pixel_enc} ({pixel_label})')
print(f' Range: {rng} ({range_label})')
print(f' EOTF: {eotf} ({eotf_label})')
print()
PYEOF