Conversation
There was a problem hiding this comment.
Pull Request Overview
This PR enhances the screenshot functionality with screen-under-mouse detection and cropping support, creating a more "native screenshot feel" for multi-screen setups.
Key changes:
- Modified screenshot provider protocol to support cropping rectangles
- Moved cropping logic from AppCoordinator to ScreenshotService
- Added comprehensive test coverage for screenshot functionality
Reviewed Changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| ComfyMarkTests/Screenshot/ScreenshotProviderTests.swift | Comprehensive test suite covering basic functionality, edge cases, and concurrency scenarios |
| ComfyMarkTests/Screenshot/MockScreenshotProvider.swift | Mock implementation with configurable behavior for testing |
| ComfyMarkTests/ComfyMarkTests.swift | Removed old test file to consolidate testing structure |
| ComfyMark/Services/ScreenshotService.swift | Added cropping parameter to protocol and moved pixel crop calculation logic |
| ComfyMark/Coordinators/SelectionOverlayCoordinator.swift | Added screen detection and overlay recreation for different screens |
| ComfyMark/App/AppCoordinator.swift | Refactored to use new cropping API and extracted coordinator configuration |
| ComfyMark.xcodeproj/project.pbxproj | Updated project structure to include new test files |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| let image = await mockProvider.takeScreenshot() | ||
|
|
||
| XCTAssertNotNil(image) | ||
|
|
||
| // Test that we can create NSImage from CGImage | ||
| let nsImage = NSImage(cgImage: image!, size: NSSize(width: image!.width, height: image!.height)) | ||
| XCTAssertNotNil(nsImage, "Should be able to create NSImage from CGImage") | ||
|
|
||
| // Test image data exists and has expected size | ||
| let expectedDataSize = image!.width * image!.height * 4 // 4 bytes per pixel (RGBA) | ||
| if let dataProvider = image!.dataProvider, |
There was a problem hiding this comment.
Force unwrapping is used multiple times on the same optional image. Consider using a guard statement or if-let binding to safely unwrap once and improve readability.
| let image = await mockProvider.takeScreenshot() | |
| XCTAssertNotNil(image) | |
| // Test that we can create NSImage from CGImage | |
| let nsImage = NSImage(cgImage: image!, size: NSSize(width: image!.width, height: image!.height)) | |
| XCTAssertNotNil(nsImage, "Should be able to create NSImage from CGImage") | |
| // Test image data exists and has expected size | |
| let expectedDataSize = image!.width * image!.height * 4 // 4 bytes per pixel (RGBA) | |
| if let dataProvider = image!.dataProvider, | |
| let imageOpt = await mockProvider.takeScreenshot() | |
| guard let image = imageOpt else { | |
| XCTFail("Image should not be nil") | |
| return | |
| } | |
| // Test that we can create NSImage from CGImage | |
| let nsImage = NSImage(cgImage: image, size: NSSize(width: image.width, height: image.height)) | |
| XCTAssertNotNil(nsImage, "Should be able to create NSImage from CGImage") | |
| // Test image data exists and has expected size | |
| let expectedDataSize = image.width * image.height * 4 // 4 bytes per pixel (RGBA) | |
| if let dataProvider = image.dataProvider, |
| let image = await mockProvider.takeScreenshot() | ||
|
|
||
| XCTAssertNotNil(image) | ||
|
|
||
| // Test that we can create NSImage from CGImage | ||
| let nsImage = NSImage(cgImage: image!, size: NSSize(width: image!.width, height: image!.height)) | ||
| XCTAssertNotNil(nsImage, "Should be able to create NSImage from CGImage") | ||
|
|
||
| // Test image data exists and has expected size | ||
| let expectedDataSize = image!.width * image!.height * 4 // 4 bytes per pixel (RGBA) | ||
| if let dataProvider = image!.dataProvider, |
There was a problem hiding this comment.
Force unwrapping is used multiple times on the same optional image. Consider using a guard statement or if-let binding to safely unwrap once and improve readability.
| let image = await mockProvider.takeScreenshot() | |
| XCTAssertNotNil(image) | |
| // Test that we can create NSImage from CGImage | |
| let nsImage = NSImage(cgImage: image!, size: NSSize(width: image!.width, height: image!.height)) | |
| XCTAssertNotNil(nsImage, "Should be able to create NSImage from CGImage") | |
| // Test image data exists and has expected size | |
| let expectedDataSize = image!.width * image!.height * 4 // 4 bytes per pixel (RGBA) | |
| if let dataProvider = image!.dataProvider, | |
| let imageOpt = await mockProvider.takeScreenshot() | |
| guard let image = imageOpt else { | |
| XCTFail("Image should not be nil") | |
| return | |
| } | |
| // Test that we can create NSImage from CGImage | |
| let nsImage = NSImage(cgImage: image, size: NSSize(width: image.width, height: image.height)) | |
| XCTAssertNotNil(nsImage, "Should be able to create NSImage from CGImage") | |
| // Test image data exists and has expected size | |
| let expectedDataSize = image.width * image.height * 4 // 4 bytes per pixel (RGBA) | |
| if let dataProvider = image.dataProvider, |
| XCTAssertNotNil(image) | ||
|
|
||
| // Test that we can create NSImage from CGImage | ||
| let nsImage = NSImage(cgImage: image!, size: NSSize(width: image!.width, height: image!.height)) | ||
| XCTAssertNotNil(nsImage, "Should be able to create NSImage from CGImage") | ||
|
|
||
| // Test image data exists and has expected size | ||
| let expectedDataSize = image!.width * image!.height * 4 // 4 bytes per pixel (RGBA) | ||
| if let dataProvider = image!.dataProvider, |
There was a problem hiding this comment.
Force unwrapping is used multiple times on the same optional image. Consider using a guard statement or if-let binding to safely unwrap once and improve readability.
| XCTAssertNotNil(image) | |
| // Test that we can create NSImage from CGImage | |
| let nsImage = NSImage(cgImage: image!, size: NSSize(width: image!.width, height: image!.height)) | |
| XCTAssertNotNil(nsImage, "Should be able to create NSImage from CGImage") | |
| // Test image data exists and has expected size | |
| let expectedDataSize = image!.width * image!.height * 4 // 4 bytes per pixel (RGBA) | |
| if let dataProvider = image!.dataProvider, | |
| guard let image = image else { | |
| XCTFail("Image should not be nil") | |
| return | |
| } | |
| // Test that we can create NSImage from CGImage | |
| let nsImage = NSImage(cgImage: image, size: NSSize(width: image.width, height: image.height)) | |
| XCTAssertNotNil(nsImage, "Should be able to create NSImage from CGImage") | |
| // Test image data exists and has expected size | |
| let expectedDataSize = image.width * image.height * 4 // 4 bytes per pixel (RGBA) | |
| if let dataProvider = image.dataProvider, |
| let width = max(0, Int(rect.width)) | ||
| let height = max(0, Int(rect.height)) | ||
|
|
||
| // Return nil for zero dimensions if that's your expected behavior | ||
| guard width > 0 && height > 0 else { return nil } | ||
|
|
||
| return makeTestImage(w: width, h: height) | ||
| } | ||
|
|
||
| private func makeTestImage(w: Int, h: Int) -> CGImage? { | ||
| // Handle zero or negative dimensions | ||
| guard w > 0 && h > 0 else { return nil } | ||
|
|
||
| let colorSpace = CGColorSpaceCreateDeviceRGB() | ||
| guard let ctx = CGContext( | ||
| data: nil, | ||
| width: w, | ||
| height: h, | ||
| bitsPerComponent: 8, | ||
| bytesPerRow: w * 4, | ||
| space: colorSpace, | ||
| bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue | ||
| ) else { return nil } | ||
|
|
||
| // Create a more interesting test pattern instead of solid color | ||
| ctx.setFillColor(NSColor.systemBlue.cgColor) | ||
| ctx.fill(CGRect(x: 0, y: 0, width: w, height: h)) | ||
|
|
||
| // Add some pattern to make images more distinguishable | ||
| ctx.setFillColor(NSColor.white.cgColor) | ||
| ctx.fill(CGRect(x: 0, y: 0, width: min(w, 10), height: min(h, 10))) |
There was a problem hiding this comment.
The test case testZeroDimensionsCrop expects zero dimensions to potentially return an image with width/height of 0 or 1, but this guard statement always returns nil for zero dimensions, making the test assertions inconsistent with the implementation.
| let width = max(0, Int(rect.width)) | |
| let height = max(0, Int(rect.height)) | |
| // Return nil for zero dimensions if that's your expected behavior | |
| guard width > 0 && height > 0 else { return nil } | |
| return makeTestImage(w: width, h: height) | |
| } | |
| private func makeTestImage(w: Int, h: Int) -> CGImage? { | |
| // Handle zero or negative dimensions | |
| guard w > 0 && h > 0 else { return nil } | |
| let colorSpace = CGColorSpaceCreateDeviceRGB() | |
| guard let ctx = CGContext( | |
| data: nil, | |
| width: w, | |
| height: h, | |
| bitsPerComponent: 8, | |
| bytesPerRow: w * 4, | |
| space: colorSpace, | |
| bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue | |
| ) else { return nil } | |
| // Create a more interesting test pattern instead of solid color | |
| ctx.setFillColor(NSColor.systemBlue.cgColor) | |
| ctx.fill(CGRect(x: 0, y: 0, width: w, height: h)) | |
| // Add some pattern to make images more distinguishable | |
| ctx.setFillColor(NSColor.white.cgColor) | |
| ctx.fill(CGRect(x: 0, y: 0, width: min(w, 10), height: min(h, 10))) | |
| let width = Int(rect.width) | |
| let height = Int(rect.height) | |
| // Allow zero or one dimensions for test purposes | |
| let safeWidth = max(0, width) | |
| let safeHeight = max(0, height) | |
| return makeTestImage(w: safeWidth, h: safeHeight) | |
| } | |
| private func makeTestImage(w: Int, h: Int) -> CGImage? { | |
| // Allow zero or one dimensions for test purposes | |
| let width = max(0, w) | |
| let height = max(0, h) | |
| // If both dimensions are zero, return nil (CGContext cannot create 0x0 images) | |
| if width == 0 && height == 0 { return nil } | |
| let colorSpace = CGColorSpaceCreateDeviceRGB() | |
| guard let ctx = CGContext( | |
| data: nil, | |
| width: width, | |
| height: height, | |
| bitsPerComponent: 8, | |
| bytesPerRow: max(1, width) * 4, | |
| space: colorSpace, | |
| bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue | |
| ) else { return nil } | |
| // Create a more interesting test pattern instead of solid color | |
| ctx.setFillColor(NSColor.systemBlue.cgColor) | |
| ctx.fill(CGRect(x: 0, y: 0, width: width, height: height)) | |
| // Add some pattern to make images more distinguishable | |
| ctx.setFillColor(NSColor.white.cgColor) | |
| ctx.fill(CGRect(x: 0, y: 0, width: min(width, 10), height: min(height, 10))) |
| return try await captureScreen(screen, content: content, excludedApps: excludedApps) | ||
| let image = try await captureScreen(screen, content: content, excludedApps: excludedApps) | ||
|
|
||
| /// Get Clamped Pixel Rect |
There was a problem hiding this comment.
The comment uses incorrect grammar. Should be 'Get clamped pixel rect' (lowercase 'clamped').
| /// Get Clamped Pixel Rect | |
| /// Get clamped pixel rect |
There was a problem hiding this comment.
Pull Request Overview
Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| let nsImage = NSImage(cgImage: image!, size: NSSize(width: image!.width, height: image!.height)) | ||
| XCTAssertNotNil(nsImage, "Should be able to create NSImage from CGImage") | ||
|
|
||
| // Test image data exists and has expected size | ||
| let expectedDataSize = image!.width * image!.height * 4 // 4 bytes per pixel (RGBA) | ||
| if let dataProvider = image!.dataProvider, | ||
| let data = dataProvider.data { | ||
| let actualDataSize = CFDataGetLength(data) | ||
| XCTAssertEqual(actualDataSize, expectedDataSize, "Image data size should match expected size") | ||
| } else { | ||
| XCTFail("Image should have valid data provider and data") | ||
| } | ||
| } |
There was a problem hiding this comment.
Avoid using force unwrapping with !. The image has already been verified as non-nil in the guard statement above, so this should use safe unwrapping.
| let nsImage = NSImage(cgImage: image!, size: NSSize(width: image!.width, height: image!.height)) | |
| XCTAssertNotNil(nsImage, "Should be able to create NSImage from CGImage") | |
| // Test image data exists and has expected size | |
| let expectedDataSize = image!.width * image!.height * 4 // 4 bytes per pixel (RGBA) | |
| if let dataProvider = image!.dataProvider, | |
| let data = dataProvider.data { | |
| let actualDataSize = CFDataGetLength(data) | |
| XCTAssertEqual(actualDataSize, expectedDataSize, "Image data size should match expected size") | |
| } else { | |
| XCTFail("Image should have valid data provider and data") | |
| } | |
| } | |
| if let image = image { | |
| let nsImage = NSImage(cgImage: image, size: NSSize(width: image.width, height: image.height)) | |
| XCTAssertNotNil(nsImage, "Should be able to create NSImage from CGImage") | |
| // Test image data exists and has expected size | |
| let expectedDataSize = image.width * image.height * 4 // 4 bytes per pixel (RGBA) | |
| if let dataProvider = image.dataProvider, | |
| let data = dataProvider.data { | |
| let actualDataSize = CFDataGetLength(data) | |
| XCTAssertEqual(actualDataSize, expectedDataSize, "Image data size should match expected size") | |
| } else { | |
| XCTFail("Image should have valid data provider and data") | |
| } | |
| } |
Easily Allow Open Settings with Command , Easily Change Screenshot Side from left to right Made default Viewport Scale out just 0.9
…, though this is the default option for now
No description provided.