@@ -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