Skip to main content

ctypes

获取当前系统语言UI

dll_h = ctypes.windll.kernel32
# currt_sys_lang = hex(dll_h.GetSystemDefaultUILanguage())
currt_sys_lang = hex(dll_h.GetSystemDefaultUILanguage())

print("当前系统语言: ", currt_sys_lang)

获取当前输入语言

import ctypes
user32 = ctypes.WinDLL("user32", use_last_error=True)
curr_window = user32.GetForegroundWindow()
thread_id = user32.GetWindowThreadProcessId(curr_window, 0)
klid = user32.GetKeyboardLayout(thread_id)
lid = klid & (2**16 - 1)
lid_hex = hex(lid)

print("当前输入语言: ", lid_hex)

print(lid_hex)
if lid_hex == '0x409':
print('当前的输入法状态是英文输入模式\n\n')
elif lid_hex == '0x804':
print('当前的输入法是中文输入模式\n\n')
else:
print('当前的输入法既不是英文输入也不是中文输入\n\n')

解决DPI缩放问题

from ctypes import windll, byref, c_ubyte
from ctypes.wintypes import RECT, HWND
import numpy as np

GetDC = windll.user32.GetDC
CreateCompatibleDC = windll.gdi32.CreateCompatibleDC
GetClientRect = windll.user32.GetClientRect
CreateCompatibleBitmap = windll.gdi32.CreateCompatibleBitmap
SelectObject = windll.gdi32.SelectObject
BitBlt = windll.gdi32.BitBlt
SRCCOPY = 0x00CC0020
GetBitmapBits = windll.gdi32.GetBitmapBits
DeleteObject = windll.gdi32.DeleteObject
ReleaseDC = windll.user32.ReleaseDC

# 排除缩放干扰
windll.user32.SetProcessDPIAware()

def capture(handle: HWND):
"""窗口客户区截图

Args:
handle (HWND): 要截图的窗口句柄

Returns:
numpy.ndarray: 截图数据
"""
# 获取窗口客户区的大小
r = RECT()
GetClientRect(handle, byref(r))
width, height = r.right, r.bottom
# 开始截图
dc = GetDC(handle)
cdc = CreateCompatibleDC(dc)
bitmap = CreateCompatibleBitmap(dc, width, height)
SelectObject(cdc, bitmap)
BitBlt(cdc, 0, 0, width, height, dc, 0, 0, SRCCOPY)
# 截图是BGRA排列,因此总元素个数需要乘以4
total_bytes = width*height*4
buffer = bytearray(total_bytes)
byte_array = c_ubyte*total_bytes
GetBitmapBits(bitmap, total_bytes, byte_array.from_buffer(buffer))
DeleteObject(bitmap)
DeleteObject(cdc)
ReleaseDC(handle, dc)
# 返回截图数据为numpy.ndarray
return np.frombuffer(buffer, dtype=np.uint8).reshape(height, width, 4)

if __name__ == "__main__":
import cv2
handle = windll.user32.FindWindowW(None, "一梦江湖")
image = capture(handle)
cv2.imshow("Capture Test", image)
cv2.waitKey()

后台键盘

对于键盘消息我们主要关注WM_KEYDOWNWM_KEYUP,其中WM_KEYDOWNWM_KEYUP分别对应按键的按下和放开。

from ctypes import windll
from ctypes.wintypes import HWND
import string
import time

PostMessageW = windll.user32.PostMessageW
MapVirtualKeyW = windll.user32.MapVirtualKeyW
VkKeyScanA = windll.user32.VkKeyScanA

WM_KEYDOWN = 0x100
WM_KEYUP = 0x101

# https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
VkCode = {
"back": 0x08,
"tab": 0x09,
"return": 0x0D,
"shift": 0x10,
"control": 0x11,
"menu": 0x12,
"pause": 0x13,
"capital": 0x14,
"escape": 0x1B,
"space": 0x20,
"end": 0x23,
"home": 0x24,
"left": 0x25,
"up": 0x26,
"right": 0x27,
"down": 0x28,
"print": 0x2A,
"snapshot": 0x2C,
"insert": 0x2D,
"delete": 0x2E,
"lwin": 0x5B,
"rwin": 0x5C,
"numpad0": 0x60,
"numpad1": 0x61,
"numpad2": 0x62,
"numpad3": 0x63,
"numpad4": 0x64,
"numpad5": 0x65,
"numpad6": 0x66,
"numpad7": 0x67,
"numpad8": 0x68,
"numpad9": 0x69,
"multiply": 0x6A,
"add": 0x6B,
"separator": 0x6C,
"subtract": 0x6D,
"decimal": 0x6E,
"divide": 0x6F,
"f1": 0x70,
"f2": 0x71,
"f3": 0x72,
"f4": 0x73,
"f5": 0x74,
"f6": 0x75,
"f7": 0x76,
"f8": 0x77,
"f9": 0x78,
"f10": 0x79,
"f11": 0x7A,
"f12": 0x7B,
"numlock": 0x90,
"scroll": 0x91,
"lshift": 0xA0,
"rshift": 0xA1,
"lcontrol": 0xA2,
"rcontrol": 0xA3,
"lmenu": 0xA4,
"rmenu": 0XA5
}

