400 lines
13 KiB
Plaintext
400 lines
13 KiB
Plaintext
#import "InputHandler.h"
|
|
#import "Permissions.h"
|
|
#import <Cocoa/Cocoa.h>
|
|
#import <Carbon/Carbon.h>
|
|
#include <unistd.h> // 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) {
|
|
CGEventPost(kCGHIDEventTap, event);
|
|
CFRelease(event);
|
|
}
|
|
}
|
|
|
|
void InputHandler::handleMouseDoubleClick(CGMouseButton button, int x, int y)
|
|
{
|
|
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;
|
|
}
|
|
|
|
// First click (clickState=1)
|
|
CGEventRef down1 = CGEventCreateMouseEvent(NULL, downType, point, button);
|
|
CGEventRef up1 = CGEventCreateMouseEvent(NULL, upType, point, button);
|
|
|
|
if (down1 && up1) {
|
|
CGEventSetIntegerValueField(down1, kCGMouseEventClickState, 1);
|
|
CGEventSetIntegerValueField(up1, kCGMouseEventClickState, 1);
|
|
CGEventPost(kCGHIDEventTap, down1);
|
|
CGEventPost(kCGHIDEventTap, up1);
|
|
}
|
|
|
|
if (down1) CFRelease(down1);
|
|
if (up1) CFRelease(up1);
|
|
|
|
// Brief delay between clicks (50ms)
|
|
usleep(50000);
|
|
|
|
// Second click (clickState=2)
|
|
CGEventRef down2 = CGEventCreateMouseEvent(NULL, downType, point, button);
|
|
CGEventRef up2 = CGEventCreateMouseEvent(NULL, upType, point, button);
|
|
|
|
if (down2 && up2) {
|
|
CGEventSetIntegerValueField(down2, kCGMouseEventClickState, 2);
|
|
CGEventSetIntegerValueField(up2, kCGMouseEventClickState, 2);
|
|
CGEventPost(kCGHIDEventTap, down2);
|
|
CGEventPost(kCGHIDEventTap, up2);
|
|
}
|
|
|
|
if (down2) CFRelease(down2);
|
|
if (up2) CFRelease(up2);
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|