Skip to content

macos/screencapturekit: add support with TCC error handling and example #279

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# DarwinKit Development Notes

## Useful Commands

### Build & Test
```bash
# Build the entire project
go build ./...

# Run tests
go test ./...

# Run specific tests
go test ./macos/appkit
```

### Example Applications
```bash
# Run the screenshot example
cd macos/_examples/screenshot && go run main.go

# Run the webview example
cd macos/_examples/webview && go run main.go
```

## Framework Information

### ScreenCaptureKit
The ScreenCaptureKit implementation supports taking screenshots with robust TCC permission handling:

```go
// Basic screenshot
manager := screencapturekit.SCScreenshotManager_SharedManager()
manager.CaptureScreenshotWithCompletion(func(image appkit.Image, err screencapturekit.SCError) {
// Process image or handle error
})

// With retry for TCC permission issues
manager.CaptureScreenshotWithRetry(3, time.Second, func(image appkit.Image, err screencapturekit.SCError, success bool) {
// Handle result with automatic retry for permission issues
})
```

When using ScreenCaptureKit, be aware that:
1. Users must grant screen recording permission via System Preferences
2. Permission issues are common and should be handled gracefully
3. The application may need to be restarted after permission is granted

### Naming Patterns
- Class method calls: `ClassName_MethodName()`
- Instance methods: `instance.MethodName()`
- Create objects: `ClassName_New()` or similar constructor patterns

## Code Style Preferences

### Imports
Order imports as follows:
1. Standard library imports
2. External library imports
3. DarwinKit imports, with `objc` last

```go
import (
"fmt"
"os"

"github.com/progrium/darwinkit/macos/appkit"
"github.com/progrium/darwinkit/macos/foundation"
"github.com/progrium/darwinkit/objc"
)
```

### Error Handling
For Objective-C errors:
- Check `IsNil()` on error objects before using them
- Use descriptive error messages when possible
- Implement the `Error()` interface on custom error types

## Memory Management
Remember to handle memory appropriately:
- Use `objc.CreateMallocBlock` for callbacks
- Check for `nil` objects before accessing them
- Be aware of ownership rules when working with Objective-C objects
34 changes: 34 additions & 0 deletions macos/_examples/screenshot/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# ScreenCaptureKit Screenshot Example

This example demonstrates how to use DarwinKit's ScreenCaptureKit implementation to take screenshots with proper permission handling.

## Features Demonstrated

- Taking screenshots with SCScreenshotManager
- Handling TCC permission issues with retry logic
- Providing user guidance when permissions are denied
- Saving screenshots to disk as PNG files

## Running the Example

```bash
go run main.go
```

## Code Overview

1. Initializes SCScreenshotManager
2. Sets up a handler to process the captured image
3. Uses retry logic to handle temporary permission issues
4. Provides guidance if permissions are persistently denied
5. Converts the captured image to PNG format
6. Saves the image to the desktop

## Permission Handling

The example demonstrates best practices for handling TCC permissions:

- Automatic retry for temporary issues
- Clear instructions for the user
- Integration with System Preferences
- Graceful failure with helpful messaging
7 changes: 7 additions & 0 deletions macos/_examples/screenshot/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module github.com/progrium/darwinkit/macos/_examples/screenshot

go 1.21

replace github.com/progrium/darwinkit => ../../../

require github.com/progrium/darwinkit v0.0.0-00010101000000-000000000000
117 changes: 117 additions & 0 deletions macos/_examples/screenshot/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package main

import (
"fmt"
"os"
"path/filepath"
"runtime"
"time"

"github.com/progrium/darwinkit/macos/appkit"
"github.com/progrium/darwinkit/macos/foundation"
"github.com/progrium/darwinkit/macos/screencapturekit"
)

func main() {
// Ensure we run on the main thread
runtime.LockOSThread()

fmt.Println("ScreenCaptureKit Screenshot Example")
fmt.Println("-----------------------------------")

// Get the screenshot manager
manager := screencapturekit.SCScreenshotManager_SharedManager()

// Create a completion handler to process the screenshot
screenshotComplete := make(chan bool, 1)

// Define the retry handler
retryHandler := func(image appkit.Image, err screencapturekit.SCError, success bool) {
if \!success {
fmt.Printf("Error taking screenshot after retries: %v\n", err)

// Handle TCC permission issues
if err.IsTCCError() {
fmt.Println("\n*** Screen Recording Permission Required ***")
fmt.Println(err.GetPermissionInstructions())

// Open System Preferences
fmt.Println("Opening System Preferences to help you grant permission...")
screencapturekit.OpenSystemPreferencesTCC()

fmt.Println("\nPlease restart this application after granting permission.")
}

screenshotComplete <- false
return
}

fmt.Println("Screenshot captured successfully\!")

// Get desktop path for saving
homeDir, err := os.UserHomeDir()
if err \!= nil {
fmt.Printf("Error getting home directory: %v\n", err)
screenshotComplete <- false
return
}

// Create filename with timestamp
desktopPath := filepath.Join(homeDir, "Desktop")
filename := filepath.Join(desktopPath, fmt.Sprintf("darwinkit_screenshot_%d.png", time.Now().Unix()))

fmt.Printf("Saving screenshot to: %s\n", filename)

// Convert to PNG data
tiffData := image.TIFFRepresentation()
if tiffData.IsNil() {
fmt.Println("Failed to convert image to TIFF")
screenshotComplete <- false
return
}

imageRep := appkit.NSBitmapImageRep_ImageRepWithData(tiffData)
if imageRep.IsNil() {
fmt.Println("Failed to create image representation")
screenshotComplete <- false
return
}

pngData := imageRep.RepresentationUsingTypeProperties(
appkit.NSBitmapImageFileTypePNG,
nil,
)
if pngData.IsNil() {
fmt.Println("Failed to convert to PNG")
screenshotComplete <- false
return
}

// Create NSString for the path
filePath := foundation.NSString_StringWithString(filename)

// Write to file
ok := pngData.WriteToFileAtomically(filePath, true)
if \!ok {
fmt.Println("Failed to write image to file")
screenshotComplete <- false
return
}

fmt.Printf("Screenshot saved to %s\n", filename)
screenshotComplete <- true
}

// Take screenshot with retry for TCC issues
fmt.Println("Taking screenshot (with automatic retry for permission issues)...")
manager.CaptureScreenshotWithRetry(3, 1*time.Second, retryHandler)

// Wait for completion
result := <-screenshotComplete
if result {
fmt.Println("\nScreenshot process completed successfully\!")
} else {
fmt.Println("\nScreenshot process failed.")
os.Exit(1)
}
}
Loading
Loading