def get_virtual_keycode(key: str):
"""根据按键名获取虚拟按键码

Args:
key (str): 按键名

Returns:
int: 虚拟按键码
"""
if len(key) == 1 and key in string.printable:
# https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-vkkeyscana
return VkKeyScanA(ord(key)) & 0xff
else:
return VkCode[key]

def key_down(handle: HWND, key: str):
"""按下指定按键

Args:
handle (HWND): 窗口句柄
key (str): 按键名
"""
vk_code = get_virtual_keycode(key)
scan_code = MapVirtualKeyW(vk_code, 0)
# https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-keydown
wparam = vk_code
lparam = (scan_code << 16) | 1
PostMessageW(handle, WM_KEYDOWN, wparam, lparam)

def key_up(handle: HWND, key: str):
"""放开指定按键

Args:
handle (HWND): 窗口句柄
key (str): 按键名
"""
vk_code = get_virtual_keycode(key)
scan_code = MapVirtualKeyW(vk_code, 0)
# https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-keyup
wparam = vk_code
lparam = (scan_code << 16) | 0XC0000001
PostMessageW(handle, WM_KEYUP, wparam, lparam)

if __name__ == "__main__":
# 需要和目标窗口同一权限,游戏窗口通常是管理员权限
import sys
if not windll.shell32.IsUserAnAdmin():
# 不是管理员就提权
windll.shell32.ShellExecuteW(
None, "runas", sys.executable, __file__, None, 1)

import cv2
handle = windll.user32.FindWindowW(None, "一梦江湖")

# 控制角色向前移动两秒
key_down(handle, 'w')
time.sleep(2)
key_up(handle, 'w')

后台鼠标

对于鼠标消息我们主要关注WM_MOUSEMOVEWM_LBUTTONDOWNWM_LBUTTONUPWM_MOUSEWHEEL,这些消息分别对应鼠标移动、鼠标左键按下和放开,以及滚轮操作。

from ctypes import windll, byref
from ctypes.wintypes import HWND, POINT
import time

PostMessageW = windll.user32.PostMessageW
ClientToScreen = windll.user32.ClientToScreen

# https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-mousemove
WM_MOUSEMOVE = 0x0200
WM_LBUTTONDOWN = 0x0201
WM_LBUTTONUP = 0x202
WM_MOUSEWHEEL = 0x020A
WHEEL_DELTA = 120

def move_to(handle: HWND, x: int, y: int):
"""移动鼠标到坐标(x, y)

Args:
handle (HWND): 窗口句柄
x (int): 横坐标
y (int): 纵坐标
"""
# https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-mousemove
wparam = 0
lparam = y << 16 | x
PostMessageW(handle, WM_MOUSEMOVE, wparam, lparam)


def left_down(handle: HWND, x: int, y: int):
"""在坐标(x, y)按下鼠标左键

Args:
handle (HWND): 窗口句柄
x (int): 横坐标
y (int): 纵坐标
"""
# https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-lbuttondown
wparam = 0
lparam = y << 16 | x
PostMessageW(handle, WM_LBUTTONDOWN, wparam, lparam)


def left_up(handle: HWND, x: int, y: int):
"""在坐标(x, y)放开鼠标左键

Args:
handle (HWND): 窗口句柄
x (int): 横坐标
y (int): 纵坐标
"""
# https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-lbuttonup
wparam = 0
lparam = y << 16 | x
PostMessageW(handle, WM_LBUTTONUP, wparam, lparam)


def scroll(handle: HWND, delta: int, x: int, y: int):
"""在坐标(x, y)滚动鼠标滚轮

Args:
handle (HWND): 窗口句柄
delta (int): 为正向上滚动,为负向下滚动
x (int): 横坐标
y (int): 纵坐标
"""
move_to(handle, x, y)
# https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-mousewheel
wparam = delta << 16
p = POINT(x, y)
ClientToScreen(handle, byref(p))
lparam = p.y << 16 | p.x
PostMessageW(handle, WM_MOUSEWHEEL, wparam, lparam)


