import time
import sys
try :
import Quartz
except :
assert False , " You must first install pyobjc-core and pyobjc: https://pyautogui.readthedocs.io/en/latest/install.html "
import AppKit
import pyautogui
if sys . platform != ' darwin ' :
raise Exception ( ' The pyautogui_osx module should only be loaded on an OS X system. ' )
""" Taken from events.h
/ System / Library / Frameworks / Carbon . framework / Versions / A / Frameworks / HIToolbox . framework / Versions / A / Headers / Events . h
The * KB dictionaries in pyautogui map a string that can be passed to keyDown ( ) ,
keyUp ( ) , or press ( ) into the code used for the OS - specific keyboard function .
They should always be lowercase , and the same keys should be used across all OSes . """
keyboardMapping = dict ( [ ( key , None ) for key in pyautogui . KEY_NAMES ] )
keyboardMapping . update ( {
' a ' : 0x00 , # kVK_ANSI_A
' s ' : 0x01 , # kVK_ANSI_S
' d ' : 0x02 , # kVK_ANSI_D
' f ' : 0x03 , # kVK_ANSI_F
' h ' : 0x04 , # kVK_ANSI_H
' g ' : 0x05 , # kVK_ANSI_G
' z ' : 0x06 , # kVK_ANSI_Z
' x ' : 0x07 , # kVK_ANSI_X
' c ' : 0x08 , # kVK_ANSI_C
' v ' : 0x09 , # kVK_ANSI_V
' b ' : 0x0b , # kVK_ANSI_B
' q ' : 0x0c , # kVK_ANSI_Q
' w ' : 0x0d , # kVK_ANSI_W
' e ' : 0x0e , # kVK_ANSI_E
' r ' : 0x0f , # kVK_ANSI_R
' y ' : 0x10 , # kVK_ANSI_Y
' t ' : 0x11 , # kVK_ANSI_T
' 1 ' : 0x12 , # kVK_ANSI_1
' ! ' : 0x12 , # kVK_ANSI_1
' 2 ' : 0x13 , # kVK_ANSI_2
' @ ' : 0x13 , # kVK_ANSI_2
' 3 ' : 0x14 , # kVK_ANSI_3
' # ' : 0x14 , # kVK_ANSI_3
' 4 ' : 0x15 , # kVK_ANSI_4
' $ ' : 0x15 , # kVK_ANSI_4
' 6 ' : 0x16 , # kVK_ANSI_6
' ^ ' : 0x16 , # kVK_ANSI_6
' 5 ' : 0x17 , # kVK_ANSI_5
' % ' : 0x17 , # kVK_ANSI_5
' = ' : 0x18 , # kVK_ANSI_Equal
' + ' : 0x18 , # kVK_ANSI_Equal
' 9 ' : 0x19 , # kVK_ANSI_9
' ( ' : 0x19 , # kVK_ANSI_9
' 7 ' : 0x1a , # kVK_ANSI_7
' & ' : 0x1a , # kVK_ANSI_7
' - ' : 0x1b , # kVK_ANSI_Minus
' _ ' : 0x1b , # kVK_ANSI_Minus
' 8 ' : 0x1c , # kVK_ANSI_8
' * ' : 0x1c , # kVK_ANSI_8
' 0 ' : 0x1d , # kVK_ANSI_0
' ) ' : 0x1d , # kVK_ANSI_0
' ] ' : 0x1e , # kVK_ANSI_RightBracket
' } ' : 0x1e , # kVK_ANSI_RightBracket
' o ' : 0x1f , # kVK_ANSI_O
' u ' : 0x20 , # kVK_ANSI_U
' [ ' : 0x21 , # kVK_ANSI_LeftBracket
' { ' : 0x21 , # kVK_ANSI_LeftBracket
' i ' : 0x22 , # kVK_ANSI_I
' p ' : 0x23 , # kVK_ANSI_P
' l ' : 0x25 , # kVK_ANSI_L
' j ' : 0x26 , # kVK_ANSI_J
" ' " : 0x27 , # kVK_ANSI_Quote
' " ' : 0x27 , # kVK_ANSI_Quote
' k ' : 0x28 , # kVK_ANSI_K
' ; ' : 0x29 , # kVK_ANSI_Semicolon
' : ' : 0x29 , # kVK_ANSI_Semicolon
' \\ ' : 0x2a , # kVK_ANSI_Backslash
' | ' : 0x2a , # kVK_ANSI_Backslash
' , ' : 0x2b , # kVK_ANSI_Comma
' < ' : 0x2b , # kVK_ANSI_Comma
' / ' : 0x2c , # kVK_ANSI_Slash
' ? ' : 0x2c , # kVK_ANSI_Slash
' n ' : 0x2d , # kVK_ANSI_N
' m ' : 0x2e , # kVK_ANSI_M
' . ' : 0x2f , # kVK_ANSI_Period
' > ' : 0x2f , # kVK_ANSI_Period
' ` ' : 0x32 , # kVK_ANSI_Grave
' ~ ' : 0x32 , # kVK_ANSI_Grave
' ' : 0x31 , # kVK_Space
' space ' : 0x31 ,
' \r ' : 0x24 , # kVK_Return
' \n ' : 0x24 , # kVK_Return
' enter ' : 0x24 , # kVK_Return
' return ' : 0x24 , # kVK_Return
' \t ' : 0x30 , # kVK_Tab
' tab ' : 0x30 , # kVK_Tab
' backspace ' : 0x33 , # kVK_Delete, which is "Backspace" on OS X.
' \b ' : 0x33 , # kVK_Delete, which is "Backspace" on OS X.
' esc ' : 0x35 , # kVK_Escape
' escape ' : 0x35 , # kVK_Escape
' command ' : 0x37 , # kVK_Command
' shift ' : 0x38 , # kVK_Shift
' shiftleft ' : 0x38 , # kVK_Shift
' capslock ' : 0x39 , # kVK_CapsLock
' option ' : 0x3a , # kVK_Option
' optionleft ' : 0x3a , # kVK_Option
' alt ' : 0x3a , # kVK_Option
' altleft ' : 0x3a , # kVK_Option
' ctrl ' : 0x3b , # kVK_Control
' ctrlleft ' : 0x3b , # kVK_Control
' shiftright ' : 0x3c , # kVK_RightShift
' optionright ' : 0x3d , # kVK_RightOption
' ctrlright ' : 0x3e , # kVK_RightControl
' fn ' : 0x3f , # kVK_Function
' f17 ' : 0x40 , # kVK_F17
' volumeup ' : 0x48 , # kVK_VolumeUp
' volumedown ' : 0x49 , # kVK_VolumeDown
' volumemute ' : 0x4a , # kVK_Mute
' f18 ' : 0x4f , # kVK_F18
' f19 ' : 0x50 , # kVK_F19
' f20 ' : 0x5a , # kVK_F20
' f5 ' : 0x60 , # kVK_F5
' f6 ' : 0x61 , # kVK_F6
' f7 ' : 0x62 , # kVK_F7
' f3 ' : 0x63 , # kVK_F3
' f8 ' : 0x64 , # kVK_F8
' f9 ' : 0x65 , # kVK_F9
' f11 ' : 0x67 , # kVK_F11
' f13 ' : 0x69 , # kVK_F13
' f16 ' : 0x6a , # kVK_F16
' f14 ' : 0x6b , # kVK_F14
' f10 ' : 0x6d , # kVK_F10
' f12 ' : 0x6f , # kVK_F12
' f15 ' : 0x71 , # kVK_F15
' help ' : 0x72 , # kVK_Help
' home ' : 0x73 , # kVK_Home
' pageup ' : 0x74 , # kVK_PageUp
' pgup ' : 0x74 , # kVK_PageUp
' del ' : 0x75 , # kVK_ForwardDelete
' delete ' : 0x75 , # kVK_ForwardDelete
' f4 ' : 0x76 , # kVK_F4
' end ' : 0x77 , # kVK_End
' f2 ' : 0x78 , # kVK_F2
' pagedown ' : 0x79 , # kVK_PageDown
' pgdn ' : 0x79 , # kVK_PageDown
' f1 ' : 0x7a , # kVK_F1
' left ' : 0x7b , # kVK_LeftArrow
' right ' : 0x7c , # kVK_RightArrow
' down ' : 0x7d , # kVK_DownArrow
' up ' : 0x7e , # kVK_UpArrow
' yen ' : 0x5d , # kVK_JIS_Yen
#'underscore' : 0x5e, # kVK_JIS_Underscore (only applies to Japanese keyboards)
#'comma': 0x5f, # kVK_JIS_KeypadComma (only applies to Japanese keyboards)
' eisu ' : 0x66 , # kVK_JIS_Eisu
' kana ' : 0x68 , # kVK_JIS_Kana
} )
"""
# TODO - additional key codes to add
kVK_ANSI_KeypadDecimal = 0x41 ,
kVK_ANSI_KeypadMultiply = 0x43 ,
kVK_ANSI_KeypadPlus = 0x45 ,
kVK_ANSI_KeypadClear = 0x47 ,
kVK_ANSI_KeypadDivide = 0x4B ,
kVK_ANSI_KeypadEnter = 0x4C ,
kVK_ANSI_KeypadMinus = 0x4E ,
kVK_ANSI_KeypadEquals = 0x51 ,
kVK_ANSI_Keypad0 = 0x52 ,
kVK_ANSI_Keypad1 = 0x53 ,
kVK_ANSI_Keypad2 = 0x54 ,
kVK_ANSI_Keypad3 = 0x55 ,
kVK_ANSI_Keypad4 = 0x56 ,
kVK_ANSI_Keypad5 = 0x57 ,
kVK_ANSI_Keypad6 = 0x58 ,
kVK_ANSI_Keypad7 = 0x59 ,
kVK_ANSI_Keypad8 = 0x5B ,
kVK_ANSI_Keypad9 = 0x5C ,
"""
# add mappings for uppercase letters
for c in ' abcdefghijklmnopqrstuvwxyz ' :
keyboardMapping [ c . upper ( ) ] = keyboardMapping [ c ]
# Taken from ev_keymap.h
# http://www.opensource.apple.com/source/IOHIDFamily/IOHIDFamily-86.1/IOHIDSystem/IOKit/hidsystem/ev_keymap.h
special_key_translate_table = {
' KEYTYPE_SOUND_UP ' : 0 ,
' KEYTYPE_SOUND_DOWN ' : 1 ,
' KEYTYPE_BRIGHTNESS_UP ' : 2 ,
' KEYTYPE_BRIGHTNESS_DOWN ' : 3 ,
' KEYTYPE_CAPS_LOCK ' : 4 ,
' KEYTYPE_HELP ' : 5 ,
' POWER_KEY ' : 6 ,
' KEYTYPE_MUTE ' : 7 ,
' UP_ARROW_KEY ' : 8 ,
' DOWN_ARROW_KEY ' : 9 ,
' KEYTYPE_NUM_LOCK ' : 10 ,
' KEYTYPE_CONTRAST_UP ' : 11 ,
' KEYTYPE_CONTRAST_DOWN ' : 12 ,
' KEYTYPE_LAUNCH_PANEL ' : 13 ,
' KEYTYPE_EJECT ' : 14 ,
' KEYTYPE_VIDMIRROR ' : 15 ,
' KEYTYPE_PLAY ' : 16 ,
' KEYTYPE_NEXT ' : 17 ,
' KEYTYPE_PREVIOUS ' : 18 ,
' KEYTYPE_FAST ' : 19 ,
' KEYTYPE_REWIND ' : 20 ,
' KEYTYPE_ILLUMINATION_UP ' : 21 ,
' KEYTYPE_ILLUMINATION_DOWN ' : 22 ,
' KEYTYPE_ILLUMINATION_TOGGLE ' : 23
}
def _keyDown ( key ) :
if key not in keyboardMapping or keyboardMapping [ key ] is None :
return
if key in special_key_translate_table :
_specialKeyEvent ( key , ' down ' )
else :
_normalKeyEvent ( key , ' down ' )
def _keyUp ( key ) :
if key not in keyboardMapping or keyboardMapping [ key ] is None :
return
if key in special_key_translate_table :
_specialKeyEvent ( key , ' up ' )
else :
_normalKeyEvent ( key , ' up ' )
def _normalKeyEvent ( key , upDown ) :
assert upDown in ( ' up ' , ' down ' ) , " upDown argument must be ' up ' or ' down ' "
try :
if pyautogui . isShiftCharacter ( key ) :
key_code = keyboardMapping [ key . lower ( ) ]
event = Quartz . CGEventCreateKeyboardEvent ( None ,
keyboardMapping [ ' shift ' ] , upDown == ' down ' )
Quartz . CGEventPost ( Quartz . kCGHIDEventTap , event )
# Tiny sleep to let OS X catch up on us pressing shift
time . sleep ( 0.01 )
else :
key_code = keyboardMapping [ key ]
event = Quartz . CGEventCreateKeyboardEvent ( None , key_code , upDown == ' down ' )
Quartz . CGEventPost ( Quartz . kCGHIDEventTap , event )
time . sleep ( 0.01 )
# TODO - wait, is the shift key's keyup not done?
# TODO - get rid of this try-except.
except KeyError :
raise RuntimeError ( " Key %s not implemented. " % ( key ) )
def _specialKeyEvent ( key , upDown ) :
""" Helper method for special keys.
Source : http : / / stackoverflow . com / questions / 11045814 / emulate - media - key - press - on - mac
"""
assert upDown in ( ' up ' , ' down ' ) , " upDown argument must be ' up ' or ' down ' "
key_code = special_key_translate_table [ key ]
ev = AppKit . NSEvent . otherEventWithType_location_modifierFlags_timestamp_windowNumber_context_subtype_data1_data2_ (
Quartz . NSSystemDefined , # type
( 0 , 0 ) , # location
0xa00 if upDown == ' down ' else 0xb00 , # flags
0 , # timestamp
0 , # window
0 , # ctx
8 , # subtype
( key_code << 16 ) | ( ( 0xa if upDown == ' down ' else 0xb ) << 8 ) , # data1
- 1 # data2
)
Quartz . CGEventPost ( 0 , ev . CGEvent ( ) )
def _position ( ) :
loc = AppKit . NSEvent . mouseLocation ( )
return int ( loc . x ) , int ( Quartz . CGDisplayPixelsHigh ( 0 ) - loc . y )
def _size ( ) :
return Quartz . CGDisplayPixelsWide ( Quartz . CGMainDisplayID ( ) ) , Quartz . CGDisplayPixelsHigh ( Quartz . CGMainDisplayID ( ) )
def _scroll ( clicks , x = None , y = None ) :
_vscroll ( clicks , x , y )
"""
According to https : / / developer . apple . com / library / mac / documentation / Carbon / Reference / QuartzEventServicesRef / Reference / reference . html #//apple_ref/c/func/Quartz.CGEventCreateScrollWheelEvent
" Scrolling movement is generally represented by small signed integer values, typically in a range from -10 to +10. Large values may have unexpected results, depending on the application that processes the event. "
The scrolling functions will create multiple events that scroll 10 each , and then scroll the remainder .
"""
def _vscroll ( clicks , x = None , y = None ) :
_moveTo ( x , y )
clicks = int ( clicks )
for _ in range ( abs ( clicks ) / / 10 ) :
scrollWheelEvent = Quartz . CGEventCreateScrollWheelEvent (
None , # no source
Quartz . kCGScrollEventUnitLine , # units
1 , # wheelCount (number of dimensions)
10 if clicks > = 0 else - 10 ) # vertical movement
Quartz . CGEventPost ( Quartz . kCGHIDEventTap , scrollWheelEvent )
scrollWheelEvent = Quartz . CGEventCreateScrollWheelEvent (
None , # no source
Quartz . kCGScrollEventUnitLine , # units
1 , # wheelCount (number of dimensions)
clicks % 10 if clicks > = 0 else - 1 * ( - clicks % 10 ) ) # vertical movement
Quartz . CGEventPost ( Quartz . kCGHIDEventTap , scrollWheelEvent )
def _hscroll ( clicks , x = None , y = None ) :
_moveTo ( x , y )
clicks = int ( clicks )
for _ in range ( abs ( clicks ) / / 10 ) :
scrollWheelEvent = Quartz . CGEventCreateScrollWheelEvent (
None , # no source
Quartz . kCGScrollEventUnitLine , # units
2 , # wheelCount (number of dimensions)
0 , # vertical movement
10 if clicks > = 0 else - 10 ) # horizontal movement
Quartz . CGEventPost ( Quartz . kCGHIDEventTap , scrollWheelEvent )
scrollWheelEvent = Quartz . CGEventCreateScrollWheelEvent (
None , # no source
Quartz . kCGScrollEventUnitLine , # units
2 , # wheelCount (number of dimensions)
0 , # vertical movement
( clicks % 10 ) if clicks > = 0 else ( - 1 * clicks % 10 ) ) # horizontal movement
Quartz . CGEventPost ( Quartz . kCGHIDEventTap , scrollWheelEvent )
def _mouseDown ( x , y , button ) :
if button == ' left ' :
_sendMouseEvent ( Quartz . kCGEventLeftMouseDown , x , y , Quartz . kCGMouseButtonLeft )
elif button == ' middle ' :
_sendMouseEvent ( Quartz . kCGEventOtherMouseDown , x , y , Quartz . kCGMouseButtonCenter )
elif button == ' right ' :
_sendMouseEvent ( Quartz . kCGEventRightMouseDown , x , y , Quartz . kCGMouseButtonRight )
else :
assert False , " button argument not in ( ' left ' , ' middle ' , ' right ' ) "
def _mouseUp ( x , y , button ) :
if button == ' left ' :
_sendMouseEvent ( Quartz . kCGEventLeftMouseUp , x , y , Quartz . kCGMouseButtonLeft )
elif button == ' middle ' :
_sendMouseEvent ( Quartz . kCGEventOtherMouseUp , x , y , Quartz . kCGMouseButtonCenter )
elif button == ' right ' :
_sendMouseEvent ( Quartz . kCGEventRightMouseUp , x , y , Quartz . kCGMouseButtonRight )
else :
assert False , " button argument not in ( ' left ' , ' middle ' , ' right ' ) "
def _click ( x , y , button ) :
if button == ' left ' :
_sendMouseEvent ( Quartz . kCGEventLeftMouseDown , x , y , Quartz . kCGMouseButtonLeft )
_sendMouseEvent ( Quartz . kCGEventLeftMouseUp , x , y , Quartz . kCGMouseButtonLeft )
elif button == ' middle ' :
_sendMouseEvent ( Quartz . kCGEventOtherMouseDown , x , y , Quartz . kCGMouseButtonCenter )
_sendMouseEvent ( Quartz . kCGEventOtherMouseUp , x , y , Quartz . kCGMouseButtonCenter )
elif button == ' right ' :
_sendMouseEvent ( Quartz . kCGEventRightMouseDown , x , y , Quartz . kCGMouseButtonRight )
_sendMouseEvent ( Quartz . kCGEventRightMouseUp , x , y , Quartz . kCGMouseButtonRight )
else :
assert False , " button argument not in ( ' left ' , ' middle ' , ' right ' ) "
def _multiClick ( x , y , button , num ) :
btn = None
down = None
up = None
if button == ' left ' :
btn = Quartz . kCGMouseButtonLeft
down = Quartz . kCGEventLeftMouseDown
up = Quartz . kCGEventLeftMouseUp
elif button == ' middle ' :
btn = Quartz . kCGMouseButtonCenter
down = Quartz . kCGEventOtherMouseDown
up = Quartz . kCGEventOtherMouseUp
elif button == ' right ' :
btn = Quartz . kCGMouseButtonRight
down = Quartz . kCGEventRightMouseDown
up = Quartz . kCGEventRightMouseUp
else :
assert False , " button argument not in ( ' left ' , ' middle ' , ' right ' ) "
return
mouseEvent = Quartz . CGEventCreateMouseEvent ( None , down , ( x , y ) , btn )
Quartz . CGEventSetIntegerValueField ( mouseEvent , Quartz . kCGMouseEventClickState , num )
Quartz . CGEventPost ( Quartz . kCGHIDEventTap , mouseEvent )
Quartz . CGEventSetType ( mouseEvent , up )
Quartz . CGEventPost ( Quartz . kCGHIDEventTap , mouseEvent )
for i in range ( 0 , num - 1 ) :
Quartz . CGEventSetType ( mouseEvent , down )
Quartz . CGEventPost ( Quartz . kCGHIDEventTap , mouseEvent )
Quartz . CGEventSetType ( mouseEvent , up )
Quartz . CGEventPost ( Quartz . kCGHIDEventTap , mouseEvent )
def _sendMouseEvent ( ev , x , y , button ) :
mouseEvent = Quartz . CGEventCreateMouseEvent ( None , ev , ( x , y ) , button )
Quartz . CGEventPost ( Quartz . kCGHIDEventTap , mouseEvent )
def _dragTo ( x , y , button ) :
if button == ' left ' :
_sendMouseEvent ( Quartz . kCGEventLeftMouseDragged , x , y , Quartz . kCGMouseButtonLeft )
elif button == ' middle ' :
_sendMouseEvent ( Quartz . kCGEventOtherMouseDragged , x , y , Quartz . kCGMouseButtonCenter )
elif button == ' right ' :
_sendMouseEvent ( Quartz . kCGEventRightMouseDragged , x , y , Quartz . kCGMouseButtonRight )
else :
assert False , " button argument not in ( ' left ' , ' middle ' , ' right ' ) "
time . sleep ( 0.01 ) # needed to allow OS time to catch up.
def _moveTo ( x , y ) :
_sendMouseEvent ( Quartz . kCGEventMouseMoved , x , y , 0 )
time . sleep ( 0.01 ) # needed to allow OS time to catch up.