-
Notifications
You must be signed in to change notification settings - Fork 0
Native Screenshot Feel #12
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
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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, |
Copilot
AI
Sep 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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, |
Copilot
AI
Sep 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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, |
Copilot
AI
Sep 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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))) |
Copilot
AI
Sep 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 |
Copilot
AI
Sep 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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") | ||
| } | ||
| } |
Copilot
AI
Sep 13, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.