Add in capturing on startup
This commit is contained in:
parent
8c27f34ee1
commit
de60cfb2a1
3 changed files with 211 additions and 43 deletions
|
|
@ -1,28 +1,107 @@
|
|||
import SwiftUI
|
||||
import AppKit
|
||||
|
||||
class CustomNSView: NSView {
|
||||
private static let invisibleCursor: NSCursor = {
|
||||
let image = NSImage(size: NSSize(width: 1, height: 1))
|
||||
image.lockFocus()
|
||||
NSColor.clear.set()
|
||||
NSRect(x: 0, y: 0, width: 1, height: 1).fill()
|
||||
image.unlockFocus()
|
||||
return NSCursor(image: image, hotSpot: .zero)
|
||||
}()
|
||||
|
||||
override init(frame frameRect: NSRect) {
|
||||
super.init(frame: frameRect)
|
||||
setupView()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
setupView()
|
||||
}
|
||||
|
||||
private func setupView() {
|
||||
wantsLayer = true
|
||||
layer?.backgroundColor = NSColor.clear.cgColor
|
||||
}
|
||||
|
||||
override func resetCursorRects() {
|
||||
super.resetCursorRects()
|
||||
addCursorRect(bounds, cursor: Self.invisibleCursor)
|
||||
}
|
||||
}
|
||||
|
||||
struct HiddenCursorView: NSViewRepresentable {
|
||||
func makeNSView(context: Context) -> CustomNSView {
|
||||
let view = CustomNSView()
|
||||
view.wantsLayer = true
|
||||
return view
|
||||
}
|
||||
|
||||
func updateNSView(_ nsView: CustomNSView, context: Context) {
|
||||
nsView.setFrameSize(nsView.frame.size)
|
||||
}
|
||||
}
|
||||
|
||||
struct CaptureStatusButton: View {
|
||||
let isCapturing: Bool
|
||||
let isBoardDetected: Bool
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
Circle()
|
||||
.fill(statusColor)
|
||||
.frame(width: 12, height: 12)
|
||||
Text(statusText)
|
||||
.foregroundColor(statusColor)
|
||||
}
|
||||
.padding()
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 8)
|
||||
.stroke(statusColor, lineWidth: 2)
|
||||
)
|
||||
}
|
||||
|
||||
private var statusColor: Color {
|
||||
if isCapturing && isBoardDetected {
|
||||
return .green
|
||||
} else if isCapturing {
|
||||
return .yellow
|
||||
} else {
|
||||
return .gray
|
||||
}
|
||||
}
|
||||
|
||||
private var statusText: String {
|
||||
if isCapturing && isBoardDetected {
|
||||
return "Capturing"
|
||||
} else if isCapturing {
|
||||
return "Waiting for board"
|
||||
} else {
|
||||
return "Not capturing"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ContentView: View {
|
||||
@StateObject private var viewModel = ScreenCaptureViewModel()
|
||||
@State private var showDebugOverlay = false
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
// Capture controls
|
||||
HStack {
|
||||
Button(action: {
|
||||
viewModel.startCapture()
|
||||
}) {
|
||||
Text("Start Capture")
|
||||
}
|
||||
.disabled(viewModel.isCapturing)
|
||||
|
||||
Button(action: {
|
||||
viewModel.stopCapture()
|
||||
}) {
|
||||
Text("Stop Capture")
|
||||
}
|
||||
.disabled(!viewModel.isCapturing)
|
||||
// Capture status indicator
|
||||
CaptureStatusButton(
|
||||
isCapturing: viewModel.isCapturing,
|
||||
isBoardDetected: viewModel.isBoardDetected
|
||||
)
|
||||
.onAppear {
|
||||
// Start monitoring for chess boards when view appears
|
||||
viewModel.startMonitoring()
|
||||
}
|
||||
.onDisappear {
|
||||
// Stop monitoring when view disappears
|
||||
viewModel.stopMonitoring()
|
||||
}
|
||||
.padding()
|
||||
|
||||
// Error display
|
||||
if let error = viewModel.captureError {
|
||||
|
|
@ -55,7 +134,10 @@ struct ContentView: View {
|
|||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(maxWidth: 400)
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.overlay(
|
||||
HiddenCursorView()
|
||||
.allowsHitTesting(true)
|
||||
)
|
||||
} else {
|
||||
Text("No board detected")
|
||||
.foregroundColor(.gray)
|
||||
|
|
|
|||
|
|
@ -9,10 +9,13 @@ class ScreenCaptureViewModel: ObservableObject {
|
|||
@Published var croppedBoardImage: NSImage?
|
||||
@Published var captureError: CaptureError?
|
||||
@Published var isCapturing = false
|
||||
@Published var isBoardDetected = false
|
||||
@Published var isAutoCapturing = true // Default to auto-capture mode
|
||||
|
||||
private let screenCapture = ScreenCapture()
|
||||
private let boardDetector = BoardDetector()
|
||||
private var captureTask: Task<Void, Never>?
|
||||
private var monitorTask: Task<Void, Never>?
|
||||
// Share CIContext to avoid creating too many Metal command queues
|
||||
private static let shared = CIContext()
|
||||
private var context: CIContext { ScreenCaptureViewModel.shared }
|
||||
|
|
@ -22,6 +25,7 @@ class ScreenCaptureViewModel: ObservableObject {
|
|||
enum CaptureError: LocalizedError {
|
||||
case chessWindowNotFound
|
||||
case boardDetectionFailed
|
||||
case noBoardDetected
|
||||
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
|
|
@ -29,11 +33,58 @@ class ScreenCaptureViewModel: ObservableObject {
|
|||
return "Chess window not found"
|
||||
case .boardDetectionFailed:
|
||||
return "Failed to detect chess board"
|
||||
case .noBoardDetected:
|
||||
return "No chess board detected"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func startMonitoring() {
|
||||
guard monitorTask == nil else { return }
|
||||
|
||||
monitorTask = Task {
|
||||
do {
|
||||
// Start continuous monitoring
|
||||
try await screenCapture.startCapture()
|
||||
|
||||
monitorLoop: while !Task.isCancelled {
|
||||
do {
|
||||
if let image = screenCapture.getCurrentImage(),
|
||||
let cgImage = image.cgImage(forProposedRect: nil, context: nil, hints: nil) {
|
||||
// Check for board
|
||||
let ciImage = CIImage(cgImage: cgImage)
|
||||
if boardDetector.detectBoard(in: ciImage) != nil {
|
||||
// Board detected, start capture if not already capturing
|
||||
if !isCapturing {
|
||||
startCapture()
|
||||
}
|
||||
}
|
||||
}
|
||||
try await Task.sleep(nanoseconds: 500_000_000) // 0.5 seconds
|
||||
} catch is CancellationError {
|
||||
break monitorLoop
|
||||
} catch {
|
||||
// Just log the error and continue monitoring
|
||||
print("Monitor error: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
try? await screenCapture.stopCapture()
|
||||
} catch {
|
||||
print("Monitor setup error: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func stopMonitoring() {
|
||||
monitorTask?.cancel()
|
||||
monitorTask = nil
|
||||
stopCapture()
|
||||
}
|
||||
|
||||
func startCapture() {
|
||||
guard !isCapturing else { return }
|
||||
|
||||
isCapturing = true
|
||||
captureError = nil
|
||||
|
||||
|
|
@ -54,6 +105,11 @@ class ScreenCaptureViewModel: ObservableObject {
|
|||
// Just update the error state but continue capturing
|
||||
if !Task.isCancelled {
|
||||
handleCaptureError(error)
|
||||
if isAutoCapturing && error as? CaptureError == .noBoardDetected {
|
||||
// In auto-capture mode, stop capture but keep monitoring
|
||||
stopCapture()
|
||||
break captureLoop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -75,6 +131,7 @@ class ScreenCaptureViewModel: ObservableObject {
|
|||
captureTask = nil
|
||||
isCapturing = false
|
||||
captureError = nil
|
||||
isBoardDetected = false
|
||||
}
|
||||
|
||||
private func processImage(_ image: NSImage) async throws {
|
||||
|
|
@ -86,16 +143,29 @@ class ScreenCaptureViewModel: ObservableObject {
|
|||
let ciImage = CIImage(cgImage: cgImage)
|
||||
|
||||
// Detect board
|
||||
guard let boardRect = boardDetector.detectBoard(in: ciImage) else {
|
||||
throw CaptureError.boardDetectionFailed
|
||||
if let boardRect = boardDetector.detectBoard(in: ciImage) {
|
||||
// Board detected
|
||||
isBoardDetected = true
|
||||
self.detectedBoardRect = boardRect
|
||||
|
||||
// Crop board image
|
||||
let croppedImage = ciImage.cropped(to: boardRect)
|
||||
updateImages(ciImage: ciImage, croppedImage: croppedImage)
|
||||
} else {
|
||||
// No board detected
|
||||
isBoardDetected = false
|
||||
self.detectedBoardRect = nil
|
||||
|
||||
// Only update the full capture image
|
||||
if let cgImage = context.createCGImage(ciImage, from: ciImage.extent) {
|
||||
self.capturedImage = NSImage(cgImage: cgImage, size: .zero)
|
||||
}
|
||||
self.croppedBoardImage = nil
|
||||
|
||||
if isAutoCapturing {
|
||||
throw CaptureError.noBoardDetected
|
||||
}
|
||||
}
|
||||
|
||||
// Update detected rectangle
|
||||
self.detectedBoardRect = boardRect
|
||||
|
||||
// Crop board image
|
||||
let croppedImage = ciImage.cropped(to: boardRect)
|
||||
updateImages(ciImage: ciImage, croppedImage: croppedImage)
|
||||
}
|
||||
|
||||
private func updateImages(ciImage: CIImage, croppedImage: CIImage) {
|
||||
|
|
|
|||
|
|
@ -7,9 +7,34 @@
|
|||
- Error handling system properly managing states
|
||||
- Resource cleanup implemented
|
||||
- Metal resource management optimized
|
||||
- Automatic board detection and capture implemented
|
||||
- Visual capture status indicator added
|
||||
- Continuous board monitoring system implemented
|
||||
|
||||
## Recent Changes
|
||||
1. Resource Management Optimization:
|
||||
1. Implemented Continuous Board Monitoring:
|
||||
- Added separate monitoring and capture tasks:
|
||||
* Monitor constantly checks for chess boards (0.5s interval)
|
||||
* Capture processes frames when active (0.1s interval)
|
||||
- Auto-capture behavior:
|
||||
* Starts monitoring when app launches
|
||||
* Automatically starts capture when board appears
|
||||
* Stops capture (but keeps monitoring) when board disappears
|
||||
* Resumes capture when new board is detected
|
||||
- Fixed image conversion pipeline:
|
||||
* Proper NSImage → CGImage → CIImage conversion
|
||||
* Efficient resource management
|
||||
* Clean error handling
|
||||
|
||||
2. Enhanced Status Indication:
|
||||
- Visual status indicator shows capture state:
|
||||
* Green: Actively capturing board
|
||||
* Yellow: Waiting for board
|
||||
* Gray: Not capturing
|
||||
- Clear error messages for different states
|
||||
- Automatic status updates based on board detection
|
||||
|
||||
3. Resource Management Optimization:
|
||||
- Implemented shared CIContext pattern:
|
||||
* Prevents command queue exhaustion
|
||||
* Reduces Metal resource usage
|
||||
|
|
@ -17,14 +42,14 @@
|
|||
- Proper cleanup on task completion
|
||||
- Efficient resource utilization
|
||||
|
||||
2. Improved Window Detection:
|
||||
4. Improved Window Detection:
|
||||
- Using SCShareableContent for window access
|
||||
- Precise window identification:
|
||||
* Exact bundle ID matching (com.chess.iphone)
|
||||
* Window visibility verification (isOnScreen)
|
||||
* Size validation (width > 100 && height > 100)
|
||||
|
||||
3. Enhanced Capture System:
|
||||
5. Enhanced Capture System:
|
||||
- Continuous capture implementation:
|
||||
* Single persistent capture stream
|
||||
* Smooth frame processing (no flickering)
|
||||
|
|
@ -33,19 +58,11 @@
|
|||
- Clean task cancellation handling
|
||||
- Main thread safety for UI updates
|
||||
|
||||
4. UI Simplification:
|
||||
- Removed debug overlay functionality
|
||||
- Cleaner, focused interface
|
||||
- Essential controls only:
|
||||
* Start/Stop capture
|
||||
* Full capture view
|
||||
* Board preview
|
||||
|
||||
5. Error Handling:
|
||||
6. Error Handling:
|
||||
- Improved error resilience:
|
||||
* Continues capturing even if board detection fails
|
||||
* Only stops on critical errors (e.g., window not found)
|
||||
* Shows error state without interrupting capture
|
||||
* Continues monitoring even if capture stops
|
||||
* Only stops on critical errors
|
||||
* Shows error state without interrupting monitoring
|
||||
- Clear error states
|
||||
- Proper async/await usage
|
||||
- Task cancellation management
|
||||
|
|
@ -68,7 +85,6 @@
|
|||
5. Integrate Stockfish engine
|
||||
|
||||
## Known Issues
|
||||
- Board detection not yet implemented
|
||||
- Need to handle different chess.com themes
|
||||
- Need to implement piece recognition
|
||||
- Position analysis pending implementation
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue