├── 01_map_walking.py ├── 02_improved_mapwalking.py ├── LICENSE ├── README.md ├── getkeys.py ├── grabscreen.py └── keys.py /01_map_walking.py: -------------------------------------------------------------------------------- 1 | from grabscreen import grab_screen 2 | import cv2 3 | import numpy as np 4 | import time 5 | import keys as k 6 | 7 | 8 | keys = k.Keys() 9 | 10 | 11 | def pathing(minimap): 12 | lower = np.array([75,150,150]) 13 | upper = np.array([150,255,255]) 14 | 15 | hsv = cv2.cvtColor(minimap, cv2.COLOR_RGB2HSV) 16 | mask = cv2.inRange(hsv, lower, upper) 17 | 18 | matches = np.argwhere(mask==255) 19 | mean_y = np.mean(matches[:,1]) 20 | target = minimap.shape[1]/2 21 | 22 | error = target - mean_y 23 | 24 | print(error) 25 | keys.directMouse(-1*int(error*3), 0) 26 | 27 | cv2.imshow("cv2screen", mask) 28 | cv2.waitKey(10) 29 | 30 | 31 | for i in range(5): 32 | print(i) 33 | time.sleep(1) 34 | 35 | 36 | keys.directKey("w") 37 | # run for just 100 frames. 38 | for i in range(100): 39 | screen = grab_screen(region=(1280, 0, 3840, 1440)) # region will vary depending on game resolution and monitor resolution 40 | screen = cv2.cvtColor(screen, cv2.COLOR_BGR2RGB) # because default will be BGR 41 | minimap = screen[81:377, 2181:2469] 42 | miniminimap = screen[185:215, 2290:2358] 43 | 44 | pathing(miniminimap) 45 | #screen = cv2.resize(screen, (960,540)) 46 | #cv2.imshow("cv2screen", screen) 47 | #cv2.waitKey(10) 48 | keys.directKey("w", keys.key_release) 49 | cv2.destroyAllWindows() 50 | -------------------------------------------------------------------------------- /02_improved_mapwalking.py: -------------------------------------------------------------------------------- 1 | from grabscreen import grab_screen 2 | import cv2 3 | import numpy as np 4 | import time 5 | import keys as k 6 | from getkeys import key_check 7 | from collections import deque 8 | 9 | 10 | keys = k.Keys() 11 | screens = deque(maxlen=2) 12 | movements = deque(maxlen=10) 13 | 14 | YELLOW_PATH = np.array([75,150,150]), np.array([150,255,255]) 15 | BLUE_PATH = np.array([0,150,150]), np.array([50,255,255]) 16 | 17 | def pathing(minimap, color): 18 | lower, upper = color 19 | 20 | hsv = cv2.cvtColor(minimap, cv2.COLOR_RGB2HSV) 21 | mask = cv2.inRange(hsv, lower, upper) 22 | 23 | matches = np.argwhere(mask==255) 24 | mean_y = np.mean(matches[:,1]) 25 | target = minimap.shape[1]/2 26 | 27 | error = target - mean_y 28 | 29 | move_size = 1 30 | full_range = int(abs(error*1.5)) 31 | 32 | for i in range(full_range): 33 | 34 | keys.directMouse(-1*int(error/abs(error))*move_size*2, 0) # doing this to retain the sign. iono. works. 35 | time.sleep(0.000001) 36 | 37 | if i < full_range / 2: 38 | move_size += 1 39 | else: 40 | move_size -= 1 41 | 42 | cv2.imshow("cv2screen", mask) 43 | cv2.waitKey(10) 44 | 45 | 46 | for i in range(5): 47 | print(i) 48 | time.sleep(1) 49 | 50 | 51 | keys.directKey("w") 52 | # run for just 100 frames. 53 | 54 | paused_status = False 55 | while True: 56 | 57 | pressed = key_check() 58 | if 'Y' in pressed: 59 | paused_status = not(paused_status) 60 | 61 | if paused_status: 62 | print("Paused... (y)") 63 | time.sleep(1) 64 | 65 | else: 66 | screen = grab_screen(region=(1280, 0, 3840, 1440)) # region will vary depending on game resolution and monitor resolution 67 | screen = cv2.cvtColor(screen, cv2.COLOR_BGR2RGB) # because default will be BGR 68 | minimap = screen[81:377, 2181:2469] 69 | miniminimap = screen[185:215, 2290:2358] 70 | 71 | screen = cv2.resize(screen, (960,540)) 72 | screens.append(screen) 73 | 74 | if len(screens) == 2: 75 | 76 | diff=cv2.absdiff(screens[1],screens[0]) 77 | gray=cv2.cvtColor(diff,cv2.COLOR_BGR2GRAY) 78 | blurred =cv2.GaussianBlur(gray,(5,5),0) 79 | _,thresh=cv2.threshold(blurred, 50, 255,cv2.THRESH_BINARY) 80 | movement_count = len(np.argwhere(thresh==255)) 81 | #print(movement_count) 82 | movements.append(movement_count) 83 | 84 | movement_avg = np.mean(movements) 85 | 86 | print('Move avg:',np.mean(movements)) 87 | cv2.imshow("movement_detection", thresh) 88 | #cv2.waitKey(10) 89 | 90 | if movement_avg < 10000: 91 | keys.directKey("space") 92 | keys.directKey("space", keys.key_release) 93 | 94 | try: 95 | try: 96 | pathing(miniminimap, color=BLUE_PATH) 97 | except Exception as e: 98 | print("falling back to yellow path for now.", str(e)) 99 | pathing(miniminimap, color=YELLOW_PATH) 100 | except: 101 | print("Full map zoom out.") 102 | try: 103 | pathing(minimap, color=BLUE_PATH) 104 | except Exception as e: 105 | print("falling back to yellow path for now.", str(e)) 106 | pathing(minimap, color=YELLOW_PATH) 107 | #screen = cv2.resize(screen, (960,540)) 108 | #cv2.imshow("cv2screen", screen) 109 | #cv2.waitKey(10) 110 | 111 | 112 | keys.directKey("w", keys.key_release) 113 | cv2.destroyAllWindows() 114 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Harrison 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CyberPython 2077 2 | Using Python to Play Cyberpunk 2077 3 | 4 | This repo will contain code from the Cyberpython 2077 video series on Youtube (youtube.com/sentdex) 5 | -------------------------------------------------------------------------------- /getkeys.py: -------------------------------------------------------------------------------- 1 | # Citation: Box Of Hats (https://github.com/Box-Of-Hats ) 2 | # avail: https://raw.githubusercontent.com/Sentdex/pygta5/master/getkeys.py 3 | 4 | import win32api as wapi 5 | import time 6 | 7 | keyList = ["\b"] 8 | for char in "ABCDEFGHIJKLMNOPQRSTUVWXYZ 123456789,.'£$/\\": 9 | keyList.append(char) 10 | 11 | def key_check(): 12 | keys = [] 13 | for key in keyList: 14 | if wapi.GetAsyncKeyState(ord(key)): 15 | keys.append(key) 16 | return keys 17 | 18 | -------------------------------------------------------------------------------- /grabscreen.py: -------------------------------------------------------------------------------- 1 | # Done by Frannecklp 2 | 3 | import cv2 4 | import numpy as np 5 | import win32gui, win32ui, win32con, win32api 6 | 7 | def grab_screen(region=None): 8 | 9 | hwin = win32gui.GetDesktopWindow() 10 | 11 | if region: 12 | left,top,x2,y2 = region 13 | width = x2 - left + 1 14 | height = y2 - top + 1 15 | else: 16 | width = win32api.GetSystemMetrics(win32con.SM_CXVIRTUALSCREEN) 17 | height = win32api.GetSystemMetrics(win32con.SM_CYVIRTUALSCREEN) 18 | left = win32api.GetSystemMetrics(win32con.SM_XVIRTUALSCREEN) 19 | top = win32api.GetSystemMetrics(win32con.SM_YVIRTUALSCREEN) 20 | 21 | hwindc = win32gui.GetWindowDC(hwin) 22 | srcdc = win32ui.CreateDCFromHandle(hwindc) 23 | memdc = srcdc.CreateCompatibleDC() 24 | bmp = win32ui.CreateBitmap() 25 | bmp.CreateCompatibleBitmap(srcdc, width, height) 26 | memdc.SelectObject(bmp) 27 | memdc.BitBlt((0, 0), (width, height), srcdc, (left, top), win32con.SRCCOPY) 28 | 29 | signedIntsArray = bmp.GetBitmapBits(True) 30 | img = np.fromstring(signedIntsArray, dtype='uint8') 31 | img.shape = (height,width,4) 32 | 33 | srcdc.DeleteDC() 34 | memdc.DeleteDC() 35 | win32gui.ReleaseDC(hwin, hwindc) 36 | win32gui.DeleteObject(bmp.GetHandle()) 37 | 38 | return cv2.cvtColor(img, cv2.COLOR_BGRA2RGB) 39 | -------------------------------------------------------------------------------- /keys.py: -------------------------------------------------------------------------------- 1 | # Code by Daniel Kukiela (https://twitter.com/daniel_kukiela) 2 | 3 | import ctypes 4 | from threading import Thread 5 | from time import time, sleep 6 | from queue import Queue 7 | 8 | 9 | # main keys class 10 | class Keys(object): 11 | 12 | common = None 13 | standalone = False 14 | 15 | # instance of worker class 16 | keys_worker = None 17 | keys_process = None 18 | 19 | # key constants 20 | direct_keys = 0x0008 21 | virtual_keys = 0x0000 22 | key_press = 0x0000 23 | key_release = 0x0002 24 | 25 | # mouse constants 26 | mouse_move = 0x0001 27 | mouse_lb_press = 0x0002 28 | mouse_lb_release = 0x0004 29 | mouse_rb_press = 0x0008 30 | mouse_rb_release = 0x0010 31 | mouse_mb_press = 0x0020 32 | mouse_mb_release = 0x0040 33 | 34 | # direct keys 35 | dk = { 36 | "1": 0x02, 37 | "2": 0x03, 38 | "3": 0x04, 39 | "4": 0x05, 40 | "5": 0x06, 41 | "6": 0x07, 42 | "7": 0x08, 43 | "8": 0x09, 44 | "9": 0x0A, 45 | "0": 0x0B, 46 | 47 | "NUMPAD1": 0x4F, "NP1": 0x4F, 48 | "NUMPAD2": 0x50, "NP2": 0x50, 49 | "NUMPAD3": 0x51, "NP3": 0x51, 50 | "NUMPAD4": 0x4B, "NP4": 0x4B, 51 | "NUMPAD5": 0x4C, "NP5": 0x4C, 52 | "NUMPAD6": 0x4D, "NP6": 0x4D, 53 | "NUMPAD7": 0x47, "NP7": 0x47, 54 | "NUMPAD8": 0x48, "NP8": 0x48, 55 | "NUMPAD9": 0x49, "NP9": 0x49, 56 | "NUMPAD0": 0x52, "NP0": 0x52, 57 | "DIVIDE": 0xB5, "NPDV": 0xB5, 58 | "MULTIPLY": 0x37, "NPM": 0x37, 59 | "SUBSTRACT": 0x4A, "NPS": 0x4A, 60 | "ADD": 0x4E, "NPA": 0x4E, 61 | "DECIMAL": 0x53, "NPDC": 0x53, 62 | "NUMPADENTER": 0x9C, "NPE": 0x9C, 63 | 64 | "A": 0x1E, 65 | "B": 0x30, 66 | "C": 0x2E, 67 | "D": 0x20, 68 | "E": 0x12, 69 | "F": 0x21, 70 | "G": 0x22, 71 | "H": 0x23, 72 | "I": 0x17, 73 | "J": 0x24, 74 | "K": 0x25, 75 | "L": 0x26, 76 | "M": 0x32, 77 | "N": 0x31, 78 | "O": 0x18, 79 | "P": 0x19, 80 | "Q": 0x10, 81 | "R": 0x13, 82 | "S": 0x1F, 83 | "T": 0x14, 84 | "U": 0x16, 85 | "V": 0x2F, 86 | "W": 0x11, 87 | "X": 0x2D, 88 | "Y": 0x15, 89 | "Z": 0x2C, 90 | 91 | "F1": 0x3B, 92 | "F2": 0x3C, 93 | "F3": 0x3D, 94 | "F4": 0x3E, 95 | "F5": 0x3F, 96 | "F6": 0x40, 97 | "F7": 0x41, 98 | "F8": 0x42, 99 | "F9": 0x43, 100 | "F10": 0x44, 101 | "F11": 0x57, 102 | "F12": 0x58, 103 | 104 | "UP": 0xC8, 105 | "LEFT": 0xCB, 106 | "RIGHT": 0xCD, 107 | "DOWN": 0xD0, 108 | 109 | "ESC": 0x01, 110 | "SPACE": 0x39, "SPC": 0x39, 111 | "RETURN": 0x1C, "ENT": 0x1C, 112 | "INSERT": 0xD2, "INS": 0xD2, 113 | "DELETE": 0xD3, "DEL": 0xD3, 114 | "HOME": 0xC7, 115 | "END": 0xCF, 116 | "PRIOR": 0xC9, "PGUP": 0xC9, 117 | "NEXT": 0xD1, "PGDN": 0xD1, 118 | "BACK": 0x0E, 119 | "TAB": 0x0F, 120 | "LCONTROL": 0x1D, "LCTRL": 0x1D, 121 | "RCONTROL": 0x9D, "RCTRL": 0x9D, 122 | "LSHIFT": 0x2A, "LSH": 0x2A, 123 | "RSHIFT": 0x36, "RSH": 0x36, 124 | "LMENU": 0x38, "LALT": 0x38, 125 | "RMENU": 0xB8, "RALT": 0xB8, 126 | "LWIN": 0xDB, 127 | "RWIN": 0xDC, 128 | "APPS": 0xDD, 129 | "CAPITAL": 0x3A, "CAPS": 0x3A, 130 | "NUMLOCK": 0x45, "NUM": 0x45, 131 | "SCROLL": 0x46, "SCR": 0x46, 132 | 133 | "MINUS": 0x0C, "MIN": 0x0C, 134 | "LBRACKET": 0x1A, "LBR": 0x1A, 135 | "RBRACKET": 0x1B, "RBR": 0x1B, 136 | "SEMICOLON": 0x27, "SEM": 0x27, 137 | "APOSTROPHE": 0x28, "APO": 0x28, 138 | "GRAVE": 0x29, "GRA": 0x29, 139 | "BACKSLASH": 0x2B, "BSL": 0x2B, 140 | "COMMA": 0x33, "COM": 0x33, 141 | "PERIOD": 0x34, "PER": 0x34, 142 | "SLASH": 0x35, "SLA": 0x35, 143 | } 144 | 145 | # virtual keys 146 | vk = { 147 | "1": 0x31, 148 | "2": 0x32, 149 | "3": 0x33, 150 | "4": 0x34, 151 | "5": 0x35, 152 | "6": 0x36, 153 | "7": 0x37, 154 | "8": 0x38, 155 | "9": 0x39, 156 | "0": 0x30, 157 | 158 | "NUMPAD1": 0x61, "NP1": 0x61, 159 | "NUMPAD2": 0x62, "NP2": 0x62, 160 | "NUMPAD3": 0x63, "NP3": 0x63, 161 | "NUMPAD4": 0x64, "NP4": 0x64, 162 | "NUMPAD5": 0x65, "NP5": 0x65, 163 | "NUMPAD6": 0x66, "NP6": 0x66, 164 | "NUMPAD7": 0x67, "NP7": 0x67, 165 | "NUMPAD8": 0x68, "NP8": 0x68, 166 | "NUMPAD9": 0x69, "NP9": 0x69, 167 | "NUMPAD0": 0x60, "NP0": 0x60, 168 | "DIVIDE": 0x6F, "NPDV": 0x6F, 169 | "MULTIPLY": 0x6A, "NPM": 0x6A, 170 | "SUBSTRACT": 0x6D, "NPS": 0x6D, 171 | "ADD": 0x6B, "NPA": 0x6B, 172 | "DECIMAL": 0x6E, "NPDC": 0x6E, 173 | "NUMPADENTER": 0x0D, "NPE": 0x0D, 174 | 175 | "A": 0x41, 176 | "B": 0x42, 177 | "C": 0x43, 178 | "D": 0x44, 179 | "E": 0x45, 180 | "F": 0x46, 181 | "G": 0x47, 182 | "H": 0x48, 183 | "I": 0x49, 184 | "J": 0x4A, 185 | "K": 0x4B, 186 | "L": 0x4C, 187 | "M": 0x4D, 188 | "N": 0x4E, 189 | "O": 0x4F, 190 | "P": 0x50, 191 | "Q": 0x51, 192 | "R": 0x52, 193 | "S": 0x53, 194 | "T": 0x54, 195 | "U": 0x55, 196 | "V": 0x56, 197 | "W": 0x57, 198 | "X": 0x58, 199 | "Y": 0x59, 200 | "Z": 0x5A, 201 | 202 | "F1": 0x70, 203 | "F2": 0x71, 204 | "F3": 0x72, 205 | "F4": 0x73, 206 | "F5": 0x74, 207 | "F6": 0x75, 208 | "F7": 0x76, 209 | "F8": 0x77, 210 | "F9": 0x78, 211 | "F10": 0x79, 212 | "F11": 0x7A, 213 | "F12": 0x7B, 214 | 215 | "UP": 0x26, 216 | "LEFT": 0x25, 217 | "RIGHT": 0x27, 218 | "DOWN": 0x28, 219 | 220 | "ESC": 0x1B, 221 | "SPACE": 0x20, "SPC": 0x20, 222 | "RETURN": 0x0D, "ENT": 0x0D, 223 | "INSERT": 0x2D, "INS": 0x2D, 224 | "DELETE": 0x2E, "DEL": 0x2E, 225 | "HOME": 0x24, 226 | "END": 0x23, 227 | "PRIOR": 0x21, "PGUP": 0x21, 228 | "NEXT": 0x22, "PGDN": 0x22, 229 | "BACK": 0x08, 230 | "TAB": 0x09, 231 | "LCONTROL": 0xA2, "LCTRL": 0xA2, 232 | "RCONTROL": 0xA3, "RCTRL": 0xA3, 233 | "LSHIFT": 0xA0, "LSH": 0xA0, 234 | "RSHIFT": 0xA1, "RSH": 0xA1, 235 | "LMENU": 0xA4, "LALT": 0xA4, 236 | "RMENU": 0xA5, "RALT": 0xA5, 237 | "LWIN": 0x5B, 238 | "RWIN": 0x5C, 239 | "APPS": 0x5D, 240 | "CAPITAL": 0x14, "CAPS": 0x14, 241 | "NUMLOCK": 0x90, "NUM": 0x90, 242 | "SCROLL": 0x91, "SCR": 0x91, 243 | 244 | "MINUS": 0xBD, "MIN": 0xBD, 245 | "LBRACKET": 0xDB, "LBR": 0xDB, 246 | "RBRACKET": 0xDD, "RBR": 0xDD, 247 | "SEMICOLON": 0xBA, "SEM": 0xBA, 248 | "APOSTROPHE": 0xDE, "APO": 0xDE, 249 | "GRAVE": 0xC0, "GRA": 0xC0, 250 | "BACKSLASH": 0xDC, "BSL": 0xDC, 251 | "COMMA": 0xBC, "COM": 0xBC, 252 | "PERIOD": 0xBE, "PER": 0xBE, 253 | "SLASH": 0xBF, "SLA": 0xBF, 254 | } 255 | 256 | # setup object 257 | def __init__(self, common = None): 258 | self.keys_worker = KeysWorker(self) 259 | # Thread(target=self.keys_worker.processQueue).start() 260 | self.common = common 261 | if common is None: 262 | self.standalone = True 263 | 264 | # parses keys string and adds keys to the queue 265 | def parseKeyString(self, string): 266 | 267 | # print keys 268 | if not self.standalone: 269 | self.common.info("Processing keys: %s" % string) 270 | 271 | key_queue = [] 272 | errors = [] 273 | 274 | # defaults to direct keys 275 | key_type = self.direct_keys 276 | 277 | # split by comma 278 | keys = string.upper().split(",") 279 | 280 | # translate 281 | for key in keys: 282 | 283 | # up, down or stroke? 284 | up = True 285 | down = True 286 | direction = key.split("_") 287 | subkey = direction[0] 288 | if len(direction) >= 2: 289 | if direction[1] == 'UP': 290 | down = False 291 | else: 292 | up = False 293 | 294 | # switch to virtual keys 295 | if subkey == "VK": 296 | key_type = self.virtual_keys 297 | 298 | # switch to direct keys 299 | elif subkey == "DK": 300 | key_type = self.direct_keys 301 | 302 | # key code 303 | elif subkey.startswith("0x"): 304 | subkey = int(subkey, 16) 305 | if subkey > 0 and subkey < 256: 306 | key_queue.append({ 307 | "key": int(subkey), 308 | "okey": subkey, 309 | "time": 0, 310 | "up": up, 311 | "down": down, 312 | "type": key_type, 313 | }) 314 | else: 315 | errors.append(key) 316 | 317 | # pause 318 | elif subkey.startswith("-"): 319 | time = float(subkey.replace("-", ""))/1000 320 | if time > 0 and time <= 10: 321 | key_queue.append({ 322 | "key": None, 323 | "okey": "", 324 | "time": time, 325 | "up": False, 326 | "down": False, 327 | "type": None, 328 | }) 329 | else: 330 | errors.append(key) 331 | 332 | # direct key 333 | elif key_type == self.direct_keys and subkey in self.dk: 334 | key_queue.append({ 335 | "key": self.dk[subkey], 336 | "okey": subkey, 337 | "time": 0, 338 | "up": up, 339 | "down": down, 340 | "type": key_type, 341 | }) 342 | 343 | # virtual key 344 | elif key_type == self.virtual_keys and subkey in self.vk: 345 | key_queue.append({ 346 | "key": self.vk[subkey], 347 | "okey": subkey, 348 | "time": 0, 349 | "up": up, 350 | "down": down, 351 | "type": key_type, 352 | }) 353 | 354 | # no match? 355 | else: 356 | errors.append(key) 357 | 358 | # if there are errors, do not process keys 359 | if len(errors): 360 | return errors 361 | 362 | # create new thread if there is no active one 363 | if self.keys_process is None or not self.keys_process.isAlive(): 364 | self.keys_process = Thread(target=self.keys_worker.processQueue) 365 | self.keys_process.start() 366 | 367 | # add keys to queue 368 | for i in key_queue: 369 | self.keys_worker.key_queue.put(i) 370 | self.keys_worker.key_queue.put(None) 371 | 372 | return True 373 | 374 | # direct key press 375 | def directKey(self, key, direction = None, type = None): 376 | if type is None: 377 | type = self.direct_keys 378 | if direction is None: 379 | direction = self.key_press 380 | if key.startswith("0x"): 381 | key = int(key, 16) 382 | else: 383 | key = key.upper() 384 | lookup_table = self.dk if type == self.direct_keys else self.vk 385 | key = lookup_table[key] if key in lookup_table else 0x0000 386 | 387 | self.keys_worker.sendKey(key, direction | type) 388 | 389 | # direct mouse move or button press 390 | def directMouse(self, dx = 0, dy = 0, buttons = 0): 391 | self.keys_worker.sendMouse(dx, dy, buttons) 392 | 393 | 394 | # threaded sending keys class 395 | class KeysWorker(): 396 | 397 | # keys object 398 | keys = None 399 | 400 | # queue of keys 401 | key_queue = Queue() 402 | 403 | # init 404 | def __init__(self, keys): 405 | self.keys = keys 406 | 407 | # main function, process key's queue in loop 408 | def processQueue(self): 409 | 410 | # endless loop 411 | while True: 412 | 413 | # get one key 414 | key = self.key_queue.get() 415 | 416 | # terminate process if queue is empty 417 | if key is None: 418 | self.key_queue.task_done() 419 | if self.key_queue.empty(): 420 | return 421 | continue 422 | # print key 423 | elif not self.keys.standalone: 424 | self.keys.common.info("Key: \033[1;35m%s/%s\033[0;37m, duration: \033[1;35m%f\033[0;37m, direction: \033[1;35m%s\033[0;37m, type: \033[1;35m%s" % ( 425 | key["okey"] if key["okey"] else "None", 426 | key["key"], key["time"], 427 | "UP" if key["up"] and not key["down"] else "DOWN" if not key["up"] and key["down"] else "BOTH" if key["up"] and key["down"] else "NONE", 428 | "None" if key["type"] is None else "DK" if key["type"] == self.keys.direct_keys else "VK"), "\033[0;35mKEY: \033[0;37m" 429 | ) 430 | 431 | # if it's a key 432 | if key["key"]: 433 | 434 | # press 435 | if key["down"]: 436 | self.sendKey(key["key"], self.keys.key_press | key["type"]) 437 | 438 | # wait 439 | sleep(key["time"]) 440 | 441 | # and release 442 | if key["up"]: 443 | self.sendKey(key["key"], self.keys.key_release | key["type"]) 444 | 445 | # not an actual key, just pause 446 | else: 447 | sleep(key["time"]) 448 | 449 | # mark as done (decrement internal queue counter) 450 | self.key_queue.task_done() 451 | 452 | # send key 453 | def sendKey(self, key, type): 454 | self.SendInput(self.Keyboard(key, type)) 455 | 456 | # send mouse 457 | def sendMouse(self, dx, dy, buttons): 458 | if dx != 0 or dy != 0: 459 | buttons |= self.keys.mouse_move 460 | self.SendInput(self.Mouse(buttons, dx, dy)) 461 | 462 | # send input 463 | def SendInput(self, *inputs): 464 | nInputs = len(inputs) 465 | LPINPUT = INPUT * nInputs 466 | pInputs = LPINPUT(*inputs) 467 | cbSize = ctypes.c_int(ctypes.sizeof(INPUT)) 468 | return ctypes.windll.user32.SendInput(nInputs, pInputs, cbSize) 469 | 470 | # get input object 471 | def Input(self, structure): 472 | if isinstance(structure, MOUSEINPUT): 473 | return INPUT(0, _INPUTunion(mi=structure)) 474 | if isinstance(structure, KEYBDINPUT): 475 | return INPUT(1, _INPUTunion(ki=structure)) 476 | if isinstance(structure, HARDWAREINPUT): 477 | return INPUT(2, _INPUTunion(hi=structure)) 478 | raise TypeError('Cannot create INPUT structure!') 479 | 480 | # mouse input 481 | def MouseInput(self, flags, x, y, data): 482 | return MOUSEINPUT(x, y, data, flags, 0, None) 483 | 484 | # keyboard input 485 | def KeybdInput(self, code, flags): 486 | return KEYBDINPUT(code, code, flags, 0, None) 487 | 488 | # hardware input 489 | def HardwareInput(self, message, parameter): 490 | return HARDWAREINPUT(message & 0xFFFFFFFF, 491 | parameter & 0xFFFF, 492 | parameter >> 16 & 0xFFFF) 493 | 494 | # mouse object 495 | def Mouse(self, flags, x=0, y=0, data=0): 496 | return self.Input(self.MouseInput(flags, x, y, data)) 497 | 498 | # keyboard object 499 | def Keyboard(self, code, flags=0): 500 | return self.Input(self.KeybdInput(code, flags)) 501 | 502 | # hardware object 503 | def Hardware(self, message, parameter=0): 504 | return self.Input(self.HardwareInput(message, parameter)) 505 | 506 | 507 | # types 508 | LONG = ctypes.c_long 509 | DWORD = ctypes.c_ulong 510 | ULONG_PTR = ctypes.POINTER(DWORD) 511 | WORD = ctypes.c_ushort 512 | 513 | 514 | class MOUSEINPUT(ctypes.Structure): 515 | _fields_ = (('dx', LONG), 516 | ('dy', LONG), 517 | ('mouseData', DWORD), 518 | ('dwFlags', DWORD), 519 | ('time', DWORD), 520 | ('dwExtraInfo', ULONG_PTR)) 521 | 522 | 523 | class KEYBDINPUT(ctypes.Structure): 524 | _fields_ = (('wVk', WORD), 525 | ('wScan', WORD), 526 | ('dwFlags', DWORD), 527 | ('time', DWORD), 528 | ('dwExtraInfo', ULONG_PTR)) 529 | 530 | 531 | class HARDWAREINPUT(ctypes.Structure): 532 | _fields_ = (('uMsg', DWORD), 533 | ('wParamL', WORD), 534 | ('wParamH', WORD)) 535 | 536 | 537 | class _INPUTunion(ctypes.Union): 538 | _fields_ = (('mi', MOUSEINPUT), 539 | ('ki', KEYBDINPUT), 540 | ('hi', HARDWAREINPUT)) 541 | 542 | 543 | class INPUT(ctypes.Structure): 544 | _fields_ = (('type', DWORD), 545 | ('union', _INPUTunion)) 546 | 547 | 548 | 549 | #example: 550 | if __name__ == '__main__': 551 | sleep(3) 552 | keys = Keys() 553 | 554 | # mouse movement 555 | for i in range(100): 556 | keys.directMouse(-1*i, 0) 557 | sleep(0.004) 558 | 559 | # mouse keys 560 | keys.directMouse(buttons=keys.mouse_rb_press) 561 | sleep(0.5) 562 | keys.directMouse(buttons=keys.mouse_lb_press) 563 | sleep(2) 564 | keys.directMouse(buttons=keys.mouse_lb_release) 565 | sleep(0.5) 566 | keys.directMouse(buttons=keys.mouse_rb_release) 567 | 568 | # or 569 | keys.directMouse(buttons=keys.mouse_lb_press | keys.mouse_rb_press) 570 | sleep(2) 571 | keys.directMouse(buttons=keys.mouse_lb_release | keys.mouse_rb_release) 572 | 573 | # keyboard (direct keys) 574 | keys.directKey("a") 575 | sleep(0.04) 576 | keys.directKey("a", keys.key_release) 577 | 578 | # keyboard (virtual keys) 579 | keys.directKey("a", type=keys.virtual_keys) 580 | sleep(0.04) 581 | keys.directKey("a", keys.key_release, keys.virtual_keys) 582 | 583 | # queue of keys (direct keys, threaded, only for keybord input) 584 | keys.parseKeyString("a_down,-4,a_up,0x01") # -4 - pause for 4 ms, 0x00 - hex code of Esc 585 | 586 | # queue of keys (virtual keys, threaded, only for keybord input) 587 | keys.parseKeyString("vk,a_down,-4,a_up") # -4 - pause for 4 ms 588 | 589 | --------------------------------------------------------------------------------