写在前面
有时需要实时监控下一次鼠标点击的动作,所以就考虑通过Hook来实现,主要原理是Windows通过消息机制来运作,在消息传递过程加上一层自己的操作,就实现了动作监听,python有现成的轮子,PyHook3,可以很方便的使用。
PyHook3
注:键盘Hook请自行研究
import pythoncom
import PyHook3 as pyHook
import ctypes
def onMouseEvent(event):
# 监听鼠标事件
print(dir(event))
if not event.WindowName:
return False
print("MessageName:", event.MessageName)
print("Message:", event.Message)
print("Time:", event.Time)
print("Window:", event.Window)
print("WindowName:", event.WindowName)
print("Position:", event.Position)
print("Wheel:", event.Wheel)
print("Injected:", event.Injected)
print("---")
hm.__del__()
ctypes.windll.user32.PostQuitMessage(0)
# 返回False将丢弃该事件,返回Ture将事件传递到后续队列
return False
def start():
hm.MouseLeftDown = onMouseEvent
hm.HookMouse()
pythoncom.PumpMessages()
start()
API实现
import ctypes
import copy
WH_MOUSE_LL = 14
# 自定义消息类型要大于WM_USER
WM_USER = 0x400
WM_LBUTTONDOWN = 0x201
WM_MOUSEMSG = WM_USER + 102
WH_KEYBOARD_LL = 13
WM_KEYDOWN = 0x0100
VK_F2 = 113
CallNext = False
# 用于包装回调函数,具体见ctypes文档中的说明
cmp_func = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int, ctypes.wintypes.HINSTANCE, ctypes.POINTER(ctypes.c_void_p))
# redefine names to avoid needless clutter
GetModuleHandleA = ctypes.windll.kernel32.GetModuleHandleA
SetWindowsHookExA = ctypes.windll.user32.SetWindowsHookExA
GetMessageW = ctypes.windll.user32.GetMessageW
DispatchMessageW = ctypes.windll.user32.DispatchMessageW
TranslateMessage = ctypes.windll.user32.TranslateMessage
CallNextHookEx = ctypes.windll.user32.CallNextHookEx
UnhookWindowsHookEx = ctypes.windll.user32.UnhookWindowsHookEx
PostMessageW = ctypes.windll.user32.PostMessageW
# specify the argument and return types of functions
GetModuleHandleA.restype = ctypes.wintypes.HMODULE
GetModuleHandleA.argtypes = [ctypes.wintypes.LPCWSTR]
SetWindowsHookExA.restype = ctypes.c_int
SetWindowsHookExA.argtypes = [ctypes.c_int, cmp_func, ctypes.wintypes.HINSTANCE, ctypes.wintypes.DWORD]
GetMessageW.argtypes = [ctypes.POINTER(ctypes.wintypes.MSG), ctypes.wintypes.HWND, ctypes.c_uint, ctypes.c_uint]
TranslateMessage.argtypes = [ctypes.POINTER(ctypes.wintypes.MSG)]
DispatchMessageW.argtypes = [ctypes.POINTER(ctypes.wintypes.MSG)]
# Structure used by WH_MOUSE_LL
class PMSLLHOOKSTRUCT(ctypes.Structure):
_fields_ = [('pt',ctypes.wintypes.POINT),
('mouseData',ctypes.wintypes.DWORD),
('flags',ctypes.wintypes.DWORD),
('time',ctypes.wintypes.DWORD),
('dwExtraInfo',ctypes.c_ulonglong)]
# Structure used by WH_KEYBOARD_LL
class PKBDLLHOOKSTRUCT(ctypes.Structure):
_fields_ = [('vkCode',ctypes.wintypes.DWORD),
('scanCode',ctypes.wintypes.DWORD),
('flags',ctypes.wintypes.DWORD),
('time',ctypes.wintypes.DWORD),
('dwExtraInfo',ctypes.c_ulonglong)]
PostMessageW.argtypes = [ctypes.wintypes.HWND,ctypes.c_uint,ctypes.wintypes.HINSTANCE, ctypes.POINTER(PMSLLHOOKSTRUCT)]
class MouseLogger(object):
def __init__(self):
self.hooked = None
# 安装
def installHookProc(self, pointer):
if self.hooked:
return True
self.hooked = SetWindowsHookExA(
WH_MOUSE_LL,
pointer,
GetModuleHandleA(None),
0)
if not self.hooked:
return False
return True
# 卸载
def uninstallHookProc(self):
if self.hooked is None:
return
UnhookWindowsHookEx(self.hooked)
self.hooked = None
class KeyLogger:
def __init__(self):
self.hooked = None
# 安装
def installHookProc(self, pointer):
if self.hooked:
return True
self.hooked = SetWindowsHookExA(
WH_KEYBOARD_LL,
pointer,
GetModuleHandleA(None),
0)
if not self.hooked:
return False
return True
# 卸载
def uninstallHookProc(self):
if self.hooked is None:
return
UnhookWindowsHookEx(self.hooked)
self.hooked = None
mouselogger = MouseLogger()
keyboardlogger = KeyLogger()
def hookMouseProc(nCode, wParam, lParam):
global mouseHookStruct_,CallNext
event_type = 0xFFFFFFFF & wParam
if event_type == WM_LBUTTONDOWN and not CallNext:
# 强制类型转换,以PMSLLHOOKSTRUCT类型解引用lParam指向的内存区域
pmouseHookStruct = ctypes.cast(lParam,ctypes.POINTER(PMSLLHOOKSTRUCT))
# 此过程执行完毕后lParam指向的内存区域会变,在此之前要创建全局内存拷贝数据
mouseHookStruct_ = copy.deepcopy(pmouseHookStruct.contents)
PostMessageW(ctypes.wintypes.HWND(0),ctypes.c_uint(WM_MOUSEMSG),wParam,ctypes.pointer(mouseHookStruct_))
mouselogger.uninstallHookProc()
ctypes.windll.user32.PostQuitMessage(0)
return True
return CallNextHookEx(mouselogger.hooked, nCode, wParam, lParam)
def hookKeyboardProc(nCode,wParam,lParam):
global CallNext
event_type = 0xFFFFFFFF & wParam
if event_type == WM_KEYDOWN:
pkeyboardHookStruct = ctypes.cast(lParam,ctypes.POINTER(PKBDLLHOOKSTRUCT))
keyboardHookStruct = pkeyboardHookStruct.contents
if VK_F2 == keyboardHookStruct.vkCode:
CallNext = not CallNext
print('监听{}'.format('开始' if not CallNext else '暂停'))
return True
return CallNextHookEx(keyboardlogger.hooked, nCode, wParam, lParam)
def startHook():
message = ctypes.wintypes.MSG()
pointer_mouse = cmp_func(hookMouseProc)
pointer_keyboard = cmp_func(hookKeyboardProc)
mouselogger.installHookProc(pointer_mouse)
keyboardlogger.installHookProc(pointer_keyboard)
while True:
msg = GetMessageW(ctypes.byref(message), 0, 0, 0)
if msg in [0,-1]:
mouselogger.uninstallHookProc()
keyboardlogger.uninstallHookProc()
break
if message.message == WM_MOUSEMSG:
pmouseHookStruct = ctypes.cast(message.lParam,ctypes.POINTER(PMSLLHOOKSTRUCT))
mouseHookStruct = pmouseHookStruct.contents
# write your code here
TranslateMessage(ctypes.byref(message))
DispatchMessageW(ctypes.byref(message))