1
/* Copyright (C) 2005-2010, Thorvald Natvig <thorvald@natvig.com>
2
Copyright (C) 2008-2009, Mikkel Krautz <mikkel@krautz.dk>
6
Redistribution and use in source and binary forms, with or without
7
modification, are permitted provided that the following conditions
10
- Redistributions of source code must retain the above copyright notice,
11
this list of conditions and the following disclaimer.
12
- Redistributions in binary form must reproduce the above copyright notice,
13
this list of conditions and the following disclaimer in the documentation
14
and/or other materials provided with the distribution.
15
- Neither the name of the Mumble Developers nor the names of its
16
contributors may be used to endorse or promote products derived from this
17
software without specific prior written permission.
19
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
23
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
24
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
25
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
26
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
27
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32
#include "GlobalShortcut_macx.h"
37
#import <AppKit/AppKit.h>
39
#define MOD_OFFSET 0x10000
40
#define MOUSE_OFFSET 0x20000
43
* We use DeferInit in here to pop up a dialog box if their system isn't
44
* properly set up to receieve global keyboard/mouse events.
47
GlobalShortcutMacInit::GlobalShortcutMacInit() : QObject(NULL) {
50
void GlobalShortcutMacInit::initialize() {
51
if (!accessibilityApiEnabled())
52
accessibilityDialog();
55
bool GlobalShortcutMacInit::accessibilityApiEnabled() const {
56
return QFile::exists("/private/var/db/.AccessibilityAPIEnabled");
59
void GlobalShortcutMacInit::openPrefsPane(const QString &) const {
60
system("open /Applications/System\\ Preferences.app/ /System/Library/PreferencePanes/UniversalAccessPref.prefPane/");
63
void GlobalShortcutMacInit::accessibilityDialog() const {
64
QMessageBox mb("Mumble",
65
tr("Mumble has detected that it is unable to receive Global Shortcut events when it is in the background.<br /><br />"
66
"This is because the Universal Access feature called 'Enable access for assistive devices' is currently disabled.<br /><br />"
67
"Please <a href=\" \">enable this setting</a> and continue when done."),
68
QMessageBox::Question, QMessageBox::Ok | QMessageBox::Default, QMessageBox::NoButton, QMessageBox::NoButton);
70
QLabel *label = mb.findChild<QLabel *>(QLatin1String("qt_msgbox_label"));
71
label->setOpenExternalLinks(false);
72
connect(label, SIGNAL(linkActivated(const QString &)), this, SLOT(openPrefsPane(const QString &)));
77
static GlobalShortcutMacInit gsminit;
81
GlobalShortcutEngine *GlobalShortcutEngine::platformInit() {
82
return new GlobalShortcutMac();
85
CGEventRef GlobalShortcutMac::callback(CGEventTapProxy proxy, CGEventType type,
86
CGEventRef event, void *udata) {
87
GlobalShortcutMac *gs = reinterpret_cast<GlobalShortcutMac *>(udata);
89
bool suppress = false;
95
case kCGEventLeftMouseDown:
96
case kCGEventRightMouseDown:
97
case kCGEventOtherMouseDown:
99
case kCGEventLeftMouseUp:
100
case kCGEventRightMouseUp:
101
case kCGEventOtherMouseUp: {
102
keycode = static_cast<unsigned int>(CGEventGetIntegerValueField(event, kCGMouseEventButtonNumber));
103
suppress = gs->handleButton(MOUSE_OFFSET+keycode, down);
104
/* Suppressing "the" mouse button is probably not a good idea :-) */
107
#ifndef COMPAT_CLIENT
113
#ifndef COMPAT_CLIENT
114
case kCGEventMouseMoved:
115
case kCGEventLeftMouseDragged:
116
case kCGEventRightMouseDragged:
117
case kCGEventOtherMouseDragged: {
119
int64_t dx = CGEventGetIntegerValueField(event, kCGMouseEventDeltaX);
120
int64_t dy = CGEventGetIntegerValueField(event, kCGMouseEventDeltaY);
121
g.ocIntercept->iMouseX = qBound<int>(0, g.ocIntercept->iMouseX + static_cast<int>(dx), g.ocIntercept->uiWidth - 1);
122
g.ocIntercept->iMouseY = qBound<int>(0, g.ocIntercept->iMouseY + static_cast<int>(dy), g.ocIntercept->uiHeight - 1);
123
QMetaObject::invokeMethod(g.ocIntercept, "updateMouse", Qt::QueuedConnection);
129
case kCGEventScrollWheel:
134
case kCGEventKeyDown:
137
repeat = CGEventGetIntegerValueField(event, kCGKeyboardEventAutorepeat);
139
keycode = static_cast<unsigned int>(CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode));
140
suppress = gs->handleButton(keycode, down);
145
case kCGEventFlagsChanged:
146
suppress = gs->handleModButton(CGEventGetFlags(event));
150
case kCGEventTapDisabledByTimeout:
151
qWarning("GlobalShortcutMac: EventTap disabled by timeout. Re-enabling.");
153
* On Snow Leopard, we get this event type quite often. It disables our event
154
* tap completely. Possible Apple bug.
156
* For now, simply call CGEventTapEnable() to enable our event tap again.
158
* See: http://lists.apple.com/archives/quartz-dev/2009/Sep/msg00007.html
160
CGEventTapEnable(gs->port, true);
162
case kCGEventTapDisabledByUserInput:
163
qWarning("GlobalShortcutMac: EventTap disabled by user input.");
170
#ifndef COMPAT_CLIENT
171
if (forward && g.ocIntercept) {
172
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
173
NSEvent *evt = [[NSEvent eventWithCGEvent:event] retain];
174
QMetaObject::invokeMethod(gs, "forwardEvent", Qt::QueuedConnection, Q_ARG(void *, evt));
180
return suppress ? NULL : event;
183
GlobalShortcutMac::GlobalShortcutMac() : modmask(0) {
185
qWarning("GlobalShortcutMac: Debug build detected. Disabling shortcut engine.");
189
CGEventMask evmask = CGEventMaskBit(kCGEventLeftMouseDown) |
190
CGEventMaskBit(kCGEventLeftMouseUp) |
191
CGEventMaskBit(kCGEventRightMouseDown) |
192
CGEventMaskBit(kCGEventRightMouseUp) |
193
CGEventMaskBit(kCGEventOtherMouseDown) |
194
CGEventMaskBit(kCGEventOtherMouseUp) |
195
CGEventMaskBit(kCGEventKeyDown) |
196
CGEventMaskBit(kCGEventKeyUp) |
197
CGEventMaskBit(kCGEventFlagsChanged) |
198
CGEventMaskBit(kCGEventMouseMoved) |
199
CGEventMaskBit(kCGEventLeftMouseDragged) |
200
CGEventMaskBit(kCGEventRightMouseDragged) |
201
CGEventMaskBit(kCGEventOtherMouseDragged) |
202
CGEventMaskBit(kCGEventScrollWheel);
203
port = CGEventTapCreate(kCGSessionEventTap,
204
kCGHeadInsertEventTap,
205
0, evmask, GlobalShortcutMac::callback,
209
qWarning("GlobalShortcutMac: Unable to create EventTap. Global Shortcuts will not be available.");
215
#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1050
216
if (TISCopyCurrentKeyboardInputSource && TISGetInputSourceProperty) {
217
TISInputSourceRef inputSource = TISCopyCurrentKeyboardInputSource();
219
CFDataRef data = static_cast<CFDataRef>(TISGetInputSourceProperty(inputSource, kTISPropertyUnicodeKeyLayoutData));
221
kbdLayout = reinterpret_cast<UCKeyboardLayout *>(const_cast<UInt8 *>(CFDataGetBytePtr(data)));
227
SInt16 currentKeyScript = GetScriptManagerVariable(smKeyScript);
228
SInt16 lastKeyLayoutID = GetScriptVariable(currentKeyScript, smScriptKeys);
229
Handle handle = GetResource('uchr', lastKeyLayoutID);
231
kbdLayout = reinterpret_cast<UCKeyboardLayout *>(*handle);
235
qWarning("GlobalShortcutMac: No keyboard layout mapping available. Unable to perform key translation.");
237
start(QThread::TimeCriticalPriority);
240
GlobalShortcutMac::~GlobalShortcutMac() {
249
void GlobalShortcutMac::forwardEvent(void *evt) {
250
NSEvent *event = (NSEvent *)evt;
256
QWidget *vp = g.ocIntercept->qgv.viewport();
257
NSView *view = (NSView *) vp->winId();
259
switch ([event type]) {
260
case NSLeftMouseDown:
261
sel = @selector(mouseDown:);
264
sel = @selector(mouseUp:);
266
case NSLeftMouseDragged:
267
sel = @selector(mouseDragged:);
269
case NSRightMouseDown:
270
sel = @selector(rightMouseDown:);
273
sel = @selector(rightMouseUp:);
275
case NSRightMouseDragged:
276
sel = @selector(rightMouseDragged:);
278
case NSOtherMouseDown:
279
sel = @selector(otherMouseDown:);
282
sel = @selector(otherMouseUp:);
284
case NSOtherMouseDragged:
285
sel = @selector(otherMouseDragged:);
288
sel = @selector(mouseEntered:);
291
sel = @selector(mouseExited:);
294
sel = @selector(mouseMoved:);
299
NSPoint p; p.x = (CGFloat) g.ocIntercept->iMouseX;
300
p.y = (CGFloat) (g.ocIntercept->uiHeight - g.ocIntercept->iMouseY);
301
NSEvent *mouseEvent = [NSEvent mouseEventWithType:[event type] location:p modifierFlags:[event modifierFlags] timestamp:[event timestamp]
302
windowNumber:0 context:nil eventNumber:[event eventNumber] clickCount:[event clickCount]
303
pressure:[event pressure]];
304
if ([view respondsToSelector:sel])
305
[view performSelector:sel withObject:mouseEvent];
310
switch ([event type]) {
312
sel = @selector(keyDown:);
315
sel = @selector(keyUp:);
318
sel = @selector(flagsChanged:);
321
sel = @selector(scrollWheel:);
328
if ([view respondsToSelector:sel])
329
[view performSelector:sel withObject:event];
335
void GlobalShortcutMac::run() {
336
loop = CFRunLoopGetCurrent();
337
CFRunLoopSourceRef src = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, port, 0);
338
CFRunLoopAddSource(loop, src, kCFRunLoopCommonModes);
342
void GlobalShortcutMac::needRemap() {
346
bool GlobalShortcutMac::handleModButton(const CGEventFlags newmask) {
348
bool suppress = false;
350
#define MOD_CHANGED(mask, btn) do { \
351
if ((newmask & mask) != (modmask & mask)) { \
352
down = newmask & mask; \
353
suppress = handleButton(MOD_OFFSET+btn, down); \
358
MOD_CHANGED(kCGEventFlagMaskAlphaShift, 0);
359
MOD_CHANGED(kCGEventFlagMaskShift, 1);
360
MOD_CHANGED(kCGEventFlagMaskControl, 2);
361
MOD_CHANGED(kCGEventFlagMaskAlternate, 3);
362
MOD_CHANGED(kCGEventFlagMaskCommand, 4);
363
MOD_CHANGED(kCGEventFlagMaskHelp, 5);
364
MOD_CHANGED(kCGEventFlagMaskSecondaryFn, 6);
365
MOD_CHANGED(kCGEventFlagMaskNumericPad, 7);
368
QString GlobalShortcutMac::translateMouseButton(const unsigned int keycode) const {
369
return QString("Mouse Button %1").arg(keycode-MOUSE_OFFSET+1);
372
QString GlobalShortcutMac::translateModifierKey(const unsigned int keycode) const {
373
unsigned int key = keycode - MOD_OFFSET;
376
return QString("Caps Lock");
378
return QString("Shift");
380
return QString("Control");
382
return QString("Alt/Option");
384
return QString("Command");
386
return QString("Help");
388
return QString("Fn");
390
return QString("Num Lock");
392
return QString("Modifier %1").arg(key);
395
QString GlobalShortcutMac::translateKeyName(const unsigned int keycode) const {
397
UniCharCount len = 64;
398
UniChar unicodeString[len];
403
OSStatus err = UCKeyTranslate(kbdLayout, static_cast<UInt16>(keycode),
404
kUCKeyActionDisplay, 0, LMGetKbdType(),
405
kUCKeyTranslateNoDeadKeysBit, &junk,
406
len, &len, unicodeString);
411
switch (unicodeString[0]) {
413
return QString("Tab");
415
return QString("Enter");
417
return QString("Backspace");
419
return QString("Escape");
421
return QString("Space");
423
return QString("Left");
425
return QString("Right");
427
return QString("Up");
429
return QString("Down");
432
if (unicodeString[0] < ' ') {
433
qWarning("GlobalShortcutMac: Unknown translation for keycode %u: %u", keycode, unicodeString[0]);
438
return QString(reinterpret_cast<const QChar *>(unicodeString), len).toUpper();
441
QString GlobalShortcutMac::buttonName(const QVariant &v) {
443
unsigned int key = v.toUInt(&ok);
447
if (key >= MOUSE_OFFSET)
448
return translateMouseButton(key);
449
else if (key >= MOD_OFFSET)
450
return translateModifierKey(key);
452
QString str = translateKeyName(key);
457
return QString("Keycode %1").arg(key);
460
bool GlobalShortcutMac::canSuppress() {