From de60cfb2a1e669bdea2c337fb2845dda8828df61 Mon Sep 17 00:00:00 2001 From: TheMaddax Date: Mon, 6 Jan 2025 23:24:44 -0600 Subject: [PATCH] Add in capturing on startup --- ChessPrism/ChessPrism/ContentView.swift | 118 +++++++++++++++--- .../ChessPrism/ScreenCaptureViewModel.swift | 88 +++++++++++-- cline_docs/activeContext.md | 48 ++++--- 3 files changed, 211 insertions(+), 43 deletions(-) diff --git a/ChessPrism/ChessPrism/ContentView.swift b/ChessPrism/ChessPrism/ContentView.swift index 291a64c..5c79d1c 100644 --- a/ChessPrism/ChessPrism/ContentView.swift +++ b/ChessPrism/ChessPrism/ContentView.swift @@ -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) diff --git a/ChessPrism/ChessPrism/ScreenCaptureViewModel.swift b/ChessPrism/ChessPrism/ScreenCaptureViewModel.swift index 9c9adb1..dac2f36 100644 --- a/ChessPrism/ChessPrism/ScreenCaptureViewModel.swift +++ b/ChessPrism/ChessPrism/ScreenCaptureViewModel.swift @@ -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? + private var monitorTask: Task? // 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) { diff --git a/cline_docs/activeContext.md b/cline_docs/activeContext.md index 73146f4..5e579cf 100644 --- a/cline_docs/activeContext.md +++ b/cline_docs/activeContext.md @@ -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