2026-10-17 22:43:38 -07:00
import AppKit
import CoreGraphics
let keyCodes : [ String : CGKeyCode ] = [
"a" : 0 , "s" : 1 , "d" : 2 , "f" : 3 , "h" : 4 , "g" : 5 , "z" : 6 , "x" : 7 , "c" : 8 , "v" : 9 , "b" : 11 , "q" : 12 ,
"w" : 13 , "e" : 14 , "r" : 15 , "y" : 16 , "t" : 17 , "1" : 18 , "2" : 19 , "3" : 20 , "4" : 21 , "6" : 22 , "5" : 23 ,
"=" : 24 , "9" : 25 , "7" : 26 , "-" : 27 , "8" : 28 , "0" : 29 , "]" : 30 , "o" : 31 , "u" : 32 , "[" : 33 , "i" : 34 ,
"p" : 35 , "return" : 36 , "enter" : 36 , "l" : 37 , "j" : 38 , "'" : 39 , "k" : 40 , ";" : 41 , " \\ " : 42 , "," : 43 ,
"/" : 44 , "n" : 45 , "m" : 46 , "." : 47 , "tab" : 48 , "space" : 49 , "`" : 50 , "delete" : 51 , "backspace" : 51 ,
"escape" : 53 , "esc" : 53 , "f1" : 122 , "f2" : 120 , "f3" : 99 , "f4" : 118 , "f5" : 96 , "f6" : 97 , "f7" : 98 ,
"f8" : 100 , "f9" : 101 , "f10" : 109 , "f11" : 103 , "f12" : 111 , "home" : 115 , "pageup" : 116 ,
"forwarddelete" : 117 , "end" : 119 , "pagedown" : 121 , "left" : 123 , "right" : 124 , "down" : 125 , "up" : 126 ,
]
let modifierFlags : [ String : CGEventFlags ] = [
"cmd" : . maskCommand , "command" : . maskCommand , "shift" : . maskShift , "alt" : . maskAlternate ,
"opt" : . maskAlternate , "option" : . maskAlternate , "ctrl" : . maskControl , "control" : . maskControl ,
"fn" : . maskSecondaryFn ,
]
func pause ( _ ms : Int ) async {
try ? await Task . sleep ( nanoseconds : UInt64 ( ms ) * 1_000_000 )
}
let shifted : [ Character : String ] = [
"!" : "1" , "@" : "2" , "#" : "3" , "$" : "4" , "%" : "5" , "^" : "6" , "&" : "7" , "*" : "8" , "(" : "9" , ")" : "0" ,
"_" : "-" , "+" : "=" , "{" : "[" , "}" : "]" , "|" : " \\ " , ":" : ";" , " \" " : "'" , "<" : "," , ">" : "." , "?" : "/" , "~" : "`" ,
]
let named : [ Character : String ] = [ " " : "space" , " \n " : "return" , " \t " : "tab" ]
func keystroke ( _ ch : Character ) -> ( CGKeyCode , CGEventFlags )? {
if let name = named [ ch ], let c = keyCodes [ name ] {
return ( c , [])
}
if let base = shifted [ ch ], let c = keyCodes [ base ] {
return ( c , . maskShift )
}
if ch . isUppercase , let c = keyCodes [ ch . lowercased ()] {
return ( c , . maskShift )
}
if let c = keyCodes [ String ( ch )] {
return ( c , [])
}
return nil
}
func lockInput () async throws -> Int32 {
let fd = open ( NSTemporaryDirectory () + "byre-hands-input.lock" , O_CREAT | O_RDWR , 0o600 )
if fd < 0 {
throw ToolError ( "open the input lock: errno \( errno ) " )
}
let start = Date ()
while flock ( fd , LOCK_EX | LOCK_NB ) != 0 {
if Date (). timeIntervalSince ( start ) > 30 {
close ( fd )
throw ToolError ( "another agent has been driving this Mac's apps for 30s, so nothing ran; try again" )
}
await pause ( 100 )
}
return fd
}
func post ( _ e : CGEvent ?) {
e ?. post ( tap : . cghidEventTap )
}
2026-10-18 08:43:52 -07:00
func isFront ( _ info : WindowInfo ) throws -> Bool {
2026-10-17 22:43:38 -07:00
guard NSWorkspace . shared . frontmostApplication ?. processIdentifier == info . pid ,
2026-10-18 08:43:52 -07:00
let focused = try attr ( appElement ( info . pid ), kAXFocusedWindowAttribute )
2026-10-17 22:43:38 -07:00
else {
return false
}
var wid : CGWindowID = 0
return _AXUIElementGetWindow ( focused as ! AXUIElement , & wid ) == . success && wid == info . id
}
func focusWindow ( _ id : Int , _ w : AXUIElement ) async throws -> WindowInfo {
let info = try window ( id )
2026-10-18 08:43:52 -07:00
if try isFront ( info ) {
2026-10-17 22:43:38 -07:00
return info
}
NSRunningApplication ( processIdentifier : info . pid )?. activate ()
AXUIElementPerformAction ( w , kAXRaiseAction as CFString )
await pause ( 150 )
let now = try window ( id )
2026-10-18 08:43:52 -07:00
if try ! isFront ( now ) {
2026-10-17 22:43:38 -07:00
throw ToolError ( "could not bring window \( id ) to the front ( \( NSWorkspace . shared . frontmostApplication ?. localizedName ?? "nothing" ) is frontmost), so no input was sent" )
}
return now
}
func click ( _ p : CGPoint , right : Bool , count : Int ) async {
let ( down , up ): ( CGEventType , CGEventType ) = right ? (. rightMouseDown , . rightMouseUp ) : (. leftMouseDown , . leftMouseUp )
let button : CGMouseButton = right ? . right : . left
post ( CGEvent ( mouseEventSource : nil , mouseType : . mouseMoved , mouseCursorPosition : p , mouseButton : button ))
await pause ( 30 )
for i in 1. .. max ( 1 , count ) {
for t in [ down , up ] {
let e = CGEvent ( mouseEventSource : nil , mouseType : t , mouseCursorPosition : p , mouseButton : button )
e ?. setIntegerValueField (. mouseEventClickState , value : Int64 ( i ))
post ( e )
}
await pause ( 30 )
}
}
func drag ( _ from : CGPoint , _ to : CGPoint ) async {
post ( CGEvent ( mouseEventSource : nil , mouseType : . mouseMoved , mouseCursorPosition : from , mouseButton : . left ))
post ( CGEvent ( mouseEventSource : nil , mouseType : . leftMouseDown , mouseCursorPosition : from , mouseButton : . left ))
await pause ( 50 )
for i in 1. .. 20 {
let f = CGFloat ( i ) / 20
let p = CGPoint ( x : from . x + ( to . x - from . x ) * f , y : from . y + ( to . y - from . y ) * f )
post ( CGEvent ( mouseEventSource : nil , mouseType : . leftMouseDragged , mouseCursorPosition : p , mouseButton : . left ))
await pause ( 10 )
}
post ( CGEvent ( mouseEventSource : nil , mouseType : . leftMouseUp , mouseCursorPosition : to , mouseButton : . left ))
}
func scroll ( _ p : CGPoint , dx : Int32 , dy : Int32 ) {
post ( CGEvent ( mouseEventSource : nil , mouseType : . mouseMoved , mouseCursorPosition : p , mouseButton : . left ))
post ( CGEvent ( scrollWheelEvent2Source : nil , units : . line , wheelCount : 2 , wheel1 : dy , wheel2 : dx , wheel3 : 0 ))
}
func type ( _ s : String ) async {
for ch in s {
for down in [ true , false ] {
if let ( code , flags ) = keystroke ( ch ) {
let e = CGEvent ( keyboardEventSource : nil , virtualKey : code , keyDown : down )
e ?. flags = flags
post ( e )
continue
}
let units = Array ( String ( ch ). utf16 )
let e = CGEvent ( keyboardEventSource : nil , virtualKey : 0 , keyDown : down )
e ?. keyboardSetUnicodeString ( stringLength : units . count , unicodeString : units )
post ( e )
}
await pause ( 15 )
}
}
func key ( _ combo : String ) throws {
var flags : CGEventFlags = []
var code : CGKeyCode ?
for part in combo . lowercased (). split ( separator : "+" ). map ( String . init ) {
if let f = modifierFlags [ part ] {
flags . insert ( f )
continue
}
guard let c = keyCodes [ part ] else {
throw ToolError ( "unknown key \( part . debugDescription ) in \( combo . debugDescription ) " )
}
code = c
}
guard let code else {
throw ToolError ( "no key in \( combo . debugDescription ) " )
}
for down in [ true , false ] {
let e = CGEvent ( keyboardEventSource : nil , virtualKey : code , keyDown : down )
e ?. flags = flags
post ( e )
}
}