~ubuntu-branches/ubuntu/raring/mumble/raring

« back to all changes in this revision

Viewing changes to src/mumble/GlobalShortcut_macx.mm

  • Committer: Bazaar Package Importer
  • Author(s): Thorvald Natvig, Patrick Matthäi, Thorvald Natvig
  • Date: 2011-02-19 22:58:58 UTC
  • mfrom: (9.1.15 sid)
  • Revision ID: james.westby@ubuntu.com-20110219225858-0xlftrf4z1z4jt9e
Tags: 1.2.3-1
[ Patrick Matthäi ]
* Do not build with non existant libpulse-dev on hurd-i386.

[ Thorvald Natvig ]
* New upstream release.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* Copyright (C) 2005-2010, Thorvald Natvig <thorvald@natvig.com>
 
2
   Copyright (C) 2008-2009, Mikkel Krautz <mikkel@krautz.dk>
 
3
 
 
4
   All rights reserved.
 
5
 
 
6
   Redistribution and use in source and binary forms, with or without
 
7
   modification, are permitted provided that the following conditions
 
8
   are met:
 
9
 
 
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.
 
18
 
 
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.
 
30
*/
 
31
 
 
32
#include "GlobalShortcut_macx.h"
 
33
#ifndef COMPAT_CLIENT
 
34
#include "Overlay.h"
 
35
#endif
 
36
 
 
37
#import <AppKit/AppKit.h>
 
38
 
 
39
#define MOD_OFFSET   0x10000
 
40
#define MOUSE_OFFSET 0x20000
 
41
 
 
42
/*
 
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.
 
45
 */
 
46
 
 
47
GlobalShortcutMacInit::GlobalShortcutMacInit() : QObject(NULL) {
 
48
}
 
49
 
 
50
void GlobalShortcutMacInit::initialize() {
 
51
        if (!accessibilityApiEnabled())
 
52
                accessibilityDialog();
 
53
}
 
54
 
 
55
bool GlobalShortcutMacInit::accessibilityApiEnabled() const {
 
56
        return QFile::exists("/private/var/db/.AccessibilityAPIEnabled");
 
57
}
 
58
 
 
59
void GlobalShortcutMacInit::openPrefsPane(const QString &) const {
 
60
        system("open /Applications/System\\ Preferences.app/ /System/Library/PreferencePanes/UniversalAccessPref.prefPane/");
 
61
}
 
62
 
 
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);
 
69
 
 
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 &)));
 
73
 
 
74
        mb.exec();
 
75
}
 
76
 
 
77
static GlobalShortcutMacInit gsminit;
 
78
 
 
79
/* --- */
 
80
 
 
81
GlobalShortcutEngine *GlobalShortcutEngine::platformInit() {
 
82
        return new GlobalShortcutMac();
 
83
}
 
84
 
 
85
CGEventRef GlobalShortcutMac::callback(CGEventTapProxy proxy, CGEventType type,
 
86
                                       CGEventRef event, void *udata) {
 
87
        GlobalShortcutMac *gs = reinterpret_cast<GlobalShortcutMac *>(udata);
 
88
        unsigned int keycode;
 
89
        bool suppress = false;
 
90
        bool forward = false;
 
91
        bool down = false;
 
92
        int64_t repeat = 0;
 
93
 
 
94
        switch (type) {
 
95
                case kCGEventLeftMouseDown:
 
96
                case kCGEventRightMouseDown:
 
97
                case kCGEventOtherMouseDown:
 
98
                        down = true;
 
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 :-) */
 
105
                        if (keycode == 0)
 
106
                                suppress = false;
 
107
#ifndef COMPAT_CLIENT
 
108
                        forward = !suppress;
 
109
#endif
 
110
                        break;
 
111
                }
 
112
 
 
113
#ifndef COMPAT_CLIENT
 
114
                case kCGEventMouseMoved:
 
115
                case kCGEventLeftMouseDragged:
 
116
                case kCGEventRightMouseDragged:
 
