~ubuntu-branches/ubuntu/precise/vlc/precise-proposed

« back to all changes in this revision

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

  • Committer: Package Import Robot
  • Author(s): Benjamin Drung
  • Date: 2012-03-24 01:33:03 UTC
  • mfrom: (1.1.46) (3.5.39 sid)
  • Revision ID: package-import@ubuntu.com-20120324013303-km51kpl9kixydb8g
Really add the preinst from Didier Raboud to vlc to drop it's doc directory
before unpacking a symlink to vlc-nox's over it. (Closes: #613121, #662217)

Show diffs side-by-side

added added

removed removed

Lines of Context:
34
34
#pragma mark Setup and teardown
35
35
-(id)initWithDelegate:(id)delegate;
36
36
{
37
 
        _delegate = delegate;
38
 
        [self startWatchingAppSwitching];
39
 
        singleton = self;
40
 
        _mediaKeyAppList = [NSMutableArray new];
41
 
        return self;
 
37
    _delegate = delegate;
 
38
    [self startWatchingAppSwitching];
 
39
    singleton = self;
 
40
    _mediaKeyAppList = [NSMutableArray new];
 
41
    return self;
42
42
}
43
43
-(void)dealloc;
44
44
{
45
 
        [self stopWatchingMediaKeys];
46
 
        [self stopWatchingAppSwitching];
47
 
        [_mediaKeyAppList release];
48
 
        [super dealloc];
 
45
    [self stopWatchingMediaKeys];
 
46
    [self stopWatchingAppSwitching];
 
47
    [_mediaKeyAppList release];
 
48
    [super dealloc];
49
49
}
50
50
 
51
51
-(void)startWatchingAppSwitching;
52
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 };
 
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
56
    OSStatus err = InstallApplicationEventHandler(NewEventHandlerUPP(appSwitched), 1, &eventType, self, &_app_switching_ref);
57
 
        assert(err == noErr);
58
 
        
59
 
        eventType.eventKind = kEventAppTerminated;
 
57
    assert(err == noErr);
 
58
 
 
59
    eventType.eventKind = kEventAppTerminated;
60
60
    err = InstallApplicationEventHandler(NewEventHandlerUPP(appTerminated), 1, &eventType, self, &_app_terminating_ref);
61
 
        assert(err == noErr);
 
61
    assert(err == noErr);
62
62
}
63
63
-(void)stopWatchingAppSwitching;
64
64
{
65
 
        if(!_app_switching_ref) return;
66
 
        RemoveEventHandler(_app_switching_ref);
67
 
        _app_switching_ref = NULL;
 
65
    if(!_app_switching_ref) return;
 
66
    RemoveEventHandler(_app_switching_ref);
 
67
    _app_switching_ref = NULL;
68
68
}
69
69
 
70
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
 
        
 
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
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];
 
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
87
}
88
88
-(void)stopWatchingMediaKeys;
89
89
{
90
 
        // TODO<nevyn>: Shut down thread, remove event tap port and source
 
90
    // TODO<nevyn>: Shut down thread, remove event tap port and source
91
91
}
92
92
 
93
93
#pragma mark -
95
95
 
96
96
+(BOOL)usesGlobalMediaKeyTap
97
97
{
98
 
        return YES;
 
98
    return YES;
99
99
#ifdef _DEBUG
100
 
        return NO;
 
100
    return NO;
101
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*/;
 
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
104
#endif
105
105
}
106
106
 
107
107
+ (NSArray*)defaultMediaKeyUserBundleIdentifiers;
108
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
 
        ];
 
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
123
}
124
124
 
125
125
 
126
126
-(BOOL)shouldInterceptMediaKeyEvents;
127
127
{
128
 
        BOOL shouldIntercept = NO;
129
 
        @synchronized(self) {
130
 
                shouldIntercept = _shouldInterceptMediaKeyEvents;
131
 
        }
132
 
        return shouldIntercept;
 
128
    BOOL shouldIntercept = NO;
 
129
    @synchronized(self) {
 
130
        shouldIntercept = _shouldInterceptMediaKeyEvents;
 
131
    }
 
132
    return shouldIntercept;
133
133
}
134
134
 
135
135
-(void)pauseTapOnTapThread:(BOOL)yeahno;
136
136
{
137
 
        CGEventTapEnable(self->_eventPort, yeahno);
 
137
    CGEventTapEnable(self->_eventPort, yeahno);
138
138
}
139
139
-(void)setShouldInterceptMediaKeyEvents:(BOOL)newSetting;
140
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
 
        }
 
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
152
}
153
153
 
