python 创建鼠标钩子和键盘钩子的测试代码
基于python 3.10.8 x64
使用ctypes 调用win32库实现
import win32api
import win32con
import win32process
import time
import ctypes
import io
import os
import threading
import queue
import tkinter as tk
import tkinter.ttk as ttk
from ctypes import wintypes, CFUNCTYPE, WINFUNCTYPE
from win32con import WH_KEYBOARD, WH_KEYBOARD_LL, WH_MOUSE, WH_MOUSE_LL, HC_ACTION
from win32con import WM_KEYDOWN, WM_KEYUP
from win32con import KF_EXTENDED, KF_ALTDOWN, KF_REPEAT, KF_UP
user32 = ctypes.windll.user32
kernel32 = ctypes.windll.kernel32
kernel32.SetConsoleMode(kernel32.GetStdHandle(-10), (0x4 | 0x80 | 0x20 | 0x2 | 0x10 | 0x1 | 0x00 | 0x100))
CallNextHookEx = user32.CallNextHookEx
UnhookWindowsHookEx = user32.UnhookWindowsHookEx
SetWindowsHookEx = user32.SetWindowsHookExW
LRESULT = wintypes.LPARAM
# HINSTANCE = wintypes.WPARAM
# HHOOK = wintypes.WPARAM
Hookproc_ptr = WINFUNCTYPE(LRESULT, wintypes.INT, wintypes.WPARAM, wintypes.LPARAM)
SetWindowsHookEx.argtypes = (wintypes.INT, Hookproc_ptr, wintypes.HINSTANCE, wintypes.DWORD)
SetWindowsHookEx.restype = wintypes.HHOOK
CallNextHookEx.argtypes = (wintypes.HHOOK, wintypes.INT, wintypes.WPARAM, wintypes.LPARAM)
CallNextHookEx.restype = LRESULT
class Structure_walk:
'''遍历结构体'''
def __init__(self, struct_obj):
self.struct_obj = struct_obj
self.stack = []
self.dump = {}
def walk_struct(self, struct_obj):
for field_name, field_type in struct_obj._fields_:
field_value = getattr(struct_obj, field_name)
self.stack.append(field_name)
if isinstance(field_value, (ctypes.Structure, ctypes.Union)):
self.walk_struct(field_value)
else:
self.dump['.'.join(self.stack)] = field_value
self.stack.pop()
def __str__(self):
self.stack.clear()
self.dump.clear()
self.walk_struct(self.struct_obj)
return str(self.dump)
class Structure_tool:
def __str__(self) -> str:
return str(Structure_walk(self))
__repr__ = __str__
class MOUSEHOOKSTRUCT(ctypes.Structure, Structure_tool):
_fields_ = [("pt", wintypes.POINT),
("hwnd", wintypes.HWND),
("wHitTestCode", wintypes.UINT),
("dwExtraInfo", wintypes.DWORD),
]
class MSLLHOOKSTRUCT(ctypes.Structure, Structure_tool):
_fields_ = [("pt", wintypes.POINT),
("mouseData", wintypes.DWORD),
("flags", wintypes.DWORD),
("time", wintypes.DWORD),
("dwExtraInfo", wintypes.DWORD),
]
class KBDLLHOOKSTRUCT(ctypes.Structure, Structure_tool):
_fields_ = [("vkCode", wintypes.DWORD),
("scanCode", wintypes.DWORD),
("flags", wintypes.DWORD),
("time", wintypes.DWORD),
("dwExtraInfo", wintypes.DWORD),
]
pidmsg = io.StringIO()
print("当前进程ID: ", os.getpid(), f'{os.getpid():08X}',file=pidmsg)
print("当前线程ID: ", threading.get_ident(), f'{threading.get_ident():08X}',file=pidmsg)
print(pidmsg.getvalue())
g_log = queue.Queue(1024)
def show_log():
while True:
msg = g_log.get()
print(msg)
td = threading.Thread(target=show_log, args=(), kwargs=None, daemon=True)
td.start()
class 钩子测试App:
def __init__(self, master=None):
# build ui
self.toplevel1 = tk.Tk() if master is None else tk.Toplevel(master)
self.toplevel1.configure(height=200, width=200)
self.toplevel1.geometry("320x200")
self.button1 = ttk.Button(self.toplevel1)
self.button1.configure(text='button1')
self.button1.pack(side="top")
self.button1.bind("<Button>", self.callback1, add="")
self.label1 = ttk.Label(self.toplevel1)
self.message1 = tk.StringVar(value='label1')
self.label1.configure(text='label1', textvariable=self.message1)
self.label1.pack(side="top")
# Main widget
self.mainwindow = self.toplevel1
def run(self):
self._hookID = 0
self.message1.set(pidmsg.getvalue())
self.mainwindow.mainloop()
def callback1(self, event=None):
if self._hookID == 0:
chwnd = self.mainwindow.winfo_id()
threadId, processId = win32process.GetWindowThreadProcessId(chwnd)
self.KeyboardProc = Hookproc_ptr(self.PyKeyboardProc)
self.MouseProc = Hookproc_ptr(self.PyMouseProc)
mode = 2
if mode == 1:
self.idHook = WH_KEYBOARD
self._hookID = SetWindowsHookEx(self.idHook, self.KeyboardProc, 0, threadId)
elif mode == 2:
self.idHook = WH_KEYBOARD_LL
self._hookID = SetWindowsHookEx(self.idHook, self.KeyboardProc, 0, 0)
elif mode == 3:
self.idHook = WH_MOUSE
self._hookID = SetWindowsHookEx(self.idHook, self.MouseProc, 0, threadId)
elif mode == 4:
self.idHook = WH_MOUSE_LL
self._hookID = SetWindowsHookEx(self.idHook, self.MouseProc, 0, 0)
print(self._hookID)
if isinstance(self._hookID, int) and self._hookID != 0:
print('挂钩成功')
pidmsg.write(f'\n挂钩ID: {self._hookID} {self._hookID:08X}')
self.message1.set(pidmsg.getvalue())
else:
print('挂钩失败')
self._hookID = 0
def PyMouseProc(self, nCode: int, wParam: int, lParam: int):
if nCode == HC_ACTION:
if self.idHook == WH_MOUSE:
MS_Data = MOUSEHOOKSTRUCT.from_address(lParam)
g_log.put((nCode, wParam, MS_Data))
elif self.idHook == WH_MOUSE_LL:
MS_Data = MSLLHOOKSTRUCT.from_address(lParam)
g_log.put((nCode, wParam, MS_Data))
return CallNextHookEx(self._hookID, nCode, wParam, lParam)
def PyKeyboardProc(self, nCode: int, wParam: int, lParam: int):
if nCode == HC_ACTION:
if self.idHook == WH_KEYBOARD:
keyFlags = (lParam & 0xFFFF_0000) >> 16
KB_Data = {
'vkCode': wParam,
'lParam': f"{lParam:08X}",
'repeatCount': lParam & 0x0000FFFF,
# 0-15 重复计数。 该值是用户按住键后重复击键的次数。
'ScanCode': (lParam & 0x00FF0000) >> 16,
# 16-23 扫描代码。 此值取决于 OEM。
# 'keyFlags': (lParam & 0xFFFF_0000) >> 16,
'isExtendedKey': (keyFlags & KF_EXTENDED) == KF_EXTENDED,
# 24 指示键是扩展键,例如功能键还是数字键盘上的键。 如果键是扩展键,则值为 1;否则为 0。
'isAltDown': (keyFlags & KF_ALTDOWN) == KF_ALTDOWN,
# 29 上下文代码。 如果 Alt 键关闭,则值为 1;否则为 0。
'isRepeat': (keyFlags & KF_REPEAT) == KF_REPEAT,
# 30 上一个键状态。 如果在发送消息之前键关闭,则值为 1;如果键已打开,则为 0。
'isKeyUp': (keyFlags & KF_UP) == KF_UP,
# 31 转换状态。 如果按下键,则值为 0;如果释放键,则值为 1。
}
g_log.put((nCode, wParam, KB_Data))
elif self.idHook == WH_KEYBOARD_LL:
KB_Data = KBDLLHOOKSTRUCT.from_address(lParam)
g_log.put((nCode, wParam, KB_Data))
return CallNextHookEx(self._hookID, nCode, wParam, lParam)
def __del__(self):
if self._hookID > 0:
UnhookWindowsHookEx(self._hookID)
if __name__ == "__main__":
app = 钩子测试App()
app.run()