Main Page | Class Hierarchy | Alphabetical List | Class List | File List | Class Members

COSXScreen.cpp

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

Generated on Fri Nov 6 00:21:14 2009 for synergy-plus by  doxygen 1.3.9.1