Reputation: 6089
I'm working on improving open source library to capture screenshots: https://github.com/kbinani/screenshot - it is somewhat popular and it uses legacy approach, and MacOS 15 (beta as of time of writing) already complains that "CGDisplayCreateImageForRect' is unavailable: obsoleted in macOS 15.0". So you can't use it on macOS 15.0 - will not compile.
I spent a couple of days trying to use ScreenCaptureKit from Apple. Which is a new way of doing things. And it works, but not in Go with C bindings.
How do I implement SCScreenshotManager_captureImage
in C99?
I have working code in swift (works absolutely fine):
import Foundation
import ScreenCaptureKit
import CoreGraphics
import AppKit
// This program captures a screenshot using Apple's ScreenCaptureKit framework.
// It detects multiple monitors and allows capturing from a specific display or the main display.
// To run this program:
// 1. Make sure you have Xcode and Swift installed on your Mac.
// 2. Save this code in a file named 'demo.swift'.
// 3. Open Terminal, navigate to the directory containing the file.
// 4. Run: swift demo.swift
// Function to capture screenshot
func captureScreenshot(display: SCDisplay?, completion: @escaping (NSImage?) -> Void) {
let filter: SCContentFilter
if let display = display {
filter = SCContentFilter(display: display, excludingApplications: [], exceptingWindows: [])
} else {
filter = SCContentFilter()
}
let configuration = SCStreamConfiguration()
configuration.width = 1920 // You can adjust this
configuration.height = 1080 // You can adjust this
configuration.showsCursor = true
configuration.scalesToFit = false
SCScreenshotManager.captureImage(contentFilter: filter, configuration: configuration) { image, error in
if let error = error {
print("Error capturing screenshot: \(error.localizedDescription)")
completion(nil)
} else if let image = image {
completion(NSImage(cgImage: image, size: NSSize(width: image.width, height: image.height)))
} else {
print("No image captured")
completion(nil)
}
}
}
// Main execution
func main() {
let semaphore = DispatchSemaphore(value: 0)
Task {
do {
let content = try await SCShareableContent.current
guard !content.displays.isEmpty else {
print("No displays found")
semaphore.signal()
return
}
print("Available displays:")
for (index, display) in content.displays.enumerated() {
print("\(index + 1). \(display.displayID) - \(display.width)x\(display.height)")
}
print("Enter the number of the display to capture (or press Enter for main display):")
let input = readLine()?.trimmingCharacters(in: .whitespacesAndNewlines)
let selectedDisplay: SCDisplay?
if let index = Int(input ?? ""), (1...content.displays.count).contains(index) {
selectedDisplay = content.displays[index - 1]
} else {
selectedDisplay = content.displays.first
}
captureScreenshot(display: selectedDisplay) { image in
if let image = image {
let currentDirectoryURL = URL(fileURLWithPath: FileManager.default.currentDirectoryPath)
let fileURL = currentDirectoryURL.appendingPathComponent("screenshot.png")
if let tiffData = image.tiffRepresentation,
let bitmapImage = NSBitmapImageRep(data: tiffData),
let pngData = bitmapImage.representation(using: .png, properties: [:]) {
do {
try pngData.write(to: fileURL)
print("Screenshot saved to: \(fileURL.path)")
} catch {
print("Error saving screenshot: \(error.localizedDescription)")
}
} else {
print("Error converting image to PNG data")
}
} else {
print("Failed to capture screenshot")
}
semaphore.signal()
}
} catch {
print("Error getting shareable content: \(error.localizedDescription)")
semaphore.signal()
}
}
semaphore.wait()
}
main()
But when it comes to making a Go package that just works - nothing works. I've spent hours trying to make it work. For Stackoverflow I created GitHub repo to demonstrate the problem with instructions: https://github.com/ro31337/screenshot_macos
The main_screencapturekit.go
has the method that is not implemented (well, I tried to implement it , but ended up with solution that just sits there and does nothing, I don't provide these extra few lines so you're not derailed):
CGImageRef SCScreenshotManager_captureImage(SCContentFilter* filter, SCStreamConfiguration* config) {
__block CGImageRef capturedImage = NULL;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
NSLog(@"Initializing SCStream...");
SCStream* stream = [[SCStream alloc] initWithFilter:filter configuration:config delegate:nil];
NSLog(@"Starting capture...");
[stream startCaptureWithCompletionHandler:^(NSError * _Nullable error) {
if (error) {
NSLog(@"Error starting capture: %@", error.localizedDescription);
NSLog(@"Error domain: %@", error.domain);
NSLog(@"Error code: %ld", (long)error.code);
NSLog(@"Error user info: %@", error.userInfo);
dispatch_semaphore_signal(semaphore);
} else {
NSLog(@"Capture started successfully, attempting to capture screenshot...");
[stream stopCaptureWithCompletionHandler:^(NSError * _Nullable error) {
if (error) {
NSLog(@"Error stopping capture: %@", error.localizedDescription);
NSLog(@"Error domain: %@", error.domain);
NSLog(@"Error code: %ld", (long)error.code);
NSLog(@"Error user info: %@", error.userInfo);
} else {
NSLog(@"Capture stopped successfully");
}
dispatch_semaphore_signal(semaphore);
}];
}
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
[stream release];
return capturedImage;
}
It's currently producing good output and new ScreenCaptureKit bindings do work (with whole bunch of warnings I wasn't able to get rid of):
2024-08-07 19:16:31.094 main_screencapturekit[10934:95763] Number of displays: 1
2024-08-07 19:16:31.152 main_screencapturekit[10934:95763] Display info - Index: 0, Width: 1440, Height: 900, X: 0, Y: 0, ID: 69734272
2024-08-07 19:16:31.206 main_screencapturekit[10934:95763] Display info - Index: 0, Width: 1440, Height: 900, X: 0, Y: 0, ID: 69734272
2024-08-07 19:16:31.259 main_screencapturekit[10934:95763] SCContentFilter created successfully for display ID: 69734272
2024-08-07 19:16:31.259 main_screencapturekit[10934:95759] SCStreamConfiguration initialized with width: 1920, height: 1080
2024-08-07 19:16:31.259 main_screencapturekit[10934:95759] SCStreamConfiguration width set to: 1440
2024-08-07 19:16:31.259 main_screencapturekit[10934:95759] SCStreamConfiguration height set to: 900
2024-08-07 19:16:31.259 main_screencapturekit[10934:95759] SCStreamConfiguration showsCursor set to: 0
2024-08-07 19:16:31.259 main_screencapturekit[10934:95759] Initializing SCStream...
2024-08-07 19:16:31.371 main_screencapturekit[10934:95759] Starting capture...
2024-08-07 19:16:31.421 main_screencapturekit[10934:95764] Capture started successfully, attempting to capture screenshot...
2024-08-07 19:16:31.424 main_screencapturekit[10934:95825] Capture stopped successfully
#0 : (0,0)-(1440,900) "0_1440x900.png"
(0,0)-(1440,900)
2024-08-07 19:16:31.510 main_screencapturekit[10934:95825] Display info - Index: 0, Width: 1440, Height: 900, X: 0, Y: 0, ID: 69734272
2024-08-07 19:16:31.566 main_screencapturekit[10934:95825] SCContentFilter created successfully for display ID: 69734272
2024-08-07 19:16:31.566 main_screencapturekit[10934:95759] SCStreamConfiguration initialized with width: 1920, height: 1080
2024-08-07 19:16:31.566 main_screencapturekit[10934:95759] SCStreamConfiguration width set to: 1440
2024-08-07 19:16:31.566 main_screencapturekit[10934:95759] SCStreamConfiguration height set to: 900
2024-08-07 19:16:31.566 main_screencapturekit[10934:95759] SCStreamConfiguration showsCursor set to: 0
2024-08-07 19:16:31.566 main_screencapturekit[10934:95759] Initializing SCStream...
2024-08-07 19:16:31.567 main_screencapturekit[10934:95759] Starting capture...
2024-08-07 19:16:31.610 main_screencapturekit[10934:95764] Capture started successfully, attempting to capture screenshot...
2024-08-07 19:16:31.612 main_screencapturekit[10934:95824] Capture stopped successfully
So we definitely have progress. Bindings do work. But now we have to capture the stream I guess? But how? I don't even know where to look.
I hope somebody out there can help me out, so we can improve the cross-platform screenshot making library for newest MacOS.
Upvotes: 0
Views: 525