Commit f83ba044 authored by Felix Paul Kühne's avatar Felix Paul Kühne

macosx: fixed media key support on OS X 10.6+

Note that this rework changes the current behavior: if VLC is in the background, the keys still control it, however if you start another key user (such as iTunes, QTP, etc.), it won't interfere and control the other app. That's why macosx-mediakeys-background was removed.

Thanks to Spotify for releasing their code.
parent 2f57ccdf
......@@ -375,6 +375,10 @@
CCD590F110E5B73200D39094 /* dialogProvider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = dialogProvider.m; path = ../../../modules/gui/macosx_dialog_provider/dialogProvider.m; sourceTree = SOURCE_ROOT; };
CCD590F210E5B76300D39094 /* VLCLoginPanel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VLCLoginPanel.h; path = ../../../modules/gui/macosx_dialog_provider/VLCLoginPanel.h; sourceTree = SOURCE_ROOT; };
CCD590F310E5B76300D39094 /* VLCLoginPanel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = VLCLoginPanel.m; path = ../../../modules/gui/macosx_dialog_provider/VLCLoginPanel.m; sourceTree = SOURCE_ROOT; };
CCF0777913659A8000AF19FD /* SPInvocationGrabbing.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SPInvocationGrabbing.h; path = ../../../modules/gui/macosx/SPInvocationGrabbing.h; sourceTree = "<group>"; };
CCF0777A13659A8000AF19FD /* SPInvocationGrabbing.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SPInvocationGrabbing.m; path = ../../../modules/gui/macosx/SPInvocationGrabbing.m; sourceTree = "<group>"; };
CCF0777B13659A8000AF19FD /* SPMediaKeyTap.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SPMediaKeyTap.h; path = ../../../modules/gui/macosx/SPMediaKeyTap.h; sourceTree = "<group>"; };
CCF0777C13659A8000AF19FD /* SPMediaKeyTap.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SPMediaKeyTap.m; path = ../../../modules/gui/macosx/SPMediaKeyTap.m; sourceTree = "<group>"; };
CCF11B530F8BFB520088542D /* English */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = English; path = Resources/English.lproj/ErrorPanel.nib; sourceTree = "<group>"; };
DC769AB7085DF0DB001A838D /* wizard.m */ = {isa = PBXFileReference; fileEncoding = 5; indentWidth = 4; lastKnownFileType = sourcecode.c.objc; name = wizard.m; path = ../../../modules/gui/macosx/wizard.m; sourceTree = SOURCE_ROOT; tabWidth = 4; usesTabs = 0; };
DC769AB8085DF0DB001A838D /* wizard.h */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.h; name = wizard.h; path = ../../../modules/gui/macosx/wizard.h; sourceTree = SOURCE_ROOT; };
......@@ -510,6 +514,10 @@
CC62B9090FC5DB9D0077BB8C /* sidebarview.m */,
CC62B90A0FC5DB9D0077BB8C /* sidestatusview.h */,
CC62B90B0FC5DB9D0077BB8C /* sidestatusview.m */,
CCF0777913659A8000AF19FD /* SPInvocationGrabbing.h */,
CCF0777A13659A8000AF19FD /* SPInvocationGrabbing.m */,
CCF0777B13659A8000AF19FD /* SPMediaKeyTap.h */,
CCF0777C13659A8000AF19FD /* SPMediaKeyTap.m */,
);
name = Classes;
sourceTree = "<group>";
......@@ -815,6 +823,7 @@
isa = PBXProject;
buildConfigurationList = C2F2A6EA09588F1B00018C74 /* Build configuration list for PBXProject "vlc" */;
compatibilityVersion = "Xcode 3.1";
developmentRegion = English;
hasScannedForEncodings = 1;
knownRegions = (
English,
......
......@@ -6,6 +6,10 @@ LIBTOOL=@LIBTOOL@ --tag=CC
SOURCES_macosx = \
AppleRemote.h \
AppleRemote.m \
SPInvocationGrabbing.h \
SPInvocationGrabbing.m \
SPMediaKeyTap.h \
SPMediaKeyTap.m \
about.h \
about.m \
applescript.h \
......
/*
Copyright (c) 2011, Joachim Bengtsson
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* 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.
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.
*/
#import <Foundation/Foundation.h>
@interface SPInvocationGrabber : NSObject {
id _object;
NSInvocation *_invocation;
int frameCount;
char **frameStrings;
BOOL backgroundAfterForward;
BOOL onMainAfterForward;
BOOL waitUntilDone;
}
-(id)initWithObject:(id)obj;
-(id)initWithObject:(id)obj stacktraceSaving:(BOOL)saveStack;
@property (readonly, retain, nonatomic) id object;
@property (readonly, retain, nonatomic) NSInvocation *invocation;
@property BOOL backgroundAfterForward;
@property BOOL onMainAfterForward;
@property BOOL waitUntilDone;
-(void)invoke; // will release object and invocation
-(void)printBacktrace;
-(void)saveBacktrace;
@end
@interface NSObject (SPInvocationGrabbing)
-(id)grab;
-(id)invokeAfter:(NSTimeInterval)delta;
-(id)nextRunloop;
-(id)inBackground;
-(id)onMainAsync:(BOOL)async;
@end
/*
Copyright (c) 2011, Joachim Bengtsson
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* 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.
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.
*/
#import "SPInvocationGrabbing.h"
#import <execinfo.h>
#pragma mark Invocation grabbing
@interface SPInvocationGrabber ()
@property (readwrite, retain, nonatomic) id object;
@property (readwrite, retain, nonatomic) NSInvocation *invocation;
@end
@implementation SPInvocationGrabber
- (id)initWithObject:(id)obj;
{
return [self initWithObject:obj stacktraceSaving:YES];
}
-(id)initWithObject:(id)obj stacktraceSaving:(BOOL)saveStack;
{
self.object = obj;
if(saveStack)
[self saveBacktrace];
return self;
}
-(void)dealloc;
{
free(frameStrings);
self.object = nil;
self.invocation = nil;
[super dealloc];
}
@synthesize invocation = _invocation, object = _object;
@synthesize backgroundAfterForward, onMainAfterForward, waitUntilDone;
- (void)runInBackground;
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
@try {
[self invoke];
}
@finally {
[pool drain];
}
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
[anInvocation retainArguments];
anInvocation.target = _object;
self.invocation = anInvocation;
if(backgroundAfterForward)
[NSThread detachNewThreadSelector:@selector(runInBackground) toTarget:self withObject:nil];
else if(onMainAfterForward)
[self performSelectorOnMainThread:@selector(invoke) withObject:nil waitUntilDone:waitUntilDone];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)inSelector {
NSMethodSignature *signature = [super methodSignatureForSelector:inSelector];
if (signature == NULL)
signature = [_object methodSignatureForSelector:inSelector];
return signature;
}
- (void)invoke;
{
@try {
[_invocation invoke];
}
@catch (NSException * e) {
NSLog(@"SPInvocationGrabber's target raised %@:\n\t%@\nInvocation was originally scheduled at:", e.name, e);
[self printBacktrace];
printf("\n");
[e raise];
}
self.invocation = nil;
self.object = nil;
}
-(void)saveBacktrace;
{
void *backtraceFrames[128];
frameCount = backtrace(&backtraceFrames[0], 128);
frameStrings = backtrace_symbols(&backtraceFrames[0], frameCount);
}
-(void)printBacktrace;
{
for(int x = 3; x < frameCount; x++) {
if(frameStrings[x] == NULL) { break; }
printf("%s\n", frameStrings[x]);
}
}
@end
@implementation NSObject (SPInvocationGrabbing)
-(id)grab;
{
return [[[SPInvocationGrabber alloc] initWithObject:self] autorelease];
}
-(id)invokeAfter:(NSTimeInterval)delta;
{
id grabber = [self grab];
[NSTimer scheduledTimerWithTimeInterval:delta target:grabber selector:@selector(invoke) userInfo:nil repeats:NO];
return grabber;
}
- (id)nextRunloop;
{
return [self invokeAfter:0];
}
-(id)inBackground;
{
SPInvocationGrabber *grabber = [self grab];
grabber.backgroundAfterForward = YES;
return grabber;
}
-(id)onMainAsync:(BOOL)async;
{
SPInvocationGrabber *grabber = [self grab];
grabber.onMainAfterForward = YES;
grabber.waitUntilDone = !async;
return grabber;
}
@end
/*
Copyright (c) 2011, Joachim Bengtsson
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* 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.
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.
*/
#import <Cocoa/Cocoa.h>
#import <IOKit/hidsystem/ev_keymap.h>
#import <Carbon/Carbon.h>
// http://overooped.com/post/2593597587/mediakeys
#define SPSystemDefinedEventMediaKeys 8
@interface SPMediaKeyTap : NSObject {
EventHandlerRef _app_switching_ref;
EventHandlerRef _app_terminating_ref;
CFMachPortRef _eventPort;
CFRunLoopSourceRef _eventPortSource;
CFRunLoopRef _tapThreadRL;
BOOL _shouldInterceptMediaKeyEvents;
id _delegate;
// The app that is frontmost in this list owns media keys
NSMutableArray *_mediaKeyAppList;
}
+ (NSArray*)defaultMediaKeyUserBundleIdentifiers;
-(id)initWithDelegate:(id)delegate;
+(BOOL)usesGlobalMediaKeyTap;
-(void)startWatchingMediaKeys;
-(void)stopWatchingMediaKeys;
-(void)handleAndReleaseMediaKeyEvent:(NSEvent *)event;
-(void)setShouldInterceptMediaKeyEvents:(BOOL)newSetting;
@end
@interface NSObject (SPMediaKeyTapDelegate)
-(void)mediaKeyTap:(SPMediaKeyTap*)keyTap receivedMediaKeyEvent:(NSEvent*)event;
@end
extern NSString *kMediaKeyUsingBundleIdentifiersDefaultsKey;
/*
Copyright (c) 2011, Joachim Bengtsson
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* 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.
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.
*/
// Copyright (c) 2010 Spotify AB
#import "SPMediaKeyTap.h"
#import "SPInvocationGrabbing.h" // https://gist.github.com/511181
@interface SPMediaKeyTap ()
-(BOOL)shouldInterceptMediaKeyEvents;
-(void)startWatchingAppSwitching;
-(void)stopWatchingAppSwitching;
-(void)eventTapThread;
@end
static SPMediaKeyTap *singleton = nil;
static pascal OSStatus appSwitched (EventHandlerCallRef nextHandler, EventRef evt, void* userData);
static pascal OSStatus appTerminated (EventHandlerCallRef nextHandler, EventRef evt, void* userData);
static CGEventRef tapEventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon);
// Inspired by http://gist.github.com/546311
@implementation SPMediaKeyTap
#pragma mark -
#pragma mark Setup and teardown
-(id)initWithDelegate:(id)delegate;
{
_delegate = delegate;
[self startWatchingAppSwitching];
singleton = self;
_mediaKeyAppList = [NSMutableArray new];
return self;
}
-(void)dealloc;
{
[self stopWatchingMediaKeys];
[self stopWatchingAppSwitching];
[_mediaKeyAppList release];
[super dealloc];
}
-(void)startWatchingAppSwitching;
{
// Listen to "app switched" event, so that we don't intercept media keys if we
// weren't the last "media key listening" app to be active
EventTypeSpec eventType = { kEventClassApplication, kEventAppFrontSwitched };
OSStatus err = InstallApplicationEventHandler(NewEventHandlerUPP(appSwitched), 1, &eventType, self, &_app_switching_ref);
assert(err == noErr);
eventType.eventKind = kEventAppTerminated;
err = InstallApplicationEventHandler(NewEventHandlerUPP(appTerminated), 1, &eventType, self, &_app_terminating_ref);
assert(err == noErr);
}
-(void)stopWatchingAppSwitching;
{
if(!_app_switching_ref) return;
RemoveEventHandler(_app_switching_ref);
_app_switching_ref = NULL;
}
-(void)startWatchingMediaKeys;{
[self setShouldInterceptMediaKeyEvents:YES];
// Add an event tap to intercept the system defined media key events
_eventPort = CGEventTapCreate(kCGSessionEventTap,
kCGHeadInsertEventTap,
kCGEventTapOptionDefault,
CGEventMaskBit(NX_SYSDEFINED),
tapEventCallback,
self);
assert(_eventPort != NULL);
_eventPortSource = CFMachPortCreateRunLoopSource(kCFAllocatorSystemDefault, _eventPort, 0);
assert(_eventPortSource != NULL);
// Let's do this in a separate thread so that a slow app doesn't lag the event tap
[NSThread detachNewThreadSelector:@selector(eventTapThread) toTarget:self withObject:nil];
}
-(void)stopWatchingMediaKeys;
{
// TODO<nevyn>: Shut down thread, remove event tap port and source
}
#pragma mark -
#pragma mark Accessors
+(BOOL)usesGlobalMediaKeyTap
{
return YES;
#ifdef _DEBUG
return NO;
#else
// XXX(nevyn): MediaKey event tap doesn't work on 10.4, feel free to figure out why if you have the energy.
return floor(NSAppKitVersionNumber) >= 949/*NSAppKitVersionNumber10_5*/;
#endif
}
+ (NSArray*)defaultMediaKeyUserBundleIdentifiers;
{
return [NSArray arrayWithObjects:
@"com.spotify.client",
@"com.apple.iTunes",
@"com.apple.QuickTimePlayerX",
@"com.apple.quicktimeplayer",
@"com.apple.iWork.Keynote",
@"com.apple.iPhoto",
@"org.videolan.vlc",
@"com.apple.Aperture",
@"com.plexsquared.Plex",
@"com.soundcloud.desktop",
@"com.macromedia.fireworks", // the tap messes up their mouse input
nil
];
}
-(BOOL)shouldInterceptMediaKeyEvents;
{
BOOL shouldIntercept = NO;
@synchronized(self) {
shouldIntercept = _shouldInterceptMediaKeyEvents;
}
return shouldIntercept;
}
-(void)pauseTapOnTapThread:(BOOL)yeahno;
{
CGEventTapEnable(self->_eventPort, yeahno);
}
-(void)setShouldInterceptMediaKeyEvents:(BOOL)newSetting;
{
BOOL oldSetting;
@synchronized(self) {
oldSetting = _shouldInterceptMediaKeyEvents;
_shouldInterceptMediaKeyEvents = newSetting;
}
if(_tapThreadRL && oldSetting != newSetting) {
id grab = [self grab];
[grab pauseTapOnTapThread:newSetting];
NSTimer *timer = [NSTimer timerWithTimeInterval:0 invocation:[grab invocation] repeats:NO];
CFRunLoopAddTimer(_tapThreadRL, (CFRunLoopTimerRef)timer, kCFRunLoopCommonModes);
}
}
#pragma mark
#pragma mark -
#pragma mark Event tap callbacks
// Note: method called on background thread
static CGEventRef tapEventCallback2(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon)
{
SPMediaKeyTap *self = refcon;
if(type == kCGEventTapDisabledByTimeout) {
NSLog(@"Media key event tap was disabled by timeout");
CGEventTapEnable(self->_eventPort, TRUE);
return event;
} else if(type == kCGEventTapDisabledByUserInput) {
// Was disabled manually by -[pauseTapOnTapThread]
return event;
}
NSEvent *nsEvent = nil;
@try {
nsEvent = [NSEvent eventWithCGEvent:event];
}
@catch (NSException * e) {
NSLog(@"Strange CGEventType: %d: %@", type, e);
assert(0);
return event;
}
if (type != NX_SYSDEFINED || [nsEvent subtype] != SPSystemDefinedEventMediaKeys)
return event;
int keyCode = (([nsEvent data1] & 0xFFFF0000) >> 16);
if (keyCode != NX_KEYTYPE_PLAY && keyCode != NX_KEYTYPE_FAST && keyCode != NX_KEYTYPE_REWIND)
return event;
if (![self shouldInterceptMediaKeyEvents])
return event;
[nsEvent retain]; // matched in handleAndReleaseMediaKeyEvent:
[self performSelectorOnMainThread:@selector(handleAndReleaseMediaKeyEvent:) withObject:nsEvent waitUntilDone:NO];
return NULL;
}
static CGEventRef tapEventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon)
{
NSAutoreleasePool *pool = [NSAutoreleasePool new];
CGEventRef ret = tapEventCallback2(proxy, type, event, refcon);
[pool drain];
return ret;
}
// event will have been retained in the other thread
-(void)handleAndReleaseMediaKeyEvent:(NSEvent *)event {
[event autorelease];
[_delegate mediaKeyTap:self receivedMediaKeyEvent:event];
}
-(void)eventTapThread;
{
_tapThreadRL = CFRunLoopGetCurrent();
CFRunLoopAddSource(_tapThreadRL, _eventPortSource, kCFRunLoopCommonModes);
CFRunLoopRun();
}
#pragma mark Task switching callbacks
NSString *kMediaKeyUsingBundleIdentifiersDefaultsKey = @"SPApplicationsNeedingMediaKeys";
-(void)mediaKeyAppListChanged;
{
if([_mediaKeyAppList count] == 0) return;
/*NSLog(@"--");
int i = 0;
for (NSValue *psnv in _mediaKeyAppList) {
ProcessSerialNumber psn; [psnv getValue:&psn];
NSDictionary *processInfo = [(id)ProcessInformationCopyDictionary(
&psn,
kProcessDictionaryIncludeAllInformationMask
) autorelease];
NSString *bundleIdentifier = [processInfo objectForKey:(id)kCFBundleIdentifierKey];
NSLog(@"%d: %@", i++, bundleIdentifier);
}*/
ProcessSerialNumber mySerial, topSerial;
GetCurrentProcess(&mySerial);
[[_mediaKeyAppList objectAtIndex:0] getValue:&topSerial];
Boolean same;
OSErr err = SameProcess(&mySerial, &topSerial, &same);
[self setShouldInterceptMediaKeyEvents:(err == noErr && same)];
}
-(void)appIsNowFrontmost:(ProcessSerialNumber)psn;
{
NSValue *psnv = [NSValue valueWithBytes:&psn objCType:@encode(ProcessSerialNumber)];
NSDictionary *processInfo = [(id)ProcessInformationCopyDictionary(
&psn,
kProcessDictionaryIncludeAllInformationMask
) autorelease];
NSString *bundleIdentifier = [processInfo objectForKey:(id)kCFBundleIdentifierKey];
NSArray *whitelistIdentifiers = [[NSUserDefaults standardUserDefaults] arrayForKey:kMediaKeyUsingBundleIdentifiersDefaultsKey];
if(![whitelistIdentifiers containsObject:bundleIdentifier]) return;
[_mediaKeyAppList removeObject:psnv];
[_mediaKeyAppList insertObject:psnv atIndex:0];
[self mediaKeyAppListChanged];
}
-(void)appTerminated:(ProcessSerialNumber)psn;
{
NSValue *psnv = [NSValue valueWithBytes:&psn objCType:@encode(ProcessSerialNumber)];
[_mediaKeyAppList removeObject:psnv];
[self mediaKeyAppListChanged];
}
static pascal OSStatus appSwitched (EventHandlerCallRef nextHandler, EventRef evt, void* userData)
{
SPMediaKeyTap *self = (id)userData;
ProcessSerialNumber newSerial;
GetFrontProcess(&newSerial);
[self appIsNowFrontmost:newSerial];
return CallNextEventHandler(nextHandler, evt);
}
static pascal OSStatus appTerminated (EventHandlerCallRef nextHandler, EventRef evt, void* userData)
{
SPMediaKeyTap *self = (id)userData;
ProcessSerialNumber deadPSN;
GetEventParameter(
evt,
kEventParamProcessID,
typeProcessSerialNumber,
NULL,
sizeof(deadPSN),
NULL,
&deadPSN
);
[self appTerminated:deadPSN];
return CallNextEventHandler(nextHandler, evt);
}
@end
/*****************************************************************************
* controls.h: MacOS X interface module
*****************************************************************************
* Copyright (C) 2002-2009 the VideoLAN team
* Copyright (C) 2002-2011 the VideoLAN team
* $Id$
*
* Authors: Jon Lech Johansen <jon-vl@nanocrew.net>
......@@ -57,8 +57,6 @@
VLCFSPanel *o_fs_panel;
BOOL b_lockAspectRatio;
}
- (void)controlTintChanged;
- (id)voutView;
- (BOOL)aspectRatioIsLocked;
......
/*****************************************************************************
* intf.h: MacOS X interface module
*****************************************************************************
* Copyright (C) 2002-2009 the VideoLAN team
* Copyright (C) 2002-2011 the VideoLAN team
* $Id$
*
* Authors: Jon Lech Johansen <jon-vl@nanocrew.net>
......@@ -36,6 +36,7 @@
#include <vlc_input.h>
#include <Cocoa/Cocoa.h>
#import "SPMediaKeyTap.h" /* for the media key support */
/*****************************************************************************
* Local prototypes.
......@@ -338,6 +339,11 @@ struct intf_sys_t
AppleRemote * o_remote;
BOOL b_remote_button_hold; /* true as long as the user holds the left,right,plus or minus on the remote control */
/* media key support */
BOOL b_mediaKeySupport;
BOOL b_mediakeyJustJumped;
SPMediaKeyTap * o_mediaKeyController;
NSArray *o_usedHotkeys;
}
......@@ -346,8 +352,6 @@ struct intf_sys_t
- (intf_thread_t *)intf;
- (void)setIntf:(intf_thread_t *)p_mainintf;
- (void)controlTintChanged;
- (id)controls;
- (id)simplePreferences;
- (id)preferences;
......@@ -423,10 +427,13 @@ struct intf_sys_t
- (void)windowDidBecomeKey:(NSNotification *)o_notification;
- (void)mediaKeyTap:(SPMediaKeyTap*)keyTap receivedMediaKeyEvent:(NSEvent*)event;
@end
@interface VLCMain (Internal)
- (void)handlePortMessage:(NSPortMessage *)o_msg;
- (void)resetMediaKeyJump;
- (void)coreChangedMediaKeySupportSetting: (NSNotification *)o_notification;
@end
/*****************************************************************************
......@@ -435,14 +442,5 @@ struct intf_sys_t
@interface VLCApplication : NSApplication
{
BOOL b_justJumped;
BOOL b_mediaKeySupport;
BOOL b_activeInBackground;
BOOL b_active;
}
- (void)coreChangedMediaKeySupportSetting: (NSNotification *)o_notification;
- (void)sendEvent: (NSEvent*)event;
- (void)resetJump;
@end
/*****************************************************************************
* intf.m: MacOS X interface module
*****************************************************************************
* Copyright (C) 2002-2009 the VideoLAN team
* Copyright (C) 2002-2011 the VideoLAN team
* $Id$
*
* Authors: Jon Lech Johansen <jon-vl@nanocrew.net>
......@@ -56,7 +56,6 @@
#import "simple_prefs.h"
#import <AddressBook/AddressBook.h> /* for crashlog send mechanism */
#import <IOKit/hidsystem/ev_keymap.h> /* for the media key support */
#import <Sparkle/Sparkle.h> /* we're the update delegate */
/*****************************************************************************
......@@ -364,7 +363,6 @@ static VLCMain *_o_sharedMainInstance = nil;
object: @"VLCEyeTVSupport"
userInfo: NULL
deliverImmediately: YES];
return _o_sharedMainInstance;
}
......@@ -547,6 +545,16 @@ static VLCMain *_o_sharedMainInstance = nil;
[o_remote setClickCountEnabledButtons: kRemoteButtonPlay];
[o_remote setDelegate: _o_sharedMainInstance];
/* init media key support */
o_mediaKeyController = [[SPMediaKeyTap alloc] initWithDelegate:self];
b_mediaKeySupport = config_GetInt( VLCIntf, "macosx-mediakeys" );
[o_mediaKeyController startWatchingMediaKeys];
[o_mediaKeyController setShouldInterceptMediaKeyEvents:b_mediaKeySupport];
[[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(coreChangedMediaKeySupportSetting:) name: @"VLCMediaKeySupportSettingChanged" object: nil];
[[NSUserDefaults standardUserDefaults] registerDefaults:[NSDictionary dictionaryWithObjectsAndKeys:
[SPMediaKeyTap defaultMediaKeyUserBundleIdentifiers], kMediaKeyUsingBundleIdentifiersDefaultsKey,
nil]];
/* yeah, we are done */
nib_main_loaded = TRUE;
}
......@@ -965,6 +973,53 @@ static NSString * VLCToolbarMediaControl = @"VLCToolbarMediaControl";
return toolbarItem;
}
#pragma mark -
#pragma mark Media Key support
-(void)mediaKeyTap:(SPMediaKeyTap*)keyTap receivedMediaKeyEvent:(NSEvent*)event
{
if( b_mediaKeySupport )
{
assert([event type] == NSSystemDefined && [event subtype] == SPSystemDefinedEventMediaKeys);
int keyCode = (([event data1] & 0xFFFF0000) >> 16);
int keyFlags = ([event data1] & 0x0000FFFF);
int keyState = (((keyFlags & 0xFF00) >> 8)) == 0xA;
int keyRepeat = (keyFlags & 0x1);
if( keyCode == NX_KEYTYPE_PLAY && keyState == 0 )
var_SetInteger( p_intf->p_libvlc, "key-action", ACTIONID_PLAY_PAUSE );
if( keyCode == NX_KEYTYPE_FAST && !b_mediakeyJustJumped )
{
if( keyState == 0 && keyRepeat == 0 )
var_SetInteger( p_intf->p_libvlc, "key-action", ACTIONID_NEXT );
else if( keyRepeat == 1 )
{
var_SetInteger( p_intf->p_libvlc, "key-action", ACTIONID_JUMP_FORWARD_SHORT );
b_mediakeyJustJumped = YES;
[self performSelector:@selector(resetMediaKeyJump)
withObject: NULL
afterDelay:0.25];
}
}
if( keyCode == NX_KEYTYPE_REWIND && !b_mediakeyJustJumped )
{
if( keyState == 0 && keyRepeat == 0 )
var_SetInteger( p_intf->p_libvlc, "key-action", ACTIONID_PREV );
else if( keyRepeat == 1 )
{
var_SetInteger( p_intf->p_libvlc, "key-action", ACTIONID_JUMP_BACKWARD_SHORT );
b_mediakeyJustJumped = YES;
[self performSelector:@selector(resetMediaKeyJump)
withObject: NULL
afterDelay:0.25];
}
}
}
}
#pragma mark -
#pragma mark Other notification
......@@ -2823,103 +2878,23 @@ end:
[o_inv invoke];
[o_lock unlockWithCondition: 1];
}
@end
/*****************************************************************************
* VLCApplication interface
* exclusively used to implement media key support on Al Apple keyboards
* b_justJumped is required as the keyboard send its events faster than
* the user can actually jump through his media
*****************************************************************************/
@implementation VLCApplication
- (void)awakeFromNib
- (void)resetMediaKeyJump
{
b_active = b_mediaKeySupport = config_GetInt( VLCIntf, "macosx-mediakeys" );
b_activeInBackground = config_GetInt( VLCIntf, "macosx-mediakeys-background" );
[[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(coreChangedMediaKeySupportSetting:) name: @"VLCMediaKeySupportSettingChanged" object: nil];
[[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(appGotActiveOrInactive:) name: @"NSApplicationDidBecomeActiveNotification" object: nil];
[[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(appGotActiveOrInactive:) name: @"NSApplicationWillResignActiveNotification" object: nil];
b_mediakeyJustJumped = NO;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver: self];
[super dealloc];
}
- (void)appGotActiveOrInactive: (NSNotification *)o_notification
{
if(( [[o_notification name] isEqualToString: @"NSApplicationWillResignActiveNotification"] && !b_activeInBackground ) || !b_mediaKeySupport)
b_active = NO;
else
b_active = YES;
}
- (void)coreChangedMediaKeySupportSetting: (NSNotification *)o_notification
{
b_active = b_mediaKeySupport = config_GetInt( VLCIntf, "macosx-mediakeys" );
b_activeInBackground = config_GetInt( VLCIntf, "macosx-mediakeys-background" );
b_mediaKeySupport = config_GetInt( VLCIntf, "macosx-mediakeys" );
[o_mediaKeyController setShouldInterceptMediaKeyEvents:b_mediaKeySupport];
}
@end
- (void)sendEvent: (NSEvent*)event
{
if( b_active )
{
if( [event type] == NSSystemDefined && [event subtype] == 8 )
{
int keyCode = (([event data1] & 0xFFFF0000) >> 16);
int keyFlags = ([event data1] & 0x0000FFFF);
int keyState = (((keyFlags & 0xFF00) >> 8)) == 0xA;
int keyRepeat = (keyFlags & 0x1);
if( keyCode == NX_KEYTYPE_PLAY && keyState == 0 )
var_SetInteger( VLCIntf->p_libvlc, "key-action", ACTIONID_PLAY_PAUSE );
if( keyCode == NX_KEYTYPE_FAST && !b_justJumped )
{
if( keyState == 0 && keyRepeat == 0 )
{
var_SetInteger( VLCIntf->p_libvlc, "key-action", ACTIONID_NEXT );
}
else if( keyRepeat == 1 )
{
var_SetInteger( VLCIntf->p_libvlc, "key-action", ACTIONID_JUMP_FORWARD_SHORT );
b_justJumped = YES;
[self performSelector:@selector(resetJump)
withObject: NULL
afterDelay:0.25];
}
}
if( keyCode == NX_KEYTYPE_REWIND && !b_justJumped )
{
if( keyState == 0 && keyRepeat == 0 )
{
var_SetInteger( VLCIntf->p_libvlc, "key-action", ACTIONID_PREV );
}
else if( keyRepeat == 1 )
{
var_SetInteger( VLCIntf->p_libvlc, "key-action", ACTIONID_JUMP_BACKWARD_SHORT );
b_justJumped = YES;
[self performSelector:@selector(resetJump)
withObject: NULL
afterDelay:0.25];
}
}
}
}
[super sendEvent: event];
}
- (void)resetJump
{
b_justJumped = NO;
}
/*****************************************************************************
* VLCApplication interface
*****************************************************************************/
@implementation VLCApplication
// when user selects the quit menu from dock it sends a terminate:
// but we need to send a stop: to properly exits libvlc.
// However, we are not able to change the action-method sent by this standard menu item.
......
/*****************************************************************************
* macosx.m: Mac OS X module for vlc
*****************************************************************************
* Copyright (C) 2001-2009 the VideoLAN team
* Copyright (C) 2001-2011 the VideoLAN team
* $Id$
*
* Authors: Colin Delacroix <colin@zoy.org>
* Eugenio Jarosiewicz <ej0@cise.ufl.edu>
* Florian G. Pflug <fgp@phlo.org>
* Jon Lech Johansen <jon-vl@nanocrew.net>
* Felix Paul Kühne <fkuehne at videolan dot org>
*
*
* This program is free software; you can redistribute it and/or modify
......@@ -95,10 +96,6 @@ void CloseVideoGL ( vlc_object_t * );
#define USE_MEDIAKEYS_LONGTEXT N_("By default, VLC can be controlled using the media keys on modern Apple " \
"keyboards.")
#define USE_MEDIAKEYS_BACKGROUND_TEXT N_("Use media key control when VLC is in background")
#define USE_MEDIAKEYS_BACKGROUND_LONGTEXT N_("By default, VLC will accept media key events also when being " \
"in background.")
vlc_module_begin ()
set_description( N_("Mac OS X interface") )
set_capability( "interface", 200 )
......@@ -118,8 +115,6 @@ vlc_module_begin ()
false )
add_bool( "macosx-mediakeys", true, USE_MEDIAKEYS_TEXT, USE_MEDIAKEYS_LONGTEXT,
false )
add_bool( "macosx-mediakeys-background", true, USE_MEDIAKEYS_BACKGROUND_TEXT, USE_MEDIAKEYS_BACKGROUND_LONGTEXT,
false )
add_submodule ()
set_description( "Mac OS X OpenGL" )
......
/*****************************************************************************
* misc.m: code not specific to vlc
*****************************************************************************
* Copyright (C) 2003-2009 the VideoLAN team
* Copyright (C) 2003-2011 the VideoLAN team
* $Id$
*
* Authors: Jon Lech Johansen <jon-vl@nanocrew.net>
......@@ -255,7 +255,7 @@ static NSMutableArray *blackoutWindows = NULL;
}
invoc = [NSInvocation invocationWithMethodSignature:[super methodSignatureForSelector:@selector(close)]];
[invoc setTarget: self];
[invoc setTarget: (id)super];
if (![self isVisible] || [self alphaValue] == 0.0)
{
......@@ -269,7 +269,7 @@ static NSMutableArray *blackoutWindows = NULL;
- (void)orderOut: (id)sender animate: (BOOL)animate
{
NSInvocation *invoc = [NSInvocation invocationWithMethodSignature:[super methodSignatureForSelector:@selector(orderOut:)]];
[invoc setTarget: self];
[invoc setTarget: (id)super];
[invoc setArgument: sender atIndex: 0];
[self orderOut: sender animate: animate callback: invoc];
}
......
/*****************************************************************************
* simple_prefs.h: Simple Preferences for Mac OS X
*****************************************************************************
* Copyright (C) 2008 the VideoLAN team
* Copyright (C) 2008-2011 the VideoLAN team
* $Id$
*
* Authors: Felix Paul Kühne <fkuehne at videolan dot org>
......@@ -91,7 +91,6 @@
IBOutlet id o_intf_fspanel_ckb;
IBOutlet id o_intf_appleremote_ckb;
IBOutlet id o_intf_mediakeys_ckb;
IBOutlet id o_intf_mediakeys_bg_ckb;
IBOutlet id o_intf_lang_pop;
IBOutlet id o_intf_lang_txt;
IBOutlet id o_intf_network_box;
......
/*****************************************************************************
* simple_prefs.m: Simple Preferences for Mac OS X
*****************************************************************************
* Copyright (C) 2008-2009 the VideoLAN team
* Copyright (C) 2008-2011 the VideoLAN team
* $Id$
*
* Authors: Felix Paul Kühne <fkuehne at videolan dot org>
......@@ -231,7 +231,6 @@ create_toolbar_item( NSString * o_itemIdent, NSString * o_name, NSString * o_des
[o_intf_network_box setTitle: _NS("Privacy / Network Interaction")];
[o_intf_appleremote_ckb setTitle: _NS("Control playback with the Apple Remote")];
[o_intf_mediakeys_ckb setTitle: _NS("Control playback with media keys")];
[o_intf_mediakeys_bg_ckb setTitle: _NS("...when VLC is in background")];
[o_intf_update_ckb setTitle: _NS("Automatically check for updates")];
[o_intf_last_update_lbl setStringValue: @""];
[o_intf_enableGrowl_ckb setStringValue: _NS("Enable Growl notifications (on playlist item change)")];
......@@ -422,8 +421,6 @@ static inline char * __config_GetLabel( vlc_object_t *p_this, const char *psz_na
[self setupButton: o_intf_embedded_ckb forBoolValue: "embedded-video"];
[self setupButton: o_intf_appleremote_ckb forBoolValue: "macosx-appleremote"];
[self setupButton: o_intf_mediakeys_ckb forBoolValue: "macosx-mediakeys"];
[self setupButton: o_intf_mediakeys_bg_ckb forBoolValue: "macosx-mediakeys-background"];
[o_intf_mediakeys_bg_ckb setEnabled: [o_intf_mediakeys_ckb state]];
if( [[SUUpdater sharedUpdater] lastUpdateCheckDate] != NULL )
[o_intf_last_update_lbl setStringValue: [NSString stringWithFormat: _NS("Last check on: %@"), [[[SUUpdater sharedUpdater] lastUpdateCheckDate] descriptionWithLocale: [[NSUserDefaults standardUserDefaults] dictionaryRepresentation]]]];
else
......@@ -780,7 +777,6 @@ static inline void save_module_list( intf_thread_t * p_intf, id object, const ch
config_PutInt( p_intf, "embedded-video", [o_intf_embedded_ckb state] );
config_PutInt( p_intf, "macosx-appleremote", [o_intf_appleremote_ckb state] );
config_PutInt( p_intf, "macosx-mediakeys", [o_intf_mediakeys_ckb state] );
config_PutInt( p_intf, "macosx-mediakeys-background", [o_intf_mediakeys_bg_ckb state] );
if( [o_intf_enableGrowl_ckb state] == NSOnState )
{
psz_tmp = config_GetPsz( p_intf, "control" );
......@@ -1101,8 +1097,6 @@ static inline void save_module_list( intf_thread_t * p_intf, id object, const ch
- (IBAction)interfaceSettingChanged:(id)sender
{
if( sender == o_intf_mediakeys_ckb )
[o_intf_mediakeys_bg_ckb setEnabled: [o_intf_mediakeys_ckb state]];
b_intfSettingChanged = YES;
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment