~ubuntu-branches/debian/wheezy/vlc/wheezy

« back to all changes in this revision

Viewing changes to modules/gui/macosx/SPMediaKeyTap.m

  • Committer: Bazaar Package Importer
  • Author(s): Benjamin Drung, Benjamin Drung, Reinhard Tartler
  • Date: 2011-06-11 19:32:24 UTC
  • mfrom: (1.3.19 upstream)
  • Revision ID: james.westby@ubuntu.com-20110611193224-dcd1xbsw8d7or72y
Tags: 1.1.10-1
[ Benjamin Drung ]
* New upstream release.
  - Security: Fix XSPF integer overflow (CVE-2011-2194) (LP: #795410)
  - Improve .desktop file:
    - Add smb as supported protocol (Closes: #622879, LP: #737192)
    - add video/webm to supported MIME formats (LP: #769463)
  - Fix libdvdread errors while playing ogg files (Closes: #622935)
  - Support three channels in pulseaudio output plugin (LP: 743478)
  - PulseAudio output re-written due to unstability of the current one
    (LP: #743323)
  - Fix crashes (LP: #754497, #785979)
  - Qt: allow drag and drop of any URL, not just a local file (LP: #664030)
  - Fix libvlcplugin.so: undefined symbol: NPP_Initialize (LP: #722690)
* Refresh patches.
* Drop as-needed patch due to autoreconf run.
* Backport PulseAudio build fix.
* Add GNOME MIME types for Ogg Vorbis and Ogg Theora (Closes: #629619).
* Mention potcast support in package description (Closes: #488771).

[ Reinhard Tartler ]
* run autoreconf on the buildds
* Weaken dependencies on libschroedinger

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 Copyright (c) 2011, Joachim Bengtsson
 
3
 All rights reserved.
 
4
 
 
5
 Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
 
6
 
 
7
 * Neither the name of the organization nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
 
8
 
 
9
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
10
*/
 
11
 
 
12
// Copyright (c) 2010 Spotify AB
 
13
#import "SPMediaKeyTap.h"
 
14
#import "SPInvocationGrabbing.h" // https://gist.github.com/511181
 
15
 
 
16
@interface SPMediaKeyTap ()
 
17
-(BOOL)shouldInterceptMediaKeyEvents;
 
18
-(void)startWatchingAppSwitching;
 
19
-(void)stopWatchingAppSwitching;
 
20
-(void)eventTapThread;
 
21
@end
 
22
static SPMediaKeyTap *singleton = nil;
 
23
 
 
24
static pascal OSStatus appSwitched (EventHandlerCallRef nextHandler, EventRef evt, void* userData);
 
25
static pascal OSStatus appTerminated (EventHandlerCallRef nextHandler, EventRef evt, void* userData);
 
26
static CGEventRef tapEventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon);
 
27
 
 
28
 
 
29
// Inspired by http://gist.github.com/546311
 
30
 
 
31
@implementation SPMediaKeyTap
 
32
 
 
33
#pragma mark -
 
34
#pragma mark Setup and teardown
 
35
-(id)initWithDelegate:(id)delegate;
 
36
{
 
37
        _delegate = delegate;
 
38
        [self startWatchingAppSwitching];
 
39
        singleton = self;
 
40
        _mediaKeyAppList = [NSMutableArray new];
 
41
        return self;
 
42
}
 
43
-(void)dealloc;
 
44
{
 
45
        [self stopWatchingMediaKeys];
 
46
        [self stopWatchingAppSwitching];
 
47
        [_mediaKeyAppList release];
 
48
        [super dealloc];
 
49
}
 
50
 
 
51
-(void)startWatchingAppSwitching;
 
52
{
 
53
        // Listen to "app switched" event, so that we don't intercept media keys if we
 
54
        // weren't the last "media key listening" app to be active
 
55
        EventTypeSpec eventType = { kEventClassApplication, kEventAppFrontSwitched };
 
56
    OSStatus err = InstallApplicationEventHandler(NewEventHandlerUPP(appSwitched), 1, &eventType, self, &_app_switching_ref);
 
57
        assert(err == noErr);
 
58
        
 
59
        eventType.eventKind = kEventAppTerminated;
 
60
    err = InstallApplicationEventHandler(NewEventHandlerUPP(appTerminated), 1, &eventType, self, &_app_terminating_ref);
 
61
        assert(err == noErr);
 
62
}
 
63
-(void)stopWatchingAppSwitching;
 
64
{
 
65
        if(!_app_switching_ref) return;
 
66
        RemoveEventHandler(_app_switching_ref);
 
67
        _app_switching_ref = NULL;
 
68
}
 
69
 
 
70
-(void)startWatchingMediaKeys;{
 
71
        [self setShouldInterceptMediaKeyEvents:YES];
 
72
        
 
73
        // Add an event tap to intercept the system defined media key events
 
74
        _eventPort = CGEventTapCreate(kCGSessionEventTap,
 
75
                                                                  kCGHeadInsertEventTap,
 
76
                                                                  kCGEventTapOptionDefault,
 
77
                                                                  CGEventMaskBit(NX_SYSDEFINED),
 
78
                                                                  tapEventCallback,
 
79
                                                                  self);
 
80
        assert(_eventPort != NULL);
 
81
        
 
82
    _eventPortSource = CFMachPortCreateRunLoopSource(kCFAllocatorSystemDefault, _eventPort, 0);
 
83
        assert(_eventPortSource != NULL);
 
84
        
 
85
        // Let's do this in a separate thread so that a slow app doesn't lag the event tap
 
86
        [NSThread detachNewThreadSelector:@selector(eventTapThread) toTarget:self withObject:nil];
 
87
}
 
88
-(void)stopWatchingMediaKeys;
 
89
{
 
90
        // TODO<nevyn>: Shut down thread, remove event tap port and source
 
91
}
 
92
 
 
93
#pragma mark -
 
94
#pragma mark Accessors
 
95
 
 
96
+(BOOL)usesGlobalMediaKeyTap
 
97
{
 
98
        return YES;
 
99
#ifdef _DEBUG
 
100
        return NO;
 
101
#else
 
102
        // XXX(nevyn): MediaKey event tap doesn't work on 10.4, feel free to figure out why if you have the energy.
 
103
        return floor(NSAppKitVersionNumber) >= 949/*NSAppKitVersionNumber10_5*/;
 
104
#endif
 
105
}
 
106
 
 
107
+ (NSArray*)defaultMediaKeyUserBundleIdentifiers;
 
108
{
 
109
        return [NSArray arrayWithObjects:
 
110
                @"com.spotify.client",
 
111
                @"com.apple.iTunes",
 
112
                @"com.apple.QuickTimePlayerX",
 
113
                @"com.apple.quicktimeplayer",
 
114
                @"com.apple.iWork.Keynote",
 
115
                @"com.apple.iPhoto",
 
116
                @"org.videolan.vlc",
 
117
                @"com.apple.Aperture",
 
118
                @"com.plexsquared.Plex",
 
119
                @"com.soundcloud.desktop",
 
120
                @"com.macromedia.fireworks", // the tap messes up their mouse input
 
121
                nil
 
122
        ];
 
123
}
 
124
 
 
125
 
 
126
-(BOOL)shouldInterceptMediaKeyEvents;
 
127
{
 
128
        BOOL shouldIntercept = NO;
 
129
        @synchronized(self) {
 
130
                shouldIntercept = _shouldInterceptMediaKeyEvents;
 
131
        }
 
132
        return shouldIntercept;
 
133
}
 
134
 
 
135
-(void)pauseTapOnTapThread:(BOOL)yeahno;
 
136
{
 
137
        CGEventTapEnable(self->_eventPort, yeahno);
 
138
}
 
139
-(void)setShouldInterceptMediaKeyEvents:(BOOL)newSetting;
 
140
{
 
141
        BOOL oldSetting;
 
142
        @synchronized(self) {
 
143
                oldSetting = _shouldInterceptMediaKeyEvents;
 
144
                _shouldInterceptMediaKeyEvents = newSetting;
 
145
        }
 
146
        if(_tapThreadRL && oldSetting != newSetting) {
 
147
                id grab = [self grab];
 
148
                [grab pauseTapOnTapThread:newSetting];
 
149
                NSTimer *timer = [NSTimer timerWithTimeInterval:0 invocation:[grab invocation] repeats:NO];
 
150
                CFRunLoopAddTimer(_tapThreadRL, (CFRunLoopTimerRef)timer, kCFRunLoopCommonModes);
 
151
        }
 
152
}
 
153
 
 
154
#pragma mark 
 
155
#pragma mark -
 
156
#pragma mark Event tap callbacks
 
157
 
 
158
// Note: method called on background thread
 
159
 
 
160
static CGEventRef tapEventCallback2(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon)
 
161
{
 
162
        SPMediaKeyTap *self = refcon;
 
163
 
 
164
    if(type == kCGEventTapDisabledByTimeout) {
 
165
                NSLog(@"Media key event tap was disabled by timeout");
 
166
                CGEventTapEnable(self->_eventPort, TRUE);
 
167
                return event;
 
168
        } else if(type == kCGEventTapDisabledByUserInput) {
 
169
                // Was disabled manually by -[pauseTapOnTapThread]
 
170
                return event;
 
171
        }
 
172
        NSEvent *nsEvent = nil;
 
173
        @try {
 
174
                nsEvent = [NSEvent eventWithCGEvent:event];
 
175
        }
 
176
        @catch (NSException * e) {
 
177
                NSLog(@"Strange CGEventType: %d: %@", type, e);
 
178
                assert(0);
 
179
                return event;
 
180
        }
 
181
 
 
182
        if (type != NX_SYSDEFINED || [nsEvent subtype] != SPSystemDefinedEventMediaKeys)
 
183
                return event;
 
184
 
 
185
        int keyCode = (([nsEvent data1] & 0xFFFF0000) >> 16);
 
186
        if (keyCode != NX_KEYTYPE_PLAY && keyCode != NX_KEYTYPE_FAST && keyCode != NX_KEYTYPE_REWIND)
 
187
                return event;
 
188
 
 
189
        if (![self shouldInterceptMediaKeyEvents])
 
190
                return event;
 
191
        
 
192
        [nsEvent retain]; // matched in handleAndReleaseMediaKeyEvent:
 
193
        [self performSelectorOnMainThread:@selector(handleAndReleaseMediaKeyEvent:) withObject:nsEvent waitUntilDone:NO];
 
194
        
 
195
        return NULL;
 
196
}
 
197
 
 
198
static CGEventRef tapEventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon)
 
199
{
 
200
        NSAutoreleasePool *pool = [NSAutoreleasePool new];
 
201
        CGEventRef ret = tapEventCallback2(proxy, type, event, refcon);
 
202
        [pool drain];
 
203
        return ret;
 
204
}
 
205
 
 
206
 
 
207
// event will have been retained in the other thread
 
208
-(void)handleAndReleaseMediaKeyEvent:(NSEvent *)event {
 
209
        [event autorelease];
 
210
        
 
211
        [_delegate mediaKeyTap:self receivedMediaKeyEvent:event];
 
212
}
 
213
 
 
214
 
 
215
-(void)eventTapThread;
 
216
{
 
217
        _tapThreadRL = CFRunLoopGetCurrent();
 
218
        CFRunLoopAddSource(_tapThreadRL, _eventPortSource, kCFRunLoopCommonModes);
 
219
        CFRunLoopRun();
 
220
}
 
221
 
 
222
#pragma mark Task switching callbacks
 
223
 
 
224
NSString *kMediaKeyUsingBundleIdentifiersDefaultsKey = @"SPApplicationsNeedingMediaKeys";
 
225
 
 
226
 
 
227
-(void)mediaKeyAppListChanged;
 
228
{
 
229
        if([_mediaKeyAppList count] == 0) return;
 
230
        
 
231
        /*NSLog(@"--");
 
232
        int i = 0;
 
233
        for (NSValue *psnv in _mediaKeyAppList) {
 
234
                ProcessSerialNumber psn; [psnv getValue:&psn];
 
235
                NSDictionary *processInfo = [(id)ProcessInformationCopyDictionary(
 
236
                        &psn,
 
237
                        kProcessDictionaryIncludeAllInformationMask
 
238
                ) autorelease];
 
239
                NSString *bundleIdentifier = [processInfo objectForKey:(id)kCFBundleIdentifierKey];
 
240
                NSLog(@"%d: %@", i++, bundleIdentifier);
 
241
        }*/
 
242
        
 
243
    ProcessSerialNumber mySerial, topSerial;
 
244
        GetCurrentProcess(&mySerial);
 
245
        [[_mediaKeyAppList objectAtIndex:0] getValue:&topSerial];
 
246
 
 
247
        Boolean same;
 
248
        OSErr err = SameProcess(&mySerial, &topSerial, &same);
 
249
        [self setShouldInterceptMediaKeyEvents:(err == noErr && same)]; 
 
250
 
 
251
}
 
252
-(void)appIsNowFrontmost:(ProcessSerialNumber)psn;
 
253
{
 
254
        NSValue *psnv = [NSValue valueWithBytes:&psn objCType:@encode(ProcessSerialNumber)];
 
255
        
 
256
        NSDictionary *processInfo = [(id)ProcessInformationCopyDictionary(
 
257
                &psn,
 
258
                kProcessDictionaryIncludeAllInformationMask
 
259
        ) autorelease];
 
260
        NSString *bundleIdentifier = [processInfo objectForKey:(id)kCFBundleIdentifierKey];
 
261
 
 
262
        NSArray *whitelistIdentifiers = [[NSUserDefaults standardUserDefaults] arrayForKey:kMediaKeyUsingBundleIdentifiersDefaultsKey];
 
263
        if(![whitelistIdentifiers containsObject:bundleIdentifier]) return;
 
264
 
 
265
        [_mediaKeyAppList removeObject:psnv];
 
266
        [_mediaKeyAppList insertObject:psnv atIndex:0];
 
267
        [self mediaKeyAppListChanged];
 
268
}
 
269
-(void)appTerminated:(ProcessSerialNumber)psn;
 
270
{
 
271
        NSValue *psnv = [NSValue valueWithBytes:&psn objCType:@encode(ProcessSerialNumber)];
 
272
        [_mediaKeyAppList removeObject:psnv];
 
273
        [self mediaKeyAppListChanged];
 
274
}
 
275
 
 
276
static pascal OSStatus appSwitched (EventHandlerCallRef nextHandler, EventRef evt, void* userData)
 
277
{
 
278
        SPMediaKeyTap *self = (id)userData;
 
279
 
 
280
    ProcessSerialNumber newSerial;
 
281
    GetFrontProcess(&newSerial);
 
282
        
 
283
        [self appIsNowFrontmost:newSerial];
 
284
                
 
285
    return CallNextEventHandler(nextHandler, evt);
 
286
}
 
287
 
 
288
static pascal OSStatus appTerminated (EventHandlerCallRef nextHandler, EventRef evt, void* userData)
 
289
{
 
290
        SPMediaKeyTap *self = (id)userData;
 
291
        
 
292
        ProcessSerialNumber deadPSN;
 
293
 
 
294
        GetEventParameter(
 
295
                evt, 
 
296
                kEventParamProcessID, 
 
297
                typeProcessSerialNumber, 
 
298
                NULL, 
 
299
                sizeof(deadPSN), 
 
300
                NULL, 
 
301
                &deadPSN
 
302
        );
 
303
 
 
304
        
 
305
        [self appTerminated:deadPSN];
 
306
    return CallNextEventHandler(nextHandler, evt);
 
307
}
 
308
 
 
309
@end