117
                case kCGEventOtherMouseDragged: {
 
118
                        if (g.ocIntercept) {
 
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);
 
124
                                forward = true;
 
125
                        }
 
126
                        break;
 
127
                }
 
128
 
 
129
                case kCGEventScrollWheel:
 
130
                        forward = true;
 
131
                        break;
 
132
#endif
 
133
 
 
134
                case kCGEventKeyDown:
 
135
                        down = true;
 
136
                case kCGEventKeyUp:
 
137
                        repeat = CGEventGetIntegerValueField(event, kCGKeyboardEventAutorepeat);
 
138
                        if (! repeat) {
 
139
                                keycode = static_cast<unsigned int>(CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode));
 
140
                                suppress = gs->handleButton(keycode, down);
 
141
                        }
 
142
                        forward = true;
 
143
                        break;
 
144
 
 
145
                case kCGEventFlagsChanged:
 
146
                        suppress = gs->handleModButton(CGEventGetFlags(event));
 
147
                        forward = !suppress;
 
148
                        break;
 
149
 
 
150
                case kCGEventTapDisabledByTimeout:
 
151
                        qWarning("GlobalShortcutMac: EventTap disabled by timeout. Re-enabling.");
 
152
                        /*
 
153
                         * On Snow Leopard, we get this event type quite often. It disables our event
 
154
                         * tap completely. Possible Apple bug.
 
155
                         *
 
156
                         * For now, simply call CGEventTapEnable() to enable our event tap again.
 
157
                         *
 
158
                         * See: http://lists.apple.com/archives/quartz-dev/2009/Sep/msg00007.html
 
159
                         */
 
160
                        CGEventTapEnable(gs->port, true);
 
161
                        break;
 
162
                case kCGEventTapDisabledByUserInput:
 
163
                        qWarning("GlobalShortcutMac: EventTap disabled by user input.");
 
164
                        break;
 
165
 
 
166
                default:
 
167
                        break;
 
168
        }
 
169
 
 
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));
 
175
                        [pool release];
 
176
                        return NULL;
 
177
                }
 
178
#endif
 
179
 
 
180
        return suppress ? NULL : event;
 
181
}
 
182
 
 
183
GlobalShortcutMac::GlobalShortcutMac() : modmask(0) {
 
184
#ifndef QT_NO_DEBUG
 
185
        qWarning("GlobalShortcutMac: Debug build detected. Disabling shortcut engine.");
 
186
        return;
 
187
#endif
 
188
 
 
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,
 
206
                                this);
 
207
 
 
208
        if (! port) {
 
209
                qWarning("GlobalShortcutMac: Unable to create EventTap. Global Shortcuts will not be available.");
 
210
                return;
 
211
        }
 
212
 
 
213
        kbdLayout = NULL;
 
214
 
 
215
#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1050
 
216
        if (TISCopyCurrentKeyboardInputSource && TISGetInputSourceProperty) {
 
217
                TISInputSourceRef inputSource = TISCopyCurrentKeyboardInputSource();
 
218
                if (inputSource) {
 
219
                        CFDataRef data = static_cast<CFDataRef>(TISGetInputSourceProperty(inputSource, kTISPropertyUnicodeKeyLayoutData));
 
220
                        if (data)
 
221
                                kbdLayout = reinterpret_cast<UCKeyboardLayout *>(const_cast<UInt8 *>(CFDataGetBytePtr(data)));
 
222
                }
 
223
        }
 
224
#endif
 
225
#ifndef __LP64__
 
226
        if (! kbdLayout) {
 
227
                SInt16 currentKeyScript = GetScriptManagerVariable(smKeyScript);
 
228
                SInt16 lastKeyLayoutID = GetScriptVariable(currentKeyScript, smScriptKeys);
 
229
                Handle handle = GetResource('uchr', lastKeyLayoutID);
 
230
                if (handle)
 
231
                        kbdLayout = reinterpret_cast<UCKeyboardLayout *>(*handle);
 
232
        }
 
233
#endif
 
234
        if (! kbdLayout)
 
