1/*
2 * barrier -- mouse and keyboard sharing utility
3 * Copyright (C) 2012-2016 Symless Ltd.
4 * Copyright (C) 2004 Chris Schoeneman
5 *
6 * This package is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * found in the file LICENSE that should have accompanied this file.
9 *
10 * This package is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 */
18
19#include "platform/OSXScreen.h"
20
21#include "base/EventQueue.h"
22#include "client/Client.h"
23#include "platform/OSXClipboard.h"
24#include "platform/OSXEventQueueBuffer.h"
25#include "platform/OSXKeyState.h"
26#include "platform/OSXScreenSaver.h"
27#include "platform/OSXDragSimulator.h"
28#include "platform/OSXMediaKeySupport.h"
29#include "platform/OSXPasteboardPeeker.h"
30#include "barrier/Clipboard.h"
31#include "barrier/KeyMap.h"
32#include "barrier/ClientApp.h"
33#include "mt/CondVar.h"
34#include "mt/Lock.h"
35#include "mt/Mutex.h"
36#include "mt/Thread.h"
37#include "arch/XArch.h"
38#include "base/Log.h"
39#include "base/IEventQueue.h"
40#include "base/TMethodEventJob.h"
41#include "base/TMethodJob.h"
42
43#include <math.h>
44#include <mach-o/dyld.h>
45#include <AvailabilityMacros.h>
46#include <IOKit/hidsystem/event_status_driver.h>
47#include <AppKit/NSEvent.h>
48
49// This isn't in any Apple SDK that I know of as of yet.
50enum {
51	kBarrierEventMouseScroll = 11,
52	kBarrierMouseScrollAxisX = 'saxx',
53	kBarrierMouseScrollAxisY = 'saxy'
54};
55
56enum {
57	kCarbonLoopWaitTimeout = 10
58};
59
60// TODO: upgrade deprecated function usage in these functions.
61void setZeroSuppressionInterval();
62void avoidSupression();
63void logCursorVisibility();
64void avoidHesitatingCursor();
65
66//
67// OSXScreen
68//
69
70bool					OSXScreen::s_testedForGHOM = false;
71bool					OSXScreen::s_hasGHOM	    = false;
72
73OSXScreen::OSXScreen(IEventQueue* events, bool isPrimary, bool autoShowHideCursor) :
74	PlatformScreen(events),
75	m_isPrimary(isPrimary),
76	m_isOnScreen(m_isPrimary),
77	m_cursorPosValid(false),
78	MouseButtonEventMap(NumButtonIDs),
79	m_cursorHidden(false),
80	m_dragNumButtonsDown(0),
81	m_dragTimer(NULL),
82	m_keyState(NULL),
83	m_sequenceNumber(0),
84	m_screensaver(NULL),
85	m_screensaverNotify(false),
86	m_ownClipboard(false),
87	m_clipboardTimer(NULL),
88	m_hiddenWindow(NULL),
89	m_userInputWindow(NULL),
90	m_switchEventHandlerRef(0),
91	m_pmMutex(new Mutex),
92	m_pmWatchThread(NULL),
93	m_pmThreadReady(new CondVar<bool>(m_pmMutex, false)),
94	m_pmRootPort(0),
95	m_activeModifierHotKey(0),
96	m_activeModifierHotKeyMask(0),
97	m_eventTapPort(nullptr),
98	m_eventTapRLSR(nullptr),
99	m_lastClickTime(0),
100	m_clickState(1),
101	m_lastSingleClickXCursor(0),
102	m_lastSingleClickYCursor(0),
103	m_autoShowHideCursor(autoShowHideCursor),
104	m_events(events),
105	m_getDropTargetThread(NULL),
106	m_impl(NULL)
107{
108	try {
109		m_displayID   = CGMainDisplayID();
110		updateScreenShape(m_displayID, 0);
111		m_screensaver = new OSXScreenSaver(m_events, getEventTarget());
112		m_keyState	  = new OSXKeyState(m_events);
113
114		// only needed when running as a server.
115		if (m_isPrimary) {
116
117#if defined(MAC_OS_X_VERSION_10_9)
118			// we can't pass options to show the dialog, this must be done by the gui.
119			if (!AXIsProcessTrusted()) {
120				throw XArch("assistive devices does not trust this process, allow it in system settings.");
121			}
122#else
123			// now deprecated in mavericks.
124			if (!AXAPIEnabled()) {
125				throw XArch("assistive devices is not enabled, enable it in system settings.");
126			}
127#endif
128		}
129
130		// install display manager notification handler
131		CGDisplayRegisterReconfigurationCallback(displayReconfigurationCallback, this);
132
133		// install fast user switching event handler
134		EventTypeSpec switchEventTypes[2];
135		switchEventTypes[0].eventClass = kEventClassSystem;
136		switchEventTypes[0].eventKind  = kEventSystemUserSessionDeactivated;
137		switchEventTypes[1].eventClass = kEventClassSystem;
138		switchEventTypes[1].eventKind  = kEventSystemUserSessionActivated;
139		EventHandlerUPP switchEventHandler =
140			NewEventHandlerUPP(userSwitchCallback);
141		InstallApplicationEventHandler(switchEventHandler, 2, switchEventTypes,
142									   this, &m_switchEventHandlerRef);
143		DisposeEventHandlerUPP(switchEventHandler);
144
145		constructMouseButtonEventMap();
146
147		// watch for requests to sleep
148		m_events->adoptHandler(m_events->forOSXScreen().confirmSleep(),
149								getEventTarget(),
150								new TMethodEventJob<OSXScreen>(this,
151									&OSXScreen::handleConfirmSleep));
152
153		// create thread for monitoring system power state.
154		*m_pmThreadReady = false;
155#if defined(MAC_OS_X_VERSION_10_7)
156		m_carbonLoopMutex = new Mutex();
157		m_carbonLoopReady = new CondVar<bool>(m_carbonLoopMutex, false);
158#endif
159		LOG((CLOG_DEBUG "starting watchSystemPowerThread"));
160		m_pmWatchThread = new Thread(new TMethodJob<OSXScreen>
161								(this, &OSXScreen::watchSystemPowerThread));
162	}
163	catch (...) {
164		m_events->removeHandler(m_events->forOSXScreen().confirmSleep(),
165								getEventTarget());
166		if (m_switchEventHandlerRef != 0) {
167			RemoveEventHandler(m_switchEventHandlerRef);
168		}
169
170		CGDisplayRemoveReconfigurationCallback(displayReconfigurationCallback, this);
171
172		delete m_keyState;
173		delete m_screensaver;
174		throw;
175	}
176
177	// install event handlers
178	m_events->adoptHandler(Event::kSystem, m_events->getSystemTarget(),
179							new TMethodEventJob<OSXScreen>(this,
180								&OSXScreen::handleSystemEvent));
181
182	// install the platform event queue
183	m_events->adoptBuffer(new OSXEventQueueBuffer(m_events));
184}
185
186OSXScreen::~OSXScreen()
187{
188	disable();
189	m_events->adoptBuffer(NULL);
190	m_events->removeHandler(Event::kSystem, m_events->getSystemTarget());
191
192	if (m_pmWatchThread) {
193		// make sure the thread has setup the runloop.
194		{
195			Lock lock(m_pmMutex);
196			while (!(bool)*m_pmThreadReady) {
197				m_pmThreadReady->wait();
198			}
199		}
200
201		// now exit the thread's runloop and wait for it to exit
202		LOG((CLOG_DEBUG "stopping watchSystemPowerThread"));
203		CFRunLoopStop(m_pmRunloop);
204		m_pmWatchThread->wait();
205		delete m_pmWatchThread;
206		m_pmWatchThread = NULL;
207	}
208	delete m_pmThreadReady;
209	delete m_pmMutex;
210
211	m_events->removeHandler(m_events->forOSXScreen().confirmSleep(),
212								getEventTarget());
213
214	RemoveEventHandler(m_switchEventHandlerRef);
215
216	CGDisplayRemoveReconfigurationCallback(displayReconfigurationCallback, this);
217
218	delete m_keyState;
219	delete m_screensaver;
220
221#if defined(MAC_OS_X_VERSION_10_7)
222	delete m_carbonLoopMutex;
223	delete m_carbonLoopReady;
224#endif
225}
226
227void*
228OSXScreen::getEventTarget() const
229{
230	return const_cast<OSXScreen*>(this);
231}
232
233bool
234OSXScreen::getClipboard(ClipboardID, IClipboard* dst) const
235{
236	Clipboard::copy(dst, &m_pasteboard);
237	return true;
238}
239
240void
241OSXScreen::getShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h) const
242{
243	x = m_x;
244	y = m_y;
245	w = m_w;
246	h = m_h;
247}
248
249void
250OSXScreen::getCursorPos(SInt32& x, SInt32& y) const
251{
252	CGEventRef event = CGEventCreate(NULL);
253	CGPoint mouse = CGEventGetLocation(event);
254	x                = mouse.x;
255	y                = mouse.y;
256	m_cursorPosValid = true;
257	m_xCursor        = x;
258	m_yCursor        = y;
259	CFRelease(event);
260}
261
262void
263OSXScreen::reconfigure(UInt32)
264{
265	// do nothing
266}
267
268void
269OSXScreen::warpCursor(SInt32 x, SInt32 y)
270{
271	// move cursor without generating events
272	CGPoint pos;
273	pos.x = x;
274	pos.y = y;
275	CGWarpMouseCursorPosition(pos);
276
277	// save new cursor position
278	m_xCursor        = x;
279	m_yCursor        = y;
280	m_cursorPosValid = true;
281}
282
283void
284OSXScreen::fakeInputBegin()
285{
286	// FIXME -- not implemented
287}
288
289void
290OSXScreen::fakeInputEnd()
291{
292	// FIXME -- not implemented
293}
294
295SInt32
296OSXScreen::getJumpZoneSize() const
297{
298	return 1;
299}
300
301bool
302OSXScreen::isAnyMouseButtonDown(UInt32& buttonID) const
303{
304	if (m_buttonState.test(0)) {
305		buttonID = kButtonLeft;
306		return true;
307	}
308
309	return (GetCurrentButtonState() != 0);
310}
311
312void
313OSXScreen::getCursorCenter(SInt32& x, SInt32& y) const
314{
315	x = m_xCenter;
316	y = m_yCenter;
317}
318
319UInt32
320OSXScreen::registerHotKey(KeyID key, KeyModifierMask mask)
321{
322	// get mac virtual key and modifier mask matching barrier key and mask
323	UInt32 macKey, macMask;
324	if (!m_keyState->mapBarrierHotKeyToMac(key, mask, macKey, macMask)) {
325		LOG((CLOG_DEBUG "could not map hotkey id=%04x mask=%04x", key, mask));
326		return 0;
327	}
328
329	// choose hotkey id
330	UInt32 id;
331	if (!m_oldHotKeyIDs.empty()) {
332		id = m_oldHotKeyIDs.back();
333		m_oldHotKeyIDs.pop_back();
334	}
335	else {
336		id = m_hotKeys.size() + 1;
337	}
338
339	// if this hot key has modifiers only then we'll handle it specially
340	EventHotKeyRef ref = NULL;
341	bool okay;
342	if (key == kKeyNone) {
343		if (m_modifierHotKeys.count(mask) > 0) {
344			// already registered
345			okay = false;
346		}
347		else {
348			m_modifierHotKeys[mask] = id;
349			okay = true;
350		}
351	}
352	else {
353		EventHotKeyID hkid = { 'SNRG', (UInt32)id };
354		OSStatus status = RegisterEventHotKey(macKey, macMask, hkid,
355								GetApplicationEventTarget(), 0,
356								&ref);
357		okay = (status == noErr);
358		m_hotKeyToIDMap[HotKeyItem(macKey, macMask)] = id;
359	}
360
361	if (!okay) {
362		m_oldHotKeyIDs.push_back(id);
363		m_hotKeyToIDMap.erase(HotKeyItem(macKey, macMask));
364		LOG((CLOG_WARN "failed to register hotkey %s (id=%04x mask=%04x)", barrier::KeyMap::formatKey(key, mask).c_str(), key, mask));
365		return 0;
366	}
367
368	m_hotKeys.insert(std::make_pair(id, HotKeyItem(ref, macKey, macMask)));
369
370	LOG((CLOG_DEBUG "registered hotkey %s (id=%04x mask=%04x) as id=%d", barrier::KeyMap::formatKey(key, mask).c_str(), key, mask, id));
371	return id;
372}
373
374void
375OSXScreen::unregisterHotKey(UInt32 id)
376{
377	// look up hotkey
378	HotKeyMap::iterator i = m_hotKeys.find(id);
379	if (i == m_hotKeys.end()) {
380		return;
381	}
382
383	// unregister with OS
384	bool okay;
385	if (i->second.getRef() != NULL) {
386		okay = (UnregisterEventHotKey(i->second.getRef()) == noErr);
387	}
388	else {
389		okay = false;
390		// XXX -- this is inefficient
391		for (ModifierHotKeyMap::iterator j = m_modifierHotKeys.begin();
392								j != m_modifierHotKeys.end(); ++j) {
393			if (j->second == id) {
394				m_modifierHotKeys.erase(j);
395				okay = true;
396				break;
397			}
398		}
399	}
400	if (!okay) {
401		LOG((CLOG_WARN "failed to unregister hotkey id=%d", id));
402	}
403	else {
404		LOG((CLOG_DEBUG "unregistered hotkey id=%d", id));
405	}
406
407	// discard hot key from map and record old id for reuse
408	m_hotKeyToIDMap.erase(i->second);
409	m_hotKeys.erase(i);
410	m_oldHotKeyIDs.push_back(id);
411	if (m_activeModifierHotKey == id) {
412		m_activeModifierHotKey     = 0;
413		m_activeModifierHotKeyMask = 0;
414	}
415}
416
417void
418OSXScreen::constructMouseButtonEventMap()
419{
420	const CGEventType source[NumButtonIDs][3] = {
421		{kCGEventLeftMouseUp, kCGEventLeftMouseDragged, kCGEventLeftMouseDown},
422		{kCGEventRightMouseUp, kCGEventRightMouseDragged, kCGEventRightMouseDown},
423		{kCGEventOtherMouseUp, kCGEventOtherMouseDragged, kCGEventOtherMouseDown},
424		{kCGEventOtherMouseUp, kCGEventOtherMouseDragged, kCGEventOtherMouseDown},
425		{kCGEventOtherMouseUp, kCGEventOtherMouseDragged, kCGEventOtherMouseDown},
426		{kCGEventOtherMouseUp, kCGEventOtherMouseDragged, kCGEventOtherMouseDown}
427	};
428
429	for (UInt16 button = 0; button < NumButtonIDs; button++) {
430		MouseButtonEventMapType new_map;
431		for (UInt16 state = (UInt32) kMouseButtonUp; state < kMouseButtonStateMax; state++) {
432			CGEventType curEvent = source[button][state];
433			new_map[state] = curEvent;
434		}
435		MouseButtonEventMap[button] = new_map;
436	}
437}
438
439void
440OSXScreen::postMouseEvent(CGPoint& pos) const
441{
442	// check if cursor position is valid on the client display configuration
443	// stkamp@users.sourceforge.net
444	CGDisplayCount displayCount = 0;
445	CGGetDisplaysWithPoint(pos, 0, NULL, &displayCount);
446	if (displayCount == 0) {
447		// cursor position invalid - clamp to bounds of last valid display.
448		// find the last valid display using the last cursor position.
449		displayCount = 0;
450		CGDirectDisplayID displayID;
451		CGGetDisplaysWithPoint(CGPointMake(m_xCursor, m_yCursor), 1,
452								&displayID, &displayCount);
453		if (displayCount != 0) {
454			CGRect displayRect = CGDisplayBounds(displayID);
455			if (pos.x < displayRect.origin.x) {
456				pos.x = displayRect.origin.x;
457			}
458			else if (pos.x > displayRect.origin.x +
459								displayRect.size.width - 1) {
460				pos.x = displayRect.origin.x + displayRect.size.width - 1;
461			}
462			if (pos.y < displayRect.origin.y) {
463				pos.y = displayRect.origin.y;
464			}
465			else if (pos.y > displayRect.origin.y +
466								displayRect.size.height - 1) {
467				pos.y = displayRect.origin.y + displayRect.size.height - 1;
468			}
469		}
470	}
471
472	CGEventType type = kCGEventMouseMoved;
473
474	SInt8 button = m_buttonState.getFirstButtonDown();
475	if (button != -1) {
476		MouseButtonEventMapType thisButtonType = MouseButtonEventMap[button];
477		type = thisButtonType[kMouseButtonDragged];
478	}
479
480	CGEventRef event = CGEventCreateMouseEvent(NULL, type, pos, static_cast<CGMouseButton>(button));
481
482    // Dragging events also need the click state
483    CGEventSetIntegerValueField(event, kCGMouseEventClickState, m_clickState);
484
485    // Fix for sticky keys
486    CGEventFlags modifiers = m_keyState->getModifierStateAsOSXFlags();
487    CGEventSetFlags(event, modifiers);
488
489    // Set movement deltas to fix issues with certain 3D programs
490    SInt64 deltaX = pos.x;
491    deltaX -= m_xCursor;
492
493    SInt64 deltaY = pos.y;
494    deltaY -= m_yCursor;
495
496    CGEventSetIntegerValueField(event, kCGMouseEventDeltaX, deltaX);
497    CGEventSetIntegerValueField(event, kCGMouseEventDeltaY, deltaY);
498
499    double deltaFX = deltaX;
500    double deltaFY = deltaY;
501
502    CGEventSetDoubleValueField(event, kCGMouseEventDeltaX, deltaFX);
503    CGEventSetDoubleValueField(event, kCGMouseEventDeltaY, deltaFY);
504
505	CGEventPost(kCGHIDEventTap, event);
506
507	CFRelease(event);
508}
509
510void
511OSXScreen::fakeMouseButton(ButtonID id, bool press)
512{
513	// Buttons are indexed from one, but the button down array is indexed from zero
514	UInt32 index = mapBarrierButtonToMac(id) - kButtonLeft;
515	if (index >= NumButtonIDs) {
516		return;
517	}
518
519	CGPoint pos;
520	if (!m_cursorPosValid) {
521		SInt32 x, y;
522		getCursorPos(x, y);
523	}
524	pos.x = m_xCursor;
525	pos.y = m_yCursor;
526
527	// variable used to detect mouse coordinate differences between
528	// old & new mouse clicks. Used in double click detection.
529	SInt32 xDiff = m_xCursor - m_lastSingleClickXCursor;
530	SInt32 yDiff = m_yCursor - m_lastSingleClickYCursor;
531	double diff = sqrt(xDiff * xDiff + yDiff * yDiff);
532	// max sqrt(x^2 + y^2) difference allowed to double click
533	// since we don't have double click distance in NX APIs
534	// we define our own defaults.
535	const double maxDiff = sqrt(2) + 0.0001;
536
537    double clickTime = [NSEvent doubleClickInterval];
538
539    // As long as the click is within the time window and distance window
540    // increase clickState (double click, triple click, etc)
541    // This will allow for higher than triple click but the quartz documenation
542    // does not specify that this should be limited to triple click
543    if (press) {
544        if ((ARCH->time() - m_lastClickTime) <= clickTime && diff <= maxDiff){
545            m_clickState++;
546        }
547        else {
548            m_clickState = 1;
549        }
550
551        m_lastClickTime = ARCH->time();
552    }
553
554    if (m_clickState == 1){
555        m_lastSingleClickXCursor = m_xCursor;
556        m_lastSingleClickYCursor = m_yCursor;
557    }
558
559    EMouseButtonState state = press ? kMouseButtonDown : kMouseButtonUp;
560
561    LOG((CLOG_DEBUG1 "faking mouse button id: %d press: %s", index, press ? "pressed" : "released"));
562
563    MouseButtonEventMapType thisButtonMap = MouseButtonEventMap[index];
564    CGEventType type = thisButtonMap[state];
565
566    CGEventRef event = CGEventCreateMouseEvent(NULL, type, pos, static_cast<CGMouseButton>(index));
567
568    CGEventSetIntegerValueField(event, kCGMouseEventClickState, m_clickState);
569
570    // Fix for sticky keys
571    CGEventFlags modifiers = m_keyState->getModifierStateAsOSXFlags();
572    CGEventSetFlags(event, modifiers);
573
574    m_buttonState.set(index, state);
575    CGEventPost(kCGHIDEventTap, event);
576
577    CFRelease(event);
578
579	if (!press && (id == kButtonLeft)) {
580		if (m_fakeDraggingStarted) {
581			m_getDropTargetThread = new Thread(new TMethodJob<OSXScreen>(
582				this, &OSXScreen::getDropTargetThread));
583		}
584
585		m_draggingStarted = false;
586	}
587}
588
589void
590OSXScreen::getDropTargetThread(void*)
591{
592#if defined(MAC_OS_X_VERSION_10_7)
593	char* cstr = NULL;
594
595	// wait for 5 secs for the drop destinaiton string to be filled.
596	UInt32 timeout = ARCH->time() + 5;
597
598	while (ARCH->time() < timeout) {
599		CFStringRef cfstr = getCocoaDropTarget();
600		cstr = CFStringRefToUTF8String(cfstr);
601		CFRelease(cfstr);
602
603		if (cstr != NULL) {
604			break;
605		}
606		ARCH->sleep(.1f);
607	}
608
609	if (cstr != NULL) {
610		LOG((CLOG_DEBUG "drop target: %s", cstr));
611		m_dropTarget = cstr;
612	}
613	else {
614		LOG((CLOG_ERR "failed to get drop target"));
615		m_dropTarget.clear();
616	}
617#else
618	LOG((CLOG_WARN "drag drop not supported"));
619#endif
620	m_fakeDraggingStarted = false;
621}
622
623void
624OSXScreen::fakeMouseMove(SInt32 x, SInt32 y)
625{
626	if (m_fakeDraggingStarted) {
627		m_buttonState.set(0, kMouseButtonDown);
628	}
629
630	// index 0 means left mouse button
631	if (m_buttonState.test(0)) {
632		m_draggingStarted = true;
633	}
634
635	// synthesize event
636	CGPoint pos;
637	pos.x = x;
638	pos.y = y;
639	postMouseEvent(pos);
640
641	// save new cursor position
642	m_xCursor        = static_cast<SInt32>(pos.x);
643	m_yCursor        = static_cast<SInt32>(pos.y);
644	m_cursorPosValid = true;
645}
646
647void
648OSXScreen::fakeMouseRelativeMove(SInt32 dx, SInt32 dy) const
649{
650	// OS X does not appear to have a fake relative mouse move function.
651	// simulate it by getting the current mouse position and adding to
652	// that.  this can yield the wrong answer but there's not much else
653	// we can do.
654
655	// get current position
656	CGEventRef event = CGEventCreate(NULL);
657	CGPoint oldPos = CGEventGetLocation(event);
658	CFRelease(event);
659
660	// synthesize event
661	CGPoint pos;
662	m_xCursor = static_cast<SInt32>(oldPos.x);
663	m_yCursor = static_cast<SInt32>(oldPos.y);
664	pos.x     = oldPos.x + dx;
665	pos.y     = oldPos.y + dy;
666	postMouseEvent(pos);
667
668	// we now assume we don't know the current cursor position
669	m_cursorPosValid = false;
670}
671
672void
673OSXScreen::fakeMouseWheel(SInt32 xDelta, SInt32 yDelta) const
674{
675	if (xDelta != 0 || yDelta != 0) {
676		// create a scroll event, post it and release it.  not sure if kCGScrollEventUnitLine
677		// is the right choice here over kCGScrollEventUnitPixel
678		CGEventRef scrollEvent = CGEventCreateScrollWheelEvent(
679			NULL, kCGScrollEventUnitLine, 2,
680			mapScrollWheelFromBarrier(yDelta),
681			-mapScrollWheelFromBarrier(xDelta));
682
683        // Fix for sticky keys
684        CGEventFlags modifiers = m_keyState->getModifierStateAsOSXFlags();
685        CGEventSetFlags(scrollEvent, modifiers);
686
687		CGEventPost(kCGHIDEventTap, scrollEvent);
688		CFRelease(scrollEvent);
689	}
690}
691
692void
693OSXScreen::showCursor()
694{
695	LOG((CLOG_DEBUG "showing cursor"));
696
697	CFStringRef propertyString = CFStringCreateWithCString(
698		NULL, "SetsCursorInBackground", kCFStringEncodingMacRoman);
699
700	CGSSetConnectionProperty(
701		_CGSDefaultConnection(), _CGSDefaultConnection(),
702		propertyString, kCFBooleanTrue);
703
704	CFRelease(propertyString);
705
706	CGError error = CGDisplayShowCursor(m_displayID);
707	if (error != kCGErrorSuccess) {
708		LOG((CLOG_ERR "failed to show cursor, error=%d", error));
709	}
710
711	// appears to fix "mouse randomly not showing" bug
712	CGAssociateMouseAndMouseCursorPosition(true);
713
714	logCursorVisibility();
715
716	m_cursorHidden = false;
717}
718
719void
720OSXScreen::hideCursor()
721{
722	LOG((CLOG_DEBUG "hiding cursor"));
723
724	CFStringRef propertyString = CFStringCreateWithCString(
725		NULL, "SetsCursorInBackground", kCFStringEncodingMacRoman);
726
727	CGSSetConnectionProperty(
728		_CGSDefaultConnection(), _CGSDefaultConnection(),
729		propertyString, kCFBooleanTrue);
730
731	CFRelease(propertyString);
732
733	CGError error = CGDisplayHideCursor(m_displayID);
734	if (error != kCGErrorSuccess) {
735		LOG((CLOG_ERR "failed to hide cursor, error=%d", error));
736	}
737
738	// appears to fix "mouse randomly not hiding" bug
739	CGAssociateMouseAndMouseCursorPosition(true);
740
741	logCursorVisibility();
742
743	m_cursorHidden = true;
744}
745
746void
747OSXScreen::enable()
748{
749	// watch the clipboard
750	m_clipboardTimer = m_events->newTimer(1.0, NULL);
751	m_events->adoptHandler(Event::kTimer, m_clipboardTimer,
752							new TMethodEventJob<OSXScreen>(this,
753								&OSXScreen::handleClipboardCheck));
754
755	if (m_isPrimary) {
756		// FIXME -- start watching jump zones
757
758		// kCGEventTapOptionDefault = 0x00000000 (Missing in 10.4, so specified literally)
759		m_eventTapPort = CGEventTapCreate(kCGHIDEventTap, kCGHeadInsertEventTap, kCGEventTapOptionDefault,
760										kCGEventMaskForAllEvents,
761										handleCGInputEvent,
762										this);
763	}
764	else {
765		// FIXME -- prevent system from entering power save mode
766
767		if (m_autoShowHideCursor) {
768			hideCursor();
769		}
770
771		// warp the mouse to the cursor center
772		fakeMouseMove(m_xCenter, m_yCenter);
773
774                // there may be a better way to do this, but we register an event handler even if we're
775                // not on the primary display (acting as a client). This way, if a local event comes in
776                // (either keyboard or mouse), we can make sure to show the cursor if we've hidden it.
777		m_eventTapPort = CGEventTapCreate(kCGHIDEventTap, kCGHeadInsertEventTap, kCGEventTapOptionDefault,
778										kCGEventMaskForAllEvents,
779										handleCGInputEventSecondary,
780										this);
781	}
782
783	if (!m_eventTapPort) {
784		LOG((CLOG_ERR "failed to create quartz event tap"));
785	}
786
787	m_eventTapRLSR = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, m_eventTapPort, 0);
788	if (!m_eventTapRLSR) {
789		LOG((CLOG_ERR "failed to create a CFRunLoopSourceRef for the quartz event tap"));
790	}
791
792	CFRunLoopAddSource(CFRunLoopGetCurrent(), m_eventTapRLSR, kCFRunLoopDefaultMode);
793}
794
795void
796OSXScreen::disable()
797{
798	if (m_autoShowHideCursor) {
799		showCursor();
800	}
801
802	// FIXME -- stop watching jump zones, stop capturing input
803
804	if (m_eventTapRLSR) {
805		CFRunLoopRemoveSource(CFRunLoopGetCurrent(), m_eventTapRLSR, kCFRunLoopDefaultMode);
806		CFRelease(m_eventTapRLSR);
807		m_eventTapRLSR = nullptr;
808	}
809
810	if (m_eventTapPort) {
811		CGEventTapEnable(m_eventTapPort, false);
812		CFRelease(m_eventTapPort);
813		m_eventTapPort = nullptr;
814	}
815	// FIXME -- allow system to enter power saving mode
816
817	// disable drag handling
818	m_dragNumButtonsDown = 0;
819	enableDragTimer(false);
820
821	// uninstall clipboard timer
822	if (m_clipboardTimer != NULL) {
823		m_events->removeHandler(Event::kTimer, m_clipboardTimer);
824		m_events->deleteTimer(m_clipboardTimer);
825		m_clipboardTimer = NULL;
826	}
827
828	m_isOnScreen = m_isPrimary;
829}
830
831void
832OSXScreen::enter()
833{
834	showCursor();
835
836	if (m_isPrimary) {
837		setZeroSuppressionInterval();
838	}
839	else {
840		// reset buttons
841		m_buttonState.reset();
842
843		// patch by Yutaka Tsutano
844		// wakes the client screen
845		// http://symless.com/spit/issues/details/3287#c12
846		io_registry_entry_t entry = IORegistryEntryFromPath(
847			kIOMasterPortDefault,
848			"IOService:/IOResources/IODisplayWrangler");
849
850		if (entry != MACH_PORT_NULL) {
851			IORegistryEntrySetCFProperty(entry, CFSTR("IORequestIdle"), kCFBooleanFalse);
852			IOObjectRelease(entry);
853		}
854
855		avoidSupression();
856	}
857
858	// now on screen
859	m_isOnScreen = true;
860}
861
862bool
863OSXScreen::leave()
864{
865    hideCursor();
866
867	if (isDraggingStarted()) {
868		String& fileList = getDraggingFilename();
869
870		if (!m_isPrimary) {
871			if (fileList.empty() == false) {
872				ClientApp& app = ClientApp::instance();
873				Client* client = app.getClientPtr();
874
875				DragInformation di;
876				di.setFilename(fileList);
877				DragFileList dragFileList;
878				dragFileList.push_back(di);
879				String info;
880				UInt32 fileCount = DragInformation::setupDragInfo(
881					dragFileList, info);
882				client->sendDragInfo(fileCount, info, info.size());
883				LOG((CLOG_DEBUG "send dragging file to server"));
884
885				// TODO: what to do with multiple file or even
886				// a folder
887				client->sendFileToServer(fileList.c_str());
888			}
889		}
890		m_draggingStarted = false;
891	}
892
893	if (m_isPrimary) {
894		avoidHesitatingCursor();
895
896	}
897
898	// now off screen
899	m_isOnScreen = false;
900
901	return true;
902}
903
904bool
905OSXScreen::setClipboard(ClipboardID, const IClipboard* src)
906{
907	if (src != NULL) {
908		LOG((CLOG_DEBUG "setting clipboard"));
909		Clipboard::copy(&m_pasteboard, src);
910	}
911	return true;
912}
913
914void
915OSXScreen::checkClipboards()
916{
917	LOG((CLOG_DEBUG2 "checking clipboard"));
918	if (m_pasteboard.synchronize()) {
919		LOG((CLOG_DEBUG "clipboard changed"));
920		sendClipboardEvent(m_events->forClipboard().clipboardGrabbed(), kClipboardClipboard);
921		sendClipboardEvent(m_events->forClipboard().clipboardGrabbed(), kClipboardSelection);
922	}
923}
924
925void
926OSXScreen::openScreensaver(bool notify)
927{
928	m_screensaverNotify = notify;
929	if (!m_screensaverNotify) {
930		m_screensaver->disable();
931	}
932}
933
934void
935OSXScreen::closeScreensaver()
936{
937	if (!m_screensaverNotify) {
938		m_screensaver->enable();
939	}
940}
941
942void
943OSXScreen::screensaver(bool activate)
944{
945	if (activate) {
946		m_screensaver->activate();
947	}
948	else {
949		m_screensaver->deactivate();
950	}
951}
952
953void
954OSXScreen::resetOptions()
955{
956	// no options
957}
958
959void
960OSXScreen::setOptions(const OptionsList&)
961{
962	// no options
963}
964
965void
966OSXScreen::setSequenceNumber(UInt32 seqNum)
967{
968	m_sequenceNumber = seqNum;
969}
970
971bool
972OSXScreen::isPrimary() const
973{
974	return m_isPrimary;
975}
976
977void
978OSXScreen::sendEvent(Event::Type type, void* data) const
979{
980	m_events->addEvent(Event(type, getEventTarget(), data));
981}
982
983void
984OSXScreen::sendClipboardEvent(Event::Type type, ClipboardID id) const
985{
986	ClipboardInfo* info   = (ClipboardInfo*)malloc(sizeof(ClipboardInfo));
987	info->m_id             = id;
988	info->m_sequenceNumber = m_sequenceNumber;
989	sendEvent(type, info);
990}
991
992void
993OSXScreen::handleSystemEvent(const Event& event, void*)
994{
995	EventRef* carbonEvent = static_cast<EventRef*>(event.getData());
996	assert(carbonEvent != NULL);
997
998	UInt32 eventClass = GetEventClass(*carbonEvent);
999
1000	switch (eventClass) {
1001	case kEventClassMouse:
1002		switch (GetEventKind(*carbonEvent)) {
1003		case kBarrierEventMouseScroll:
1004		{
1005			OSStatus r;
1006			long xScroll;
1007			long yScroll;
1008
1009			// get scroll amount
1010			r = GetEventParameter(*carbonEvent,
1011					kBarrierMouseScrollAxisX,
1012					typeSInt32,
1013					NULL,
1014					sizeof(xScroll),
1015					NULL,
1016					&xScroll);
1017			if (r != noErr) {
1018				xScroll = 0;
1019			}
1020			r = GetEventParameter(*carbonEvent,
1021					kBarrierMouseScrollAxisY,
1022					typeSInt32,
1023					NULL,
1024					sizeof(yScroll),
1025					NULL,
1026					&yScroll);
1027			if (r != noErr) {
1028				yScroll = 0;
1029			}
1030
1031			if (xScroll != 0 || yScroll != 0) {
1032				onMouseWheel(-mapScrollWheelToBarrier(xScroll),
1033								mapScrollWheelToBarrier(yScroll));
1034			}
1035		}
1036		}
1037		break;
1038
1039	case kEventClassKeyboard:
1040			switch (GetEventKind(*carbonEvent)) {
1041				case kEventHotKeyPressed:
1042				case kEventHotKeyReleased:
1043					onHotKey(*carbonEvent);
1044					break;
1045			}
1046
1047			break;
1048
1049	case kEventClassWindow:
1050		// 2nd param was formerly GetWindowEventTarget(m_userInputWindow) which is 32-bit only,
1051		// however as m_userInputWindow is never initialized to anything we can take advantage of
1052		// the fact that GetWindowEventTarget(NULL) == NULL
1053		SendEventToEventTarget(*carbonEvent, NULL);
1054		switch (GetEventKind(*carbonEvent)) {
1055		case kEventWindowActivated:
1056			LOG((CLOG_DEBUG1 "window activated"));
1057			break;
1058
1059		case kEventWindowDeactivated:
1060			LOG((CLOG_DEBUG1 "window deactivated"));
1061			break;
1062
1063		case kEventWindowFocusAcquired:
1064			LOG((CLOG_DEBUG1 "focus acquired"));
1065			break;
1066
1067		case kEventWindowFocusRelinquish:
1068			LOG((CLOG_DEBUG1 "focus released"));
1069			break;
1070		}
1071		break;
1072
1073	default:
1074		SendEventToEventTarget(*carbonEvent, GetEventDispatcherTarget());
1075		break;
1076	}
1077}
1078
1079bool
1080OSXScreen::onMouseMove(CGFloat mx, CGFloat my)
1081{
1082	LOG((CLOG_DEBUG2 "mouse move %+f,%+f", mx, my));
1083
1084	CGFloat x = mx - m_xCursor;
1085	CGFloat y = my - m_yCursor;
1086
1087	if ((x == 0 && y == 0) || (mx == m_xCenter && mx == m_yCenter)) {
1088		return true;
1089	}
1090
1091	// save position to compute delta of next motion
1092	m_xCursor = (SInt32)mx;
1093	m_yCursor = (SInt32)my;
1094
1095	if (m_isOnScreen) {
1096		// motion on primary screen
1097		sendEvent(m_events->forIPrimaryScreen().motionOnPrimary(),
1098							MotionInfo::alloc(m_xCursor, m_yCursor));
1099		if (m_buttonState.test(0)) {
1100			m_draggingStarted = true;
1101		}
1102	}
1103	else {
1104		// motion on secondary screen.  warp mouse back to
1105		// center.
1106		warpCursor(m_xCenter, m_yCenter);
1107
1108		// examine the motion.  if it's about the distance
1109		// from the center of the screen to an edge then
1110		// it's probably a bogus motion that we want to
1111		// ignore (see warpCursorNoFlush() for a further
1112		// description).
1113		static SInt32 bogusZoneSize = 10;
1114		if (-x + bogusZoneSize > m_xCenter - m_x ||
1115			 x + bogusZoneSize > m_x + m_w - m_xCenter ||
1116			-y + bogusZoneSize > m_yCenter - m_y ||
1117			 y + bogusZoneSize > m_y + m_h - m_yCenter) {
1118			LOG((CLOG_DEBUG "dropped bogus motion %+d,%+d", x, y));
1119		}
1120		else {
1121			// send motion
1122			// Accumulate together the move into the running total
1123			static CGFloat m_xFractionalMove = 0;
1124			static CGFloat m_yFractionalMove = 0;
1125
1126			m_xFractionalMove += x;
1127			m_yFractionalMove += y;
1128
1129			// Return the integer part
1130			SInt32 intX = (SInt32)m_xFractionalMove;
1131			SInt32 intY = (SInt32)m_yFractionalMove;
1132
1133			// And keep only the fractional part
1134			m_xFractionalMove -= intX;
1135			m_yFractionalMove -= intY;
1136			sendEvent(m_events->forIPrimaryScreen().motionOnSecondary(), MotionInfo::alloc(intX, intY));
1137		}
1138	}
1139
1140	return true;
1141}
1142
1143bool
1144OSXScreen::onMouseButton(bool pressed, UInt16 macButton)
1145{
1146	// Buttons 2 and 3 are inverted on the mac
1147	ButtonID button = mapMacButtonToBarrier(macButton);
1148
1149	if (pressed) {
1150		LOG((CLOG_DEBUG1 "event: button press button=%d", button));
1151		if (button != kButtonNone) {
1152			KeyModifierMask mask = m_keyState->getActiveModifiers();
1153			sendEvent(m_events->forIPrimaryScreen().buttonDown(), ButtonInfo::alloc(button, mask));
1154		}
1155	}
1156	else {
1157		LOG((CLOG_DEBUG1 "event: button release button=%d", button));
1158		if (button != kButtonNone) {
1159			KeyModifierMask mask = m_keyState->getActiveModifiers();
1160			sendEvent(m_events->forIPrimaryScreen().buttonUp(), ButtonInfo::alloc(button, mask));
1161		}
1162	}
1163
1164	// handle drags with any button other than button 1 or 2
1165	if (macButton > 2) {
1166		if (pressed) {
1167			// one more button
1168			if (m_dragNumButtonsDown++ == 0) {
1169				enableDragTimer(true);
1170			}
1171		}
1172		else {
1173			// one less button
1174			if (--m_dragNumButtonsDown == 0) {
1175				enableDragTimer(false);
1176			}
1177		}
1178	}
1179
1180	if (macButton == kButtonLeft) {
1181		EMouseButtonState state = pressed ? kMouseButtonDown : kMouseButtonUp;
1182		m_buttonState.set(kButtonLeft - 1, state);
1183		if (pressed) {
1184			m_draggingFilename.clear();
1185			LOG((CLOG_DEBUG2 "dragging file directory is cleared"));
1186		}
1187		else {
1188			if (m_fakeDraggingStarted) {
1189				m_getDropTargetThread = new Thread(new TMethodJob<OSXScreen>(
1190																			   this, &OSXScreen::getDropTargetThread));
1191			}
1192
1193			m_draggingStarted = false;
1194		}
1195	}
1196
1197	return true;
1198}
1199
1200bool
1201OSXScreen::onMouseWheel(SInt32 xDelta, SInt32 yDelta) const
1202{
1203	LOG((CLOG_DEBUG1 "event: button wheel delta=%+d,%+d", xDelta, yDelta));
1204	sendEvent(m_events->forIPrimaryScreen().wheel(), WheelInfo::alloc(xDelta, yDelta));
1205	return true;
1206}
1207
1208void
1209OSXScreen::handleClipboardCheck(const Event&, void*)
1210{
1211	checkClipboards();
1212}
1213
1214void
1215OSXScreen::displayReconfigurationCallback(CGDirectDisplayID displayID, CGDisplayChangeSummaryFlags flags, void* inUserData)
1216{
1217	OSXScreen* screen = (OSXScreen*)inUserData;
1218
1219	// Closing or opening the lid when an external monitor is
1220    // connected causes an kCGDisplayBeginConfigurationFlag event
1221	CGDisplayChangeSummaryFlags mask = kCGDisplayBeginConfigurationFlag | kCGDisplayMovedFlag |
1222		kCGDisplaySetModeFlag | kCGDisplayAddFlag | kCGDisplayRemoveFlag |
1223		kCGDisplayEnabledFlag | kCGDisplayDisabledFlag |
1224		kCGDisplayMirrorFlag | kCGDisplayUnMirrorFlag |
1225		kCGDisplayDesktopShapeChangedFlag;
1226
1227	LOG((CLOG_DEBUG1 "event: display was reconfigured: %x %x %x", flags, mask, flags & mask));
1228
1229	if (flags & mask) { /* Something actually did change */
1230
1231		LOG((CLOG_DEBUG1 "event: screen changed shape; refreshing dimensions"));
1232		screen->updateScreenShape(displayID, flags);
1233	}
1234}
1235
1236bool
1237OSXScreen::onKey(CGEventRef event)
1238{
1239	CGEventType eventKind = CGEventGetType(event);
1240
1241	// get the key and active modifiers
1242	UInt32 virtualKey = CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode);
1243	CGEventFlags macMask = CGEventGetFlags(event);
1244	LOG((CLOG_DEBUG1 "event: Key event kind: %d, keycode=%d", eventKind, virtualKey));
1245
1246	// Special handling to track state of modifiers
1247	if (eventKind == kCGEventFlagsChanged) {
1248		// get old and new modifier state
1249		KeyModifierMask oldMask = getActiveModifiers();
1250		KeyModifierMask newMask = m_keyState->mapModifiersFromOSX(macMask);
1251		m_keyState->handleModifierKeys(getEventTarget(), oldMask, newMask);
1252
1253		// if the current set of modifiers exactly matches a modifiers-only
1254		// hot key then generate a hot key down event.
1255		if (m_activeModifierHotKey == 0) {
1256			if (m_modifierHotKeys.count(newMask) > 0) {
1257				m_activeModifierHotKey     = m_modifierHotKeys[newMask];
1258				m_activeModifierHotKeyMask = newMask;
1259				m_events->addEvent(Event(m_events->forIPrimaryScreen().hotKeyDown(),
1260								getEventTarget(),
1261								HotKeyInfo::alloc(m_activeModifierHotKey)));
1262			}
1263		}
1264
1265		// if a modifiers-only hot key is active and should no longer be
1266		// then generate a hot key up event.
1267		else if (m_activeModifierHotKey != 0) {
1268			KeyModifierMask mask = (newMask & m_activeModifierHotKeyMask);
1269			if (mask != m_activeModifierHotKeyMask) {
1270				m_events->addEvent(Event(m_events->forIPrimaryScreen().hotKeyUp(),
1271								getEventTarget(),
1272								HotKeyInfo::alloc(m_activeModifierHotKey)));
1273				m_activeModifierHotKey     = 0;
1274				m_activeModifierHotKeyMask = 0;
1275			}
1276		}
1277
1278		return true;
1279	}
1280
1281	HotKeyToIDMap::const_iterator i = m_hotKeyToIDMap.find(HotKeyItem(virtualKey, m_keyState->mapModifiersToCarbon(macMask) & 0xff00u));
1282	if (i != m_hotKeyToIDMap.end()) {
1283		UInt32 id = i->second;
1284		// determine event type
1285		Event::Type type;
1286		//UInt32 eventKind = GetEventKind(event);
1287		if (eventKind == kCGEventKeyDown) {
1288			type = m_events->forIPrimaryScreen().hotKeyDown();
1289		}
1290		else if (eventKind == kCGEventKeyUp) {
1291			type = m_events->forIPrimaryScreen().hotKeyUp();
1292		}
1293		else {
1294			return false;
1295		}
1296		m_events->addEvent(Event(type, getEventTarget(), HotKeyInfo::alloc(id)));
1297		return true;
1298	}
1299
1300	// decode event type
1301	bool down	  = (eventKind == kCGEventKeyDown);
1302	bool up		  = (eventKind == kCGEventKeyUp);
1303	bool isRepeat = (CGEventGetIntegerValueField(event, kCGKeyboardEventAutorepeat) == 1);
1304
1305	// map event to keys
1306	KeyModifierMask mask;
1307	OSXKeyState::KeyIDs keys;
1308	KeyButton button = m_keyState->mapKeyFromEvent(keys, &mask, event);
1309	if (button == 0) {
1310		return false;
1311	}
1312
1313	// check for AltGr in mask.  if set we send neither the AltGr nor
1314	// the super modifiers to clients then remove AltGr before passing
1315	// the modifiers to onKey.
1316	KeyModifierMask sendMask = (mask & ~KeyModifierAltGr);
1317	if ((mask & KeyModifierAltGr) != 0) {
1318		sendMask &= ~KeyModifierSuper;
1319	}
1320	mask &= ~KeyModifierAltGr;
1321
1322	// update button state
1323	if (down) {
1324		m_keyState->onKey(button, true, mask);
1325	}
1326	else if (up) {
1327		if (!m_keyState->isKeyDown(button)) {
1328			// up event for a dead key.  throw it away.
1329			return false;
1330		}
1331		m_keyState->onKey(button, false, mask);
1332	}
1333
1334	// send key events
1335	for (OSXKeyState::KeyIDs::const_iterator i = keys.begin();
1336							i != keys.end(); ++i) {
1337		m_keyState->sendKeyEvent(getEventTarget(), down, isRepeat,
1338							*i, sendMask, 1, button);
1339	}
1340
1341	return true;
1342}
1343
1344void
1345OSXScreen::onMediaKey(CGEventRef event)
1346{
1347	KeyID keyID;
1348	bool down;
1349	bool isRepeat;
1350
1351	if (!getMediaKeyEventInfo (event, &keyID, &down, &isRepeat)) {
1352		LOG ((CLOG_ERR "Failed to decode media key event"));
1353		return;
1354	}
1355
1356	LOG ((CLOG_DEBUG2 "Media key event: keyID=0x%02x, %s, repeat=%s",
1357						keyID, (down ? "down": "up"),
1358						(isRepeat ? "yes" : "no")));
1359
1360	KeyButton button = 0;
1361	KeyModifierMask mask = m_keyState->getActiveModifiers();
1362	m_keyState->sendKeyEvent(getEventTarget(), down, isRepeat, keyID, mask, 1, button);
1363}
1364
1365bool
1366OSXScreen::onHotKey(EventRef event) const
1367{
1368	// get the hotkey id
1369	EventHotKeyID hkid;
1370	GetEventParameter(event, kEventParamDirectObject, typeEventHotKeyID,
1371							NULL, sizeof(EventHotKeyID), NULL, &hkid);
1372	UInt32 id = hkid.id;
1373
1374	// determine event type
1375	Event::Type type;
1376	UInt32 eventKind = GetEventKind(event);
1377	if (eventKind == kEventHotKeyPressed) {
1378		type = m_events->forIPrimaryScreen().hotKeyDown();
1379	}
1380	else if (eventKind == kEventHotKeyReleased) {
1381		type = m_events->forIPrimaryScreen().hotKeyUp();
1382	}
1383	else {
1384		return false;
1385	}
1386
1387	m_events->addEvent(Event(type, getEventTarget(),
1388								HotKeyInfo::alloc(id)));
1389
1390	return true;
1391}
1392
1393ButtonID
1394OSXScreen::mapBarrierButtonToMac(UInt16 button) const
1395{
1396    switch (button) {
1397    case 1:
1398        return kButtonLeft;
1399    case 2:
1400        return kMacButtonMiddle;
1401    case 3:
1402        return kMacButtonRight;
1403    }
1404
1405    return static_cast<ButtonID>(button);
1406}
1407
1408ButtonID
1409OSXScreen::mapMacButtonToBarrier(UInt16 macButton) const
1410{
1411	switch (macButton) {
1412	case 1:
1413		return kButtonLeft;
1414
1415	case 2:
1416		return kButtonRight;
1417
1418	case 3:
1419		return kButtonMiddle;
1420	}
1421
1422	return static_cast<ButtonID>(macButton);
1423}
1424
1425SInt32
1426OSXScreen::mapScrollWheelToBarrier(float x) const
1427{
1428	// return accelerated scrolling but not exponentially scaled as it is
1429	// on the mac.
1430	double d = (1.0 + getScrollSpeed()) * x / getScrollSpeedFactor();
1431	return static_cast<SInt32>(120.0 * d);
1432}
1433
1434SInt32
1435OSXScreen::mapScrollWheelFromBarrier(float x) const
1436{
1437	// use server's acceleration with a little boost since other platforms
1438	// take one wheel step as a larger step than the mac does.
1439	return static_cast<SInt32>(3.0 * x / 120.0);
1440}
1441
1442double
1443OSXScreen::getScrollSpeed() const
1444{
1445	double scaling = 0.0;
1446
1447	CFPropertyListRef pref = ::CFPreferencesCopyValue(
1448							CFSTR("com.apple.scrollwheel.scaling") ,
1449							kCFPreferencesAnyApplication,
1450							kCFPreferencesCurrentUser,
1451							kCFPreferencesAnyHost);
1452	if (pref != NULL) {
1453		CFTypeID id = CFGetTypeID(pref);
1454		if (id == CFNumberGetTypeID()) {
1455			CFNumberRef value = static_cast<CFNumberRef>(pref);
1456			if (CFNumberGetValue(value, kCFNumberDoubleType, &scaling)) {
1457				if (scaling < 0.0) {
1458					scaling = 0.0;
1459				}
1460			}
1461		}
1462		CFRelease(pref);
1463	}
1464
1465	return scaling;
1466}
1467
1468double
1469OSXScreen::getScrollSpeedFactor() const
1470{
1471	return pow(10.0, getScrollSpeed());
1472}
1473
1474void
1475OSXScreen::enableDragTimer(bool enable)
1476{
1477	if (enable && m_dragTimer == NULL) {
1478		m_dragTimer = m_events->newTimer(0.01, NULL);
1479		m_events->adoptHandler(Event::kTimer, m_dragTimer,
1480							new TMethodEventJob<OSXScreen>(this,
1481								&OSXScreen::handleDrag));
1482		CGEventRef event = CGEventCreate(NULL);
1483		CGPoint mouse = CGEventGetLocation(event);
1484		m_dragLastPoint.h = (short)mouse.x;
1485		m_dragLastPoint.v = (short)mouse.y;
1486		CFRelease(event);
1487	}
1488	else if (!enable && m_dragTimer != NULL) {
1489		m_events->removeHandler(Event::kTimer, m_dragTimer);
1490		m_events->deleteTimer(m_dragTimer);
1491		m_dragTimer = NULL;
1492	}
1493}
1494
1495void
1496OSXScreen::handleDrag(const Event&, void*)
1497{
1498	CGEventRef event = CGEventCreate(NULL);
1499	CGPoint p = CGEventGetLocation(event);
1500	CFRelease(event);
1501
1502	if ((short)p.x != m_dragLastPoint.h || (short)p.y != m_dragLastPoint.v) {
1503		m_dragLastPoint.h = (short)p.x;
1504		m_dragLastPoint.v = (short)p.y;
1505		onMouseMove((SInt32)p.x, (SInt32)p.y);
1506	}
1507}
1508
1509void
1510OSXScreen::updateButtons()
1511{
1512	UInt32 buttons = GetCurrentButtonState();
1513
1514	m_buttonState.overwrite(buttons);
1515}
1516
1517IKeyState*
1518OSXScreen::getKeyState() const
1519{
1520	return m_keyState;
1521}
1522
1523void
1524OSXScreen::updateScreenShape(const CGDirectDisplayID, const CGDisplayChangeSummaryFlags flags)
1525{
1526	updateScreenShape();
1527}
1528
1529void
1530OSXScreen::updateScreenShape()
1531{
1532	// get info for each display
1533	CGDisplayCount displayCount = 0;
1534
1535	if (CGGetActiveDisplayList(0, NULL, &displayCount) != CGDisplayNoErr) {
1536		return;
1537	}
1538
1539	if (displayCount == 0) {
1540		return;
1541	}
1542
1543	CGDirectDisplayID* displays = new CGDirectDisplayID[displayCount];
1544	if (displays == NULL) {
1545		return;
1546	}
1547
1548	if (CGGetActiveDisplayList(displayCount,
1549							displays, &displayCount) != CGDisplayNoErr) {
1550		delete[] displays;
1551		return;
1552	}
1553
1554	// get smallest rect enclosing all display rects
1555	CGRect totalBounds = CGRectZero;
1556	for (CGDisplayCount i = 0; i < displayCount; ++i) {
1557		CGRect bounds = CGDisplayBounds(displays[i]);
1558		totalBounds   = CGRectUnion(totalBounds, bounds);
1559	}
1560
1561	// get shape of default screen
1562	m_x = (SInt32)totalBounds.origin.x;
1563	m_y = (SInt32)totalBounds.origin.y;
1564	m_w = (SInt32)totalBounds.size.width;
1565	m_h = (SInt32)totalBounds.size.height;
1566
1567	// get center of default screen
1568  CGDirectDisplayID main = CGMainDisplayID();
1569  const CGRect rect = CGDisplayBounds(main);
1570  m_xCenter = (rect.origin.x + rect.size.width) / 2;
1571  m_yCenter = (rect.origin.y + rect.size.height) / 2;
1572
1573	delete[] displays;
1574	// We want to notify the peer screen whether we are primary screen or not
1575	sendEvent(m_events->forIScreen().shapeChanged());
1576
1577	LOG((CLOG_DEBUG "screen shape: center=%d,%d size=%dx%d on %u %s",
1578         m_x, m_y, m_w, m_h, displayCount,
1579         (displayCount == 1) ? "display" : "displays"));
1580}
1581
1582#pragma mark -
1583
1584//
1585// FAST USER SWITCH NOTIFICATION SUPPORT
1586//
1587// OSXScreen::userSwitchCallback(void*)
1588//
1589// gets called if a fast user switch occurs
1590//
1591
1592pascal OSStatus
1593OSXScreen::userSwitchCallback(EventHandlerCallRef nextHandler,
1594								EventRef theEvent,
1595								void* inUserData)
1596{
1597	OSXScreen* screen = (OSXScreen*)inUserData;
1598	UInt32 kind        = GetEventKind(theEvent);
1599	IEventQueue* events = screen->getEvents();
1600
1601	if (kind == kEventSystemUserSessionDeactivated) {
1602		LOG((CLOG_DEBUG "user session deactivated"));
1603		events->addEvent(Event(events->forIScreen().suspend(),
1604									screen->getEventTarget()));
1605	}
1606	else if (kind == kEventSystemUserSessionActivated) {
1607		LOG((CLOG_DEBUG "user session activated"));
1608		events->addEvent(Event(events->forIScreen().resume(),
1609									screen->getEventTarget()));
1610	}
1611	return (CallNextEventHandler(nextHandler, theEvent));
1612}
1613
1614#pragma mark -
1615
1616//
1617// SLEEP/WAKEUP NOTIFICATION SUPPORT
1618//
1619// OSXScreen::watchSystemPowerThread(void*)
1620//
1621// main of thread monitoring system power (sleep/wakup) using a CFRunLoop
1622//
1623
1624void
1625OSXScreen::watchSystemPowerThread(void*)
1626{
1627	io_object_t				notifier;
1628	IONotificationPortRef	notificationPortRef;
1629	CFRunLoopSourceRef		runloopSourceRef = 0;
1630
1631	m_pmRunloop = CFRunLoopGetCurrent();
1632	// install system power change callback
1633	m_pmRootPort = IORegisterForSystemPower(this, &notificationPortRef,
1634											powerChangeCallback, &notifier);
1635	if (m_pmRootPort == 0) {
1636		LOG((CLOG_WARN "IORegisterForSystemPower failed"));
1637	}
1638	else {
1639		runloopSourceRef =
1640			IONotificationPortGetRunLoopSource(notificationPortRef);
1641		CFRunLoopAddSource(m_pmRunloop, runloopSourceRef,
1642								kCFRunLoopCommonModes);
1643	}
1644
1645	// thread is ready
1646	{
1647		Lock lock(m_pmMutex);
1648		*m_pmThreadReady = true;
1649		m_pmThreadReady->signal();
1650	}
1651
1652	// if we were unable to initialize then exit.  we must do this after
1653	// setting m_pmThreadReady to true otherwise the parent thread will
1654	// block waiting for it.
1655	if (m_pmRootPort == 0) {
1656		LOG((CLOG_WARN "failed to init watchSystemPowerThread"));
1657		return;
1658	}
1659
1660	LOG((CLOG_DEBUG "started watchSystemPowerThread"));
1661
1662	LOG((CLOG_DEBUG "waiting for event loop"));
1663	m_events->waitForReady();
1664
1665#if defined(MAC_OS_X_VERSION_10_7)
1666	{
1667		Lock lockCarbon(m_carbonLoopMutex);
1668		if (*m_carbonLoopReady == false) {
1669
1670			// we signalling carbon loop ready before starting
1671			// unless we know how to do it within the loop
1672			LOG((CLOG_DEBUG "signalling carbon loop ready"));
1673
1674			*m_carbonLoopReady = true;
1675			m_carbonLoopReady->signal();
1676		}
1677	}
1678#endif
1679
1680	// start the run loop
1681	LOG((CLOG_DEBUG "starting carbon loop"));
1682	CFRunLoopRun();
1683	LOG((CLOG_DEBUG "carbon loop has stopped"));
1684
1685	// cleanup
1686	if (notificationPortRef) {
1687		CFRunLoopRemoveSource(m_pmRunloop,
1688								runloopSourceRef, kCFRunLoopDefaultMode);
1689		CFRunLoopSourceInvalidate(runloopSourceRef);
1690		CFRelease(runloopSourceRef);
1691	}
1692
1693	Lock lock(m_pmMutex);
1694	IODeregisterForSystemPower(&notifier);
1695	m_pmRootPort = 0;
1696	LOG((CLOG_DEBUG "stopped watchSystemPowerThread"));
1697}
1698
1699void
1700OSXScreen::powerChangeCallback(void* refcon, io_service_t service,
1701						  natural_t messageType, void* messageArg)
1702{
1703	((OSXScreen*)refcon)->handlePowerChangeRequest(messageType, messageArg);
1704}
1705
1706void
1707OSXScreen::handlePowerChangeRequest(natural_t messageType, void* messageArg)
1708{
1709	// we've received a power change notification
1710	switch (messageType) {
1711	case kIOMessageSystemWillSleep:
1712		// OSXScreen has to handle this in the main thread so we have to
1713		// queue a confirm sleep event here.  we actually don't allow the
1714		// system to sleep until the event is handled.
1715		m_events->addEvent(Event(m_events->forOSXScreen().confirmSleep(),
1716								getEventTarget(), messageArg,
1717								Event::kDontFreeData));
1718		return;
1719
1720	case kIOMessageSystemHasPoweredOn:
1721		LOG((CLOG_DEBUG "system wakeup"));
1722		m_events->addEvent(Event(m_events->forIScreen().resume(),
1723								getEventTarget()));
1724		break;
1725
1726	default:
1727		break;
1728	}
1729
1730	Lock lock(m_pmMutex);
1731	if (m_pmRootPort != 0) {
1732		IOAllowPowerChange(m_pmRootPort, (long)messageArg);
1733	}
1734}
1735
1736void
1737OSXScreen::handleConfirmSleep(const Event& event, void*)
1738{
1739	long messageArg = (long)event.getData();
1740	if (messageArg != 0) {
1741		Lock lock(m_pmMutex);
1742		if (m_pmRootPort != 0) {
1743			// deliver suspend event immediately.
1744			m_events->addEvent(Event(m_events->forIScreen().suspend(),
1745									getEventTarget(), NULL,
1746									Event::kDeliverImmediately));
1747
1748			LOG((CLOG_DEBUG "system will sleep"));
1749			IOAllowPowerChange(m_pmRootPort, messageArg);
1750		}
1751	}
1752}
1753
1754#pragma mark -
1755
1756//
1757// GLOBAL HOTKEY OPERATING MODE SUPPORT (10.3)
1758//
1759// CoreGraphics private API (OSX 10.3)
1760// Source: http://ichiro.nnip.org/osx/Cocoa/GlobalHotkey.html
1761//
1762// We load the functions dynamically because they're not available in
1763// older SDKs.  We don't use weak linking because we want users of
1764// older SDKs to build an app that works on newer systems and older
1765// SDKs will not provide the symbols.
1766//
1767// FIXME: This is hosed as of OS 10.5; patches to repair this are
1768// a good thing.
1769//
1770#if 0
1771
1772#ifdef	__cplusplus
1773extern "C" {
1774#endif
1775
1776typedef int CGSConnection;
1777typedef enum {
1778	CGSGlobalHotKeyEnable = 0,
1779	CGSGlobalHotKeyDisable = 1,
1780} CGSGlobalHotKeyOperatingMode;
1781
1782extern CGSConnection _CGSDefaultConnection(void) WEAK_IMPORT_ATTRIBUTE;
1783extern CGError CGSGetGlobalHotKeyOperatingMode(CGSConnection connection, CGSGlobalHotKeyOperatingMode *mode) WEAK_IMPORT_ATTRIBUTE;
1784extern CGError CGSSetGlobalHotKeyOperatingMode(CGSConnection connection, CGSGlobalHotKeyOperatingMode mode) WEAK_IMPORT_ATTRIBUTE;
1785
1786typedef CGSConnection (*_CGSDefaultConnection_t)(void);
1787typedef CGError (*CGSGetGlobalHotKeyOperatingMode_t)(CGSConnection connection, CGSGlobalHotKeyOperatingMode *mode);
1788typedef CGError (*CGSSetGlobalHotKeyOperatingMode_t)(CGSConnection connection, CGSGlobalHotKeyOperatingMode mode);
1789
1790static _CGSDefaultConnection_t				s__CGSDefaultConnection;
1791static CGSGetGlobalHotKeyOperatingMode_t	s_CGSGetGlobalHotKeyOperatingMode;
1792static CGSSetGlobalHotKeyOperatingMode_t	s_CGSSetGlobalHotKeyOperatingMode;
1793
1794#ifdef	__cplusplus
1795}
1796#endif
1797
1798#define LOOKUP(name_)													\
1799	s_ ## name_ = NULL;													\
1800	if (NSIsSymbolNameDefinedWithHint("_" #name_, "CoreGraphics")) {	\
1801		s_ ## name_ = (name_ ## _t)NSAddressOfSymbol(					\
1802							NSLookupAndBindSymbolWithHint(				\
1803								"_" #name_, "CoreGraphics"));			\
1804	}
1805
1806bool
1807OSXScreen::isGlobalHotKeyOperatingModeAvailable()
1808{
1809	if (!s_testedForGHOM) {
1810		s_testedForGHOM = true;
1811		LOOKUP(_CGSDefaultConnection);
1812		LOOKUP(CGSGetGlobalHotKeyOperatingMode);
1813		LOOKUP(CGSSetGlobalHotKeyOperatingMode);
1814		s_hasGHOM = (s__CGSDefaultConnection != NULL &&
1815					s_CGSGetGlobalHotKeyOperatingMode != NULL &&
1816					s_CGSSetGlobalHotKeyOperatingMode != NULL);
1817	}
1818	return s_hasGHOM;
1819}
1820
1821void
1822OSXScreen::setGlobalHotKeysEnabled(bool enabled)
1823{
1824	if (isGlobalHotKeyOperatingModeAvailable()) {
1825		CGSConnection conn = s__CGSDefaultConnection();
1826
1827		CGSGlobalHotKeyOperatingMode mode;
1828		s_CGSGetGlobalHotKeyOperatingMode(conn, &mode);
1829
1830		if (enabled && mode == CGSGlobalHotKeyDisable) {
1831			s_CGSSetGlobalHotKeyOperatingMode(conn, CGSGlobalHotKeyEnable);
1832		}
1833		else if (!enabled && mode == CGSGlobalHotKeyEnable) {
1834			s_CGSSetGlobalHotKeyOperatingMode(conn, CGSGlobalHotKeyDisable);
1835		}
1836	}
1837}
1838
1839bool
1840OSXScreen::getGlobalHotKeysEnabled()
1841{
1842	CGSGlobalHotKeyOperatingMode mode;
1843	if (isGlobalHotKeyOperatingModeAvailable()) {
1844		CGSConnection conn = s__CGSDefaultConnection();
1845		s_CGSGetGlobalHotKeyOperatingMode(conn, &mode);
1846	}
1847	else {
1848		mode = CGSGlobalHotKeyEnable;
1849	}
1850	return (mode == CGSGlobalHotKeyEnable);
1851}
1852
1853#endif
1854
1855//
1856// OSXScreen::HotKeyItem
1857//
1858
1859OSXScreen::HotKeyItem::HotKeyItem(UInt32 keycode, UInt32 mask) :
1860	m_ref(NULL),
1861	m_keycode(keycode),
1862	m_mask(mask)
1863{
1864	// do nothing
1865}
1866
1867OSXScreen::HotKeyItem::HotKeyItem(EventHotKeyRef ref,
1868				UInt32 keycode, UInt32 mask) :
1869	m_ref(ref),
1870	m_keycode(keycode),
1871	m_mask(mask)
1872{
1873	// do nothing
1874}
1875
1876EventHotKeyRef
1877OSXScreen::HotKeyItem::getRef() const
1878{
1879	return m_ref;
1880}
1881
1882bool
1883OSXScreen::HotKeyItem::operator<(const HotKeyItem& x) const
1884{
1885	return (m_keycode < x.m_keycode ||
1886			(m_keycode == x.m_keycode && m_mask < x.m_mask));
1887}
1888
1889// Quartz event tap support for the secondary display. This makes sure that we
1890// will show the cursor if a local event comes in while barrier has the cursor
1891// off the screen.
1892CGEventRef
1893OSXScreen::handleCGInputEventSecondary(
1894	CGEventTapProxy proxy,
1895	CGEventType type,
1896	CGEventRef event,
1897	void* refcon)
1898{
1899	// this fix is really screwing with the correct show/hide behavior. it
1900	// should be tested better before reintroducing.
1901	return event;
1902
1903	OSXScreen* screen = (OSXScreen*)refcon;
1904	if (screen->m_cursorHidden && type == kCGEventMouseMoved) {
1905
1906		CGPoint pos = CGEventGetLocation(event);
1907		if (pos.x != screen->m_xCenter || pos.y != screen->m_yCenter) {
1908
1909			LOG((CLOG_DEBUG "show cursor on secondary, type=%d pos=%d,%d",
1910					type, pos.x, pos.y));
1911			screen->showCursor();
1912		}
1913	}
1914	return event;
1915}
1916
1917// Quartz event tap support
1918CGEventRef
1919OSXScreen::handleCGInputEvent(CGEventTapProxy proxy,
1920							   CGEventType type,
1921							   CGEventRef event,
1922							   void* refcon)
1923{
1924	OSXScreen* screen = (OSXScreen*)refcon;
1925	CGPoint pos;
1926
1927	switch(type) {
1928		case kCGEventLeftMouseDown:
1929		case kCGEventRightMouseDown:
1930		case kCGEventOtherMouseDown:
1931			screen->onMouseButton(true, CGEventGetIntegerValueField(event, kCGMouseEventButtonNumber) + 1);
1932			break;
1933		case kCGEventLeftMouseUp:
1934		case kCGEventRightMouseUp:
1935		case kCGEventOtherMouseUp:
1936			screen->onMouseButton(false, CGEventGetIntegerValueField(event, kCGMouseEventButtonNumber) + 1);
1937			break;
1938		case kCGEventLeftMouseDragged:
1939		case kCGEventRightMouseDragged:
1940		case kCGEventOtherMouseDragged:
1941		case kCGEventMouseMoved:
1942			pos = CGEventGetLocation(event);
1943			screen->onMouseMove(pos.x, pos.y);
1944
1945			// The system ignores our cursor-centering calls if
1946			// we don't return the event. This should be harmless,
1947			// but might register as slight movement to other apps
1948			// on the system. It hasn't been a problem before, though.
1949			return event;
1950			break;
1951		case kCGEventScrollWheel:
1952			screen->onMouseWheel(screen->mapScrollWheelToBarrier(
1953								 CGEventGetIntegerValueField(event, kCGScrollWheelEventFixedPtDeltaAxis2) / 65536.0f),
1954								 screen->mapScrollWheelToBarrier(
1955								 CGEventGetIntegerValueField(event, kCGScrollWheelEventFixedPtDeltaAxis1) / 65536.0f));
1956			break;
1957		case kCGEventKeyDown:
1958		case kCGEventKeyUp:
1959		case kCGEventFlagsChanged:
1960			screen->onKey(event);
1961			break;
1962		case kCGEventTapDisabledByTimeout:
1963			// Re-enable our event-tap
1964			CGEventTapEnable(screen->m_eventTapPort, true);
1965			LOG((CLOG_INFO "quartz event tap was disabled by timeout, re-enabling"));
1966			break;
1967		case kCGEventTapDisabledByUserInput:
1968			LOG((CLOG_ERR "quartz event tap was disabled by user input"));
1969			break;
1970		case NX_NULLEVENT:
1971			break;
1972		default:
1973			if (type == NX_SYSDEFINED) {
1974			if (isMediaKeyEvent (event)) {
1975				LOG((CLOG_DEBUG2 "detected media key event"));
1976				screen->onMediaKey (event);
1977			} else {
1978				LOG((CLOG_DEBUG2 "ignoring unknown system defined event"));
1979				return event;
1980			}
1981			break;
1982			}
1983
1984			LOG((CLOG_DEBUG3 "unknown quartz event type: 0x%02x", type));
1985	}
1986
1987	if (screen->m_isOnScreen) {
1988		return event;
1989	} else {
1990		return NULL;
1991	}
1992}
1993
1994void
1995OSXScreen::MouseButtonState::set(UInt32 button, EMouseButtonState state)
1996{
1997	bool newState = (state == kMouseButtonDown);
1998	m_buttons.set(button, newState);
1999}
2000
2001bool
2002OSXScreen::MouseButtonState::any()
2003{
2004	return m_buttons.any();
2005}
2006
2007void
2008OSXScreen::MouseButtonState::reset()
2009{
2010	m_buttons.reset();
2011}
2012
2013void
2014OSXScreen::MouseButtonState::overwrite(UInt32 buttons)
2015{
2016	m_buttons = std::bitset<NumButtonIDs>(buttons);
2017}
2018
2019bool
2020OSXScreen::MouseButtonState::test(UInt32 button) const
2021{
2022	return m_buttons.test(button);
2023}
2024
2025SInt8
2026OSXScreen::MouseButtonState::getFirstButtonDown() const
2027{
2028	if (m_buttons.any()) {
2029		for (unsigned short button = 0; button < m_buttons.size(); button++) {
2030			if (m_buttons.test(button)) {
2031				return button;
2032			}
2033		}
2034	}
2035	return -1;
2036}
2037
2038char*
2039OSXScreen::CFStringRefToUTF8String(CFStringRef aString)
2040{
2041	if (aString == NULL) {
2042		return NULL;
2043	}
2044
2045	CFIndex length = CFStringGetLength(aString);
2046	CFIndex maxSize = CFStringGetMaximumSizeForEncoding(
2047		length,
2048		kCFStringEncodingUTF8);
2049	char* buffer = (char*)malloc(maxSize);
2050	if (CFStringGetCString(aString, buffer, maxSize, kCFStringEncodingUTF8)) {
2051		return buffer;
2052	}
2053	return NULL;
2054}
2055
2056void
2057OSXScreen::fakeDraggingFiles(DragFileList fileList)
2058{
2059	m_fakeDraggingStarted = true;
2060	String fileExt;
2061	if (fileList.size() == 1) {
2062		fileExt = DragInformation::getDragFileExtension(
2063			fileList.at(0).getFilename());
2064	}
2065
2066#if defined(MAC_OS_X_VERSION_10_7)
2067	fakeDragging(fileExt.c_str(), m_xCursor, m_yCursor);
2068#else
2069	LOG((CLOG_WARN "drag drop not supported"));
2070#endif
2071}
2072
2073String&
2074OSXScreen::getDraggingFilename()
2075{
2076	if (m_draggingStarted) {
2077		CFStringRef dragInfo = getDraggedFileURL();
2078		char* info = NULL;
2079		info = CFStringRefToUTF8String(dragInfo);
2080		if (info == NULL) {
2081			m_draggingFilename.clear();
2082		}
2083		else {
2084			LOG((CLOG_DEBUG "drag info: %s", info));
2085			CFRelease(dragInfo);
2086			String fileList(info);
2087			m_draggingFilename = fileList;
2088		}
2089
2090		// fake a escape key down and up then left mouse button up
2091		fakeKeyDown(kKeyEscape, 8192, 1);
2092		fakeKeyUp(1);
2093		fakeMouseButton(kButtonLeft, false);
2094	}
2095	return m_draggingFilename;
2096}
2097
2098void
2099OSXScreen::waitForCarbonLoop() const
2100{
2101#if defined(MAC_OS_X_VERSION_10_7)
2102	if (*m_carbonLoopReady) {
2103		LOG((CLOG_DEBUG "carbon loop already ready"));
2104		return;
2105	}
2106
2107	Lock lock(m_carbonLoopMutex);
2108
2109	LOG((CLOG_DEBUG "waiting for carbon loop"));
2110
2111	double timeout = ARCH->time() + kCarbonLoopWaitTimeout;
2112	while (!m_carbonLoopReady->wait()) {
2113		if (ARCH->time() > timeout) {
2114			LOG((CLOG_DEBUG "carbon loop not ready, waiting again"));
2115			timeout = ARCH->time() + kCarbonLoopWaitTimeout;
2116		}
2117	}
2118
2119	LOG((CLOG_DEBUG "carbon loop ready"));
2120#endif
2121
2122}
2123
2124#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
2125
2126void
2127setZeroSuppressionInterval()
2128{
2129	CGSetLocalEventsSuppressionInterval(0.0);
2130}
2131
2132void
2133avoidSupression()
2134{
2135	// avoid suppression of local hardware events
2136	// stkamp@users.sourceforge.net
2137	CGSetLocalEventsFilterDuringSupressionState(
2138							kCGEventFilterMaskPermitAllEvents,
2139							kCGEventSupressionStateSupressionInterval);
2140	CGSetLocalEventsFilterDuringSupressionState(
2141							(kCGEventFilterMaskPermitLocalKeyboardEvents |
2142							kCGEventFilterMaskPermitSystemDefinedEvents),
2143							kCGEventSupressionStateRemoteMouseDrag);
2144}
2145
2146void
2147logCursorVisibility()
2148{
2149	// CGCursorIsVisible is probably deprecated because its unreliable.
2150	if (!CGCursorIsVisible()) {
2151		LOG((CLOG_WARN "cursor may not be visible"));
2152	}
2153}
2154
2155void
2156avoidHesitatingCursor()
2157{
2158	// This used to be necessary to get smooth mouse motion on other screens,
2159	// but now is just to avoid a hesitating cursor when transitioning to
2160	// the primary (this) screen.
2161	CGSetLocalEventsSuppressionInterval(0.0001);
2162}
2163
2164#pragma GCC diagnostic error "-Wdeprecated-declarations"
2165