superkey-windows上的快捷键,python

65 0

这是一个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)
最新回复 ( 0 )
发新帖