235
                qWarning("GlobalShortcutMac: No keyboard layout mapping available. Unable to perform key translation.");
 
236
 
 
237
        start(QThread::TimeCriticalPriority);
 
238
}
 
239
 
 
240
GlobalShortcutMac::~GlobalShortcutMac() {
 
241
#ifndef QT_NO_DEBUG
 
242
        return;
 
243
#endif
 
244
        CFRunLoopStop(loop);
 
245
        loop = NULL;
 
246
        wait();
 
247
}
 
248
 
 
249
void GlobalShortcutMac::forwardEvent(void *evt) {
 
250
        NSEvent *event = (NSEvent *)evt;
 
251
        SEL sel = nil;
 
252
 
 
253
        if (! g.ocIntercept)
 
254
                return;
 
255
 
 
256
        QWidget *vp = g.ocIntercept->qgv.viewport();
 
257
        NSView *view = (NSView *) vp->winId();
 
258
 
 
259
        switch ([event type]) {
 
260
                case NSLeftMouseDown:
 
261
                        sel = @selector(mouseDown:);
 
262
                        break;
 
263
                case NSLeftMouseUp:
 
264
                        sel = @selector(mouseUp:);
 
265
                        break;
 
266
                case NSLeftMouseDragged:
 
267
                        sel = @selector(mouseDragged:);
 
268
                        break;
 
269
                case NSRightMouseDown:
 
270
                        sel = @selector(rightMouseDown:);
 
271
                        break;
 
272
                case NSRightMouseUp:
 
273
                        sel = @selector(rightMouseUp:);
 
274
                        break;
 
275
                case NSRightMouseDragged:
 
276
                        sel = @selector(rightMouseDragged:);
 
277
                        break;
 
278
                case NSOtherMouseDown:
 
279
                        sel = @selector(otherMouseDown:);
 
280
                        break;
 
281
                case NSOtherMouseUp:
 
282
                        sel = @selector(otherMouseUp:);
 
283
                        break;
 
284
                case NSOtherMouseDragged:
 
285
                        sel = @selector(otherMouseDragged:);
 
286
                        break;
 
287
                case NSMouseEntered:
 
288
                        sel = @selector(mouseEntered:);
 
289
                        break;
 
290
                case NSMouseExited:
 
291
                        sel = @selector(mouseExited:);
 
292
                        break;
 
293
                case NSMouseMoved:
 
294
                        sel = @selector(mouseMoved:);
 
295
                        break;
 
296
        }
 
297
 
 
298
        if (sel) {
 
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];
 
306
                [event release];
 
307
                return;
 
308
        }
 
309
 
 
310
        switch ([event type]) {
 
311
                case NSKeyDown:
 
312
                        sel = @selector(keyDown:);
 
313
                        break;
 
314
                case NSKeyUp:
 
315
                        sel = @selector(keyUp:);
 
316
                        break;
 
317
                case NSFlagsChanged:
 
318
                        sel = @selector(flagsChanged:);
 
319
                        break;
 
320
                case NSScrollWheel:
 
321
                        sel = @selector(scrollWheel:);
 
322
                        break;
 
323
                default:
 
324
                        break;
 
325
        }
 
326
 
 
327
        if (sel) {
 
328
                if ([view respondsToSelector:sel])
 
329
                                [view performSelector:sel withObject:event];
 
330
        }
 
331
 
 
332
        [event release];
 
333
}
 
334
 
 
335
void GlobalShortcutMac::run() {
 
336
        loop = CFRunLoopGetCurrent();
 
337
        CFRunLoopSourceRef src = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, port, 0);
 
338
        CFRunLoopAddSource(loop, src, kCFRunLoopCommonModes);
 
339
        CFRunLoopRun();
 
340
}
 
341
 
 
342
void GlobalShortcutMac::needRemap() {
 
343
        remap();
 
344
}
 
