Commit 55f33b61 authored by Eric Petit's avatar Eric Petit

Adds support for Apple's remote (only handles Play/Pause at the moment).

AppleRemote.[mh] is MIT licensed code from Martin Kahr:
http://www.martinkahr.com/source-code
parent ee5179d0
......@@ -348,6 +348,8 @@ VLC-release.app: vlc
mkdir -p $(top_builddir)/tmp/modules/audio_output
mkdir -p $(top_builddir)/tmp/modules/gui/macosx
for i in \
AppleRemote.h \
AppleRemote.m \
about.h \
about.m \
applescript.h \
......@@ -486,6 +488,8 @@ VLC.app: vlc
mkdir -p $(top_builddir)/tmp/modules/audio_output
mkdir -p $(top_builddir)/tmp/modules/gui/macosx
for i in \
AppleRemote.h \
AppleRemote.m \
about.h \
about.m \
applescript.h \
......
//
// AppleRemote.h
// AppleRemote
//
// Created by Martin Kahr on 11.03.06.
// Copyright 2006 martinkahr.com. All rights reserved.
//
#import <Cocoa/Cocoa.h>
#import <mach/mach.h>
#import <mach/mach_error.h>
#import <IOKit/IOKitLib.h>
#import <IOKit/IOCFPlugIn.h>
#import <IOKit/hid/IOHIDLib.h>
#import <IOKit/hid/IOHIDKeys.h>
enum AppleRemoteEventIdentifier
{
kRemoteButtonVolume_Plus=0,
kRemoteButtonVolume_Minus,
kRemoteButtonMenu,
kRemoteButtonPlay,
kRemoteButtonRight,
kRemoteButtonLeft,
kRemoteButtonRight_Hold,
kRemoteButtonLeft_Hold,
kRemoteButtonMenu_Hold,
kRemoteButtonPlay_Sleep,
kRemoteControl_Switched
};
typedef enum AppleRemoteEventIdentifier AppleRemoteEventIdentifier;
/* Encapsulates usage of the apple remote control
This class is implemented as a singleton as there is exactly one remote per machine (until now)
The class is not thread safe
*/
@interface AppleRemote : NSObject {
IOHIDDeviceInterface** hidDeviceInterface;
IOHIDQueueInterface** queue;
NSMutableArray* allCookies;
NSMutableDictionary* cookieToButtonMapping;
BOOL openInExclusiveMode;
int remoteId;
IBOutlet id delegate;
}
- (void) setRemoteId: (int) aValue;
- (int) remoteId;
- (BOOL) isRemoteAvailable;
- (BOOL) isListeningToRemote;
- (void) setListeningToRemote: (BOOL) value;
- (BOOL) isOpenInExclusiveMode;
- (void) setOpenInExclusiveMode: (BOOL) value;
- (void) setDelegate: (id) delegate;
- (id) delegate;
- (IBAction) startListening: (id) sender;
- (IBAction) stopListening: (id) sender;
@end
@interface AppleRemote (Singleton)
+ (AppleRemote*) sharedRemote;
@end
/* Method definitions for the delegate of the AppleRemote class
*/
@interface NSObject(NSAppleRemoteDelegate)
- (void) appleRemoteButton: (AppleRemoteEventIdentifier)buttonIdentifier pressedDown: (BOOL) pressedDown;
@end
@interface AppleRemote (PrivateMethods)
- (NSDictionary*) cookieToButtonMapping;
- (IOHIDQueueInterface**) queue;
- (IOHIDDeviceInterface**) hidDeviceInterface;
- (void) handleEventWithCookieString: (NSString*) cookieString sumOfValues: (SInt32) sumOfValues;
@end
@interface AppleRemote (IOKitMethods)
- (io_object_t) findAppleRemoteDevice;
- (IOHIDDeviceInterface**) createInterfaceForDevice: (io_object_t) hidDevice;
- (BOOL) initializeCookies;
- (BOOL) openDevice;
@end
\ No newline at end of file
//
// AppleRemote.m
// AppleRemote
//
// Created by Martin Kahr on 11.03.06.
// Copyright 2006 martinkahr.com. All rights reserved.
//
#import "AppleRemote.h"
const char* AppleRemoteDeviceName = "AppleIRController";
const int REMOTE_SWITCH_COOKIE=19;
@implementation AppleRemote
#pragma public interface
- (id) init {
if ( self = [super init] ) {
openInExclusiveMode = YES;
queue = NULL;
hidDeviceInterface = NULL;
cookieToButtonMapping = [[NSMutableDictionary alloc] init];
[cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonVolume_Plus] forKey:@"14_12_11_6_5_"];
[cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonVolume_Minus] forKey:@"14_13_11_6_5_"];
[cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMenu] forKey:@"14_7_6_5_14_7_6_5_"];
[cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlay] forKey:@"14_8_6_5_14_8_6_5_"];
[cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonRight] forKey:@"14_9_6_5_14_9_6_5_"];
[cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonLeft] forKey:@"14_10_6_5_14_10_6_5_"];
[cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonRight_Hold] forKey:@"14_6_5_4_2_"];
[cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonLeft_Hold] forKey:@"14_6_5_3_2_"];
[cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMenu_Hold] forKey:@"14_6_5_14_6_5_"];
[cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlay_Sleep] forKey:@"18_14_6_5_18_14_6_5_"];
[cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteControl_Switched] forKey:@"19_"];
}
return self;
}
- (void) dealloc {
[self stopListening:self];
[cookieToButtonMapping release];
[super dealloc];
}
- (void) setRemoteId: (int) value {
remoteId = value;
}
- (int) remoteId {
return remoteId;
}
- (BOOL) isRemoteAvailable {
io_object_t hidDevice = [self findAppleRemoteDevice];
if (hidDevice != 0) {
IOObjectRelease(hidDevice);
return YES;
} else {
return NO;
}
}
- (BOOL) isListeningToRemote {
return (hidDeviceInterface != NULL && allCookies != NULL && queue != NULL);
}
- (void) setListeningToRemote: (BOOL) value {
if (value == NO) {
[self stopListening:self];
} else {
[self startListening:self];
}
}
- (void) setDelegate: (id) _delegate {
if ([_delegate respondsToSelector:@selector(appleRemoteButton:pressedDown:)]==NO) return;
[_delegate retain];
[delegate release];
delegate = _delegate;
}
- (id) delegate {
return delegate;
}
- (BOOL) isOpenInExclusiveMode {
return openInExclusiveMode;
}
- (void) setOpenInExclusiveMode: (BOOL) value {
openInExclusiveMode = value;
}
- (IBAction) startListening: (id) sender {
if ([self isListeningToRemote]) return;
io_object_t hidDevice = [self findAppleRemoteDevice];
if (hidDevice == 0) return;
if ([self createInterfaceForDevice:hidDevice] == NULL) {
goto error;
}
if ([self initializeCookies]==NO) {
goto error;
}
if ([self openDevice]==NO) {
goto error;
}
goto cleanup;
error:
[self stopListening:self];
cleanup:
IOObjectRelease(hidDevice);
}
- (IBAction) stopListening: (id) sender {
if (queue != NULL) {
(*queue)->stop(queue);
//dispose of queue
(*queue)->dispose(queue);
//release the queue we allocated
(*queue)->Release(queue);
queue = NULL;
}
if (allCookies != nil) {
[allCookies autorelease];
allCookies = nil;
}
if (hidDeviceInterface != NULL) {
//close the device
(*hidDeviceInterface)->close(hidDeviceInterface);
//release the interface
(*hidDeviceInterface)->Release(hidDeviceInterface);
hidDeviceInterface = NULL;
}
}
@end
@implementation AppleRemote (Singleton)
static AppleRemote* sharedInstance=nil;
+ (AppleRemote*) sharedRemote {
@synchronized(self) {
if (sharedInstance == nil) {
sharedInstance = [[self alloc] init];
}
}
return sharedInstance;
}
+ (id)allocWithZone:(NSZone *)zone {
@synchronized(self) {
if (sharedInstance == nil) {
return [super allocWithZone:zone];
}
}
return sharedInstance;
}
- (id)copyWithZone:(NSZone *)zone {
return self;
}
- (id)retain {
return self;
}
- (unsigned)retainCount {
return UINT_MAX; //denotes an object that cannot be released
}
- (void)release {
//do nothing
}
- (id)autorelease {
return self;
}
@end
@implementation AppleRemote (PrivateMethods)
- (IOHIDQueueInterface**) queue {
return queue;
}
- (IOHIDDeviceInterface**) hidDeviceInterface {
return hidDeviceInterface;
}
- (NSDictionary*) cookieToButtonMapping {
return cookieToButtonMapping;
}
- (void) handleEventWithCookieString: (NSString*) cookieString sumOfValues: (SInt32) sumOfValues {
NSNumber* buttonId = [[self cookieToButtonMapping] objectForKey: cookieString];
if (buttonId != nil) {
if (delegate) {
[delegate appleRemoteButton:[buttonId intValue] pressedDown: (sumOfValues>0)];
}
} else {
NSLog(@"Unknown button for cookiestring %@", cookieString);
}
}
@end
/* Callback method for the device queue
Will be called for any event of any type (cookie) to which we subscribe
*/
static void QueueCallbackFunction(void* target, IOReturn result, void* refcon, void* sender) {
AppleRemote* remote = (AppleRemote*)target;
IOHIDEventStruct event;
AbsoluteTime zeroTime = {0,0};
NSMutableString* cookieString = [NSMutableString string];
SInt32 sumOfValues = 0;
while (result == kIOReturnSuccess)
{
result = (*[remote queue])->getNextEvent([remote queue], &event, zeroTime, 0);
if ( result != kIOReturnSuccess )
continue;
if (REMOTE_SWITCH_COOKIE == (int)event.elementCookie) {
[remote setRemoteId: event.value];
[remote handleEventWithCookieString: @"19_" sumOfValues: 0];
} else {
sumOfValues+=event.value;
[cookieString appendString:[NSString stringWithFormat:@"%d_", event.elementCookie]];
}
//printf("%d %d %d\n", event.elementCookie, event.value, event.longValue);
}
[remote handleEventWithCookieString: cookieString sumOfValues: sumOfValues];
}
@implementation AppleRemote (IOKitMethods)
- (IOHIDDeviceInterface**) createInterfaceForDevice: (io_object_t) hidDevice {
io_name_t className;
IOCFPlugInInterface** plugInInterface = NULL;
HRESULT plugInResult = S_OK;
SInt32 score = 0;
IOReturn ioReturnValue = kIOReturnSuccess;
hidDeviceInterface = NULL;
ioReturnValue = IOObjectGetClass(hidDevice, className);
if (ioReturnValue != kIOReturnSuccess) {
NSLog(@"Error: Failed to get class name.");
return NULL;
}
ioReturnValue = IOCreatePlugInInterfaceForService(hidDevice,
kIOHIDDeviceUserClientTypeID,
kIOCFPlugInInterfaceID,
&plugInInterface,
&score);
if (ioReturnValue == kIOReturnSuccess)
{
//Call a method of the intermediate plug-in to create the device interface
plugInResult = (*plugInInterface)->QueryInterface(plugInInterface, CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID), (LPVOID) &hidDeviceInterface);
if (plugInResult != S_OK) {
NSLog(@"Error: Couldn't create HID class device interface");
}
// Release
if (plugInInterface) (*plugInInterface)->Release(plugInInterface);
}
return hidDeviceInterface;
}
- (io_object_t) findAppleRemoteDevice {
CFMutableDictionaryRef hidMatchDictionary = NULL;
IOReturn ioReturnValue = kIOReturnSuccess;
io_iterator_t hidObjectIterator = 0;
io_object_t hidDevice = 0;
// Set up a matching dictionary to search the I/O Registry by class
// name for all HID class devices
hidMatchDictionary = IOServiceMatching(AppleRemoteDeviceName);
// Now search I/O Registry for matching devices.
ioReturnValue = IOServiceGetMatchingServices(kIOMasterPortDefault, hidMatchDictionary, &hidObjectIterator);
if ((ioReturnValue == kIOReturnSuccess) && (hidObjectIterator != 0)) {
hidDevice = IOIteratorNext(hidObjectIterator);
}
// release the iterator
IOObjectRelease(hidObjectIterator);
return hidDevice;
}
- (BOOL) initializeCookies {
IOHIDDeviceInterface122** handle = (IOHIDDeviceInterface122**)hidDeviceInterface;
IOHIDElementCookie cookie;
long usage;
long usagePage;
id object;
NSArray* elements = nil;
NSDictionary* element;
IOReturn success;
if (!handle || !(*handle)) return NO;
// Copy all elements, since we're grabbing most of the elements
// for this device anyway, and thus, it's faster to iterate them
// ourselves. When grabbing only one or two elements, a matching
// dictionary should be passed in here instead of NULL.
success = (*handle)->copyMatchingElements(handle, NULL, (CFArrayRef*)&elements);
if (success == kIOReturnSuccess) {
[elements autorelease];
/*
cookies = calloc(NUMBER_OF_APPLE_REMOTE_ACTIONS, sizeof(IOHIDElementCookie));
memset(cookies, 0, sizeof(IOHIDElementCookie) * NUMBER_OF_APPLE_REMOTE_ACTIONS);
*/
allCookies = [[NSMutableArray alloc] init];
int i;
for (i=0; i< [elements count]; i++) {
element = [elements objectAtIndex:i];
//Get cookie
object = [element valueForKey: (NSString*)CFSTR(kIOHIDElementCookieKey) ];
if (object == nil || ![object isKindOfClass:[NSNumber class]]) continue;
if (object == 0 || CFGetTypeID(object) != CFNumberGetTypeID()) continue;
cookie = (IOHIDElementCookie) [object longValue];
//Get usage
object = [element valueForKey: (NSString*)CFSTR(kIOHIDElementUsageKey) ];
if (object == nil || ![object isKindOfClass:[NSNumber class]]) continue;
usage = [object longValue];
//Get usage page
object = [element valueForKey: (NSString*)CFSTR(kIOHIDElementUsagePageKey) ];
if (object == nil || ![object isKindOfClass:[NSNumber class]]) continue;
usagePage = [object longValue];
[allCookies addObject: [NSNumber numberWithInt:(int)cookie]];
}
} else {
return NO;
}
return YES;
}
- (BOOL) openDevice {
HRESULT result;
IOHIDOptionsType openMode = kIOHIDOptionsTypeNone;
if ([self isOpenInExclusiveMode]) openMode = kIOHIDOptionsTypeSeizeDevice;
IOReturn ioReturnValue = (*hidDeviceInterface)->open(hidDeviceInterface, openMode);
if (ioReturnValue == KERN_SUCCESS) {
queue = (*hidDeviceInterface)->allocQueue(hidDeviceInterface);
if (queue) {
result = (*queue)->create(queue, 0, 12); //depth: maximum number of elements in queue before oldest elements in queue begin to be lost.
int i=0;
for(i=0; i<[allCookies count]; i++) {
IOHIDElementCookie cookie = (IOHIDElementCookie)[[allCookies objectAtIndex:i] intValue];
(*queue)->addElement(queue, cookie, 0);
}
// add callback for async events
CFRunLoopSourceRef eventSource;
ioReturnValue = (*queue)->createAsyncEventSource(queue, &eventSource);
if (ioReturnValue == KERN_SUCCESS) {
ioReturnValue = (*queue)->setEventCallout(queue,QueueCallbackFunction, self, NULL);
if (ioReturnValue == KERN_SUCCESS) {
CFRunLoopAddSource(CFRunLoopGetCurrent(), eventSource, kCFRunLoopDefaultMode);
//start data delivery to queue
(*queue)->start(queue);
return YES;
} else {
NSLog(@"Error when setting event callout");
}
} else {
NSLog(@"Error when creating async event source");
}
} else {
NSLog(@"Error when opening device");
}
}
return NO;
}
@end
\ No newline at end of file
SOURCES_macosx = \
AppleRemote.h \
AppleRemote.m \
about.h \
about.m \
applescript.h \
......
......@@ -86,6 +86,7 @@ struct intf_sys_t
/*****************************************************************************
* VLCMain interface
*****************************************************************************/
@class AppleRemote;
@interface VLCMain : NSObject
{
intf_thread_t *p_intf; /* The main intf object */
......@@ -275,6 +276,8 @@ struct intf_sys_t
NSSize o_size_with_playlist;
int i_lastShownVolume;
AppleRemote * o_remote;
}
+ (VLCMain *)sharedInstance;
......
......@@ -45,6 +45,7 @@
#include "interaction.h"
#include "embeddedwindow.h"
#include "update.h"
#include "AppleRemote.h"
/*****************************************************************************
* Local prototypes.
......@@ -343,6 +344,10 @@ static VLCMain *_o_sharedMainInstance = nil;
o_update = [[VLCUpdate alloc] init];
i_lastShownVolume = -1;
o_remote = [[AppleRemote alloc] init];
[o_remote setDelegate: _o_sharedMainInstance];
return _o_sharedMainInstance;
}
......@@ -684,6 +689,33 @@ static VLCMain *_o_sharedMainInstance = nil;
return( o_str );
}
/* Listen to the remote in exclusive mode, only when VLC is the active
application */
- (void)applicationDidBecomeActive:(NSNotification *)aNotification
{
[o_remote startListening: self];
}
- (void)applicationDidResignActive:(NSNotification *)aNotification
{
[o_remote stopListening: self];
}
/* Apple Remote callback */
- (void)appleRemoteButton:(AppleRemoteEventIdentifier)buttonIdentifier
pressedDown:(BOOL)pressedDown
{
switch( buttonIdentifier )
{
case kRemoteButtonPlay:
[o_controls play: self];
break;
default:
/* Add here whatever you want other buttons to do */
break;
}
}
- (char *)delocalizeString:(NSString *)id
{
NSData * o_data = [id dataUsingEncoding: NSUTF8StringEncoding
......
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