Skip to content

Instantly share code, notes, and snippets.

@FelikZ
Last active February 20, 2026 18:02
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
# List displays from plist, matched with system_profiler names by resolution
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
# 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
#!/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
@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

@FelikZ
Copy link
Author

FelikZ commented Feb 20, 2026

Method 3

$ ./verify.sh

=== DisplayAnyUserSets (2 display(s)) ===

  [1] Color LCD (2992x1934 @ 120.0Hz)
      UUID:          37D8832A-2D66-02CA-B9F7-8F30A301B230
      BitDepth:      8
      PixelEncoding: 0 (RGB)
      Range:         1 (Full)
      EOTF:          0 (SDR)

  [2] SwitchResX4 - 27GL850 (2560x1440 @ 100.0Hz)
      UUID:          A3C3F35C-6BE5-4C0C-99F1-702C732633B3
      BitDepth:      10
      PixelEncoding: 0 (RGB)
      Range:         1 (Full)
      EOTF:          0 (SDR)

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