Here's some code that should do it. On the one hand, I wasn't able to test on a multi-monitor system yet, but, on the other, the code was written without any assumptions about which display to use or where it is positioned. So, it should work.
CGDirectDisplayID displays[32];
uint32_t count;
if (CGGetActiveDisplayList(sizeof(displays)/sizeof(displays[0]), displays, &count) != kCGErrorSuccess)
{
NSLog(@"failed to get display list");
exit(EXIT_FAILURE);
}
CGRect rect = CGRectNull;
CGRect primaryDisplayRect = CGRectZero;
for (uint32_t i = 0; i < count; i++)
{
// if display is secondary mirror of another display, skip it
if (CGDisplayMirrorsDisplay(displays[i]) != kCGNullDirectDisplay)
continue;
CGRect displayRect = CGDisplayBounds(displays[i]);
if (i == 0)
primaryDisplayRect = displayRect;
displayRect.origin.y = CGRectGetMaxY(primaryDisplayRect) - CGRectGetMaxY(displayRect);
rect = CGRectUnion(rect, displayRect);
}
NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
pixelsWide:CGRectGetWidth(rect)
pixelsHigh:CGRectGetHeight(rect)
bitsPerSample:8
samplesPerPixel:4
hasAlpha:YES
isPlanar:NO
colorSpaceName:NSCalibratedRGBColorSpace
bitmapFormat:0
bytesPerRow:0
bitsPerPixel:32];
if (!imageRep)
{
NSLog(@"failed to create bitmap image rep");
exit(EXIT_FAILURE);
}
NSGraphicsContext* context = [NSGraphicsContext graphicsContextWithBitmapImageRep:imageRep];
if (!context)
{
NSLog(@"failed to create graphics context");
exit(EXIT_FAILURE);
}
[NSGraphicsContext saveGraphicsState];
{
[NSGraphicsContext setCurrentContext:context];
CGContextRef cgcontext = [context graphicsPort];
CGContextClearRect(cgcontext, CGRectMake(0, 0, CGRectGetWidth(rect), CGRectGetHeight(rect)));
for (uint32_t i = 0; i < count; i++)
{
// if display is secondary mirror of another display, skip it
if (CGDisplayMirrorsDisplay(displays[i]) != kCGNullDirectDisplay)
continue;
CGRect displayRect = CGDisplayBounds(displays[i]);
displayRect.origin.y = CGRectGetMaxY(primaryDisplayRect) - CGRectGetMaxY(displayRect);
CGImageRef image = CGDisplayCreateImage(displays[i]);
if (!image)
continue;
CGRect dest = CGRectMake(displayRect.origin.x - rect.origin.x,
displayRect.origin.y - rect.origin.y,
displayRect.size.width,
displayRect.size.height);
CGContextDrawImage(cgcontext, dest, image);
CGImageRelease(image);
}
[[NSGraphicsContext currentContext] flushGraphics];
}
[NSGraphicsContext restoreGraphicsState];
NSData* data = [imageRep representationUsingType:NSPNGFileType properties:@{ }];
[data writeToFile:@"/tmp/screenshot.png" atomically:YES];
The main possible point of failure is in allocating a bitmap image context for a rectangle large enough to encompass all displays. Note that the total rect for all displays can be much larger than the rect for any one. For example, if two monitors are arranged so that they barely touch at a corner, the rectangle encompassing them would be nearly as big as four monitors in a 2x2 arrangement. For three monitors, it can be as big as 9 monitors in a 3x3 arrangement. Etc.
Here's an implementation that doesn't use Cocoa, just Core Graphics:
CGDirectDisplayID displays[32];
uint32_t count;
if (CGGetActiveDisplayList(sizeof(displays)/sizeof(displays[0]), displays, &count) != kCGErrorSuccess)
{
NSLog(@"failed to get display list");
exit(EXIT_FAILURE);
}
CGRect rect = CGRectNull;
for (uint32_t i = 0; i < count; i++)
{
// if display is secondary mirror of another display, skip it
if (CGDisplayMirrorsDisplay(displays[i]) != kCGNullDirectDisplay)
continue;
rect = CGRectUnion(rect, CGDisplayBounds(displays[i]));
}
CGColorSpaceRef colorspace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
if (!colorspace)
{
NSLog(@"failed to create colorspace");
exit(EXIT_FAILURE);
}
CGContextRef cgcontext = CGBitmapContextCreate(NULL, CGRectGetWidth(rect), CGRectGetHeight(rect), 8, 0, colorspace, (CGBitmapInfo)kCGImageAlphaPremultipliedFirst);
CGColorSpaceRelease(colorspace);
if (!cgcontext)
{
NSLog(@"failed to create bitmap context");
exit(EXIT_FAILURE);
}
CGContextClearRect(cgcontext, CGRectMake(0, 0, CGRectGetWidth(rect), CGRectGetHeight(rect)));
for (uint32_t i = 0; i < count; i++)
{
// if display is secondary mirror of another display, skip it
if (CGDisplayMirrorsDisplay(displays[i]) != kCGNullDirectDisplay)
continue;
CGRect displayRect = CGDisplayBounds(displays[i]);
CGImageRef image = CGDisplayCreateImage(displays[i]);
if (!image)
continue;
CGRect dest = CGRectMake(displayRect.origin.x - rect.origin.x,
displayRect.origin.y - rect.origin.y,
displayRect.size.width,
displayRect.size.height);
CGContextDrawImage(cgcontext, dest, image);
CGImageRelease(image);
}
CGImageRef image = CGBitmapContextCreateImage(cgcontext);
CGContextRelease(cgcontext);
if (!image)
{
NSLog(@"failed to create image from bitmap context");
exit(EXIT_FAILURE);
}
CFURLRef url = CFURLCreateWithFileSystemPath(NULL, CFSTR("/tmp/screenshot.png"), kCFURLPOSIXPathStyle, NO);
if (!url)
{
NSLog(@"failed to create URL");
exit(EXIT_FAILURE);
}
CGImageDestinationRef dest = CGImageDestinationCreateWithURL(url, kUTTypePNG, 1, NULL);
CFRelease(url);
if (!dest)
{
NSLog(@"failed to create image destination");
exit(EXIT_FAILURE);
}
CGImageDestinationAddImage(dest, image, NULL);
CGImageRelease(image);
if (!CGImageDestinationFinalize(dest))
{
NSLog(@"failed to finalize image destination");
exit(EXIT_FAILURE);
}
CFRelease(dest);