153 lines
5.0 KiB
Go
153 lines
5.0 KiB
Go
//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 <CoreGraphics/CoreGraphics.h>
|
|
#import <AppKit/AppKit.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <dlfcn.h>
|
|
|
|
// 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
|
|
}
|