├── .gitignore ├── CHANGELOG.txt ├── LICENSE.txt ├── README.txt ├── pyWinhook ├── HookManager.py ├── __init__.py ├── aa hook.py ├── cpyHook.i ├── doc.py └── example.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | #ignore thumbnails created by windows 2 | Thumbs.db 3 | #Ignore files build by Visual Studio 4 | *.obj 5 | *.exe 6 | *.pdb 7 | *.user 8 | *.aps 9 | *.pch 10 | *.vspscc 11 | *_i.c 12 | *_p.c 13 | *.ncb 14 | *.suo 15 | *.tlb 16 | *.tlh 17 | *.bak 18 | *.cache 19 | *.ilk 20 | *.log 21 | [Bb]in 22 | [Dd]ebug*/ 23 | *.lib 24 | *.sbr 25 | obj/ 26 | [Rr]elease*/ 27 | _ReSharper*/ 28 | [Tt]est[Rr]esult* 29 | *.edbkup 30 | *.pyc 31 | *.sln 32 | *.pyproj 33 | .git/ 34 | [Oo]ther/ 35 | .vs/ -------------------------------------------------------------------------------- /CHANGELOG.txt: -------------------------------------------------------------------------------- 1 | pyHook Change Log 2 | 3 | ---------- 4 | 1.6.2 (2020-01-17) 5 | 6 | - Fixed AttributeError: 'HookManager' object has no attribute 'keyboard_hook' 7 | 8 | ---------- 9 | 1.6.1 (2019-02-10) 10 | 11 | - add _setup()_ `install_requires` for pywin32 on pypi 12 | - Removed 'classifiers type' warning when build the package, by adding a list before the result of 'filter' function. 13 | - Fixed a 'missing file' error by forced copying of cpyHook.py, that was raised when you trying to build from a compressed or "clean" package with pip, because the cpyHook.py file was not copied automatically from source to destination folder. 14 | - fix an "unicode error" issue that afflicts only Python 3.x 15 | - fix issue generated by previous fix that afflicts Python 2.x only 16 | 17 | ---------- 18 | 1.6.0 (2019-01-24) 19 | 20 | - Added support for Python 3.x 21 | 22 | ---------- 23 | 1.5.2 (2017-03-9) 24 | 25 | - Change the package name from pyHook to pyWinhook. 26 | - Update setup.py distutils import to setuptools, since this fix the compiling step with last versions of visual studio compilers. 27 | - Change the package files/folders arrangment to as expected in the python default one. 28 | 29 | ---------- 30 | 1.5.1 (2008-10-07) 31 | 32 | - Back out the bugfix for deadkeys, since it broke ascii conversion. 33 | - Allow the processing of negative ncode key events, because, why not? 34 | - Update the SWIG typemap syntax from a deprecated style to avoid warning during build. 35 | 36 | ---------- 37 | 1.6-pre (2008-07-20), not released 38 | 39 | - BUGFIX: deadkeys on international keyboards work correctly. 40 | - BUGFIX: pyHook can now be used in binaries built with py2exe (maybe this will 41 | work with PyInstaller too). 42 | 43 | ---------- 44 | 1.5.0 (1.5a) (2005-04-01) 45 | - Changed stateful key tracking to work better with SendKeys 46 | - Possibly fixed bug where weird chars were insert when Alt+Arrow key pressed 47 | 48 | ---------- 49 | 1.4.0 (2005-02-23) 50 | - Added documentation 51 | - Added KeyAll property to HookManager 52 | 53 | (2004-10-11) 54 | - Added support for translating virtual keycodes to ASCII characters when possible 55 | - Added support for stopping event propagation 56 | 57 | ---------- 58 | 1.3.0 (2004-09-13) 59 | - AA example was updated to work with the wx namespace 60 | - Added support for allowing/disallowing event propagation (see example.py) 61 | - Added a proper __init__.py to the package 62 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Tungsteno 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | 9 | ------------------- 10 | 11 | Copyright (c) 2003 Peter Parente 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 18 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | A pyHook fork with some updates for support latest Visual Studio compilers. 2 | See the website, http://pyhook.sourceforge.net/, for the original pyHook project. 3 | 4 | Known bugs 5 | ---------- 6 | - PyInstaller can't build single-file executables using pyWinhook. This may be 7 | fixed in 1.5.1, but hasn't been tested. 8 | - pyWinhook is reported to break dead keys on non-US-english keyboards. 9 | - WM_CHAR messages are not intercepted by pyWinhook, even if SubscribeKeyChar() or 10 | SubscribeKeyAll() are used to set the callback function. 11 | 12 | Limitations 13 | ----------- 14 | - pyWinhook will not work on Win9x (no messages show up) as it uses hooks which 15 | are not present in Windows systems prior to NT 4.0 SP3. 16 | -------------------------------------------------------------------------------- /pyWinhook/HookManager.py: -------------------------------------------------------------------------------- 1 | from . import cpyHook 2 | 3 | def GetKeyState(key_id): 4 | return cpyHook.cGetKeyState(key_id) 5 | 6 | class HookConstants: 7 | ''' 8 | Stores internal windows hook constants including hook types, mappings from virtual 9 | keycode name to value and value to name, and event type value to name. 10 | ''' 11 | WH_MIN = -1 12 | WH_MSGFILTER = -1 13 | WH_JOURNALRECORD = 0 14 | WH_JOURNALPLAYBACK = 1 15 | WH_KEYBOARD = 2 16 | WH_GETMESSAGE = 3 17 | WH_CALLWNDPROC = 4 18 | WH_CBT = 5 19 | WH_SYSMSGFILTER = 6 20 | WH_MOUSE = 7 21 | WH_HARDWARE = 8 22 | WH_DEBUG = 9 23 | WH_SHELL = 10 24 | WH_FOREGROUNDIDLE = 11 25 | WH_CALLWNDPROCRET = 12 26 | WH_KEYBOARD_LL = 13 27 | WH_MOUSE_LL = 14 28 | WH_MAX = 15 29 | 30 | WM_MOUSEFIRST = 0x0200 31 | WM_MOUSEMOVE = 0x0200 32 | WM_LBUTTONDOWN = 0x0201 33 | WM_LBUTTONUP = 0x0202 34 | WM_LBUTTONDBLCLK = 0x0203 35 | WM_RBUTTONDOWN =0x0204 36 | WM_RBUTTONUP = 0x0205 37 | WM_RBUTTONDBLCLK = 0x0206 38 | WM_MBUTTONDOWN = 0x0207 39 | WM_MBUTTONUP = 0x0208 40 | WM_MBUTTONDBLCLK = 0x0209 41 | WM_MOUSEWHEEL = 0x020A 42 | WM_MOUSELAST = 0x020A 43 | 44 | WM_KEYFIRST = 0x0100 45 | WM_KEYDOWN = 0x0100 46 | WM_KEYUP = 0x0101 47 | WM_CHAR = 0x0102 48 | WM_DEADCHAR = 0x0103 49 | WM_SYSKEYDOWN = 0x0104 50 | WM_SYSKEYUP = 0x0105 51 | WM_SYSCHAR = 0x0106 52 | WM_SYSDEADCHAR = 0x0107 53 | WM_KEYLAST = 0x0108 54 | 55 | 56 | #VK_0 thru VK_9 are the same as ASCII '0' thru '9' (0x30 -' : 0x39) 57 | #VK_A thru VK_Z are the same as ASCII 'A' thru 'Z' (0x41 -' : 0x5A) 58 | 59 | #virtual keycode constant names to virtual keycodes numerical id 60 | vk_to_id = {'VK_LBUTTON' : 0x01, 'VK_RBUTTON' : 0x02, 'VK_CANCEL' : 0x03, 'VK_MBUTTON' : 0x04, 61 | 'VK_BACK' : 0x08, 'VK_TAB' : 0x09, 'VK_CLEAR' : 0x0C, 'VK_RETURN' : 0x0D, 'VK_SHIFT' : 0x10, 62 | 'VK_CONTROL' : 0x11, 'VK_MENU' : 0x12, 'VK_PAUSE' : 0x13, 'VK_CAPITAL' : 0x14, 'VK_KANA' : 0x15, 63 | 'VK_HANGEUL' : 0x15, 'VK_HANGUL' : 0x15, 'VK_JUNJA' : 0x17, 'VK_FINAL' : 0x18, 'VK_HANJA' : 0x19, 64 | 'VK_KANJI' : 0x19, 'VK_ESCAPE' : 0x1B, 'VK_CONVERT' : 0x1C, 'VK_NONCONVERT' : 0x1D, 'VK_ACCEPT' : 0x1E, 65 | 'VK_MODECHANGE' : 0x1F, 'VK_SPACE' : 0x20, 'VK_PRIOR' : 0x21, 'VK_NEXT' : 0x22, 'VK_END' : 0x23, 66 | 'VK_HOME' : 0x24, 'VK_LEFT' : 0x25, 'VK_UP' : 0x26, 'VK_RIGHT' : 0x27, 'VK_DOWN' : 0x28, 67 | 'VK_SELECT' : 0x29, 'VK_PRINT' : 0x2A, 'VK_EXECUTE' : 0x2B, 'VK_SNAPSHOT' : 0x2C, 'VK_INSERT' : 0x2D, 68 | 'VK_DELETE' : 0x2E, 'VK_HELP' : 0x2F, 'VK_LWIN' : 0x5B, 'VK_RWIN' : 0x5C, 'VK_APPS' : 0x5D, 69 | 'VK_NUMPAD0' : 0x60, 'VK_NUMPAD1' : 0x61, 'VK_NUMPAD2' : 0x62, 'VK_NUMPAD3' : 0x63, 'VK_NUMPAD4' : 0x64, 70 | 'VK_NUMPAD5' : 0x65, 'VK_NUMPAD6' : 0x66, 'VK_NUMPAD7' : 0x67, 'VK_NUMPAD8' : 0x68, 'VK_NUMPAD9' : 0x69, 71 | 'VK_MULTIPLY' : 0x6A, 'VK_ADD' : 0x6B, 'VK_SEPARATOR' : 0x6C, 'VK_SUBTRACT' : 0x6D, 'VK_DECIMAL' : 0x6E, 72 | 'VK_DIVIDE' : 0x6F ,'VK_F1' : 0x70, 'VK_F2' : 0x71, 'VK_F3' : 0x72, 'VK_F4' : 0x73, 'VK_F5' : 0x74, 73 | 'VK_F6' : 0x75, 'VK_F7' : 0x76, 'VK_F8' : 0x77, 'VK_F9' : 0x78, 'VK_F10' : 0x79, 'VK_F11' : 0x7A, 74 | 'VK_F12' : 0x7B, 'VK_F13' : 0x7C, 'VK_F14' : 0x7D, 'VK_F15' : 0x7E, 'VK_F16' : 0x7F, 'VK_F17' : 0x80, 75 | 'VK_F18' : 0x81, 'VK_F19' : 0x82, 'VK_F20' : 0x83, 'VK_F21' : 0x84, 'VK_F22' : 0x85, 'VK_F23' : 0x86, 76 | 'VK_F24' : 0x87, 'VK_NUMLOCK' : 0x90, 'VK_SCROLL' : 0x91, 'VK_LSHIFT' : 0xA0, 'VK_RSHIFT' : 0xA1, 77 | 'VK_LCONTROL' : 0xA2, 'VK_RCONTROL' : 0xA3, 'VK_LMENU' : 0xA4, 'VK_RMENU' : 0xA5, 'VK_PROCESSKEY' : 0xE5, 78 | 'VK_ATTN' : 0xF6, 'VK_CRSEL' : 0xF7, 'VK_EXSEL' : 0xF8, 'VK_EREOF' : 0xF9, 'VK_PLAY' : 0xFA, 79 | 'VK_ZOOM' : 0xFB, 'VK_NONAME' : 0xFC, 'VK_PA1' : 0xFD, 'VK_OEM_CLEAR' : 0xFE, 'VK_BROWSER_BACK' : 0xA6, 80 | 'VK_BROWSER_FORWARD' : 0xA7, 'VK_BROWSER_REFRESH' : 0xA8, 'VK_BROWSER_STOP' : 0xA9, 'VK_BROWSER_SEARCH' : 0xAA, 81 | 'VK_BROWSER_FAVORITES' : 0xAB, 'VK_BROWSER_HOME' : 0xAC, 'VK_VOLUME_MUTE' : 0xAD, 'VK_VOLUME_DOWN' : 0xAE, 82 | 'VK_VOLUME_UP' : 0xAF, 'VK_MEDIA_NEXT_TRACK' : 0xB0, 'VK_MEDIA_PREV_TRACK' : 0xB1, 'VK_MEDIA_STOP' : 0xB2, 83 | 'VK_MEDIA_PLAY_PAUSE' : 0xB3, 'VK_LAUNCH_MAIL' : 0xB4, 'VK_LAUNCH_MEDIA_SELECT' : 0xB5, 'VK_LAUNCH_APP1' : 0xB6, 84 | 'VK_LAUNCH_APP2' : 0xB7, 'VK_OEM_1' : 0xBA, 'VK_OEM_PLUS' : 0xBB, 'VK_OEM_COMMA' : 0xBC, 'VK_OEM_MINUS' : 0xBD, 85 | 'VK_OEM_PERIOD' : 0xBE, 'VK_OEM_2' : 0xBF, 'VK_OEM_3' : 0xC0, 'VK_OEM_4' : 0xDB, 'VK_OEM_5' : 0xDC, 86 | 'VK_OEM_6' : 0xDD, 'VK_OEM_7' : 0xDE, 'VK_OEM_8' : 0xDF, 'VK_OEM_102' : 0xE2, 'VK_PROCESSKEY' : 0xE5, 87 | 'VK_PACKET' : 0xE7} 88 | 89 | #inverse mapping of keycodes 90 | id_to_vk = dict([(v,k) for k,v in vk_to_id.items()]) 91 | 92 | #message constants to message names 93 | msg_to_name = {WM_MOUSEMOVE : 'mouse move', WM_LBUTTONDOWN : 'mouse left down', 94 | WM_LBUTTONUP : 'mouse left up', WM_LBUTTONDBLCLK : 'mouse left double', 95 | WM_RBUTTONDOWN : 'mouse right down', WM_RBUTTONUP : 'mouse right up', 96 | WM_RBUTTONDBLCLK : 'mouse right double', WM_MBUTTONDOWN : 'mouse middle down', 97 | WM_MBUTTONUP : 'mouse middle up', WM_MBUTTONDBLCLK : 'mouse middle double', 98 | WM_MOUSEWHEEL : 'mouse wheel', WM_KEYDOWN : 'key down', 99 | WM_KEYUP : 'key up', WM_CHAR : 'key char', WM_DEADCHAR : 'key dead char', 100 | WM_SYSKEYDOWN : 'key sys down', WM_SYSKEYUP : 'key sys up', 101 | WM_SYSCHAR : 'key sys char', WM_SYSDEADCHAR : 'key sys dead char'} 102 | 103 | def MsgToName(cls, msg): 104 | ''' 105 | Class method. Converts a message value to message name. 106 | 107 | @param msg: Keyboard or mouse event message 108 | @type msg: integer 109 | @return: Name of the event 110 | @rtype: string 111 | ''' 112 | return HookConstants.msg_to_name.get(msg) 113 | 114 | def VKeyToID(cls, vkey): 115 | ''' 116 | Class method. Converts a virtual keycode name to its value. 117 | 118 | @param vkey: Virtual keycode name 119 | @type vkey: string 120 | @return: Virtual keycode value 121 | @rtype: integer 122 | ''' 123 | return HookConstants.vk_to_id.get(vkey) 124 | 125 | def IDToName(cls, code): 126 | ''' 127 | Class method. Gets the keycode name for the given value. 128 | 129 | @param code: Virtual keycode value 130 | @type code: integer 131 | @return: Virtual keycode name 132 | @rtype: string 133 | ''' 134 | if (code >= 0x30 and code <= 0x39) or (code >= 0x41 and code <= 0x5A): 135 | text = chr(code) 136 | else: 137 | text = HookConstants.id_to_vk.get(code) 138 | if text is not None: 139 | text = text[3:].title() 140 | return text 141 | 142 | MsgToName=classmethod(MsgToName) 143 | IDToName=classmethod(IDToName) 144 | VKeyToID=classmethod(VKeyToID) 145 | 146 | class HookEvent(object): 147 | ''' 148 | Holds information about a general hook event. 149 | 150 | @ivar Message: Keyboard or mouse event message 151 | @type Message: integer 152 | @ivar Time: Seconds since the epoch when the even current 153 | @type Time: integer 154 | @ivar Window: Window handle of the foreground window at the time of the event 155 | @type Window: integer 156 | @ivar WindowName: Name of the foreground window at the time of the event 157 | @type WindowName: string 158 | ''' 159 | def __init__(self, msg, time, hwnd, window_name): 160 | '''Initializes an event instance.''' 161 | self.Message = msg 162 | self.Time = time 163 | self.Window = hwnd 164 | self.WindowName = window_name 165 | 166 | def GetMessageName(self): 167 | ''' 168 | @return: Name of the event 169 | @rtype: string 170 | ''' 171 | return HookConstants.MsgToName(self.Message) 172 | MessageName = property(fget=GetMessageName) 173 | 174 | class MouseEvent(HookEvent): 175 | ''' 176 | Holds information about a mouse event. 177 | 178 | @ivar Position: Location of the mouse event on the screen 179 | @type Position: 2-tuple of integer 180 | @ivar Wheel: Positive if the wheel scrolls up, negative if down, zero otherwise 181 | @type Wheel: integer 182 | @ivar Injected: Was this event generated programmatically? 183 | @type Injected: boolean 184 | ''' 185 | def __init__(self, msg, x, y, data, flags, time, hwnd, window_name): 186 | '''Initializes an instance of the class.''' 187 | HookEvent.__init__(self, msg, time, hwnd, window_name) 188 | self.Position = (x,y) 189 | if data > 0: w = 1 190 | elif data < 0: w = -1 191 | else: w = 0 192 | self.Wheel = w 193 | self.Injected = flags & 0x01 194 | 195 | class KeyboardEvent(HookEvent): 196 | ''' 197 | Holds information about a mouse event. 198 | 199 | @ivar KeyID: Virtual key code 200 | @type KeyID: integer 201 | @ivar ScanCode: Scan code 202 | @type ScanCode: integer 203 | @ivar Ascii: ASCII value, if one exists 204 | @type Ascii: string 205 | ''' 206 | def __init__(self, msg, vk_code, scan_code, ascii, flags, time, hwnd, window_name): 207 | '''Initializes an instances of the class.''' 208 | HookEvent.__init__(self, msg, time, hwnd, window_name) 209 | self.KeyID = vk_code 210 | self.ScanCode = scan_code 211 | self.Ascii = ascii 212 | self.flags = flags 213 | 214 | def GetKey(self): 215 | ''' 216 | @return: Name of the virtual keycode 217 | @rtype: string 218 | ''' 219 | return HookConstants.IDToName(self.KeyID) 220 | 221 | def IsExtended(self): 222 | ''' 223 | @return: Is this an extended key? 224 | @rtype: boolean 225 | ''' 226 | return self.flags & 0x01 227 | 228 | def IsInjected(self): 229 | ''' 230 | @return: Was this event generated programmatically? 231 | @rtype: boolean 232 | ''' 233 | return self.flags & 0x10 234 | 235 | def IsAlt(self): 236 | ''' 237 | @return: Was the alt key depressed? 238 | @rtype: boolean 239 | ''' 240 | return self.flags & 0x20 241 | 242 | def IsTransition(self): 243 | ''' 244 | @return: Is this a transition from up to down or vice versa? 245 | @rtype: boolean 246 | ''' 247 | return self.flags & 0x80 248 | 249 | Key = property(fget=GetKey) 250 | Extended = property(fget=IsExtended) 251 | Injected = property(fget=IsInjected) 252 | Alt = property(fget=IsAlt) 253 | Transition = property(fget=IsTransition) 254 | 255 | class HookManager(object): 256 | ''' 257 | Registers and manages callbacks for low level mouse and keyboard events. 258 | 259 | @ivar mouse_funcs: Callbacks for mouse events 260 | @type mouse_funcs: dictionary 261 | @ivar keyboard_funcs: Callbacks for keyboard events 262 | @type keyboard_funcs: dictionary 263 | @ivar mouse_hook: Is a mouse hook set? 264 | @type mouse_hook: boolean 265 | @ivar keyboard_hook: Is a keyboard hook set? 266 | @type keyboard_hook: boolean 267 | ''' 268 | def __init__(self): 269 | '''Initializes an instance by setting up an empty set of handlers.''' 270 | self.mouse_funcs = {} 271 | self.keyboard_funcs = {} 272 | 273 | self.mouse_hook = False 274 | self.keyboard_hook = False 275 | 276 | def __del__(self): 277 | '''Unhook all registered hooks.''' 278 | self.UnhookMouse() 279 | self.UnhookKeyboard() 280 | 281 | def HookMouse(self): 282 | '''Begins watching for mouse events.''' 283 | cpyHook.cSetHook(HookConstants.WH_MOUSE_LL, self.MouseSwitch) 284 | self.mouse_hook = True 285 | 286 | def HookKeyboard(self): 287 | '''Begins watching for keyboard events.''' 288 | cpyHook.cSetHook(HookConstants.WH_KEYBOARD_LL, self.KeyboardSwitch) 289 | self.keyboard_hook = True 290 | 291 | def UnhookMouse(self): 292 | '''Stops watching for mouse events.''' 293 | if self.mouse_hook: 294 | cpyHook.cUnhook(HookConstants.WH_MOUSE_LL) 295 | self.mouse_hook = False 296 | 297 | def UnhookKeyboard(self): 298 | '''Stops watching for keyboard events.''' 299 | if self.keyboard_hook: 300 | cpyHook.cUnhook(HookConstants.WH_KEYBOARD_LL) 301 | self.keyboard_hook = False 302 | 303 | def MouseSwitch(self, msg, x, y, data, flags, time, hwnd, window_name): 304 | ''' 305 | Passes a mouse event on to the appropriate handler if one is registered. 306 | 307 | @param msg: Message value 308 | @type msg: integer 309 | @param x: x-coordinate of the mouse event 310 | @type x: integer 311 | @param y: y-coordinate of the mouse event 312 | @type y: integer 313 | @param data: Data associated with the mouse event (scroll information) 314 | @type data: integer 315 | @param flags: Flags associated with the mouse event (injected or not) 316 | @type flags: integer 317 | @param time: Seconds since the epoch when the even current 318 | @type time: integer 319 | @param hwnd: Window handle of the foreground window at the time of the event 320 | @type hwnd: integer 321 | ''' 322 | event = MouseEvent(msg, x, y, data, flags, time, hwnd, window_name) 323 | func = self.mouse_funcs.get(msg) 324 | if func: 325 | return func(event) 326 | else: 327 | return True 328 | 329 | def KeyboardSwitch(self, msg, vk_code, scan_code, ascii, flags, time, hwnd, win_name): 330 | ''' 331 | Passes a keyboard event on to the appropriate handler if one is registered. 332 | 333 | @param msg: Message value 334 | @type msg: integer 335 | @param vk_code: The virtual keycode of the key 336 | @type vk_code: integer 337 | @param scan_code: The scan code of the key 338 | @type scan_code: integer 339 | @param ascii: ASCII numeric value for the key if available 340 | @type ascii: integer 341 | @param flags: Flags associated with the key event (injected or not, extended key, etc.) 342 | @type flags: integer 343 | @param time: Time since the epoch of the key event 344 | @type time: integer 345 | @param hwnd: Window handle of the foreground window at the time of the event 346 | @type hwnd: integer 347 | ''' 348 | event = KeyboardEvent(msg, vk_code, scan_code, ascii, flags, time, hwnd, win_name) 349 | func = self.keyboard_funcs.get(msg) 350 | if func: 351 | return func(event) 352 | else: 353 | return True 354 | 355 | def SubscribeMouseMove(self, func): 356 | ''' 357 | Registers the given function as the callback for this mouse event type. Use the 358 | MouseMove property as a shortcut. 359 | 360 | @param func: Callback function 361 | @type func: callable 362 | ''' 363 | if func is None: 364 | self.disconnect(self.mouse_funcs, HookConstants.WM_MOUSEMOVE) 365 | else: 366 | self.connect(self.mouse_funcs, HookConstants.WM_MOUSEMOVE, func) 367 | 368 | def SubscribeMouseLeftUp(self, func): 369 | ''' 370 | Registers the given function as the callback for this mouse event type. Use the 371 | MouseLeftUp property as a shortcut. 372 | 373 | @param func: Callback function 374 | @type func: callable 375 | ''' 376 | if func is None: 377 | self.disconnect(self.mouse_funcs, HookConstants.WM_LBUTTONUP) 378 | else: 379 | self.connect(self.mouse_funcs, HookConstants.WM_LBUTTONUP, func) 380 | 381 | def SubscribeMouseLeftDown(self, func): 382 | ''' 383 | Registers the given function as the callback for this mouse event type. Use the 384 | MouseLeftDown property as a shortcut. 385 | 386 | @param func: Callback function 387 | @type func: callable 388 | ''' 389 | if func is None: 390 | self.disconnect(self.mouse_funcs, HookConstants.WM_LBUTTONDOWN) 391 | else: 392 | self.connect(self.mouse_funcs, HookConstants.WM_LBUTTONDOWN, func) 393 | 394 | def SubscribeMouseLeftDbl(self, func): 395 | ''' 396 | Registers the given function as the callback for this mouse event type. Use the 397 | MouseLeftDbl property as a shortcut. 398 | 399 | @param func: Callback function 400 | @type func: callable 401 | ''' 402 | if func is None: 403 | self.disconnect(self.mouse_funcs, HookConstants.WM_LBUTTONDBLCLK) 404 | else: 405 | self.connect(self.mouse_funcs, HookConstants.WM_LBUTTONDBLCLK, func) 406 | 407 | def SubscribeMouseRightUp(self, func): 408 | ''' 409 | Registers the given function as the callback for this mouse event type. Use the 410 | MouseRightUp property as a shortcut. 411 | 412 | @param func: Callback function 413 | @type func: callable 414 | ''' 415 | if func is None: 416 | self.disconnect(self.mouse_funcs, HookConstants.WM_RBUTTONUP) 417 | else: 418 | self.connect(self.mouse_funcs, HookConstants.WM_RBUTTONUP, func) 419 | 420 | def SubscribeMouseRightDown(self, func): 421 | ''' 422 | Registers the given function as the callback for this mouse event type. Use the 423 | MouseRightDown property as a shortcut. 424 | 425 | @param func: Callback function 426 | @type func: callable 427 | ''' 428 | if func is None: 429 | self.disconnect(self.mouse_funcs, HookConstants.WM_RBUTTONDOWN) 430 | else: 431 | self.connect(self.mouse_funcs, HookConstants.WM_RBUTTONDOWN, func) 432 | 433 | def SubscribeMouseRightDbl(self, func): 434 | ''' 435 | Registers the given function as the callback for this mouse event type. Use the 436 | MouseRightDbl property as a shortcut. 437 | 438 | @param func: Callback function 439 | @type func: callable 440 | ''' 441 | if func is None: 442 | self.disconnect(self.mouse_funcs, HookConstants.WM_RBUTTONDBLCLK) 443 | else: 444 | self.connect(self.mouse_funcs, HookConstants.WM_RBUTTONDBLCLK, func) 445 | 446 | def SubscribeMouseMiddleUp(self, func): 447 | ''' 448 | Registers the given function as the callback for this mouse event type. Use the 449 | MouseMiddleUp property as a shortcut. 450 | 451 | @param func: Callback function 452 | @type func: callable 453 | ''' 454 | if func is None: 455 | self.disconnect(self.mouse_funcs, HookConstants.WM_MBUTTONUP) 456 | else: 457 | self.connect(self.mouse_funcs, HookConstants.WM_MBUTTONUP, func) 458 | 459 | def SubscribeMouseMiddleDown(self, func): 460 | ''' 461 | Registers the given function as the callback for this mouse event type. Use the 462 | MouseMiddleDown property as a shortcut. 463 | 464 | @param func: Callback function 465 | @type func: callable 466 | ''' 467 | if func is None: 468 | self.disconnect(self.mouse_funcs, HookConstants.WM_MBUTTONDOWN) 469 | else: 470 | self.connect(self.mouse_funcs, HookConstants.WM_MBUTTONDOWN, func) 471 | 472 | def SubscribeMouseMiddleDbl(self, func): 473 | ''' 474 | Registers the given function as the callback for this mouse event type. Use the 475 | MouseMiddleDbl property as a shortcut. 476 | 477 | @param func: Callback function 478 | @type func: callable 479 | ''' 480 | if func is None: 481 | self.disconnect(self.mouse_funcs, HookConstants.WM_MBUTTONDBLCLK) 482 | else: 483 | self.connect(self.mouse_funcs, HookConstants.WM_MBUTTONDBLCLK, func) 484 | 485 | def SubscribeMouseWheel(self, func): 486 | ''' 487 | Registers the given function as the callback for this mouse event type. Use the 488 | MouseWheel property as a shortcut. 489 | 490 | @param func: Callback function 491 | @type func: callable 492 | ''' 493 | if func is None: 494 | self.disconnect(self.mouse_funcs, HookConstants.WM_MOUSEWHEEL) 495 | else: 496 | self.connect(self.mouse_funcs, HookConstants.WM_MOUSEWHEEL, func) 497 | 498 | def SubscribeMouseAll(self, func): 499 | ''' 500 | Registers the given function as the callback for all mouse events. Use the 501 | MouseAll property as a shortcut. 502 | 503 | @param func: Callback function 504 | @type func: callable 505 | ''' 506 | self.SubscribeMouseMove(func) 507 | self.SubscribeMouseWheel(func) 508 | self.SubscribeMouseAllButtons(func) 509 | 510 | def SubscribeMouseAllButtons(self, func): 511 | ''' 512 | Registers the given function as the callback for all mouse button events. Use the 513 | MouseButtonAll property as a shortcut. 514 | 515 | @param func: Callback function 516 | @type func: callable 517 | ''' 518 | self.SubscribeMouseAllButtonsDown(func) 519 | self. SubscribeMouseAllButtonsUp(func) 520 | self.SubscribeMouseAllButtonsDbl(func) 521 | 522 | def SubscribeMouseAllButtonsDown(self, func): 523 | ''' 524 | Registers the given function as the callback for all mouse button down events. 525 | Use the MouseAllButtonsDown property as a shortcut. 526 | 527 | @param func: Callback function 528 | @type func: callable 529 | ''' 530 | self.SubscribeMouseLeftDown(func) 531 | self.SubscribeMouseRightDown(func) 532 | self.SubscribeMouseMiddleDown(func) 533 | 534 | def SubscribeMouseAllButtonsUp(self, func): 535 | ''' 536 | Registers the given function as the callback for all mouse button up events. 537 | Use the MouseAllButtonsUp property as a shortcut. 538 | 539 | @param func: Callback function 540 | @type func: callable 541 | ''' 542 | self.SubscribeMouseLeftUp(func) 543 | self.SubscribeMouseRightUp(func) 544 | self.SubscribeMouseMiddleUp(func) 545 | 546 | def SubscribeMouseAllButtonsDbl(self, func): 547 | ''' 548 | Registers the given function as the callback for all mouse button double click 549 | events. Use the MouseAllButtonsDbl property as a shortcut. 550 | 551 | @param func: Callback function 552 | @type func: callable 553 | ''' 554 | self.SubscribeMouseLeftDbl(func) 555 | self.SubscribeMouseRightDbl(func) 556 | self.SubscribeMouseMiddleDbl(func) 557 | 558 | def SubscribeKeyDown(self, func): 559 | ''' 560 | Registers the given function as the callback for this keyboard event type. 561 | Use the KeyDown property as a shortcut. 562 | 563 | @param func: Callback function 564 | @type func: callable 565 | ''' 566 | if func is None: 567 | self.disconnect(self.keyboard_funcs, HookConstants.WM_KEYDOWN) 568 | self.disconnect(self.keyboard_funcs, HookConstants.WM_SYSKEYDOWN) 569 | else: 570 | self.connect(self.keyboard_funcs, HookConstants.WM_KEYDOWN, func) 571 | self.connect(self.keyboard_funcs, HookConstants.WM_SYSKEYDOWN, func) 572 | 573 | def SubscribeKeyUp(self, func): 574 | ''' 575 | Registers the given function as the callback for this keyboard event type. 576 | Use the KeyUp property as a shortcut. 577 | 578 | @param func: Callback function 579 | @type func: callable 580 | ''' 581 | if func is None: 582 | self.disconnect(self.keyboard_funcs, HookConstants.WM_KEYUP) 583 | self.disconnect(self.keyboard_funcs, HookConstants.WM_SYSKEYUP) 584 | else: 585 | self.connect(self.keyboard_funcs, HookConstants.WM_KEYUP, func) 586 | self.connect(self.keyboard_funcs, HookConstants.WM_SYSKEYUP, func) 587 | 588 | def SubscribeKeyChar(self, func): 589 | ''' 590 | Registers the given function as the callback for this keyboard event type. 591 | Use the KeyChar property as a shortcut. 592 | 593 | B{Note}: this is currently non-functional, no WM_*CHAR messages are 594 | processed by the keyboard hook. 595 | 596 | @param func: Callback function 597 | @type func: callable 598 | ''' 599 | if func is None: 600 | self.disconnect(self.keyboard_funcs, HookConstants.WM_CHAR) 601 | self.disconnect(self.keyboard_funcs, HookConstants.WM_DEADCHAR) 602 | self.disconnect(self.keyboard_funcs, HookConstants.WM_SYSCHAR) 603 | self.disconnect(self.keyboard_funcs, HookConstants.WM_SYSDEADCHAR) 604 | else: 605 | self.connect(self.keyboard_funcs, HookConstants.WM_CHAR, func) 606 | self.connect(self.keyboard_funcs, HookConstants.WM_DEADCHAR, func) 607 | self.connect(self.keyboard_funcs, HookConstants.WM_SYSCHAR, func) 608 | self.connect(self.keyboard_funcs, HookConstants.WM_SYSDEADCHAR, func) 609 | 610 | def SubscribeKeyAll(self, func): 611 | ''' 612 | Registers the given function as the callback for all keyboard events. 613 | Use the KeyAll property as a shortcut. 614 | 615 | @param func: Callback function 616 | @type func: callable 617 | ''' 618 | self.SubscribeKeyDown(func) 619 | self.SubscribeKeyUp(func) 620 | self.SubscribeKeyChar(func) 621 | 622 | MouseAll = property(fset=SubscribeMouseAll) 623 | MouseAllButtons = property(fset=SubscribeMouseAllButtons) 624 | MouseAllButtonsUp = property(fset=SubscribeMouseAllButtonsUp) 625 | MouseAllButtonsDown = property(fset=SubscribeMouseAllButtonsDown) 626 | MouseAllButtonsDbl = property(fset=SubscribeMouseAllButtonsDbl) 627 | 628 | MouseWheel = property(fset=SubscribeMouseWheel) 629 | MouseMove = property(fset=SubscribeMouseMove) 630 | MouseLeftUp = property(fset=SubscribeMouseLeftUp) 631 | MouseLeftDown = property(fset=SubscribeMouseLeftDown) 632 | MouseLeftDbl = property(fset=SubscribeMouseLeftDbl) 633 | MouseRightUp = property(fset=SubscribeMouseRightUp) 634 | MouseRightDown = property(fset=SubscribeMouseRightDown) 635 | MouseRightDbl = property(fset=SubscribeMouseRightDbl) 636 | MouseMiddleUp = property(fset=SubscribeMouseMiddleUp) 637 | MouseMiddleDown = property(fset=SubscribeMouseMiddleDown) 638 | MouseMiddleDbl = property(fset=SubscribeMouseMiddleDbl) 639 | 640 | KeyUp = property(fset=SubscribeKeyUp) 641 | KeyDown = property(fset=SubscribeKeyDown) 642 | KeyChar = property(fset=SubscribeKeyChar) 643 | KeyAll = property(fset=SubscribeKeyAll) 644 | 645 | def connect(self, switch, id, func): 646 | ''' 647 | Registers a callback to the given function for the event with the given ID in the 648 | provided dictionary. Internal use only. 649 | 650 | @param switch: Collection of callbacks 651 | @type switch: dictionary 652 | @param id: Event type 653 | @type id: integer 654 | @param func: Callback function 655 | @type func: callable 656 | ''' 657 | switch[id] = func 658 | 659 | def disconnect(self, switch, id): 660 | ''' 661 | Unregisters a callback for the event with the given ID in the provided dictionary. 662 | Internal use only. 663 | 664 | @param switch: Collection of callbacks 665 | @type switch: dictionary 666 | @param id: Event type 667 | @type id: integer 668 | ''' 669 | try: 670 | del switch[id] 671 | except: 672 | pass 673 | -------------------------------------------------------------------------------- /pyWinhook/__init__.py: -------------------------------------------------------------------------------- 1 | from .HookManager import * -------------------------------------------------------------------------------- /pyWinhook/aa hook.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import wx 3 | import pyWinhook as pyHook 4 | from pyAA import * 5 | 6 | class myFrame(wx.Frame): 7 | def __init__(self): 8 | wx.Frame.__init__(self, None, -1, 'My Frame') 9 | 10 | self.hm = pyHook.HookManager() 11 | self.hm.MouseAllButtonsDown = self.OnMouseEvent 12 | self.hm.KeyDown = self.OnKeyboardEvent 13 | 14 | self.hm.HookMouse() 15 | self.hm.HookKeyboard() 16 | 17 | wx.EVT_CLOSE(self, self.OnClose) 18 | 19 | def OnGetAO(self, event): 20 | if event.Type == 'keyboard': 21 | ao = AccessibleObjectFromWindow(event.Window, OBJID_CLIENT) 22 | elif event.Type == 'mouse': 23 | ao = AccessibleObjectFromPoint(event.Position) 24 | 25 | print('') 26 | print('---------------------------') 27 | print('Event:') 28 | print(' %s' % event.MessageName) 29 | print(' Window: %s' % event.WindowName) 30 | if event.Type == 'keyboard': 31 | print(' Key: %s' % event.Key) 32 | print('') 33 | print('Object:') 34 | try: 35 | print(' Name: %s' % ao.Name) 36 | except: 37 | print() 38 | 39 | try: 40 | print(' Value: %s' % ao.Value) 41 | except: 42 | print() 43 | 44 | try: 45 | print(' Role: %s' % ao.RoleText) 46 | except: 47 | print() 48 | 49 | try: 50 | print(' Description: %s' %ao.Description) 51 | except: 52 | print() 53 | 54 | try: 55 | print(' State: %s' % ao.StateText) 56 | except: 57 | print() 58 | 59 | try: 60 | print(' Shortcut: %s' % ao.KeyboardShortcut) 61 | except: 62 | print() 63 | 64 | def OnMouseEvent(self, event): 65 | event.Type = 'mouse' 66 | wx.CallAfter(self.OnGetAO, event) 67 | 68 | def OnKeyboardEvent(self, event): 69 | event.Type = 'keyboard' 70 | wx.CallAfter(self.OnGetAO, event) 71 | 72 | def OnClose(self, event): 73 | del self.hm 74 | self.Destroy() 75 | 76 | if __name__ == '__main__': 77 | app = wx.PySimpleApp(0) 78 | frame = myFrame() 79 | app.SetTopWindow(frame) 80 | frame.Show() 81 | app.MainLoop() 82 | -------------------------------------------------------------------------------- /pyWinhook/cpyHook.i: -------------------------------------------------------------------------------- 1 | %module cpyHook 2 | %include typemaps.i 3 | 4 | %{ 5 | #define _WIN32_WINNT 0x400 6 | #include "windows.h" 7 | 8 | #if PY_MAJOR_VERSION >= 3 9 | #define PY3K 10 | #endif 11 | 12 | PyObject* callback_funcs[WH_MAX]; 13 | HHOOK hHooks[WH_MAX]; 14 | BYTE key_state[256]; 15 | %} 16 | 17 | #ifdef SWIGPYTHON 18 | %typemap(in) PyObject *pyfunc { 19 | if (!PyCallable_Check($input)) { 20 | PyErr_SetString(PyExc_TypeError, "Need a callable object"); 21 | return NULL; 22 | } 23 | $1 = $input; 24 | } 25 | #endif 26 | 27 | %init %{ 28 | memset(key_state, 0, 256); 29 | memset(callback_funcs, 0, WH_MAX); 30 | memset(hHooks, 0, WH_MAX); 31 | PyEval_InitThreads(); 32 | 33 | // get initial key state 34 | Py_BEGIN_ALLOW_THREADS 35 | key_state[VK_NUMLOCK] = (GetKeyState(VK_NUMLOCK)&0x0001) ? 0x01 : 0x00; 36 | key_state[VK_CAPITAL] = (GetKeyState(VK_CAPITAL)&0x0001) ? 0x01 : 0x00; 37 | key_state[VK_SCROLL] = (GetKeyState(VK_SCROLL)&0x0001) ? 0x01 : 0x00; 38 | Py_END_ALLOW_THREADS 39 | %} 40 | 41 | %wrapper %{ 42 | unsigned short ConvertToASCII(unsigned int keycode, unsigned int scancode); 43 | void UpdateKeyState(unsigned int vkey, int msg); 44 | 45 | LRESULT CALLBACK cLLKeyboardCallback(int code, WPARAM wParam, LPARAM lParam) { 46 | PyObject *arglist, *r; 47 | PKBDLLHOOKSTRUCT kbd; 48 | HWND hwnd; 49 | PWSTR win_name = NULL; 50 | unsigned short ascii = 0; 51 | static int win_len; 52 | static long result; 53 | long pass = 1; 54 | PyGILState_STATE gil; 55 | 56 | // uncomment this next bit if you do not want to process events like "ctl-alt-del" 57 | // and other events that are not supposed to be processed 58 | // as per msdn documentation: 59 | // http://msdn.microsoft.com/en-us/library/ms644985(VS.85).aspx 60 | 61 | // if message code < 0, return immediately 62 | //if(code<0) 63 | // CallNextHookEx(hHooks[WH_KEYBOARD_LL], code, wParam, lParam); 64 | 65 | // get the GIL 66 | gil = PyGILState_Ensure(); 67 | 68 | // cast to a keyboard event struct 69 | kbd = (PKBDLLHOOKSTRUCT)lParam; 70 | // get the current foreground window (might not be the real window that received the event) 71 | hwnd = GetForegroundWindow(); 72 | 73 | // convert to an ASCII code if possible 74 | ascii = ConvertToASCII(kbd->vkCode, kbd->scanCode); 75 | 76 | #ifdef PY3K 77 | // grab the window name if possible 78 | win_len = GetWindowTextLengthW(hwnd); 79 | if(win_len > 0) { 80 | win_name = (PWSTR) malloc(sizeof(wchar_t) * win_len + 1); 81 | GetWindowTextW(hwnd, win_name, win_len + 1); 82 | } 83 | 84 | //build the argument list to the callback function 85 | arglist = Py_BuildValue("(iiiiiiiu)", wParam, kbd->vkCode, kbd->scanCode, ascii, 86 | kbd->flags, kbd->time, hwnd, win_name); 87 | #else 88 | // grab the window name if possible 89 | win_len = GetWindowTextLength(hwnd); 90 | if(win_len > 0) { 91 | win_name = (PSTR) malloc(sizeof(char) * win_len + 1); 92 | GetWindowText(hwnd, win_name, win_len + 1); 93 | } 94 | 95 | //build the argument list to the callback function 96 | arglist = Py_BuildValue("(iiiiiiiz)", wParam, kbd->vkCode, kbd->scanCode, ascii, 97 | kbd->flags, kbd->time, hwnd, win_name); 98 | #endif 99 | 100 | r = PyObject_CallObject(callback_funcs[WH_KEYBOARD_LL], arglist); 101 | 102 | // check if we should pass the event on or not 103 | if(r == NULL) 104 | PyErr_Print(); 105 | else 106 | pass = PyInt_AsLong(r); 107 | 108 | Py_XDECREF(r); 109 | Py_DECREF(arglist); 110 | // release the GIL 111 | PyGILState_Release(gil); 112 | 113 | // free the memory for the window name 114 | if(win_name != NULL) 115 | free(win_name); 116 | 117 | // decide whether or not to call the next hook 118 | if(code < 0 || pass) { 119 | UpdateKeyState(kbd->vkCode, wParam); 120 | result = CallNextHookEx(hHooks[WH_KEYBOARD_LL], code, wParam, lParam); 121 | } else { 122 | // return a non-zero to prevent further processing 123 | result = 42; 124 | } 125 | return result; 126 | } 127 | 128 | LRESULT CALLBACK cLLMouseCallback(int code, WPARAM wParam, LPARAM lParam) { 129 | PyObject *arglist, *r; 130 | PMSLLHOOKSTRUCT ms; 131 | HWND hwnd; 132 | PWSTR win_name = NULL; 133 | static int win_len; 134 | static long result; 135 | long pass = 1; 136 | PyGILState_STATE gil; 137 | 138 | // get the GIL 139 | gil = PyGILState_Ensure(); 140 | 141 | //pass the message on to the Python function 142 | ms = (PMSLLHOOKSTRUCT)lParam; 143 | hwnd = WindowFromPoint(ms->pt); 144 | 145 | #ifdef PY3K 146 | //grab the window name if possible 147 | win_len = GetWindowTextLengthW(hwnd); 148 | if(win_len > 0) { 149 | win_name = (PWSTR) malloc(sizeof(wchar_t) * win_len + 1); 150 | GetWindowTextW(hwnd, win_name, win_len + 1); 151 | } 152 | 153 | //build the argument list to the callback function 154 | arglist = Py_BuildValue("(iiiiiiiu)", wParam, ms->pt.x, ms->pt.y, ms->mouseData, 155 | ms->flags, ms->time, hwnd, win_name); 156 | #else 157 | //grab the window name if possible 158 | win_len = GetWindowTextLength(hwnd); 159 | if(win_len > 0) { 160 | win_name = (PSTR) malloc(sizeof(char) * win_len + 1); 161 | GetWindowText(hwnd, win_name, win_len + 1); 162 | } 163 | 164 | //build the argument list to the callback function 165 | arglist = Py_BuildValue("(iiiiiiiz)", wParam, ms->pt.x, ms->pt.y, ms->mouseData, 166 | ms->flags, ms->time, hwnd, win_name); 167 | #endif 168 | 169 | r = PyObject_CallObject(callback_funcs[WH_MOUSE_LL], arglist); 170 | 171 | // check if we should pass the event on or not 172 | if(r == NULL) 173 | PyErr_Print(); 174 | else 175 | pass = PyInt_AsLong(r); 176 | 177 | Py_XDECREF(r); 178 | Py_DECREF(arglist); 179 | // release the GIL 180 | PyGILState_Release(gil); 181 | 182 | //free the memory for the window name 183 | if(win_name != NULL) 184 | free(win_name); 185 | 186 | // decide whether or not to call the next hook 187 | if(code < 0 || pass) 188 | result = CallNextHookEx(hHooks[WH_MOUSE_LL], code, wParam, lParam); 189 | else { 190 | // return non-zero to prevent further processing 191 | result = 42; 192 | } 193 | return result; 194 | } 195 | 196 | int cSetHook(int idHook, PyObject *pyfunc) { 197 | HINSTANCE hMod; 198 | 199 | //make sure we have a valid hook number 200 | if(idHook > WH_MAX || idHook < WH_MIN) { 201 | PyErr_SetString(PyExc_ValueError, "Hooking error: invalid hook ID"); 202 | } 203 | 204 | //get the module handle 205 | Py_BEGIN_ALLOW_THREADS 206 | // try to get handle for current file - will succeed if called from a compiled .exe 207 | hMod = GetModuleHandle(NULL); 208 | if(NULL == hMod) // otherwise use name for DLL 209 | hMod = GetModuleHandle("_cpyHook.pyd"); 210 | Py_END_ALLOW_THREADS 211 | 212 | //switch on the type of hook so we point to the right C callback 213 | switch(idHook) { 214 | case WH_MOUSE_LL: 215 | if(callback_funcs[idHook] != NULL) 216 | break; 217 | 218 | callback_funcs[idHook] = pyfunc; 219 | Py_INCREF(callback_funcs[idHook]); 220 | 221 | Py_BEGIN_ALLOW_THREADS 222 | hHooks[idHook] = SetWindowsHookEx(WH_MOUSE_LL, cLLMouseCallback, (HINSTANCE) hMod, 0); 223 | Py_END_ALLOW_THREADS 224 | break; 225 | 226 | case WH_KEYBOARD_LL: 227 | if(callback_funcs[idHook] != NULL) 228 | break; 229 | 230 | callback_funcs[idHook] = pyfunc; 231 | Py_INCREF(callback_funcs[idHook]); 232 | 233 | Py_BEGIN_ALLOW_THREADS 234 | hHooks[idHook] = SetWindowsHookEx(WH_KEYBOARD_LL, cLLKeyboardCallback, (HINSTANCE) hMod, 0); 235 | Py_END_ALLOW_THREADS 236 | break; 237 | 238 | default: 239 | return 0; 240 | } 241 | 242 | if(!hHooks[idHook]) { 243 | PyErr_SetString(PyExc_TypeError, "Could not set hook"); 244 | } 245 | 246 | return 1; 247 | } 248 | 249 | int cUnhook(int idHook) { 250 | BOOL result; 251 | 252 | //make sure we have a valid hook number 253 | if(idHook > WH_MAX || idHook < WH_MIN) { 254 | PyErr_SetString(PyExc_ValueError, "Invalid hook ID"); 255 | } 256 | 257 | //unhook the callback 258 | Py_BEGIN_ALLOW_THREADS 259 | result = UnhookWindowsHookEx(hHooks[idHook]); 260 | Py_END_ALLOW_THREADS 261 | 262 | if(result) { 263 | //decrease the ref to the Python callback 264 | Py_DECREF(callback_funcs[idHook]); 265 | callback_funcs[idHook] = NULL; 266 | } 267 | 268 | return result; 269 | } 270 | 271 | void SetKeyState(unsigned int vkey, int down) { 272 | // (1 > 0) ? True : False 273 | if (vkey == VK_MENU || vkey == VK_LMENU || vkey == VK_RMENU) { 274 | key_state[vkey] = (down) ? 0x80 : 0x00; 275 | key_state[VK_MENU] = key_state[VK_LMENU] | key_state[VK_RMENU]; 276 | } else if (vkey == VK_SHIFT || vkey == VK_LSHIFT || vkey == VK_RSHIFT) { 277 | key_state[vkey] = (down) ? 0x80 : 0x00; 278 | key_state[VK_SHIFT] = key_state[VK_LSHIFT] | key_state[VK_RSHIFT]; 279 | } else if (vkey == VK_CONTROL || vkey == VK_LCONTROL || vkey == VK_RCONTROL) { 280 | key_state[vkey] = (down) ? 0x80 : 0x00; 281 | key_state[VK_CONTROL] = key_state[VK_LCONTROL] | key_state[VK_RCONTROL]; 282 | } else if (vkey == VK_NUMLOCK && !down) { 283 | key_state[VK_NUMLOCK] = !key_state[VK_NUMLOCK]; 284 | } else if (vkey == VK_CAPITAL && !down) { 285 | key_state[VK_CAPITAL] = !key_state[VK_CAPITAL]; 286 | } else if (vkey == VK_SCROLL && !down) { 287 | key_state[VK_SCROLL] = !key_state[VK_SCROLL]; 288 | } 289 | } 290 | 291 | void UpdateKeyState(unsigned int vkey, int msg) { 292 | if (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN) { 293 | SetKeyState(vkey, 1); 294 | } else if (msg == WM_KEYUP || msg == WM_SYSKEYUP) { 295 | SetKeyState(vkey, 0); 296 | } 297 | } 298 | 299 | unsigned int cGetKeyState(unsigned int vkey) { 300 | return key_state[vkey]; 301 | } 302 | 303 | unsigned short ConvertToASCII(unsigned int keycode, unsigned int scancode) { 304 | int r; 305 | unsigned short c = 0; 306 | 307 | Py_BEGIN_ALLOW_THREADS 308 | r = ToAscii(keycode, scancode, key_state, &c, 0); 309 | Py_END_ALLOW_THREADS 310 | if(r < 0) { 311 | //PyErr_SetString(PyExc_ValueError, "Could not convert to ASCII"); 312 | return 0; 313 | } 314 | return c; 315 | } 316 | %} 317 | 318 | unsigned int cGetKeyState(unsigned int vkey); 319 | int cSetHook(int idHook, PyObject *pyfunc); 320 | int cUnhook(int idHook); 321 | -------------------------------------------------------------------------------- /pyWinhook/doc.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from epydoc.cli import cli 3 | 4 | # add default parameters to the command line 5 | options = ['--html', '-o', 'doc', '--inheritance', 'listed'] 6 | sys.argv.extend(options) 7 | # extend the command line to include the given files 8 | files = ['__init__.py', 'HookManager.py'] 9 | sys.argv.extend(files) 10 | cli() -------------------------------------------------------------------------------- /pyWinhook/example.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import pyWinhook as pyHook 3 | 4 | def OnMouseEvent(event): 5 | print('MessageName: %s' % event.MessageName) 6 | print('Message: %s' % event.Message) 7 | print('Time: %s' % event.Time) 8 | print('Window: %s' % event.Window) 9 | print('WindowName: %s' % event.WindowName) 10 | print('Position: (%d, %d)' % event.Position) 11 | print('Wheel: %s' % event.Wheel) 12 | print('Injected: %s' % event.Injected) 13 | print('---') 14 | 15 | # return True to pass the event to other handlers 16 | # return False to stop the event from propagating 17 | return True 18 | 19 | def OnKeyboardEvent(event): 20 | print('MessageName: %s' % event.MessageName) 21 | print('Message: %s' % event.Message) 22 | print('Time: %s' % event.Time) 23 | print('Window: %s' % event.Window) 24 | print('WindowName: %s' % event.WindowName) 25 | print('Ascii: %s' % event.Ascii, chr(event.Ascii)) 26 | print('Key: %s' % event.Key) 27 | print('KeyID: %s' % event.KeyID) 28 | print('ScanCode: %s' % event.ScanCode) 29 | print('Extended: %s' % event.Extended) 30 | print('Injected: %s' % event.Injected) 31 | print('Alt %s' % event.Alt) 32 | print('Transition %s' % event.Transition) 33 | print('---') 34 | 35 | # return True to pass the event to other handlers 36 | # return False to stop the event from propagating 37 | return True 38 | 39 | # create the hook mananger 40 | hm = pyHook.HookManager() 41 | # register two callbacks 42 | hm.MouseAllButtonsDown = OnMouseEvent 43 | hm.KeyDown = OnKeyboardEvent 44 | 45 | # hook into the mouse and keyboard events 46 | hm.HookMouse() 47 | hm.HookKeyboard() 48 | 49 | if __name__ == '__main__': 50 | import pythoncom 51 | pythoncom.PumpMessages() 52 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | '''A pyHook module fork from Peter P. with some updates. 2 | 3 | Python wrapper for out-of-context input hooks in Windows. 4 | The pyWinhook package provides callbacks for global mouse and keyboard events in Windows. Python 5 | applications register event handlers for user input events such as left mouse down, left mouse up, 6 | key down, etc. and set the keyboard and/or mouse hook. The underlying C library reports information 7 | like the time of the event, the name of the window in which the event occurred, the value of the 8 | event, any keyboard modifiers, etc. 9 | pyHook, original project download url: http://www.sourceforge.net/projects/pyhook 10 | ''' 11 | 12 | classifiers = """\ 13 | Development Status :: 5 - Production/Stable 14 | Intended Audience :: Developers 15 | License :: OSI Approved :: MIT License 16 | Programming Language :: Python 17 | Topic :: System :: Monitoring 18 | Topic :: Software Development :: Libraries :: Python Modules 19 | Operating System :: Microsoft :: Windows 20 | """ 21 | __version__='1.6.2' 22 | from setuptools import setup, Extension 23 | 24 | libs = ['user32'] 25 | doclines = __doc__.split('\n') 26 | 27 | setup(name='pyWinhook', 28 | version=__version__, 29 | author='Tungsteno', 30 | author_email='contacts00-pywinhook@yahoo.it', 31 | url='https://github.com/Tungsteno74/pyWinhook', 32 | download_url=''.join(('https://codeload.github.com/Tungsteno74/pyWinhook/zip/',__version__)), 33 | license='http://www.opensource.org/licenses/mit-license.php', 34 | platforms=['Win32', 'Win-amd64', 'Win-ia64'], 35 | description = doclines[0], 36 | classifiers = list(filter(None, classifiers.split('\n'))), 37 | long_description = ' '.join(doclines[2:]), 38 | packages = ['pyWinhook'], 39 | install_requires = ['pywin32'], 40 | ext_modules = [Extension('pyWinhook._cpyHook', ['pyWinhook/cpyHook.i'], libraries=libs)], 41 | data_files = [('Lib/site-packages/pyWinhook', ['pyWinhook/cpyHook.py', 'LICENSE.txt', 'README.txt', 'CHANGELOG.txt'])], 42 | keywords = 'hook win32 win64 keyboard input user control' 43 | ) 44 | --------------------------------------------------------------------------------