aboutsummaryrefslogtreecommitdiffstats
path: root/src/lib/platform/OSXScreen.mm
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/platform/OSXScreen.mm')
-rw-r--r--src/lib/platform/OSXScreen.mm2162
1 files changed, 2162 insertions, 0 deletions
diff --git a/src/lib/platform/OSXScreen.mm b/src/lib/platform/OSXScreen.mm
new file mode 100644
index 0000000..1d80521
--- /dev/null
+++ b/src/lib/platform/OSXScreen.mm
@@ -0,0 +1,2162 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2004 Chris Schoeneman
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "platform/OSXScreen.h"
+
+#include "base/EventQueue.h"
+#include "client/Client.h"
+#include "platform/OSXClipboard.h"
+#include "platform/OSXEventQueueBuffer.h"
+#include "platform/OSXKeyState.h"
+#include "platform/OSXScreenSaver.h"
+#include "platform/OSXDragSimulator.h"
+#include "platform/OSXMediaKeySupport.h"
+#include "platform/OSXPasteboardPeeker.h"
+#include "barrier/Clipboard.h"
+#include "barrier/KeyMap.h"
+#include "barrier/ClientApp.h"
+#include "mt/CondVar.h"
+#include "mt/Lock.h"
+#include "mt/Mutex.h"
+#include "mt/Thread.h"
+#include "arch/XArch.h"
+#include "base/Log.h"
+#include "base/IEventQueue.h"
+#include "base/TMethodEventJob.h"
+#include "base/TMethodJob.h"
+
+#include <math.h>
+#include <mach-o/dyld.h>
+#include <AvailabilityMacros.h>
+#include <IOKit/hidsystem/event_status_driver.h>
+#include <AppKit/NSEvent.h>
+
+// This isn't in any Apple SDK that I know of as of yet.
+enum {
+ kBarrierEventMouseScroll = 11,
+ kBarrierMouseScrollAxisX = 'saxx',
+ kBarrierMouseScrollAxisY = 'saxy'
+};
+
+enum {
+ kCarbonLoopWaitTimeout = 10
+};
+
+// TODO: upgrade deprecated function usage in these functions.
+void setZeroSuppressionInterval();
+void avoidSupression();
+void logCursorVisibility();
+void avoidHesitatingCursor();
+
+//
+// OSXScreen
+//
+
+bool OSXScreen::s_testedForGHOM = false;
+bool OSXScreen::s_hasGHOM = false;
+
+OSXScreen::OSXScreen(IEventQueue* events, bool isPrimary, bool autoShowHideCursor) :
+ PlatformScreen(events),
+ m_isPrimary(isPrimary),
+ m_isOnScreen(m_isPrimary),
+ m_cursorPosValid(false),
+ MouseButtonEventMap(NumButtonIDs),
+ m_cursorHidden(false),
+ m_dragNumButtonsDown(0),
+ m_dragTimer(NULL),
+ m_keyState(NULL),
+ m_sequenceNumber(0),
+ m_screensaver(NULL),
+ m_screensaverNotify(false),
+ m_ownClipboard(false),
+ m_clipboardTimer(NULL),
+ m_hiddenWindow(NULL),
+ m_userInputWindow(NULL),
+ m_switchEventHandlerRef(0),
+ m_pmMutex(new Mutex),
+ m_pmWatchThread(NULL),
+ m_pmThreadReady(new CondVar<bool>(m_pmMutex, false)),
+ m_pmRootPort(0),
+ m_activeModifierHotKey(0),
+ m_activeModifierHotKeyMask(0),
+ m_eventTapPort(nullptr),
+ m_eventTapRLSR(nullptr),
+ m_lastClickTime(0),
+ m_clickState(1),
+ m_lastSingleClickXCursor(0),
+ m_lastSingleClickYCursor(0),
+ m_autoShowHideCursor(autoShowHideCursor),
+ m_events(events),
+ m_getDropTargetThread(NULL),
+ m_impl(NULL)
+{
+ try {
+ m_displayID = CGMainDisplayID();
+ updateScreenShape(m_displayID, 0);
+ m_screensaver = new OSXScreenSaver(m_events, getEventTarget());
+ m_keyState = new OSXKeyState(m_events);
+
+ // only needed when running as a server.
+ if (m_isPrimary) {
+
+#if defined(MAC_OS_X_VERSION_10_9)
+ // we can't pass options to show the dialog, this must be done by the gui.
+ if (!AXIsProcessTrusted()) {
+ throw XArch("assistive devices does not trust this process, allow it in system settings.");
+ }
+#else
+ // now deprecated in mavericks.
+ if (!AXAPIEnabled()) {
+ throw XArch("assistive devices is not enabled, enable it in system settings.");
+ }
+#endif
+ }
+
+ // install display manager notification handler
+ CGDisplayRegisterReconfigurationCallback(displayReconfigurationCallback, this);
+
+ // install fast user switching event handler
+ EventTypeSpec switchEventTypes[2];
+ switchEventTypes[0].eventClass = kEventClassSystem;
+ switchEventTypes[0].eventKind = kEventSystemUserSessionDeactivated;
+ switchEventTypes[1].eventClass = kEventClassSystem;
+ switchEventTypes[1].eventKind = kEventSystemUserSessionActivated;
+ EventHandlerUPP switchEventHandler =
+ NewEventHandlerUPP(userSwitchCallback);
+ InstallApplicationEventHandler(switchEventHandler, 2, switchEventTypes,
+ this, &m_switchEventHandlerRef);
+ DisposeEventHandlerUPP(switchEventHandler);
+
+ constructMouseButtonEventMap();
+
+ // watch for requests to sleep
+ m_events->adoptHandler(m_events->forOSXScreen().confirmSleep(),
+ getEventTarget(),
+ new TMethodEventJob<OSXScreen>(this,
+ &OSXScreen::handleConfirmSleep));
+
+ // create thread for monitoring system power state.
+ *m_pmThreadReady = false;
+#if defined(MAC_OS_X_VERSION_10_7)
+ m_carbonLoopMutex = new Mutex();
+ m_carbonLoopReady = new CondVar<bool>(m_carbonLoopMutex, false);
+#endif
+ LOG((CLOG_DEBUG "starting watchSystemPowerThread"));
+ m_pmWatchThread = new Thread(new TMethodJob<OSXScreen>
+ (this, &OSXScreen::watchSystemPowerThread));
+ }
+ catch (...) {
+ m_events->removeHandler(m_events->forOSXScreen().confirmSleep(),
+ getEventTarget());
+ if (m_switchEventHandlerRef != 0) {
+ RemoveEventHandler(m_switchEventHandlerRef);
+ }
+
+ CGDisplayRemoveReconfigurationCallback(displayReconfigurationCallback, this);
+
+ delete m_keyState;
+ delete m_screensaver;
+ throw;
+ }
+
+ // install event handlers
+ m_events->adoptHandler(Event::kSystem, m_events->getSystemTarget(),
+ new TMethodEventJob<OSXScreen>(this,
+ &OSXScreen::handleSystemEvent));
+
+ // install the platform event queue
+ m_events->adoptBuffer(new OSXEventQueueBuffer(m_events));
+}
+
+OSXScreen::~OSXScreen()
+{
+ disable();
+ m_events->adoptBuffer(NULL);
+ m_events->removeHandler(Event::kSystem, m_events->getSystemTarget());
+
+ if (m_pmWatchThread) {
+ // make sure the thread has setup the runloop.
+ {
+ Lock lock(m_pmMutex);
+ while (!(bool)*m_pmThreadReady) {
+ m_pmThreadReady->wait();
+ }
+ }
+
+ // now exit the thread's runloop and wait for it to exit
+ LOG((CLOG_DEBUG "stopping watchSystemPowerThread"));
+ CFRunLoopStop(m_pmRunloop);
+ m_pmWatchThread->wait();
+ delete m_pmWatchThread;
+ m_pmWatchThread = NULL;
+ }
+ delete m_pmThreadReady;
+ delete m_pmMutex;
+
+ m_events->removeHandler(m_events->forOSXScreen().confirmSleep(),
+ getEventTarget());
+
+ RemoveEventHandler(m_switchEventHandlerRef);
+
+ CGDisplayRemoveReconfigurationCallback(displayReconfigurationCallback, this);
+
+ delete m_keyState;
+ delete m_screensaver;
+
+#if defined(MAC_OS_X_VERSION_10_7)
+ delete m_carbonLoopMutex;
+ delete m_carbonLoopReady;
+#endif
+}
+
+void*
+OSXScreen::getEventTarget() const
+{
+ return const_cast<OSXScreen*>(this);
+}
+
+bool
+OSXScreen::getClipboard(ClipboardID, IClipboard* dst) const
+{
+ Clipboard::copy(dst, &m_pasteboard);
+ return true;
+}
+
+void
+OSXScreen::getShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h) const
+{
+ x = m_x;
+ y = m_y;
+ w = m_w;
+ h = m_h;
+}
+
+void
+OSXScreen::getCursorPos(SInt32& x, SInt32& y) const
+{
+ CGEventRef event = CGEventCreate(NULL);
+ CGPoint mouse = CGEventGetLocation(event);
+ x = mouse.x;
+ y = mouse.y;
+ m_cursorPosValid = true;
+ m_xCursor = x;
+ m_yCursor = y;
+ CFRelease(event);
+}
+
+void
+OSXScreen::reconfigure(UInt32)
+{
+ // do nothing
+}
+
+void
+OSXScreen::warpCursor(SInt32 x, SInt32 y)
+{
+ // move cursor without generating events
+ CGPoint pos;
+ pos.x = x;
+ pos.y = y;
+ CGWarpMouseCursorPosition(pos);
+
+ // save new cursor position
+ m_xCursor = x;
+ m_yCursor = y;
+ m_cursorPosValid = true;
+}
+
+void
+OSXScreen::fakeInputBegin()
+{
+ // FIXME -- not implemented
+}
+
+void
+OSXScreen::fakeInputEnd()
+{
+ // FIXME -- not implemented
+}
+
+SInt32
+OSXScreen::getJumpZoneSize() const
+{
+ return 1;
+}
+
+bool
+OSXScreen::isAnyMouseButtonDown(UInt32& buttonID) const
+{
+ if (m_buttonState.test(0)) {
+ buttonID = kButtonLeft;
+ return true;
+ }
+
+ return (GetCurrentButtonState() != 0);
+}
+
+void
+OSXScreen::getCursorCenter(SInt32& x, SInt32& y) const
+{
+ x = m_xCenter;
+ y = m_yCenter;
+}
+
+UInt32
+OSXScreen::registerHotKey(KeyID key, KeyModifierMask mask)
+{
+ // get mac virtual key and modifier mask matching barrier key and mask
+ UInt32 macKey, macMask;
+ if (!m_keyState->mapBarrierHotKeyToMac(key, mask, macKey, macMask)) {
+ LOG((CLOG_DEBUG "could not map hotkey id=%04x mask=%04x", key, mask));
+ return 0;
+ }
+
+ // choose hotkey id
+ UInt32 id;
+ if (!m_oldHotKeyIDs.empty()) {
+ id = m_oldHotKeyIDs.back();
+ m_oldHotKeyIDs.pop_back();
+ }
+ else {
+ id = m_hotKeys.size() + 1;
+ }
+
+ // if this hot key has modifiers only then we'll handle it specially
+ EventHotKeyRef ref = NULL;
+ bool okay;
+ if (key == kKeyNone) {
+ if (m_modifierHotKeys.count(mask) > 0) {
+ // already registered
+ okay = false;
+ }
+ else {
+ m_modifierHotKeys[mask] = id;
+ okay = true;
+ }
+ }
+ else {
+ EventHotKeyID hkid = { 'SNRG', (UInt32)id };
+ OSStatus status = RegisterEventHotKey(macKey, macMask, hkid,
+ GetApplicationEventTarget(), 0,
+ &ref);
+ okay = (status == noErr);
+ m_hotKeyToIDMap[HotKeyItem(macKey, macMask)] = id;
+ }
+
+ if (!okay) {
+ m_oldHotKeyIDs.push_back(id);
+ m_hotKeyToIDMap.erase(HotKeyItem(macKey, macMask));
+ LOG((CLOG_WARN "failed to register hotkey %s (id=%04x mask=%04x)", barrier::KeyMap::formatKey(key, mask).c_str(), key, mask));
+ return 0;
+ }
+
+ m_hotKeys.insert(std::make_pair(id, HotKeyItem(ref, macKey, macMask)));
+
+ LOG((CLOG_DEBUG "registered hotkey %s (id=%04x mask=%04x) as id=%d", barrier::KeyMap::formatKey(key, mask).c_str(), key, mask, id));
+ return id;
+}
+
+void
+OSXScreen::unregisterHotKey(UInt32 id)
+{
+ // look up hotkey
+ HotKeyMap::iterator i = m_hotKeys.find(id);
+ if (i == m_hotKeys.end()) {
+ return;
+ }
+
+ // unregister with OS
+ bool okay;
+ if (i->second.getRef() != NULL) {
+ okay = (UnregisterEventHotKey(i->second.getRef()) == noErr);
+ }
+ else {
+ okay = false;
+ // XXX -- this is inefficient
+ for (ModifierHotKeyMap::iterator j = m_modifierHotKeys.begin();
+ j != m_modifierHotKeys.end(); ++j) {
+ if (j->second == id) {
+ m_modifierHotKeys.erase(j);
+ okay = true;
+ break;
+ }
+ }
+ }
+ if (!okay) {
+ LOG((CLOG_WARN "failed to unregister hotkey id=%d", id));
+ }
+ else {
+ LOG((CLOG_DEBUG "unregistered hotkey id=%d", id));
+ }
+
+ // discard hot key from map and record old id for reuse
+ m_hotKeyToIDMap.erase(i->second);
+ m_hotKeys.erase(i);
+ m_oldHotKeyIDs.push_back(id);
+ if (m_activeModifierHotKey == id) {
+ m_activeModifierHotKey = 0;
+ m_activeModifierHotKeyMask = 0;
+ }
+}
+
+void
+OSXScreen::constructMouseButtonEventMap()
+{
+ const CGEventType source[NumButtonIDs][3] = {
+ {kCGEventLeftMouseUp, kCGEventLeftMouseDragged, kCGEventLeftMouseDown},
+ {kCGEventRightMouseUp, kCGEventRightMouseDragged, kCGEventRightMouseDown},
+ {kCGEventOtherMouseUp, kCGEventOtherMouseDragged, kCGEventOtherMouseDown},
+ {kCGEventOtherMouseUp, kCGEventOtherMouseDragged, kCGEventOtherMouseDown},
+ {kCGEventOtherMouseUp, kCGEventOtherMouseDragged, kCGEventOtherMouseDown}
+ };
+
+ for (UInt16 button = 0; button < NumButtonIDs; button++) {
+ MouseButtonEventMapType new_map;
+ for (UInt16 state = (UInt32) kMouseButtonUp; state < kMouseButtonStateMax; state++) {
+ CGEventType curEvent = source[button][state];
+ new_map[state] = curEvent;
+ }
+ MouseButtonEventMap[button] = new_map;
+ }
+}
+
+void
+OSXScreen::postMouseEvent(CGPoint& pos) const
+{
+ // check if cursor position is valid on the client display configuration
+ // stkamp@users.sourceforge.net
+ CGDisplayCount displayCount = 0;
+ CGGetDisplaysWithPoint(pos, 0, NULL, &displayCount);
+ if (displayCount == 0) {
+ // cursor position invalid - clamp to bounds of last valid display.
+ // find the last valid display using the last cursor position.
+ displayCount = 0;
+ CGDirectDisplayID displayID;
+ CGGetDisplaysWithPoint(CGPointMake(m_xCursor, m_yCursor), 1,
+ &displayID, &displayCount);
+ if (displayCount != 0) {
+ CGRect displayRect = CGDisplayBounds(displayID);
+ if (pos.x < displayRect.origin.x) {
+ pos.x = displayRect.origin.x;
+ }
+ else if (pos.x > displayRect.origin.x +
+ displayRect.size.width - 1) {
+ pos.x = displayRect.origin.x + displayRect.size.width - 1;
+ }
+ if (pos.y < displayRect.origin.y) {
+ pos.y = displayRect.origin.y;
+ }
+ else if (pos.y > displayRect.origin.y +
+ displayRect.size.height - 1) {
+ pos.y = displayRect.origin.y + displayRect.size.height - 1;
+ }
+ }
+ }
+
+ CGEventType type = kCGEventMouseMoved;
+
+ SInt8 button = m_buttonState.getFirstButtonDown();
+ if (button != -1) {
+ MouseButtonEventMapType thisButtonType = MouseButtonEventMap[button];
+ type = thisButtonType[kMouseButtonDragged];
+ }
+
+ CGEventRef event = CGEventCreateMouseEvent(NULL, type, pos, static_cast<CGMouseButton>(button));
+
+ // Dragging events also need the click state
+ CGEventSetIntegerValueField(event, kCGMouseEventClickState, m_clickState);
+
+ // Fix for sticky keys
+ CGEventFlags modifiers = m_keyState->getModifierStateAsOSXFlags();
+ CGEventSetFlags(event, modifiers);
+
+ // Set movement deltas to fix issues with certain 3D programs
+ SInt64 deltaX = pos.x;
+ deltaX -= m_xCursor;
+
+ SInt64 deltaY = pos.y;
+ deltaY -= m_yCursor;
+
+ CGEventSetIntegerValueField(event, kCGMouseEventDeltaX, deltaX);
+ CGEventSetIntegerValueField(event, kCGMouseEventDeltaY, deltaY);
+
+ double deltaFX = deltaX;
+ double deltaFY = deltaY;
+
+ CGEventSetDoubleValueField(event, kCGMouseEventDeltaX, deltaFX);
+ CGEventSetDoubleValueField(event, kCGMouseEventDeltaY, deltaFY);
+
+ CGEventPost(kCGHIDEventTap, event);
+
+ CFRelease(event);
+}
+
+void
+OSXScreen::fakeMouseButton(ButtonID id, bool press)
+{
+ // Buttons are indexed from one, but the button down array is indexed from zero
+ UInt32 index = mapBarrierButtonToMac(id) - kButtonLeft;
+ if (index >= NumButtonIDs) {
+ return;
+ }
+
+ CGPoint pos;
+ if (!m_cursorPosValid) {
+ SInt32 x, y;
+ getCursorPos(x, y);
+ }
+ pos.x = m_xCursor;
+ pos.y = m_yCursor;
+
+ // variable used to detect mouse coordinate differences between
+ // old & new mouse clicks. Used in double click detection.
+ SInt32 xDiff = m_xCursor - m_lastSingleClickXCursor;
+ SInt32 yDiff = m_yCursor - m_lastSingleClickYCursor;
+ double diff = sqrt(xDiff * xDiff + yDiff * yDiff);
+ // max sqrt(x^2 + y^2) difference allowed to double click
+ // since we don't have double click distance in NX APIs
+ // we define our own defaults.
+ const double maxDiff = sqrt(2) + 0.0001;
+
+ double clickTime = [NSEvent doubleClickInterval];
+
+ // As long as the click is within the time window and distance window
+ // increase clickState (double click, triple click, etc)
+ // This will allow for higher than triple click but the quartz documenation
+ // does not specify that this should be limited to triple click
+ if (press) {
+ if ((ARCH->time() - m_lastClickTime) <= clickTime && diff <= maxDiff){
+ m_clickState++;
+ }
+ else {
+ m_clickState = 1;
+ }
+
+ m_lastClickTime = ARCH->time();
+ }
+
+ if (m_clickState == 1){
+ m_lastSingleClickXCursor = m_xCursor;
+ m_lastSingleClickYCursor = m_yCursor;
+ }
+
+ EMouseButtonState state = press ? kMouseButtonDown : kMouseButtonUp;
+
+ LOG((CLOG_DEBUG1 "faking mouse button id: %d press: %s", index, press ? "pressed" : "released"));
+
+ MouseButtonEventMapType thisButtonMap = MouseButtonEventMap[index];
+ CGEventType type = thisButtonMap[state];
+
+ CGEventRef event = CGEventCreateMouseEvent(NULL, type, pos, static_cast<CGMouseButton>(index));
+
+ CGEventSetIntegerValueField(event, kCGMouseEventClickState, m_clickState);
+
+ // Fix for sticky keys
+ CGEventFlags modifiers = m_keyState->getModifierStateAsOSXFlags();
+ CGEventSetFlags(event, modifiers);
+
+ m_buttonState.set(index, state);
+ CGEventPost(kCGHIDEventTap, event);
+
+ CFRelease(event);
+
+ if (!press && (id == kButtonLeft)) {
+ if (m_fakeDraggingStarted) {
+ m_getDropTargetThread = new Thread(new TMethodJob<OSXScreen>(
+ this, &OSXScreen::getDropTargetThread));
+ }
+
+ m_draggingStarted = false;
+ }
+}
+
+void
+OSXScreen::getDropTargetThread(void*)
+{
+#if defined(MAC_OS_X_VERSION_10_7)
+ char* cstr = NULL;
+
+ // wait for 5 secs for the drop destinaiton string to be filled.
+ UInt32 timeout = ARCH->time() + 5;
+
+ while (ARCH->time() < timeout) {
+ CFStringRef cfstr = getCocoaDropTarget();
+ cstr = CFStringRefToUTF8String(cfstr);
+ CFRelease(cfstr);
+
+ if (cstr != NULL) {
+ break;
+ }
+ ARCH->sleep(.1f);
+ }
+
+ if (cstr != NULL) {
+ LOG((CLOG_DEBUG "drop target: %s", cstr));
+ m_dropTarget = cstr;
+ }
+ else {
+ LOG((CLOG_ERR "failed to get drop target"));
+ m_dropTarget.clear();
+ }
+#else
+ LOG((CLOG_WARN "drag drop not supported"));
+#endif
+ m_fakeDraggingStarted = false;
+}
+
+void
+OSXScreen::fakeMouseMove(SInt32 x, SInt32 y)
+{
+ if (m_fakeDraggingStarted) {
+ m_buttonState.set(0, kMouseButtonDown);
+ }
+
+ // index 0 means left mouse button
+ if (m_buttonState.test(0)) {
+ m_draggingStarted = true;
+ }
+
+ // synthesize event
+ CGPoint pos;
+ pos.x = x;
+ pos.y = y;
+ postMouseEvent(pos);
+
+ // save new cursor position
+ m_xCursor = static_cast<SInt32>(pos.x);
+ m_yCursor = static_cast<SInt32>(pos.y);
+ m_cursorPosValid = true;
+}
+
+void
+OSXScreen::fakeMouseRelativeMove(SInt32 dx, SInt32 dy) const
+{
+ // OS X does not appear to have a fake relative mouse move function.
+ // simulate it by getting the current mouse position and adding to
+ // that. this can yield the wrong answer but there's not much else
+ // we can do.
+
+ // get current position
+ CGEventRef event = CGEventCreate(NULL);
+ CGPoint oldPos = CGEventGetLocation(event);
+ CFRelease(event);
+
+ // synthesize event
+ CGPoint pos;
+ m_xCursor = static_cast<SInt32>(oldPos.x);
+ m_yCursor = static_cast<SInt32>(oldPos.y);
+ pos.x = oldPos.x + dx;
+ pos.y = oldPos.y + dy;
+ postMouseEvent(pos);
+
+ // we now assume we don't know the current cursor position
+ m_cursorPosValid = false;
+}
+
+void
+OSXScreen::fakeMouseWheel(SInt32 xDelta, SInt32 yDelta) const
+{
+ if (xDelta != 0 || yDelta != 0) {
+ // create a scroll event, post it and release it. not sure if kCGScrollEventUnitLine
+ // is the right choice here over kCGScrollEventUnitPixel
+ CGEventRef scrollEvent = CGEventCreateScrollWheelEvent(
+ NULL, kCGScrollEventUnitLine, 2,
+ mapScrollWheelFromBarrier(yDelta),
+ -mapScrollWheelFromBarrier(xDelta));
+
+ // Fix for sticky keys
+ CGEventFlags modifiers = m_keyState->getModifierStateAsOSXFlags();
+ CGEventSetFlags(scrollEvent, modifiers);
+
+ CGEventPost(kCGHIDEventTap, scrollEvent);
+ CFRelease(scrollEvent);
+ }
+}
+
+void
+OSXScreen::showCursor()
+{
+ LOG((CLOG_DEBUG "showing cursor"));
+
+ CFStringRef propertyString = CFStringCreateWithCString(
+ NULL, "SetsCursorInBackground", kCFStringEncodingMacRoman);
+
+ CGSSetConnectionProperty(
+ _CGSDefaultConnection(), _CGSDefaultConnection(),
+ propertyString, kCFBooleanTrue);
+
+ CFRelease(propertyString);
+
+ CGError error = CGDisplayShowCursor(m_displayID);
+ if (error != kCGErrorSuccess) {
+ LOG((CLOG_ERR "failed to show cursor, error=%d", error));
+ }
+
+ // appears to fix "mouse randomly not showing" bug
+ CGAssociateMouseAndMouseCursorPosition(true);
+
+ logCursorVisibility();
+
+ m_cursorHidden = false;
+}
+
+void
+OSXScreen::hideCursor()
+{
+ LOG((CLOG_DEBUG "hiding cursor"));
+
+ CFStringRef propertyString = CFStringCreateWithCString(
+ NULL, "SetsCursorInBackground", kCFStringEncodingMacRoman);
+
+ CGSSetConnectionProperty(
+ _CGSDefaultConnection(), _CGSDefaultConnection(),
+ propertyString, kCFBooleanTrue);
+
+ CFRelease(propertyString);
+
+ CGError error = CGDisplayHideCursor(m_displayID);
+ if (error != kCGErrorSuccess) {
+ LOG((CLOG_ERR "failed to hide cursor, error=%d", error));
+ }
+
+ // appears to fix "mouse randomly not hiding" bug
+ CGAssociateMouseAndMouseCursorPosition(true);
+
+ logCursorVisibility();
+
+ m_cursorHidden = true;
+}
+
+void
+OSXScreen::enable()
+{
+ // watch the clipboard
+ m_clipboardTimer = m_events->newTimer(1.0, NULL);
+ m_events->adoptHandler(Event::kTimer, m_clipboardTimer,
+ new TMethodEventJob<OSXScreen>(this,
+ &OSXScreen::handleClipboardCheck));
+
+ if (m_isPrimary) {
+ // FIXME -- start watching jump zones
+
+ // kCGEventTapOptionDefault = 0x00000000 (Missing in 10.4, so specified literally)
+ m_eventTapPort = CGEventTapCreate(kCGHIDEventTap, kCGHeadInsertEventTap, kCGEventTapOptionDefault,
+ kCGEventMaskForAllEvents,
+ handleCGInputEvent,
+ this);
+ }
+ else {
+ // FIXME -- prevent system from entering power save mode
+
+ if (m_autoShowHideCursor) {
+ hideCursor();
+ }
+
+ // warp the mouse to the cursor center
+ fakeMouseMove(m_xCenter, m_yCenter);
+
+ // there may be a better way to do this, but we register an event handler even if we're
+ // not on the primary display (acting as a client). This way, if a local event comes in
+ // (either keyboard or mouse), we can make sure to show the cursor if we've hidden it.
+ m_eventTapPort = CGEventTapCreate(kCGHIDEventTap, kCGHeadInsertEventTap, kCGEventTapOptionDefault,
+ kCGEventMaskForAllEvents,
+ handleCGInputEventSecondary,
+ this);
+ }
+
+ if (!m_eventTapPort) {
+ LOG((CLOG_ERR "failed to create quartz event tap"));
+ }
+
+ m_eventTapRLSR = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, m_eventTapPort, 0);
+ if (!m_eventTapRLSR) {
+ LOG((CLOG_ERR "failed to create a CFRunLoopSourceRef for the quartz event tap"));
+ }
+
+ CFRunLoopAddSource(CFRunLoopGetCurrent(), m_eventTapRLSR, kCFRunLoopDefaultMode);
+}
+
+void
+OSXScreen::disable()
+{
+ if (m_autoShowHideCursor) {
+ showCursor();
+ }
+
+ // FIXME -- stop watching jump zones, stop capturing input
+
+ if (m_eventTapRLSR) {
+ CFRunLoopRemoveSource(CFRunLoopGetCurrent(), m_eventTapRLSR, kCFRunLoopDefaultMode);
+ CFRelease(m_eventTapRLSR);
+ m_eventTapRLSR = nullptr;
+ }
+
+ if (m_eventTapPort) {
+ CGEventTapEnable(m_eventTapPort, false);
+ CFRelease(m_eventTapPort);
+ m_eventTapPort = nullptr;
+ }
+ // FIXME -- allow system to enter power saving mode
+
+ // disable drag handling
+ m_dragNumButtonsDown = 0;
+ enableDragTimer(false);
+
+ // uninstall clipboard timer
+ if (m_clipboardTimer != NULL) {
+ m_events->removeHandler(Event::kTimer, m_clipboardTimer);
+ m_events->deleteTimer(m_clipboardTimer);
+ m_clipboardTimer = NULL;
+ }
+
+ m_isOnScreen = m_isPrimary;
+}
+
+void
+OSXScreen::enter()
+{
+ showCursor();
+
+ if (m_isPrimary) {
+ setZeroSuppressionInterval();
+ }
+ else {
+ // reset buttons
+ m_buttonState.reset();
+
+ // patch by Yutaka Tsutano
+ // wakes the client screen
+ // http://symless.com/spit/issues/details/3287#c12
+ io_registry_entry_t entry = IORegistryEntryFromPath(
+ kIOMasterPortDefault,
+ "IOService:/IOResources/IODisplayWrangler");
+
+ if (entry != MACH_PORT_NULL) {
+ IORegistryEntrySetCFProperty(entry, CFSTR("IORequestIdle"), kCFBooleanFalse);
+ IOObjectRelease(entry);
+ }
+
+ avoidSupression();
+ }
+
+ // now on screen
+ m_isOnScreen = true;
+}
+
+bool
+OSXScreen::leave()
+{
+ hideCursor();
+
+ if (isDraggingStarted()) {
+ String& fileList = getDraggingFilename();
+
+ if (!m_isPrimary) {
+ if (fileList.empty() == false) {
+ ClientApp& app = ClientApp::instance();
+ Client* client = app.getClientPtr();
+
+ DragInformation di;
+ di.setFilename(fileList);
+ DragFileList dragFileList;
+ dragFileList.push_back(di);
+ String info;
+ UInt32 fileCount = DragInformation::setupDragInfo(
+ dragFileList, info);
+ client->sendDragInfo(fileCount, info, info.size());
+ LOG((CLOG_DEBUG "send dragging file to server"));
+
+ // TODO: what to do with multiple file or even
+ // a folder
+ client->sendFileToServer(fileList.c_str());
+ }
+ }
+ m_draggingStarted = false;
+ }
+
+ if (m_isPrimary) {
+ avoidHesitatingCursor();
+
+ }
+
+ // now off screen
+ m_isOnScreen = false;
+
+ return true;
+}
+
+bool
+OSXScreen::setClipboard(ClipboardID, const IClipboard* src)
+{
+ if (src != NULL) {
+ LOG((CLOG_DEBUG "setting clipboard"));
+ Clipboard::copy(&m_pasteboard, src);
+ }
+ return true;
+}
+
+void
+OSXScreen::checkClipboards()
+{
+ LOG((CLOG_DEBUG2 "checking clipboard"));
+ if (m_pasteboard.synchronize()) {
+ LOG((CLOG_DEBUG "clipboard changed"));
+ sendClipboardEvent(m_events->forClipboard().clipboardGrabbed(), kClipboardClipboard);
+ sendClipboardEvent(m_events->forClipboard().clipboardGrabbed(), kClipboardSelection);
+ }
+}
+
+void
+OSXScreen::openScreensaver(bool notify)
+{
+ m_screensaverNotify = notify;
+ if (!m_screensaverNotify) {
+ m_screensaver->disable();
+ }
+}
+
+void
+OSXScreen::closeScreensaver()
+{
+ if (!m_screensaverNotify) {
+ m_screensaver->enable();
+ }
+}
+
+void
+OSXScreen::screensaver(bool activate)
+{
+ if (activate) {
+ m_screensaver->activate();
+ }
+ else {
+ m_screensaver->deactivate();
+ }
+}
+
+void
+OSXScreen::resetOptions()
+{
+ // no options
+}
+
+void
+OSXScreen::setOptions(const OptionsList&)
+{
+ // no options
+}
+
+void
+OSXScreen::setSequenceNumber(UInt32 seqNum)
+{
+ m_sequenceNumber = seqNum;
+}
+
+bool
+OSXScreen::isPrimary() const
+{
+ return m_isPrimary;
+}
+
+void
+OSXScreen::sendEvent(Event::Type type, void* data) const
+{
+ m_events->addEvent(Event(type, getEventTarget(), data));
+}
+
+void
+OSXScreen::sendClipboardEvent(Event::Type type, ClipboardID id) const
+{
+ ClipboardInfo* info = (ClipboardInfo*)malloc(sizeof(ClipboardInfo));
+ info->m_id = id;
+ info->m_sequenceNumber = m_sequenceNumber;
+ sendEvent(type, info);
+}
+
+void
+OSXScreen::handleSystemEvent(const Event& event, void*)
+{
+ EventRef* carbonEvent = static_cast<EventRef*>(event.getData());
+ assert(carbonEvent != NULL);
+
+ UInt32 eventClass = GetEventClass(*carbonEvent);
+
+ switch (eventClass) {
+ case kEventClassMouse:
+ switch (GetEventKind(*carbonEvent)) {
+ case kBarrierEventMouseScroll:
+ {
+ OSStatus r;
+ long xScroll;
+ long yScroll;
+
+ // get scroll amount
+ r = GetEventParameter(*carbonEvent,
+ kBarrierMouseScrollAxisX,
+ typeSInt32,
+ NULL,
+ sizeof(xScroll),
+ NULL,
+ &xScroll);
+ if (r != noErr) {
+ xScroll = 0;
+ }
+ r = GetEventParameter(*carbonEvent,
+ kBarrierMouseScrollAxisY,
+ typeSInt32,
+ NULL,
+ sizeof(yScroll),
+ NULL,
+ &yScroll);
+ if (r != noErr) {
+ yScroll = 0;
+ }
+
+ if (xScroll != 0 || yScroll != 0) {
+ onMouseWheel(-mapScrollWheelToBarrier(xScroll),
+ mapScrollWheelToBarrier(yScroll));
+ }
+ }
+ }
+ break;
+
+ case kEventClassKeyboard:
+ switch (GetEventKind(*carbonEvent)) {
+ case kEventHotKeyPressed:
+ case kEventHotKeyReleased:
+ onHotKey(*carbonEvent);
+ break;
+ }
+
+ break;
+
+ case kEventClassWindow:
+ // 2nd param was formerly GetWindowEventTarget(m_userInputWindow) which is 32-bit only,
+ // however as m_userInputWindow is never initialized to anything we can take advantage of
+ // the fact that GetWindowEventTarget(NULL) == NULL
+ SendEventToEventTarget(*carbonEvent, NULL);
+ switch (GetEventKind(*carbonEvent)) {
+ case kEventWindowActivated:
+ LOG((CLOG_DEBUG1 "window activated"));
+ break;
+
+ case kEventWindowDeactivated:
+ LOG((CLOG_DEBUG1 "window deactivated"));
+ break;
+
+ case kEventWindowFocusAcquired:
+ LOG((CLOG_DEBUG1 "focus acquired"));
+ break;
+
+ case kEventWindowFocusRelinquish:
+ LOG((CLOG_DEBUG1 "focus released"));
+ break;
+ }
+ break;
+
+ default:
+ SendEventToEventTarget(*carbonEvent, GetEventDispatcherTarget());
+ break;
+ }
+}
+
+bool
+OSXScreen::onMouseMove(SInt32 mx, SInt32 my)
+{
+ LOG((CLOG_DEBUG2 "mouse move %+d,%+d", mx, my));
+
+ SInt32 x = mx - m_xCursor;
+ SInt32 y = my - m_yCursor;
+
+ if ((x == 0 && y == 0) || (mx == m_xCenter && mx == m_yCenter)) {
+ return true;
+ }
+
+ // save position to compute delta of next motion
+ m_xCursor = mx;
+ m_yCursor = my;
+
+ if (m_isOnScreen) {
+ // motion on primary screen
+ sendEvent(m_events->forIPrimaryScreen().motionOnPrimary(),
+ MotionInfo::alloc(m_xCursor, m_yCursor));
+ if (m_buttonState.test(0)) {
+ m_draggingStarted = true;
+ }
+ }
+ else {
+ // motion on secondary screen. warp mouse back to
+ // center.
+ warpCursor(m_xCenter, m_yCenter);
+
+ // examine the motion. if it's about the distance
+ // from the center of the screen to an edge then
+ // it's probably a bogus motion that we want to
+ // ignore (see warpCursorNoFlush() for a further
+ // description).
+ static SInt32 bogusZoneSize = 10;
+ if (-x + bogusZoneSize > m_xCenter - m_x ||
+ x + bogusZoneSize > m_x + m_w - m_xCenter ||
+ -y + bogusZoneSize > m_yCenter - m_y ||
+ y + bogusZoneSize > m_y + m_h - m_yCenter) {
+ LOG((CLOG_DEBUG "dropped bogus motion %+d,%+d", x, y));
+ }
+ else {
+ // send motion
+ sendEvent(m_events->forIPrimaryScreen().motionOnSecondary(), MotionInfo::alloc(x, y));
+ }
+ }
+
+ return true;
+}
+
+bool
+OSXScreen::onMouseButton(bool pressed, UInt16 macButton)
+{
+ // Buttons 2 and 3 are inverted on the mac
+ ButtonID button = mapMacButtonToBarrier(macButton);
+
+ if (pressed) {
+ LOG((CLOG_DEBUG1 "event: button press button=%d", button));
+ if (button != kButtonNone) {
+ KeyModifierMask mask = m_keyState->getActiveModifiers();
+ sendEvent(m_events->forIPrimaryScreen().buttonDown(), ButtonInfo::alloc(button, mask));
+ }
+ }
+ else {
+ LOG((CLOG_DEBUG1 "event: button release button=%d", button));
+ if (button != kButtonNone) {
+ KeyModifierMask mask = m_keyState->getActiveModifiers();
+ sendEvent(m_events->forIPrimaryScreen().buttonUp(), ButtonInfo::alloc(button, mask));
+ }
+ }
+
+ // handle drags with any button other than button 1 or 2
+ if (macButton > 2) {
+ if (pressed) {
+ // one more button
+ if (m_dragNumButtonsDown++ == 0) {
+ enableDragTimer(true);
+ }
+ }
+ else {
+ // one less button
+ if (--m_dragNumButtonsDown == 0) {
+ enableDragTimer(false);
+ }
+ }
+ }
+
+ if (macButton == kButtonLeft) {
+ EMouseButtonState state = pressed ? kMouseButtonDown : kMouseButtonUp;
+ m_buttonState.set(kButtonLeft - 1, state);
+ if (pressed) {
+ m_draggingFilename.clear();
+ LOG((CLOG_DEBUG2 "dragging file directory is cleared"));
+ }
+ else {
+ if (m_fakeDraggingStarted) {
+ m_getDropTargetThread = new Thread(new TMethodJob<OSXScreen>(
+ this, &OSXScreen::getDropTargetThread));
+ }
+
+ m_draggingStarted = false;
+ }
+ }
+
+ return true;
+}
+
+bool
+OSXScreen::onMouseWheel(SInt32 xDelta, SInt32 yDelta) const
+{
+ LOG((CLOG_DEBUG1 "event: button wheel delta=%+d,%+d", xDelta, yDelta));
+ sendEvent(m_events->forIPrimaryScreen().wheel(), WheelInfo::alloc(xDelta, yDelta));
+ return true;
+}
+
+void
+OSXScreen::handleClipboardCheck(const Event&, void*)
+{
+ checkClipboards();
+}
+
+void
+OSXScreen::displayReconfigurationCallback(CGDirectDisplayID displayID, CGDisplayChangeSummaryFlags flags, void* inUserData)
+{
+ OSXScreen* screen = (OSXScreen*)inUserData;
+
+ // Closing or opening the lid when an external monitor is
+ // connected causes an kCGDisplayBeginConfigurationFlag event
+ CGDisplayChangeSummaryFlags mask = kCGDisplayBeginConfigurationFlag | kCGDisplayMovedFlag |
+ kCGDisplaySetModeFlag | kCGDisplayAddFlag | kCGDisplayRemoveFlag |
+ kCGDisplayEnabledFlag | kCGDisplayDisabledFlag |
+ kCGDisplayMirrorFlag | kCGDisplayUnMirrorFlag |
+ kCGDisplayDesktopShapeChangedFlag;
+
+ LOG((CLOG_DEBUG1 "event: display was reconfigured: %x %x %x", flags, mask, flags & mask));
+
+ if (flags & mask) { /* Something actually did change */
+
+ LOG((CLOG_DEBUG1 "event: screen changed shape; refreshing dimensions"));
+ screen->updateScreenShape(displayID, flags);
+ }
+}
+
+bool
+OSXScreen::onKey(CGEventRef event)
+{
+ CGEventType eventKind = CGEventGetType(event);
+
+ // get the key and active modifiers
+ UInt32 virtualKey = CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode);
+ CGEventFlags macMask = CGEventGetFlags(event);
+ LOG((CLOG_DEBUG1 "event: Key event kind: %d, keycode=%d", eventKind, virtualKey));
+
+ // Special handling to track state of modifiers
+ if (eventKind == kCGEventFlagsChanged) {
+ // get old and new modifier state
+ KeyModifierMask oldMask = getActiveModifiers();
+ KeyModifierMask newMask = m_keyState->mapModifiersFromOSX(macMask);
+ m_keyState->handleModifierKeys(getEventTarget(), oldMask, newMask);
+
+ // if the current set of modifiers exactly matches a modifiers-only
+ // hot key then generate a hot key down event.
+ if (m_activeModifierHotKey == 0) {
+ if (m_modifierHotKeys.count(newMask) > 0) {
+ m_activeModifierHotKey = m_modifierHotKeys[newMask];
+ m_activeModifierHotKeyMask = newMask;
+ m_events->addEvent(Event(m_events->forIPrimaryScreen().hotKeyDown(),
+ getEventTarget(),
+ HotKeyInfo::alloc(m_activeModifierHotKey)));
+ }
+ }
+
+ // if a modifiers-only hot key is active and should no longer be
+ // then generate a hot key up event.
+ else if (m_activeModifierHotKey != 0) {
+ KeyModifierMask mask = (newMask & m_activeModifierHotKeyMask);
+ if (mask != m_activeModifierHotKeyMask) {
+ m_events->addEvent(Event(m_events->forIPrimaryScreen().hotKeyUp(),
+ getEventTarget(),
+ HotKeyInfo::alloc(m_activeModifierHotKey)));
+ m_activeModifierHotKey = 0;
+ m_activeModifierHotKeyMask = 0;
+ }
+ }
+
+ return true;
+ }
+
+ // check for hot key. when we're on a secondary screen we disable
+ // all hotkeys so we can capture the OS defined hot keys as regular
+ // keystrokes but that means we don't get our own hot keys either.
+ // so we check for a key/modifier match in our hot key map.
+ if (!m_isOnScreen) {
+ HotKeyToIDMap::const_iterator i =
+ m_hotKeyToIDMap.find(HotKeyItem(virtualKey,
+ m_keyState->mapModifiersToCarbon(macMask)
+ & 0xff00u));
+ if (i != m_hotKeyToIDMap.end()) {
+ UInt32 id = i->second;
+
+ // determine event type
+ Event::Type type;
+ //UInt32 eventKind = GetEventKind(event);
+ if (eventKind == kCGEventKeyDown) {
+ type = m_events->forIPrimaryScreen().hotKeyDown();
+ }
+ else if (eventKind == kCGEventKeyUp) {
+ type = m_events->forIPrimaryScreen().hotKeyUp();
+ }
+ else {
+ return false;
+ }
+
+ m_events->addEvent(Event(type, getEventTarget(),
+ HotKeyInfo::alloc(id)));
+
+ return true;
+ }
+ }
+
+ // decode event type
+ bool down = (eventKind == kCGEventKeyDown);
+ bool up = (eventKind == kCGEventKeyUp);
+ bool isRepeat = (CGEventGetIntegerValueField(event, kCGKeyboardEventAutorepeat) == 1);
+
+ // map event to keys
+ KeyModifierMask mask;
+ OSXKeyState::KeyIDs keys;
+ KeyButton button = m_keyState->mapKeyFromEvent(keys, &mask, event);
+ if (button == 0) {
+ return false;
+ }
+
+ // check for AltGr in mask. if set we send neither the AltGr nor
+ // the super modifiers to clients then remove AltGr before passing
+ // the modifiers to onKey.
+ KeyModifierMask sendMask = (mask & ~KeyModifierAltGr);
+ if ((mask & KeyModifierAltGr) != 0) {
+ sendMask &= ~KeyModifierSuper;
+ }
+ mask &= ~KeyModifierAltGr;
+
+ // update button state
+ if (down) {
+ m_keyState->onKey(button, true, mask);
+ }
+ else if (up) {
+ if (!m_keyState->isKeyDown(button)) {
+ // up event for a dead key. throw it away.
+ return false;
+ }
+ m_keyState->onKey(button, false, mask);
+ }
+
+ // send key events
+ for (OSXKeyState::KeyIDs::const_iterator i = keys.begin();
+ i != keys.end(); ++i) {
+ m_keyState->sendKeyEvent(getEventTarget(), down, isRepeat,
+ *i, sendMask, 1, button);
+ }
+
+ return true;
+}
+
+void
+OSXScreen::onMediaKey(CGEventRef event)
+{
+ KeyID keyID;
+ bool down;
+ bool isRepeat;
+
+ if (!getMediaKeyEventInfo (event, &keyID, &down, &isRepeat)) {
+ LOG ((CLOG_ERR "Failed to decode media key event"));
+ return;
+ }
+
+ LOG ((CLOG_DEBUG2 "Media key event: keyID=0x%02x, %s, repeat=%s",
+ keyID, (down ? "down": "up"),
+ (isRepeat ? "yes" : "no")));
+
+ KeyButton button = 0;
+ KeyModifierMask mask = m_keyState->getActiveModifiers();
+ m_keyState->sendKeyEvent(getEventTarget(), down, isRepeat, keyID, mask, 1, button);
+}
+
+bool
+OSXScreen::onHotKey(EventRef event) const
+{
+ // get the hotkey id
+ EventHotKeyID hkid;
+ GetEventParameter(event, kEventParamDirectObject, typeEventHotKeyID,
+ NULL, sizeof(EventHotKeyID), NULL, &hkid);
+ UInt32 id = hkid.id;
+
+ // determine event type
+ Event::Type type;
+ UInt32 eventKind = GetEventKind(event);
+ if (eventKind == kEventHotKeyPressed) {
+ type = m_events->forIPrimaryScreen().hotKeyDown();
+ }
+ else if (eventKind == kEventHotKeyReleased) {
+ type = m_events->forIPrimaryScreen().hotKeyUp();
+ }
+ else {
+ return false;
+ }
+
+ m_events->addEvent(Event(type, getEventTarget(),
+ HotKeyInfo::alloc(id)));
+
+ return true;
+}
+
+ButtonID
+OSXScreen::mapBarrierButtonToMac(UInt16 button) const
+{
+ switch (button) {
+ case 1:
+ return kButtonLeft;
+ case 2:
+ return kMacButtonMiddle;
+ case 3:
+ return kMacButtonRight;
+ }
+
+ return static_cast<ButtonID>(button);
+}
+
+ButtonID
+OSXScreen::mapMacButtonToBarrier(UInt16 macButton) const
+{
+ switch (macButton) {
+ case 1:
+ return kButtonLeft;
+
+ case 2:
+ return kButtonRight;
+
+ case 3:
+ return kButtonMiddle;
+ }
+
+ return static_cast<ButtonID>(macButton);
+}
+
+SInt32
+OSXScreen::mapScrollWheelToBarrier(SInt32 x) const
+{
+ // return accelerated scrolling but not exponentially scaled as it is
+ // on the mac.
+ double d = (1.0 + getScrollSpeed()) * x / getScrollSpeedFactor();
+ return static_cast<SInt32>(120.0 * d);
+}
+
+SInt32
+OSXScreen::mapScrollWheelFromBarrier(SInt32 x) const
+{
+ // use server's acceleration with a little boost since other platforms
+ // take one wheel step as a larger step than the mac does.
+ return static_cast<SInt32>(3.0 * x / 120.0);
+}
+
+double
+OSXScreen::getScrollSpeed() const
+{
+ double scaling = 0.0;
+
+ CFPropertyListRef pref = ::CFPreferencesCopyValue(
+ CFSTR("com.apple.scrollwheel.scaling") ,
+ kCFPreferencesAnyApplication,
+ kCFPreferencesCurrentUser,
+ kCFPreferencesAnyHost);
+ if (pref != NULL) {
+ CFTypeID id = CFGetTypeID(pref);
+ if (id == CFNumberGetTypeID()) {
+ CFNumberRef value = static_cast<CFNumberRef>(pref);
+ if (CFNumberGetValue(value, kCFNumberDoubleType, &scaling)) {
+ if (scaling < 0.0) {
+ scaling = 0.0;
+ }
+ }
+ }
+ CFRelease(pref);
+ }
+
+ return scaling;
+}
+
+double
+OSXScreen::getScrollSpeedFactor() const
+{
+ return pow(10.0, getScrollSpeed());
+}
+
+void
+OSXScreen::enableDragTimer(bool enable)
+{
+ if (enable && m_dragTimer == NULL) {
+ m_dragTimer = m_events->newTimer(0.01, NULL);
+ m_events->adoptHandler(Event::kTimer, m_dragTimer,
+ new TMethodEventJob<OSXScreen>(this,
+ &OSXScreen::handleDrag));
+ CGEventRef event = CGEventCreate(NULL);
+ CGPoint mouse = CGEventGetLocation(event);
+ m_dragLastPoint.h = (short)mouse.x;
+ m_dragLastPoint.v = (short)mouse.y;
+ CFRelease(event);
+ }
+ else if (!enable && m_dragTimer != NULL) {
+ m_events->removeHandler(Event::kTimer, m_dragTimer);
+ m_events->deleteTimer(m_dragTimer);
+ m_dragTimer = NULL;
+ }
+}
+
+void
+OSXScreen::handleDrag(const Event&, void*)
+{
+ CGEventRef event = CGEventCreate(NULL);
+ CGPoint p = CGEventGetLocation(event);
+ CFRelease(event);
+
+ if ((short)p.x != m_dragLastPoint.h || (short)p.y != m_dragLastPoint.v) {
+ m_dragLastPoint.h = (short)p.x;
+ m_dragLastPoint.v = (short)p.y;
+ onMouseMove((SInt32)p.x, (SInt32)p.y);
+ }
+}
+
+void
+OSXScreen::updateButtons()
+{
+ UInt32 buttons = GetCurrentButtonState();
+
+ m_buttonState.overwrite(buttons);
+}
+
+IKeyState*
+OSXScreen::getKeyState() const
+{
+ return m_keyState;
+}
+
+void
+OSXScreen::updateScreenShape(const CGDirectDisplayID, const CGDisplayChangeSummaryFlags flags)
+{
+ updateScreenShape();
+}
+
+void
+OSXScreen::updateScreenShape()
+{
+ // get info for each display
+ CGDisplayCount displayCount = 0;
+
+ if (CGGetActiveDisplayList(0, NULL, &displayCount) != CGDisplayNoErr) {
+ return;
+ }
+
+ if (displayCount == 0) {
+ return;
+ }
+
+ CGDirectDisplayID* displays = new CGDirectDisplayID[displayCount];
+ if (displays == NULL) {
+ return;
+ }
+
+ if (CGGetActiveDisplayList(displayCount,
+ displays, &displayCount) != CGDisplayNoErr) {
+ delete[] displays;
+ return;
+ }
+
+ // get smallest rect enclosing all display rects
+ CGRect totalBounds = CGRectZero;
+ for (CGDisplayCount i = 0; i < displayCount; ++i) {
+ CGRect bounds = CGDisplayBounds(displays[i]);
+ totalBounds = CGRectUnion(totalBounds, bounds);
+ }
+
+ // get shape of default screen
+ m_x = (SInt32)totalBounds.origin.x;
+ m_y = (SInt32)totalBounds.origin.y;
+ m_w = (SInt32)totalBounds.size.width;
+ m_h = (SInt32)totalBounds.size.height;
+
+ // get center of default screen
+ CGDirectDisplayID main = CGMainDisplayID();
+ const CGRect rect = CGDisplayBounds(main);
+ m_xCenter = (rect.origin.x + rect.size.width) / 2;
+ m_yCenter = (rect.origin.y + rect.size.height) / 2;
+
+ delete[] displays;
+ // We want to notify the peer screen whether we are primary screen or not
+ sendEvent(m_events->forIScreen().shapeChanged());
+
+ LOG((CLOG_DEBUG "screen shape: center=%d,%d size=%dx%d on %u %s",
+ m_x, m_y, m_w, m_h, displayCount,
+ (displayCount == 1) ? "display" : "displays"));
+}
+
+#pragma mark -
+
+//
+// FAST USER SWITCH NOTIFICATION SUPPORT
+//
+// OSXScreen::userSwitchCallback(void*)
+//
+// gets called if a fast user switch occurs
+//
+
+pascal OSStatus
+OSXScreen::userSwitchCallback(EventHandlerCallRef nextHandler,
+ EventRef theEvent,
+ void* inUserData)
+{
+ OSXScreen* screen = (OSXScreen*)inUserData;
+ UInt32 kind = GetEventKind(theEvent);
+ IEventQueue* events = screen->getEvents();
+
+ if (kind == kEventSystemUserSessionDeactivated) {
+ LOG((CLOG_DEBUG "user session deactivated"));
+ events->addEvent(Event(events->forIScreen().suspend(),
+ screen->getEventTarget()));
+ }
+ else if (kind == kEventSystemUserSessionActivated) {
+ LOG((CLOG_DEBUG "user session activated"));
+ events->addEvent(Event(events->forIScreen().resume(),
+ screen->getEventTarget()));
+ }
+ return (CallNextEventHandler(nextHandler, theEvent));
+}
+
+#pragma mark -
+
+//
+// SLEEP/WAKEUP NOTIFICATION SUPPORT
+//
+// OSXScreen::watchSystemPowerThread(void*)
+//
+// main of thread monitoring system power (sleep/wakup) using a CFRunLoop
+//
+
+void
+OSXScreen::watchSystemPowerThread(void*)
+{
+ io_object_t notifier;
+ IONotificationPortRef notificationPortRef;
+ CFRunLoopSourceRef runloopSourceRef = 0;
+
+ m_pmRunloop = CFRunLoopGetCurrent();
+ // install system power change callback
+ m_pmRootPort = IORegisterForSystemPower(this, &notificationPortRef,
+ powerChangeCallback, &notifier);
+ if (m_pmRootPort == 0) {
+ LOG((CLOG_WARN "IORegisterForSystemPower failed"));
+ }
+ else {
+ runloopSourceRef =
+ IONotificationPortGetRunLoopSource(notificationPortRef);
+ CFRunLoopAddSource(m_pmRunloop, runloopSourceRef,
+ kCFRunLoopCommonModes);
+ }
+
+ // thread is ready
+ {
+ Lock lock(m_pmMutex);
+ *m_pmThreadReady = true;
+ m_pmThreadReady->signal();
+ }
+
+ // if we were unable to initialize then exit. we must do this after
+ // setting m_pmThreadReady to true otherwise the parent thread will
+ // block waiting for it.
+ if (m_pmRootPort == 0) {
+ LOG((CLOG_WARN "failed to init watchSystemPowerThread"));
+ return;
+ }
+
+ LOG((CLOG_DEBUG "started watchSystemPowerThread"));
+
+ LOG((CLOG_DEBUG "waiting for event loop"));
+ m_events->waitForReady();
+
+#if defined(MAC_OS_X_VERSION_10_7)
+ {
+ Lock lockCarbon(m_carbonLoopMutex);
+ if (*m_carbonLoopReady == false) {
+
+ // we signalling carbon loop ready before starting
+ // unless we know how to do it within the loop
+ LOG((CLOG_DEBUG "signalling carbon loop ready"));
+
+ *m_carbonLoopReady = true;
+ m_carbonLoopReady->signal();
+ }
+ }
+#endif
+
+ // start the run loop
+ LOG((CLOG_DEBUG "starting carbon loop"));
+ CFRunLoopRun();
+ LOG((CLOG_DEBUG "carbon loop has stopped"));
+
+ // cleanup
+ if (notificationPortRef) {
+ CFRunLoopRemoveSource(m_pmRunloop,
+ runloopSourceRef, kCFRunLoopDefaultMode);
+ CFRunLoopSourceInvalidate(runloopSourceRef);
+ CFRelease(runloopSourceRef);
+ }
+
+ Lock lock(m_pmMutex);
+ IODeregisterForSystemPower(&notifier);
+ m_pmRootPort = 0;
+ LOG((CLOG_DEBUG "stopped watchSystemPowerThread"));
+}
+
+void
+OSXScreen::powerChangeCallback(void* refcon, io_service_t service,
+ natural_t messageType, void* messageArg)
+{
+ ((OSXScreen*)refcon)->handlePowerChangeRequest(messageType, messageArg);
+}
+
+void
+OSXScreen::handlePowerChangeRequest(natural_t messageType, void* messageArg)
+{
+ // we've received a power change notification
+ switch (messageType) {
+ case kIOMessageSystemWillSleep:
+ // OSXScreen has to handle this in the main thread so we have to
+ // queue a confirm sleep event here. we actually don't allow the
+ // system to sleep until the event is handled.
+ m_events->addEvent(Event(m_events->forOSXScreen().confirmSleep(),
+ getEventTarget(), messageArg,
+ Event::kDontFreeData));
+ return;
+
+ case kIOMessageSystemHasPoweredOn:
+ LOG((CLOG_DEBUG "system wakeup"));
+ m_events->addEvent(Event(m_events->forIScreen().resume(),
+ getEventTarget()));
+ break;
+
+ default:
+ break;
+ }
+
+ Lock lock(m_pmMutex);
+ if (m_pmRootPort != 0) {
+ IOAllowPowerChange(m_pmRootPort, (long)messageArg);
+ }
+}
+
+void
+OSXScreen::handleConfirmSleep(const Event& event, void*)
+{
+ long messageArg = (long)event.getData();
+ if (messageArg != 0) {
+ Lock lock(m_pmMutex);
+ if (m_pmRootPort != 0) {
+ // deliver suspend event immediately.
+ m_events->addEvent(Event(m_events->forIScreen().suspend(),
+ getEventTarget(), NULL,
+ Event::kDeliverImmediately));
+
+ LOG((CLOG_DEBUG "system will sleep"));
+ IOAllowPowerChange(m_pmRootPort, messageArg);
+ }
+ }
+}
+
+#pragma mark -
+
+//
+// GLOBAL HOTKEY OPERATING MODE SUPPORT (10.3)
+//
+// CoreGraphics private API (OSX 10.3)
+// Source: http://ichiro.nnip.org/osx/Cocoa/GlobalHotkey.html
+//
+// We load the functions dynamically because they're not available in
+// older SDKs. We don't use weak linking because we want users of
+// older SDKs to build an app that works on newer systems and older
+// SDKs will not provide the symbols.
+//
+// FIXME: This is hosed as of OS 10.5; patches to repair this are
+// a good thing.
+//
+#if 0
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef int CGSConnection;
+typedef enum {
+ CGSGlobalHotKeyEnable = 0,
+ CGSGlobalHotKeyDisable = 1,
+} CGSGlobalHotKeyOperatingMode;
+
+extern CGSConnection _CGSDefaultConnection(void) WEAK_IMPORT_ATTRIBUTE;
+extern CGError CGSGetGlobalHotKeyOperatingMode(CGSConnection connection, CGSGlobalHotKeyOperatingMode *mode) WEAK_IMPORT_ATTRIBUTE;
+extern CGError CGSSetGlobalHotKeyOperatingMode(CGSConnection connection, CGSGlobalHotKeyOperatingMode mode) WEAK_IMPORT_ATTRIBUTE;
+
+typedef CGSConnection (*_CGSDefaultConnection_t)(void);
+typedef CGError (*CGSGetGlobalHotKeyOperatingMode_t)(CGSConnection connection, CGSGlobalHotKeyOperatingMode *mode);
+typedef CGError (*CGSSetGlobalHotKeyOperatingMode_t)(CGSConnection connection, CGSGlobalHotKeyOperatingMode mode);
+
+static _CGSDefaultConnection_t s__CGSDefaultConnection;
+static CGSGetGlobalHotKeyOperatingMode_t s_CGSGetGlobalHotKeyOperatingMode;
+static CGSSetGlobalHotKeyOperatingMode_t s_CGSSetGlobalHotKeyOperatingMode;
+
+#ifdef __cplusplus
+}
+#endif
+
+#define LOOKUP(name_) \
+ s_ ## name_ = NULL; \
+ if (NSIsSymbolNameDefinedWithHint("_" #name_, "CoreGraphics")) { \
+ s_ ## name_ = (name_ ## _t)NSAddressOfSymbol( \
+ NSLookupAndBindSymbolWithHint( \
+ "_" #name_, "CoreGraphics")); \
+ }
+
+bool
+OSXScreen::isGlobalHotKeyOperatingModeAvailable()
+{
+ if (!s_testedForGHOM) {
+ s_testedForGHOM = true;
+ LOOKUP(_CGSDefaultConnection);
+ LOOKUP(CGSGetGlobalHotKeyOperatingMode);
+ LOOKUP(CGSSetGlobalHotKeyOperatingMode);
+ s_hasGHOM = (s__CGSDefaultConnection != NULL &&
+ s_CGSGetGlobalHotKeyOperatingMode != NULL &&
+ s_CGSSetGlobalHotKeyOperatingMode != NULL);
+ }
+ return s_hasGHOM;
+}
+
+void
+OSXScreen::setGlobalHotKeysEnabled(bool enabled)
+{
+ if (isGlobalHotKeyOperatingModeAvailable()) {
+ CGSConnection conn = s__CGSDefaultConnection();
+
+ CGSGlobalHotKeyOperatingMode mode;
+ s_CGSGetGlobalHotKeyOperatingMode(conn, &mode);
+
+ if (enabled && mode == CGSGlobalHotKeyDisable) {
+ s_CGSSetGlobalHotKeyOperatingMode(conn, CGSGlobalHotKeyEnable);
+ }
+ else if (!enabled && mode == CGSGlobalHotKeyEnable) {
+ s_CGSSetGlobalHotKeyOperatingMode(conn, CGSGlobalHotKeyDisable);
+ }
+ }
+}
+
+bool
+OSXScreen::getGlobalHotKeysEnabled()
+{
+ CGSGlobalHotKeyOperatingMode mode;
+ if (isGlobalHotKeyOperatingModeAvailable()) {
+ CGSConnection conn = s__CGSDefaultConnection();
+ s_CGSGetGlobalHotKeyOperatingMode(conn, &mode);
+ }
+ else {
+ mode = CGSGlobalHotKeyEnable;
+ }
+ return (mode == CGSGlobalHotKeyEnable);
+}
+
+#endif
+
+//
+// OSXScreen::HotKeyItem
+//
+
+OSXScreen::HotKeyItem::HotKeyItem(UInt32 keycode, UInt32 mask) :
+ m_ref(NULL),
+ m_keycode(keycode),
+ m_mask(mask)
+{
+ // do nothing
+}
+
+OSXScreen::HotKeyItem::HotKeyItem(EventHotKeyRef ref,
+ UInt32 keycode, UInt32 mask) :
+ m_ref(ref),
+ m_keycode(keycode),
+ m_mask(mask)
+{
+ // do nothing
+}
+
+EventHotKeyRef
+OSXScreen::HotKeyItem::getRef() const
+{
+ return m_ref;
+}
+
+bool
+OSXScreen::HotKeyItem::operator<(const HotKeyItem& x) const
+{
+ return (m_keycode < x.m_keycode ||
+ (m_keycode == x.m_keycode && m_mask < x.m_mask));
+}
+
+// Quartz event tap support for the secondary display. This makes sure that we
+// will show the cursor if a local event comes in while barrier has the cursor
+// off the screen.
+CGEventRef
+OSXScreen::handleCGInputEventSecondary(
+ CGEventTapProxy proxy,
+ CGEventType type,
+ CGEventRef event,
+ void* refcon)
+{
+ // this fix is really screwing with the correct show/hide behavior. it
+ // should be tested better before reintroducing.
+ return event;
+
+ OSXScreen* screen = (OSXScreen*)refcon;
+ if (screen->m_cursorHidden && type == kCGEventMouseMoved) {
+
+ CGPoint pos = CGEventGetLocation(event);
+ if (pos.x != screen->m_xCenter || pos.y != screen->m_yCenter) {
+
+ LOG((CLOG_DEBUG "show cursor on secondary, type=%d pos=%d,%d",
+ type, pos.x, pos.y));
+ screen->showCursor();
+ }
+ }
+ return event;
+}
+
+// Quartz event tap support
+CGEventRef
+OSXScreen::handleCGInputEvent(CGEventTapProxy proxy,
+ CGEventType type,
+ CGEventRef event,
+ void* refcon)
+{
+ OSXScreen* screen = (OSXScreen*)refcon;
+ CGPoint pos;
+
+ switch(type) {
+ case kCGEventLeftMouseDown:
+ case kCGEventRightMouseDown:
+ case kCGEventOtherMouseDown:
+ screen->onMouseButton(true, CGEventGetIntegerValueField(event, kCGMouseEventButtonNumber) + 1);
+ break;
+ case kCGEventLeftMouseUp:
+ case kCGEventRightMouseUp:
+ case kCGEventOtherMouseUp:
+ screen->onMouseButton(false, CGEventGetIntegerValueField(event, kCGMouseEventButtonNumber) + 1);
+ break;
+ case kCGEventLeftMouseDragged:
+ case kCGEventRightMouseDragged:
+ case kCGEventOtherMouseDragged:
+ case kCGEventMouseMoved:
+ pos = CGEventGetLocation(event);
+ screen->onMouseMove(pos.x, pos.y);
+
+ // The system ignores our cursor-centering calls if
+ // we don't return the event. This should be harmless,
+ // but might register as slight movement to other apps
+ // on the system. It hasn't been a problem before, though.
+ return event;
+ break;
+ case kCGEventScrollWheel:
+ screen->onMouseWheel(screen->mapScrollWheelToBarrier(
+ CGEventGetIntegerValueField(event, kCGScrollWheelEventDeltaAxis2)),
+ screen->mapScrollWheelToBarrier(
+ CGEventGetIntegerValueField(event, kCGScrollWheelEventDeltaAxis1)));
+ break;
+ case kCGEventKeyDown:
+ case kCGEventKeyUp:
+ case kCGEventFlagsChanged:
+ screen->onKey(event);
+ break;
+ case kCGEventTapDisabledByTimeout:
+ // Re-enable our event-tap
+ CGEventTapEnable(screen->m_eventTapPort, true);
+ LOG((CLOG_INFO "quartz event tap was disabled by timeout, re-enabling"));
+ break;
+ case kCGEventTapDisabledByUserInput:
+ LOG((CLOG_ERR "quartz event tap was disabled by user input"));
+ break;
+ case NX_NULLEVENT:
+ break;
+ default:
+ if (type == NX_SYSDEFINED) {
+ if (isMediaKeyEvent (event)) {
+ LOG((CLOG_DEBUG2 "detected media key event"));
+ screen->onMediaKey (event);
+ } else {
+ LOG((CLOG_DEBUG2 "ignoring unknown system defined event"));
+ return event;
+ }
+ break;
+ }
+
+ LOG((CLOG_DEBUG3 "unknown quartz event type: 0x%02x", type));
+ }
+
+ if (screen->m_isOnScreen) {
+ return event;
+ } else {
+ return NULL;
+ }
+}
+
+void
+OSXScreen::MouseButtonState::set(UInt32 button, EMouseButtonState state)
+{
+ bool newState = (state == kMouseButtonDown);
+ m_buttons.set(button, newState);
+}
+
+bool
+OSXScreen::MouseButtonState::any()
+{
+ return m_buttons.any();
+}
+
+void
+OSXScreen::MouseButtonState::reset()
+{
+ m_buttons.reset();
+}
+
+void
+OSXScreen::MouseButtonState::overwrite(UInt32 buttons)
+{
+ m_buttons = std::bitset<NumButtonIDs>(buttons);
+}
+
+bool
+OSXScreen::MouseButtonState::test(UInt32 button) const
+{
+ return m_buttons.test(button);
+}
+
+SInt8
+OSXScreen::MouseButtonState::getFirstButtonDown() const
+{
+ if (m_buttons.any()) {
+ for (unsigned short button = 0; button < m_buttons.size(); button++) {
+ if (m_buttons.test(button)) {
+ return button;
+ }
+ }
+ }
+ return -1;
+}
+
+char*
+OSXScreen::CFStringRefToUTF8String(CFStringRef aString)
+{
+ if (aString == NULL) {
+ return NULL;
+ }
+
+ CFIndex length = CFStringGetLength(aString);
+ CFIndex maxSize = CFStringGetMaximumSizeForEncoding(
+ length,
+ kCFStringEncodingUTF8);
+ char* buffer = (char*)malloc(maxSize);
+ if (CFStringGetCString(aString, buffer, maxSize, kCFStringEncodingUTF8)) {
+ return buffer;
+ }
+ return NULL;
+}
+
+void
+OSXScreen::fakeDraggingFiles(DragFileList fileList)
+{
+ m_fakeDraggingStarted = true;
+ String fileExt;
+ if (fileList.size() == 1) {
+ fileExt = DragInformation::getDragFileExtension(
+ fileList.at(0).getFilename());
+ }
+
+#if defined(MAC_OS_X_VERSION_10_7)
+ fakeDragging(fileExt.c_str(), m_xCursor, m_yCursor);
+#else
+ LOG((CLOG_WARN "drag drop not supported"));
+#endif
+}
+
+String&
+OSXScreen::getDraggingFilename()
+{
+ if (m_draggingStarted) {
+ CFStringRef dragInfo = getDraggedFileURL();
+ char* info = NULL;
+ info = CFStringRefToUTF8String(dragInfo);
+ if (info == NULL) {
+ m_draggingFilename.clear();
+ }
+ else {
+ LOG((CLOG_DEBUG "drag info: %s", info));
+ CFRelease(dragInfo);
+ String fileList(info);
+ m_draggingFilename = fileList;
+ }
+
+ // fake a escape key down and up then left mouse button up
+ fakeKeyDown(kKeyEscape, 8192, 1);
+ fakeKeyUp(1);
+ fakeMouseButton(kButtonLeft, false);
+ }
+ return m_draggingFilename;
+}
+
+void
+OSXScreen::waitForCarbonLoop() const
+{
+#if defined(MAC_OS_X_VERSION_10_7)
+ if (*m_carbonLoopReady) {
+ LOG((CLOG_DEBUG "carbon loop already ready"));
+ return;
+ }
+
+ Lock lock(m_carbonLoopMutex);
+
+ LOG((CLOG_DEBUG "waiting for carbon loop"));
+
+ double timeout = ARCH->time() + kCarbonLoopWaitTimeout;
+ while (!m_carbonLoopReady->wait()) {
+ if (ARCH->time() > timeout) {
+ LOG((CLOG_DEBUG "carbon loop not ready, waiting again"));
+ timeout = ARCH->time() + kCarbonLoopWaitTimeout;
+ }
+ }
+
+ LOG((CLOG_DEBUG "carbon loop ready"));
+#endif
+
+}
+
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+
+void
+setZeroSuppressionInterval()
+{
+ CGSetLocalEventsSuppressionInterval(0.0);
+}
+
+void
+avoidSupression()
+{
+ // avoid suppression of local hardware events
+ // stkamp@users.sourceforge.net
+ CGSetLocalEventsFilterDuringSupressionState(
+ kCGEventFilterMaskPermitAllEvents,
+ kCGEventSupressionStateSupressionInterval);
+ CGSetLocalEventsFilterDuringSupressionState(
+ (kCGEventFilterMaskPermitLocalKeyboardEvents |
+ kCGEventFilterMaskPermitSystemDefinedEvents),
+ kCGEventSupressionStateRemoteMouseDrag);
+}
+
+void
+logCursorVisibility()
+{
+ // CGCursorIsVisible is probably deprecated because its unreliable.
+ if (!CGCursorIsVisible()) {
+ LOG((CLOG_WARN "cursor may not be visible"));
+ }
+}
+
+void
+avoidHesitatingCursor()
+{
+ // This used to be necessary to get smooth mouse motion on other screens,
+ // but now is just to avoid a hesitating cursor when transitioning to
+ // the primary (this) screen.
+ CGSetLocalEventsSuppressionInterval(0.0001);
+}
+
+#pragma GCC diagnostic error "-Wdeprecated-declarations"