Files
SimpleRemoter/macos/InputHandler.mm

397 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) {
// 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
}
}