345
 
 
346
bool GlobalShortcutMac::handleModButton(const CGEventFlags newmask) {
 
347
        bool down;
 
348
        bool suppress = false;
 
349
 
 
350
#define MOD_CHANGED(mask, btn) do { \
 
351
            if ((newmask & mask) != (modmask & mask)) { \
 
352
                down = newmask & mask; \
 
353
                suppress = handleButton(MOD_OFFSET+btn, down); \
 
354
                modmask = newmask; \
 
355
                return suppress; \
 
356
            }} while (0)
 
357
 
 
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);
 
366
}
 
367
 
 
368
QString GlobalShortcutMac::translateMouseButton(const unsigned int keycode) const {
 
369
        return QString("Mouse Button %1").arg(keycode-MOUSE_OFFSET+1);
 
370
}
 
371
 
 
372
QString GlobalShortcutMac::translateModifierKey(const unsigned int keycode) const {
 
373
        unsigned int key = keycode - MOD_OFFSET;
 
374
        switch (key) {
 
375
                case 0:
 
376
                        return QString("Caps Lock");
 
377
                case 1:
 
378
                        return QString("Shift");
 
379
                case 2:
 
380
                        return QString("Control");
 
381
                case 3:
 
382
                        return QString("Alt/Option");
 
383
                case 4:
 
384
                        return QString("Command");
 
385
                case 5:
 
386
                        return QString("Help");
 
387
                case 6:
 
388
                        return QString("Fn");
 
389
                case 7:
 
390
                        return QString("Num Lock");
 
391
        }
 
392
        return QString("Modifier %1").arg(key);
 
393
}
 
394
 
 
395
QString GlobalShortcutMac::translateKeyName(const unsigned int keycode) const {
 
396
        UInt32 junk = 0;
 
397
        UniCharCount len = 64;
 
398
        UniChar unicodeString[len];
 
399
 
 
400
        if (! kbdLayout)
 
401
                return QString();
 
402
 
 
403
        OSStatus err = UCKeyTranslate(kbdLayout, static_cast<UInt16>(keycode),
 
404
                                      kUCKeyActionDisplay, 0, LMGetKbdType(),
 
405
                                      kUCKeyTranslateNoDeadKeysBit, &junk,
 
406
                                      len, &len, unicodeString);
 
407
        if (err != noErr)
 
408
                return QString();
 
409
 
 
410
        if (len == 1) {
 
411
                switch (unicodeString[0]) {
 
412
                        case '\t':
 
413
                                return QString("Tab");
 
414
                        case '\r':
 
415
                                return QString("Enter");
 
416
                        case '\b':
 
417
                                return QString("Backspace");
 
418
                        case '\e':
 
419
                                return QString("Escape");
 
420
                        case ' ':
 
421
                                return QString("Space");
 
422
                        case 28:
 
423
                                return QString("Left");
 
424
                        case 29:
 
425
                                return QString("Right");
 
426
                        case 30:
 
427
                                return QString("Up");
 
428
                        case 31:
 
429
                                return QString("Down");
 
430
                }
 
431
 
 
432
                if (unicodeString[0] < ' ') {
 
433
                        qWarning("GlobalShortcutMac: Unknown translation for keycode %u: %u", keycode, unicodeString[0]);
 
434
                        return QString();
 
435
                }
 
436
        }
 
437
 
 
438
        return QString(reinterpret_cast<const QChar *>(unicodeString), len).toUpper();
 
439
}
 
440
 
 
441
QString GlobalShortcutMac::buttonName(const QVariant &v) {
 
442
        bool ok;
 
443
        unsigned int key = v.toUInt(&ok);
 
444
        if (!ok)
 
445
                return QString();
 
446
 
 
447
        if (key >= MOUSE_OFFSET)
 
448
                return translateMouseButton(key);
 
449
        else if (key >= MOD_OFFSET)
 
450
                return translateModifierKey(key);
 
451
        else {
 
452
                QString str = translateKeyName(key);
 
453
                if (!str.isEmpty())
 
454
                        return str;
 
455
        }
 
456
 
 
457
        return QString("Keycode %1").arg(key);
 
458
}
 
459
 
 
460
bool GlobalShortcutMac::canSuppress() {
 
461
        return true;
 
462
}