//go:build darwin package screen /* #cgo CFLAGS: -x objective-c -Wno-deprecated-declarations -Wno-unguarded-availability-new #cgo LDFLAGS: -framework CoreGraphics -framework Foundation -framework AppKit // Disable availability checks - we handle this at runtime #define __API_UNAVAILABLE(...) #define API_UNAVAILABLE(...) #import #import #include #include #include // Use dlsym to call CGWindowListCreateImage dynamically to bypass macOS 15 availability check typedef CGImageRef (*CGWindowListCreateImageFunc)(CGRect, CGWindowListOption, CGWindowID, CGWindowImageOption); static unsigned char* CaptureFullDesktop(int* outLen, int quality) { // Dynamically load CGWindowListCreateImage to bypass compile-time availability check static CGWindowListCreateImageFunc createImageFunc = NULL; if (!createImageFunc) { void *handle = dlopen("/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics", RTLD_LAZY); if (handle) { createImageFunc = (CGWindowListCreateImageFunc)dlsym(handle, "CGWindowListCreateImage"); } } if (!createImageFunc) { *outLen = 0; return NULL; } CGImageRef image = createImageFunc( CGRectInfinite, kCGWindowListOptionOnScreenOnly, kCGNullWindowID, kCGWindowImageDefault ); if (!image) { *outLen = 0; return NULL; } @autoreleasepool { // Calculate new size maintaining aspect ratio (limiting to max 1024x768) CGFloat originalWidth = CGImageGetWidth(image); CGFloat originalHeight = CGImageGetHeight(image); CGFloat maxWidth = 1024.0f; CGFloat maxHeight = 768.0f; CGFloat ratio = 1.0f; if (originalWidth > maxWidth || originalHeight > maxHeight) { CGFloat ratioW = maxWidth / originalWidth; CGFloat ratioH = maxHeight / originalHeight; ratio = ratioW < ratioH ? ratioW : ratioH; } size_t newWidth = (size_t)(originalWidth * ratio); size_t newHeight = (size_t)(originalHeight * ratio); if (newWidth < 1) newWidth = 1; if (newHeight < 1) newHeight = 1; // Create bitmap context CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGContextRef context = CGBitmapContextCreate(NULL, newWidth, newHeight, 8, newWidth * 4, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big); CGColorSpaceRelease(colorSpace); if (!context) { CGImageRelease(image); *outLen = 0; return NULL; } // Draw image into context to resize CGContextSetInterpolationQuality(context, kCGInterpolationHigh); CGContextDrawImage(context, CGRectMake(0, 0, newWidth, newHeight), image); CGImageRelease(image); // Get resized image CGImageRef resizedImage = CGBitmapContextCreateImage(context); CGContextRelease(context); if (!resizedImage) { *outLen = 0; return NULL; } NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] initWithCGImage:resizedImage]; CGImageRelease(resizedImage); if (!rep) { *outLen = 0; return NULL; } float q = (float)quality / 100.0f; if (q < 0.1f) q = 0.1f; if (q > 1.0f) q = 1.0f; NSDictionary *props = @{NSImageCompressionFactor: @(q)}; NSData *jpegData = [rep representationUsingType:NSBitmapImageFileTypeJPEG properties:props]; if (!jpegData || jpegData.length == 0) { [rep release]; *outLen = 0; return NULL; } *outLen = (int)jpegData.length; unsigned char *buf = (unsigned char*)malloc(jpegData.length); memcpy(buf, jpegData.bytes, jpegData.length); [rep release]; return buf; } } */ import "C" import ( "encoding/base64" "fmt" "log" "unsafe" ) // CaptureScreen captures the full desktop on macOS using CGWindowListCreateImage (via dlsym) // Returns a data:image/jpeg;base64,... string func CaptureScreen() (string, error) { var outLen C.int quality := C.int(50) // JPEG quality 50% buf := C.CaptureFullDesktop(&outLen, quality) if buf == nil || int(outLen) == 0 { return "", fmt.Errorf("failed to capture screen: CGWindowListCreateImage returned nil") } defer C.free(unsafe.Pointer(buf)) jpegBytes := C.GoBytes(unsafe.Pointer(buf), outLen) if len(jpegBytes) < 100 { log.Printf("[SCREEN] Warning: captured image is very small (%d bytes), may indicate permission issue", len(jpegBytes)) } encoded := base64.StdEncoding.EncodeToString(jpegBytes) return "data:image/jpeg;base64," + encoded, nil }