• Main Page
  • Classes
  • Files
  • File List

COSXScreen.cpp

00001 /*
00002  * synergy -- mouse and keyboard sharing utility
00003  * Copyright (C) 2012 Bolton Software Ltd.
00004  * Copyright (C) 2004 Chris Schoeneman
00005  * 
00006  * This package is free software; you can redistribute it and/or
00007  * modify it under the terms of the GNU General Public License
00008  * found in the file COPYING that should have accompanied this file.
00009  * 
00010  * This package is distributed in the hope that it will be useful,
00011  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00013  * GNU General Public License for more details.
00014  *
00015  * You should have received a copy of the GNU General Public License
00016  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
00017  */
00018 
00019 #include "COSXScreen.h"
00020 #include "COSXClipboard.h"
00021 #include "COSXEventQueueBuffer.h"
00022 #include "COSXKeyState.h"
00023 #include "COSXScreenSaver.h"
00024 #include "CClipboard.h"
00025 #include "CKeyMap.h"
00026 #include "CCondVar.h"
00027 #include "CLock.h"
00028 #include "CMutex.h"
00029 #include "CThread.h"
00030 #include "CLog.h"
00031 #include "IEventQueue.h"
00032 #include "TMethodEventJob.h"
00033 #include "TMethodJob.h"
00034 #include "XArch.h"
00035 
00036 #include <math.h>
00037 
00038 #include <mach-o/dyld.h>
00039 #include <AvailabilityMacros.h>
00040 #include <IOKit/hidsystem/event_status_driver.h>
00041 
00042 // Set some enums for fast user switching if we're building with an SDK
00043 // from before such support was added.
00044 #if !defined(MAC_OS_X_VERSION_10_3) || \
00045     (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_3)
00046 enum {
00047     kEventClassSystem                 = 'macs',
00048     kEventSystemUserSessionActivated   = 10,
00049     kEventSystemUserSessionDeactivated = 11
00050 };
00051 #endif
00052 
00053 // This isn't in any Apple SDK that I know of as of yet.
00054 enum {
00055     kSynergyEventMouseScroll = 11,
00056     kSynergyMouseScrollAxisX = 'saxx',
00057     kSynergyMouseScrollAxisY = 'saxy'
00058 };
00059 
00060 //
00061 // COSXScreen
00062 //
00063 
00064 
00065 
00066 bool                    COSXScreen::s_testedForGHOM = false;
00067 bool                    COSXScreen::s_hasGHOM       = false;
00068 CEvent::Type            COSXScreen::s_confirmSleepEvent = CEvent::kUnknown;
00069 
00070 COSXScreen::COSXScreen(bool isPrimary, bool autoShowHideCursor) :
00071     MouseButtonEventMap(NumButtonIDs),
00072     m_isPrimary(isPrimary),
00073     m_isOnScreen(m_isPrimary),
00074     m_cursorPosValid(false),
00075     m_cursorHidden(false),
00076     m_dragNumButtonsDown(0),
00077     m_dragTimer(NULL),
00078     m_keyState(NULL),
00079     m_sequenceNumber(0),
00080     m_screensaver(NULL),
00081     m_screensaverNotify(false),
00082     m_ownClipboard(false),
00083     m_clipboardTimer(NULL),
00084     m_hiddenWindow(NULL),
00085     m_userInputWindow(NULL),
00086     m_switchEventHandlerRef(0),
00087     m_pmMutex(new CMutex),
00088     m_pmWatchThread(NULL),
00089     m_pmThreadReady(new CCondVar<bool>(m_pmMutex, false)),
00090     m_activeModifierHotKey(0),
00091     m_activeModifierHotKeyMask(0),
00092     m_lastSingleClick(0),
00093     m_lastDoubleClick(0),
00094     m_lastSingleClickXCursor(0),
00095     m_lastSingleClickYCursor(0),
00096     m_autoShowHideCursor(autoShowHideCursor),
00097     m_eventTapRLSR(nullptr),
00098     m_eventTapPort(nullptr),
00099     m_pmRootPort(0)
00100 {
00101     try {
00102         m_displayID   = CGMainDisplayID();
00103         updateScreenShape(m_displayID, 0);
00104         m_screensaver = new COSXScreenSaver(getEventTarget());
00105         m_keyState    = new COSXKeyState();
00106         
00107     // TODO: http://stackoverflow.com/questions/2950124/enable-access-for-assistive-device-programmatically
00108         if (m_isPrimary && !AXAPIEnabled())
00109             throw XArch("system setting not enabled: \"Enable access for assistive devices\"");
00110         
00111         // install display manager notification handler
00112 #if defined(MAC_OS_X_VERSION_10_5)
00113         CGDisplayRegisterReconfigurationCallback(displayReconfigurationCallback, this);
00114 #else
00115         m_displayManagerNotificationUPP =
00116             NewDMExtendedNotificationUPP(displayManagerCallback);
00117         OSStatus err = GetCurrentProcess(&m_PSN);
00118         err = DMRegisterExtendedNotifyProc(m_displayManagerNotificationUPP,
00119                             this, 0, &m_PSN);
00120 #endif
00121 
00122         // install fast user switching event handler
00123         EventTypeSpec switchEventTypes[2];
00124         switchEventTypes[0].eventClass = kEventClassSystem;
00125         switchEventTypes[0].eventKind  = kEventSystemUserSessionDeactivated;
00126         switchEventTypes[1].eventClass = kEventClassSystem;
00127         switchEventTypes[1].eventKind  = kEventSystemUserSessionActivated;
00128         EventHandlerUPP switchEventHandler =
00129             NewEventHandlerUPP(userSwitchCallback);
00130         InstallApplicationEventHandler(switchEventHandler, 2, switchEventTypes,
00131                                        this, &m_switchEventHandlerRef);
00132         DisposeEventHandlerUPP(switchEventHandler);
00133 
00134         constructMouseButtonEventMap();
00135 
00136         // watch for requests to sleep
00137         EVENTQUEUE->adoptHandler(COSXScreen::getConfirmSleepEvent(),
00138                                 getEventTarget(),
00139                                 new TMethodEventJob<COSXScreen>(this,
00140                                     &COSXScreen::handleConfirmSleep));
00141 
00142         // create thread for monitoring system power state.
00143         *m_pmThreadReady = false;
00144         LOG((CLOG_DEBUG "starting watchSystemPowerThread"));
00145         m_pmWatchThread = new CThread(new TMethodJob<COSXScreen>
00146                                 (this, &COSXScreen::watchSystemPowerThread));
00147     }
00148     catch (...) {
00149         EVENTQUEUE->removeHandler(COSXScreen::getConfirmSleepEvent(),
00150                                 getEventTarget());
00151         if (m_switchEventHandlerRef != 0) {
00152             RemoveEventHandler(m_switchEventHandlerRef);
00153         }
00154 #if defined(MAC_OS_X_VERSION_10_5)
00155         CGDisplayRemoveReconfigurationCallback(displayReconfigurationCallback, this);
00156 #else
00157         if (m_displayManagerNotificationUPP != NULL) {
00158             DMRemoveExtendedNotifyProc(m_displayManagerNotificationUPP,
00159                             NULL, &m_PSN, 0);
00160         }
00161 
00162         if (m_hiddenWindow) {
00163             ReleaseWindow(m_hiddenWindow);
00164             m_hiddenWindow = NULL;
00165         }
00166 
00167         if (m_userInputWindow) {
00168             ReleaseWindow(m_userInputWindow);
00169             m_userInputWindow = NULL;
00170         }
00171 #endif
00172 
00173         delete m_keyState;
00174         delete m_screensaver;
00175         throw;
00176     }
00177 
00178     // install event handlers
00179     EVENTQUEUE->adoptHandler(CEvent::kSystem, IEventQueue::getSystemTarget(),
00180                             new TMethodEventJob<COSXScreen>(this,
00181                                 &COSXScreen::handleSystemEvent));
00182 
00183     // install the platform event queue
00184     EVENTQUEUE->adoptBuffer(new COSXEventQueueBuffer);
00185 }
00186 
00187 COSXScreen::~COSXScreen()
00188 {
00189     disable();
00190     EVENTQUEUE->adoptBuffer(NULL);
00191     EVENTQUEUE->removeHandler(CEvent::kSystem, IEventQueue::getSystemTarget());
00192 
00193     if (m_pmWatchThread) {
00194         // make sure the thread has setup the runloop.
00195         {
00196             CLock lock(m_pmMutex);
00197             while (!(bool)*m_pmThreadReady) {
00198                 m_pmThreadReady->wait();
00199             }
00200         }
00201 
00202         // now exit the thread's runloop and wait for it to exit
00203         LOG((CLOG_DEBUG "stopping watchSystemPowerThread"));
00204         CFRunLoopStop(m_pmRunloop);
00205         m_pmWatchThread->wait();
00206         delete m_pmWatchThread;
00207         m_pmWatchThread = NULL;
00208     }
00209     delete m_pmThreadReady;
00210     delete m_pmMutex;
00211 
00212     EVENTQUEUE->removeHandler(COSXScreen::getConfirmSleepEvent(),
00213                                 getEventTarget());
00214 
00215     RemoveEventHandler(m_switchEventHandlerRef);
00216 
00217 #if defined(MAC_OS_X_VERSION_10_5)
00218     CGDisplayRemoveReconfigurationCallback(displayReconfigurationCallback, this);
00219 #else
00220     DMRemoveExtendedNotifyProc(m_displayManagerNotificationUPP,
00221                             NULL, &m_PSN, 0);
00222 
00223     if (m_hiddenWindow) {
00224         ReleaseWindow(m_hiddenWindow);
00225         m_hiddenWindow = NULL;
00226     }
00227 
00228     if (m_userInputWindow) {
00229         ReleaseWindow(m_userInputWindow);
00230         m_userInputWindow = NULL;
00231     }
00232 #endif
00233 
00234     delete m_keyState;
00235     delete m_screensaver;
00236 }
00237 
00238 void*
00239 COSXScreen::getEventTarget() const
00240 {
00241     return const_cast<COSXScreen*>(this);
00242 }
00243 
00244 bool
00245 COSXScreen::getClipboard(ClipboardID, IClipboard* dst) const
00246 {
00247     CClipboard::copy(dst, &m_pasteboard);
00248     return true;
00249 }
00250 
00251 void
00252 COSXScreen::getShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h) const
00253 {
00254     x = m_x;
00255     y = m_y;
00256     w = m_w;
00257     h = m_h;
00258 }
00259 
00260 void
00261 COSXScreen::getCursorPos(SInt32& x, SInt32& y) const
00262 {
00263     Point mouse;
00264     GetGlobalMouse(&mouse);
00265     x                = mouse.h;
00266     y                = mouse.v;
00267     m_cursorPosValid = true;
00268     m_xCursor        = x;
00269     m_yCursor        = y;
00270 }
00271 
00272 void
00273 COSXScreen::reconfigure(UInt32)
00274 {
00275     // do nothing
00276 }
00277 
00278 void
00279 COSXScreen::warpCursor(SInt32 x, SInt32 y)
00280 {
00281     // move cursor without generating events
00282     CGPoint pos;
00283     pos.x = x;
00284     pos.y = y;
00285     CGWarpMouseCursorPosition(pos);
00286     
00287     // save new cursor position
00288     m_xCursor        = x;
00289     m_yCursor        = y;
00290     m_cursorPosValid = true;
00291 }
00292 
00293 void
00294 COSXScreen::fakeInputBegin()
00295 {
00296     // FIXME -- not implemented
00297 }
00298 
00299 void
00300 COSXScreen::fakeInputEnd()
00301 {
00302     // FIXME -- not implemented
00303 }
00304 
00305 SInt32
00306 COSXScreen::getJumpZoneSize() const
00307 {
00308     return 1;
00309 }
00310 
00311 bool
00312 COSXScreen::isAnyMouseButtonDown() const
00313 {
00314     return (GetCurrentButtonState() != 0);
00315 }
00316 
00317 void
00318 COSXScreen::getCursorCenter(SInt32& x, SInt32& y) const
00319 {
00320     x = m_xCenter;
00321     y = m_yCenter;
00322 }
00323 
00324 UInt32
00325 COSXScreen::registerHotKey(KeyID key, KeyModifierMask mask)
00326 {
00327     // get mac virtual key and modifier mask matching synergy key and mask
00328     UInt32 macKey, macMask;
00329     if (!m_keyState->mapSynergyHotKeyToMac(key, mask, macKey, macMask)) {
00330         LOG((CLOG_DEBUG "could not map hotkey id=%04x mask=%04x", key, mask));
00331         return 0;
00332     }
00333     
00334     // choose hotkey id
00335     UInt32 id;
00336     if (!m_oldHotKeyIDs.empty()) {
00337         id = m_oldHotKeyIDs.back();
00338         m_oldHotKeyIDs.pop_back();
00339     }
00340     else {
00341         id = m_hotKeys.size() + 1;
00342     }
00343 
00344     // if this hot key has modifiers only then we'll handle it specially
00345     EventHotKeyRef ref = NULL;
00346     bool okay;
00347     if (key == kKeyNone) {
00348         if (m_modifierHotKeys.count(mask) > 0) {
00349             // already registered
00350             okay = false;
00351         }
00352         else {
00353             m_modifierHotKeys[mask] = id;
00354             okay = true;
00355         }
00356     }
00357     else {
00358         EventHotKeyID hkid = { 'SNRG', (UInt32)id };
00359         OSStatus status = RegisterEventHotKey(macKey, macMask, hkid, 
00360                                 GetApplicationEventTarget(), 0,
00361                                 &ref);
00362         okay = (status == noErr);
00363         m_hotKeyToIDMap[CHotKeyItem(macKey, macMask)] = id;
00364     }
00365 
00366     if (!okay) {
00367         m_oldHotKeyIDs.push_back(id);
00368         m_hotKeyToIDMap.erase(CHotKeyItem(macKey, macMask));
00369         LOG((CLOG_WARN "failed to register hotkey %s (id=%04x mask=%04x)", CKeyMap::formatKey(key, mask).c_str(), key, mask));
00370         return 0;
00371     }
00372 
00373     m_hotKeys.insert(std::make_pair(id, CHotKeyItem(ref, macKey, macMask)));
00374     
00375     LOG((CLOG_DEBUG "registered hotkey %s (id=%04x mask=%04x) as id=%d", CKeyMap::formatKey(key, mask).c_str(), key, mask, id));
00376     return id;
00377 }
00378 
00379 void
00380 COSXScreen::unregisterHotKey(UInt32 id)
00381 {
00382     // look up hotkey
00383     HotKeyMap::iterator i = m_hotKeys.find(id);
00384     if (i == m_hotKeys.end()) {
00385         return;
00386     }
00387 
00388     // unregister with OS
00389     bool okay;
00390     if (i->second.getRef() != NULL) {
00391         okay = (UnregisterEventHotKey(i->second.getRef()) == noErr);
00392     }
00393     else {
00394         okay = false;
00395         // XXX -- this is inefficient
00396         for (ModifierHotKeyMap::iterator j = m_modifierHotKeys.begin();
00397                                 j != m_modifierHotKeys.end(); ++j) {
00398             if (j->second == id) {
00399                 m_modifierHotKeys.erase(j);
00400                 okay = true;
00401                 break;
00402             }
00403         }
00404     }
00405     if (!okay) {
00406         LOG((CLOG_WARN "failed to unregister hotkey id=%d", id));
00407     }
00408     else {
00409         LOG((CLOG_DEBUG "unregistered hotkey id=%d", id));
00410     }
00411 
00412     // discard hot key from map and record old id for reuse
00413     m_hotKeyToIDMap.erase(i->second);
00414     m_hotKeys.erase(i);
00415     m_oldHotKeyIDs.push_back(id);
00416     if (m_activeModifierHotKey == id) {
00417         m_activeModifierHotKey     = 0;
00418         m_activeModifierHotKeyMask = 0;
00419     }
00420 }
00421 
00422 void
00423 COSXScreen::constructMouseButtonEventMap()
00424 {
00425     const CGEventType source[NumButtonIDs][3] = {
00426         kCGEventLeftMouseUp,kCGEventLeftMouseDragged,kCGEventLeftMouseDown,
00427         kCGEventOtherMouseUp,kCGEventOtherMouseDragged,kCGEventOtherMouseDown,
00428         kCGEventRightMouseUp,kCGEventRightMouseDragged,kCGEventRightMouseDown,
00429         kCGEventOtherMouseUp,kCGEventOtherMouseDragged,kCGEventOtherMouseDown,
00430         kCGEventOtherMouseUp,kCGEventOtherMouseDragged,kCGEventOtherMouseDown};
00431 
00432     for (UInt16 button = 0; button < NumButtonIDs; button++) {
00433         MouseButtonEventMapType new_map;
00434         for (UInt16 state = (UInt32) kMouseButtonUp; state < kMouseButtonStateMax; state++) {
00435             CGEventType curEvent = source[button][state];
00436             new_map[state] = curEvent;
00437         }
00438         MouseButtonEventMap[button] = new_map;
00439     }
00440 }
00441 
00442 void
00443 COSXScreen::postMouseEvent(CGPoint& pos) const
00444 {
00445     // check if cursor position is valid on the client display configuration
00446     // stkamp@users.sourceforge.net
00447     CGDisplayCount displayCount = 0;
00448     CGGetDisplaysWithPoint(pos, 0, NULL, &displayCount);
00449     if (displayCount == 0) {
00450         // cursor position invalid - clamp to bounds of last valid display.
00451         // find the last valid display using the last cursor position.
00452         displayCount = 0;
00453         CGDirectDisplayID displayID;
00454         CGGetDisplaysWithPoint(CGPointMake(m_xCursor, m_yCursor), 1,
00455                                 &displayID, &displayCount);
00456         if (displayCount != 0) {
00457             CGRect displayRect = CGDisplayBounds(displayID);
00458             if (pos.x < displayRect.origin.x) {
00459                 pos.x = displayRect.origin.x;
00460             }
00461             else if (pos.x > displayRect.origin.x +
00462                                 displayRect.size.width - 1) {
00463                 pos.x = displayRect.origin.x + displayRect.size.width - 1;
00464             }
00465             if (pos.y < displayRect.origin.y) {
00466                 pos.y = displayRect.origin.y;
00467             }
00468             else if (pos.y > displayRect.origin.y +
00469                                 displayRect.size.height - 1) {
00470                 pos.y = displayRect.origin.y + displayRect.size.height - 1;
00471             }
00472         }
00473     }
00474     
00475     CGEventType type = kCGEventMouseMoved;
00476 
00477     SInt8 button = m_buttonState.getFirstButtonDown();
00478     if (button != -1) {
00479         MouseButtonEventMapType thisButtonType = MouseButtonEventMap[button];
00480         type = thisButtonType[kMouseButtonDragged];
00481     }
00482 
00483     CGEventRef event = CGEventCreateMouseEvent(NULL, type, pos, button);
00484     CGEventPost(kCGHIDEventTap, event);
00485     
00486     CFRelease(event);
00487 }
00488 
00489 void
00490 COSXScreen::fakeMouseButton(ButtonID id, bool press)
00491 {
00492     NXEventHandle handle = NXOpenEventStatus();
00493     double clickTime = NXClickTime(handle);
00494     
00495     if ((ARCH->time() - m_lastDoubleClick) <= clickTime) {
00496         // drop all down and up fakes immedately after a double click.
00497         // TODO: perhaps there is a better way to do this, usually in
00498         // finder, if you tripple click a folder, it will open it and
00499         // then select a folder under the cursor -- and perhaps other
00500         // strange behaviour might happen?
00501         LOG((CLOG_DEBUG1 "dropping mouse button %s",
00502             press ? "press" : "release"));
00503         return;
00504     }
00505     
00506     // Buttons are indexed from one, but the button down array is indexed from zero
00507     UInt32 index = id - kButtonLeft;
00508     if (index >= NumButtonIDs) {
00509         return;
00510     }
00511     
00512     CGPoint pos;
00513     if (!m_cursorPosValid) {
00514         SInt32 x, y;
00515         getCursorPos(x, y);
00516     }
00517     pos.x = m_xCursor;
00518     pos.y = m_yCursor;
00519 
00520     // variable used to detect mouse coordinate differences between
00521     // old & new mouse clicks. Used in double click detection.
00522     SInt32 xDiff = m_xCursor - m_lastSingleClickXCursor;
00523     SInt32 yDiff = m_yCursor - m_lastSingleClickYCursor;
00524     double diff = sqrt(xDiff * xDiff + yDiff * yDiff);
00525     // max sqrt(x^2 + y^2) difference allowed to double click
00526     // since we don't have double click distance in NX APIs
00527     // we define our own defaults.
00528     const double maxDiff = sqrt(2) + 0.0001;
00529 
00530     if (press && (id == kButtonLeft) &&
00531         ((ARCH->time() - m_lastSingleClick) <= clickTime) &&
00532         diff <= maxDiff) {
00533 
00534         LOG((CLOG_DEBUG1 "faking mouse left double click"));
00535         
00536         // finder does not seem to detect double clicks from two separate
00537         // CGEventCreateMouseEvent calls. so, if we detect a double click we
00538         // use CGEventSetIntegerValueField to tell the OS.
00539         // 
00540         // the caveat here is that findor will see this as a single click 
00541         // followed by a double click (even though there should be only a
00542         // double click). this may cause weird behaviour in other apps.
00543         //
00544         // for some reason using the old CGPostMouseEvent function, doesn't
00545         // cause double clicks (though i'm sure it did work at some point).
00546         
00547         CGEventRef event = CGEventCreateMouseEvent(
00548             NULL, kCGEventLeftMouseDown, pos, kCGMouseButtonLeft);
00549         
00550         CGEventSetIntegerValueField(event, kCGMouseEventClickState, 2);
00551         m_buttonState.set(index, kMouseButtonDown);
00552         CGEventPost(kCGHIDEventTap, event);
00553         
00554         CGEventSetType(event, kCGEventLeftMouseUp);
00555         m_buttonState.set(index, kMouseButtonUp);
00556         CGEventPost(kCGHIDEventTap, event);
00557         
00558         CFRelease(event);
00559     
00560         m_lastDoubleClick = ARCH->time();
00561     }
00562     else {
00563         
00564         // ... otherwise, perform a single press or release as normal.
00565         
00566         MouseButtonState state = press ? kMouseButtonDown : kMouseButtonUp;
00567         
00568         LOG((CLOG_DEBUG1 "faking mouse button %s", press ? "press" : "release"));
00569         
00570         MouseButtonEventMapType thisButtonMap = MouseButtonEventMap[index];
00571         CGEventType type = thisButtonMap[state];
00572         
00573         CGEventRef event = CGEventCreateMouseEvent(NULL, type, pos, index);
00574     
00575         m_buttonState.set(index, state);
00576         CGEventPost(kCGHIDEventTap, event);
00577         
00578         CFRelease(event);
00579     
00580         m_lastSingleClick = ARCH->time();
00581         m_lastSingleClickXCursor = m_xCursor;
00582         m_lastSingleClickYCursor = m_yCursor;
00583     }
00584 }
00585 
00586 void
00587 COSXScreen::fakeMouseMove(SInt32 x, SInt32 y) const
00588 {
00589     // synthesize event
00590     CGPoint pos;
00591     pos.x = x;
00592     pos.y = y;
00593     postMouseEvent(pos);
00594 
00595     // save new cursor position
00596     m_xCursor        = static_cast<SInt32>(pos.x);
00597     m_yCursor        = static_cast<SInt32>(pos.y);
00598     m_cursorPosValid = true;
00599 }
00600 
00601 void
00602 COSXScreen::fakeMouseRelativeMove(SInt32 dx, SInt32 dy) const
00603 {
00604     // OS X does not appear to have a fake relative mouse move function.
00605     // simulate it by getting the current mouse position and adding to
00606     // that.  this can yield the wrong answer but there's not much else
00607     // we can do.
00608 
00609     // get current position
00610     Point oldPos;
00611     GetGlobalMouse(&oldPos);
00612 
00613     // synthesize event
00614     CGPoint pos;
00615     m_xCursor = static_cast<SInt32>(oldPos.h);
00616     m_yCursor = static_cast<SInt32>(oldPos.v);
00617     pos.x     = oldPos.h + dx;
00618     pos.y     = oldPos.v + dy;
00619     postMouseEvent(pos);
00620 
00621     // we now assume we don't know the current cursor position
00622     m_cursorPosValid = false;
00623 }
00624 
00625 void
00626 COSXScreen::fakeMouseWheel(SInt32 xDelta, SInt32 yDelta) const
00627 {
00628     if (xDelta != 0 || yDelta != 0) {
00629 #if defined(MAC_OS_X_VERSION_10_5)
00630         // create a scroll event, post it and release it.  not sure if kCGScrollEventUnitLine
00631         // is the right choice here over kCGScrollEventUnitPixel
00632         CGEventRef scrollEvent = CGEventCreateScrollWheelEvent(
00633             NULL, kCGScrollEventUnitLine, 2,
00634             mapScrollWheelFromSynergy(yDelta),
00635             -mapScrollWheelFromSynergy(xDelta));
00636         
00637         CGEventPost(kCGHIDEventTap, scrollEvent);
00638         CFRelease(scrollEvent);
00639 #else
00640 
00641         CGPostScrollWheelEvent(
00642             2, mapScrollWheelFromSynergy(yDelta),
00643             -mapScrollWheelFromSynergy(xDelta));
00644 #endif
00645     }
00646 }
00647 
00648 void
00649 COSXScreen::showCursor()
00650 {
00651     LOG((CLOG_DEBUG "showing cursor"));
00652 
00653     CFStringRef propertyString = CFStringCreateWithCString(
00654         NULL, "SetsCursorInBackground", kCFStringEncodingMacRoman);
00655 
00656     CGSSetConnectionProperty(
00657         _CGSDefaultConnection(), _CGSDefaultConnection(),
00658         propertyString, kCFBooleanTrue);
00659 
00660     CFRelease(propertyString);
00661 
00662     CGError error = CGDisplayShowCursor(m_displayID);
00663     if (error != kCGErrorSuccess) {
00664         LOG((CLOG_ERR "failed to show cursor, error=%d", error));
00665     }
00666 
00667     // appears to fix "mouse randomly not showing" bug
00668     CGAssociateMouseAndMouseCursorPosition(true);
00669 
00670     if (!CGCursorIsVisible()) {
00671         LOG((CLOG_WARN "cursor may not be visible"));
00672     }
00673 
00674     m_cursorHidden = false;
00675 }
00676 
00677 void
00678 COSXScreen::hideCursor()
00679 {
00680     LOG((CLOG_DEBUG "hiding cursor"));
00681 
00682     CFStringRef propertyString = CFStringCreateWithCString(
00683         NULL, "SetsCursorInBackground", kCFStringEncodingMacRoman);
00684 
00685     CGSSetConnectionProperty(
00686         _CGSDefaultConnection(), _CGSDefaultConnection(),
00687         propertyString, kCFBooleanTrue);
00688 
00689     CFRelease(propertyString);
00690 
00691     CGError error = CGDisplayHideCursor(m_displayID);
00692     if (error != kCGErrorSuccess) {
00693         LOG((CLOG_ERR "failed to hide cursor, error=%d", error));
00694     }
00695 
00696     // appears to fix "mouse randomly not hiding" bug
00697     CGAssociateMouseAndMouseCursorPosition(true);
00698 
00699     if (CGCursorIsVisible()) {
00700         LOG((CLOG_WARN "cursor may be still visible"));
00701     }
00702 
00703     m_cursorHidden = true;
00704 }
00705 
00706 void
00707 COSXScreen::enable()
00708 {
00709     // watch the clipboard
00710     m_clipboardTimer = EVENTQUEUE->newTimer(1.0, NULL);
00711     EVENTQUEUE->adoptHandler(CEvent::kTimer, m_clipboardTimer,
00712                             new TMethodEventJob<COSXScreen>(this,
00713                                 &COSXScreen::handleClipboardCheck));
00714 
00715     if (m_isPrimary) {
00716         // FIXME -- start watching jump zones
00717         
00718         // kCGEventTapOptionDefault = 0x00000000 (Missing in 10.4, so specified literally)
00719         m_eventTapPort = CGEventTapCreate(kCGHIDEventTap, kCGHeadInsertEventTap, 0,
00720                                         kCGEventMaskForAllEvents, 
00721                                         handleCGInputEvent, 
00722                                         this);
00723     }
00724     else {
00725         // FIXME -- prevent system from entering power save mode
00726 
00727         if (m_autoShowHideCursor) {
00728             hideCursor();
00729         }
00730 
00731         // warp the mouse to the cursor center
00732         fakeMouseMove(m_xCenter, m_yCenter);
00733 
00734                 // there may be a better way to do this, but we register an event handler even if we're
00735                 // not on the primary display (acting as a client). This way, if a local event comes in
00736                 // (either keyboard or mouse), we can make sure to show the cursor if we've hidden it. 
00737         m_eventTapPort = CGEventTapCreate(kCGHIDEventTap, kCGHeadInsertEventTap, 0,
00738                                         kCGEventMaskForAllEvents, 
00739                                         handleCGInputEventSecondary, 
00740                                         this);
00741     }
00742 
00743     if (!m_eventTapPort) {
00744         LOG((CLOG_ERR "failed to create quartz event tap"));
00745     }
00746 
00747     m_eventTapRLSR = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, m_eventTapPort, 0);
00748     if (!m_eventTapRLSR) {
00749         LOG((CLOG_ERR "failed to create a CFRunLoopSourceRef for the quartz event tap"));
00750     }
00751 
00752     CFRunLoopAddSource(CFRunLoopGetCurrent(), m_eventTapRLSR, kCFRunLoopDefaultMode);
00753 }
00754 
00755 void
00756 COSXScreen::disable()
00757 {
00758     if (m_autoShowHideCursor) {
00759         showCursor();
00760     }
00761     
00762     // FIXME -- stop watching jump zones, stop capturing input
00763     
00764     if (m_eventTapRLSR) {
00765         CFRelease(m_eventTapRLSR);
00766         m_eventTapRLSR = nullptr;
00767     }
00768 
00769     if (m_eventTapPort) {
00770         CFRelease(m_eventTapPort);
00771         m_eventTapPort = nullptr;
00772     }
00773     // FIXME -- allow system to enter power saving mode
00774 
00775     // disable drag handling
00776     m_dragNumButtonsDown = 0;
00777     enableDragTimer(false);
00778 
00779     // uninstall clipboard timer
00780     if (m_clipboardTimer != NULL) {
00781         EVENTQUEUE->removeHandler(CEvent::kTimer, m_clipboardTimer);
00782         EVENTQUEUE->deleteTimer(m_clipboardTimer);
00783         m_clipboardTimer = NULL;
00784     }
00785 
00786     m_isOnScreen = m_isPrimary;
00787 }
00788 
00789 void
00790 COSXScreen::enter()
00791 {
00792     showCursor();
00793 
00794     if (m_isPrimary) {
00795         CGSetLocalEventsSuppressionInterval(0.0);
00796         
00797         // enable global hotkeys
00798         //setGlobalHotKeysEnabled(true);
00799     }
00800     else {
00801         // reset buttons
00802         m_buttonState.reset();
00803 
00804         // avoid suppression of local hardware events
00805         // stkamp@users.sourceforge.net
00806         CGSetLocalEventsFilterDuringSupressionState(
00807                                 kCGEventFilterMaskPermitAllEvents,
00808                                 kCGEventSupressionStateSupressionInterval);
00809         CGSetLocalEventsFilterDuringSupressionState(
00810                                 (kCGEventFilterMaskPermitLocalKeyboardEvents |
00811                                 kCGEventFilterMaskPermitSystemDefinedEvents),
00812                                 kCGEventSupressionStateRemoteMouseDrag);
00813     }
00814 
00815     // now on screen
00816     m_isOnScreen = true;
00817 }
00818 
00819 bool
00820 COSXScreen::leave()
00821 {
00822     hideCursor();
00823     
00824     if (m_isPrimary) {
00825         // warp to center
00826         //warpCursor(m_xCenter, m_yCenter);
00827         
00828         // This used to be necessary to get smooth mouse motion on other screens,
00829         // but now is just to avoid a hesitating cursor when transitioning to
00830         // the primary (this) screen.
00831         CGSetLocalEventsSuppressionInterval(0.0001);
00832         
00833         // disable global hotkeys
00834         //setGlobalHotKeysEnabled(false);
00835     }
00836     else {
00837         // warp the mouse to the cursor center
00838         //fakeMouseMove(m_xCenter, m_yCenter);
00839 
00840         // FIXME -- prepare to show cursor if it moves
00841 
00842         // take keyboard focus  
00843         // FIXME
00844     }
00845 
00846     // now off screen
00847     m_isOnScreen = false;
00848 
00849     return true;
00850 }
00851 
00852 bool
00853 COSXScreen::setClipboard(ClipboardID, const IClipboard* src)
00854 {
00855     if(src != NULL) {
00856         LOG((CLOG_DEBUG "setting clipboard"));
00857         CClipboard::copy(&m_pasteboard, src);   
00858     }   
00859     return true;
00860 }
00861 
00862 void
00863 COSXScreen::checkClipboards()
00864 {
00865     LOG((CLOG_DEBUG2 "checking clipboard"));
00866     if (m_pasteboard.synchronize()) {
00867         LOG((CLOG_DEBUG "clipboard changed"));
00868         sendClipboardEvent(getClipboardGrabbedEvent(), kClipboardClipboard);
00869         sendClipboardEvent(getClipboardGrabbedEvent(), kClipboardSelection);
00870     }
00871 }
00872 
00873 void
00874 COSXScreen::openScreensaver(bool notify)
00875 {
00876     m_screensaverNotify = notify;
00877     if (!m_screensaverNotify) {
00878         m_screensaver->disable();
00879     }
00880 }
00881 
00882 void
00883 COSXScreen::closeScreensaver()
00884 {
00885     if (!m_screensaverNotify) {
00886         m_screensaver->enable();
00887     }
00888 }
00889 
00890 void
00891 COSXScreen::screensaver(bool activate)
00892 {
00893     if (activate) {
00894         m_screensaver->activate();
00895     }
00896     else {
00897         m_screensaver->deactivate();
00898     }
00899 }
00900 
00901 void
00902 COSXScreen::resetOptions()
00903 {
00904     // no options
00905 }
00906 
00907 void
00908 COSXScreen::setOptions(const COptionsList&)
00909 {
00910     // no options
00911 }
00912 
00913 void
00914 COSXScreen::setSequenceNumber(UInt32 seqNum)
00915 {
00916     m_sequenceNumber = seqNum;
00917 }
00918 
00919 bool
00920 COSXScreen::isPrimary() const
00921 {
00922     return m_isPrimary;
00923 }
00924 
00925 void
00926 COSXScreen::sendEvent(CEvent::Type type, void* data) const
00927 {
00928     EVENTQUEUE->addEvent(CEvent(type, getEventTarget(), data));
00929 }
00930 
00931 void
00932 COSXScreen::sendClipboardEvent(CEvent::Type type, ClipboardID id) const
00933 {
00934     CClipboardInfo* info   = (CClipboardInfo*)malloc(sizeof(CClipboardInfo));
00935     info->m_id             = id;
00936     info->m_sequenceNumber = m_sequenceNumber;
00937     sendEvent(type, info);
00938 }
00939 
00940 void
00941 COSXScreen::handleSystemEvent(const CEvent& event, void*)
00942 {
00943     EventRef* carbonEvent = reinterpret_cast<EventRef*>(event.getData());
00944     assert(carbonEvent != NULL);
00945 
00946     UInt32 eventClass = GetEventClass(*carbonEvent);
00947 
00948     switch (eventClass) {
00949     case kEventClassMouse:
00950         switch (GetEventKind(*carbonEvent)) {
00951         case kSynergyEventMouseScroll:
00952         {
00953             OSStatus r;
00954             long xScroll;
00955             long yScroll;
00956 
00957             // get scroll amount
00958             r = GetEventParameter(*carbonEvent,
00959                     kSynergyMouseScrollAxisX,
00960                     typeLongInteger,
00961                     NULL,
00962                     sizeof(xScroll),
00963                     NULL,
00964                     &xScroll);
00965             if (r != noErr) {
00966                 xScroll = 0;
00967             }
00968             r = GetEventParameter(*carbonEvent,
00969                     kSynergyMouseScrollAxisY,
00970                     typeLongInteger,
00971                     NULL,
00972                     sizeof(yScroll),
00973                     NULL,
00974                     &yScroll);
00975             if (r != noErr) {
00976                 yScroll = 0;
00977             }
00978 
00979             if (xScroll != 0 || yScroll != 0) {
00980                 onMouseWheel(-mapScrollWheelToSynergy(xScroll),
00981                                 mapScrollWheelToSynergy(yScroll));
00982             }
00983         }
00984         }
00985         break;
00986 
00987     case kEventClassKeyboard: 
00988             switch (GetEventKind(*carbonEvent)) {
00989                 case kEventHotKeyPressed:
00990                 case kEventHotKeyReleased:
00991                     onHotKey(*carbonEvent);
00992                     break;
00993             }
00994             
00995             break;
00996             
00997     case kEventClassWindow:
00998         SendEventToWindow(*carbonEvent, m_userInputWindow);
00999         switch (GetEventKind(*carbonEvent)) {
01000         case kEventWindowActivated:
01001             LOG((CLOG_DEBUG1 "window activated"));
01002             break;
01003 
01004         case kEventWindowDeactivated:
01005             LOG((CLOG_DEBUG1 "window deactivated"));
01006             break;
01007 
01008         case kEventWindowFocusAcquired:
01009             LOG((CLOG_DEBUG1 "focus acquired"));
01010             break;
01011 
01012         case kEventWindowFocusRelinquish:
01013             LOG((CLOG_DEBUG1 "focus released"));
01014             break;
01015         }
01016         break;
01017 
01018     default:
01019         SendEventToEventTarget(*carbonEvent, GetEventDispatcherTarget());
01020         break;
01021     }
01022 }
01023 
01024 bool 
01025 COSXScreen::onMouseMove(SInt32 mx, SInt32 my)
01026 {
01027     LOG((CLOG_DEBUG2 "mouse move %+d,%+d", mx, my));
01028 
01029     SInt32 x = mx - m_xCursor;
01030     SInt32 y = my - m_yCursor;
01031 
01032     if ((x == 0 && y == 0) || (mx == m_xCenter && mx == m_yCenter)) {
01033         return true;
01034     }
01035 
01036     // save position to compute delta of next motion
01037     m_xCursor = mx;
01038     m_yCursor = my;
01039 
01040     if (m_isOnScreen) {
01041         // motion on primary screen
01042         sendEvent(getMotionOnPrimaryEvent(),
01043                             CMotionInfo::alloc(m_xCursor, m_yCursor));
01044     }
01045     else {
01046         // motion on secondary screen.  warp mouse back to
01047         // center.
01048         warpCursor(m_xCenter, m_yCenter);
01049 
01050         // examine the motion.  if it's about the distance
01051         // from the center of the screen to an edge then
01052         // it's probably a bogus motion that we want to
01053         // ignore (see warpCursorNoFlush() for a further
01054         // description).
01055         static SInt32 bogusZoneSize = 10;
01056         if (-x + bogusZoneSize > m_xCenter - m_x ||
01057              x + bogusZoneSize > m_x + m_w - m_xCenter ||
01058             -y + bogusZoneSize > m_yCenter - m_y ||
01059              y + bogusZoneSize > m_y + m_h - m_yCenter) {
01060             LOG((CLOG_DEBUG "dropped bogus motion %+d,%+d", x, y));
01061         }
01062         else {
01063             // send motion
01064             sendEvent(getMotionOnSecondaryEvent(), CMotionInfo::alloc(x, y));
01065         }
01066     }
01067 
01068     return true;
01069 }
01070 
01071 bool                
01072 COSXScreen::onMouseButton(bool pressed, UInt16 macButton)
01073 {
01074     // Buttons 2 and 3 are inverted on the mac
01075     ButtonID button = mapMacButtonToSynergy(macButton);
01076 
01077     if (pressed) {
01078         LOG((CLOG_DEBUG1 "event: button press button=%d", button));
01079         if (button != kButtonNone) {
01080             KeyModifierMask mask = m_keyState->getActiveModifiers();
01081             sendEvent(getButtonDownEvent(), CButtonInfo::alloc(button, mask));
01082         }
01083     }
01084     else {
01085         LOG((CLOG_DEBUG1 "event: button release button=%d", button));
01086         if (button != kButtonNone) {
01087             KeyModifierMask mask = m_keyState->getActiveModifiers();
01088             sendEvent(getButtonUpEvent(), CButtonInfo::alloc(button, mask));
01089         }
01090     }
01091 
01092     // handle drags with any button other than button 1 or 2
01093     if (macButton > 2) {
01094         if (pressed) {
01095             // one more button
01096             if (m_dragNumButtonsDown++ == 0) {
01097                 enableDragTimer(true);
01098             }
01099         }
01100         else {
01101             // one less button
01102             if (--m_dragNumButtonsDown == 0) {
01103                 enableDragTimer(false);
01104             }
01105         }
01106     }
01107 
01108     return true;
01109 }
01110 
01111 bool
01112 COSXScreen::onMouseWheel(SInt32 xDelta, SInt32 yDelta) const
01113 {
01114     LOG((CLOG_DEBUG1 "event: button wheel delta=%+d,%+d", xDelta, yDelta));
01115     sendEvent(getWheelEvent(), CWheelInfo::alloc(xDelta, yDelta));
01116     return true;
01117 }
01118 
01119 void
01120 COSXScreen::handleClipboardCheck(const CEvent&, void*)
01121 {
01122     checkClipboards();
01123 }
01124 
01125 #if !defined(MAC_OS_X_VERSION_10_5)
01126 pascal void 
01127 COSXScreen::displayManagerCallback(void* inUserData, SInt16 inMessage, void*)
01128 {
01129     COSXScreen* screen = (COSXScreen*)inUserData;
01130 
01131     if (inMessage == kDMNotifyEvent) {
01132         screen->onDisplayChange();
01133     }
01134 }
01135 
01136 bool
01137 COSXScreen::onDisplayChange()
01138 {
01139     // screen resolution may have changed.  save old shape.
01140     SInt32 xOld = m_x, yOld = m_y, wOld = m_w, hOld = m_h;
01141 
01142     // update shape
01143     updateScreenShape();
01144 
01145     // do nothing if resolution hasn't changed
01146     if (xOld != m_x || yOld != m_y || wOld != m_w || hOld != m_h) {
01147         if (m_isPrimary) {
01148             // warp mouse to center if off screen
01149             if (!m_isOnScreen) {
01150                 warpCursor(m_xCenter, m_yCenter);
01151             }
01152         }
01153 
01154         // send new screen info
01155         sendEvent(getShapeChangedEvent());
01156     }
01157 
01158     return true;
01159 }
01160 #else
01161 void 
01162 COSXScreen::displayReconfigurationCallback(CGDirectDisplayID displayID, CGDisplayChangeSummaryFlags flags, void* inUserData)
01163 {
01164     COSXScreen* screen = (COSXScreen*)inUserData;
01165 
01166     // Closing or opening the lid when an external monitor is
01167     // connected causes an kCGDisplayBeginConfigurationFlag event
01168     CGDisplayChangeSummaryFlags mask = kCGDisplayBeginConfigurationFlag | kCGDisplayMovedFlag | 
01169         kCGDisplaySetModeFlag | kCGDisplayAddFlag | kCGDisplayRemoveFlag | 
01170         kCGDisplayEnabledFlag | kCGDisplayDisabledFlag | 
01171         kCGDisplayMirrorFlag | kCGDisplayUnMirrorFlag | 
01172         kCGDisplayDesktopShapeChangedFlag;
01173  
01174     LOG((CLOG_DEBUG1 "event: display was reconfigured: %x %x %x", flags, mask, flags & mask));
01175 
01176     if (flags & mask) { /* Something actually did change */
01177         
01178         LOG((CLOG_DEBUG1 "event: screen changed shape; refreshing dimensions"));
01179         screen->updateScreenShape(displayID, flags);
01180     }
01181 }
01182 #endif
01183 
01184 bool
01185 COSXScreen::onKey(CGEventRef event)
01186 {
01187     CGEventType eventKind = CGEventGetType(event);
01188 
01189     // get the key and active modifiers
01190     UInt32 virtualKey = CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode);
01191     CGEventFlags macMask = CGEventGetFlags(event);
01192     LOG((CLOG_DEBUG1 "event: Key event kind: %d, keycode=%d", eventKind, virtualKey));
01193 
01194     // Special handling to track state of modifiers
01195     if (eventKind == kCGEventFlagsChanged) {
01196         // get old and new modifier state
01197         KeyModifierMask oldMask = getActiveModifiers();
01198         KeyModifierMask newMask = m_keyState->mapModifiersFromOSX(macMask);
01199         m_keyState->handleModifierKeys(getEventTarget(), oldMask, newMask);
01200 
01201         // if the current set of modifiers exactly matches a modifiers-only
01202         // hot key then generate a hot key down event.
01203         if (m_activeModifierHotKey == 0) {
01204             if (m_modifierHotKeys.count(newMask) > 0) {
01205                 m_activeModifierHotKey     = m_modifierHotKeys[newMask];
01206                 m_activeModifierHotKeyMask = newMask;
01207                 EVENTQUEUE->addEvent(CEvent(getHotKeyDownEvent(),
01208                                 getEventTarget(),
01209                                 CHotKeyInfo::alloc(m_activeModifierHotKey)));
01210             }
01211         }
01212 
01213         // if a modifiers-only hot key is active and should no longer be
01214         // then generate a hot key up event.
01215         else if (m_activeModifierHotKey != 0) {
01216             KeyModifierMask mask = (newMask & m_activeModifierHotKeyMask);
01217             if (mask != m_activeModifierHotKeyMask) {
01218                 EVENTQUEUE->addEvent(CEvent(getHotKeyUpEvent(),
01219                                 getEventTarget(),
01220                                 CHotKeyInfo::alloc(m_activeModifierHotKey)));
01221                 m_activeModifierHotKey     = 0;
01222                 m_activeModifierHotKeyMask = 0;
01223             }
01224         }
01225             
01226         return true;
01227     }
01228 
01229     // check for hot key.  when we're on a secondary screen we disable
01230     // all hotkeys so we can capture the OS defined hot keys as regular
01231     // keystrokes but that means we don't get our own hot keys either.
01232     // so we check for a key/modifier match in our hot key map.
01233     if (!m_isOnScreen) {
01234         HotKeyToIDMap::const_iterator i =
01235             m_hotKeyToIDMap.find(CHotKeyItem(virtualKey, 
01236                                              m_keyState->mapModifiersToCarbon(macMask) 
01237                                              & 0xff00u));
01238         if (i != m_hotKeyToIDMap.end()) {
01239             UInt32 id = i->second;
01240     
01241             // determine event type
01242             CEvent::Type type;
01243             //UInt32 eventKind = GetEventKind(event);
01244             if (eventKind == kCGEventKeyDown) {
01245                 type = getHotKeyDownEvent();
01246             }
01247             else if (eventKind == kCGEventKeyUp) {
01248                 type = getHotKeyUpEvent();
01249             }
01250             else {
01251                 return false;
01252             }
01253     
01254             EVENTQUEUE->addEvent(CEvent(type, getEventTarget(),
01255                                         CHotKeyInfo::alloc(id)));
01256         
01257             return true;
01258         }
01259     }
01260 
01261     // decode event type
01262     bool down     = (eventKind == kCGEventKeyDown);
01263     bool up       = (eventKind == kCGEventKeyUp);
01264     bool isRepeat = (CGEventGetIntegerValueField(event, kCGKeyboardEventAutorepeat) == 1);
01265 
01266     // map event to keys
01267     KeyModifierMask mask;
01268     COSXKeyState::CKeyIDs keys;
01269     KeyButton button = m_keyState->mapKeyFromEvent(keys, &mask, event);
01270     if (button == 0) {
01271         return false;
01272     }
01273 
01274     // check for AltGr in mask.  if set we send neither the AltGr nor
01275     // the super modifiers to clients then remove AltGr before passing
01276     // the modifiers to onKey.
01277     KeyModifierMask sendMask = (mask & ~KeyModifierAltGr);
01278     if ((mask & KeyModifierAltGr) != 0) {
01279         sendMask &= ~KeyModifierSuper;
01280     }
01281     mask &= ~KeyModifierAltGr;
01282 
01283     // update button state
01284     if (down) {
01285         m_keyState->onKey(button, true, mask);
01286     }
01287     else if (up) {
01288         if (!m_keyState->isKeyDown(button)) {
01289             // up event for a dead key.  throw it away.
01290             return false;
01291         }
01292         m_keyState->onKey(button, false, mask);
01293     }
01294 
01295     // send key events
01296     for (COSXKeyState::CKeyIDs::const_iterator i = keys.begin();
01297                             i != keys.end(); ++i) {
01298         m_keyState->sendKeyEvent(getEventTarget(), down, isRepeat,
01299                             *i, sendMask, 1, button);
01300     }
01301 
01302     return true;
01303 }
01304 
01305 bool
01306 COSXScreen::onHotKey(EventRef event) const
01307 {
01308     // get the hotkey id
01309     EventHotKeyID hkid;
01310     GetEventParameter(event, kEventParamDirectObject, typeEventHotKeyID,
01311                             NULL, sizeof(EventHotKeyID), NULL, &hkid);
01312     UInt32 id = hkid.id;
01313 
01314     // determine event type
01315     CEvent::Type type;
01316     UInt32 eventKind = GetEventKind(event);
01317     if (eventKind == kEventHotKeyPressed) {
01318         type = getHotKeyDownEvent();
01319     }
01320     else if (eventKind == kEventHotKeyReleased) {
01321         type = getHotKeyUpEvent();
01322     }
01323     else {
01324         return false;
01325     }
01326 
01327     EVENTQUEUE->addEvent(CEvent(type, getEventTarget(),
01328                                 CHotKeyInfo::alloc(id)));
01329 
01330     return true;
01331 }
01332 
01333 ButtonID 
01334 COSXScreen::mapMacButtonToSynergy(UInt16 macButton) const
01335 {
01336     switch (macButton) {
01337     case 1:
01338         return kButtonLeft;
01339 
01340     case 2:
01341         return kButtonRight;
01342 
01343     case 3:
01344         return kButtonMiddle;
01345     }
01346     
01347     return static_cast<ButtonID>(macButton);
01348 }
01349 
01350 SInt32
01351 COSXScreen::mapScrollWheelToSynergy(SInt32 x) const
01352 {
01353     // return accelerated scrolling but not exponentially scaled as it is
01354     // on the mac.
01355     double d = (1.0 + getScrollSpeed()) * x / getScrollSpeedFactor();
01356     return static_cast<SInt32>(120.0 * d);
01357 }
01358 
01359 SInt32
01360 COSXScreen::mapScrollWheelFromSynergy(SInt32 x) const
01361 {
01362     // use server's acceleration with a little boost since other platforms
01363     // take one wheel step as a larger step than the mac does.
01364     return static_cast<SInt32>(3.0 * x / 120.0);
01365 }
01366 
01367 double
01368 COSXScreen::getScrollSpeed() const
01369 {
01370     double scaling = 0.0;
01371 
01372     CFPropertyListRef pref = ::CFPreferencesCopyValue(
01373                             CFSTR("com.apple.scrollwheel.scaling") , 
01374                             kCFPreferencesAnyApplication, 
01375                             kCFPreferencesCurrentUser,
01376                             kCFPreferencesAnyHost);
01377     if (pref != NULL) {
01378         CFTypeID id = CFGetTypeID(pref);
01379         if (id == CFNumberGetTypeID()) {
01380             CFNumberRef value = static_cast<CFNumberRef>(pref);
01381             if (CFNumberGetValue(value, kCFNumberDoubleType, &scaling)) {
01382                 if (scaling < 0.0) {
01383                     scaling = 0.0;
01384                 }
01385             }
01386         }
01387         CFRelease(pref);
01388     }
01389 
01390     return scaling;
01391 }
01392 
01393 double
01394 COSXScreen::getScrollSpeedFactor() const
01395 {
01396     return pow(10.0, getScrollSpeed());
01397 }
01398 
01399 void
01400 COSXScreen::enableDragTimer(bool enable)
01401 {
01402   UInt32 modifiers;
01403   MouseTrackingResult res; 
01404 
01405     if (enable && m_dragTimer == NULL) {
01406         m_dragTimer = EVENTQUEUE->newTimer(0.01, NULL);
01407         EVENTQUEUE->adoptHandler(CEvent::kTimer, m_dragTimer,
01408                             new TMethodEventJob<COSXScreen>(this,
01409                                 &COSXScreen::handleDrag));
01410         TrackMouseLocationWithOptions(NULL, 0, 0, &m_dragLastPoint, &modifiers, &res);
01411     }
01412     else if (!enable && m_dragTimer != NULL) {
01413         EVENTQUEUE->removeHandler(CEvent::kTimer, m_dragTimer);
01414         EVENTQUEUE->deleteTimer(m_dragTimer);
01415         m_dragTimer = NULL;
01416     }
01417 }
01418 
01419 void
01420 COSXScreen::handleDrag(const CEvent&, void*)
01421 {
01422     Point p;
01423   UInt32 modifiers;
01424   MouseTrackingResult res; 
01425 
01426     TrackMouseLocationWithOptions(NULL, 0, 0, &p, &modifiers, &res);
01427 
01428     if (res != kMouseTrackingTimedOut && (p.h != m_dragLastPoint.h || p.v != m_dragLastPoint.v)) {
01429         m_dragLastPoint = p;
01430         onMouseMove((SInt32)p.h, (SInt32)p.v);
01431     }
01432 }
01433 
01434 void
01435 COSXScreen::updateButtons()
01436 {
01437     UInt32 buttons = GetCurrentButtonState();
01438 
01439     m_buttonState.overwrite(buttons);
01440 }
01441 
01442 IKeyState*
01443 COSXScreen::getKeyState() const
01444 {
01445     return m_keyState;
01446 }
01447 
01448 void
01449 COSXScreen::updateScreenShape(const CGDirectDisplayID, const CGDisplayChangeSummaryFlags flags)
01450 {
01451     updateScreenShape();
01452 }
01453 
01454 void
01455 COSXScreen::updateScreenShape()
01456 {
01457     // get info for each display
01458     CGDisplayCount displayCount = 0;
01459 
01460     if (CGGetActiveDisplayList(0, NULL, &displayCount) != CGDisplayNoErr) {
01461         return;
01462     }
01463     
01464     if (displayCount == 0) {
01465         return;
01466     }
01467 
01468     CGDirectDisplayID* displays = new CGDirectDisplayID[displayCount];
01469     if (displays == NULL) {
01470         return;
01471     }
01472 
01473     if (CGGetActiveDisplayList(displayCount,
01474                             displays, &displayCount) != CGDisplayNoErr) {
01475         delete[] displays;
01476         return;
01477     }
01478 
01479     // get smallest rect enclosing all display rects
01480     CGRect totalBounds = CGRectZero;
01481     for (CGDisplayCount i = 0; i < displayCount; ++i) {
01482         CGRect bounds = CGDisplayBounds(displays[i]);
01483         totalBounds   = CGRectUnion(totalBounds, bounds);
01484     }
01485 
01486     // get shape of default screen
01487     m_x = (SInt32)totalBounds.origin.x;
01488     m_y = (SInt32)totalBounds.origin.y;
01489     m_w = (SInt32)totalBounds.size.width;
01490     m_h = (SInt32)totalBounds.size.height;
01491 
01492     // get center of default screen
01493   CGDirectDisplayID main = CGMainDisplayID();
01494   const CGRect rect = CGDisplayBounds(main);
01495   m_xCenter = (rect.origin.x + rect.size.width) / 2;
01496   m_yCenter = (rect.origin.y + rect.size.height) / 2;
01497 
01498     delete[] displays;
01499     // We want to notify the peer screen whether we are primary screen or not
01500     sendEvent(getShapeChangedEvent());
01501 
01502     LOG((CLOG_DEBUG "screen shape: center=%d,%d size=%dx%d on %u %s",
01503          m_x, m_y, m_w, m_h, displayCount,
01504          (displayCount == 1) ? "display" : "displays"));
01505 }
01506 
01507 #pragma mark - 
01508 
01509 //
01510 // FAST USER SWITCH NOTIFICATION SUPPORT
01511 //
01512 // COSXScreen::userSwitchCallback(void*)
01513 // 
01514 // gets called if a fast user switch occurs
01515 //
01516 
01517 pascal OSStatus
01518 COSXScreen::userSwitchCallback(EventHandlerCallRef nextHandler,
01519                                 EventRef theEvent,
01520                                 void* inUserData)
01521 {
01522     COSXScreen* screen = (COSXScreen*)inUserData;
01523     UInt32 kind        = GetEventKind(theEvent);
01524 
01525     if (kind == kEventSystemUserSessionDeactivated) {
01526         LOG((CLOG_DEBUG "user session deactivated"));
01527         EVENTQUEUE->addEvent(CEvent(IScreen::getSuspendEvent(),
01528                                     screen->getEventTarget()));
01529     }
01530     else if (kind == kEventSystemUserSessionActivated) {
01531         LOG((CLOG_DEBUG "user session activated"));
01532         EVENTQUEUE->addEvent(CEvent(IScreen::getResumeEvent(),
01533                                     screen->getEventTarget()));
01534     }
01535     return (CallNextEventHandler(nextHandler, theEvent));
01536 }
01537 
01538 #pragma mark - 
01539 
01540 //
01541 // SLEEP/WAKEUP NOTIFICATION SUPPORT
01542 //
01543 // COSXScreen::watchSystemPowerThread(void*)
01544 // 
01545 // main of thread monitoring system power (sleep/wakup) using a CFRunLoop
01546 //
01547 
01548 void
01549 COSXScreen::watchSystemPowerThread(void*)
01550 {
01551     io_object_t             notifier;
01552     IONotificationPortRef   notificationPortRef;
01553     CFRunLoopSourceRef      runloopSourceRef = 0;
01554 
01555     m_pmRunloop = CFRunLoopGetCurrent();
01556     // install system power change callback
01557     m_pmRootPort = IORegisterForSystemPower(this, &notificationPortRef,
01558                                             powerChangeCallback, &notifier);
01559     if (m_pmRootPort == 0) {
01560         LOG((CLOG_WARN "IORegisterForSystemPower failed"));
01561     }
01562     else {
01563         runloopSourceRef =
01564             IONotificationPortGetRunLoopSource(notificationPortRef);
01565         CFRunLoopAddSource(m_pmRunloop, runloopSourceRef,
01566                                 kCFRunLoopCommonModes);
01567     }
01568     
01569     // thread is ready
01570     {
01571         CLock lock(m_pmMutex);
01572         *m_pmThreadReady = true;
01573         m_pmThreadReady->signal();
01574     }
01575 
01576     // if we were unable to initialize then exit.  we must do this after
01577     // setting m_pmThreadReady to true otherwise the parent thread will
01578     // block waiting for it.
01579     if (m_pmRootPort == 0) {
01580         return;
01581     }
01582 
01583     // start the run loop
01584     LOG((CLOG_DEBUG "started watchSystemPowerThread"));
01585     CFRunLoopRun();
01586     
01587     // cleanup
01588     if (notificationPortRef) {
01589         CFRunLoopRemoveSource(m_pmRunloop,
01590                                 runloopSourceRef, kCFRunLoopDefaultMode);
01591         CFRunLoopSourceInvalidate(runloopSourceRef);
01592         CFRelease(runloopSourceRef);
01593     }
01594 
01595     CLock lock(m_pmMutex);
01596     IODeregisterForSystemPower(&notifier);
01597     m_pmRootPort = 0;
01598     LOG((CLOG_DEBUG "stopped watchSystemPowerThread"));
01599 }
01600 
01601 void
01602 COSXScreen::powerChangeCallback(void* refcon, io_service_t service,
01603                           natural_t messageType, void* messageArg)
01604 {
01605     ((COSXScreen*)refcon)->handlePowerChangeRequest(messageType, messageArg);
01606 }
01607 
01608 void
01609 COSXScreen::handlePowerChangeRequest(natural_t messageType, void* messageArg)
01610 {
01611     // we've received a power change notification
01612     switch (messageType) {
01613     case kIOMessageSystemWillSleep:
01614         // COSXScreen has to handle this in the main thread so we have to
01615         // queue a confirm sleep event here.  we actually don't allow the
01616         // system to sleep until the event is handled.
01617         EVENTQUEUE->addEvent(CEvent(COSXScreen::getConfirmSleepEvent(),
01618                                 getEventTarget(), messageArg,
01619                                 CEvent::kDontFreeData));
01620         return;
01621             
01622     case kIOMessageSystemHasPoweredOn:
01623         LOG((CLOG_DEBUG "system wakeup"));
01624         EVENTQUEUE->addEvent(CEvent(IScreen::getResumeEvent(),
01625                                 getEventTarget()));
01626         break;
01627 
01628     default:
01629         break;
01630     }
01631 
01632     CLock lock(m_pmMutex);
01633     if (m_pmRootPort != 0) {
01634         IOAllowPowerChange(m_pmRootPort, (long)messageArg);
01635     }
01636 }
01637 
01638 CEvent::Type
01639 COSXScreen::getConfirmSleepEvent()
01640 {
01641     return EVENTQUEUE->registerTypeOnce(s_confirmSleepEvent,
01642                                     "COSXScreen::confirmSleep");
01643 }
01644 
01645 void
01646 COSXScreen::handleConfirmSleep(const CEvent& event, void*)
01647 {
01648     long messageArg = (long)event.getData();
01649     if (messageArg != 0) {
01650         CLock lock(m_pmMutex);
01651         if (m_pmRootPort != 0) {
01652             // deliver suspend event immediately.
01653             EVENTQUEUE->addEvent(CEvent(IScreen::getSuspendEvent(),
01654                                     getEventTarget(), NULL, 
01655                                     CEvent::kDeliverImmediately));
01656     
01657             LOG((CLOG_DEBUG "system will sleep"));
01658             IOAllowPowerChange(m_pmRootPort, messageArg);
01659         }
01660     }
01661 }
01662 
01663 #pragma mark - 
01664 
01665 //
01666 // GLOBAL HOTKEY OPERATING MODE SUPPORT (10.3)
01667 //
01668 // CoreGraphics private API (OSX 10.3)
01669 // Source: http://ichiro.nnip.org/osx/Cocoa/GlobalHotkey.html
01670 //
01671 // We load the functions dynamically because they're not available in
01672 // older SDKs.  We don't use weak linking because we want users of
01673 // older SDKs to build an app that works on newer systems and older
01674 // SDKs will not provide the symbols.
01675 //
01676 // FIXME: This is hosed as of OS 10.5; patches to repair this are
01677 // a good thing.
01678 //
01679 #if 0
01680 
01681 #ifdef  __cplusplus
01682 extern "C" {
01683 #endif
01684 
01685 typedef int CGSConnection;
01686 typedef enum {
01687     CGSGlobalHotKeyEnable = 0,
01688     CGSGlobalHotKeyDisable = 1,
01689 } CGSGlobalHotKeyOperatingMode;
01690 
01691 extern CGSConnection _CGSDefaultConnection(void) WEAK_IMPORT_ATTRIBUTE;
01692 extern CGError CGSGetGlobalHotKeyOperatingMode(CGSConnection connection, CGSGlobalHotKeyOperatingMode *mode) WEAK_IMPORT_ATTRIBUTE;
01693 extern CGError CGSSetGlobalHotKeyOperatingMode(CGSConnection connection, CGSGlobalHotKeyOperatingMode mode) WEAK_IMPORT_ATTRIBUTE;
01694 
01695 typedef CGSConnection (*_CGSDefaultConnection_t)(void);
01696 typedef CGError (*CGSGetGlobalHotKeyOperatingMode_t)(CGSConnection connection, CGSGlobalHotKeyOperatingMode *mode);
01697 typedef CGError (*CGSSetGlobalHotKeyOperatingMode_t)(CGSConnection connection, CGSGlobalHotKeyOperatingMode mode);
01698 
01699 static _CGSDefaultConnection_t              s__CGSDefaultConnection;
01700 static CGSGetGlobalHotKeyOperatingMode_t    s_CGSGetGlobalHotKeyOperatingMode;
01701 static CGSSetGlobalHotKeyOperatingMode_t    s_CGSSetGlobalHotKeyOperatingMode;
01702 
01703 #ifdef  __cplusplus
01704 }
01705 #endif
01706 
01707 #define LOOKUP(name_)                                                   \
01708     s_ ## name_ = NULL;                                                 \
01709     if (NSIsSymbolNameDefinedWithHint("_" #name_, "CoreGraphics")) {    \
01710         s_ ## name_ = (name_ ## _t)NSAddressOfSymbol(                   \
01711                             NSLookupAndBindSymbolWithHint(              \
01712                                 "_" #name_, "CoreGraphics"));           \
01713     }
01714 
01715 bool
01716 COSXScreen::isGlobalHotKeyOperatingModeAvailable()
01717 {
01718     if (!s_testedForGHOM) {
01719         s_testedForGHOM = true;
01720         LOOKUP(_CGSDefaultConnection);
01721         LOOKUP(CGSGetGlobalHotKeyOperatingMode);
01722         LOOKUP(CGSSetGlobalHotKeyOperatingMode);
01723         s_hasGHOM = (s__CGSDefaultConnection != NULL &&
01724                     s_CGSGetGlobalHotKeyOperatingMode != NULL &&
01725                     s_CGSSetGlobalHotKeyOperatingMode != NULL);
01726     }
01727     return s_hasGHOM;
01728 }
01729 
01730 void
01731 COSXScreen::setGlobalHotKeysEnabled(bool enabled)
01732 {
01733     if (isGlobalHotKeyOperatingModeAvailable()) {
01734         CGSConnection conn = s__CGSDefaultConnection();
01735 
01736         CGSGlobalHotKeyOperatingMode mode;
01737         s_CGSGetGlobalHotKeyOperatingMode(conn, &mode);
01738 
01739         if (enabled && mode == CGSGlobalHotKeyDisable) {
01740             s_CGSSetGlobalHotKeyOperatingMode(conn, CGSGlobalHotKeyEnable);
01741         }
01742         else if (!enabled && mode == CGSGlobalHotKeyEnable) {
01743             s_CGSSetGlobalHotKeyOperatingMode(conn, CGSGlobalHotKeyDisable);
01744         }
01745     }
01746 }
01747 
01748 bool
01749 COSXScreen::getGlobalHotKeysEnabled()
01750 {
01751     CGSGlobalHotKeyOperatingMode mode;
01752     if (isGlobalHotKeyOperatingModeAvailable()) {
01753         CGSConnection conn = s__CGSDefaultConnection();
01754         s_CGSGetGlobalHotKeyOperatingMode(conn, &mode);
01755     }
01756     else {
01757         mode = CGSGlobalHotKeyEnable;
01758     }
01759     return (mode == CGSGlobalHotKeyEnable);
01760 }
01761 
01762 #endif
01763 
01764 //
01765 // COSXScreen::CHotKeyItem
01766 //
01767 
01768 COSXScreen::CHotKeyItem::CHotKeyItem(UInt32 keycode, UInt32 mask) :
01769     m_ref(NULL),
01770     m_keycode(keycode),
01771     m_mask(mask)
01772 {
01773     // do nothing
01774 }
01775 
01776 COSXScreen::CHotKeyItem::CHotKeyItem(EventHotKeyRef ref,
01777                 UInt32 keycode, UInt32 mask) :
01778     m_ref(ref),
01779     m_keycode(keycode),
01780     m_mask(mask)
01781 {
01782     // do nothing
01783 }
01784 
01785 EventHotKeyRef
01786 COSXScreen::CHotKeyItem::getRef() const
01787 {
01788     return m_ref;
01789 }
01790 
01791 bool
01792 COSXScreen::CHotKeyItem::operator<(const CHotKeyItem& x) const
01793 {
01794     return (m_keycode < x.m_keycode ||
01795             (m_keycode == x.m_keycode && m_mask < x.m_mask));
01796 }
01797 
01798 // Quartz event tap support for the secondary display. This makes sure that we
01799 // will show the cursor if a local event comes in while synergy has the cursor
01800 // off the screen.
01801 CGEventRef
01802 COSXScreen::handleCGInputEventSecondary(
01803     CGEventTapProxy proxy,
01804     CGEventType type,
01805     CGEventRef event,
01806     void* refcon)
01807 {
01808     // this fix is really screwing with the correct show/hide behavior. it
01809     // should be tested better before reintroducing.
01810     return event;
01811 
01812     COSXScreen* screen = (COSXScreen*)refcon;
01813     if (screen->m_cursorHidden && type == kCGEventMouseMoved) {
01814 
01815         CGPoint pos = CGEventGetLocation(event);
01816         if (pos.x != screen->m_xCenter || pos.y != screen->m_yCenter) {
01817 
01818             LOG((CLOG_DEBUG "show cursor on secondary, type=%d pos=%d,%d",
01819                     type, pos.x, pos.y));
01820             screen->showCursor();
01821         }
01822     }
01823     return event;
01824 }
01825 
01826 // Quartz event tap support
01827 CGEventRef
01828 COSXScreen::handleCGInputEvent(CGEventTapProxy proxy,
01829                                CGEventType type,
01830                                CGEventRef event,
01831                                void* refcon)
01832 {
01833     COSXScreen* screen = (COSXScreen*)refcon;
01834     CGPoint pos;
01835 
01836     switch(type) {
01837         case kCGEventLeftMouseDown:
01838         case kCGEventRightMouseDown:
01839         case kCGEventOtherMouseDown:
01840             screen->onMouseButton(true, CGEventGetIntegerValueField(event, kCGMouseEventButtonNumber) + 1);
01841             break;
01842         case kCGEventLeftMouseUp:
01843         case kCGEventRightMouseUp:
01844         case kCGEventOtherMouseUp:
01845             screen->onMouseButton(false, CGEventGetIntegerValueField(event, kCGMouseEventButtonNumber) + 1);
01846             break;
01847         case kCGEventMouseMoved:
01848         case kCGEventLeftMouseDragged:
01849         case kCGEventRightMouseDragged:
01850         case kCGEventOtherMouseDragged:
01851             pos = CGEventGetLocation(event);
01852             screen->onMouseMove(pos.x, pos.y);
01853             
01854             // The system ignores our cursor-centering calls if
01855             // we don't return the event. This should be harmless,
01856             // but might register as slight movement to other apps
01857             // on the system. It hasn't been a problem before, though.
01858             return event;
01859             break;
01860         case kCGEventScrollWheel:
01861             screen->onMouseWheel(screen->mapScrollWheelToSynergy(
01862                                  CGEventGetIntegerValueField(event, kCGScrollWheelEventDeltaAxis2)),
01863                                  screen->mapScrollWheelToSynergy(
01864                                  CGEventGetIntegerValueField(event, kCGScrollWheelEventDeltaAxis1)));
01865             break;
01866         case kCGEventKeyDown:
01867         case kCGEventKeyUp:
01868         case kCGEventFlagsChanged:
01869             screen->onKey(event);
01870             break;
01871         case kCGEventTapDisabledByTimeout:
01872             // Re-enable our event-tap
01873             CGEventTapEnable(screen->m_eventTapPort, true);
01874             LOG((CLOG_NOTE "quartz event tap was disabled by timeout, re-enabling"));
01875             break;
01876         case kCGEventTapDisabledByUserInput:
01877             LOG((CLOG_ERR "quartz event tap was disabled by user input"));
01878             break;
01879         case NX_NULLEVENT:
01880             break;
01881         case NX_SYSDEFINED:
01882             // Unknown, forward it
01883             return event;
01884             break;
01885         case NX_NUMPROCS:
01886             break;
01887         default:
01888             LOG((CLOG_NOTE "unknown quartz event type: 0x%02x", type));
01889     }
01890     
01891     if(screen->m_isOnScreen) {
01892         return event;
01893     } else {
01894         return NULL;
01895     }
01896 }
01897 
01898 void
01899 COSXScreen::CMouseButtonState::set(UInt32 button, MouseButtonState state) 
01900 {
01901     bool newState = (state == kMouseButtonDown);
01902     m_buttons.set(button, newState);
01903 }
01904 
01905 bool
01906 COSXScreen::CMouseButtonState::any() 
01907 {
01908     return m_buttons.any();
01909 }
01910 
01911 void
01912 COSXScreen::CMouseButtonState::reset() 
01913 {
01914     m_buttons.reset();
01915 }
01916 
01917 void
01918 COSXScreen::CMouseButtonState::overwrite(UInt32 buttons) 
01919 {
01920     m_buttons = std::bitset<NumButtonIDs>(buttons);
01921 }
01922 
01923 bool
01924 COSXScreen::CMouseButtonState::test(UInt32 button) const 
01925 {
01926     return m_buttons.test(button);
01927 }
01928 
01929 SInt8
01930 COSXScreen::CMouseButtonState::getFirstButtonDown() const 
01931 {
01932     if (m_buttons.any()) {
01933         for (unsigned short button = 0; button < m_buttons.size(); button++) {
01934             if (m_buttons.test(button)) {
01935                 return button;
01936             }
01937         }
01938     }
01939     return -1;
01940 }

Generated on Fri May 24 2013 00:00:04 for Synergy by  doxygen 1.7.1