├── LICENSE.txt ├── PyDmGame ├── __init__.py ├── dm.py ├── model │ └── model.py ├── modular │ ├── __init__.py │ ├── backgroundSettings.py │ ├── basicSettings.py │ ├── display_ │ │ ├── __init__.py │ │ ├── gdi.py │ │ └── normal.py │ ├── keyboard.py │ ├── keyboard_ │ │ ├── __init__.py │ │ ├── normal.py │ │ ├── normal2.py │ │ ├── windows.py │ │ ├── windows2.py │ │ └── yijianshu.py │ ├── mouse.py │ ├── mouse_ │ │ ├── __init__.py │ │ ├── normal.py │ │ ├── normal2.py │ │ ├── windows.py │ │ ├── windows2.py │ │ └── yijianshu.py │ ├── ocr.py │ ├── picColor.py │ ├── publicFunction.py │ ├── pydirectinput.py │ ├── vk_code.py │ └── window.py └── tests │ ├── 单点比色.py │ ├── 截图测试.py │ ├── 键盘测试.py │ └── 鼠标测试.py ├── README.md └── setup.py /LICENSE.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiong421/PyDmGame/08ba8aefeb509bb4183e0fdfab23bd96a16c5856/LICENSE.txt -------------------------------------------------------------------------------- /PyDmGame/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | @Time : 2023/2/12 11:34 4 | @Auth : 大雄 5 | @File :__init__.py 6 | @IDE :PyCharm 7 | @Email:3475228828@qq.com 8 | @func:功能 9 | """ 10 | from PyDmGame.dm import DM 11 | 12 | -------------------------------------------------------------------------------- /PyDmGame/dm.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | @Time : 2023/2/6 15:44 4 | @Auth : 大雄 5 | @File :dm.py 6 | @IDE :PyCharm 7 | @Email:3475228828@qq.com 8 | @func:找图找色 9 | """ 10 | 11 | from PyDmGame.modular import * 12 | from PyDmGame.model.model import td_info,thread_max_num 13 | 14 | class DM(Keyboard,Mouse,BackgroundSettings,BasicSettings,Ocr,PicColor,Window): 15 | count = 0 16 | def __getattribute__(self, item): 17 | ret = super(Keyboard, self).__getattribute__(item) 18 | if str(type(ret))=="" or str(type(ret))=="": 19 | flag = [td_info[self.id].display,td_info[self.id].keyboard,td_info[self.id].mouse] 20 | if not None is flag: 21 | for i in flag: 22 | if hasattr(i,ret.__name__): 23 | def res(*args): 24 | func = getattr(i, ret.__name__) 25 | return func(*args) 26 | return res 27 | return ret 28 | 29 | def __del__(self): 30 | td_info[self.id].clear() 31 | 32 | def __init__(self): 33 | if self.count None: 73 | MOUSEEVENTF_MOVE = 0x0001 74 | MOUSEEVENTF_ABSOLUTE = 0x8000 75 | SM_CXSCREEN = 0 76 | SM_CYSCREEN = 1 77 | cx_screen = Mouse_normal2.winuser32.GetSystemMetrics(SM_CXSCREEN) 78 | cy_screen = Mouse_normal2.winuser32.GetSystemMetrics(SM_CYSCREEN) 79 | real_x = round(65535 * x / cx_screen) 80 | real_y = round(65535 * y / cy_screen) 81 | Mouse_normal2.winuser32.mouse_event(MOUSEEVENTF_ABSOLUTE|MOUSEEVENTF_MOVE,real_x,real_y,0,0) 82 | 83 | @staticmethod 84 | def WheelDown(): 85 | win32api.mouse_event(win32con.MOUSEEVENTF_WHEEL, -120, 0) 86 | 87 | @staticmethod 88 | def WheelUp(): 89 | win32api.mouse_event(win32con.MOUSEEVENTF_WHEEL, 120, 0) -------------------------------------------------------------------------------- /PyDmGame/modular/mouse_/windows.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | @Time : 2023/2/13 23:25 4 | @Auth : 大雄 5 | @File :window.py 6 | @IDE :PyCharm 7 | @Email:3475228828@qq.com 8 | @func:功能 9 | """ 10 | 11 | import time 12 | 13 | import win32api 14 | import win32con 15 | 16 | 17 | class Mouse_windows: 18 | def __init__(self,hwnd): 19 | self.hwnd = hwnd 20 | self.SetMouseDelay() 21 | self.x = 0 22 | self.y = 0 23 | 24 | def LeftDown(self): 25 | win32api.SendMessage(self.hwnd, win32con.WM_LBUTTONDOWN, win32con.MK_LBUTTON, win32api.MAKELONG(self.x,self.y)) # 鼠标按下 26 | 27 | def LeftUp(self): 28 | win32api.SendMessage(self.hwnd, win32con.WM_LBUTTONUP, win32con.MK_LBUTTON, 29 | win32api.MAKELONG(self.x, self.y)) # 鼠标按下 30 | 31 | def LeftClick(self): 32 | self.LeftDown() 33 | time.sleep(self.mouse_delay) 34 | self.LeftUp() 35 | 36 | def LeftDoubleClick(self): 37 | self.LeftClick() 38 | time.sleep(self.mouse_delay) 39 | self.LeftClick() 40 | 41 | def RightDown(self): 42 | win32api.SendMessage(self.hwnd, win32con.WM_RBUTTONDOWN, win32con.MK_LBUTTON, 43 | win32api.MAKELONG(self.x, self.y)) 44 | 45 | def RightUp(self): 46 | win32api.SendMessage(self.hwnd, win32con.WM_RBUTTONUP, win32con.MK_LBUTTON, 47 | win32api.MAKELONG(self.x, self.y)) 48 | 49 | def RightClick(self): 50 | self.RightClick() 51 | time.sleep(self.mouse_delay) 52 | self.RightClick() 53 | 54 | def SetMouseDelay(self,delay=None): 55 | self.mouse_delay = 0.03 if delay is None else delay/1000 56 | 57 | def MiddleDown(self): 58 | win32api.SendMessage(self.hwnd, win32con.WM_MBUTTONDOWN, win32con.MK_LBUTTON, 59 | win32api.MAKELONG(self.x, self.y)) 60 | 61 | def MiddleUp(self): 62 | win32api.SendMessage(self.hwnd, win32con.WM_MBUTTONUP, win32con.MK_LBUTTON, 63 | win32api.MAKELONG(self.x, self.y)) 64 | 65 | def MoveTo(self,x,y): 66 | self.x = x 67 | self.y = y 68 | 69 | # def WheelDown(self): 70 | # pass 71 | # 72 | # def WheelUp(self): 73 | # pass 74 | -------------------------------------------------------------------------------- /PyDmGame/modular/mouse_/windows2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | @Time : 2023/2/13 23:25 4 | @Auth : 大雄 5 | @File :window2.py 6 | @IDE :PyCharm 7 | @Email:3475228828@qq.com 8 | @func:功能 9 | """ 10 | 11 | import time 12 | import win32api 13 | import win32con 14 | 15 | class Mouse_windows2: 16 | def __init__(self,hwnd): 17 | self.hwnd = hwnd 18 | self.SetMouseDelay() 19 | self.x = 0 20 | self.y = 0 21 | 22 | def LeftDown(self): 23 | win32api.PostMessage(self.hwnd, win32con.WM_LBUTTONDOWN, win32con.MK_LBUTTON, win32api.MAKELONG(self.x,self.y)) # 鼠标按下 24 | 25 | def LeftUp(self): 26 | win32api.PostMessage(self.hwnd, win32con.WM_LBUTTONUP, win32con.MK_LBUTTON, 27 | win32api.MAKELONG(self.x, self.y)) # 鼠标按下 28 | 29 | def LeftClick(self): 30 | self.LeftDown() 31 | time.sleep(self.mouse_delay) 32 | self.LeftUp() 33 | 34 | def LeftDoubleClick(self): 35 | self.LeftClick() 36 | time.sleep(self.mouse_delay) 37 | self.LeftClick() 38 | 39 | def RightDown(self): 40 | win32api.PostMessage(self.hwnd, win32con.WM_RBUTTONDOWN, win32con.MK_LBUTTON, 41 | win32api.MAKELONG(self.x, self.y)) 42 | 43 | def RightUp(self): 44 | win32api.PostMessage(self.hwnd, win32con.WM_RBUTTONUP, win32con.MK_LBUTTON, 45 | win32api.MAKELONG(self.x, self.y)) 46 | 47 | def RightClick(self): 48 | self.RightClick() 49 | time.sleep(self.mouse_delay) 50 | self.RightClick() 51 | 52 | def SetMouseDelay(self,delay=None): 53 | self.mouse_delay = 0.03 if delay is None else delay/1000 54 | 55 | def MiddleDown(self): 56 | win32api.PostMessage(self.hwnd, win32con.WM_MBUTTONDOWN, win32con.MK_LBUTTON, 57 | win32api.MAKELONG(self.x, self.y)) 58 | 59 | def MiddleUp(self): 60 | win32api.PostMessage(self.hwnd, win32con.WM_MBUTTONUP, win32con.MK_LBUTTON, 61 | win32api.MAKELONG(self.x, self.y)) 62 | 63 | def MoveTo(self,x,y): 64 | self.x = x 65 | self.y = y 66 | point = win32api.MAKELONG(x, y) 67 | win32api.PostMessage(self.hwnd, win32con.WM_MOUSEMOVE, None, point) 68 | 69 | # def WheelDown(self): 70 | # pass 71 | # 72 | # def WheelUp(self): 73 | # pass 74 | -------------------------------------------------------------------------------- /PyDmGame/modular/mouse_/yijianshu.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | @Time : 2023/2/18 22:10 4 | @Auth : 大雄 5 | @File :yijianshu.py 6 | @IDE :PyCharm 7 | @Email:3475228828@qq.com 8 | @func:功能 9 | """ 10 | class Mouse_YJS: 11 | def __init__(self): 12 | self.SetMouseDelay() 13 | 14 | @staticmethod 15 | def LeftDown(): 16 | pass 17 | 18 | @staticmethod 19 | def LeftUp(): 20 | pass 21 | 22 | def LeftClick(self): 23 | pass 24 | 25 | def LeftDoubleClick(self): 26 | pass 27 | 28 | @staticmethod 29 | def RightDown(): 30 | pass 31 | 32 | @staticmethod 33 | def RightUp(): 34 | pass 35 | 36 | def RightClick(self): 37 | pass 38 | 39 | def SetMouseDelay(self,delay=None): 40 | pass 41 | 42 | def MiddleClick(self): 43 | pass 44 | 45 | @staticmethod 46 | def MiddleDown(): 47 | pass 48 | 49 | @staticmethod 50 | def MiddleUp(): 51 | pass 52 | 53 | @staticmethod 54 | def MoveTo(x,y): 55 | pass 56 | 57 | @staticmethod 58 | def WheelDown(): 59 | pass 60 | 61 | @staticmethod 62 | def WheelUp(): 63 | pass -------------------------------------------------------------------------------- /PyDmGame/modular/ocr.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | @Time : 2023/2/12 11:23 4 | @Auth : 大雄 5 | @File :ocr.py 6 | @IDE :PyCharm 7 | @Email:3475228828@qq.com 8 | @func:功能 9 | """ 10 | import os 11 | 12 | 13 | 14 | class Ocr: 15 | # # 设置服务器地址 16 | # def SetServerUrl(self,url): 17 | # self.ocr_server_url = url 18 | 19 | # 查找数字是否存在 20 | def FindNum(self, x1, y1, x2, y2, numString, color_format, sim): 21 | """ 22 | :param x1: x1 整形数:区域的左上X坐标 23 | :param y1: y1 整形数:区域的左上Y坐标 24 | :param x2: x2 整形数:区域的右下X坐标 25 | :param y2: y2 整形数:区域的右下Y坐标 26 | :param numString: 字符串:如数字"1","56","789" 27 | :param color_format:字符串:颜色格式串, 可以包含换行分隔符,语法是","后加分割字符串. 具体可以查看下面的示例 .注意,RGB和HSV,以及灰度格式都支持. 28 | :param sim: 双精度浮点数:相似度,取值范围0.1-1.0 29 | :return:bool 30 | """ 31 | if numString in str(self.OcrNum(x1, y1, x2, y2, color_format, sim)): 32 | return True 33 | return False 34 | 35 | # 识别数字 36 | def OcrNum(self, x1, y1, x2, y2, color_format, sim, dirPath): 37 | """ 38 | :param x1: x1 整形数:区域的左上X坐标 39 | :param y1: y1 整形数:区域的左上Y坐标 40 | :param x2: x2 整形数:区域的右下X坐标 41 | :param y2: y2 整形数:区域的右下Y坐标 42 | :param color_format: 字符串:颜色格式串, 可以包含换行分隔符,语法是","后加分割字符串. 具体可以查看下面的示例 .注意,RGB和HSV,以及灰度格式都支持. 43 | :param sim: 双精度浮点数:相似度,取值范围0.1-1.0 44 | :param dirPath: 图库路径,用于存储0-9数字模板 45 | :return: num:字符串数字 46 | """ 47 | num_dict = {} 48 | # 遍历图像,并挨个识别 49 | for i in range(10): 50 | img_num = dirPath + os.path.sep + f"{i}.bmp" 51 | ret, locs = self.FindPics(x1, y1, x2, y2, img_num, color_format, sim) 52 | if ret != -1: 53 | for loc in locs: 54 | num_dict.update({loc[0]: i}) 55 | # 排序字典 56 | new_num_list = sorted(num_dict.items(), key=lambda x: x[0]) # 对x轴进行排序 57 | 58 | # 遍历并拼接数字 59 | nums = "".join([str(new_num[1]) for new_num in new_num_list]) 60 | try: 61 | return nums 62 | except: 63 | return "" 64 | 65 | # # 服务器ocr 66 | # def OcrServer(self, x1, y1, x2, y2, color_format, sim): 67 | # """ 68 | # :param x1: 整形数:区域的左上X坐标 69 | # :param y1: 整形数:区域的左上Y坐标 70 | # :param x2: 整形数:区域的右下X坐标 71 | # :param y2: 整形数:区域的右下Y坐标 72 | # :param color_format: 偏色,可以是RGB偏色,格式"FFFFFF-202020",也可以是HSV偏色,格式((0,0,0),(180,255,255)) 73 | # :param sim: 相似度 74 | # :return: 列表,包含坐标和文字 75 | # """ 76 | # if not None in [x1, y1, x2, y2]: 77 | # img = self.img[y1:y2, x1:x2] 78 | # img = self.__ps_to_img(img, color_format) 79 | # img_bt = np.array(cv2.imencode('.png', img)[1]).tobytes() 80 | # data = { 81 | # 'username': 270207756, 82 | # 'pwd': 123456, 83 | # 'lang': "ch", # ch中文,eh英文 84 | # "det": True, # 是指是否要检测文本的位置,False为识别单行,Ture为识别多行,只有多行才有坐标返回 85 | # "ret": True, # 是指是否要识别文本的内容 86 | # "file":io.BytesIO(img_bt) 87 | # } 88 | # data = urllib.parse.urlencode(data).encode('utf8') 89 | # request = urllib.request.Request(self.ocr_server_url, data=data,method="post") 90 | # response = urllib.request.urlopen(request) 91 | # # response = requests.post(self.ocr_server_url, files=[('img', ("", file))], data=data) 92 | # if response.status_code == 200: 93 | # result = response.json() 94 | # msg_code = result["msg_code"] 95 | # if msg_code == 200: 96 | # content = result["content"] 97 | # result = [item for item in content[0] if item[1][1] > sim] 98 | # new_result = [] 99 | # for item in result: 100 | # item[0] = [[loc[0] + x1, loc[1] + y1] for loc in item[0]] 101 | # new_result.append(item) 102 | # return new_result 103 | # else: 104 | # print(f"识别异常 {response.json()}") 105 | # 106 | # else: 107 | # print(f"服务器异常 {response.status_code}") 108 | # return False -------------------------------------------------------------------------------- /PyDmGame/modular/picColor.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | @Time : 2023/2/12 11:21 4 | @Auth : 大雄 5 | @File :picColor.py 6 | @IDE :PyCharm 7 | @Email:3475228828@qq.com 8 | @func:功能 9 | """ 10 | import os 11 | from PyDmGame.modular.publicFunction import * 12 | from functools import reduce 13 | import cv2,numpy as np 14 | 15 | class PicColor: 16 | 17 | # 随机创建图片,用于测试算法 18 | def create_random_img(self, width, height, item=3): 19 | img = np.random.randint(0, 255, (width, height, item)) 20 | img = img.astype(np.uint8) 21 | return img 22 | 23 | # 转换大漠格式RGB "ffffff-303030" 为 BGR遮罩范围(100,100,100),(255,255,255) 24 | def __color_to_range(self, color, sim): 25 | if sim <= 1: 26 | if len(color) == 6: 27 | c = color 28 | weight = "000000" 29 | elif "-" in color: 30 | c, weight = color.split("-") 31 | else: 32 | raise "参数错误" 33 | else: 34 | raise "参数错误" 35 | color = int(c[4:], 16), int(c[2:4], 16), int(c[:2], 16) 36 | weight = int(weight[4:], 16), int(weight[2:4], 16), int(weight[:2], 16) 37 | sim = int((1 - sim) * 255) 38 | lower = tuple(map(lambda c, w: max(0, c - w - sim), color, weight)) 39 | upper = tuple(map(lambda c, w: min(c + w + sim, 255), color, weight)) 40 | return lower, upper 41 | 42 | def __imread(self, path): 43 | # 读取图片 44 | if is_chinese(path): 45 | img = cv2.imdecode(np.fromfile(path, dtype=np.uint8), -1) # 避免路径有中文 46 | else: 47 | img = cv2.imread(path) 48 | return img 49 | 50 | def __inRange(self, img, lower, upper): 51 | mask = cv2.inRange(img, np.array(lower), np.array(upper)) 52 | img = cv2.bitwise_and(img, img, mask=mask) 53 | return img 54 | 55 | def __imgshow(self, img): 56 | windows_name = "img" 57 | cv2.imshow(windows_name, img) 58 | cv2.waitKey() 59 | cv2.destroyWindow(windows_name) 60 | 61 | def __ps_to_img(self, img, ps): 62 | """ 63 | :param img: cv图像 64 | :param ps: 偏色 65 | :return: 偏色后的cv图像 66 | """ 67 | # 判断是RGB偏色还是HSV偏色,对应使用遮罩过滤 68 | if not ps: 69 | return img 70 | 71 | elif type(ps) == str: 72 | lower, upper = self.__color_to_range(ps, 1) 73 | img = self.__inRange(img, lower, upper) 74 | 75 | elif type(ps) == tuple: 76 | lower, upper = ps 77 | img_hsv1 = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) 78 | img = self.__inRange(img_hsv1, lower, upper) 79 | return img 80 | 81 | 82 | def __FindPic(self, x1, y1, x2, y2, pic_name, delta_color, sim, method, drag=None): 83 | # 读取图片 84 | ret = self.Capture(x1, y1, x2, y2) 85 | if ret: 86 | img1 = self.GetCVImg() 87 | else: 88 | raise "截圖失敗" 89 | img2 = self.__imread(self.path + os.path.sep + pic_name) 90 | 91 | # 判断是RGB偏色还是HSV偏色,对应使用遮罩过滤 92 | img1 = self.__ps_to_img(img1, delta_color) 93 | img2 = self.__ps_to_img(img2, delta_color) 94 | # 利用cv的模板匹配找图 95 | result = cv2.matchTemplate(img1, img2, method) 96 | min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result) 97 | yloc, xloc = np.where(result >= sim) 98 | height, width = img2.shape[:2] 99 | return result, min_val, max_val, min_loc, max_loc, yloc, xloc, height, width 100 | 101 | # 单点比色 102 | def CmpColor(self, x, y, color, sim=1): 103 | """ 104 | :param x: 坐标x 105 | :param y: 坐标y 106 | :param color: 颜色字符串,可以支持偏色,"ffffff-202020",最多支持一个 107 | :param sim:相似度(0.1-1.0) (0,255) 108 | :return:bool 109 | """ 110 | ret = self.Capture(0, 0, 0, 0) 111 | if ret: 112 | img = self.GetCVImg() 113 | else: 114 | raise "截圖失敗" 115 | lower, upper = self.__color_to_range(color, sim) 116 | if not lower is None: 117 | new_color = img[y,x] 118 | for i in [0, 1, 2]: 119 | if new_color[i] < lower[i] or new_color[i] > upper[i]: 120 | return False 121 | return True 122 | return False 123 | 124 | # 范围找色 125 | def FindColor(self, x1, y1, x2, y2, color, sim, dir=None): 126 | ret = self.Capture(x1, y1, x2, y2) 127 | if ret: 128 | img = self.GetCVImg() 129 | else: 130 | raise "截圖失敗" 131 | lower, upper = self.__color_to_range(color, sim) 132 | height, width = img.shape[:2] 133 | b, g, r = cv2.split(img) 134 | b = b.reshape(1, height * width) 135 | g = g.reshape(1, height * width) 136 | r = r.reshape(1, height * width) 137 | key1 = np.where(lower[0] <= b) 138 | key2 = np.where(lower[1] <= g) 139 | key3 = np.where(lower[2] <= r) 140 | key4 = np.where(upper[0] >= b) 141 | key5 = np.where(upper[1] >= g) 142 | key6 = np.where(upper[2] >= r) 143 | 144 | if len(key1[0]) and len(key2[0]) and len(key3[0]) and len(key4[0]) and len(key5[0]) and len(key6[0]): 145 | keys = reduce(np.intersect1d, [key1, key2, key3, key4, key5, key6]) # 相似度越小,交集数据越多,找的慢,相似度越大,找的越快,主要耗时的地方 146 | if len(keys): 147 | x, y = divmod(keys[1], width) 148 | return 0, x + x1, y + y1 149 | return -1, -1, -1 150 | 151 | # 找图 152 | def FindPic(self, x1, y1, x2, y2, pic_name, delta_color, sim, method=5, drag=None): 153 | """ 154 | :param x1:区域的左上X坐标 155 | :param y1:区域的左上Y坐标 156 | :param x2:区域的右下X坐标 157 | :param y2:区域的右下Y坐标 158 | :param pic_name:图片名,只能单个图片 159 | :param delta_color:偏色,可以是RGB偏色,格式"FFFFFF-202020",也可以是HSV偏色,格式((0,0,0),(180,255,255)) 160 | :param sim:相似度,和算法相关 161 | :param dir:仿大漠,总共有6总 162 | :param drag:是否在找到的位置画图并显示,默认不画 163 | 方差匹配方法:匹配度越高,值越接近于0。 164 | 归一化方差匹配方法:完全匹配结果为0。 165 | 相关性匹配方法:完全匹配会得到很大值,不匹配会得到一个很小值或0。 166 | 归一化的互相关匹配方法:完全匹配会得到1, 完全不匹配会得到0。 167 | 相关系数匹配方法:完全匹配会得到一个很大值,完全不匹配会得到0,完全负相关会得到很大的负数。 168 | (此处与书籍以及大部分分享的资料所认为不同,研究公式发现,只有归一化的相关系数才会有[-1,1]的值域) 169 | 归一化的相关系数匹配方法:完全匹配会得到1,完全负相关匹配会得到-1,完全不匹配会得到0。 170 | :return: 171 | """ 172 | result, min_val, max_val, min_loc, max_loc, yloc, xloc, height, width = self.__FindPic(x1, y1, x2, y2, pic_name, 173 | delta_color, sim, 174 | method=5, drag=None) 175 | if len(xloc): 176 | x, y = max_loc[0] + x1, max_loc[1] + y1 177 | if drag: 178 | img = cv2.rectangle(self.GetCVImg(), (x, y), (x + width, y + height), (255, 0, 0), thickness=2) 179 | self.__imgshow(img) 180 | return 0, x, y 181 | return -1, -1, -1 182 | 183 | # 找图,返回多个匹配地址 184 | def FindPics(self, x1, y1, x2, y2, pic_name, delta_color, sim, method=5, drag=None): 185 | result, min_val, max_val, min_loc, max_loc, yloc, xloc, height, width = self.__FindPic(x1, y1, x2, y2, pic_name, 186 | delta_color, sim, 187 | method=5, drag=None) 188 | if len(xloc): 189 | if drag: 190 | for loc in zip(xloc, yloc): 191 | img = cv2.rectangle(self.GetCVImg(), (loc[0], loc[1]), (loc[0] + width, loc[1] + height), (255, 0, 0), 192 | thickness=2) 193 | self.__imgshow(img) 194 | return 0, zip(xloc, yloc) 195 | return -1, [-1, -1] 196 | 197 | 198 | # 截图 199 | def Capture(self, x1, y1, x2, y2, file=None): 200 | """ 201 | :param x1: x1 整形数:区域的左上X坐标 202 | :param y1: y1 整形数:区域的左上Y坐标 203 | :param x2: x2 整形数:区域的右下X坐标 204 | :param y2: y2 整形数:区域的右下Y坐标 205 | :param file: 保存文件路径,不填写也可以通过 206 | :return: 207 | """ 208 | pass 209 | 210 | # 获取cv图像 211 | def GetCVImg(self): 212 | pass -------------------------------------------------------------------------------- /PyDmGame/modular/publicFunction.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | @Time : 2023/2/12 11:18 4 | @Auth : 大雄 5 | @File :publicFunction.py 6 | @IDE :PyCharm 7 | @Email:3475228828@qq.com 8 | @func:功能 9 | """ 10 | import time 11 | 12 | 13 | # 测试耗时 14 | def test_run_time(func): 15 | def inner(*args, **kwargs): 16 | t_start = time.time() 17 | res = func(*args, **kwargs) 18 | t_end = time.time() 19 | print(f"一共花费了{t_end - t_start}秒时间,函数运行结果是 {res}") 20 | return res 21 | 22 | return inner 23 | 24 | 25 | # 判断字符串是否为中文 26 | def is_chinese(string): 27 | """ 28 | 检查整个字符串是否包含中文 29 | :param string: 需要检查的字符串 30 | :return: bool 31 | """ 32 | for ch in string: 33 | if u'\u4e00' <= ch <= u'\u9fff': 34 | return True 35 | return False 36 | 37 | def raise_dm_error(name,description): 38 | error = f"报错类型:{name},报错描述:{description}" 39 | raise error -------------------------------------------------------------------------------- /PyDmGame/modular/pydirectinput.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | @Time : 2023/2/12 19:30 4 | @Auth : 大雄 5 | @File :pydirectinput.py 6 | @IDE :PyCharm 7 | @Email:3475228828@qq.com 8 | @func:功能 9 | """ 10 | import ctypes 11 | import functools 12 | import inspect 13 | import time 14 | 15 | SendInput = ctypes.windll.user32.SendInput 16 | MapVirtualKey = ctypes.windll.user32.MapVirtualKeyW 17 | 18 | # Constants for failsafe check and pause 19 | 20 | FAILSAFE = True 21 | FAILSAFE_POINTS = [(0, 0)] 22 | PAUSE = 0.1 # Tenth-second pause by default. 23 | 24 | # Constants for the mouse button names 25 | LEFT = "left" 26 | MIDDLE = "middle" 27 | RIGHT = "right" 28 | PRIMARY = "primary" 29 | SECONDARY = "secondary" 30 | WHEEL = "wheel" 31 | 32 | # Mouse Scan Code Mappings 33 | MOUSEEVENTF_MOVE = 0x0001 34 | MOUSEEVENTF_ABSOLUTE = 0x8000 35 | MOUSEEVENTF_LEFTDOWN = 0x0002 36 | MOUSEEVENTF_LEFTUP = 0x0004 37 | MOUSEEVENTF_LEFTCLICK = MOUSEEVENTF_LEFTDOWN + MOUSEEVENTF_LEFTUP 38 | MOUSEEVENTF_RIGHTDOWN = 0x0008 39 | MOUSEEVENTF_RIGHTUP = 0x0010 40 | MOUSEEVENTF_RIGHTCLICK = MOUSEEVENTF_RIGHTDOWN + MOUSEEVENTF_RIGHTUP 41 | MOUSEEVENTF_MIDDLEDOWN = 0x0020 42 | MOUSEEVENTF_MIDDLEUP = 0x0040 43 | MOUSEEVENTF_MIDDLECLICK = MOUSEEVENTF_MIDDLEDOWN + MOUSEEVENTF_MIDDLEUP 44 | MOUSEEVENTF_WHEEL = 0x0800 45 | MOUSEEVENTF_WHEELUP = -0x0078 46 | MOUSEEVENTF_WHEELDOWN = 0x0078 47 | 48 | # KeyBdInput Flags 49 | KEYEVENTF_EXTENDEDKEY = 0x0001 50 | KEYEVENTF_KEYUP = 0x0002 51 | KEYEVENTF_SCANCODE = 0x0008 52 | KEYEVENTF_UNICODE = 0x0004 53 | 54 | # MapVirtualKey Map Types 55 | MAPVK_VK_TO_CHAR = 2 56 | MAPVK_VK_TO_VSC = 0 57 | MAPVK_VSC_TO_VK = 1 58 | MAPVK_VSC_TO_VK_EX = 3 59 | 60 | # Keyboard Scan Code Mappings 61 | KEYBOARD_MAPPING = { 62 | 'escape': 0x01, 63 | 'esc': 0x01, 64 | 'f1': 0x3B, 65 | 'f2': 0x3C, 66 | 'f3': 0x3D, 67 | 'f4': 0x3E, 68 | 'f5': 0x3F, 69 | 'f6': 0x40, 70 | 'f7': 0x41, 71 | 'f8': 0x42, 72 | 'f9': 0x43, 73 | 'f10': 0x44, 74 | 'f11': 0x57, 75 | 'f12': 0x58, 76 | 'printscreen': 0xB7, 77 | 'prntscrn': 0xB7, 78 | 'prtsc': 0xB7, 79 | 'prtscr': 0xB7, 80 | 'scrolllock': 0x46, 81 | 'pause': 0xC5, 82 | '`': 0x29, 83 | '1': 0x02, 84 | '2': 0x03, 85 | '3': 0x04, 86 | '4': 0x05, 87 | '5': 0x06, 88 | '6': 0x07, 89 | '7': 0x08, 90 | '8': 0x09, 91 | '9': 0x0A, 92 | '0': 0x0B, 93 | '-': 0x0C, 94 | '=': 0x0D, 95 | 'backspace': 0x0E, 96 | 'insert': 0xD2 + 1024, 97 | 'home': 0xC7 + 1024, 98 | 'pageup': 0xC9 + 1024, 99 | 'pagedown': 0xD1 + 1024, 100 | # numpad 101 | 'numlock': 0x45, 102 | 'divide': 0xB5 + 1024, 103 | 'multiply': 0x37, 104 | 'subtract': 0x4A, 105 | 'add': 0x4E, 106 | 'decimal': 0x53, 107 | 'numpadenter': 0x9C + 1024, 108 | 'numpad1': 0x4F, 109 | 'numpad2': 0x50, 110 | 'numpad3': 0x51, 111 | 'numpad4': 0x4B, 112 | 'numpad5': 0x4C, 113 | 'numpad6': 0x4D, 114 | 'numpad7': 0x47, 115 | 'numpad8': 0x48, 116 | 'numpad9': 0x49, 117 | 'numpad0': 0x52, 118 | # end numpad 119 | 'tab': 0x0F, 120 | 'q': 0x10, 121 | 'w': 0x11, 122 | 'e': 0x12, 123 | 'r': 0x13, 124 | 't': 0x14, 125 | 'y': 0x15, 126 | 'u': 0x16, 127 | 'i': 0x17, 128 | 'o': 0x18, 129 | 'p': 0x19, 130 | '[': 0x1A, 131 | ']': 0x1B, 132 | '\\': 0x2B, 133 | 'del': 0xD3 + 1024, 134 | 'delete': 0xD3 + 1024, 135 | 'end': 0xCF + 1024, 136 | 'capslock': 0x3A, 137 | 'a': 0x1E, 138 | 's': 0x1F, 139 | 'd': 0x20, 140 | 'f': 0x21, 141 | 'g': 0x22, 142 | 'h': 0x23, 143 | 'j': 0x24, 144 | 'k': 0x25, 145 | 'l': 0x26, 146 | ';': 0x27, 147 | "'": 0x28, 148 | 'enter': 0x1C, 149 | 'return': 0x1C, 150 | 'shift': 0x2A, 151 | 'shiftleft': 0x2A, 152 | 'z': 0x2C, 153 | 'x': 0x2D, 154 | 'c': 0x2E, 155 | 'v': 0x2F, 156 | 'b': 0x30, 157 | 'n': 0x31, 158 | 'm': 0x32, 159 | ',': 0x33, 160 | '.': 0x34, 161 | '/': 0x35, 162 | 'shiftright': 0x36, 163 | 'ctrl': 0x1D, 164 | 'ctrlleft': 0x1D, 165 | 'win': 0xDB + 1024, 166 | 'winleft': 0xDB + 1024, 167 | 'alt': 0x38, 168 | 'altleft': 0x38, 169 | ' ': 0x39, 170 | 'space': 0x39, 171 | 'altright': 0xB8 + 1024, 172 | 'winright': 0xDC + 1024, 173 | 'apps': 0xDD + 1024, 174 | 'ctrlright': 0x9D + 1024, 175 | # arrow key scancodes can be different depending on the hardware, 176 | # so I think the best solution is to look it up based on the virtual key 177 | # https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-mapvirtualkeya?redirectedfrom=MSDN 178 | 'up': MapVirtualKey(0x26, MAPVK_VK_TO_VSC), 179 | 'left': MapVirtualKey(0x25, MAPVK_VK_TO_VSC), 180 | 'down': MapVirtualKey(0x28, MAPVK_VK_TO_VSC), 181 | 'right': MapVirtualKey(0x27, MAPVK_VK_TO_VSC), 182 | } 183 | 184 | # C struct redefinitions 185 | 186 | PUL = ctypes.POINTER(ctypes.c_ulong) 187 | 188 | 189 | class KeyBdInput(ctypes.Structure): 190 | _fields_ = [("wVk", ctypes.c_ushort), 191 | ("wScan", ctypes.c_ushort), 192 | ("dwFlags", ctypes.c_ulong), 193 | ("time", ctypes.c_ulong), 194 | ("dwExtraInfo", PUL)] 195 | 196 | 197 | class HardwareInput(ctypes.Structure): 198 | _fields_ = [("uMsg", ctypes.c_ulong), 199 | ("wParamL", ctypes.c_short), 200 | ("wParamH", ctypes.c_ushort)] 201 | 202 | 203 | class MouseInput(ctypes.Structure): 204 | _fields_ = [("dx", ctypes.c_long), 205 | ("dy", ctypes.c_long), 206 | ("mouseData", ctypes.c_ulong), 207 | ("dwFlags", ctypes.c_ulong), 208 | ("time", ctypes.c_ulong), 209 | ("dwExtraInfo", PUL)] 210 | 211 | 212 | class POINT(ctypes.Structure): 213 | _fields_ = [("x", ctypes.c_long), 214 | ("y", ctypes.c_long)] 215 | 216 | 217 | class Input_I(ctypes.Union): 218 | _fields_ = [("ki", KeyBdInput), 219 | ("mi", MouseInput), 220 | ("hi", HardwareInput)] 221 | 222 | 223 | class Input(ctypes.Structure): 224 | _fields_ = [("type", ctypes.c_ulong), 225 | ("ii", Input_I)] 226 | 227 | 228 | # Fail Safe and Pause implementation 229 | 230 | class FailSafeException(Exception): 231 | pass 232 | 233 | 234 | def failSafeCheck(): 235 | if FAILSAFE and tuple(position()) in FAILSAFE_POINTS: 236 | raise FailSafeException( 237 | "PyDirectInput fail-safe triggered from mouse moving to a corner of the screen. To disable this " \ 238 | "fail-safe, set pydirectinput.FAILSAFE to False. DISABLING FAIL-SAFE IS NOT RECOMMENDED." 239 | ) 240 | 241 | 242 | def _handlePause(_pause): 243 | if _pause: 244 | assert isinstance(PAUSE, int) or isinstance(PAUSE, float) 245 | time.sleep(PAUSE) 246 | 247 | 248 | # direct copy of _genericPyAutoGUIChecks() 249 | def _genericPyDirectInputChecks(wrappedFunction): 250 | @functools.wraps(wrappedFunction) 251 | def wrapper(*args, **kwargs): 252 | funcArgs = inspect.getcallargs(wrappedFunction, *args, **kwargs) 253 | 254 | failSafeCheck() 255 | returnVal = wrappedFunction(*args, **kwargs) 256 | _handlePause(funcArgs.get("_pause")) 257 | return returnVal 258 | 259 | return wrapper 260 | 261 | 262 | # Helper Functions 263 | 264 | def _to_windows_coordinates(x=0, y=0): 265 | display_width, display_height = size() 266 | 267 | # the +1 here prevents exactly mouse movements from sometimes ending up off by 1 pixel 268 | windows_x = (x * 65536) // display_width + 1 269 | windows_y = (y * 65536) // display_height + 1 270 | 271 | return windows_x, windows_y 272 | 273 | 274 | # position() works exactly the same as PyAutoGUI. I've duplicated it here so that moveRel() can use it to calculate 275 | # relative mouse positions. 276 | def position(x=None, y=None): 277 | cursor = POINT() 278 | ctypes.windll.user32.GetCursorPos(ctypes.byref(cursor)) 279 | return (x if x else cursor.x, y if y else cursor.y) 280 | 281 | 282 | # size() works exactly the same as PyAutoGUI. I've duplicated it here so that _to_windows_coordinates() can use it 283 | # to calculate the window size. 284 | def size(): 285 | return (ctypes.windll.user32.GetSystemMetrics(0), ctypes.windll.user32.GetSystemMetrics(1)) 286 | 287 | 288 | # Main Mouse Functions 289 | 290 | # Ignored parameters: duration, tween, logScreenshot 291 | @_genericPyDirectInputChecks 292 | def mouseDown(x=None, y=None, button=PRIMARY, duration=None, tween=None, logScreenshot=None, _pause=True): 293 | if not x is None or not y is None: 294 | moveTo(x, y) 295 | mouseData = 0 296 | ev = None 297 | if button == PRIMARY or button == LEFT: 298 | ev = MOUSEEVENTF_LEFTDOWN 299 | elif button == MIDDLE: 300 | ev = MOUSEEVENTF_MIDDLEDOWN 301 | elif button == SECONDARY or button == RIGHT: 302 | ev = MOUSEEVENTF_RIGHTDOWN 303 | elif button == WHEEL: 304 | ev = MOUSEEVENTF_WHEEL 305 | mouseData = MOUSEEVENTF_WHEELDOWN 306 | if not ev: 307 | raise ValueError('button arg to _click() must be one of "left", "middle", or "right", not %s' % button) 308 | 309 | extra = ctypes.c_ulong(0) 310 | ii_ = Input_I() 311 | ii_.mi = MouseInput(0, 0, mouseData, ev, 0, ctypes.pointer(extra)) 312 | x = Input(ctypes.c_ulong(0), ii_) 313 | SendInput(1, ctypes.pointer(x), ctypes.sizeof(x)) 314 | 315 | 316 | # Ignored parameters: duration, tween, logScreenshot 317 | @_genericPyDirectInputChecks 318 | def mouseUp(x=None, y=None, button=PRIMARY, duration=None, tween=None, logScreenshot=None, _pause=True): 319 | if not x is None or not y is None: 320 | moveTo(x, y) 321 | mouseData = 0 322 | ev = None 323 | if button == PRIMARY or button == LEFT: 324 | ev = MOUSEEVENTF_LEFTUP 325 | elif button == MIDDLE: 326 | ev = MOUSEEVENTF_MIDDLEUP 327 | elif button == SECONDARY or button == RIGHT: 328 | ev = MOUSEEVENTF_RIGHTUP 329 | elif button == WHEEL: 330 | ev = MOUSEEVENTF_WHEEL 331 | mouseData = MOUSEEVENTF_WHEELDOWN 332 | if not ev: 333 | raise ValueError('button arg to _click() must be one of "left", "middle", or "right", not %s' % button) 334 | 335 | extra = ctypes.c_ulong(0) 336 | ii_ = Input_I() 337 | ii_.mi = MouseInput(0, 0, mouseData, ev, 0, ctypes.pointer(extra)) 338 | x = Input(ctypes.c_ulong(0), ii_) 339 | SendInput(1, ctypes.pointer(x), ctypes.sizeof(x)) 340 | 341 | 342 | # Ignored parameters: duration, tween, logScreenshot 343 | @_genericPyDirectInputChecks 344 | def click(x=None, y=None, clicks=1, interval=0.0, button=PRIMARY, duration=None, tween=None, logScreenshot=None, 345 | _pause=True): 346 | if not x is None or not y is None: 347 | moveTo(x, y) 348 | 349 | ev = None 350 | if button == PRIMARY or button == LEFT: 351 | ev = MOUSEEVENTF_LEFTCLICK 352 | elif button == MIDDLE: 353 | ev = MOUSEEVENTF_MIDDLECLICK 354 | elif button == SECONDARY or button == RIGHT: 355 | ev = MOUSEEVENTF_RIGHTCLICK 356 | 357 | if not ev: 358 | raise ValueError('button arg to _click() must be one of "left", "middle", or "right", not %s' % button) 359 | 360 | for i in range(clicks): 361 | failSafeCheck() 362 | 363 | extra = ctypes.c_ulong(0) 364 | ii_ = Input_I() 365 | ii_.mi = MouseInput(0, 0, 0, ev, 0, ctypes.pointer(extra)) 366 | x = Input(ctypes.c_ulong(0), ii_) 367 | SendInput(1, ctypes.pointer(x), ctypes.sizeof(x)) 368 | 369 | time.sleep(interval) 370 | 371 | 372 | def leftClick(x=None, y=None, interval=0.0, duration=0.0, tween=None, logScreenshot=None, _pause=True): 373 | click(x, y, 1, interval, LEFT, duration, tween, logScreenshot, _pause) 374 | 375 | 376 | def rightClick(x=None, y=None, interval=0.0, duration=0.0, tween=None, logScreenshot=None, _pause=True): 377 | click(x, y, 1, interval, RIGHT, duration, tween, logScreenshot, _pause) 378 | 379 | 380 | def middleClick(x=None, y=None, interval=0.0, duration=0.0, tween=None, logScreenshot=None, _pause=True): 381 | click(x, y, 1, interval, MIDDLE, duration, tween, logScreenshot, _pause) 382 | 383 | 384 | def doubleClick(x=None, y=None, interval=0.0, button=LEFT, duration=0.0, tween=None, logScreenshot=None, _pause=True): 385 | click(x, y, 2, interval, button, duration, tween, logScreenshot, _pause) 386 | 387 | 388 | def tripleClick(x=None, y=None, interval=0.0, button=LEFT, duration=0.0, tween=None, logScreenshot=None, _pause=True): 389 | click(x, y, 3, interval, button, duration, tween, logScreenshot, _pause) 390 | 391 | 392 | # Missing feature: scroll functions 393 | 394 | 395 | # Ignored parameters: duration, tween, logScreenshot 396 | # PyAutoGUI uses ctypes.windll.user32.SetCursorPos(x, y) for this, which might still work fine in DirectInput 397 | # environments. 398 | # Use the relative flag to do a raw win32 api relative movement call (no MOUSEEVENTF_ABSOLUTE flag), which may be more 399 | # appropriate for some applications. Note that this may produce inexact results depending on mouse movement speed. 400 | @_genericPyDirectInputChecks 401 | def moveTo(x=None, y=None, duration=None, tween=None, logScreenshot=False, _pause=True, relative=False): 402 | if not relative: 403 | x, y = position(x, y) # if only x or y is provided, will keep the current position for the other axis 404 | x, y = _to_windows_coordinates(x, y) 405 | extra = ctypes.c_ulong(0) 406 | ii_ = Input_I() 407 | ii_.mi = MouseInput(x, y, 0, (MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE), 0, ctypes.pointer(extra)) 408 | command = Input(ctypes.c_ulong(0), ii_) 409 | SendInput(1, ctypes.pointer(command), ctypes.sizeof(command)) 410 | else: 411 | currentX, currentY = position() 412 | moveRel(x - currentX, y - currentY, relative=True) 413 | 414 | 415 | # Ignored parameters: duration, tween, logScreenshot 416 | # move() and moveRel() are equivalent. 417 | # Use the relative flag to do a raw win32 api relative movement call (no MOUSEEVENTF_ABSOLUTE flag), which may be more 418 | # appropriate for some applications. 419 | @_genericPyDirectInputChecks 420 | def moveRel(xOffset=None, yOffset=None, duration=None, tween=None, logScreenshot=False, _pause=True, relative=False): 421 | if not relative: 422 | x, y = position() 423 | if xOffset is None: 424 | xOffset = 0 425 | if yOffset is None: 426 | yOffset = 0 427 | moveTo(x + xOffset, y + yOffset) 428 | else: 429 | # When using MOUSEEVENTF_MOVE for relative movement the results may be inconsistent. 430 | # "Relative mouse motion is subject to the effects of the mouse speed and the two-mouse threshold values. A user 431 | # sets these three values with the Pointer Speed slider of the Control Panel's Mouse Properties sheet. You can 432 | # obtain and set these values using the SystemParametersInfo function." 433 | # https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-mouseinput 434 | # https://stackoverflow.com/questions/50601200/pyhon-directinput-mouse-relative-moving-act-not-as-expected 435 | extra = ctypes.c_ulong(0) 436 | ii_ = Input_I() 437 | ii_.mi = MouseInput(xOffset, yOffset, 0, MOUSEEVENTF_MOVE, 0, ctypes.pointer(extra)) 438 | command = Input(ctypes.c_ulong(0), ii_) 439 | SendInput(1, ctypes.pointer(command), ctypes.sizeof(command)) 440 | 441 | 442 | move = moveRel 443 | 444 | 445 | # Missing feature: drag functions 446 | 447 | 448 | # Keyboard Functions 449 | 450 | 451 | # Ignored parameters: logScreenshot 452 | # Missing feature: auto shift for special characters (ie. '!', '@', '#'...) 453 | @_genericPyDirectInputChecks 454 | def keyDown(key, logScreenshot=None, _pause=True): 455 | if not key in KEYBOARD_MAPPING or KEYBOARD_MAPPING[key] is None: 456 | return 457 | 458 | keybdFlags = KEYEVENTF_SCANCODE 459 | 460 | # Init event tracking 461 | insertedEvents = 0 462 | expectedEvents = 1 463 | 464 | # arrow keys need the extended key flag 465 | if key in ['up', 'left', 'down', 'right']: 466 | keybdFlags |= KEYEVENTF_EXTENDEDKEY 467 | # if numlock is on and an arrow key is being pressed, we need to send an additional scancode 468 | # https://stackoverflow.com/questions/14026496/sendinput-sends-num8-when-i-want-to-send-vk-up-how-come 469 | # https://handmade.network/wiki/2823-keyboard_inputs_-_scancodes,_raw_input,_text_input,_key_names 470 | if ctypes.windll.user32.GetKeyState(0x90): 471 | # We need to press two keys, so we expect to have inserted 2 events when done 472 | expectedEvents = 2 473 | hexKeyCode = 0xE0 474 | extra = ctypes.c_ulong(0) 475 | ii_ = Input_I() 476 | ii_.ki = KeyBdInput(0, hexKeyCode, KEYEVENTF_SCANCODE, 0, ctypes.pointer(extra)) 477 | x = Input(ctypes.c_ulong(1), ii_) 478 | 479 | # SendInput returns the number of event successfully inserted into input stream 480 | # https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-sendinput#return-value 481 | insertedEvents += SendInput(1, ctypes.pointer(x), ctypes.sizeof(x)) 482 | 483 | hexKeyCode = KEYBOARD_MAPPING[key] 484 | extra = ctypes.c_ulong(0) 485 | ii_ = Input_I() 486 | ii_.ki = KeyBdInput(0, hexKeyCode, keybdFlags, 0, ctypes.pointer(extra)) 487 | x = Input(ctypes.c_ulong(1), ii_) 488 | insertedEvents += SendInput(1, ctypes.pointer(x), ctypes.sizeof(x)) 489 | 490 | return insertedEvents == expectedEvents 491 | 492 | 493 | # Ignored parameters: logScreenshot 494 | # Missing feature: auto shift for special characters (ie. '!', '@', '#'...) 495 | @_genericPyDirectInputChecks 496 | def keyUp(key, logScreenshot=None, _pause=True): 497 | if not key in KEYBOARD_MAPPING or KEYBOARD_MAPPING[key] is None: 498 | return 499 | 500 | keybdFlags = KEYEVENTF_SCANCODE | KEYEVENTF_KEYUP 501 | 502 | # Init event tracking 503 | insertedEvents = 0 504 | expectedEvents = 1 505 | 506 | # arrow keys need the extended key flag 507 | if key in ['up', 'left', 'down', 'right']: 508 | keybdFlags |= KEYEVENTF_EXTENDEDKEY 509 | 510 | hexKeyCode = KEYBOARD_MAPPING[key] 511 | extra = ctypes.c_ulong(0) 512 | ii_ = Input_I() 513 | ii_.ki = KeyBdInput(0, hexKeyCode, keybdFlags, 0, ctypes.pointer(extra)) 514 | x = Input(ctypes.c_ulong(1), ii_) 515 | 516 | # SendInput returns the number of event successfully inserted into input stream 517 | # https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-sendinput#return-value 518 | insertedEvents += SendInput(1, ctypes.pointer(x), ctypes.sizeof(x)) 519 | 520 | # if numlock is on and an arrow key is being pressed, we need to send an additional scancode 521 | # https://stackoverflow.com/questions/14026496/sendinput-sends-num8-when-i-want-to-send-vk-up-how-come 522 | # https://handmade.network/wiki/2823-keyboard_inputs_-_scancodes,_raw_input,_text_input,_key_names 523 | if key in ['up', 'left', 'down', 'right'] and ctypes.windll.user32.GetKeyState(0x90): 524 | # We need to press two keys, so we expect to have inserted 2 events when done 525 | expectedEvents = 2 526 | hexKeyCode = 0xE0 527 | extra = ctypes.c_ulong(0) 528 | ii_ = Input_I() 529 | ii_.ki = KeyBdInput(0, hexKeyCode, KEYEVENTF_SCANCODE | KEYEVENTF_KEYUP, 0, ctypes.pointer(extra)) 530 | x = Input(ctypes.c_ulong(1), ii_) 531 | insertedEvents += SendInput(1, ctypes.pointer(x), ctypes.sizeof(x)) 532 | 533 | return insertedEvents == expectedEvents 534 | 535 | 536 | # Ignored parameters: logScreenshot 537 | # nearly identical to PyAutoGUI's implementation 538 | @_genericPyDirectInputChecks 539 | def press(keys, presses=1, interval=0.0, logScreenshot=None, _pause=True): 540 | if type(keys) == str: 541 | if len(keys) > 1: 542 | keys = keys.lower() 543 | keys = [keys] # If keys is 'enter', convert it to ['enter']. 544 | else: 545 | lowerKeys = [] 546 | for s in keys: 547 | if len(s) > 1: 548 | lowerKeys.append(s.lower()) 549 | else: 550 | lowerKeys.append(s) 551 | keys = lowerKeys 552 | interval = float(interval) 553 | 554 | # We need to press x keys y times, which comes out to x*y presses in total 555 | expectedPresses = presses * len(keys) 556 | completedPresses = 0 557 | 558 | for i in range(presses): 559 | for k in keys: 560 | failSafeCheck() 561 | downed = keyDown(k) 562 | upped = keyUp(k) 563 | # Count key press as complete if key was "downed" and "upped" successfully 564 | if downed and upped: 565 | completedPresses += 1 566 | 567 | time.sleep(interval) 568 | 569 | return completedPresses == expectedPresses 570 | 571 | 572 | # Ignored parameters: logScreenshot 573 | # nearly identical to PyAutoGUI's implementation 574 | @_genericPyDirectInputChecks 575 | def typewrite(message, interval=0.0, logScreenshot=None, _pause=True): 576 | interval = float(interval) 577 | for c in message: 578 | if len(c) > 1: 579 | c = c.lower() 580 | press(c, _pause=False) 581 | time.sleep(interval) 582 | failSafeCheck() 583 | 584 | 585 | write = typewrite 586 | -------------------------------------------------------------------------------- /PyDmGame/modular/vk_code.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | @Time : 2023/2/17 10:34 4 | @Auth : 大雄 5 | @File :vk_code.py 6 | @IDE :PyCharm 7 | @Email:3475228828@qq.com 8 | @func:功能 9 | """ 10 | import ctypes 11 | 12 | MapVirtualKey = ctypes.windll.user32.MapVirtualKeyW 13 | # MapVirtualKey Map Types 14 | MAPVK_VK_TO_CHAR = 2 15 | MAPVK_VK_TO_VSC = 0 16 | MAPVK_VSC_TO_VK = 1 17 | MAPVK_VSC_TO_VK_EX = 3 18 | # event 19 | VK_CODE = { 20 | 'backspace': 0x08, 21 | 'tab': 0x09, 22 | 'clear': 0x0C, 23 | 'enter': 0x0D, 24 | 'shift': 0x10, 25 | 'ctrl': 0x11, 26 | 'alt': 0x12, 27 | 'pause': 0x13, 28 | 'caps_lock': 0x14, 29 | 'esc': 0x1B, 30 | 'spacebar': 0x20, 31 | 'page_up': 0x21, 32 | 'page_down': 0x22, 33 | 'end': 0x23, 34 | 'home': 0x24, 35 | 'left_arrow': 0x25, 36 | 'up_arrow': 0x26, 37 | 'right_arrow': 0x27, 38 | 'down_arrow': 0x28, 39 | 'select': 0x29, 40 | 'print': 0x2A, 41 | 'execute': 0x2B, 42 | 'print_screen': 0x2C, 43 | 'ins': 0x2D, 44 | 'del': 0x2E, 45 | 'help': 0x2F, 46 | '0': 0x30, 47 | '1': 0x31, 48 | '2': 0x32, 49 | '3': 0x33, 50 | '4': 0x34, 51 | '5': 0x35, 52 | '6': 0x36, 53 | '7': 0x37, 54 | '8': 0x38, 55 | '9': 0x39, 56 | 'a': 0x41, 57 | 'b': 0x42, 58 | 'c': 0x43, 59 | 'd': 0x44, 60 | 'e': 0x45, 61 | 'f': 0x46, 62 | 'g': 0x47, 63 | 'h': 0x48, 64 | 'i': 0x49, 65 | 'j': 0x4A, 66 | 'k': 0x4B, 67 | 'l': 0x4C, 68 | 'm': 0x4D, 69 | 'n': 0x4E, 70 | 'o': 0x4F, 71 | 'p': 0x50, 72 | 'q': 0x51, 73 | 'r': 0x52, 74 | 's': 0x53, 75 | 't': 0x54, 76 | 'u': 0x55, 77 | 'v': 0x56, 78 | 'w': 0x57, 79 | 'x': 0x58, 80 | 'y': 0x59, 81 | 'z': 0x5A, 82 | 'numpad_0': 0x60, 83 | 'numpad_1': 0x61, 84 | 'numpad_2': 0x62, 85 | 'numpad_3': 0x63, 86 | 'numpad_4': 0x64, 87 | 'numpad_5': 0x65, 88 | 'numpad_6': 0x66, 89 | 'numpad_7': 0x67, 90 | 'numpad_8': 0x68, 91 | 'numpad_9': 0x69, 92 | 'multiply_key': 0x6A, 93 | 'add_key': 0x6B, 94 | 'separator_key': 0x6C, 95 | 'subtract_key': 0x6D, 96 | 'decimal_key': 0x6E, 97 | 'divide_key': 0x6F, 98 | 'f1': 0x70, 99 | 'f2': 0x71, 100 | 'f3': 0x72, 101 | 'f4': 0x73, 102 | 'f5': 0x74, 103 | 'f6': 0x75, 104 | 'f7': 0x76, 105 | 'f8': 0x77, 106 | 'f9': 0x78, 107 | 'f10': 0x79, 108 | 'f11': 0x7A, 109 | 'f12': 0x7B, 110 | 'f13': 0x7C, 111 | 'f14': 0x7D, 112 | 'f15': 0x7E, 113 | 'f16': 0x7F, 114 | 'f17': 0x80, 115 | 'f18': 0x81, 116 | 'f19': 0x82, 117 | 'f20': 0x83, 118 | 'f21': 0x84, 119 | 'f22': 0x85, 120 | 'f23': 0x86, 121 | 'f24': 0x87, 122 | 'num_lock': 0x90, 123 | 'scroll_lock': 0x91, 124 | 'left_shift': 0xA0, 125 | 'right_shift ': 0xA1, 126 | 'left_control': 0xA2, 127 | 'right_control': 0xA3, 128 | 'left_menu': 0xA4, 129 | 'right_menu': 0xA5, 130 | 'browser_back': 0xA6, 131 | 'browser_forward': 0xA7, 132 | 'browser_refresh': 0xA8, 133 | 'browser_stop': 0xA9, 134 | 'browser_search': 0xAA, 135 | 'browser_favorites': 0xAB, 136 | 'browser_start_and_home': 0xAC, 137 | 'volume_mute': 0xAD, 138 | 'volume_Down': 0xAE, 139 | 'volume_up': 0xAF, 140 | 'next_track': 0xB0, 141 | 'previous_track': 0xB1, 142 | 'stop_media': 0xB2, 143 | 'play/pause_media': 0xB3, 144 | 'start_mail': 0xB4, 145 | 'select_media': 0xB5, 146 | 'start_application_1': 0xB6, 147 | 'start_application_2': 0xB7, 148 | 'attn_key': 0xF6, 149 | 'crsel_key': 0xF7, 150 | 'exsel_key': 0xF8, 151 | 'play_key': 0xFA, 152 | 'zoom_key': 0xFB, 153 | 'clear_key': 0xFE, 154 | '+': 0xBB, 155 | ',': 0xBC, 156 | '-': 0xBD, 157 | '.': 0xBE, 158 | '/': 0xBF, 159 | '`': 0xC0, 160 | ';': 0xBA, 161 | '[': 0xDB, 162 | '\\': 0xDC, 163 | ']': 0xDD, 164 | "'": 0xDE} 165 | # SendInput 166 | VK_CODE2 = { 167 | 'escape': 0x01, 168 | 'esc': 0x01, 169 | 'f1': 0x3B, 170 | 'f2': 0x3C, 171 | 'f3': 0x3D, 172 | 'f4': 0x3E, 173 | 'f5': 0x3F, 174 | 'f6': 0x40, 175 | 'f7': 0x41, 176 | 'f8': 0x42, 177 | 'f9': 0x43, 178 | 'f10': 0x44, 179 | 'f11': 0x57, 180 | 'f12': 0x58, 181 | 'printscreen': 0xB7, 182 | 'prntscrn': 0xB7, 183 | 'prtsc': 0xB7, 184 | 'prtscr': 0xB7, 185 | 'scrolllock': 0x46, 186 | 'pause': 0xC5, 187 | '`': 0x29, 188 | '1': 0x02, 189 | '2': 0x03, 190 | '3': 0x04, 191 | '4': 0x05, 192 | '5': 0x06, 193 | '6': 0x07, 194 | '7': 0x08, 195 | '8': 0x09, 196 | '9': 0x0A, 197 | '0': 0x0B, 198 | '-': 0x0C, 199 | '=': 0x0D, 200 | 'backspace': 0x0E, 201 | 'insert': 0xD2 + 1024, 202 | 'home': 0xC7 + 1024, 203 | 'pageup': 0xC9 + 1024, 204 | 'pagedown': 0xD1 + 1024, 205 | # numpad 206 | 'numlock': 0x45, 207 | 'divide': 0xB5 + 1024, 208 | 'multiply': 0x37, 209 | 'subtract': 0x4A, 210 | 'add': 0x4E, 211 | 'decimal': 0x53, 212 | 'numpadenter': 0x9C + 1024, 213 | 'numpad1': 0x4F, 214 | 'numpad2': 0x50, 215 | 'numpad3': 0x51, 216 | 'numpad4': 0x4B, 217 | 'numpad5': 0x4C, 218 | 'numpad6': 0x4D, 219 | 'numpad7': 0x47, 220 | 'numpad8': 0x48, 221 | 'numpad9': 0x49, 222 | 'numpad0': 0x52, 223 | # end numpad 224 | 'tab': 0x0F, 225 | 'q': 0x10, 226 | 'w': 0x11, 227 | 'e': 0x12, 228 | 'r': 0x13, 229 | 't': 0x14, 230 | 'y': 0x15, 231 | 'u': 0x16, 232 | 'i': 0x17, 233 | 'o': 0x18, 234 | 'p': 0x19, 235 | '[': 0x1A, 236 | ']': 0x1B, 237 | '\\': 0x2B, 238 | 'del': 0xD3 + 1024, 239 | 'delete': 0xD3 + 1024, 240 | 'end': 0xCF + 1024, 241 | 'capslock': 0x3A, 242 | 'a': 0x1E, 243 | 's': 0x1F, 244 | 'd': 0x20, 245 | 'f': 0x21, 246 | 'g': 0x22, 247 | 'h': 0x23, 248 | 'j': 0x24, 249 | 'k': 0x25, 250 | 'l': 0x26, 251 | ';': 0x27, 252 | "'": 0x28, 253 | 'enter': 0x1C, 254 | 'return': 0x1C, 255 | 'shift': 0x2A, 256 | 'shiftleft': 0x2A, 257 | 'z': 0x2C, 258 | 'x': 0x2D, 259 | 'c': 0x2E, 260 | 'v': 0x2F, 261 | 'b': 0x30, 262 | 'n': 0x31, 263 | 'm': 0x32, 264 | ',': 0x33, 265 | '.': 0x34, 266 | '/': 0x35, 267 | 'shiftright': 0x36, 268 | 'ctrl': 0x1D, 269 | 'ctrlleft': 0x1D, 270 | 'win': 0xDB + 1024, 271 | 'winleft': 0xDB + 1024, 272 | 'alt': 0x38, 273 | 'altleft': 0x38, 274 | ' ': 0x39, 275 | 'space': 0x39, 276 | 'altright': 0xB8 + 1024, 277 | 'winright': 0xDC + 1024, 278 | 'apps': 0xDD + 1024, 279 | 'ctrlright': 0x9D + 1024, 280 | # arrow key scancodes can be different depending on the hardware, 281 | # so I think the best solution is to look it up based on the virtual key 282 | # https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-mapvirtualkeya?redirectedfrom=MSDN 283 | 'up': MapVirtualKey(0x26, MAPVK_VK_TO_VSC), 284 | 'left': MapVirtualKey(0x25, MAPVK_VK_TO_VSC), 285 | 'down': MapVirtualKey(0x28, MAPVK_VK_TO_VSC), 286 | 'right': MapVirtualKey(0x27, MAPVK_VK_TO_VSC), 287 | } 288 | def vk_to_char(key_str:str,type_=VK_CODE): 289 | return type_[key_str.lower()] 290 | 291 | 292 | -------------------------------------------------------------------------------- /PyDmGame/modular/window.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | @Time : 2023/2/17 14:57 4 | @Auth : 大雄 5 | @File :window.py 6 | @IDE :PyCharm 7 | @Email:3475228828@qq.com 8 | @func:功能 9 | """ 10 | import ctypes.wintypes 11 | import ctypes 12 | 13 | 14 | class Window(): 15 | winKernel32 = ctypes.windll.kernel32 16 | winuser32 = ctypes.windll.LoadLibrary('user32.dll') 17 | wingdi32 = ctypes.windll.LoadLibrary('gdi32.dll') 18 | winntdll = ctypes.windll.LoadLibrary('ntdll.dll') 19 | winwinmm = ctypes.windll.LoadLibrary('winmm.dll') 20 | 21 | @staticmethod 22 | def ClientToScreen(hwnd, x, y) -> tuple: 23 | point = ctypes.wintypes.POINT() 24 | point.x = x 25 | point.y = y 26 | is_ok: bool = Window.winuser32.ClientToScreen(hwnd, ctypes.byref(point)) 27 | if not is_ok: 28 | raise Exception('call ClientToScreen failed') 29 | return (point.x, point.y) 30 | 31 | @staticmethod 32 | def EnumProcess(name) -> str: 33 | '''需安装psutil''' 34 | try: 35 | import psutil 36 | except: 37 | raise Exception("called EnumProcess failed:psutil not install") 38 | ret = [] 39 | for proc in psutil.process_iter(): 40 | pinfo = proc.as_dict(attrs=['pid', 'name', 'create_time']) 41 | pname = '' 42 | if pinfo['name'] != None: 43 | pname = pinfo['name'].upper() 44 | if name.upper() in pname: 45 | ret.append((pinfo['pid'], pinfo['create_time'])) 46 | if len(ret) == 0: 47 | raise Exception("called EnumProcess failed:process not found") 48 | ret = sorted(ret, key=lambda x: x[1]) 49 | ret = [str(i[0]) for i in ret] 50 | return ','.join(ret) 51 | 52 | @staticmethod 53 | def FindWindow(class_, title) -> int: 54 | return Window.FindWindowEx(None, class_, title) 55 | 56 | @staticmethod 57 | def FindWindowEx(parent, class_, title) -> int: 58 | retArr = [] 59 | 60 | def mycallback(hwnd, extra) -> bool: 61 | if not Window.winuser32.IsWindowVisible(hwnd): 62 | return True 63 | if class_.upper() not in Window.GetWindowClass(hwnd).upper(): 64 | return True 65 | if title.upper() not in Window.GetWindowTitle(hwnd).upper(): 66 | return True 67 | retArr.append(hwnd) 68 | return False 69 | 70 | CMPFUNC = ctypes.WINFUNCTYPE(ctypes.wintypes.BOOL, ctypes.wintypes.HWND, ctypes.wintypes.LPARAM) 71 | Window.winuser32.EnumChildWindows(parent, CMPFUNC(mycallback), 0) 72 | if len(retArr) == 0: 73 | raise Exception('call FindWindow failed:Not Found Window') 74 | return retArr[0] 75 | 76 | @staticmethod 77 | def FindWindowByProcessId(process_id, class_, title) -> int: 78 | retArr = [] 79 | 80 | def mycallback(hwnd, extra) -> bool: 81 | if not Window.winuser32.IsWindowVisible(hwnd): 82 | return True 83 | lProcessId = ctypes.wintypes.LONG() 84 | Window.winuser32.GetWindowThreadProcessId(hwnd, ctypes.byref(lProcessId)) 85 | if process_id != lProcessId.value: 86 | return True 87 | if class_.upper() not in Window.GetWindowClass(hwnd).upper(): 88 | return True 89 | if title.upper() not in Window.GetWindowTitle(hwnd).upper(): 90 | return True 91 | retArr.append(hwnd) 92 | return False 93 | 94 | CMPFUNC = ctypes.WINFUNCTYPE(ctypes.wintypes.BOOL, ctypes.wintypes.HWND, ctypes.wintypes.LPARAM) 95 | Window.winuser32.EnumChildWindows(None, CMPFUNC(mycallback), 0) 96 | if len(retArr) == 0: 97 | raise Exception('call FindWindowByProcessId failed:Not Found Window') 98 | return retArr[0] 99 | 100 | @staticmethod 101 | def FindWindowByProcess(process_name, class_, title) -> int: 102 | '''需安装psutil''' 103 | try: 104 | import psutil 105 | except: 106 | raise Exception("called FindWindowByProcess failed:psutil not install") 107 | retArr = [] 108 | 109 | def mycallback(hwnd, extra) -> bool: 110 | if not Window.winuser32.IsWindowVisible(hwnd): 111 | return True 112 | lProcessId = Window.GetWindowProcessId(hwnd) 113 | lProcessName = None 114 | try: # 有些进程是无法打开的 115 | lProcessName = psutil.Process(lProcessId).name() 116 | except: 117 | return True 118 | if (lProcessName == None): lProcessName = "" 119 | if process_name.upper() not in lProcessName.upper(): 120 | return True 121 | if class_.upper() not in Window.GetWindowClass(hwnd).upper(): 122 | return True 123 | if title.upper() not in Window.GetWindowTitle(hwnd).upper(): 124 | return True 125 | retArr.append(hwnd) 126 | return False 127 | 128 | CMPFUNC = ctypes.WINFUNCTYPE(ctypes.wintypes.BOOL, ctypes.wintypes.HWND, ctypes.wintypes.LPARAM) 129 | Window.winuser32.EnumChildWindows(None, CMPFUNC(mycallback), 0) 130 | if len(retArr) == 0: 131 | raise Exception('call FindWindowByProcessId failed:Not Found Window') 132 | return retArr[0] 133 | 134 | @staticmethod 135 | def GetProcessInfo(pid): 136 | '''需安装psutil''' 137 | try: 138 | import psutil 139 | except: 140 | raise Exception("called GetProcessInfo failed:psutil not install") 141 | p = psutil.Process(pid) 142 | return p.name() + '|' + p.exe() + '|' + str(p.cpu_percent()) + '|' + str(p.memory_info().rss) 143 | 144 | @staticmethod 145 | def GetWindowClass(hwnd) -> str: 146 | classStr = ctypes.create_string_buffer(''.encode(), 1000) 147 | is_ok: bool = Window.winuser32.GetClassNameA(hwnd, classStr, 1000) 148 | if (not is_ok) and Window.winKernel32.GetLastError() != 0: 149 | raise Exception("call GetWindowClass failed") 150 | return ctypes.string_at(classStr).decode('GB2312') 151 | 152 | @staticmethod 153 | def GetWindowProcessId(hwnd) -> int: 154 | lProcessId = ctypes.wintypes.LONG() 155 | is_ok: bool = Window.winuser32.GetWindowThreadProcessId(hwnd, ctypes.byref(lProcessId)) 156 | if (not is_ok) and Window.winKernel32.GetLastError() != 0: 157 | raise Exception("call GetWindowProcessId failed") 158 | return lProcessId.value 159 | 160 | @staticmethod 161 | def GetWindowTitle(hwnd) -> str: 162 | titleStr = ctypes.create_string_buffer(''.encode(), 1000) 163 | is_ok: bool = Window.winuser32.GetWindowTextA(hwnd, titleStr, 1000) 164 | if (not is_ok) and Window.winKernel32.GetLastError() != 0: 165 | raise Exception("call GetWindowTitle failed") 166 | return ctypes.string_at(titleStr).decode('GB2312') 167 | 168 | @staticmethod 169 | def GetWindowProcessPath(hwnd) -> str: 170 | '''需安装psutil''' 171 | try: 172 | import psutil 173 | except: 174 | raise Exception("called GetWindowProcessPath failed:psutil not install") 175 | process_id = Window.GetWindowProcessId(hwnd) 176 | p = psutil.Process(process_id) 177 | return p.exe() 178 | 179 | @staticmethod 180 | def GetSpecialWindow(flag) -> int: 181 | if flag == 0: 182 | return Window.winuser32.GetDesktopWindow() 183 | elif flag == 1: 184 | return Window.winuser32.FindWindowW("Shell_TrayWnd", 0) 185 | else: 186 | raise Exception('call GetSpecialWindow Failed') 187 | 188 | @staticmethod 189 | def GetForegroundWindow() -> int: 190 | is_ok: int = Window.winuser32.GetForegroundWindow() 191 | if not is_ok: 192 | raise Exception('call GetForegroundWindow Failed') 193 | return is_ok 194 | 195 | @staticmethod 196 | def GetForegroundFocus() -> int: 197 | wnd: int = Window.GetForegroundWindow() 198 | if not wnd: 199 | raise Exception('call GetForegroundFocus Failed') 200 | SelfThreadId = Window.winKernel32.GetCurrentThreadId() 201 | ForeThreadId = Window.winuser32.GetWindowThreadProcessId(wnd, 0) 202 | Window.winuser32.AttachThreadInput(ForeThreadId, SelfThreadId, True) 203 | wnd = Window.winuser32.GetFocus() 204 | Window.winuser32.AttachThreadInput(ForeThreadId, SelfThreadId, False) 205 | if not wnd: 206 | raise Exception('call GetForegroundFocus Failed') 207 | return wnd 208 | 209 | @staticmethod 210 | def GetMousePointWindow() -> int: 211 | class POINT(ctypes.Structure): 212 | _fields_ = [ 213 | ("x", ctypes.wintypes.LONG), 214 | ("y", ctypes.wintypes.LONG) 215 | ] 216 | 217 | point = POINT() 218 | Window.winuser32.GetCursorPos(ctypes.byref(point)) 219 | hwnd = Window.winuser32.WindowFromPoint(point) 220 | if not hwnd: 221 | raise Exception('call GetMousePointWindow failed') 222 | return hwnd 223 | 224 | @staticmethod 225 | def GetPointWindow(x, y) -> int: 226 | class POINT(ctypes.Structure): 227 | _fields_ = [ 228 | ("x", ctypes.wintypes.LONG), 229 | ("y", ctypes.wintypes.LONG) 230 | ] 231 | 232 | point = POINT() 233 | point.x = x 234 | point.y = y 235 | hwnd = Window.winuser32.WindowFromPoint(point) 236 | if not hwnd: 237 | raise Exception('call GetMousePointWindow failed') 238 | return hwnd 239 | 240 | @staticmethod 241 | def GetWindow(hwnd, flag) -> int: 242 | rethwnd = None 243 | if flag == 0: 244 | rethwnd = Window.winuser32.GetParent(hwnd) 245 | elif flag == 1: 246 | rethwnd = Window.winuser32.GetWindow(hwnd, 5) 247 | # def mycallback(hwnd,extra) -> bool: 248 | # nonlocal rethwnd 249 | # rethwnd = hwnd 250 | # return False 251 | # CMPFUNC = ctypes.WINFUNCTYPE(ctypes.wintypes.BOOL,ctypes.wintypes.HWND, ctypes.wintypes.LPARAM) 252 | # Window.winuser32.EnumChildWindows(hwnd,CMPFUNC(mycallback),0) 253 | elif flag == 2: 254 | rethwnd = Window.winuser32.GetWindow(hwnd, 0) 255 | elif flag == 3: 256 | rethwnd = Window.winuser32.GetWindow(hwnd, 1) 257 | elif flag == 4: 258 | rethwnd = Window.winuser32.GetWindow(hwnd, 2) 259 | elif flag == 5: 260 | rethwnd = Window.winuser32.GetWindow(hwnd, 3) 261 | elif flag == 6: 262 | rethwnd = Window.winuser32.GetWindow(hwnd, 4) 263 | if not rethwnd: 264 | rethwnd = Window.winuser32.GetParent(hwnd) 265 | if not rethwnd: 266 | rethwnd = hwnd 267 | elif flag == 7: 268 | rethwnd = Window.winuser32.GetTopWindow(hwnd) 269 | # top = hwnd 270 | # while True: 271 | # hd = Window.winuser32.GetParent(top) 272 | # if not hd: 273 | # rethwnd = top 274 | # break 275 | # top = hd 276 | if not rethwnd: 277 | raise Exception('call GetWindow failed') 278 | return rethwnd 279 | 280 | @staticmethod 281 | def GetWindowRect(hwnd) -> tuple: 282 | rect = ctypes.wintypes.RECT() 283 | is_ok: bool = Window.winuser32.GetWindowRect(hwnd, ctypes.byref(rect)) 284 | if not is_ok: 285 | raise Exception('call GetWindowRect failed') 286 | return (rect.left, rect.top, rect.right, rect.bottom) 287 | 288 | @staticmethod 289 | def GetWindowState(hwnd, flag) -> bool: 290 | if flag == 0: 291 | return Window.winuser32.IsWindow(hwnd) == 1 292 | elif flag == 1: 293 | return Window.GetForegroundWindow() == hwnd 294 | elif flag == 2: 295 | return Window.winuser32.IsWindowVisible(hwnd) == 1 296 | elif flag == 3: 297 | return Window.winuser32.IsIconic(hwnd) == 1 298 | elif flag == 4: 299 | return Window.winuser32.IsZoomed(hwnd) == 1 300 | elif flag == 5: 301 | GWL_EXSTYLE = -20 302 | WS_EX_TOPMOST = 0x00000008 303 | if (Window.winuser32.GetWindowLongA(hwnd, GWL_EXSTYLE) & WS_EX_TOPMOST): 304 | return True 305 | else: 306 | return False 307 | elif flag == 6 or flag == 8: 308 | return Window.winuser32.IsHungAppWindow(hwnd) == 1 309 | elif flag == 7: 310 | return Window.winuser32.IsWindowEnabled(hwnd) == 1 311 | elif flag == 9: 312 | def Is64Bit() -> bool: 313 | class _SYSTEM_INFO(ctypes.Structure): 314 | _fields_ = [ 315 | ("dwOemId", ctypes.wintypes.DWORD), 316 | ("dwProcessorType", ctypes.wintypes.DWORD), 317 | ("lpMinimumApplicationAddress", ctypes.wintypes.LPVOID), 318 | ("lpMaximumApplicationAddress", ctypes.wintypes.LPVOID), 319 | ("dwActiveProcessorMask", ctypes.wintypes.LPVOID), 320 | ("dwNumberOfProcessors", ctypes.wintypes.DWORD), 321 | ("dwProcessorType", ctypes.wintypes.DWORD), 322 | ("dwAllocationGranularity", ctypes.wintypes.DWORD), 323 | ("wProcessorLevel", ctypes.wintypes.WORD), 324 | ("wProcessorRevision", ctypes.wintypes.WORD), 325 | ] 326 | 327 | lpSystemInfo = _SYSTEM_INFO() 328 | Window.winKernel32.GetNativeSystemInfo(ctypes.byref(lpSystemInfo)) 329 | PROCESSOR_ARCHITECTURE_IA64 = 6 330 | PROCESSOR_ARCHITECTURE_AMD64 = 9 331 | if lpSystemInfo.dwOemId in [PROCESSOR_ARCHITECTURE_IA64, PROCESSOR_ARCHITECTURE_AMD64]: 332 | return True 333 | else: 334 | return False 335 | 336 | if not Is64Bit(): 337 | return False 338 | isWow64Process = ctypes.wintypes.BOOL(True) 339 | processId = Window.GetWindowProcessId(hwnd) 340 | PROCESS_QUERY_INFORMATION = 0x0400 341 | hProcess = Window.winKernel32.OpenProcess(PROCESS_QUERY_INFORMATION, False, processId) 342 | if not hProcess: 343 | raise Exception('call GetWindowState failed:OpenProcess') 344 | is_ok: bool = Window.winKernel32.IsWow64Process(hProcess, ctypes.pointer(isWow64Process)) 345 | Window.winKernel32.CloseHandle(hProcess) 346 | if not is_ok: 347 | raise Exception('call GetWindowState failed:IsWow64Process') 348 | if isWow64Process.value: 349 | return False 350 | return True 351 | 352 | @staticmethod 353 | def GetClientSize(hwnd) -> tuple: 354 | rect = ctypes.wintypes.RECT() 355 | is_ok: bool = Window.winuser32.GetClientRect(hwnd, ctypes.byref(rect)) 356 | if not is_ok: 357 | raise Exception('call GetClientRect failed') 358 | return (rect.right, rect.bottom) 359 | 360 | @staticmethod 361 | def ScreenToClient(hwnd, x, y) -> tuple: 362 | point = ctypes.wintypes.POINT() 363 | point.x = x 364 | point.y = y 365 | is_ok: bool = Window.winuser32.ScreenToClient(hwnd, ctypes.byref(point)) 366 | if not is_ok: 367 | raise Exception('call ScreenToClient failed') 368 | return (point.x, point.y) 369 | 370 | @staticmethod 371 | def GetClientRect(hwnd) -> tuple: 372 | x1, y1 = Window.ClientToScreen(hwnd, 0, 0) 373 | x2, y2 = Window.GetClientSize(hwnd) 374 | x2, y2 = Window.ClientToScreen(hwnd, x2, y2) 375 | return (x1, y1, x2, y2) 376 | 377 | @staticmethod 378 | def MoveWindow(hwnd, x, y) -> None: 379 | x1, y1, x2, y2 = Window.GetWindowRect(hwnd) 380 | is_ok: bool = Window.winuser32.MoveWindow(hwnd, 0, 0, x2 - x1, y2 - y1, True) 381 | if not is_ok: 382 | raise Exception('call MoveWindow failed') 383 | 384 | @staticmethod 385 | def SetWindowSize(hwnd, width, height) -> None: 386 | x1, y1, x2, y2 = Window.GetWindowRect(hwnd) 387 | is_ok: bool = Window.winuser32.MoveWindow(hwnd, x1, y1, width, height, True) 388 | if not is_ok: 389 | raise Exception('call SetWindowSize failed') 390 | 391 | @staticmethod 392 | def SetWindowText(hwnd, title): 393 | is_ok: bool = Window.winuser32.SetWindowTextW(hwnd, title) 394 | if not is_ok: 395 | raise Exception('call SetWindowText failed') 396 | 397 | @staticmethod 398 | def SetWindowTransparent(hwnd, trans): 399 | is_ok: bool = Window.winuser32.SetLayeredWindowAttributes(hwnd, 0, trans, 2) 400 | if not is_ok: 401 | raise Exception('call SetWindowTransparent failed') 402 | 403 | @staticmethod 404 | def SetClientSize(hwnd, width, height) -> None: 405 | wx1, wy1, wx2, wy2 = Window.GetWindowRect(hwnd) 406 | w, h = Window.GetClientSize(hwnd) 407 | Window.SetWindowSize(hwnd, wx2 - wx1 + width - w, wy2 - wy1 + height - h) 408 | 409 | @staticmethod 410 | def SendPaste(hwnd) -> None: 411 | class WINDOWPLACEMENT(ctypes.Structure): 412 | _fields_ = [ 413 | ("length", ctypes.wintypes.UINT), 414 | ("flags", ctypes.wintypes.UINT), 415 | ("showCmd", ctypes.wintypes.UINT), 416 | ("ptMinPosition", ctypes.wintypes.POINT), 417 | ("ptMaxPosition", ctypes.wintypes.POINT), 418 | ("rcNormalPosition", ctypes.wintypes.RECT) 419 | ] 420 | 421 | if not Window.GetWindowState(hwnd, 0): 422 | raise Exception('call SendPaste failed:window not exist') 423 | wtp = WINDOWPLACEMENT() 424 | Window.winuser32.GetWindowPlacement(hwnd, ctypes.byref(wtp)) 425 | if (wtp.showCmd != 1): # 没有最小化 426 | if wtp.showCmd == 2: # 被最小化了 427 | wtp.showCmd = 9 428 | Window.winuser32.SetWindowPlacement(hwnd, ctypes.byref(wtp)) 429 | # 正常情况下wtp.showCmd为3,表示前台 430 | Window.winuser32.SetForegroundWindow(hwnd) 431 | Window.winuser32.BringWindowToTop(hwnd) 432 | Window.winuser32.keybd_event(0x11, 0, 0x0001, 0); 433 | Window.winuser32.keybd_event(86, 0, 0x0001, 0); 434 | Window.winuser32.keybd_event(86, 0, 0x0001 | 0x0002, 0) 435 | Window.winuser32.keybd_event(0x11, 0, 0x0001 | 0x0002, 0) 436 | 437 | @staticmethod 438 | def SetWindowState(hwnd, flag): 439 | class WINDOWPLACEMENT(ctypes.Structure): 440 | _fields_ = [ 441 | ("length", ctypes.wintypes.UINT), 442 | ("flags", ctypes.wintypes.UINT), 443 | ("showCmd", ctypes.wintypes.UINT), 444 | ("ptMinPosition", ctypes.wintypes.POINT), 445 | ("ptMaxPosition", ctypes.wintypes.POINT), 446 | ("rcNormalPosition", ctypes.wintypes.RECT) 447 | ] 448 | 449 | is_ok = False 450 | if flag == 0: 451 | is_ok = Window.winuser32.SendMessageW(hwnd, 0x0010, 0, 0) 452 | elif flag == 1: 453 | is_ok = Window.winuser32.SetActiveWindow(hwnd) 454 | elif flag == 2 or flag == 3: 455 | is_ok = Window.winuser32.ShowWindow(hwnd, 2) 456 | elif flag == 4: 457 | is_ok = Window.winuser32.ShowWindow(hwnd, 3) 458 | is_ok = Window.winuser32.SetActiveWindow(hwnd) 459 | elif flag == 5: 460 | wtp = WINDOWPLACEMENT() 461 | is_ok = Window.winuser32.GetWindowPlacement(hwnd, ctypes.byref(wtp)) 462 | wtp.showCmd = 9 463 | is_ok = Window.winuser32.SetWindowPlacement(hwnd, ctypes.byref(wtp)) 464 | elif flag == 6: 465 | wtp = WINDOWPLACEMENT() 466 | is_ok = Window.winuser32.GetWindowPlacement(hwnd, ctypes.byref(wtp)) 467 | wtp.showCmd = 0 468 | is_ok = Window.winuser32.SetWindowPlacement(hwnd, ctypes.byref(wtp)) 469 | elif flag == 7: 470 | wtp = WINDOWPLACEMENT() 471 | is_ok = Window.winuser32.GetWindowPlacement(hwnd, ctypes.byref(wtp)) 472 | wtp.showCmd = 5 473 | is_ok = Window.winuser32.SetWindowPlacement(hwnd, ctypes.byref(wtp)) 474 | elif flag == 8: 475 | is_ok = Window.winuser32.SetWindowPos(hwnd, -1, 0, 0, 0, 0, 3) 476 | elif flag == 9: 477 | is_ok = Window.winuser32.SetWindowPos(hwnd, -2, 0, 0, 0, 0, 3) 478 | elif flag in [10, 11, 12]: 479 | pass 480 | elif flag == 13: 481 | pid = Window.GetWindowProcessId(hwnd) 482 | hProcessHandle = Window.winKernel32.OpenProcess(1, False, pid) 483 | is_ok = Window.winKernel32.TerminateProcess(hProcessHandle, 4) 484 | Window.winKernel32.CloseHandle(hProcessHandle) 485 | elif flag == 14: 486 | is_ok = Window.winuser32.FlashWindow(hwnd, True) 487 | elif flag == 15: 488 | hCurWnd = Window.winuser32.GetForegroundWindow() 489 | dwMyID = Window.winKernel32.GetCurrentThreadId() 490 | dwCurID = Window.winuser32.GetWindowThreadProcessId(hCurWnd, 0) 491 | Window.winuser32.AttachThreadInput(dwCurID, dwMyID, True) 492 | is_ok = Window.winuser32.SetFocus(hwnd) 493 | Window.winuser32.AttachThreadInput(dwCurID, dwMyID, False) 494 | if not is_ok: 495 | raise Exception('窗口设置失败,请检查句柄或者参数') 496 | 497 | @staticmethod 498 | def EnumWindow(parent_, title, class_name, filter): 499 | if not parent_: 500 | parent_ = Window.GetSpecialWindow(0) 501 | martchVec = [] 502 | 503 | def mycallback(hwnd, extra) -> bool: 504 | wtitle = None 505 | wclass = None 506 | try: 507 | wtitle = Window.GetWindowTitle(hwnd) 508 | wclass = Window.GetWindowClass(hwnd) 509 | except: 510 | return True 511 | if filter & 1 == 1: 512 | if title.upper() not in wtitle.upper(): 513 | return True 514 | if ((filter & 2) >> 1) == 1: 515 | if class_name.upper() not in wclass.upper(): 516 | return True 517 | if ((filter & 4) >> 2) == 1: 518 | try: 519 | if Window.GetWindow(hwnd, 0) != parent_: 520 | return True 521 | except: 522 | return True 523 | if ((filter & 8) >> 3) == 1: 524 | if not (Window.winuser32.GetParent(hwnd) == 0): 525 | return True 526 | if Window.winKernel32.GetLastError() != 0: 527 | return True 528 | if ((filter & 16) >> 4) == 1: 529 | if Window.GetWindowState(2) == False: 530 | return True 531 | martchVec.append(hwnd) 532 | return True 533 | 534 | CMPFUNC = ctypes.WINFUNCTYPE(ctypes.wintypes.BOOL, ctypes.wintypes.HWND, ctypes.wintypes.LPARAM) 535 | Window.winuser32.EnumChildWindows(parent_, CMPFUNC(mycallback), 0) 536 | if len(martchVec) == 0: 537 | raise Exception('call EnumWindow failed:not found any window') 538 | return ','.join([str(i) for i in martchVec]) 539 | -------------------------------------------------------------------------------- /PyDmGame/tests/单点比色.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | @Time : 2023/2/12 12:28 4 | @Auth : 大雄 5 | @File :单点比色.py 6 | @IDE :PyCharm 7 | @Email:3475228828@qq.com 8 | @func:功能 9 | """ 10 | 11 | import PyDmGame 12 | 13 | dm = PyDmGame.DM() 14 | dm.BindWindow(68150,"gdi","windows","windows",0) 15 | result = dm.CmpColor(199,175, "f4c51f", 0.9) 16 | print(result) -------------------------------------------------------------------------------- /PyDmGame/tests/截图测试.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | @Time : 2023/2/12 16:48 4 | @Auth : 大雄 5 | @File :截图测试.py 6 | @IDE :PyCharm 7 | @Email:3475228828@qq.com 8 | @func:功能 9 | """ 10 | import time 11 | 12 | import cv2 13 | from PyDmGame import * 14 | 15 | def normal_capture(hwnd): 16 | dm = DM() 17 | dm.BindWindow(hwnd,"normal","normal","windows",0) 18 | # 置顶窗口 19 | dm.SetWindowState(hwnd,7) 20 | time.sleep(1) 21 | s = time.time() 22 | ret = dm.Capture(0,0,0,0) 23 | if ret: 24 | print(f"耗时:{time.time()-s}") 25 | cv2.imshow("img",dm.GetCVImg()) 26 | cv2.waitKey() 27 | 28 | def windows_capture(hwnd): 29 | dm = DM() 30 | dm.BindWindow(hwnd,"normal","windows","windows",0) 31 | s = time.time() 32 | ret = dm.Capture(0,0,0,0) 33 | if ret: 34 | print(f"耗时:{time.time()-s}") 35 | cv2.imshow("img",dm.GetCVImg()) 36 | cv2.waitKey() 37 | 38 | if __name__ == '__main__': 39 | # 前台截图,请自行修改句柄hwnd 40 | # time.sleep(5) 41 | hwnd = 198944 42 | # normal_capture(hwnd) 43 | # 后台截图 44 | windows_capture(hwnd) -------------------------------------------------------------------------------- /PyDmGame/tests/键盘测试.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | @Time : 2023/2/12 19:45 4 | @Auth : 大雄 5 | @File :键盘测试.py 6 | @IDE :PyCharm 7 | @Email:3475228828@qq.com 8 | @func:功能 9 | """ 10 | import time 11 | from PyDmGame import * 12 | 13 | def normal_keyboard(hwnd): 14 | dm = DM() 15 | dm.BindWindow(hwnd, "normal", "normal", "normal", 0) 16 | s = time.time() 17 | dm.KeyPressChar("x") 18 | # time.sleep(1) 19 | # dm.KeyPressChar("right") 20 | # time.sleep(1) 21 | # dm.KeyPressChar("up") 22 | # time.sleep(1) 23 | # dm.KeyPressChar("down") 24 | time.sleep(1) 25 | print(f"耗时:{time.time() - s}") 26 | 27 | 28 | def windows_keyboard(hwnd): 29 | dm = DM() 30 | dm.BindWindow(hwnd, "normal", "windows", "windows", 0) 31 | s = time.time() 32 | dm.KeyPressStr("1", 50) 33 | # dm.KeyPressChar("a") 34 | print(f"耗时:{time.time() - s}") 35 | 36 | 37 | def sendstring_keyboard(hwnd): 38 | dm = DM() 39 | dm.BindWindow(hwnd, "gdi", "windows", "windows", 0) 40 | dm.SendString(hwnd, "我是大雄") 41 | 42 | 43 | if __name__ == '__main__': 44 | hwnd = 264426 45 | # # 测试按键输入-前台 46 | time.sleep(3) 47 | normal_keyboard(hwnd) 48 | 49 | # 测试按键输入-后台 50 | # windows_keyboard(hwnd) 51 | 52 | # # 测试后台输入字符串 53 | # sendstring_keyboard(hwnd) 54 | -------------------------------------------------------------------------------- /PyDmGame/tests/鼠标测试.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | @Time : 2023/2/13 20:57 4 | @Auth : 大雄 5 | @File :鼠标测试.py 6 | @IDE :PyCharm 7 | @Email:3475228828@qq.com 8 | @func:功能 9 | """ 10 | import time 11 | 12 | from PyDmGame import * 13 | def mouse_normal(): 14 | dm = DM() 15 | dm.MoveTo(100,100) 16 | dm.LeftClick() 17 | time.sleep(1) 18 | dm.RightClick() 19 | time.sleep(1) 20 | dm.MoveTo(50,100) 21 | dm.LeftClick() 22 | dm.WheelDown() 23 | time.sleep(1) 24 | dm.WheelUp() 25 | 26 | def mouse_windows(hwnd): 27 | dm = DM() 28 | dm.BindWindow(hwnd,"gdi","windows","windows") 29 | dm.MoveTo(89,188) 30 | dm.LeftClick() 31 | time.sleep(1) 32 | dm.MoveTo(200,300) 33 | dm.LeftClick() 34 | 35 | 36 | 37 | if __name__ == '__main__': 38 | # # 前台测试 39 | # time.sleep(1) 40 | # mouse_normal() 41 | 42 | # 后台测试 43 | time.sleep(2) 44 | hwnd = 198944 45 | mouse_windows(hwnd) 46 | 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PyDmGame 2 | 3 | ## 目录 4 | 5 | ## [安装](#1) 6 | 7 | ## [初始化](#2) 8 | 9 | ## [基本设置](#3) 10 | 11 | ## [后台设置](#4) 12 | 13 | ## [窗口](#5) 14 | 15 | ## [图色](#6) 16 | 17 | ## [键鼠](#7) 18 | 19 | ## [文字识别](#8) 20 | 21 | ## [虚拟按键码](#9) 22 | 23 | ### 安装 24 | 25 | pip install PyDmGame 26 | 27 | ### 初始化 28 | 29 | improt PyDmGame 30 | dm = PyDmGame.DM() 31 | dm.SetPath("imagePath") # 设置默认找图路径,不适用找图可以不设置 32 | dm.BindWindow(hwnd,"normal","normal","windows",0) # 绑定模式,请参考后台设置 33 | 34 | ### 基本设置 35 | 36 | #### SetPath(path) 37 | 38 | 设置全局路径,设置了此路径后,所有接口调用中,相关的文件都相对于此路径. 比如图片 39 | :param path 字符串: 路径,绝对路径 40 | 返回值:None 41 | 42 | ### 后台设置 43 | 44 | #### BindWindow(hwnd,display,mouse,keypad,mode=0) 45 | 46 | 绑定指定的窗口,并指定这个窗口的屏幕颜色获取方式,鼠标仿真模式,键盘仿真模式,以及模式设定 47 | :param hwnd 整形数: 指定的窗口句柄 48 | display 字符串: 屏幕颜色获取方式 取值有以下几种 49 | "normal" : 正常模式,平常我们用的前台截屏模式 50 | "gdi" : gdi模式,用于窗口采用GDI方式刷新时 51 | 52 | :param mouse 字符串: 鼠标仿真模式 取值有以下几种 53 | "normal" : 正常模式,平常我们用的前台鼠标模式,api为SendInput函数 54 | "normal2" : 正常模式,平常我们用的前台鼠标模式,api为event事件,微软官方已废弃,被SendInput取代 55 | "windows": 后台模式,采取模拟windows消息方式 同按键自带后台插件,api为SendMessage函数,雷电模拟器可用这个模式 56 | "windows2": 后台模式,采取模拟windows消息方式 同按键自带后台插件,api为PostMessage函数 57 | 58 | :param keypad 字符串: 键盘仿真模式 取值有以下几种 59 | "normal" : 正常模式,平常我们用的前台鼠标模式,api为SendInput函数 60 | "normal2" : 正常模式,平常我们用的前台鼠标模式,api为event事件,微软官方已废弃,被SendInput取代 61 | "windows": 后台模式,采取模拟windows消息方式 同按键自带后台插件,api为SendMessage函数,雷电模拟器可用这个模式 62 | "windows2": 后台模式,采取模拟windows消息方式 同按键自带后台插件,api为PostMessage函数 63 | 64 | :param mode 整形数: 模式 65 | 预留接口,此参数可不写 66 | 返回值:None 67 | 68 | ### 窗口(未测试验证) 69 | 70 | #### ClientToScreen(hwnd,x,y) 71 | 72 | 把窗口坐标转换为屏幕坐标 73 | :param hwnd 整形数: 指定的窗口句柄 74 | :param x 变参指针: 窗口X坐标 75 | :param y 变参指针: 窗口Y坐标 76 | 77 | #### EnumProcess(name) 78 | 79 | :param name 字符串:进程名,比如qq.exe 80 | 返回值:返回所有匹配的进程PID,并按打开顺序排序,格式"pid1,pid2,pid3" 81 | 82 | #### EnumWindow(parent,title,class_name,filter) 83 | 84 | #### FindWindow(class,title) 85 | 86 | #### FindWindowByProcess(process_name,class,title) 87 | 88 | #### FindWindowByProcessId(process_id,class,title) 89 | 90 | #### FindWindowEx(parent,class,title) 91 | 92 | #### GetClientRect(hwnd,x1,y1,x2,y2) 93 | 94 | #### GetClientSize(hwnd,width,height) 95 | 96 | #### GetForegroundFocus() 97 | 98 | #### GetForegroundWindow() 99 | 100 | #### GetMousePointWindow() 101 | 102 | #### GetPointWindow(x,y) 103 | 104 | #### GetProcessInfo(pid) 105 | 106 | #### GetSpecialWindow(flag) 107 | 108 | #### GetWindow(hwnd,flag) 109 | 110 | #### GetWindowClass(hwnd) 111 | 112 | #### GetWindowProcessId(hwnd) 113 | 114 | #### GetWindowProcessPath(hwnd) 115 | 116 | #### GetWindowRect(hwnd,x1,y1,x2,y2) 117 | 118 | #### GetWindowState(hwnd,flag) 119 | 120 | #### GetWindowTitle(hwnd) 121 | 122 | #### MoveWindow(hwnd,x,y) 123 | 124 | #### ScreenToClient(hwnd,x,y) 125 | 126 | #### SendPaste(hwnd) 127 | 128 | #### SetClientSize(hwnd,width,height) 129 | 130 | #### SetWindowSize(hwnd,width,height) 131 | 132 | #### SetWindowState(hwnd,flag) 133 | 134 | #### SetWindowText(hwnd,title) 135 | 136 | #### SetWindowTransparent(hwnd,trans) 137 | 138 | ### 图色 139 | 140 | #### Capture(x1, y1, x2, y2, file) 141 | 142 | 抓取指定区域(x1, y1, x2, y2)的图像保存为file,图像后缀任意填写 143 | :param x1 整形数:区域的左上X坐标 144 | :param y1 整形数:区域的左上Y坐标 145 | :param x2 整形数:区域的右下X坐标 146 | :param y2 整形数:区域的右下Y坐标 147 | :param file 字符串:保存的文件名,填写绝对路径,不填写也可以,通过GetCVImg()获取CV图像 148 | 149 | #### CmpColor(x, y, color, sim=1) 150 | 151 | 比较指定坐标点(x,y)的颜色 152 | x 整形数: X坐标 153 | y 整形数: Y坐标 154 | color 字符串: 颜色字符串,可以支持偏色,支持RGB偏色和HSV偏色 155 | sim 双精度浮点数: 相似度(0.1-1.0) 156 | 返回值: 157 | True颜色匹配 158 | False颜色不匹配 159 | 160 | #### FindColor(x1, y1, x2, y2, color, sim, dir=None) 161 | 162 | 查找指定区域内的颜色,支持RGB和HSV颜色 163 | :param x1 整形数:区域的左上X坐标 164 | :param y1 整形数:区域的左上Y坐标 165 | :param x2 整形数:区域的右下X坐标 166 | :param y2 整形数:区域的右下Y坐标 167 | :param color 字符串:颜色 格式为"RRGGBB-DRDGDB",比如"123456-000000",或者HSV格式((0,0,0),(180,255,255)) 168 | :param sim 双精度浮点数:相似度,取值范围0.1-1.0 169 | :param dir 整形数:预留参数,可不写 170 | 返回值: 171 | 找到:0,x,y 172 | 未找到:-1, -1, -1 173 | 174 | #### FindPic(x1, y1, x2, y2, pic_name, delta_color, sim, method=5, drag=None) 175 | 176 | 查找指定区域内的图片,返回相似度最大的坐标,原来是利用opencv的模板匹配,默认使用算法5 177 | :param x1:区域的左上X坐标 178 | :param y1:区域的左上Y坐标 179 | :param x2:区域的右下X坐标 180 | :param y2:区域的右下Y坐标 181 | :param pic_name:图片名,只能单个图片 182 | :param delta_color:偏色,可以是RGB偏色,格式"FFFFFF-202020",也可以是HSV偏色,格式((0,0,0),(180,255,255)) 183 | :param sim:相似度,和算法相关 184 | :param dir:仿大漠,总共有6总 185 | :param drag:是否在找到的位置画图并显示,默认不画 186 | 方差匹配方法:匹配度越高,值越接近于0。 187 | 归一化方差匹配方法:完全匹配结果为0。 188 | 相关性匹配方法:完全匹配会得到很大值,不匹配会得到一个很小值或0。 189 | 归一化的互相关匹配方法:完全匹配会得到1, 完全不匹配会得到0。 190 | 相关系数匹配方法:完全匹配会得到一个很大值,完全不匹配会得到0,完全负相关会得到很大的负数。 191 | (此处与书籍以及大部分分享的资料所认为不同,研究公式发现,只有归一化的相关系数才会有[-1,1]的值域) 192 | 归一化的相关系数匹配方法:完全匹配会得到1,完全负相关匹配会得到-1,完全不匹配会得到0。 193 | 返回值: 194 | 找到:0,x,y 195 | 未找到:-1, -1, -1 196 | 197 | #### FindPics(x1, y1, x2, y2, pic_name, delta_color, sim, method=5, drag=None) 198 | 199 | 查找指定区域内的图片,返回多个坐标,原来是利用opencv的模板匹配,默认使用算法5 200 | :param x1:区域的左上X坐标 201 | :param y1:区域的左上Y坐标 202 | :param x2:区域的右下X坐标 203 | :param y2:区域的右下Y坐标 204 | :param pic_name:图片名,只能单个图片 205 | :param delta_color:偏色,可以是RGB偏色,格式"FFFFFF-202020",也可以是HSV偏色,格式((0,0,0),(180,255,255)) 206 | :param sim:相似度,和算法相关 207 | :param dir:仿大漠,总共有6总 208 | :param drag:是否在找到的位置画图并显示,默认不画 209 | 方差匹配方法:匹配度越高,值越接近于0。 210 | 归一化方差匹配方法:完全匹配结果为0。 211 | 相关性匹配方法:完全匹配会得到很大值,不匹配会得到一个很小值或0。 212 | 归一化的互相关匹配方法:完全匹配会得到1, 完全不匹配会得到0。 213 | 相关系数匹配方法:完全匹配会得到一个很大值,完全不匹配会得到0,完全负相关会得到很大的负数。 214 | (此处与书籍以及大部分分享的资料所认为不同,研究公式发现,只有归一化的相关系数才会有[-1,1]的值域) 215 | 归一化的相关系数匹配方法:完全匹配会得到1,完全负相关匹配会得到-1,完全不匹配会得到0。 216 | 返回值: 217 | 找到:0,[[x1,y1],[x2,y2],...)] 218 | 未找到:-1, -1, -1 219 | 220 | #### GetCVImg() 221 | 222 | 获取截图的内存图像,格式cv,需要先使用Capture截图 223 | 224 | ### 键鼠 225 | 226 | #### GetCursorPos(x,y) 227 | 228 | 获取鼠标位置 229 | :param x:鼠标横坐标 230 | :param y:鼠标束坐标 231 | 232 | #### GetCursorShape() 233 | 234 | 获取鼠标特征码 235 | 成功时,返回鼠标特征码. 236 | 失败时,返回空的串. 237 | 238 | #### LeftDown() 239 | 240 | 按住鼠标左键 241 | 242 | #### LeftUp() 243 | 244 | 弹起鼠标左键 245 | 246 | #### LeftClick() 247 | 248 | 单击鼠标左键 249 | 250 | #### LeftDoubleClick() 251 | 252 | 双击鼠标左键 253 | 254 | #### RightDown() 255 | 256 | 按住鼠标右键 257 | 258 | #### RightUp() 259 | 260 | 弹起鼠标右键 261 | 262 | #### RightClick() 263 | 264 | 单击鼠标右键 265 | 266 | #### SetMouseDelay(self,type,delay) 267 | 268 | 设置鼠标单击或者双击时,鼠标按下和弹起的时间间隔。高级用户使用。某些窗口可能需要调整这个参数才可以正常点击。 269 | :param type 字符串: 鼠标类型,取值有以下 270 | "normal" : 对应normal鼠标 默认内部延时为 30ms 271 | "windows": 对应windows 鼠标 默认内部延时为 10ms 272 | :paramd elay 整形数: 延时,单位是毫秒 273 | 274 | #### MoveTo(x,y) 275 | 276 | 把鼠标移动到目的点(x,y) 277 | :param x 整形数:X坐标 278 | :param y 整形数:Y坐标 279 | 280 | #### WheelDown() 281 | 282 | 滚轮向下滚 283 | 284 | #### WheelUp() 285 | 286 | 滚轮向上滚 287 | 288 | #### SetKeypadDelay(delay=None) 289 | 290 | 设置按键时,键盘按下和弹起的时间间隔。高级用户使用。某些窗口可能需要调整这个参数才可以正常按键。 291 | type 字符串: 键盘类型,取值有以下 292 | "normal" : 对应normal键盘 默认内部延时为30ms 293 | "windows": 对应windows 键盘 默认内部延时为10ms 294 | delay 整形数: 延时,单位是毫秒 295 | 296 | #### KeyDownChar(key_str) 297 | 298 | 按住指定的虚拟键码 299 | 300 | #### KeyUpChar(key_str) 301 | 302 | 弹起来虚拟键key_str 303 | 304 | #### KeyPressChar(key_str) 305 | 306 | 按下指定的虚拟键码 307 | 308 | #### KeyPressStr(key_str,delay) 309 | 310 | 根据指定的字符串序列,依次按顺序按下其中的字符. 311 | key_str 字符串: 需要按下的字符串序列. 比如"1234","abcd","7389,1462"等. 312 | delay 整形数: 每按下一个按键,需要延时多久. 单位毫秒.这个值越大,按的速度越慢。 313 | 314 | #### SendString(hwnd,str) 315 | 316 | 向指定窗口发送文本数据 317 | hwnd 整形数: 指定的窗口句柄. 如果为0,则对当前激活的窗口发送. 318 | str 字符串: 发送的文本数据 319 | 320 | ### 文字识别 321 | 322 | #### FindNum(x1, y1, x2, y2, numString, color_format, sim) 323 | 324 | :param x1: x1 整形数:区域的左上X坐标 325 | :param y1: y1 整形数:区域的左上Y坐标 326 | :param x2: x2 整形数:区域的右下X坐标 327 | :param y2: y2 整形数:区域的右下Y坐标 328 | :param numString: 字符串:如数字"1","56","789" 329 | :param color_format:字符串:颜色格式串, 可以包含换行分隔符,语法是","后加分割字符串. 具体可以查看下面的示例 .注意,RGB和HSV,以及灰度格式都支持. 330 | :param sim: 双精度浮点数:相似度,取值范围0.1-1.0 331 | :return:bool 332 | 333 | #### OcrNum(x1, y1, x2, y2, color_format, sim, dirPath) 334 | 335 | :param x1: x1 整形数:区域的左上X坐标 336 | :param y1: y1 整形数:区域的左上Y坐标 337 | :param x2: x2 整形数:区域的右下X坐标 338 | :param y2: y2 整形数:区域的右下Y坐标 339 | :param color_format: 字符串:颜色格式串, 可以包含换行分隔符,语法是","后加分割字符串. 具体可以查看下面的示例 .注意,RGB和HSV,以及灰度格式都支持. 340 | :param sim: 双精度浮点数:相似度,取值范围0.1-1.0 341 | :param dirPath: 图库路径,用于存储0-9数字模板 342 | :return: num:字符串数字 343 | 344 | ### 虚拟按键码 345 | 'backspace': 0x08, 346 | 'tab': 0x09, 347 | 'clear': 0x0C, 348 | 'enter': 0x0D, 349 | 'shift': 0x10, 350 | 'ctrl': 0x11, 351 | 'alt': 0x12, 352 | 'pause': 0x13, 353 | 'caps_lock': 0x14, 354 | 'esc': 0x1B, 355 | 'spacebar': 0x20, 356 | 'page_up': 0x21, 357 | 'page_down': 0x22, 358 | 'end': 0x23, 359 | 'home': 0x24, 360 | 'left_arrow': 0x25, 361 | 'up_arrow': 0x26, 362 | 'right_arrow': 0x27, 363 | 'down_arrow': 0x28, 364 | 'select': 0x29, 365 | 'print': 0x2A, 366 | 'execute': 0x2B, 367 | 'print_screen': 0x2C, 368 | 'ins': 0x2D, 369 | 'del': 0x2E, 370 | 'help': 0x2F, 371 | '0': 0x30, 372 | '1': 0x31, 373 | '2': 0x32, 374 | '3': 0x33, 375 | '4': 0x34, 376 | '5': 0x35, 377 | '6': 0x36, 378 | '7': 0x37, 379 | '8': 0x38, 380 | '9': 0x39, 381 | 'a': 0x41, 382 | 'b': 0x42, 383 | 'c': 0x43, 384 | 'd': 0x44, 385 | 'e': 0x45, 386 | 'f': 0x46, 387 | 'g': 0x47, 388 | 'h': 0x48, 389 | 'i': 0x49, 390 | 'j': 0x4A, 391 | 'k': 0x4B, 392 | 'l': 0x4C, 393 | 'm': 0x4D, 394 | 'n': 0x4E, 395 | 'o': 0x4F, 396 | 'p': 0x50, 397 | 'q': 0x51, 398 | 'r': 0x52, 399 | 's': 0x53, 400 | 't': 0x54, 401 | 'u': 0x55, 402 | 'v': 0x56, 403 | 'w': 0x57, 404 | 'x': 0x58, 405 | 'y': 0x59, 406 | 'z': 0x5A, 407 | 'numpad_0': 0x60, 408 | 'numpad_1': 0x61, 409 | 'numpad_2': 0x62, 410 | 'numpad_3': 0x63, 411 | 'numpad_4': 0x64, 412 | 'numpad_5': 0x65, 413 | 'numpad_6': 0x66, 414 | 'numpad_7': 0x67, 415 | 'numpad_8': 0x68, 416 | 'numpad_9': 0x69, 417 | 'multiply_key': 0x6A, 418 | 'add_key': 0x6B, 419 | 'separator_key': 0x6C, 420 | 'subtract_key': 0x6D, 421 | 'decimal_key': 0x6E, 422 | 'divide_key': 0x6F, 423 | 'f1': 0x70, 424 | 'f2': 0x71, 425 | 'f3': 0x72, 426 | 'f4': 0x73, 427 | 'f5': 0x74, 428 | 'f6': 0x75, 429 | 'f7': 0x76, 430 | 'f8': 0x77, 431 | 'f9': 0x78, 432 | 'f10': 0x79, 433 | 'f11': 0x7A, 434 | 'f12': 0x7B, 435 | 'f13': 0x7C, 436 | 'f14': 0x7D, 437 | 'f15': 0x7E, 438 | 'f16': 0x7F, 439 | 'f17': 0x80, 440 | 'f18': 0x81, 441 | 'f19': 0x82, 442 | 'f20': 0x83, 443 | 'f21': 0x84, 444 | 'f22': 0x85, 445 | 'f23': 0x86, 446 | 'f24': 0x87, 447 | 'num_lock': 0x90, 448 | 'scroll_lock': 0x91, 449 | 'left_shift': 0xA0, 450 | 'right_shift ': 0xA1, 451 | 'left_control': 0xA2, 452 | 'right_control': 0xA3, 453 | 'left_menu': 0xA4, 454 | 'right_menu': 0xA5, 455 | 'browser_back': 0xA6, 456 | 'browser_forward': 0xA7, 457 | 'browser_refresh': 0xA8, 458 | 'browser_stop': 0xA9, 459 | 'browser_search': 0xAA, 460 | 'browser_favorites': 0xAB, 461 | 'browser_start_and_home': 0xAC, 462 | 'volume_mute': 0xAD, 463 | 'volume_Down': 0xAE, 464 | 'volume_up': 0xAF, 465 | 'next_track': 0xB0, 466 | 'previous_track': 0xB1, 467 | 'stop_media': 0xB2, 468 | 'play/pause_media': 0xB3, 469 | 'start_mail': 0xB4, 470 | 'select_media': 0xB5, 471 | 'start_application_1': 0xB6, 472 | 'start_application_2': 0xB7, 473 | 'attn_key': 0xF6, 474 | 'crsel_key': 0xF7, 475 | 'exsel_key': 0xF8, 476 | 'play_key': 0xFA, 477 | 'zoom_key': 0xFB, 478 | 'clear_key': 0xFE, 479 | '+': 0xBB, 480 | ',': 0xBC, 481 | '-': 0xBD, 482 | '.': 0xBE, 483 | '/': 0xBF, 484 | '`': 0xC0, 485 | ';': 0xBA, 486 | '[': 0xDB, 487 | '\\': 0xDC, 488 | ']': 0xDD, 489 | "'": 0xDE} -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | @Time : 2023/2/17 22:57 4 | @Auth : 大雄 5 | @File :setup.py 6 | @IDE :PyCharm 7 | @Email:3475228828@qq.com 8 | @func:功能 9 | """ 10 | import setuptools 11 | import os 12 | 13 | CUR_DIR = os.path.abspath(os.path.dirname(__file__)) 14 | README = os.path.join(CUR_DIR, "README.md") 15 | with open("README.md", "r", encoding="utf-8") as fd: 16 | long_description = fd.read() 17 | setuptools.setup( 18 | name="PyDmGame", 19 | version="0.0.2", 20 | description="Python version of dm", 21 | long_description=long_description, 22 | long_description_content_type="text/markdown", 23 | url="https://space.bilibili.com/470514128?spm_id_from=333.788.0.0", 24 | author="da xiong", 25 | author_email="270207756@foxmail.com", 26 | install_requires=[ 27 | "opencv-python>=4.3.0.38", 28 | "pywin32>=305", 29 | ], 30 | packages=['PyDmGame', "PyDmGame/modular", "PyDmGame/model", "PyDmGame/tests", "PyDmGame/model", 31 | "PyDmGame/modular/display_", "PyDmGame/modular/keyboard_", "PyDmGame/modular/mouse_", ], 32 | python_requires='>=3.6', 33 | keywords='windows game dm', 34 | ) 35 | --------------------------------------------------------------------------------