Skip to content

Instantly share code, notes, and snippets.

@FelikZ
Last active December 8, 2025 14:39
Show Gist options
  • Select an option

  • Save FelikZ/e287c60e0407f9eeb5434471ad050ff9 to your computer and use it in GitHub Desktop.

Select an option

Save FelikZ/e287c60e0407f9eeb5434471ad050ff9 to your computer and use it in GitHub Desktop.
Fix RGB Range Limit on MacOS
#!/usr/bin/env bash
# Script to force RGB Color Output on M1 and M2 based Macs for a selected display
# Function to display an error message and exit
function error_exit {
echo "Error: $1" >&2
exit 1
}
# Function to check if a command executed successfully
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"
# Step 1: Unlock the file if locked
sudo chflags nouchg "$ORIGINAL_PLIST" || true
echo "Step 1: File unlocked if necessary."
# Step 2: Copy the plist to temp directory
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
# Get number of displays from plist using Python plistlib
num=$(python3 -c "
import plistlib
with open('$TEMP_PLIST', 'rb') as f:
data = plistlib.load(f)
print(len(data['DisplayAnyUserSets']['Configs'][0]['DisplayConfig']))
" 2>/dev/null)
if [ -z "$num" ] || [ "$num" -eq 0 ]; then
error_exit "Unable to parse number of displays from plist."
fi
# Get display names from system_profiler
mapfile -t display_names < <(system_profiler SPDisplaysDataType | awk '/^ [^:]+:/ { gsub(/:/,""); gsub(/^ /,""); print }' | grep -v '^ ')
# Check if numbers match
if [ ${#display_names[@]} -ne $num ]; then
echo "Mismatch in number of displays detected (${#display_names[@]} vs $num). Using indices only."
display_names=()
for ((j=0; j<num; j++)); do
display_names+=("Display $j")
done
fi
# List displays
echo "Connected displays:"
for j in "${!display_names[@]}"; do
echo "$((j+1)): ${display_names[j]}"
done
# Ask user to choose
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))
# Get the UUID of the selected display
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}..."
# Ask user for bit depth
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
# Step 4: Modify the plist using embedded Python with plistlib
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
# Modify DisplayAnyUserSets section
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
# Modify DisplaySets section (CRITICAL - was missing before!)
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."
# Step 5: No conversion needed since edited in binary
# Step 6: Validate the plist
plutil -lint "$TEMP_PLIST"
check_success "Validating plist"
echo "Step 6: Plist validated successfully."
# Step 7: Backup original file (only if backup doesn't exist, to preserve original)
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
# Step 8: Remove user preferences if exists (they can override system settings)
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
# Step 9: Remove ByHost preferences if exists (they can override system settings)
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
# Step 10: Copy modified plist back
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."
# Step 11: Set Stationery flag and lock the file (as per original gist)
# Set Stationery Pad flag BEFORE locking (using SetFile if available)
if command -v SetFile &> /dev/null; then
sudo SetFile -a T "$ORIGINAL_PLIST" 2>/dev/null || true
fi
# Lock the file
sudo chflags uchg "$ORIGINAL_PLIST"
check_success "Locking the file"
echo "Step 11: File locked successfully."
# Step 12: Verify the changes were applied
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 ""
# Step 13: Ask for reboot
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
@FelikZ
Copy link
Author

FelikZ commented Dec 8, 2025

To verify that it actually works. (10 Bit, RGB, Full Range)
Method 1

python3 -c "
import plistlib
with open('/Library/Preferences/com.apple.windowserver.displays.plist', 'rb') as f:
    data = plistlib.load(f)
for config in data['DisplaySets']['Configs']:
    for disp in config.get('DisplayConfig', []):
        if disp.get('UUID') == '7C8B2481-D9F5-4817-8429-A746119C302D':
            print('DisplaySets LinkDescription:', disp.get('LinkDescription', 'MISSING'))
"
# DisplaySets LinkDescription: {'BitDepth': 10, 'EOTF': 0, 'PixelEncoding': 0, 'Range': 1}
# DisplaySets LinkDescription: {'BitDepth': 10, 'EOTF': 0, 'PixelEncoding': 0, 'Range': 1}

Method 2
via BetterDisplay utility. It has color menu for each monitor, with all possible modes.

PS. The algorithm is based on the following gist

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment