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, ¬ificationPortRef, 1634 powerChangeCallback, ¬ifier); 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(¬ifier); 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