@@ -363,11 +363,25 @@ func requireForegroundTarget(pid: pid_t, windowID: String, point: CGPoint, frame
363363 }
364364 guard let top, ( top [ kCGWindowNumber as String ] as? NSNumber ) ? . stringValue == windowID else { throw ComputerError ( " window_occluded " , " The target point is covered by another window; bring the intended window forward and observe again. " ) }
365365}
366- func postMouse( pid: pid_t , type: CGEventType , point: CGPoint , button: CGMouseButton , count: Int = 1 , windowID: String ? = nil , frame: CGRect ? = nil , release: Bool = false ) throws {
366+ final class CoordinateDispatch {
367+ private( set) var hasDispatched = false
368+ func willDispatch( ) { hasDispatched = true }
369+ func classify( _ error: ComputerError ) -> ComputerError {
370+ ComputerError ( error. code, error. message, dispatchState: hasDispatched ? nil : " not_started " )
371+ }
372+ }
373+ @MainActor
374+ func coordinateAction( _ operation: ( CoordinateDispatch ) async throws -> Void ) async throws {
375+ let dispatch = CoordinateDispatch ( )
376+ do { try await operation ( dispatch) }
377+ catch let error as ComputerError { throw dispatch. classify ( error) }
378+ }
379+ func postMouse( pid: pid_t , type: CGEventType , point: CGPoint , button: CGMouseButton , count: Int = 1 , windowID: String ? = nil , frame: CGRect ? = nil , release: Bool = false , dispatch: CoordinateDispatch ) throws {
367380 guard let windowID, let frame else { throw ComputerError ( " invalid_window " , " Mouse input requires an observed app window. " ) }
368381 if !release { try requireForegroundTarget ( pid: pid, windowID: windowID, point: point, frame: frame) }
369382 guard let source = CGEventSource ( stateID: . privateState) , let event = CGEvent ( mouseEventSource: source, mouseType: type, mouseCursorPosition: point, mouseButton: button) else { throw ComputerError ( " input_failed " , " Could not create mouse event. " ) }
370383 event. setIntegerValueField ( . mouseEventClickState, value: Int64 ( count) )
384+ dispatch. willDispatch ( )
371385 event. post ( tap: . cghidEventTap)
372386}
373387
@@ -482,6 +496,7 @@ final class Driver {
482496 } catch let error as ComputerError { result [ " screenshotError " ] = String ( ( error. code + " : " + error. message) . prefix ( 2000 ) ) }
483497 catch { result [ " screenshotError " ] = String ( ( " capture_failed: " + String( describing: error) ) . prefix ( 2000 ) ) }
484498 }
499+ result [ " isActive " ] = app. isActive
485500 return result
486501 case " click " , " type_text " , " input_sequence " , " press_key " , " scroll " , " drag " , " set_value " , " perform_action " : break
487502 default : throw ComputerError ( " unknown_method " , " Unknown computer-use method. " )
@@ -508,13 +523,15 @@ final class Driver {
508523 guard [ " left " , " right " ] . contains ( button) , ( 1 ... 3 ) . contains ( count) else { throw ComputerError ( " invalid_arguments " , " Invalid button or clickCount. " ) }
509524 if p. elementId != nil && button == " left " && count == 1 { try axCheck ( AXUIElementPerformAction ( try snapshot. element ( p. elementId) , kAXPressAction as CFString ) ) }
510525 else {
511- let position = try point ( p, app: app, snapshot: snapshot) ; let mouse : CGMouseButton = button == " right " ? . right : . left
512- let windowID = try required ( p. windowId ?? snapshot. windowFrames. first ( where: { $0. value. contains ( position) } ) ? . key, " windowId " )
513- let frame = try required ( snapshot. windowFrames [ windowID] , " window frame " )
514- for click in 1 ... count {
515- try checkCancellation ( )
516- try postMouse ( pid: pid, type: button == " right " ? . rightMouseDown : . leftMouseDown, point: position, button: mouse, count: click, windowID: windowID, frame: frame)
517- try postMouse ( pid: pid, type: button == " right " ? . rightMouseUp : . leftMouseUp, point: position, button: mouse, count: click, windowID: windowID, frame: frame, release: true )
526+ try await coordinateAction { dispatch in
527+ let position = try point ( p, app: app, snapshot: snapshot) ; let mouse : CGMouseButton = button == " right " ? . right : . left
528+ let windowID = try required ( p. windowId ?? snapshot. windowFrames. first ( where: { $0. value. contains ( position) } ) ? . key, " windowId " )
529+ let frame = try required ( snapshot. windowFrames [ windowID] , " window frame " )
530+ for click in 1 ... count {
531+ try checkCancellation ( )
532+ try postMouse ( pid: pid, type: button == " right " ? . rightMouseDown : . leftMouseDown, point: position, button: mouse, count: click, windowID: windowID, frame: frame, dispatch: dispatch)
533+ try postMouse ( pid: pid, type: button == " right " ? . rightMouseUp : . leftMouseUp, point: position, button: mouse, count: click, windowID: windowID, frame: frame, release: true , dispatch: dispatch)
534+ }
518535 }
519536 }
520537 case " type_text " , " input_sequence " :
@@ -594,27 +611,31 @@ final class Driver {
594611 }
595612 if !selectedAll { try postKey ( pid: pid, code: code, flags: flags) }
596613 case " scroll " :
597- let position = try point ( p, app: app, snapshot: snapshot) ; let dx = p. deltaX ?? 0 ; let dy = p. deltaY ?? 0
598- guard dx. isFinite, dy. isFinite, abs ( dx) <= 10000 , abs ( dy) <= 10000 else { throw ComputerError ( " invalid_arguments " , " Scroll deltas exceed bounds. " ) }
599- guard let event = CGEvent ( scrollWheelEvent2Source: CGEventSource ( stateID: . privateState) , units: . pixel, wheelCount: 2 , wheel1: Int32 ( - dy) , wheel2: Int32 ( - dx) , wheel3: 0 ) else { throw ComputerError ( " input_failed " , " Could not create scroll event. " ) }
600- let windowID = try required ( p. windowId ?? snapshot. windowFrames. first ( where: { $0. value. contains ( position) } ) ? . key, " windowId " )
601- let frame = try required ( snapshot. windowFrames [ windowID] , " window frame " )
602- try requireForegroundTarget ( pid: pid, windowID: windowID, point: position, frame: frame)
603- event. location = position; event. post ( tap: . cghidEventTap)
614+ try await coordinateAction { dispatch in
615+ let position = try point ( p, app: app, snapshot: snapshot) ; let dx = p. deltaX ?? 0 ; let dy = p. deltaY ?? 0
616+ guard dx. isFinite, dy. isFinite, abs ( dx) <= 10000 , abs ( dy) <= 10000 else { throw ComputerError ( " invalid_arguments " , " Scroll deltas exceed bounds. " ) }
617+ guard let event = CGEvent ( scrollWheelEvent2Source: CGEventSource ( stateID: . privateState) , units: . pixel, wheelCount: 2 , wheel1: Int32 ( - dy) , wheel2: Int32 ( - dx) , wheel3: 0 ) else { throw ComputerError ( " input_failed " , " Could not create scroll event. " ) }
618+ let windowID = try required ( p. windowId ?? snapshot. windowFrames. first ( where: { $0. value. contains ( position) } ) ? . key, " windowId " )
619+ let frame = try required ( snapshot. windowFrames [ windowID] , " window frame " )
620+ try requireForegroundTarget ( pid: pid, windowID: windowID, point: position, frame: frame)
621+ event. location = position; dispatch. willDispatch ( ) ; event. post ( tap: . cghidEventTap)
622+ }
604623 case " drag " :
605- let start = try point ( p, app: app, snapshot: snapshot) ; let end = try point ( p, app: app, snapshot: snapshot, end: true )
606- let windowID = try required ( p. windowId, " windowId " ) ; let frame = try required ( snapshot. windowFrames [ windowID] , " window frame " )
607- try postMouse ( pid: pid, type: . leftMouseDown, point: start, button: . left, windowID: windowID, frame: frame)
608- var lastPoint = start
609- defer { try ? postMouse ( pid: pid, type: . leftMouseUp, point: lastPoint, button: . left, windowID: windowID, frame: frame, release: true ) }
610- try await Task . sleep ( for: . milliseconds( 10 ) )
611- for step in 1 ... 10 {
612- try checkCancellation ( )
613- let amount = CGFloat ( step) / 10
614- let position = CGPoint ( x: start. x + ( end. x - start. x) * amount, y: start. y + ( end. y - start. y) * amount)
615- try postMouse ( pid: pid, type: . leftMouseDragged, point: position, button: . left, windowID: windowID, frame: frame)
616- lastPoint = position
624+ try await coordinateAction { dispatch in
625+ let start = try point ( p, app: app, snapshot: snapshot) ; let end = try point ( p, app: app, snapshot: snapshot, end: true )
626+ let windowID = try required ( p. windowId, " windowId " ) ; let frame = try required ( snapshot. windowFrames [ windowID] , " window frame " )
627+ try postMouse ( pid: pid, type: . leftMouseDown, point: start, button: . left, windowID: windowID, frame: frame, dispatch: dispatch)
628+ var lastPoint = start
629+ defer { try ? postMouse ( pid: pid, type: . leftMouseUp, point: lastPoint, button: . left, windowID: windowID, frame: frame, release: true , dispatch: dispatch) }
617630 try await Task . sleep ( for: . milliseconds( 10 ) )
631+ for step in 1 ... 10 {
632+ try checkCancellation ( )
633+ let amount = CGFloat ( step) / 10
634+ let position = CGPoint ( x: start. x + ( end. x - start. x) * amount, y: start. y + ( end. y - start. y) * amount)
635+ try postMouse ( pid: pid, type: . leftMouseDragged, point: position, button: . left, windowID: windowID, frame: frame, dispatch: dispatch)
636+ lastPoint = position
637+ try await Task . sleep ( for: . milliseconds( 10 ) )
638+ }
618639 }
619640 default : throw ComputerError ( " unknown_method " , " Unknown method. " )
620641 }
0 commit comments