Python实现鼠标和键盘Hook


写在前面

有时需要实时监控下一次鼠标点击的动作,所以就考虑通过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))