From 9515338c753515b0751a9accfb1efe2006d5d104 Mon Sep 17 00:00:00 2001 From: "syed.amer@gilani.eu" Date: Sun, 1 Nov 2009 19:15:10 +0000 Subject: [PATCH] Update input-event handing to Quartz-taps on OS X Created by mthiel Issue #238 --- lib/platform/COSXKeyState.cpp | 94 ++++++----- lib/platform/COSXKeyState.h | 8 +- lib/platform/COSXScreen.cpp | 309 ++++++++++++++-------------------- lib/platform/COSXScreen.h | 15 +- 4 files changed, 203 insertions(+), 223 deletions(-) diff --git a/lib/platform/COSXKeyState.cpp b/lib/platform/COSXKeyState.cpp index 7178a68e..b9d48e20 100644 --- a/lib/platform/COSXKeyState.cpp +++ b/lib/platform/COSXKeyState.cpp @@ -138,40 +138,57 @@ COSXKeyState::mapModifiersFromOSX(UInt32 mask) const LOG((CLOG_DEBUG1 "mask: %04x", mask)); // convert KeyModifierMask outMask = 0; - if ((mask & shiftKey) != 0) { + if ((mask & kCGEventFlagMaskShift) != 0) { outMask |= KeyModifierShift; } - if ((mask & rightShiftKey) != 0) { - outMask |= KeyModifierShift; - } - if ((mask & controlKey) != 0) { + if ((mask & kCGEventFlagMaskControl) != 0) { outMask |= KeyModifierControl; } - if ((mask & rightControlKey) != 0) { - outMask |= KeyModifierControl; - } - if ((mask & cmdKey) != 0) { + if ((mask & kCGEventFlagMaskAlternate) != 0) { outMask |= KeyModifierAlt; } - if ((mask & optionKey) != 0) { + if ((mask & kCGEventFlagMaskCommand) != 0) { outMask |= KeyModifierSuper; } - if ((mask & rightOptionKey) != 0) { - outMask |= KeyModifierSuper; - } - if ((mask & alphaLock) != 0) { + if ((mask & kCGEventFlagMaskAlphaShift) != 0) { outMask |= KeyModifierCapsLock; } - if ((mask & s_osxNumLock) != 0) { + if ((mask & kCGEventFlagMaskNumericPad) != 0) { outMask |= KeyModifierNumLock; } return outMask; } +KeyModifierMask +COSXKeyState::mapModifiersToCarbon(UInt32 mask) const +{ + KeyModifierMask outMask = 0; + if ((mask & kCGEventFlagMaskShift) != 0) { + outMask |= shiftKey; + } + if ((mask & kCGEventFlagMaskControl) != 0) { + outMask |= controlKey; + } + if ((mask & kCGEventFlagMaskCommand) != 0) { + outMask |= cmdKey; + } + if ((mask & kCGEventFlagMaskAlternate) != 0) { + outMask |= optionKey; + } + if ((mask & kCGEventFlagMaskAlphaShift) != 0) { + outMask |= alphaLock; + } + if ((mask & kCGEventFlagMaskNumericPad) != 0) { + outMask |= s_osxNumLock; + } + + return outMask; +} + KeyButton COSXKeyState::mapKeyFromEvent(CKeyIDs& ids, - KeyModifierMask* maskOut, EventRef event) const + KeyModifierMask* maskOut, CGEventRef event) const { ids.clear(); @@ -183,13 +200,11 @@ COSXKeyState::mapKeyFromEvent(CKeyIDs& ids, } // get virtual key - UInt32 vkCode; - GetEventParameter(event, kEventParamKeyCode, typeUInt32, - NULL, sizeof(vkCode), NULL, &vkCode); + UInt32 vkCode = CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode); // handle up events - UInt32 eventKind = GetEventKind(event); - if (eventKind == kEventRawKeyUp) { + UInt32 eventKind = CGEventGetType(event); + if (eventKind == kCGEventKeyUp) { // the id isn't used. we just need the same button we used on // the key press. note that we don't use or reset the dead key // state; up events should not affect the dead key state. @@ -214,9 +229,9 @@ COSXKeyState::mapKeyFromEvent(CKeyIDs& ids, // get the event modifiers and remove the command and control // keys. note if we used them though. + // UCKeyTranslate expects old-style Carbon modifiers, so convert. UInt32 modifiers; - GetEventParameter(event, kEventParamKeyModifiers, typeUInt32, - NULL, sizeof(modifiers), NULL, &modifiers); + modifiers = mapModifiersToCarbon(CGEventGetFlags(event)); static const UInt32 s_commandModifiers = cmdKey | controlKey | rightControlKey; bool isCommand = ((modifiers & s_commandModifiers) != 0); @@ -224,24 +239,22 @@ COSXKeyState::mapKeyFromEvent(CKeyIDs& ids, // if we've used a command key then we want the glyph produced without // the option key (i.e. the base glyph). - if (isCommand) { + //if (isCommand) { modifiers &= ~optionKey; - } + //} // choose action UInt16 action; - switch (eventKind) { - case kEventRawKeyDown: + if(eventKind==kCGEventKeyDown) { action = kUCKeyActionDown; - break; - - case kEventRawKeyRepeat: + } + else if(CGEventGetIntegerValueField(event, kCGKeyboardEventAutorepeat)==1) { action = kUCKeyActionAutoKey; - break; - - default: + } + else { return 0; } + // translate via uchr resource CFDataRef ref = (CFDataRef) TISGetInputSourceProperty(currentKeyboardLayout, kTISPropertyUnicodeKeyLayoutData); @@ -251,12 +264,13 @@ COSXKeyState::mapKeyFromEvent(CKeyIDs& ids, // translate key UniCharCount count; UniChar chars[2]; + //LOG((CLOG_DEBUG "modifiers: %08x", modifiers & 0xffu)); OSStatus status = UCKeyTranslate(layout, vkCode & 0xffu, action, (modifiers >> 8) & 0xffu, LMGetKbdType(), 0, &m_deadKeyState, sizeof(chars) / sizeof(chars[0]), &count, chars); - + // get the characters if (status == 0) { if (count != 0 || m_deadKeyState == 0) { @@ -353,7 +367,8 @@ COSXKeyState::fakeKey(const Keystroke& keystroke) switch (keystroke.m_type) { case Keystroke::kButton: - LOG((CLOG_DEBUG1 " %03x (%08x) %s", keystroke.m_data.m_button.m_button, keystroke.m_data.m_button.m_client, keystroke.m_data.m_button.m_press ? "down" : "up")); + { + LOG((CLOG_DEBUG1 " %03x (%08x) %s", keystroke.m_data.m_button.m_button, keystroke.m_data.m_button.m_client, keystroke.m_data.m_button.m_press ? "down" : "up")); // let system figure out character for us ref = CGEventCreateKeyboardEvent(0, mapKeyButtonToVirtualKey( @@ -363,11 +378,12 @@ COSXKeyState::fakeKey(const Keystroke& keystroke) LOG((CLOG_CRIT "unable to create keyboard event for keystroke")); } - CGEventPost(kCGHIDEventTap, ref); + CGEventPost(kCGHIDEventTap, ref); - // add a delay if client data isn't zero - if (keystroke.m_data.m_button.m_client) { - ARCH->sleep(0.01); + // add a delay if client data isn't zero + if (keystroke.m_data.m_button.m_client) { + ARCH->sleep(0.01); + } } break; diff --git a/lib/platform/COSXKeyState.h b/lib/platform/COSXKeyState.h index bb0cd1e4..d1f4db8a 100644 --- a/lib/platform/COSXKeyState.h +++ b/lib/platform/COSXKeyState.h @@ -54,6 +54,12 @@ public: */ KeyModifierMask mapModifiersFromOSX(UInt32 mask) const; + //! Convert CG flags-style modifier mask to old-style Carbon + /*! + Still required in a few places for translation calls. + */ + KeyModifierMask mapModifiersToCarbon(UInt32 mask) const; + //! Map key event to keys /*! Converts a key event into a sequence of KeyIDs and the shadow modifier @@ -63,7 +69,7 @@ public: KeyID. */ KeyButton mapKeyFromEvent(CKeyIDs& ids, - KeyModifierMask* maskOut, EventRef event) const; + KeyModifierMask* maskOut, CGEventRef event) const; //! Map key and mask to native values /*! diff --git a/lib/platform/COSXScreen.cpp b/lib/platform/COSXScreen.cpp index a1a91079..a8e53cf6 100644 --- a/lib/platform/COSXScreen.cpp +++ b/lib/platform/COSXScreen.cpp @@ -85,42 +85,13 @@ COSXScreen::COSXScreen(bool isPrimary) : updateScreenShape(m_displayID, 0); m_screensaver = new COSXScreenSaver(getEventTarget()); m_keyState = new COSXKeyState(); - - if (m_isPrimary) { - // 1x1 window (to minimze the back buffer allocated for this - // window. - Rect bounds = { 100, 100, 101, 101 }; - - // m_hiddenWindow is a window meant to let us get mouse moves - // when the focus is on another computer. If you get your event - // from the application event target you'll get every mouse - // moves. On the other hand the Window event target will only - // get events when the mouse moves over the window. - - // The ignoreClicks attributes makes it impossible for the - // user to click on our invisible window. - CreateNewWindow(kUtilityWindowClass, - kWindowNoShadowAttribute | - kWindowIgnoreClicksAttribute | - kWindowNoActivatesAttribute, - &bounds, &m_hiddenWindow); - - // Make it invisible - SetWindowAlpha(m_hiddenWindow, 0); - ShowWindow(m_hiddenWindow); - - // m_userInputWindow is a window meant to let us get mouse moves - // when the focus is on this computer. - Rect inputBounds = { 100, 100, 200, 200 }; - CreateNewWindow(kUtilityWindowClass, - kWindowNoShadowAttribute | - kWindowOpaqueForEventsAttribute | - kWindowStandardHandlerAttribute, - &inputBounds, &m_userInputWindow); - - SetWindowAlpha(m_userInputWindow, 0); - } + if (m_isPrimary) { + if (!AXAPIEnabled()) { + LOG((CLOG_ERR "Synergy server requires accessibility API enabled. Please check the option for \"Enable access for assistive devices\" in the Universal Access System Preferences panel. Unintentional key-replication will occur until this is fixed.")); + } + } + // install display manager notification handler CGDisplayRegisterReconfigurationCallback(displayReconfigurationCallback, this); @@ -155,15 +126,6 @@ COSXScreen::COSXScreen(bool isPrimary) : } CGDisplayRemoveReconfigurationCallback(displayReconfigurationCallback, this); - if (m_hiddenWindow) { - CFRelease(m_hiddenWindow); - m_hiddenWindow = NULL; - } - - if (m_userInputWindow) { - CFRelease(m_userInputWindow); - m_userInputWindow = NULL; - } delete m_keyState; delete m_screensaver; throw; @@ -209,15 +171,6 @@ COSXScreen::~COSXScreen() RemoveEventHandler(m_switchEventHandlerRef); CGDisplayRemoveReconfigurationCallback(displayReconfigurationCallback, this); - if (m_hiddenWindow) { - CFRelease(m_hiddenWindow); - m_hiddenWindow = NULL; - } - - if (m_userInputWindow) { - CFRelease(m_userInputWindow); - m_userInputWindow = NULL; - } delete m_keyState; delete m_screensaver; @@ -272,7 +225,7 @@ COSXScreen::warpCursor(SInt32 x, SInt32 y) pos.x = x; pos.y = y; CGWarpMouseCursorPosition(pos); - + // save new cursor position m_xCursor = x; m_yCursor = y; @@ -544,6 +497,20 @@ COSXScreen::enable() if (m_isPrimary) { // FIXME -- start watching jump zones + + // kCGEventTapOptionDefault = 0x00000000 (Missing in 10.4, so specified literally) + m_eventTapPort=CGEventTapCreate(kCGHIDEventTap, kCGHIDEventTap, 0, + kCGEventMaskForAllEvents, + handleCGInputEvent, + this); + if(!m_eventTapPort) { + LOG((CLOG_ERR "Failed to create quartz event tap.")); + } + m_eventTapRLSR=CFMachPortCreateRunLoopSource(kCFAllocatorDefault, m_eventTapPort, 0); + if(!m_eventTapRLSR) { + LOG((CLOG_ERR "Failed to create a CFRunLoopSourceRef for the quartz event tap.")); + } + CFRunLoopAddSource(CFRunLoopGetCurrent(), m_eventTapRLSR, kCFRunLoopDefaultMode); } else { // FIXME -- prevent system from entering power save mode @@ -566,6 +533,15 @@ COSXScreen::disable() { if (m_isPrimary) { // FIXME -- stop watching jump zones, stop capturing input + + if(m_eventTapRLSR) { + CFRelease(m_eventTapRLSR); + m_eventTapRLSR=NULL; + } + if(m_eventTapPort) { + CFRelease(m_eventTapPort); + m_eventTapPort=NULL; + } } else { // show cursor @@ -595,14 +571,8 @@ void COSXScreen::enter() { if (m_isPrimary) { - // stop capturing input, watch jump zones - HideWindow( m_userInputWindow ); - ShowWindow( m_hiddenWindow ); - - SetMouseCoalescingEnabled(true, NULL); - CGSetLocalEventsSuppressionInterval(0.0); - + // enable global hotkeys //setGlobalHotKeysEnabled(true); } @@ -639,20 +609,12 @@ COSXScreen::leave() if (m_isPrimary) { // warp to center warpCursor(m_xCenter, m_yCenter); - - // capture events - HideWindow(m_hiddenWindow); - ShowWindow(m_userInputWindow); - RepositionWindow(m_userInputWindow, - m_userInputWindow, kWindowCenterOnMainScreen); - SetUserFocusWindow(m_userInputWindow); - - // The OS will coalesce some events if they are similar enough in a - // short period of time this is bad for us since we need every event - // to send it over to other machines. So disable it. - SetMouseCoalescingEnabled(false, NULL); + + // This used to be necessary to get smooth mouse motion on other screens, + // but now is just to avoid a hesitating cursor when transitioning to + // the primary (this) screen. CGSetLocalEventsSuppressionInterval(0.0001); - + // disable global hotkeys //setGlobalHotKeysEnabled(false); } @@ -796,79 +758,6 @@ COSXScreen::handleSystemEvent(const CEvent& event, void*) switch (eventClass) { case kEventClassMouse: switch (GetEventKind(*carbonEvent)) { - case kEventMouseDown: - { - UInt16 myButton; - GetEventParameter(*carbonEvent, - kEventParamMouseButton, - typeMouseButton, - NULL, - sizeof(myButton), - NULL, - &myButton); - onMouseButton(true, myButton); - break; - } - - case kEventMouseUp: - { - UInt16 myButton; - GetEventParameter(*carbonEvent, - kEventParamMouseButton, - typeMouseButton, - NULL, - sizeof(myButton), - NULL, - &myButton); - onMouseButton(false, myButton); - break; - } - - case kEventMouseDragged: - case kEventMouseMoved: - { - HIPoint point; - GetEventParameter(*carbonEvent, - kEventParamMouseLocation, - typeHIPoint, - NULL, - sizeof(point), - NULL, - &point); - onMouseMove((SInt32)point.x, (SInt32)point.y); - break; - } - - case kEventMouseWheelMoved: - { - EventMouseWheelAxis axis; - SInt32 delta; - GetEventParameter(*carbonEvent, - kEventParamMouseWheelAxis, - typeMouseWheelAxis, - NULL, - sizeof(axis), - NULL, - &axis); - if (axis == kEventMouseWheelAxisX || - axis == kEventMouseWheelAxisY) { - GetEventParameter(*carbonEvent, - kEventParamMouseWheelDelta, - typeLongInteger, - NULL, - sizeof(delta), - NULL, - &delta); - if (axis == kEventMouseWheelAxisX) { - onMouseWheel(-mapScrollWheelToSynergy((SInt32)delta), 0); - } - else { - onMouseWheel(0, mapScrollWheelToSynergy((SInt32)delta)); - } - } - break; - } - case kSynergyEventMouseScroll: { OSStatus r; @@ -906,22 +795,15 @@ COSXScreen::handleSystemEvent(const CEvent& event, void*) break; case kEventClassKeyboard: - switch (GetEventKind(*carbonEvent)) { - case kEventRawKeyUp: - case kEventRawKeyDown: - case kEventRawKeyRepeat: - case kEventRawKeyModifiersChanged: - onKey(*carbonEvent); + switch (GetEventKind(*carbonEvent)) { + case kEventHotKeyPressed: + case kEventHotKeyReleased: + onHotKey(*carbonEvent); + break; + } + break; - - case kEventHotKeyPressed: - case kEventHotKeyReleased: - onHotKey(*carbonEvent); - break; - } - - break; - + case kEventClassWindow: SendEventToWindow(*carbonEvent, m_userInputWindow); switch (GetEventKind(*carbonEvent)) { @@ -1071,22 +953,17 @@ COSXScreen::displayReconfigurationCallback(CGDirectDisplayID displayID, CGDispla } bool -COSXScreen::onKey(EventRef event) +COSXScreen::onKey(CGEventRef event) { - UInt32 eventKind = GetEventKind(event); + CGEventType eventKind = CGEventGetType(event); // get the key and active modifiers - UInt32 virtualKey, macMask; - GetEventParameter(event, kEventParamKeyCode, typeUInt32, - NULL, sizeof(virtualKey), NULL, &virtualKey); - GetEventParameter(event, kEventParamKeyModifiers, typeUInt32, - NULL, sizeof(macMask), NULL, &macMask); + UInt32 virtualKey = CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode); + CGEventFlags macMask = CGEventGetFlags(event); LOG((CLOG_DEBUG1 "event: Key event kind: %d, keycode=%d", eventKind, virtualKey)); - // sadly, OS X doesn't report the virtualKey for modifier keys. - // virtualKey will be zero for modifier keys. since that's not good - // enough we'll have to figure out what the key was. - if (virtualKey == 0 && eventKind == kEventRawKeyModifiersChanged) { + // Special handling to track state of modifiers + if (eventKind == kCGEventFlagsChanged) { // get old and new modifier state KeyModifierMask oldMask = getActiveModifiers(); KeyModifierMask newMask = m_keyState->mapModifiersFromOSX(macMask); @@ -1126,17 +1003,19 @@ COSXScreen::onKey(EventRef event) // so we check for a key/modifier match in our hot key map. if (!m_isOnScreen) { HotKeyToIDMap::const_iterator i = - m_hotKeyToIDMap.find(CHotKeyItem(virtualKey, macMask & 0xff00u)); + m_hotKeyToIDMap.find(CHotKeyItem(virtualKey, + m_keyState->mapModifiersToCarbon(macMask) + & 0xff00u)); if (i != m_hotKeyToIDMap.end()) { UInt32 id = i->second; // determine event type CEvent::Type type; - UInt32 eventKind = GetEventKind(event); - if (eventKind == kEventRawKeyDown) { + //UInt32 eventKind = GetEventKind(event); + if (eventKind == kCGEventKeyDown) { type = getHotKeyDownEvent(); } - else if (eventKind == kEventRawKeyUp) { + else if (eventKind == kCGEventKeyUp) { type = getHotKeyUpEvent(); } else { @@ -1151,9 +1030,9 @@ COSXScreen::onKey(EventRef event) } // decode event type - bool down = (eventKind == kEventRawKeyDown); - bool up = (eventKind == kEventRawKeyUp); - bool isRepeat = (eventKind == kEventRawKeyRepeat); + bool down = (eventKind == kCGEventKeyDown); + bool up = (eventKind == kCGEventKeyUp); + bool isRepeat = (CGEventGetIntegerValueField(event, kCGKeyboardEventAutorepeat) == 1); // map event to keys KeyModifierMask mask; @@ -1681,3 +1560,73 @@ COSXScreen::CHotKeyItem::operator<(const CHotKeyItem& x) const return (m_keycode < x.m_keycode || (m_keycode == x.m_keycode && m_mask < x.m_mask)); } + +// Quartz event tap support +CGEventRef +COSXScreen::handleCGInputEvent(CGEventTapProxy proxy, + CGEventType type, + CGEventRef event, + void* refcon) +{ + COSXScreen* screen = (COSXScreen*)refcon; + CGPoint pos; + + switch(type) { + case kCGEventLeftMouseDown: + case kCGEventRightMouseDown: + case kCGEventOtherMouseDown: + screen->onMouseButton(true, CGEventGetIntegerValueField(event, kCGMouseEventButtonNumber) + 1); + break; + case kCGEventLeftMouseUp: + case kCGEventRightMouseUp: + case kCGEventOtherMouseUp: + screen->onMouseButton(false, CGEventGetIntegerValueField(event, kCGMouseEventButtonNumber) + 1); + break; + case kCGEventMouseMoved: + case kCGEventLeftMouseDragged: + case kCGEventRightMouseDragged: + case kCGEventOtherMouseDragged: + pos = CGEventGetLocation(event); + screen->onMouseMove(pos.x, pos.y); + + // The system ignores our cursor-centering calls if + // we don't return the event. This should be harmless, + // but might register as slight movement to other apps + // on the system. It hasn't been a problem before, though. + return event; + break; + case kCGEventScrollWheel: + screen->onMouseWheel(screen->mapScrollWheelToSynergy( + CGEventGetIntegerValueField(event, kCGScrollWheelEventDeltaAxis2)), + screen->mapScrollWheelToSynergy( + CGEventGetIntegerValueField(event, kCGScrollWheelEventDeltaAxis1))); + break; + case kCGEventKeyDown: + case kCGEventKeyUp: + case kCGEventFlagsChanged: + screen->onKey(event); + break; + case kCGEventTapDisabledByTimeout: + // Re-enable our event-tap + CGEventTapEnable(screen->m_eventTapPort, true); + LOG((CLOG_NOTE "Quartz Event tap was disabled by timeout. Re-enabling.")); + break; + case kCGEventTapDisabledByUserInput: + LOG((CLOG_ERR "Quartz Event tap was disabled by user input!")); + break; + case NX_NULLEVENT: + break; + case NX_SYSDEFINED: + // Unknown, forward it + return event; + break; + default: + LOG((CLOG_NOTE "Unknown Quartz Event type: 0x%02x", type)); + } + + if(screen->m_isOnScreen) { + return event; + } else { + return NULL; + } +} \ No newline at end of file diff --git a/lib/platform/COSXScreen.h b/lib/platform/COSXScreen.h index a4cdf319..d2bc3685 100644 --- a/lib/platform/COSXScreen.h +++ b/lib/platform/COSXScreen.h @@ -94,14 +94,14 @@ private: void sendClipboardEvent(CEvent::Type type, ClipboardID id) const; // message handlers - bool onMouseMove(SInt32 x, SInt32 y); + bool onMouseMove(SInt32 mx, SInt32 my); // mouse button handler. pressed is true if this is a mousedown // event, false if it is a mouseup event. macButton is the index // of the button pressed using the mac button mapping. bool onMouseButton(bool pressed, UInt16 macButton); bool onMouseWheel(SInt32 xDelta, SInt32 yDelta) const; - bool onKey(EventRef event); + bool onKey(CGEventRef event); bool onHotKey(EventRef event) const; // map mac mouse button to synergy buttons @@ -152,7 +152,12 @@ private: static bool isGlobalHotKeyOperatingModeAvailable(); static void setGlobalHotKeysEnabled(bool enabled); static bool getGlobalHotKeysEnabled(); - + + // Quartz event tap support + static CGEventRef handleCGInputEvent(CGEventTapProxy proxy, + CGEventType type, + CGEventRef event, + void* refcon); private: struct CHotKeyItem { public: @@ -241,6 +246,10 @@ private: // events static CEvent::Type s_confirmSleepEvent; + + // Quartz input event support + CFMachPortRef m_eventTapPort; + CFRunLoopSourceRef m_eventTapRLSR; }; #endif