154
154
#pragma mark 
159
159
 
160
160
static CGEventRef tapEventCallback2(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon)
161
161
{
162
 
        SPMediaKeyTap *self = refcon;
 
162
    SPMediaKeyTap *self = refcon;
163
163
 
164
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;
 
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
196
}
197
197
 
198
198
static CGEventRef tapEventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon)
199
199
{
200
 
        NSAutoreleasePool *pool = [NSAutoreleasePool new];
201
 
        CGEventRef ret = tapEventCallback2(proxy, type, event, refcon);
202
 
        [pool drain];
203
 
        return ret;
 
200
    NSAutoreleasePool *pool = [NSAutoreleasePool new];
 
201
    CGEventRef ret = tapEventCallback2(proxy, type, event, refcon);
 
202
    [pool drain];
 
203
    return ret;
204
204
}
205
205
 
206
206
 
207
207
// event will have been retained in the other thread
208
208
-(void)handleAndReleaseMediaKeyEvent:(NSEvent *)event {
209
 
        [event autorelease];
210
 
        
211
 
        [_delegate mediaKeyTap:self receivedMediaKeyEvent:event];
 
209
    [event autorelease];
 
210
 
 
211
    [_delegate mediaKeyTap:self receivedMediaKeyEvent:event];
212
212
}
213
213
 
214
214
 
215
215
-(void)eventTapThread;
216
216
{
217
 
        _tapThreadRL = CFRunLoopGetCurrent();
218
 
        CFRunLoopAddSource(_tapThreadRL, _eventPortSource, kCFRunLoopCommonModes);
219
 
        CFRunLoopRun();
 
217
    _tapThreadRL = CFRunLoopGetCurrent();
 
218
    CFRunLoopAddSource(_tapThreadRL, _eventPortSource, kCFRunLoopCommonModes);
 
219
    CFRunLoopRun();
220
220
}
221
221
 
222
222
#pragma mark Task switching callbacks
226
226
 
227
227
-(void)mediaKeyAppListChanged;
228
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
 
        
 
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
243
    ProcessSerialNumber mySerial, topSerial;
244
 
        GetCurrentProcess(&mySerial);
245
 
        [[_mediaKeyAppList objectAtIndex:0] getValue:&topSerial];
 
244
    GetCurrentProcess(&mySerial);
 
245
    [[_mediaKeyAppList objectAtIndex:0] getValue:&topSerial];
246
246
 
247
 
        Boolean same;
248
 
        OSErr err = SameProcess(&mySerial, &topSerial, &same);
249
 
        [self setShouldInterceptMediaKeyEvents:(err == noErr && same)]; 
 
247
    Boolean same;
 
248
    OSErr err = SameProcess(&mySerial, &topSerial, &same);
 
249
    [self setShouldInterceptMediaKeyEvents:(err == noErr && same)];
250
250
 
251
251
}
252
252
-(void)appIsNowFrontmost:(ProcessSerialNumber)psn;
253
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];
 
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
268
}
269
269
-(void)appTerminated:(ProcessSerialNumber)psn;
270
270
{
271
 
        NSValue *psnv = [NSValue valueWithBytes:&psn objCType:@encode(ProcessSerialNumber)];
272
 
        [_mediaKeyAppList removeObject:psnv];
273
 
        [self mediaKeyAppListChanged];
 
271
    NSValue *psnv = [NSValue valueWithBytes:&psn objCType:@encode(ProcessSerialNumber)];
 
272
    [_mediaKeyAppList removeObject:psnv];
 
273
    [self mediaKeyAppListChanged];
274
274
}
275
275
 
276
276
static pascal OSStatus appSwitched (EventHandlerCallRef nextHandler, EventRef evt, void* userData)
277
277
{
278
 
        SPMediaKeyTap *self = (id)userData;
 
278
    SPMediaKeyTap *self = (id)userData;
279
279
 
280
280
    ProcessSerialNumber newSerial;
281
281
    GetFrontProcess(&newSerial);
282
 
        
283
 
        [self appIsNowFrontmost:newSerial];
284
 
                
 
282
 
 
283
    [self appIsNowFrontmost:newSerial];
 
284
 
285
285
    return CallNextEventHandler(nextHandler, evt);
286
286
}
287
287
 
288
288
static pascal OSStatus appTerminated (EventHandlerCallRef nextHandler, EventRef evt, void* userData)
289
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];
 
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
306
    return CallNextEventHandler(nextHandler, evt);
307
307
}
308
308