You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
624 lines
24 KiB
624 lines
24 KiB
# GUI Application automation and testing library
|
|
# Copyright (C) 2006-2018 Mark Mc Mahon and Contributors
|
|
# https://github.com/pywinauto/pywinauto/graphs/contributors
|
|
# http://pywinauto.readthedocs.io/en/latest/credits.html
|
|
# All rights reserved.
|
|
#
|
|
# Redistribution and use in source and binary forms, with or without
|
|
# modification, are permitted provided that the following conditions are met:
|
|
#
|
|
# * Redistributions of source code must retain the above copyright notice, this
|
|
# list of conditions and the following disclaimer.
|
|
#
|
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
|
# this list of conditions and the following disclaimer in the documentation
|
|
# and/or other materials provided with the distribution.
|
|
#
|
|
# * Neither the name of pywinauto nor the names of its
|
|
# contributors may be used to endorse or promote products derived from
|
|
# this software without specific prior written permission.
|
|
#
|
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
"""
|
|
Windows global hooks in pure Python
|
|
|
|
The implementation uses foreign function interface (FFI) provided by
|
|
standard Python module **ctypes** and inspired by pyHook, pyhooked and other
|
|
similar modules (the code was re-written from scratch). It tends to be
|
|
a superset of pyHook but in pure Python only so it doesn't require compilation.
|
|
|
|
Current set of hooks implemented:
|
|
* WH_MOUSE_LL
|
|
* WH_KEYBOARD_LL
|
|
|
|
More detailed documentation about Windows hooks can be found in MSDN:
|
|
https://msdn.microsoft.com/en-us/library/windows/desktop/ms632589.aspx
|
|
|
|
This module can be used as a stand alone or along with pywinauto.
|
|
The fork of this code (at some moment) was used in
|
|
standalone library pyhooked 0.8 maintained by Ethan Smith.
|
|
"""
|
|
|
|
import six
|
|
from ctypes import wintypes
|
|
from ctypes import windll
|
|
from ctypes import CFUNCTYPE
|
|
from ctypes import POINTER
|
|
from ctypes import c_int
|
|
from ctypes import c_uint
|
|
from ctypes import byref
|
|
from ctypes import pointer
|
|
import atexit
|
|
import sys
|
|
import time
|
|
import win32con
|
|
from .win32defines import VK_PACKET
|
|
from .actionlogger import ActionLogger
|
|
from .win32structures import KBDLLHOOKSTRUCT
|
|
from .win32structures import MSLLHOOKSTRUCT
|
|
|
|
LRESULT = wintypes.LPARAM
|
|
HOOKCB = CFUNCTYPE(LRESULT, c_int, wintypes.WPARAM, wintypes.LPARAM)
|
|
|
|
windll.kernel32.GetModuleHandleA.restype = wintypes.HMODULE
|
|
windll.kernel32.GetModuleHandleA.argtypes = [wintypes.LPCWSTR]
|
|
windll.user32.SetWindowsHookExA.restype = c_int
|
|
windll.user32.SetWindowsHookExA.argtypes = [c_int, HOOKCB, wintypes.HINSTANCE, wintypes.DWORD]
|
|
windll.user32.GetMessageW.argtypes = [POINTER(wintypes.MSG), wintypes.HWND, c_uint, c_uint]
|
|
windll.user32.TranslateMessage.argtypes = [POINTER(wintypes.MSG)]
|
|
windll.user32.DispatchMessageW.argtypes = [POINTER(wintypes.MSG)]
|
|
|
|
# BOOL WINAPI PeekMessage(
|
|
# _Out_ LPMSG lpMsg,
|
|
# _In_opt_ HWND hWnd,
|
|
# _In_ UINT wMsgFilterMin,
|
|
# _In_ UINT wMsgFilterMax,
|
|
# _In_ UINT wRemoveMsg
|
|
#);
|
|
windll.user32.PeekMessageW.argtypes = [POINTER(wintypes.MSG), wintypes.HWND, c_uint, c_uint, c_uint]
|
|
windll.user32.PeekMessageW.restypes = wintypes.BOOL
|
|
|
|
# LRESULT WINAPI CallNextHookEx(
|
|
# _In_opt_ HHOOK hhk,
|
|
# _In_ int nCode,
|
|
# _In_ WPARAM wParam,
|
|
# _In_ LPARAM lParam
|
|
# );
|
|
windll.user32.CallNextHookEx.argtypes = [wintypes.HHOOK, c_int, wintypes.WPARAM, wintypes.LPARAM]
|
|
windll.user32.CallNextHookEx.restypes = LRESULT
|
|
|
|
|
|
class KeyboardEvent(object):
|
|
|
|
"""Created when a keyboard event happened"""
|
|
|
|
def __init__(self, current_key=None, event_type=None, pressed_key=None):
|
|
self.current_key = current_key
|
|
self.event_type = event_type
|
|
self.pressed_key = pressed_key
|
|
|
|
|
|
class MouseEvent(object):
|
|
|
|
"""Created when a mouse event happened"""
|
|
|
|
def __init__(self, current_key=None, event_type=None, mouse_x=0, mouse_y=0):
|
|
self.current_key = current_key
|
|
self.event_type = event_type
|
|
self.mouse_x = mouse_x
|
|
self.mouse_y = mouse_y
|
|
|
|
|
|
class Hook(object):
|
|
|
|
"""Hook for low level keyboard and mouse events"""
|
|
|
|
MOUSE_ID_TO_KEY = {win32con.WM_MOUSEMOVE: 'Move',
|
|
win32con.WM_LBUTTONDOWN: 'LButton',
|
|
win32con.WM_LBUTTONUP: 'LButton',
|
|
win32con.WM_RBUTTONDOWN: 'RButton',
|
|
win32con.WM_RBUTTONUP: 'RButton',
|
|
win32con.WM_MBUTTONDOWN: 'WheelButton',
|
|
win32con.WM_MBUTTONUP: 'WheelButton',
|
|
win32con.WM_MOUSEWHEEL: 'Wheel'}
|
|
|
|
MOUSE_ID_TO_EVENT_TYPE = {win32con.WM_MOUSEMOVE: None,
|
|
win32con.WM_LBUTTONDOWN: 'key down',
|
|
win32con.WM_LBUTTONUP: 'key up',
|
|
win32con.WM_RBUTTONDOWN: 'key down',
|
|
win32con.WM_RBUTTONUP: 'key up',
|
|
win32con.WM_MBUTTONDOWN: 'key down',
|
|
win32con.WM_MBUTTONUP: 'key up',
|
|
win32con.WM_MOUSEWHEEL: None}
|
|
|
|
# TODO: use constants from win32con: VK_BACK, VK_TAB, VK_RETURN ...
|
|
ID_TO_KEY = {1: 'LButton', # win32con.VK_LBUTTON
|
|
2: 'RButton', # win32con.VK_RBUTTON
|
|
3: 'Cancel', # win32con.VK_CANCEL
|
|
4: 'MButton', # win32con.VK_MBUTTON
|
|
5: 'XButton1', # win32con.VK_XBUTTON1
|
|
6: 'XButton2', # win32con.VK_XBUTTON2
|
|
7: 'Undefined1',
|
|
8: 'Back',
|
|
9: 'Tab',
|
|
10: 'Reserved1',
|
|
11: 'Reserved2',
|
|
12: 'Clear', # win32con.VK_CLEAR
|
|
13: 'Return', # win32con.VK_RETURN
|
|
14: 'Undefined2',
|
|
15: 'Undefined3',
|
|
16: 'SHIFT', # win32con.VK_SHIFT
|
|
17: 'CONTROL', # win32con.VK_CONTROL
|
|
18: 'Menu', # win32con.VK_MENU
|
|
19: 'Pause', # win32con.VK_PAUSE
|
|
20: 'Capital',
|
|
21: 'Kana', # win32con.VK_KANA and win32con.VK_HANGUL
|
|
22: 'Undefined4',
|
|
23: 'Junja', # win32con.VK_JUNJA
|
|
24: 'Final', # win32con.VK_FINAL
|
|
25: 'Kanji', # win32con.VK_KANJI and win32con.VK_HANJA
|
|
26: 'Undefined5',
|
|
27: 'Escape',
|
|
28: 'Convert', # win32con.VK_CONVERT
|
|
29: 'NonConvert', # win32con.VK_NONCONVERT
|
|
30: 'Accept', # win32con.VK_ACCEPT
|
|
31: 'ModeChange', # win32con.VK_MODECHANGE
|
|
32: 'Space',
|
|
33: 'Prior',
|
|
34: 'Next',
|
|
35: 'End',
|
|
36: 'Home',
|
|
37: 'Left',
|
|
38: 'Up',
|
|
39: 'Right',
|
|
40: 'Down',
|
|
41: 'Select', # win32con.VK_SELECT
|
|
42: 'Print', # win32con.VK_PRINT
|
|
43: 'Execute', # win32con.VK_EXECUTE
|
|
44: 'Snapshot',
|
|
45: 'Insert', # win32con.VK_INSERT
|
|
46: 'Delete',
|
|
47: 'Help', # win32con.VK_HELP
|
|
48: '0',
|
|
49: '1',
|
|
50: '2',
|
|
51: '3',
|
|
52: '4',
|
|
53: '5',
|
|
54: '6',
|
|
55: '7',
|
|
56: '8',
|
|
57: '9',
|
|
58: 'Undefined6',
|
|
59: 'Undefined7',
|
|
60: 'Undefined8',
|
|
61: 'Undefined9',
|
|
62: 'Undefined10',
|
|
63: 'Undefined11',
|
|
64: 'Undefined12',
|
|
65: 'A',
|
|
66: 'B',
|
|
67: 'C',
|
|
68: 'D',
|
|
69: 'E',
|
|
70: 'F',
|
|
71: 'G',
|
|
72: 'H',
|
|
73: 'I',
|
|
74: 'J',
|
|
75: 'K',
|
|
76: 'L',
|
|
77: 'M',
|
|
78: 'N',
|
|
79: 'O',
|
|
80: 'P',
|
|
81: 'Q',
|
|
82: 'R',
|
|
83: 'S',
|
|
84: 'T',
|
|
85: 'U',
|
|
86: 'V',
|
|
87: 'W',
|
|
88: 'X',
|
|
89: 'Y',
|
|
90: 'Z',
|
|
91: 'Lwin',
|
|
92: 'Rwin',
|
|
93: 'App',
|
|
94: 'Reserved3',
|
|
95: 'Sleep',
|
|
96: 'Numpad0',
|
|
97: 'Numpad1',
|
|
98: 'Numpad2',
|
|
99: 'Numpad3',
|
|
100: 'Numpad4',
|
|
101: 'Numpad5',
|
|
102: 'Numpad6',
|
|
103: 'Numpad7',
|
|
104: 'Numpad8',
|
|
105: 'Numpad9',
|
|
106: 'Multiply',
|
|
107: 'Add',
|
|
108: 'Separator', # win32con.VK_SEPARATOR
|
|
109: 'Subtract',
|
|
110: 'Decimal',
|
|
111: 'Divide',
|
|
112: 'F1',
|
|
113: 'F2',
|
|
114: 'F3',
|
|
115: 'F4',
|
|
116: 'F5',
|
|
117: 'F6',
|
|
118: 'F7',
|
|
119: 'F8',
|
|
120: 'F9',
|
|
121: 'F10',
|
|
122: 'F11',
|
|
123: 'F12',
|
|
124: 'F13',
|
|
125: 'F14',
|
|
126: 'F15',
|
|
127: 'F16',
|
|
128: 'F17',
|
|
129: 'F18',
|
|
130: 'F19',
|
|
131: 'F20',
|
|
132: 'F21',
|
|
133: 'F22',
|
|
134: 'F23',
|
|
135: 'F24',
|
|
136: 'Unassigned1',
|
|
137: 'Unassigned2',
|
|
138: 'Unassigned3',
|
|
139: 'Unassigned4',
|
|
140: 'Unassigned5',
|
|
141: 'Unassigned6',
|
|
142: 'Unassigned7',
|
|
143: 'Unassigned8',
|
|
144: 'Numlock',
|
|
145: 'Scroll', # win32con.VK_SCROLL
|
|
146: 'OemSpecific1',
|
|
147: 'OemSpecific2',
|
|
148: 'OemSpecific3',
|
|
149: 'OemSpecific4',
|
|
150: 'OemSpecific5',
|
|
151: 'OemSpecific6',
|
|
152: 'OemSpecific7',
|
|
153: 'OemSpecific8',
|
|
154: 'OemSpecific9',
|
|
155: 'OemSpecific10',
|
|
156: 'OemSpecific11',
|
|
157: 'OemSpecific12',
|
|
158: 'OemSpecific13',
|
|
159: 'OemSpecific14',
|
|
160: 'Lshift',
|
|
161: 'Rshift',
|
|
162: 'Lcontrol',
|
|
163: 'Rcontrol',
|
|
164: 'Lmenu',
|
|
165: 'Rmenu',
|
|
166: 'BrowserBack', # win32con.VK_BROWSER_BACK
|
|
167: 'BrowserForward', # win32con.VK_BROWSER_FORWARD
|
|
168: 'BrowserRefresh', # not defined in win32con
|
|
169: 'BrowserStop', # not defined in win32con
|
|
170: 'BrowserSearch', # not defined in win32con
|
|
171: 'BrowserFavourites', # not defined in win32con
|
|
172: 'BrowserHome', # not defined in win32con
|
|
173: 'Volume_mute', # win32con.VK_VOLUME_MUTE
|
|
174: 'Volume_down', # win32con.VK_VOLUME_DOWN
|
|
175: 'Volume_up', # win32con.VK_VOLUME_UP
|
|
176: 'NextTrack', # win32con.VK_MEDIA_NEXT_TRACK
|
|
177: 'PrevTrack', # win32con.VK_MEDIA_PREV_TRACK
|
|
178: 'StopTrack', # not defined in win32con
|
|
179: 'PlayPause', # win32con.VK_MEDIA_PLAY_PAUSE
|
|
180: 'LaunchMail', # not defined in win32con
|
|
181: 'MediaSelect', # not defined in win32con
|
|
182: 'LaunchApp1', # not defined in win32con
|
|
183: 'LaunchApp2', # not defined in win32con
|
|
184: 'Reserved4',
|
|
185: 'Reserved5',
|
|
186: 'Oem_1',
|
|
187: 'Oem_Plus',
|
|
188: 'Oem_Comma',
|
|
189: 'Oem_Minus',
|
|
190: 'Oem_Period',
|
|
191: 'Oem_2',
|
|
192: 'Oem_3',
|
|
193: 'Reserved6',
|
|
194: 'Reserved7',
|
|
195: 'Reserved8',
|
|
196: 'Reserved9',
|
|
197: 'Reserved10',
|
|
198: 'Reserved11',
|
|
199: 'Reserved12',
|
|
200: 'Reserved13',
|
|
201: 'Reserved14',
|
|
202: 'Reserved15',
|
|
203: 'Reserved16',
|
|
204: 'Reserved17',
|
|
205: 'Reserved18',
|
|
206: 'Reserved19',
|
|
207: 'Reserved20',
|
|
208: 'Reserved21',
|
|
209: 'Reserved22',
|
|
210: 'Reserved23',
|
|
211: 'Reserved24',
|
|
212: 'Reserved25',
|
|
213: 'Reserved26',
|
|
214: 'Reserved27',
|
|
215: 'Reserved28',
|
|
216: 'Unassigned9',
|
|
217: 'Unassigned10',
|
|
218: 'Unassigned11',
|
|
219: 'Oem_4',
|
|
220: 'Oem_5',
|
|
221: 'Oem_6',
|
|
222: 'Oem_7',
|
|
223: 'Oem_8', # not defined in win32cona
|
|
224: 'Reserved29',
|
|
225: 'OemSpecific15',
|
|
226: 'Oem_102',
|
|
227: 'OemSpecific16',
|
|
228: 'OemSpecific17',
|
|
229: 'ProcessKey', # win32con.VK_PROCESSKEY
|
|
230: 'OemSpecific18',
|
|
231: 'VkPacket', # win32con.VK_PACKET. It has a special processing in kbd_ll !
|
|
232: 'Unassigned12',
|
|
233: 'OemSpecific19',
|
|
234: 'OemSpecific20',
|
|
235: 'OemSpecific21',
|
|
236: 'OemSpecific22',
|
|
237: 'OemSpecific23',
|
|
238: 'OemSpecific24',
|
|
239: 'OemSpecific25',
|
|
240: 'OemSpecific26',
|
|
241: 'OemSpecific27',
|
|
242: 'OemSpecific28',
|
|
243: 'OemSpecific29',
|
|
244: 'OemSpecific30',
|
|
245: 'OemSpecific31',
|
|
246: 'Attn', # win32con.VK_ATTN
|
|
247: 'CrSel', # win32con.VK_CRSEL
|
|
248: 'ExSel', # win32con.VK_EXSEL
|
|
249: 'ErEOF', # win32con.VK_EREOF
|
|
250: 'Play', # win32con.VK_PLAY
|
|
251: 'Zoom', # win32con.VK_ZOOM
|
|
252: 'Noname', # win32con.VK_NONAME
|
|
253: 'PA1', # win32con.VK_PA1
|
|
254: 'OemClear', # win32con.VK_OEM_CLEAR
|
|
1001: 'mouse left', # mouse hotkeys
|
|
1002: 'mouse right',
|
|
1003: 'mouse middle',
|
|
1000: 'mouse move', # single event hotkeys
|
|
1004: 'mouse wheel up',
|
|
1005: 'mouse wheel down',
|
|
1010: 'Ctrl', # merged hotkeys
|
|
1011: 'Alt',
|
|
1012: 'Shift',
|
|
1013: 'Win'}
|
|
|
|
event_types = {win32con.WM_KEYDOWN: 'key down', # WM_KEYDOWN for normal keys
|
|
win32con.WM_KEYUP: 'key up', # WM_KEYUP for normal keys
|
|
win32con.WM_SYSKEYDOWN: 'key down', # WM_SYSKEYDOWN, is used for Alt key.
|
|
win32con.WM_SYSKEYUP: 'key up', # WM_SYSKEYUP, is used for Alt key.
|
|
}
|
|
|
|
def __init__(self):
|
|
self.handler = None
|
|
self.pressed_keys = []
|
|
self.keyboard_id = None
|
|
self.mouse_id = None
|
|
self.mouse_is_hook = False
|
|
self.keyboard_is_hook = False
|
|
|
|
def _process_kbd_data(self, kb_data_ptr):
|
|
"""Process KBDLLHOOKSTRUCT data received from low level keyboard hook calls"""
|
|
kbd = KBDLLHOOKSTRUCT.from_address(kb_data_ptr)
|
|
current_key = None
|
|
key_code = kbd.vkCode
|
|
if key_code == VK_PACKET:
|
|
scan_code = kbd.scanCode
|
|
current_key = six.unichr(scan_code)
|
|
elif key_code in self.ID_TO_KEY:
|
|
current_key = six.u(self.ID_TO_KEY[key_code])
|
|
else:
|
|
al = ActionLogger()
|
|
al.log("_process_kbd_data, bad key_code: {0}".format(key_code))
|
|
|
|
return current_key
|
|
|
|
def _process_kbd_msg_type(self, event_code, current_key):
|
|
"""Process event codes from low level keyboard hook calls"""
|
|
event_type = None
|
|
event_code_word = 0xFFFFFFFF & event_code
|
|
if event_code_word in self.event_types:
|
|
event_type = self.event_types[event_code_word]
|
|
else:
|
|
al = ActionLogger()
|
|
al.log("_process_kbd_msg_type, bad event_type: {0}".format(event_type))
|
|
|
|
if event_type == 'key down':
|
|
self.pressed_keys.append(current_key)
|
|
elif event_type == 'key up':
|
|
if current_key in self.pressed_keys:
|
|
self.pressed_keys.remove(current_key)
|
|
else:
|
|
al = ActionLogger()
|
|
al.log("_process_kbd_msg_type, can't remove a key: {0}".format(current_key))
|
|
|
|
return event_type
|
|
|
|
def _keyboard_ll_hdl(self, code, event_code, kb_data_ptr):
|
|
"""Execute when a keyboard low level event has been triggered"""
|
|
try:
|
|
# The next hook in chain must be always called
|
|
res = windll.user32.CallNextHookEx(self.keyboard_id,
|
|
code,
|
|
event_code,
|
|
kb_data_ptr)
|
|
if not self.handler:
|
|
return res
|
|
|
|
current_key = self._process_kbd_data(kb_data_ptr)
|
|
event_type = self._process_kbd_msg_type(event_code, current_key)
|
|
event = KeyboardEvent(current_key, event_type, self.pressed_keys)
|
|
self.handler(event)
|
|
|
|
except Exception:
|
|
al = ActionLogger()
|
|
al.log("_keyboard_ll_hdl, {0}".format(sys.exc_info()[0]))
|
|
al.log("_keyboard_ll_hdl, code {0}, event_code {1}".format(code, event_code))
|
|
raise
|
|
|
|
return res
|
|
|
|
def _mouse_ll_hdl(self, code, event_code, mouse_data_ptr):
|
|
"""Execute when a mouse low level event has been triggerred"""
|
|
try:
|
|
# The next hook in chain must be always called
|
|
res = windll.user32.CallNextHookEx(self.mouse_id, code, event_code, mouse_data_ptr)
|
|
if not self.handler:
|
|
return res
|
|
|
|
current_key = None
|
|
event_code_word = 0xFFFFFFFF & event_code
|
|
if event_code_word in self.MOUSE_ID_TO_KEY:
|
|
current_key = self.MOUSE_ID_TO_KEY[event_code_word]
|
|
|
|
event_type = None
|
|
if current_key != 'Move':
|
|
if event_code in self.MOUSE_ID_TO_EVENT_TYPE:
|
|
event_type = self.MOUSE_ID_TO_EVENT_TYPE[event_code]
|
|
|
|
# Get the mouse position: x and y
|
|
ms = MSLLHOOKSTRUCT.from_address(mouse_data_ptr)
|
|
event = MouseEvent(current_key, event_type, ms.pt.x, ms.pt.y)
|
|
self.handler(event)
|
|
|
|
except Exception:
|
|
al = ActionLogger()
|
|
al.log("_mouse_ll_hdl, {0}".format(sys.exc_info()[0]))
|
|
al.log("_mouse_ll_hdl, code {0}, event_code {1}".format(code, event_code))
|
|
raise
|
|
|
|
return res
|
|
|
|
def hook(self, keyboard=True, mouse=False):
|
|
"""Hook mouse and/or keyboard events"""
|
|
if not (mouse or keyboard):
|
|
return
|
|
|
|
self.mouse_is_hook = mouse
|
|
self.keyboard_is_hook = keyboard
|
|
|
|
if self.keyboard_is_hook:
|
|
@HOOKCB
|
|
def _kbd_ll_cb(ncode, wparam, lparam):
|
|
"""Forward the hook event to ourselves"""
|
|
return self._keyboard_ll_hdl(ncode, wparam, lparam)
|
|
|
|
self.keyboard_id = windll.user32.SetWindowsHookExW(
|
|
win32con.WH_KEYBOARD_LL,
|
|
_kbd_ll_cb,
|
|
windll.kernel32.GetModuleHandleA(None),
|
|
0)
|
|
|
|
if self.mouse_is_hook:
|
|
@HOOKCB
|
|
def _mouse_ll_cb(code, event_code, mouse_data_ptr):
|
|
"""Forward the hook event to ourselves"""
|
|
return self._mouse_ll_hdl(code, event_code, mouse_data_ptr)
|
|
|
|
self.mouse_id = windll.user32.SetWindowsHookExA(
|
|
win32con.WH_MOUSE_LL,
|
|
_mouse_ll_cb,
|
|
windll.kernel32.GetModuleHandleA(None),
|
|
0)
|
|
|
|
self.listen()
|
|
|
|
def unhook_mouse(self):
|
|
"""Unhook mouse events"""
|
|
if self.mouse_is_hook:
|
|
self.mouse_is_hook = False
|
|
windll.user32.UnhookWindowsHookEx(self.mouse_id)
|
|
|
|
def unhook_keyboard(self):
|
|
"""Unhook keyboard events"""
|
|
if self.keyboard_is_hook:
|
|
self.keyboard_is_hook = False
|
|
windll.user32.UnhookWindowsHookEx(self.keyboard_id)
|
|
|
|
def stop(self):
|
|
"""Stop the listening loop"""
|
|
self.unhook_keyboard()
|
|
self.unhook_mouse()
|
|
|
|
def is_hooked(self):
|
|
"""Verify if any of hooks are active"""
|
|
return self.mouse_is_hook or self.keyboard_is_hook
|
|
|
|
def _process_win_msgs(self):
|
|
"""Peek and process queued windows messages"""
|
|
message = wintypes.MSG()
|
|
while True:
|
|
res = windll.user32.PeekMessageW(pointer(message), 0, 0, 0, win32con.PM_REMOVE)
|
|
if not res:
|
|
break
|
|
if message.message == win32con.WM_QUIT:
|
|
self.stop()
|
|
sys.exit(0)
|
|
else:
|
|
windll.user32.TranslateMessage(byref(message))
|
|
windll.user32.DispatchMessageW(byref(message))
|
|
|
|
def listen(self):
|
|
"""Listen for events"""
|
|
atexit.register(windll.user32.UnhookWindowsHookEx, self.keyboard_id)
|
|
atexit.register(windll.user32.UnhookWindowsHookEx, self.mouse_id)
|
|
|
|
while self.is_hooked():
|
|
self._process_win_msgs()
|
|
time.sleep(0.02)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
def on_event(args):
|
|
"""Callback for keyboard and mouse events"""
|
|
if isinstance(args, KeyboardEvent):
|
|
if args.current_key == 'A' and args.event_type == 'key down' and 'Lcontrol' in args.pressed_key:
|
|
print("Ctrl + A was pressed")
|
|
|
|
if args.current_key == 'K' and args.event_type == 'key down':
|
|
print("K was pressed")
|
|
|
|
if args.current_key == 'M' and args.event_type == 'key down' and 'U' in args.pressed_key:
|
|
hk.unhook_mouse()
|
|
print("Unhook mouse")
|
|
|
|
if args.current_key == 'K' and args.event_type == 'key down' and 'U' in args.pressed_key:
|
|
hk.unhook_keyboard()
|
|
print("Unhook keyboard")
|
|
|
|
if isinstance(args, MouseEvent):
|
|
if args.current_key == 'RButton' and args.event_type == 'key down':
|
|
print("Right button pressed at ({0}, {1})".format(args.mouse_x, args.mouse_y))
|
|
|
|
if args.current_key == 'WheelButton' and args.event_type == 'key down':
|
|
print("Wheel button pressed")
|
|
|
|
hk = Hook()
|
|
hk.handler = on_event
|
|
hk.hook(keyboard=True, mouse=True)
|