from sneakysnek.recorder import Recorder from sneakysnek.keyboard_keys import KeyboardKey from sneakysnek.mouse_buttons import MouseButton from sneakysnek.keyboard_event import KeyboardEvent, KeyboardEvents from sneakysnek.mouse_event import MouseEvent, MouseEvents import ctypes from ctypes import windll, wintypes import threading import atexit class WindowsRecorder(Recorder): def __init__(self, callback): self.callback = callback self.is_recording = False self.thread = None self.thread_id = None self.keyboard_hook = None self.mouse_hook = None def start(self): self.is_recording = True self.thread_id = windll.kernel32.GetCurrentThreadId() self.register_hooks() self.listen() def stop(self): PostThreadMessage(self.thread_id, 0x0401, 0, 0) if self.keyboard_hook is not None: UnhookWindowsHookEx(self.keyboard_hook) if self.mouse_hook is not None: UnhookWindowsHookEx(self.mouse_hook) self.is_recording = False def event_handler(self, event): self.callback(event) def register_hooks(self): self._register_keyboard_hook() self._register_mouse_hook() def listen(self): while True: message = wintypes.MSG() message_pointer = ctypes.byref(message) if GetMessage(message_pointer, None, 0, 0) <= 0 or message.message == 0x0401: break def _register_keyboard_hook(self): def low_level_callback(code, wparam, lparam): scan_code = (lparam.contents.scanCode + (lparam.contents.flags & 1) * 1024) if scan_code in keyboard_scan_code_mapping and wparam in [256, 257]: self.event_handler(KeyboardEvent( KeyboardEvents.DOWN if wparam == 256 else KeyboardEvents.UP, keyboard_scan_code_mapping[scan_code] )) return CallNextHookEx(None, code, wparam, lparam) callback = LowLevelKeyboardProc(low_level_callback) self.keyboard_hook = SetWindowsHookEx(13, callback, None, None) atexit.register(UnhookWindowsHookEx, callback) def _register_mouse_hook(self): def low_level_callback(code, wparam, lparam): if wparam == 512: self.event_handler(MouseEvent( MouseEvents.MOVE, x=lparam.contents.pt.x, y=lparam.contents.pt.y )) elif wparam == 522: self.event_handler(MouseEvent( MouseEvents.SCROLL, direction="UP" if (wintypes.SHORT(lparam.contents.mouseData >> 16).value // 120) == 1 else "DOWN", velocity=1, x=lparam.contents.pt.x, y=lparam.contents.pt.y )) elif wparam in [513, 514, 516, 517, 519, 520]: self.event_handler(MouseEvent( MouseEvents.CLICK, button=mouse_button_mapping[wparam], direction="DOWN" if wparam in [513, 516, 519] else "UP", x=lparam.contents.pt.x, y=lparam.contents.pt.y )) return CallNextHookEx(None, code, wparam, lparam) callback = LowLevelMouseProc(low_level_callback) self.mouse_hook = SetWindowsHookEx(14, callback, None, None) atexit.register(UnhookWindowsHookEx, callback) @classmethod def record(cls, callback): recorder = cls(callback) recorder.thread = threading.Thread(target=recorder.start, args=()) recorder.thread.daemon = True recorder.thread.start() return recorder keyboard_scan_code_mapping = { 1: KeyboardKey.KEY_ESCAPE, 59: KeyboardKey.KEY_F1, 60: KeyboardKey.KEY_F2, 61: KeyboardKey.KEY_F3, 62: KeyboardKey.KEY_F4, 63: KeyboardKey.KEY_F5, 64: KeyboardKey.KEY_F6, 65: KeyboardKey.KEY_F7, 66: KeyboardKey.KEY_F8, 67: KeyboardKey.KEY_F9, 68: KeyboardKey.KEY_F10, 87: KeyboardKey.KEY_F11, 88: KeyboardKey.KEY_F12, 1079: KeyboardKey.KEY_PRINT_SCREEN, 70: KeyboardKey.KEY_SCROLL_LOCK, 69: KeyboardKey.KEY_PAUSE, 41: KeyboardKey.KEY_GRAVE, 2: KeyboardKey.KEY_1, 3: KeyboardKey.KEY_2, 4: KeyboardKey.KEY_3, 5: KeyboardKey.KEY_4, 6: KeyboardKey.KEY_5, 7: KeyboardKey.KEY_6, 8: KeyboardKey.KEY_7, 9: KeyboardKey.KEY_8, 10: KeyboardKey.KEY_9, 11: KeyboardKey.KEY_0, 12: KeyboardKey.KEY_MINUS, 13: KeyboardKey.KEY_EQUALS, 14: KeyboardKey.KEY_BACKSPACE, 1106: KeyboardKey.KEY_INSERT, 1095: KeyboardKey.KEY_HOME, 1097: KeyboardKey.KEY_PAGE_UP, 1093: KeyboardKey.KEY_NUMLOCK, 1077: KeyboardKey.KEY_NUMPAD_DIVIDE, 55: KeyboardKey.KEY_NUMPAD_MULTIPLY, 74: KeyboardKey.KEY_NUMPAD_SUBTRACT, 15: KeyboardKey.KEY_TAB, 16: KeyboardKey.KEY_Q, 17: KeyboardKey.KEY_W, 18: KeyboardKey.KEY_E, 19: KeyboardKey.KEY_R, 20: KeyboardKey.KEY_T, 21: KeyboardKey.KEY_Y, 22: KeyboardKey.KEY_U, 23: KeyboardKey.KEY_I, 24: KeyboardKey.KEY_O, 25: KeyboardKey.KEY_P, 26: KeyboardKey.KEY_LEFT_BRACKET, 27: KeyboardKey.KEY_RIGHT_BRACKET, 43: KeyboardKey.KEY_BACKSLASH, 1107: KeyboardKey.KEY_DELETE, 1103: KeyboardKey.KEY_END, 1105: KeyboardKey.KEY_PAGE_DOWN, 71: KeyboardKey.KEY_NUMPAD_7, 72: KeyboardKey.KEY_NUMPAD_8, 73: KeyboardKey.KEY_NUMPAD_9, 78: KeyboardKey.KEY_NUMPAD_ADD, 58: KeyboardKey.KEY_CAPSLOCK, 30: KeyboardKey.KEY_A, 31: KeyboardKey.KEY_S, 32: KeyboardKey.KEY_D, 33: KeyboardKey.KEY_F, 34: KeyboardKey.KEY_G, 35: KeyboardKey.KEY_H, 36: KeyboardKey.KEY_J, 37: KeyboardKey.KEY_K, 38: KeyboardKey.KEY_L, 39: KeyboardKey.KEY_SEMICOLON, 40: KeyboardKey.KEY_APOSTROPHE, 28: KeyboardKey.KEY_RETURN, 75: KeyboardKey.KEY_NUMPAD_4, 76: KeyboardKey.KEY_NUMPAD_5, 77: KeyboardKey.KEY_NUMPAD_6, 42: KeyboardKey.KEY_LEFT_SHIFT, 44: KeyboardKey.KEY_Z, 45: KeyboardKey.KEY_X, 46: KeyboardKey.KEY_C, 47: KeyboardKey.KEY_V, 48: KeyboardKey.KEY_B, 49: KeyboardKey.KEY_N, 50: KeyboardKey.KEY_M, 51: KeyboardKey.KEY_COMMA, 52: KeyboardKey.KEY_PERIOD, 53: KeyboardKey.KEY_SLASH, 1078: KeyboardKey.KEY_RIGHT_SHIFT, 1096: KeyboardKey.KEY_UP, 79: KeyboardKey.KEY_NUMPAD_1, 80: KeyboardKey.KEY_NUMPAD_2, 81: KeyboardKey.KEY_NUMPAD_3, 1052: KeyboardKey.KEY_NUMPAD_RETURN, 29: KeyboardKey.KEY_LEFT_CTRL, 56: KeyboardKey.KEY_LEFT_ALT, 57: KeyboardKey.KEY_SPACE, 1080: KeyboardKey.KEY_RIGHT_ALT, 1053: KeyboardKey.KEY_RIGHT_CTRL, 1099: KeyboardKey.KEY_LEFT, 1104: KeyboardKey.KEY_DOWN, 1101: KeyboardKey.KEY_RIGHT, 82: KeyboardKey.KEY_NUMPAD_0, 83: KeyboardKey.KEY_NUMPAD_PERIOD, 91: KeyboardKey.KEY_LEFT_WINDOWS, 1116: KeyboardKey.KEY_RIGHT_WINDOWS, 1117: KeyboardKey.KEY_APP_MENU } mouse_button_mapping = { 513: MouseButton.LEFT, 514: MouseButton.LEFT, 516: MouseButton.RIGHT, 517: MouseButton.RIGHT, 519: MouseButton.MIDDLE, 520: MouseButton.MIDDLE } class KBDLLHOOKSTRUCT(ctypes.Structure): _fields_ = [ ("vkCode", wintypes.DWORD), ("scanCode", wintypes.DWORD), ("flags", wintypes.DWORD), ("time", wintypes.DWORD), ("dwExtraInfo", ctypes.c_void_p) ] class MSLLHOOKSTRUCT(ctypes.Structure): _fields_ = [ ("pt", wintypes.POINT), ("mouseData", wintypes.DWORD), ("flags", wintypes.DWORD), ("time", wintypes.DWORD), ("dwExtraInfo", ctypes.c_void_p) ] LPMSG = ctypes.POINTER(wintypes.MSG) LowLevelKeyboardProc = ctypes.CFUNCTYPE(wintypes.INT, wintypes.WPARAM, wintypes.LPARAM, ctypes.POINTER(KBDLLHOOKSTRUCT)) LowLevelMouseProc = ctypes.CFUNCTYPE(wintypes.INT, wintypes.WPARAM, wintypes.LPARAM, ctypes.POINTER(MSLLHOOKSTRUCT)) GetMessage = windll.user32.GetMessageW PostThreadMessage = windll.user32.PostThreadMessageW SetWindowsHookEx = windll.user32.SetWindowsHookExA UnhookWindowsHookEx = windll.user32.UnhookWindowsHookEx CallNextHookEx = windll.user32.CallNextHookEx