def scroll_up(handle: HWND, x: int, y: int):
"""在坐标(x, y)向上滚动鼠标滚轮

Args:
handle (HWND): 窗口句柄
x (int): 横坐标
y (int): 纵坐标
"""
scroll(handle, WHEEL_DELTA, x, y)


def scroll_down(handle: HWND, x: int, y: int):
"""在坐标(x, y)向下滚动鼠标滚轮

Args:
handle (HWND): 窗口句柄
x (int): 横坐标
y (int): 纵坐标
"""
scroll(handle, -WHEEL_DELTA, x, y)


if __name__ == "__main__":
# 需要和目标窗口同一权限,游戏窗口通常是管理员权限
import sys
if not windll.shell32.IsUserAnAdmin():
# 不是管理员就提权
windll.shell32.ShellExecuteW(
None, "runas", sys.executable, __file__, None, 1)

import cv2
handle = windll.user32.FindWindowW(None, "一梦江湖")
# 点击线路
left_down(handle, 1234, 20)
time.sleep(0.1)
left_up(handle, 1234, 20)
time.sleep(1)
# 滚动线路列表
scroll_down(handle, 1000, 200)

窗体控制

改变窗口大小和位置 SetWindowPos

BOOL SetWindowPos(
HWND hWnd,
HWND hWndInsertAfter,
int X,
int Y,
int cx,
int cy,
UINT uFlags
);

锁定窗口 EnableWindow

BOOL EnableWindow(
HWND hWnd,
BOOL bEnable
);
from ctypes import windll, byref
from ctypes.wintypes import RECT, HWND
import time

SetWindowPos = windll.user32.SetWindowPos
GetClientRect = windll.user32.GetClientRect
GetWindowRect = windll.user32.GetWindowRect
EnableWindow = windll.user32.EnableWindow

SWP_NOSIZE = 0x0001
SWP_NOMOVE = 0X0002
SWP_NOZORDER = 0x0004


def move_window(handle: HWND, x: int, y: int):
"""移动窗口到坐标(x, y)

Args:
handle (HWND): 窗口句柄
x (int): 横坐标
y (int): 纵坐标
"""
SetWindowPos(handle, 0, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER)


def resize_window(handle: HWND, width: int, height: int):
"""设置窗口大小为width × height

Args:
handle (HWND): 窗口句柄
width (int): 宽
height (int): 高
"""
SetWindowPos(handle, 0, 0, 0, width, height, SWP_NOMOVE | SWP_NOZORDER)


def resize_client(handle: HWND, width: int, height: int):
"""设置客户区大小为width × height

Args:
handle (HWND): 窗口句柄
width (int): 宽
height (int): 高
"""
client_rect = RECT()
GetClientRect(handle, byref(client_rect))
delta_w = width - client_rect.right
delta_h = height - client_rect.bottom
window_rect = RECT()
GetWindowRect(handle, byref(window_rect))
current_width = window_rect.right - window_rect.left
current_height = window_rect.bottom - window_rect.top
resize_window(handle, current_width+delta_w, current_height+delta_h)


def lock_window(handle: HWND):
"""锁定窗口

Args:
handle (HWND): 窗口句柄
"""
EnableWindow(handle, 0)


def unlock_window(handle: HWND):
"""解锁窗口

Args:
handle (HWND): 窗口句柄
"""
EnableWindow(handle, 1)

注意窗口操作也需要和目标窗口同权限

刷新资源管理器

import ctypes
import ctypes.wintypes

def refresh_explorer(path:str=None):
"""
通知资源管理器刷新指定目录(传None则刷新整个桌面)
"""
# 定义Windows API常量
SHCNE_ASSOCCHANGED = 0x08000000 # 文件关联变更事件
SHCNE_UPDATEDIR = 0x00001000 # 目录内容更新事件
SHCNF_IDLIST = 0x0000 # 标识按路径刷新
SHCNF_PATHW = 0x0005 # 使用宽字符路径
SHCNF_FLUSH = 0x1000 # 立即刷新

shell32 = ctypes.windll.shell32

if path:
# 转换为绝对路径
abs_path = ctypes.wintypes.LPCWSTR(path)
# 发送目录更新事件
shell32.SHChangeNotify(
SHCNE_UPDATEDIR, SHCNF_PATHW | SHCNF_FLUSH, abs_path, None
)
else:
# 发送全局关联变更事件(刷新整个桌面)
shell32.SHChangeNotify(
SHCNE_ASSOCCHANGED, SHCNF_IDLIST | SHCNF_FLUSH, None, None
)