Skip to content

Instantly share code, notes, and snippets.

@danii1
Last active March 10, 2026 11:32
Show Gist options
  • Select an option

  • Save danii1/99fa5f849a4d42807418259abfbf03d5 to your computer and use it in GitHub Desktop.

Select an option

Save danii1/99fa5f849a4d42807418259abfbf03d5 to your computer and use it in GitHub Desktop.
Force EDID override on Apple Silicon Macs using private IOAVService API (fixes KVM switch 4K issues)

force-edid

Force a custom EDID on external displays connected to Apple Silicon Macs (M1/M2/M3/M4).

Uses the private IOAVServiceSetVirtualEDIDMode API to dynamically override EDID at runtime — the only method that works on Apple Silicon (file-based plist overrides are ignored).

Use case

KVM switches often report fake/limited EDID to macOS, causing external displays to be stuck at 1080p even when they support 4K. This tool lets you apply the monitor's real EDID, unlocking the full resolution.

Requirements

  • Apple Silicon Mac (M1/M2/M3/M4)
  • macOS Ventura or later
  • Xcode Command Line Tools (xcode-select --install)
  • A raw EDID binary file (128 or 256 bytes) from your monitor

Build

clang -framework Foundation -framework IOKit -o force-edid force-edid.m

Usage

Apply a custom EDID

./force-edid /path/to/your-monitor.bin

Reset to the original EDID

./force-edid --reset

Full workflow: fix 4K + enable HiDPI

After applying the EDID, you can use displayplacer to switch to your desired resolution:

# Install displayplacer
brew install displayplacer

# Apply your monitor's EDID
./force-edid /path/to/your-monitor.bin

# Wait for the display to re-negotiate
sleep 3

# Find the external display ID and set HiDPI (4K rendered, looks like 1920x1080)
SCREEN_ID=$(displayplacer list 2>/dev/null | grep -B5 'external screen' | grep 'Persistent' | awk '{print $NF}')
displayplacer "id:$SCREEN_ID res:1920x1080 hz:60 color_depth:8 scaling:on"

Getting your monitor's EDID

You can dump the EDID when the monitor is connected directly (without the KVM):

ioreg -l -w0 -r -c IOMobileFramebuffer | grep -A1 "IODisplayEDID" | tail -1 | sed 's/.*<//;s/>//' | xxd -r -p > monitor.bin

Or use tools like Lunar, AW EDID Editor (Windows), or a hardware EDID reader.

How it works

On Apple Silicon, display communication goes through the DCP (Display CoProcessor). Traditional EDID override plists placed in /Library/Displays/Contents/Resources/Overrides/ are ignored. This tool uses the private IOAVServiceSetVirtualEDIDMode function from the IOKit framework to inject a custom EDID at runtime, which triggers a display re-negotiation with the new capabilities.

Limitations

  • The override is not persistent across reboots — you need to reapply it after each restart
  • Uses private/undocumented Apple APIs that could break with future macOS updates
  • Only works on Apple Silicon Macs (Intel Macs should use the traditional plist override method)

Credits

Built with knowledge from:

// force-edid: Apply a custom EDID to an external display on Apple Silicon
// Uses private IOAVService API (IOAVServiceSetVirtualEDIDMode)
#import <Foundation/Foundation.h>
#import <IOKit/IOKitLib.h>
// Private API declarations
typedef CFTypeRef IOAVServiceRef;
extern IOAVServiceRef IOAVServiceCreateWithService(CFAllocatorRef allocator, io_service_t service);
extern IOReturn IOAVServiceCopyEDID(IOAVServiceRef service, CFDataRef *edidData);
extern IOReturn IOAVServiceSetVirtualEDIDMode(IOAVServiceRef service, uint32_t mode, CFDataRef edidData);
int main(int argc, const char *argv[]) {
@autoreleasepool {
if (argc < 2) {
fprintf(stderr, "Usage: force-edid <edid-file.bin> [--reset]\n");
fprintf(stderr, " <edid-file.bin> Path to raw EDID binary file\n");
fprintf(stderr, " --reset Reset to original EDID (pass 0 as mode)\n");
return 1;
}
BOOL reset = NO;
NSString *edidPath = nil;
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--reset") == 0) {
reset = YES;
} else {
edidPath = [NSString stringWithUTF8String:argv[i]];
}
}
// Load EDID file
NSData *edidData = nil;
if (!reset) {
if (!edidPath) {
fprintf(stderr, "Error: No EDID file specified\n");
return 1;
}
edidData = [NSData dataWithContentsOfFile:edidPath];
if (!edidData) {
fprintf(stderr, "Error: Cannot read EDID file: %s\n", [edidPath UTF8String]);
return 1;
}
printf("Loaded EDID: %lu bytes from %s\n", (unsigned long)[edidData length], [edidPath UTF8String]);
}
// Find external display services
io_iterator_t iterator;
kern_return_t kr = IOServiceGetMatchingServices(
kIOMainPortDefault,
IOServiceMatching("DCPAVServiceProxy"),
&iterator
);
if (kr != KERN_SUCCESS) {
fprintf(stderr, "Error: Cannot find DCPAVServiceProxy services (kr=%d)\n", kr);
return 1;
}
io_service_t service;
int displayCount = 0;
BOOL applied = NO;
while ((service = IOIteratorNext(iterator)) != IO_OBJECT_NULL) {
CFStringRef location = IORegistryEntrySearchCFProperty(
service, kIOServicePlane, CFSTR("Location"),
kCFAllocatorDefault, kIORegistryIterateRecursively
);
NSString *loc = (__bridge_transfer NSString *)location;
printf("Found display service: Location=%s\n", loc ? [loc UTF8String] : "(null)");
// Skip internal display
if (loc && [loc isEqualToString:@"Embedded"]) {
printf(" Skipping (built-in display)\n");
IOObjectRelease(service);
continue;
}
displayCount++;
IOAVServiceRef avService = IOAVServiceCreateWithService(kCFAllocatorDefault, service);
if (!avService) {
fprintf(stderr, " Error: Cannot create IOAVService\n");
IOObjectRelease(service);
continue;
}
// Read current EDID
CFDataRef currentEDID = NULL;
IOReturn result = IOAVServiceCopyEDID(avService, &currentEDID);
if (result == kIOReturnSuccess && currentEDID) {
printf(" Current EDID: %ld bytes\n", CFDataGetLength(currentEDID));
CFRelease(currentEDID);
}
if (reset) {
printf(" Resetting to original EDID...\n");
result = IOAVServiceSetVirtualEDIDMode(avService, 0, NULL);
} else {
printf(" Applying custom EDID (%lu bytes)...\n", (unsigned long)[edidData length]);
// mode=1 to set virtual EDID, mode=0 to reset
result = IOAVServiceSetVirtualEDIDMode(avService, 1, (__bridge CFDataRef)edidData);
}
if (result == kIOReturnSuccess) {
printf(" Success! EDID applied.\n");
applied = YES;
} else {
fprintf(stderr, " Failed with IOReturn: 0x%x\n", result);
}
CFRelease(avService);
IOObjectRelease(service);
}
IOObjectRelease(iterator);
if (displayCount == 0) {
fprintf(stderr, "No external displays found.\n");
return 1;
}
if (!applied) {
fprintf(stderr, "Failed to apply EDID to any display.\n");
return 1;
}
printf("\nDone. The display may flicker as it re-negotiates.\n");
return 0;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment