source: trunk/synergy/lib/platform/COSXScreen.cpp

Last change on this file was 2749, checked in by bird, 19 years ago

synergy v1.3.1 sources (zip).

File size: 40.5 KB
Line 
1/*
2 * synergy -- mouse and keyboard sharing utility
3 * Copyright (C) 2004 Chris Schoeneman
4 *
5 * This package is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * found in the file COPYING that should have accompanied this file.
8 *
9 * This package is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 */
14
15#include "COSXScreen.h"
16#include "COSXClipboard.h"
17#include "COSXEventQueueBuffer.h"
18#include "COSXKeyState.h"
19#include "COSXScreenSaver.h"
20#include "CClipboard.h"
21#include "CKeyMap.h"
22#include "CCondVar.h"
23#include "CLock.h"
24#include "CMutex.h"
25#include "CThread.h"
26#include "CLog.h"
27#include "IEventQueue.h"
28#include "TMethodEventJob.h"
29#include "TMethodJob.h"
30#include "XArch.h"
31
32#include <mach-o/dyld.h>
33#include <AvailabilityMacros.h>
34
35// Set some enums for fast user switching if we're building with an SDK
36// from before such support was added.
37#if !defined(MAC_OS_X_VERSION_10_3) || \
38 (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_3)
39enum {
40 kEventClassSystem = 'macs',
41 kEventSystemUserSessionActivated = 10,
42 kEventSystemUserSessionDeactivated = 11
43};
44#endif
45
46// This isn't in any Apple SDK that I know of as of yet.
47enum {
48 kSynergyEventMouseScroll = 11,
49 kSynergyMouseScrollAxisX = 'saxx',
50 kSynergyMouseScrollAxisY = 'saxy'
51};
52
53//
54// COSXScreen
55//
56
57bool COSXScreen::s_testedForGHOM = false;
58bool COSXScreen::s_hasGHOM = false;
59CEvent::Type COSXScreen::s_confirmSleepEvent = CEvent::kUnknown;
60
61COSXScreen::COSXScreen(bool isPrimary) :
62 m_isPrimary(isPrimary),
63 m_isOnScreen(m_isPrimary),
64 m_cursorPosValid(false),
65 m_cursorHidden(false),
66 m_dragNumButtonsDown(0),
67 m_dragTimer(NULL),
68 m_keyState(NULL),
69 m_sequenceNumber(0),
70 m_screensaver(NULL),
71 m_screensaverNotify(false),
72 m_ownClipboard(false),
73 m_clipboardTimer(NULL),
74 m_hiddenWindow(NULL),
75 m_userInputWindow(NULL),
76 m_displayManagerNotificationUPP(NULL),
77 m_switchEventHandlerRef(0),
78 m_pmMutex(new CMutex),
79 m_pmWatchThread(NULL),
80 m_pmThreadReady(new CCondVar<bool>(m_pmMutex, false)),
81 m_activeModifierHotKey(0),
82 m_activeModifierHotKeyMask(0)
83{
84 try {
85 m_displayID = CGMainDisplayID();
86 updateScreenShape();
87 m_screensaver = new COSXScreenSaver(getEventTarget());
88 m_keyState = new COSXKeyState();
89
90 if (m_isPrimary) {
91 // 1x1 window (to minimze the back buffer allocated for this
92 // window.
93 Rect bounds = { 100, 100, 101, 101 };
94
95 // m_hiddenWindow is a window meant to let us get mouse moves
96 // when the focus is on another computer. If you get your event
97 // from the application event target you'll get every mouse
98 // moves. On the other hand the Window event target will only
99 // get events when the mouse moves over the window.
100
101 // The ignoreClicks attributes makes it impossible for the
102 // user to click on our invisible window.
103 CreateNewWindow(kUtilityWindowClass,
104 kWindowNoShadowAttribute |
105 kWindowIgnoreClicksAttribute |
106 kWindowNoActivatesAttribute,
107 &bounds, &m_hiddenWindow);
108
109 // Make it invisible
110 SetWindowAlpha(m_hiddenWindow, 0);
111 ShowWindow(m_hiddenWindow);
112
113 // m_userInputWindow is a window meant to let us get mouse moves
114 // when the focus is on this computer.
115 Rect inputBounds = { 100, 100, 200, 200 };
116 CreateNewWindow(kUtilityWindowClass,
117 kWindowNoShadowAttribute |
118 kWindowOpaqueForEventsAttribute |
119 kWindowStandardHandlerAttribute,
120 &inputBounds, &m_userInputWindow);
121
122 SetWindowAlpha(m_userInputWindow, 0);
123 }
124
125 // install display manager notification handler
126 m_displayManagerNotificationUPP =
127 NewDMExtendedNotificationUPP(displayManagerCallback);
128 OSStatus err = GetCurrentProcess(&m_PSN);
129 err = DMRegisterExtendedNotifyProc(m_displayManagerNotificationUPP,
130 this, 0, &m_PSN);
131
132 // install fast user switching event handler
133 EventTypeSpec switchEventTypes[2];
134 switchEventTypes[0].eventClass = kEventClassSystem;
135 switchEventTypes[0].eventKind = kEventSystemUserSessionDeactivated;
136 switchEventTypes[1].eventClass = kEventClassSystem;
137 switchEventTypes[1].eventKind = kEventSystemUserSessionActivated;
138 EventHandlerUPP switchEventHandler =
139 NewEventHandlerUPP(userSwitchCallback);
140 InstallApplicationEventHandler(switchEventHandler, 2, switchEventTypes,
141 this, &m_switchEventHandlerRef);
142 DisposeEventHandlerUPP(switchEventHandler);
143
144 // watch for requests to sleep
145 EVENTQUEUE->adoptHandler(COSXScreen::getConfirmSleepEvent(),
146 getEventTarget(),
147 new TMethodEventJob<COSXScreen>(this,
148 &COSXScreen::handleConfirmSleep));
149
150 // create thread for monitoring system power state.
151 LOG((CLOG_DEBUG "starting watchSystemPowerThread"));
152 m_pmWatchThread = new CThread(new TMethodJob<COSXScreen>
153 (this, &COSXScreen::watchSystemPowerThread));
154 }
155 catch (...) {
156 EVENTQUEUE->removeHandler(COSXScreen::getConfirmSleepEvent(),
157 getEventTarget());
158 if (m_switchEventHandlerRef != 0) {
159 RemoveEventHandler(m_switchEventHandlerRef);
160 }
161 if (m_displayManagerNotificationUPP != NULL) {
162 DMRemoveExtendedNotifyProc(m_displayManagerNotificationUPP,
163 NULL, &m_PSN, 0);
164 }
165
166 if (m_hiddenWindow) {
167 ReleaseWindow(m_hiddenWindow);
168 m_hiddenWindow = NULL;
169 }
170
171 if (m_userInputWindow) {
172 ReleaseWindow(m_userInputWindow);
173 m_userInputWindow = NULL;
174 }
175 delete m_keyState;
176 delete m_screensaver;
177 throw;
178 }
179
180 // install event handlers
181 EVENTQUEUE->adoptHandler(CEvent::kSystem, IEventQueue::getSystemTarget(),
182 new TMethodEventJob<COSXScreen>(this,
183 &COSXScreen::handleSystemEvent));
184
185 // install the platform event queue
186 EVENTQUEUE->adoptBuffer(new COSXEventQueueBuffer);
187}
188
189COSXScreen::~COSXScreen()
190{
191 disable();
192 EVENTQUEUE->adoptBuffer(NULL);
193 EVENTQUEUE->removeHandler(CEvent::kSystem, IEventQueue::getSystemTarget());
194
195 if (m_pmWatchThread) {
196 // make sure the thread has setup the runloop.
197 {
198 CLock lock(m_pmMutex);
199 while (!(bool)*m_pmThreadReady) {
200 m_pmThreadReady->wait();
201 }
202 }
203
204 // now exit the thread's runloop and wait for it to exit
205 LOG((CLOG_DEBUG "stopping watchSystemPowerThread"));
206 CFRunLoopStop(m_pmRunloop);
207 m_pmWatchThread->wait();
208 delete m_pmWatchThread;
209 m_pmWatchThread = NULL;
210 }
211 delete m_pmThreadReady;
212 delete m_pmMutex;
213
214 EVENTQUEUE->removeHandler(COSXScreen::getConfirmSleepEvent(),
215 getEventTarget());
216
217 RemoveEventHandler(m_switchEventHandlerRef);
218
219 DMRemoveExtendedNotifyProc(m_displayManagerNotificationUPP,
220 NULL, &m_PSN, 0);
221
222 if (m_hiddenWindow) {
223 ReleaseWindow(m_hiddenWindow);
224 m_hiddenWindow = NULL;
225 }
226
227 if (m_userInputWindow) {
228 ReleaseWindow(m_userInputWindow);
229 m_userInputWindow = NULL;
230 }
231
232 delete m_keyState;
233 delete m_screensaver;
234}
235
236void*
237COSXScreen::getEventTarget() const
238{
239 return const_cast<COSXScreen*>(this);
240}
241
242bool
243COSXScreen::getClipboard(ClipboardID, IClipboard* dst) const
244{
245 COSXClipboard src;
246 CClipboard::copy(dst, &src);
247 return true;
248}
249
250void
251COSXScreen::getShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h) const
252{
253 x = m_x;
254 y = m_y;
255 w = m_w;
256 h = m_h;
257}
258
259void
260COSXScreen::getCursorPos(SInt32& x, SInt32& y) const
261{
262 Point mouse;
263 GetGlobalMouse(&mouse);
264 x = mouse.h;
265 y = mouse.v;
266 m_cursorPosValid = true;
267 m_xCursor = x;
268 m_yCursor = y;
269}
270
271void
272COSXScreen::reconfigure(UInt32)
273{
274 // do nothing
275}
276
277void
278COSXScreen::warpCursor(SInt32 x, SInt32 y)
279{
280 // move cursor without generating events
281 CGPoint pos;
282 pos.x = x;
283 pos.y = y;
284 CGWarpMouseCursorPosition(pos);
285
286 // save new cursor position
287 m_xCursor = x;
288 m_yCursor = y;
289 m_cursorPosValid = true;
290}
291
292void
293COSXScreen::fakeInputBegin()
294{
295 // FIXME -- not implemented
296}
297
298void
299COSXScreen::fakeInputEnd()
300{
301 // FIXME -- not implemented
302}
303
304SInt32
305COSXScreen::getJumpZoneSize() const
306{
307 return 1;
308}
309
310bool
311COSXScreen::isAnyMouseButtonDown() const
312{
313 return (GetCurrentButtonState() != 0);
314}
315
316void
317COSXScreen::getCursorCenter(SInt32& x, SInt32& y) const
318{
319 x = m_xCenter;
320 y = m_yCenter;
321}
322
323UInt32
324COSXScreen::registerHotKey(KeyID key, KeyModifierMask mask)
325{
326 // get mac virtual key and modifier mask matching synergy key and mask
327 UInt32 macKey, macMask;
328 if (!m_keyState->mapSynergyHotKeyToMac(key, mask, macKey, macMask)) {
329 LOG((CLOG_WARN "could not map hotkey id=%04x mask=%04x", key, mask));
330 return 0;
331 }
332
333 // choose hotkey id
334 UInt32 id;
335 if (!m_oldHotKeyIDs.empty()) {
336 id = m_oldHotKeyIDs.back();
337 m_oldHotKeyIDs.pop_back();
338 }
339 else {
340 id = m_hotKeys.size() + 1;
341 }
342
343 // if this hot key has modifiers only then we'll handle it specially
344 EventHotKeyRef ref = NULL;
345 bool okay;
346 if (key == kKeyNone) {
347 if (m_modifierHotKeys.count(mask) > 0) {
348 // already registered
349 okay = false;
350 }
351 else {
352 m_modifierHotKeys[mask] = id;
353 okay = true;
354 }
355 }
356 else {
357 EventHotKeyID hkid = { 'SNRG', (UInt32)id };
358 OSStatus status = RegisterEventHotKey(macKey, macMask, hkid,
359 GetApplicationEventTarget(), 0,
360 &ref);
361 okay = (status == noErr);
362 m_hotKeyToIDMap[CHotKeyItem(macKey, macMask)] = id;
363 }
364
365 if (!okay) {
366 m_oldHotKeyIDs.push_back(id);
367 m_hotKeyToIDMap.erase(CHotKeyItem(macKey, macMask));
368 LOG((CLOG_WARN "failed to register hotkey %s (id=%04x mask=%04x)", CKeyMap::formatKey(key, mask).c_str(), key, mask));
369 return 0;
370 }
371
372 m_hotKeys.insert(std::make_pair(id, CHotKeyItem(ref, macKey, macMask)));
373
374 LOG((CLOG_DEBUG "registered hotkey %s (id=%04x mask=%04x) as id=%d", CKeyMap::formatKey(key, mask).c_str(), key, mask, id));
375 return id;
376}
377
378void
379COSXScreen::unregisterHotKey(UInt32 id)
380{
381 // look up hotkey
382 HotKeyMap::iterator i = m_hotKeys.find(id);
383 if (i == m_hotKeys.end()) {
384 return;
385 }
386
387 // unregister with OS
388 bool okay;
389 if (i->second.getRef() != NULL) {
390 okay = (UnregisterEventHotKey(i->second.getRef()) == noErr);
391 }
392 else {
393 okay = false;
394 // XXX -- this is inefficient
395 for (ModifierHotKeyMap::iterator j = m_modifierHotKeys.begin();
396 j != m_modifierHotKeys.end(); ++j) {
397 if (j->second == id) {
398 m_modifierHotKeys.erase(j);
399 okay = true;
400 break;
401 }
402 }
403 }
404 if (!okay) {
405 LOG((CLOG_WARN "failed to unregister hotkey id=%d", id));
406 }
407 else {
408 LOG((CLOG_DEBUG "unregistered hotkey id=%d", id));
409 }
410
411 // discard hot key from map and record old id for reuse
412 m_hotKeyToIDMap.erase(i->second);
413 m_hotKeys.erase(i);
414 m_oldHotKeyIDs.push_back(id);
415 if (m_activeModifierHotKey == id) {
416 m_activeModifierHotKey = 0;
417 m_activeModifierHotKeyMask = 0;
418 }
419}
420
421void
422COSXScreen::postMouseEvent(CGPoint& pos) const
423{
424 // check if cursor position is valid on the client display configuration
425 // stkamp@users.sourceforge.net
426 CGDisplayCount displayCount = 0;
427 CGGetDisplaysWithPoint(pos, 0, NULL, &displayCount);
428 if (displayCount == 0) {
429 // cursor position invalid - clamp to bounds of last valid display.
430 // find the last valid display using the last cursor position.
431 displayCount = 0;
432 CGDirectDisplayID displayID;
433 CGGetDisplaysWithPoint(CGPointMake(m_xCursor, m_yCursor), 1,
434 &displayID, &displayCount);
435 if (displayCount != 0) {
436 CGRect displayRect = CGDisplayBounds(displayID);
437 if (pos.x < displayRect.origin.x) {
438 pos.x = displayRect.origin.x;
439 }
440 else if (pos.x > displayRect.origin.x +
441 displayRect.size.width - 1) {
442 pos.x = displayRect.origin.x + displayRect.size.width - 1;
443 }
444 if (pos.y < displayRect.origin.y) {
445 pos.y = displayRect.origin.y;
446 }
447 else if (pos.y > displayRect.origin.y +
448 displayRect.size.height - 1) {
449 pos.y = displayRect.origin.y + displayRect.size.height - 1;
450 }
451 }
452 }
453
454 // synthesize event. CGPostMouseEvent is a particularly good
455 // example of a bad API. we have to shadow the mouse state to
456 // use this API and if we want to support more buttons we have
457 // to recompile.
458 //
459 // the order of buttons on the mac is:
460 // 1 - Left
461 // 2 - Right
462 // 3 - Middle
463 // Whatever the USB device defined.
464 //
465 // It is a bit weird that the behaviour of buttons over 3 are dependent
466 // on currently plugged in USB devices.
467 CGPostMouseEvent(pos, true, sizeof(m_buttons) / sizeof(m_buttons[0]),
468 m_buttons[0],
469 m_buttons[2],
470 m_buttons[1],
471 m_buttons[3],
472 m_buttons[4]);
473}
474
475
476void
477COSXScreen::fakeMouseButton(ButtonID id, bool press) const
478{
479 // get button index
480 UInt32 index = id - kButtonLeft;
481 if (index >= sizeof(m_buttons) / sizeof(m_buttons[0])) {
482 return;
483 }
484
485 // update state
486 m_buttons[index] = press;
487
488 CGPoint pos;
489 if (!m_cursorPosValid) {
490 SInt32 x, y;
491 getCursorPos(x, y);
492 }
493 pos.x = m_xCursor;
494 pos.y = m_yCursor;
495 postMouseEvent(pos);
496}
497
498void
499COSXScreen::fakeMouseMove(SInt32 x, SInt32 y) const
500{
501 // synthesize event
502 CGPoint pos;
503 pos.x = x;
504 pos.y = y;
505 postMouseEvent(pos);
506
507 // save new cursor position
508 m_xCursor = static_cast<SInt32>(pos.x);
509 m_yCursor = static_cast<SInt32>(pos.y);
510 m_cursorPosValid = true;
511}
512
513void
514COSXScreen::fakeMouseRelativeMove(SInt32 dx, SInt32 dy) const
515{
516 // OS X does not appear to have a fake relative mouse move function.
517 // simulate it by getting the current mouse position and adding to
518 // that. this can yield the wrong answer but there's not much else
519 // we can do.
520
521 // get current position
522 Point oldPos;
523 GetGlobalMouse(&oldPos);
524
525 // synthesize event
526 CGPoint pos;
527 m_xCursor = static_cast<SInt32>(oldPos.h);
528 m_yCursor = static_cast<SInt32>(oldPos.v);
529 pos.x = oldPos.h + dx;
530 pos.y = oldPos.v + dy;
531 postMouseEvent(pos);
532
533 // we now assume we don't know the current cursor position
534 m_cursorPosValid = false;
535}
536
537void
538COSXScreen::fakeMouseWheel(SInt32 xDelta, SInt32 yDelta) const
539{
540 if (xDelta != 0 || yDelta != 0) {
541 CGPostScrollWheelEvent(2, mapScrollWheelFromSynergy(yDelta),
542 -mapScrollWheelFromSynergy(xDelta));
543 }
544}
545
546void
547COSXScreen::enable()
548{
549 // watch the clipboard
550 m_clipboardTimer = EVENTQUEUE->newTimer(1.0, NULL);
551 EVENTQUEUE->adoptHandler(CEvent::kTimer, m_clipboardTimer,
552 new TMethodEventJob<COSXScreen>(this,
553 &COSXScreen::handleClipboardCheck));
554
555 if (m_isPrimary) {
556 // FIXME -- start watching jump zones
557 }
558 else {
559 // FIXME -- prevent system from entering power save mode
560
561 // hide cursor
562 if (!m_cursorHidden) {
563// CGDisplayHideCursor(m_displayID);
564 m_cursorHidden = true;
565 }
566
567 // warp the mouse to the cursor center
568 fakeMouseMove(m_xCenter, m_yCenter);
569
570 // FIXME -- prepare to show cursor if it moves
571 }
572}
573
574void
575COSXScreen::disable()
576{
577 if (m_isPrimary) {
578 // FIXME -- stop watching jump zones, stop capturing input
579 }
580 else {
581 // show cursor
582 if (m_cursorHidden) {
583// CGDisplayShowCursor(m_displayID);
584 m_cursorHidden = false;
585 }
586
587 // FIXME -- allow system to enter power saving mode
588 }
589
590 // disable drag handling
591 m_dragNumButtonsDown = 0;
592 enableDragTimer(false);
593
594 // uninstall clipboard timer
595 if (m_clipboardTimer != NULL) {
596 EVENTQUEUE->removeHandler(CEvent::kTimer, m_clipboardTimer);
597 EVENTQUEUE->deleteTimer(m_clipboardTimer);
598 m_clipboardTimer = NULL;
599 }
600
601 m_isOnScreen = m_isPrimary;
602}
603
604void
605COSXScreen::enter()
606{
607 if (m_isPrimary) {
608 // stop capturing input, watch jump zones
609 HideWindow( m_userInputWindow );
610 ShowWindow( m_hiddenWindow );
611
612 SetMouseCoalescingEnabled(true, NULL);
613
614 CGSetLocalEventsSuppressionInterval(0.0);
615
616 // enable global hotkeys
617 setGlobalHotKeysEnabled(true);
618 }
619 else {
620 // show cursor
621 if (m_cursorHidden) {
622// CGDisplayShowCursor(m_displayID);
623 m_cursorHidden = false;
624 }
625
626 // reset buttons
627 for (UInt32 i = 0; i < sizeof(m_buttons) / sizeof(m_buttons[0]); ++i) {
628 m_buttons[i] = false;
629 }
630
631 // avoid suppression of local hardware events
632 // stkamp@users.sourceforge.net
633 CGSetLocalEventsFilterDuringSupressionState(
634 kCGEventFilterMaskPermitAllEvents,
635 kCGEventSupressionStateSupressionInterval);
636 CGSetLocalEventsFilterDuringSupressionState(
637 (kCGEventFilterMaskPermitLocalKeyboardEvents |
638 kCGEventFilterMaskPermitSystemDefinedEvents),
639 kCGEventSupressionStateRemoteMouseDrag);
640 }
641
642 // now on screen
643 m_isOnScreen = true;
644}
645
646bool
647COSXScreen::leave()
648{
649 if (m_isPrimary) {
650 // warp to center
651 warpCursor(m_xCenter, m_yCenter);
652
653 // capture events
654 HideWindow(m_hiddenWindow);
655 ShowWindow(m_userInputWindow);
656 RepositionWindow(m_userInputWindow,
657 m_userInputWindow, kWindowCenterOnMainScreen);
658 SetUserFocusWindow(m_userInputWindow);
659
660 // The OS will coalesce some events if they are similar enough in a
661 // short period of time this is bad for us since we need every event
662 // to send it over to other machines. So disable it.
663 SetMouseCoalescingEnabled(false, NULL);
664 CGSetLocalEventsSuppressionInterval(0.0001);
665
666 // disable global hotkeys
667 setGlobalHotKeysEnabled(false);
668 }
669 else {
670 // hide cursor
671 if (!m_cursorHidden) {
672// CGDisplayHideCursor(m_displayID);
673 m_cursorHidden = true;
674 }
675
676 // warp the mouse to the cursor center
677 fakeMouseMove(m_xCenter, m_yCenter);
678
679 // FIXME -- prepare to show cursor if it moves
680
681 // take keyboard focus
682 // FIXME
683 }
684
685 // now off screen
686 m_isOnScreen = false;
687
688 return true;
689}
690
691bool
692COSXScreen::setClipboard(ClipboardID, const IClipboard* src)
693{
694 COSXClipboard dst;
695 if (src != NULL) {
696 // save clipboard data
697 if (!CClipboard::copy(&dst, src)) {
698 return false;
699 }
700 }
701 else {
702 // assert clipboard ownership
703 if (!dst.open(0)) {
704 return false;
705 }
706 dst.empty();
707 dst.close();
708 }
709 checkClipboards();
710 return true;
711}
712
713void
714COSXScreen::checkClipboards()
715{
716 // check if clipboard ownership changed
717 if (!COSXClipboard::isOwnedBySynergy()) {
718 if (m_ownClipboard) {
719 LOG((CLOG_DEBUG "clipboard changed: lost ownership"));
720 m_ownClipboard = false;
721 sendClipboardEvent(getClipboardGrabbedEvent(), kClipboardClipboard);
722 sendClipboardEvent(getClipboardGrabbedEvent(), kClipboardSelection);
723 }
724 }
725 else if (!m_ownClipboard) {
726 LOG((CLOG_DEBUG "clipboard changed: synergy owned"));
727 m_ownClipboard = true;
728 }
729}
730
731void
732COSXScreen::openScreensaver(bool notify)
733{
734 m_screensaverNotify = notify;
735 if (!m_screensaverNotify) {
736 m_screensaver->disable();
737 }
738}
739
740void
741COSXScreen::closeScreensaver()
742{
743 if (!m_screensaverNotify) {
744 m_screensaver->enable();
745 }
746}
747
748void
749COSXScreen::screensaver(bool activate)
750{
751 if (activate) {
752 m_screensaver->activate();
753 }
754 else {
755 m_screensaver->deactivate();
756 }
757}
758
759void
760COSXScreen::resetOptions()
761{
762 // no options
763}
764
765void
766COSXScreen::setOptions(const COptionsList&)
767{
768 // no options
769}
770
771void
772COSXScreen::setSequenceNumber(UInt32 seqNum)
773{
774 m_sequenceNumber = seqNum;
775}
776
777bool
778COSXScreen::isPrimary() const
779{
780 return m_isPrimary;
781}
782
783void
784COSXScreen::sendEvent(CEvent::Type type, void* data) const
785{
786 EVENTQUEUE->addEvent(CEvent(type, getEventTarget(), data));
787}
788
789void
790COSXScreen::sendClipboardEvent(CEvent::Type type, ClipboardID id) const
791{
792 CClipboardInfo* info = (CClipboardInfo*)malloc(sizeof(CClipboardInfo));
793 info->m_id = id;
794 info->m_sequenceNumber = m_sequenceNumber;
795 sendEvent(type, info);
796}
797
798void
799COSXScreen::handleSystemEvent(const CEvent& event, void*)
800{
801 EventRef* carbonEvent = reinterpret_cast<EventRef*>(event.getData());
802 assert(carbonEvent != NULL);
803
804 UInt32 eventClass = GetEventClass(*carbonEvent);
805
806 switch (eventClass) {
807 case kEventClassMouse:
808 switch (GetEventKind(*carbonEvent)) {
809 case kEventMouseDown:
810 {
811 UInt16 myButton;
812 GetEventParameter(*carbonEvent,
813 kEventParamMouseButton,
814 typeMouseButton,
815 NULL,
816 sizeof(myButton),
817 NULL,
818 &myButton);
819 onMouseButton(true, myButton);
820 break;
821 }
822
823 case kEventMouseUp:
824 {
825 UInt16 myButton;
826 GetEventParameter(*carbonEvent,
827 kEventParamMouseButton,
828 typeMouseButton,
829 NULL,
830 sizeof(myButton),
831 NULL,
832 &myButton);
833 onMouseButton(false, myButton);
834 break;
835 }
836
837 case kEventMouseDragged:
838 case kEventMouseMoved:
839 {
840 HIPoint point;
841 GetEventParameter(*carbonEvent,
842 kEventParamMouseLocation,
843 typeHIPoint,
844 NULL,
845 sizeof(point),
846 NULL,
847 &point);
848 onMouseMove((SInt32)point.x, (SInt32)point.y);
849 break;
850 }
851
852 case kEventMouseWheelMoved:
853 {
854 EventMouseWheelAxis axis;
855 SInt32 delta;
856 GetEventParameter(*carbonEvent,
857 kEventParamMouseWheelAxis,
858 typeMouseWheelAxis,
859 NULL,
860 sizeof(axis),
861 NULL,
862 &axis);
863 if (axis == kEventMouseWheelAxisX ||
864 axis == kEventMouseWheelAxisY) {
865 GetEventParameter(*carbonEvent,
866 kEventParamMouseWheelDelta,
867 typeLongInteger,
868 NULL,
869 sizeof(delta),
870 NULL,
871 &delta);
872 if (axis == kEventMouseWheelAxisX) {
873 onMouseWheel(-mapScrollWheelToSynergy((SInt32)delta), 0);
874 }
875 else {
876 onMouseWheel(0, mapScrollWheelToSynergy((SInt32)delta));
877 }
878 }
879 break;
880 }
881
882 case kSynergyEventMouseScroll:
883 {
884 OSStatus r;
885 long xScroll;
886 long yScroll;
887
888 // get scroll amount
889 r = GetEventParameter(*carbonEvent,
890 kSynergyMouseScrollAxisX,
891 typeLongInteger,
892 NULL,
893 sizeof(xScroll),
894 NULL,
895 &xScroll);
896 if (r != noErr) {
897 xScroll = 0;
898 }
899 r = GetEventParameter(*carbonEvent,
900 kSynergyMouseScrollAxisY,
901 typeLongInteger,
902 NULL,
903 sizeof(yScroll),
904 NULL,
905 &yScroll);
906 if (r != noErr) {
907 yScroll = 0;
908 }
909
910 if (xScroll != 0 || yScroll != 0) {
911 onMouseWheel(-mapScrollWheelToSynergy(xScroll),
912 mapScrollWheelToSynergy(yScroll));
913 }
914 }
915 }
916 break;
917
918 case kEventClassKeyboard:
919 switch (GetEventKind(*carbonEvent)) {
920 case kEventRawKeyUp:
921 case kEventRawKeyDown:
922 case kEventRawKeyRepeat:
923 case kEventRawKeyModifiersChanged:
924 onKey(*carbonEvent);
925 break;
926
927 case kEventHotKeyPressed:
928 case kEventHotKeyReleased:
929 onHotKey(*carbonEvent);
930 break;
931 }
932
933 break;
934
935 case kEventClassWindow:
936 SendEventToWindow(*carbonEvent, m_userInputWindow);
937 switch (GetEventKind(*carbonEvent)) {
938 case kEventWindowActivated:
939 LOG((CLOG_DEBUG1 "window activated"));
940 break;
941
942 case kEventWindowDeactivated:
943 LOG((CLOG_DEBUG1 "window deactivated"));
944 break;
945
946 case kEventWindowFocusAcquired:
947 LOG((CLOG_DEBUG1 "focus acquired"));
948 break;
949
950 case kEventWindowFocusRelinquish:
951 LOG((CLOG_DEBUG1 "focus released"));
952 break;
953 }
954 break;
955
956 default:
957 SendEventToEventTarget(*carbonEvent, GetEventDispatcherTarget());
958 break;
959 }
960}
961
962bool
963COSXScreen::onMouseMove(SInt32 mx, SInt32 my)
964{
965 LOG((CLOG_DEBUG2 "mouse move %+d,%+d", mx, my));
966
967 SInt32 x = mx - m_xCursor;
968 SInt32 y = my - m_yCursor;
969
970 if ((x == 0 && y == 0) || (mx == m_xCenter && mx == m_yCenter)) {
971 return true;
972 }
973
974 // save position to compute delta of next motion
975 m_xCursor = mx;
976 m_yCursor = my;
977
978 if (m_isOnScreen) {
979 // motion on primary screen
980 sendEvent(getMotionOnPrimaryEvent(),
981 CMotionInfo::alloc(m_xCursor, m_yCursor));
982 }
983 else {
984 // motion on secondary screen. warp mouse back to
985 // center.
986 warpCursor(m_xCenter, m_yCenter);
987
988 // examine the motion. if it's about the distance
989 // from the center of the screen to an edge then
990 // it's probably a bogus motion that we want to
991 // ignore (see warpCursorNoFlush() for a further
992 // description).
993 static SInt32 bogusZoneSize = 10;
994 if (-x + bogusZoneSize > m_xCenter - m_x ||
995 x + bogusZoneSize > m_x + m_w - m_xCenter ||
996 -y + bogusZoneSize > m_yCenter - m_y ||
997 y + bogusZoneSize > m_y + m_h - m_yCenter) {
998 LOG((CLOG_DEBUG "dropped bogus motion %+d,%+d", x, y));
999 }
1000 else {
1001 // send motion
1002 sendEvent(getMotionOnSecondaryEvent(), CMotionInfo::alloc(x, y));
1003 }
1004 }
1005
1006 return true;
1007}
1008
1009bool
1010COSXScreen::onMouseButton(bool pressed, UInt16 macButton)
1011{
1012 // Buttons 2 and 3 are inverted on the mac
1013 ButtonID button = mapMacButtonToSynergy(macButton);
1014
1015 if (pressed) {
1016 LOG((CLOG_DEBUG1 "event: button press button=%d", button));
1017 if (button != kButtonNone) {
1018 KeyModifierMask mask = m_keyState->getActiveModifiers();
1019 sendEvent(getButtonDownEvent(), CButtonInfo::alloc(button, mask));
1020 }
1021 }
1022 else {
1023 LOG((CLOG_DEBUG1 "event: button release button=%d", button));
1024 if (button != kButtonNone) {
1025 KeyModifierMask mask = m_keyState->getActiveModifiers();
1026 sendEvent(getButtonUpEvent(), CButtonInfo::alloc(button, mask));
1027 }
1028 }
1029
1030 // handle drags with any button other than button 1 or 2
1031 if (macButton > 2) {
1032 if (pressed) {
1033 // one more button
1034 if (m_dragNumButtonsDown++ == 0) {
1035 enableDragTimer(true);
1036 }
1037 }
1038 else {
1039 // one less button
1040 if (--m_dragNumButtonsDown == 0) {
1041 enableDragTimer(false);
1042 }
1043 }
1044 }
1045
1046 return true;
1047}
1048
1049bool
1050COSXScreen::onMouseWheel(SInt32 xDelta, SInt32 yDelta) const
1051{
1052 LOG((CLOG_DEBUG1 "event: button wheel delta=%+d,%+d", xDelta, yDelta));
1053 sendEvent(getWheelEvent(), CWheelInfo::alloc(xDelta, yDelta));
1054 return true;
1055}
1056
1057void
1058COSXScreen::handleClipboardCheck(const CEvent&, void*)
1059{
1060 checkClipboards();
1061}
1062
1063pascal void
1064COSXScreen::displayManagerCallback(void* inUserData, SInt16 inMessage, void*)
1065{
1066 COSXScreen* screen = (COSXScreen*)inUserData;
1067
1068 if (inMessage == kDMNotifyEvent) {
1069 screen->onDisplayChange();
1070 }
1071}
1072
1073bool
1074COSXScreen::onDisplayChange()
1075{
1076 // screen resolution may have changed. save old shape.
1077 SInt32 xOld = m_x, yOld = m_y, wOld = m_w, hOld = m_h;
1078
1079 // update shape
1080 updateScreenShape();
1081
1082 // do nothing if resolution hasn't changed
1083 if (xOld != m_x || yOld != m_y || wOld != m_w || hOld != m_h) {
1084 if (m_isPrimary) {
1085 // warp mouse to center if off screen
1086 if (!m_isOnScreen) {
1087 warpCursor(m_xCenter, m_yCenter);
1088 }
1089 }
1090
1091 // send new screen info
1092 sendEvent(getShapeChangedEvent());
1093 }
1094
1095 return true;
1096}
1097
1098bool
1099COSXScreen::onKey(EventRef event)
1100{
1101 UInt32 eventKind = GetEventKind(event);
1102
1103 // get the key and active modifiers
1104 UInt32 virtualKey, macMask;
1105 GetEventParameter(event, kEventParamKeyCode, typeUInt32,
1106 NULL, sizeof(virtualKey), NULL, &virtualKey);
1107 GetEventParameter(event, kEventParamKeyModifiers, typeUInt32,
1108 NULL, sizeof(macMask), NULL, &macMask);
1109 LOG((CLOG_DEBUG1 "event: Key event kind: %d, keycode=%d", eventKind, virtualKey));
1110
1111 // sadly, OS X doesn't report the virtualKey for modifier keys.
1112 // virtualKey will be zero for modifier keys. since that's not good
1113 // enough we'll have to figure out what the key was.
1114 if (virtualKey == 0 && eventKind == kEventRawKeyModifiersChanged) {
1115 // get old and new modifier state
1116 KeyModifierMask oldMask = getActiveModifiers();
1117 KeyModifierMask newMask = m_keyState->mapModifiersFromOSX(macMask);
1118 m_keyState->handleModifierKeys(getEventTarget(), oldMask, newMask);
1119
1120 // if the current set of modifiers exactly matches a modifiers-only
1121 // hot key then generate a hot key down event.
1122 if (m_activeModifierHotKey == 0) {
1123 if (m_modifierHotKeys.count(newMask) > 0) {
1124 m_activeModifierHotKey = m_modifierHotKeys[newMask];
1125 m_activeModifierHotKeyMask = newMask;
1126 EVENTQUEUE->addEvent(CEvent(getHotKeyDownEvent(),
1127 getEventTarget(),
1128 CHotKeyInfo::alloc(m_activeModifierHotKey)));
1129 }
1130 }
1131
1132 // if a modifiers-only hot key is active and should no longer be
1133 // then generate a hot key up event.
1134 else if (m_activeModifierHotKey != 0) {
1135 KeyModifierMask mask = (newMask & m_activeModifierHotKeyMask);
1136 if (mask != m_activeModifierHotKeyMask) {
1137 EVENTQUEUE->addEvent(CEvent(getHotKeyUpEvent(),
1138 getEventTarget(),
1139 CHotKeyInfo::alloc(m_activeModifierHotKey)));
1140 m_activeModifierHotKey = 0;
1141 m_activeModifierHotKeyMask = 0;
1142 }
1143 }
1144
1145 return true;
1146 }
1147
1148 // check for hot key. when we're on a secondary screen we disable
1149 // all hotkeys so we can capture the OS defined hot keys as regular
1150 // keystrokes but that means we don't get our own hot keys either.
1151 // so we check for a key/modifier match in our hot key map.
1152 if (!m_isOnScreen) {
1153 HotKeyToIDMap::const_iterator i =
1154 m_hotKeyToIDMap.find(CHotKeyItem(virtualKey, macMask & 0xff00u));
1155 if (i != m_hotKeyToIDMap.end()) {
1156 UInt32 id = i->second;
1157
1158 // determine event type
1159 CEvent::Type type;
1160 UInt32 eventKind = GetEventKind(event);
1161 if (eventKind == kEventRawKeyDown) {
1162 type = getHotKeyDownEvent();
1163 }
1164 else if (eventKind == kEventRawKeyUp) {
1165 type = getHotKeyUpEvent();
1166 }
1167 else {
1168 return false;
1169 }
1170
1171 EVENTQUEUE->addEvent(CEvent(type, getEventTarget(),
1172 CHotKeyInfo::alloc(id)));
1173
1174 return true;
1175 }
1176 }
1177
1178 // decode event type
1179 bool down = (eventKind == kEventRawKeyDown);
1180 bool up = (eventKind == kEventRawKeyUp);
1181 bool isRepeat = (eventKind == kEventRawKeyRepeat);
1182
1183 // map event to keys
1184 KeyModifierMask mask;
1185 COSXKeyState::CKeyIDs keys;
1186 KeyButton button = m_keyState->mapKeyFromEvent(keys, &mask, event);
1187 if (button == 0) {
1188 return false;
1189 }
1190
1191 // check for AltGr in mask. if set we send neither the AltGr nor
1192 // the super modifiers to clients then remove AltGr before passing
1193 // the modifiers to onKey.
1194 KeyModifierMask sendMask = (mask & ~KeyModifierAltGr);
1195 if ((mask & KeyModifierAltGr) != 0) {
1196 sendMask &= ~KeyModifierSuper;
1197 }
1198 mask &= ~KeyModifierAltGr;
1199
1200 // update button state
1201 if (down) {
1202 m_keyState->onKey(button, true, mask);
1203 }
1204 else if (up) {
1205 if (!m_keyState->isKeyDown(button)) {
1206 // up event for a dead key. throw it away.
1207 return false;
1208 }
1209 m_keyState->onKey(button, false, mask);
1210 }
1211
1212 // send key events
1213 for (COSXKeyState::CKeyIDs::const_iterator i = keys.begin();
1214 i != keys.end(); ++i) {
1215 m_keyState->sendKeyEvent(getEventTarget(), down, isRepeat,
1216 *i, sendMask, 1, button);
1217 }
1218
1219 return true;
1220}
1221
1222bool
1223COSXScreen::onHotKey(EventRef event) const
1224{
1225 // get the hotkey id
1226 EventHotKeyID hkid;
1227 GetEventParameter(event, kEventParamDirectObject, typeEventHotKeyID,
1228 NULL, sizeof(EventHotKeyID), NULL, &hkid);
1229 UInt32 id = hkid.id;
1230
1231 // determine event type
1232 CEvent::Type type;
1233 UInt32 eventKind = GetEventKind(event);
1234 if (eventKind == kEventHotKeyPressed) {
1235 type = getHotKeyDownEvent();
1236 }
1237 else if (eventKind == kEventHotKeyReleased) {
1238 type = getHotKeyUpEvent();
1239 }
1240 else {
1241 return false;
1242 }
1243
1244 EVENTQUEUE->addEvent(CEvent(type, getEventTarget(),
1245 CHotKeyInfo::alloc(id)));
1246
1247 return true;
1248}
1249
1250ButtonID
1251COSXScreen::mapMacButtonToSynergy(UInt16 macButton) const
1252{
1253 switch (macButton) {
1254 case 1:
1255 return kButtonLeft;
1256
1257 case 2:
1258 return kButtonRight;
1259
1260 case 3:
1261 return kButtonMiddle;
1262 }
1263
1264 return static_cast<ButtonID>(macButton);
1265}
1266
1267SInt32
1268COSXScreen::mapScrollWheelToSynergy(SInt32 x) const
1269{
1270 // return accelerated scrolling but not exponentially scaled as it is
1271 // on the mac.
1272 double d = (1.0 + getScrollSpeed()) * x / getScrollSpeedFactor();
1273 return static_cast<SInt32>(120.0 * d);
1274}
1275
1276SInt32
1277COSXScreen::mapScrollWheelFromSynergy(SInt32 x) const
1278{
1279 // use server's acceleration with a little boost since other platforms
1280 // take one wheel step as a larger step than the mac does.
1281 return static_cast<SInt32>(3.0 * x / 120.0);
1282}
1283
1284double
1285COSXScreen::getScrollSpeed() const
1286{
1287 double scaling = 0.0;
1288
1289 CFPropertyListRef pref = ::CFPreferencesCopyValue(
1290 CFSTR("com.apple.scrollwheel.scaling") ,
1291 kCFPreferencesAnyApplication,
1292 kCFPreferencesCurrentUser,
1293 kCFPreferencesAnyHost);
1294 if (pref != NULL) {
1295 CFTypeID id = CFGetTypeID(pref);
1296 if (id == CFNumberGetTypeID()) {
1297 CFNumberRef value = static_cast<CFNumberRef>(pref);
1298 if (CFNumberGetValue(value, kCFNumberDoubleType, &scaling)) {
1299 if (scaling < 0.0) {
1300 scaling = 0.0;
1301 }
1302 }
1303 }
1304 CFRelease(pref);
1305 }
1306
1307 return scaling;
1308}
1309
1310double
1311COSXScreen::getScrollSpeedFactor() const
1312{
1313 return pow(10.0, getScrollSpeed());
1314}
1315
1316void
1317COSXScreen::enableDragTimer(bool enable)
1318{
1319 if (enable && m_dragTimer == NULL) {
1320 m_dragTimer = EVENTQUEUE->newTimer(0.01, NULL);
1321 EVENTQUEUE->adoptHandler(CEvent::kTimer, m_dragTimer,
1322 new TMethodEventJob<COSXScreen>(this,
1323 &COSXScreen::handleDrag));
1324 GetMouse(&m_dragLastPoint);
1325 }
1326 else if (!enable && m_dragTimer != NULL) {
1327 EVENTQUEUE->removeHandler(CEvent::kTimer, m_dragTimer);
1328 EVENTQUEUE->deleteTimer(m_dragTimer);
1329 m_dragTimer = NULL;
1330 }
1331}
1332
1333void
1334COSXScreen::handleDrag(const CEvent&, void*)
1335{
1336 Point p;
1337 GetMouse(&p);
1338 if (p.h != m_dragLastPoint.h || p.v != m_dragLastPoint.v) {
1339 m_dragLastPoint = p;
1340 onMouseMove((SInt32)p.h, (SInt32)p.v);
1341 }
1342}
1343
1344void
1345COSXScreen::updateButtons()
1346{
1347 UInt32 buttons = GetCurrentButtonState();
1348 for (size_t i = 0; i < sizeof(m_buttons) / sizeof(m_buttons[0]); ++i) {
1349 m_buttons[i] = ((buttons & (1u << i)) != 0);
1350 }
1351}
1352
1353IKeyState*
1354COSXScreen::getKeyState() const
1355{
1356 return m_keyState;
1357}
1358
1359void
1360COSXScreen::updateScreenShape()
1361{
1362 // get info for each display
1363 CGDisplayCount displayCount = 0;
1364
1365 if (CGGetActiveDisplayList(0, NULL, &displayCount) != CGDisplayNoErr) {
1366 return;
1367 }
1368
1369 if (displayCount == 0) {
1370 return;
1371 }
1372
1373 CGDirectDisplayID* displays = new CGDirectDisplayID[displayCount];
1374 if (displays == NULL) {
1375 return;
1376 }
1377
1378 if (CGGetActiveDisplayList(displayCount,
1379 displays, &displayCount) != CGDisplayNoErr) {
1380 delete[] displays;
1381 return;
1382 }
1383
1384 // get smallest rect enclosing all display rects
1385 CGRect totalBounds = CGRectZero;
1386 for (CGDisplayCount i = 0; i < displayCount; ++i) {
1387 CGRect bounds = CGDisplayBounds(displays[i]);
1388 totalBounds = CGRectUnion(totalBounds, bounds);
1389 }
1390
1391 // get shape of default screen
1392 m_x = (SInt32)totalBounds.origin.x;
1393 m_y = (SInt32)totalBounds.origin.y;
1394 m_w = (SInt32)totalBounds.size.width;
1395 m_h = (SInt32)totalBounds.size.height;
1396
1397 // get center of default screen
1398 GDHandle mainScreen = GetMainDevice();
1399 if (mainScreen != NULL) {
1400 const Rect& rect = (*mainScreen)->gdRect;
1401 m_xCenter = (rect.left + rect.right) / 2;
1402 m_yCenter = (rect.top + rect.bottom) / 2;
1403 }
1404 else {
1405 m_xCenter = m_x + (m_w >> 1);
1406 m_yCenter = m_y + (m_h >> 1);
1407 }
1408
1409 delete[] displays;
1410
1411 LOG((CLOG_DEBUG "screen shape: %d,%d %dx%d on %u %s", m_x, m_y, m_w, m_h, displayCount, (displayCount == 1) ? "display" : "displays"));
1412}
1413
1414#pragma mark -
1415
1416//
1417// FAST USER SWITCH NOTIFICATION SUPPORT
1418//
1419// COSXScreen::userSwitchCallback(void*)
1420//
1421// gets called if a fast user switch occurs
1422//
1423
1424pascal OSStatus
1425COSXScreen::userSwitchCallback(EventHandlerCallRef nextHandler,
1426 EventRef theEvent,
1427 void* inUserData)
1428{
1429 COSXScreen* screen = (COSXScreen*)inUserData;
1430 UInt32 kind = GetEventKind(theEvent);
1431
1432 if (kind == kEventSystemUserSessionDeactivated) {
1433 LOG((CLOG_DEBUG "user session deactivated"));
1434 EVENTQUEUE->addEvent(CEvent(IScreen::getSuspendEvent(),
1435 screen->getEventTarget()));
1436 }
1437 else if (kind == kEventSystemUserSessionActivated) {
1438 LOG((CLOG_DEBUG "user session activated"));
1439 EVENTQUEUE->addEvent(CEvent(IScreen::getResumeEvent(),
1440 screen->getEventTarget()));
1441 }
1442 return (CallNextEventHandler(nextHandler, theEvent));
1443}
1444
1445#pragma mark -
1446
1447//
1448// SLEEP/WAKEUP NOTIFICATION SUPPORT
1449//
1450// COSXScreen::watchSystemPowerThread(void*)
1451//
1452// main of thread monitoring system power (sleep/wakup) using a CFRunLoop
1453//
1454
1455void
1456COSXScreen::watchSystemPowerThread(void*)
1457{
1458 io_object_t notifier;
1459 IONotificationPortRef notificationPortRef;
1460 CFRunLoopSourceRef runloopSourceRef = 0;
1461
1462 m_pmRunloop = CFRunLoopGetCurrent();
1463
1464 // install system power change callback
1465 m_pmRootPort = IORegisterForSystemPower(this, &notificationPortRef,
1466 powerChangeCallback, &notifier);
1467 if (m_pmRootPort == 0) {
1468 LOG((CLOG_WARN "IORegisterForSystemPower failed"));
1469 }
1470 else {
1471 runloopSourceRef =
1472 IONotificationPortGetRunLoopSource(notificationPortRef);
1473 CFRunLoopAddSource(m_pmRunloop, runloopSourceRef,
1474 kCFRunLoopCommonModes);
1475 }
1476
1477 // thread is ready
1478 {
1479 CLock lock(m_pmMutex);
1480 *m_pmThreadReady = true;
1481 m_pmThreadReady->signal();
1482 }
1483
1484 // if we were unable to initialize then exit. we must do this after
1485 // setting m_pmThreadReady to true otherwise the parent thread will
1486 // block waiting for it.
1487 if (m_pmRootPort == 0) {
1488 return;
1489 }
1490
1491 // start the run loop
1492 LOG((CLOG_DEBUG "started watchSystemPowerThread"));
1493 CFRunLoopRun();
1494
1495 // cleanup
1496 if (notificationPortRef) {
1497 CFRunLoopRemoveSource(m_pmRunloop,
1498 runloopSourceRef, kCFRunLoopDefaultMode);
1499 CFRunLoopSourceInvalidate(runloopSourceRef);
1500 CFRelease(runloopSourceRef);
1501 }
1502
1503 CLock lock(m_pmMutex);
1504 IODeregisterForSystemPower(&notifier);
1505 m_pmRootPort = 0;
1506 LOG((CLOG_DEBUG "stopped watchSystemPowerThread"));
1507}
1508
1509void
1510COSXScreen::powerChangeCallback(void* refcon, io_service_t service,
1511 natural_t messageType, void* messageArg)
1512{
1513 ((COSXScreen*)refcon)->handlePowerChangeRequest(messageType, messageArg);
1514}
1515
1516void
1517COSXScreen::handlePowerChangeRequest(natural_t messageType, void* messageArg)
1518{
1519 // we've received a power change notification
1520 switch (messageType) {
1521 case kIOMessageSystemWillSleep:
1522 // COSXScreen has to handle this in the main thread so we have to
1523 // queue a confirm sleep event here. we actually don't allow the
1524 // system to sleep until the event is handled.
1525 EVENTQUEUE->addEvent(CEvent(COSXScreen::getConfirmSleepEvent(),
1526 getEventTarget(), messageArg,
1527 CEvent::kDontFreeData));
1528 return;
1529
1530 case kIOMessageSystemHasPoweredOn:
1531 LOG((CLOG_DEBUG "system wakeup"));
1532 EVENTQUEUE->addEvent(CEvent(IScreen::getResumeEvent(),
1533 getEventTarget()));
1534 break;
1535
1536 default:
1537 break;
1538 }
1539
1540 CLock lock(m_pmMutex);
1541 if (m_pmRootPort != 0) {
1542 IOAllowPowerChange(m_pmRootPort, (long)messageArg);
1543 }
1544}
1545
1546CEvent::Type
1547COSXScreen::getConfirmSleepEvent()
1548{
1549 return CEvent::registerTypeOnce(s_confirmSleepEvent,
1550 "COSXScreen::confirmSleep");
1551}
1552
1553void
1554COSXScreen::handleConfirmSleep(const CEvent& event, void*)
1555{
1556 long messageArg = (long)event.getData();
1557 if (messageArg != 0) {
1558 CLock lock(m_pmMutex);
1559 if (m_pmRootPort != 0) {
1560 // deliver suspend event immediately.
1561 EVENTQUEUE->addEvent(CEvent(IScreen::getSuspendEvent(),
1562 getEventTarget(), NULL,
1563 CEvent::kDeliverImmediately));
1564
1565 LOG((CLOG_DEBUG "system will sleep"));
1566 IOAllowPowerChange(m_pmRootPort, messageArg);
1567 }
1568 }
1569}
1570
1571#pragma mark -
1572
1573//
1574// GLOBAL HOTKEY OPERATING MODE SUPPORT (10.3)
1575//
1576// CoreGraphics private API (OSX 10.3)
1577// Source: http://ichiro.nnip.org/osx/Cocoa/GlobalHotkey.html
1578//
1579// We load the functions dynamically because they're not available in
1580// older SDKs. We don't use weak linking because we want users of
1581// older SDKs to build an app that works on newer systems and older
1582// SDKs will not provide the symbols.
1583//
1584
1585#ifdef __cplusplus
1586extern "C" {
1587#endif
1588
1589typedef int CGSConnection;
1590typedef enum {
1591 CGSGlobalHotKeyEnable = 0,
1592 CGSGlobalHotKeyDisable = 1,
1593} CGSGlobalHotKeyOperatingMode;
1594
1595extern CGSConnection _CGSDefaultConnection(void) WEAK_IMPORT_ATTRIBUTE;
1596extern CGError CGSGetGlobalHotKeyOperatingMode(CGSConnection connection, CGSGlobalHotKeyOperatingMode *mode) WEAK_IMPORT_ATTRIBUTE;
1597extern CGError CGSSetGlobalHotKeyOperatingMode(CGSConnection connection, CGSGlobalHotKeyOperatingMode mode) WEAK_IMPORT_ATTRIBUTE;
1598
1599typedef CGSConnection (*_CGSDefaultConnection_t)(void);
1600typedef CGError (*CGSGetGlobalHotKeyOperatingMode_t)(CGSConnection connection, CGSGlobalHotKeyOperatingMode *mode);
1601typedef CGError (*CGSSetGlobalHotKeyOperatingMode_t)(CGSConnection connection, CGSGlobalHotKeyOperatingMode mode);
1602
1603static _CGSDefaultConnection_t s__CGSDefaultConnection;
1604static CGSGetGlobalHotKeyOperatingMode_t s_CGSGetGlobalHotKeyOperatingMode;
1605static CGSSetGlobalHotKeyOperatingMode_t s_CGSSetGlobalHotKeyOperatingMode;
1606
1607#ifdef __cplusplus
1608}
1609#endif
1610
1611#define LOOKUP(name_) \
1612 s_ ## name_ = NULL; \
1613 if (NSIsSymbolNameDefinedWithHint("_" #name_, "CoreGraphics")) { \
1614 s_ ## name_ = (name_ ## _t)NSAddressOfSymbol( \
1615 NSLookupAndBindSymbolWithHint( \
1616 "_" #name_, "CoreGraphics")); \
1617 }
1618
1619bool
1620COSXScreen::isGlobalHotKeyOperatingModeAvailable()
1621{
1622 if (!s_testedForGHOM) {
1623 s_testedForGHOM = true;
1624 LOOKUP(_CGSDefaultConnection);
1625 LOOKUP(CGSGetGlobalHotKeyOperatingMode);
1626 LOOKUP(CGSSetGlobalHotKeyOperatingMode);
1627 s_hasGHOM = (s__CGSDefaultConnection != NULL &&
1628 s_CGSGetGlobalHotKeyOperatingMode != NULL &&
1629 s_CGSSetGlobalHotKeyOperatingMode != NULL);
1630 }
1631 return s_hasGHOM;
1632}
1633
1634void
1635COSXScreen::setGlobalHotKeysEnabled(bool enabled)
1636{
1637 if (isGlobalHotKeyOperatingModeAvailable()) {
1638 CGSConnection conn = s__CGSDefaultConnection();
1639
1640 CGSGlobalHotKeyOperatingMode mode;
1641 s_CGSGetGlobalHotKeyOperatingMode(conn, &mode);
1642
1643 if (enabled && mode == CGSGlobalHotKeyDisable) {
1644 s_CGSSetGlobalHotKeyOperatingMode(conn, CGSGlobalHotKeyEnable);
1645 }
1646 else if (!enabled && mode == CGSGlobalHotKeyEnable) {
1647 s_CGSSetGlobalHotKeyOperatingMode(conn, CGSGlobalHotKeyDisable);
1648 }
1649 }
1650}
1651
1652bool
1653COSXScreen::getGlobalHotKeysEnabled()
1654{
1655 CGSGlobalHotKeyOperatingMode mode;
1656 if (isGlobalHotKeyOperatingModeAvailable()) {
1657 CGSConnection conn = s__CGSDefaultConnection();
1658 s_CGSGetGlobalHotKeyOperatingMode(conn, &mode);
1659 }
1660 else {
1661 mode = CGSGlobalHotKeyEnable;
1662 }
1663 return (mode == CGSGlobalHotKeyEnable);
1664}
1665
1666
1667//
1668// COSXScreen::CHotKeyItem
1669//
1670
1671COSXScreen::CHotKeyItem::CHotKeyItem(UInt32 keycode, UInt32 mask) :
1672 m_ref(NULL),
1673 m_keycode(keycode),
1674 m_mask(mask)
1675{
1676 // do nothing
1677}
1678
1679COSXScreen::CHotKeyItem::CHotKeyItem(EventHotKeyRef ref,
1680 UInt32 keycode, UInt32 mask) :
1681 m_ref(ref),
1682 m_keycode(keycode),
1683 m_mask(mask)
1684{
1685 // do nothing
1686}
1687
1688EventHotKeyRef
1689COSXScreen::CHotKeyItem::getRef() const
1690{
1691 return m_ref;
1692}
1693
1694bool
1695COSXScreen::CHotKeyItem::operator<(const CHotKeyItem& x) const
1696{
1697 return (m_keycode < x.m_keycode ||
1698 (m_keycode == x.m_keycode && m_mask < x.m_mask));
1699}
Note: See TracBrowser for help on using the repository browser.