ScreenCaptureKit example in Go/C

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.

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật