#import "InputHandler.h" #import "Permissions.h" #import #import #include // for usleep InputHandler::InputHandler() : m_lastMousePos(CGPointZero) { // atomic members are initialized in class declaration } InputHandler::~InputHandler() { } bool InputHandler::init() { m_hasPermission = Permissions::checkAccessibility(); if (!m_hasPermission) { NSLog(@"InputHandler: Accessibility permission not granted"); // Request permission (shows system dialog) Permissions::requestAccessibility(); } return m_hasPermission; } void InputHandler::handleInputEvent(const MSG64_MAC* msg) { if (!m_hasPermission) { // Re-check permission m_hasPermission = Permissions::checkAccessibility(); if (!m_hasPermission) { if (!m_warningLogged) { NSLog(@"InputHandler: Cannot handle input - no accessibility permission"); m_warningLogged = true; } return; } m_warningLogged = false; } uint32_t message = (uint32_t)msg->message; // Extract coordinates from lParam (MAKELPARAM format: low=x, high=y) int x = (int)(msg->lParam & 0xFFFF); int y = (int)((msg->lParam >> 16) & 0xFFFF); switch (message) { // Mouse movement case WM_MOUSEMOVE: handleMouseMove(x, y); break; // Left button case WM_LBUTTONDOWN: handleMouseButton(kCGMouseButtonLeft, true, x, y); break; case WM_LBUTTONUP: handleMouseButton(kCGMouseButtonLeft, false, x, y); break; case WM_LBUTTONDBLCLK: handleMouseDoubleClick(kCGMouseButtonLeft, x, y); break; // Right button case WM_RBUTTONDOWN: handleMouseButton(kCGMouseButtonRight, true, x, y); break; case WM_RBUTTONUP: handleMouseButton(kCGMouseButtonRight, false, x, y); break; case WM_RBUTTONDBLCLK: handleMouseDoubleClick(kCGMouseButtonRight, x, y); break; // Middle button case WM_MBUTTONDOWN: handleMouseButton(kCGMouseButtonCenter, true, x, y); break; case WM_MBUTTONUP: handleMouseButton(kCGMouseButtonCenter, false, x, y); break; case WM_MBUTTONDBLCLK: handleMouseDoubleClick(kCGMouseButtonCenter, x, y); break; // Mouse wheel case WM_MOUSEWHEEL: { short delta = GET_WHEEL_DELTA_WPARAM(msg->wParam); handleMouseWheel(delta); break; } // Keyboard case WM_KEYDOWN: case WM_SYSKEYDOWN: handleKeyEvent((uint32_t)msg->wParam, true); break; case WM_KEYUP: case WM_SYSKEYUP: handleKeyEvent((uint32_t)msg->wParam, false); break; } } void InputHandler::handleMouseMove(int x, int y) { CGPoint point = CGPointMake(x, y); m_lastMousePos = point; CGEventType eventType = kCGEventMouseMoved; CGMouseButton button = kCGMouseButtonLeft; // If button is held, use drag event if (m_leftButtonDown) { eventType = kCGEventLeftMouseDragged; button = kCGMouseButtonLeft; } else if (m_rightButtonDown) { eventType = kCGEventRightMouseDragged; button = kCGMouseButtonRight; } else if (m_middleButtonDown) { eventType = kCGEventOtherMouseDragged; button = kCGMouseButtonCenter; } CGEventRef event = CGEventCreateMouseEvent(NULL, eventType, point, button); if (event) { CGEventPost(kCGHIDEventTap, event); CFRelease(event); } } void InputHandler::handleMouseButton(CGMouseButton button, bool down, int x, int y) { CGPoint point = CGPointMake(x, y); m_lastMousePos = point; CGEventType eventType; switch (button) { case kCGMouseButtonLeft: eventType = down ? kCGEventLeftMouseDown : kCGEventLeftMouseUp; m_leftButtonDown = down; break; case kCGMouseButtonRight: eventType = down ? kCGEventRightMouseDown : kCGEventRightMouseUp; m_rightButtonDown = down; break; case kCGMouseButtonCenter: default: eventType = down ? kCGEventOtherMouseDown : kCGEventOtherMouseUp; m_middleButtonDown = down; break; } CGEventRef event = CGEventCreateMouseEvent(NULL, eventType, point, button); if (event) { // clickState=1 for all single clicks CGEventSetIntegerValueField(event, kCGMouseEventClickState, 1); CGEventPost(kCGHIDEventTap, event); CFRelease(event); } } void InputHandler::handleMouseDoubleClick(CGMouseButton button, int x, int y) { // WM_LBUTTONDBLCLK represents the second click of a double-click. // The first click was already sent via WM_LBUTTONDOWN/WM_LBUTTONUP. // // We send complete down(2) + up(2) here because: // - Web client: dblclick fires AFTER mouseup, no subsequent WM_LBUTTONUP // - MFC client: WM_LBUTTONUP follows, but extra up(1) is harmless CGPoint point = CGPointMake(x, y); m_lastMousePos = point; CGEventType downType, upType; switch (button) { case kCGMouseButtonLeft: downType = kCGEventLeftMouseDown; upType = kCGEventLeftMouseUp; break; case kCGMouseButtonRight: downType = kCGEventRightMouseDown; upType = kCGEventRightMouseUp; break; case kCGMouseButtonCenter: default: downType = kCGEventOtherMouseDown; upType = kCGEventOtherMouseUp; break; } // Send second click: down(2) + up(2) CGEventRef down = CGEventCreateMouseEvent(NULL, downType, point, button); CGEventRef up = CGEventCreateMouseEvent(NULL, upType, point, button); if (down) { CGEventSetIntegerValueField(down, kCGMouseEventClickState, 2); CGEventPost(kCGHIDEventTap, down); CFRelease(down); } if (up) { CGEventSetIntegerValueField(up, kCGMouseEventClickState, 2); CGEventPost(kCGHIDEventTap, up); CFRelease(up); } // Note: For MFC client, an extra WM_LBUTTONUP will follow (sending up(1)), // but this is harmless since mouse is already up. } void InputHandler::handleMouseWheel(int delta) { // Convert Windows wheel delta (120 = one notch) to macOS pixel units // Using pixel units provides smoother scrolling than line units // Windows: 120 = one standard notch // macOS: approximately 10 pixels per notch feels natural int32_t scrollAmount = (delta * 10) / 120; // Use pixel units for smoother scrolling experience CGEventRef event = CGEventCreateScrollWheelEvent( NULL, kCGScrollEventUnitPixel, 1, scrollAmount ); if (event) { CGEventPost(kCGHIDEventTap, event); CFRelease(event); } } void InputHandler::handleKeyEvent(uint32_t vkCode, bool down) { CGKeyCode keyCode = vkToMacKeyCode(vkCode); if (keyCode == 0xFF) { return; // Unknown key } // Update modifier flags based on key CGEventFlags flag = 0; switch (keyCode) { case kVK_Shift: case kVK_RightShift: flag = kCGEventFlagMaskShift; break; case kVK_Control: case kVK_RightControl: flag = kCGEventFlagMaskControl; break; case kVK_Option: case kVK_RightOption: flag = kCGEventFlagMaskAlternate; break; case kVK_Command: case kVK_RightCommand: flag = kCGEventFlagMaskCommand; break; case kVK_CapsLock: flag = kCGEventFlagMaskAlphaShift; break; } if (flag) { CGEventFlags current = m_modifierFlags.load(); if (down) { m_modifierFlags.store(current | flag); } else { m_modifierFlags.store(current & ~flag); } } CGEventRef event = CGEventCreateKeyboardEvent(NULL, keyCode, down); if (event) { // Set current modifier flags to ensure proper key combinations CGEventSetFlags(event, m_modifierFlags.load()); CGEventPost(kCGHIDEventTap, event); CFRelease(event); } } // Convert Windows VK code to macOS key code // Reference: Carbon/HIToolbox/Events.h CGKeyCode InputHandler::vkToMacKeyCode(uint32_t vk) { // Letters A-Z (VK 0x41-0x5A) if (vk >= 0x41 && vk <= 0x5A) { // macOS key codes for A-Z are not sequential static const CGKeyCode letterKeys[] = { kVK_ANSI_A, kVK_ANSI_B, kVK_ANSI_C, kVK_ANSI_D, kVK_ANSI_E, kVK_ANSI_F, kVK_ANSI_G, kVK_ANSI_H, kVK_ANSI_I, kVK_ANSI_J, kVK_ANSI_K, kVK_ANSI_L, kVK_ANSI_M, kVK_ANSI_N, kVK_ANSI_O, kVK_ANSI_P, kVK_ANSI_Q, kVK_ANSI_R, kVK_ANSI_S, kVK_ANSI_T, kVK_ANSI_U, kVK_ANSI_V, kVK_ANSI_W, kVK_ANSI_X, kVK_ANSI_Y, kVK_ANSI_Z }; return letterKeys[vk - 0x41]; } // Numbers 0-9 (VK 0x30-0x39) if (vk >= 0x30 && vk <= 0x39) { static const CGKeyCode numberKeys[] = { kVK_ANSI_0, kVK_ANSI_1, kVK_ANSI_2, kVK_ANSI_3, kVK_ANSI_4, kVK_ANSI_5, kVK_ANSI_6, kVK_ANSI_7, kVK_ANSI_8, kVK_ANSI_9 }; return numberKeys[vk - 0x30]; } // Numpad 0-9 (VK 0x60-0x69) if (vk >= 0x60 && vk <= 0x69) { static const CGKeyCode numpadKeys[] = { kVK_ANSI_Keypad0, kVK_ANSI_Keypad1, kVK_ANSI_Keypad2, kVK_ANSI_Keypad3, kVK_ANSI_Keypad4, kVK_ANSI_Keypad5, kVK_ANSI_Keypad6, kVK_ANSI_Keypad7, kVK_ANSI_Keypad8, kVK_ANSI_Keypad9 }; return numpadKeys[vk - 0x60]; } // F1-F12 (VK 0x70-0x7B) if (vk >= 0x70 && vk <= 0x7B) { static const CGKeyCode fKeys[] = { kVK_F1, kVK_F2, kVK_F3, kVK_F4, kVK_F5, kVK_F6, kVK_F7, kVK_F8, kVK_F9, kVK_F10, kVK_F11, kVK_F12 }; return fKeys[vk - 0x70]; } // Special keys switch (vk) { case 0x08: return kVK_Delete; // VK_BACK (Backspace) case 0x09: return kVK_Tab; // VK_TAB case 0x0D: return kVK_Return; // VK_RETURN case 0x10: return kVK_Shift; // VK_SHIFT case 0x11: return kVK_Control; // VK_CONTROL case 0x12: return kVK_Option; // VK_MENU (Alt -> Option) case 0x13: return kVK_F15; // VK_PAUSE (no direct equivalent) case 0x14: return kVK_CapsLock; // VK_CAPITAL case 0x1B: return kVK_Escape; // VK_ESCAPE case 0x20: return kVK_Space; // VK_SPACE case 0x21: return kVK_PageUp; // VK_PRIOR case 0x22: return kVK_PageDown; // VK_NEXT case 0x23: return kVK_End; // VK_END case 0x24: return kVK_Home; // VK_HOME case 0x25: return kVK_LeftArrow; // VK_LEFT case 0x26: return kVK_UpArrow; // VK_UP case 0x27: return kVK_RightArrow; // VK_RIGHT case 0x28: return kVK_DownArrow; // VK_DOWN case 0x2C: return kVK_F13; // VK_SNAPSHOT (PrintScreen) case 0x2D: return kVK_Help; // VK_INSERT (Help on Mac) case 0x2E: return kVK_ForwardDelete; // VK_DELETE // Windows keys -> Command case 0x5B: return kVK_Command; // VK_LWIN case 0x5C: return kVK_RightCommand; // VK_RWIN // Numpad operators case 0x6A: return kVK_ANSI_KeypadMultiply; // VK_MULTIPLY case 0x6B: return kVK_ANSI_KeypadPlus; // VK_ADD case 0x6D: return kVK_ANSI_KeypadMinus; // VK_SUBTRACT case 0x6E: return kVK_ANSI_KeypadDecimal; // VK_DECIMAL case 0x6F: return kVK_ANSI_KeypadDivide; // VK_DIVIDE // Lock keys case 0x90: return kVK_ANSI_KeypadClear; // VK_NUMLOCK (Clear on Mac) case 0x91: return kVK_F14; // VK_SCROLL // Shift variants case 0xA0: return kVK_Shift; // VK_LSHIFT case 0xA1: return kVK_RightShift; // VK_RSHIFT case 0xA2: return kVK_Control; // VK_LCONTROL case 0xA3: return kVK_RightControl; // VK_RCONTROL case 0xA4: return kVK_Option; // VK_LMENU case 0xA5: return kVK_RightOption; // VK_RMENU // OEM keys (US keyboard layout) case 0xBA: return kVK_ANSI_Semicolon; // VK_OEM_1 (;:) case 0xBB: return kVK_ANSI_Equal; // VK_OEM_PLUS (=+) case 0xBC: return kVK_ANSI_Comma; // VK_OEM_COMMA (,<) case 0xBD: return kVK_ANSI_Minus; // VK_OEM_MINUS (-_) case 0xBE: return kVK_ANSI_Period; // VK_OEM_PERIOD (.>) case 0xBF: return kVK_ANSI_Slash; // VK_OEM_2 (/?) case 0xC0: return kVK_ANSI_Grave; // VK_OEM_3 (`~) case 0xDB: return kVK_ANSI_LeftBracket; // VK_OEM_4 ([{) case 0xDC: return kVK_ANSI_Backslash; // VK_OEM_5 (\|) case 0xDD: return kVK_ANSI_RightBracket; // VK_OEM_6 (]}) case 0xDE: return kVK_ANSI_Quote; // VK_OEM_7 ('") default: return 0xFF; // Unknown key } }