这是一个python库,继承自keyboard,最近我重写了一遍。现在只支持windows。记录一下代码,以及工作心得。
基础部分,与windows api的交互。
导入部分:
from typing import Callable
from threading import Lock
import atexit
from functools import singledispatch
from ctypes import Structure, Union, WinDLL, sizeof, POINTER, CFUNCTYPE, create_unicode_buffer
from ctypes.wintypes import (LONG, DWORD, LPVOID, WORD, INT, MSG, HINSTANCE, HHOOK, WPARAM,
LPARAM, HMODULE, BYTE, UINT, HKL)
from ._std import singleton
唯一的dll导入
user32 = WinDLL('user32', use_last_error=True)
用于获取按键扫描码、虚拟键码、名称的api:
@singleton
class GetVKWinApi:
lock = Lock()
unicode_buffer = create_unicode_buffer(32)
name_buffer = create_unicode_buffer(32)
buffer_len = len(name_buffer)
# ---------------------------------------------------------
__MapVirtualKeyW__ = user32.MapVirtualKeyW
__MapVirtualKeyExW__ = user32.MapVirtualKeyExW
__ToUnicode__ = user32.ToUnicode
__GetKeyNameTextW__ = user32.GetKeyNameTextW
__GetKeyboardState__ = user32.GetKeyboardState
def to_unicode(self, vk: int, scan_code: int) -> int:
with self.lock:
keyboard_state = (BYTE * 256)()
res = self.__ToUnicode__(UINT(vk), UINT(scan_code), keyboard_state, self.unicode_buffer, self.buffer_len)
return res
def get_key_name(self, none_struct: int) -> int:
with self.lock:
res = self.__GetKeyNameTextW__(none_struct, self.name_buffer, self.buffer_len)
return res
def map_vk_w(self, vk_sc: int, map_type: int) -> int:
return self.__MapVirtualKeyW__(UINT(vk_sc), UINT(map_type))
def map_vk_ex_w(self, vk_sc: int, map_type: int, hkl: HKL = HKL(0)) -> int:
return self.__MapVirtualKeyExW__(UINT(vk_sc), UINT(map_type), hkl)
def traverse() -> list[tuple[int, int]]:
# map scan code to virtual key ex 3; map scan code to virtual key ex 1
# map virtual key to scan code ex 4; map virtual key to scan code ex 0
# map virtual key to char 2
api = GetVKWinApi()
all_scs1 = [(sc, api.map_vk_ex_w(sc, 3)) for sc in range(0x100)]
all_scs2 = [(sc, api.map_vk_ex_w(sc, 1)) for sc in range(0x100)]
all_vks1 = [(api.map_vk_ex_w(vk, 4), vk) for vk in range(0x100)]
all_vks2 = [(api.map_vk_ex_w(vk, 0), vk) for vk in range(0x100)]
return list(set(all_scs1 + all_scs2 + all_vks1 + all_vks2))
消息循环:
LP_MSG = POINTER(MSG)
class MsgWinApi:
__GetMessageW__ = user32.GetMessageW
__TranslateMessage__ = user32.TranslateMessage
__DispatchMessageW__ = user32.DispatchMessageW
def __init__(self):
self.msg = LP_MSG()
def get_msg(self) -> None:
return self.__GetMessageW__(self.msg, 0, 0, 0)
def translate_msg(self) -> None:
return self.__TranslateMessage__(self.msg)
def dispatch_msg(self) -> None:
return self.__DispatchMessageW__(self.msg)
底层钩子,消息回调函数:
class KbDllHookStruct(Structure):
"""
键盘钩子结构体
https://learn.microsoft.com/zh-cn/windows/win32/api/winuser/ns-winuser-kbdllhookstruct
"""
_fields_ = [("vk_code", DWORD),
("scan_code", DWORD),
("flags", DWORD),
("time", DWORD),
("dwExtraInfo", LPVOID)]
HOOK_PROC = CFUNCTYPE(INT, WPARAM, LPARAM, POINTER(KbDllHookStruct))
class HookWinApi:
__SetWindowsHookExW__ = user32.SetWindowsHookExW
__UnhookWindowsHookEx__ = user32.UnhookWindowsHookEx
# ---------------------------------------------------------
# 函数签名
__SetWindowsHookExW__.argtypes = [INT, HOOK_PROC, HINSTANCE, DWORD] # 函数签名
__SetWindowsHookExW__.restype = HHOOK # 返回值类型
__UnhookWindowsHookEx__.argtypes = [HHOOK] # 函数签名
__UnhookWindowsHookEx__.restype = INT # 返回值类型
def __init__(self):
get_handler = WinDLL('kernel32', use_last_error=True).GetModuleHandleW
get_handler.restype = HMODULE
self.handler = get_handler(None)
self.hook_id = None
def do(self, callback: Callable, thread_id: int = 0) -> None:
# 13表示WH_KEYBOARD_LL,
# 详情:https://learn.microsoft.com/zh-cn/windows/win32/api/winuser/nf-winuser-setwindowshookexw
self.hook_id = self.__SetWindowsHookExW__(13, callback, self.handler, thread_id)
atexit.register(self.__unhook__)
def __unhook__(self) -> int:
return self.__UnhookWindowsHookEx__(self.hook_id)
为外部接口调用的,回调修饰函数和循环(引用loop函数即可)
key_event_state = {
0x0100: 1, # 普通按键按下
0x0101: 0, # 普通按键抬起
0x104: 1, # 系统按键按下
0x105: 0, # 系统按键抬起
}
def low_level_kb_proc(func: Callable) -> Callable[[int, WPARAM, LPARAM], int]:
def wrapper(n_code, w_param, l_param):
extra_info = l_param.contents.dwExtraInfo
if extra_info != 114:
key_state = key_event_state[w_param]
vk = l_param.contents.vk_code
scan_code = l_param.contents.scan_code
is_extended = l_param.contents.flags & 1
should_next = func(scan_code, vk, key_state, is_extended)
if should_next == -1: return 1
return user32.CallNextHookEx(0, n_code, w_param, l_param)
return HOOK_PROC(wrapper)
def loop(callback: Callable) -> None:
"""
程序的回调和消息队列的注册函数
:param callback: 回调函数
"""
HookWinApi().do(callback)
msg = MsgWinApi()
while msg.get_msg():
msg.translate_msg()
msg.dispatch_msg()
以下是最后的,用于发送按键和字符串的函数
class MouseInput(Structure):
"""
鼠标输入结构体
https://learn.microsoft.com/zh-cn/windows/win32/api/winuser/ns-winuser-mouseinput
"""
_fields_ = [("dx", LONG),
("dy", LONG),
("mouseData", DWORD),
("dwFlags", DWORD),
("time", DWORD),
("dwExtraInfo", LPVOID)]
class KeyboardInput(Structure):
"""
键盘输入结构体
https://learn.microsoft.com/zh-cn/windows/win32/api/winuser/ns-winuser-keybdinput
"""
_fields_ = [("wVk", WORD),
("wScan", WORD),
("dwFlags", DWORD),
("time", DWORD),
("dwExtraInfo", LPVOID)]
class HardwareInput(Structure):
"""
硬件输入结构体
https://learn.microsoft.com/zh-cn/windows/win32/api/winuser/ns-winuser-hardwareinput
"""
_fields_ = [("uMsg", DWORD),
("wParamL", WORD),
("wParamH", WORD)]
class InputUnion(Union):
"""
输入联合体
https://learn.microsoft.com/zh-cn/windows/win32/api/winuser/ns-winuser-inputunion
"""
_fields_ = [("mi", MouseInput),
("ki", KeyboardInput),
("hi", HardwareInput)]
class Input(Structure):
"""
输入结构体
https://learn.microsoft.com/zh-cn/windows/win32/api/winuser/ns-winuser-input
"""
_fields_ = [("type", DWORD),
("union", InputUnion)]
@singledispatch
def send(scan_code: int, vk: int, key_state: bool = True) -> None:
"""
发送键盘按键
https://learn.microsoft.com/zh-cn/windows/win32/api/winuser/nf-winuser-keybd_event
:param scan_code: 扫描码
:param vk: 虚拟键码
:param key_state: 按键状态,True表示按下,False表示释放
"""
state = 0x0000 if key_state else 0x0002
user32.keybd_event(vk, scan_code, state, 114)
@send.register
def _(character: str) -> None:
"""
发送字符串
https://learn.microsoft.com/zh-cn/windows/win32/api/winuser/nf-winuser-sendinput
:param character: 要发送的消息
"""
surrogates = bytearray(character.encode('utf-16le'))
presses, releases = [], []
for i in range(0, len(surrogates), 2):
code = (surrogates[i + 1] << 8) | surrogates[i]
# KeyboardInput 第三个参数,0x04表示输入类型为 unicode 字符事件, 0x02表示输入类型为按下事件
# Input 类型中,0代表鼠标,1代表键盘,2代表其他
structure = KeyboardInput(0, code, 0x04, 0, None)
presses.append(Input(1, InputUnion(ki=structure)))
structure = KeyboardInput(0, code, 0x04 | 0x02, 0, None)
releases.append(Input(1, InputUnion(ki=structure)))
inputs = presses + releases
n_inputs = len(inputs)
p_inputs = (Input * n_inputs)(*inputs)
cb_size = INT(sizeof(Input))
return user32.SendInput(n_inputs, p_inputs, cb_size)