Commit 92c36210 authored by Felix Paul Kühne's avatar Felix Paul Kühne

* various improvements to the AppleRemote support by Martin Kahr <martin...

* various improvements to the AppleRemote support by Martin Kahr <martin -attt-> martinkahr com> feat. the following changes:
    - holding +/- continuously increases/decreases volume
    - pressing Play twice toggles fullscreen mode
    -  a press on Menu shows the "Position" overlay (like pressing the key
't')

This updates our copy of Martin's unofficial framework to the latest version released.
parent b8e3c1ea
...@@ -61,38 +61,51 @@ ...@@ -61,38 +61,51 @@
enum AppleRemoteEventIdentifier enum AppleRemoteEventIdentifier
{ {
kRemoteButtonVolume_Plus=0, kRemoteButtonVolume_Plus =1<<1,
kRemoteButtonVolume_Minus, kRemoteButtonVolume_Minus =1<<2,
kRemoteButtonMenu, kRemoteButtonMenu =1<<3,
kRemoteButtonPlay, kRemoteButtonPlay =1<<4,
kRemoteButtonRight, kRemoteButtonRight =1<<5,
kRemoteButtonLeft, kRemoteButtonLeft =1<<6,
kRemoteButtonRight_Hold, kRemoteButtonRight_Hold =1<<7,
kRemoteButtonLeft_Hold, kRemoteButtonLeft_Hold =1<<8,
kRemoteButtonMenu_Hold, kRemoteButtonMenu_Hold =1<<9,
kRemoteButtonPlay_Sleep, kRemoteButtonPlay_Sleep =1<<10,
kRemoteControl_Switched kRemoteControl_Switched =1<<11,
kRemoteButtonVolume_Plus_Hold =1<<12,
kRemoteButtonVolume_Minus_Hold =1<<13
}; };
typedef enum AppleRemoteEventIdentifier AppleRemoteEventIdentifier; typedef enum AppleRemoteEventIdentifier AppleRemoteEventIdentifier;
/* Encapsulates usage of the apple remote control /* Encapsulates usage of the apple remote control
This class is implemented as a singleton as there is exactly one remote per machine (until now) This class is implemented as a singleton as there is exactly one remote per machine (until now)
The class is not thread safe The class is not thread safe
*/ */
@interface AppleRemote : NSObject { @interface AppleRemote : NSObject {
IOHIDDeviceInterface** hidDeviceInterface; IOHIDDeviceInterface** hidDeviceInterface;
IOHIDQueueInterface** queue; IOHIDQueueInterface** queue;
NSMutableArray* allCookies; NSMutableArray* allCookies;
NSMutableDictionary* cookieToButtonMapping; NSMutableDictionary* cookieToButtonMapping;
BOOL openInExclusiveMode; BOOL openInExclusiveMode;
BOOL simulatePlusMinusHold;
int remoteId; BOOL processesBacklog;
IBOutlet id delegate; /* state for simulating plus/minus hold */
BOOL lastEventSimulatedHold;
AppleRemoteEventIdentifier lastPlusMinusEvent;
NSTimeInterval lastPlusMinusEventTime;
int remoteId;
unsigned int clickCountEnabledButtons;
NSTimeInterval maxClickTimeDifference;
NSTimeInterval lastClickCountEventTime;
AppleRemoteEventIdentifier lastClickCountEvent;
unsigned int eventClickCount;
IBOutlet id delegate;
} }
- (void) setRemoteId: (int) aValue;
- (int) remoteId; - (int) remoteId;
- (BOOL) isRemoteAvailable; - (BOOL) isRemoteAvailable;
...@@ -103,6 +116,42 @@ typedef enum AppleRemoteEventIdentifier AppleRemoteEventIdentifier; ...@@ -103,6 +116,42 @@ typedef enum AppleRemoteEventIdentifier AppleRemoteEventIdentifier;
- (BOOL) isOpenInExclusiveMode; - (BOOL) isOpenInExclusiveMode;
- (void) setOpenInExclusiveMode: (BOOL) value; - (void) setOpenInExclusiveMode: (BOOL) value;
/* click counting makes it possible to recognize if the user has pressed a button repeatedly
* click counting does delay each event as it has to wait if there is another event (second click)
* therefore there is a slight time difference (maximumClickCountTimeDifference) between a single click
* of the user and the call of your delegate method
* click counting can be enabled individually for specific buttons. Use the property clickCountEnableButtons
* to set the buttons for which click counting shall be enabled */
- (BOOL) clickCountingEnabled;
- (void) setClickCountingEnabled: (BOOL) value;
- (unsigned int) clickCountEnabledButtons;
- (void) setClickCountEnabledButtons: (unsigned int)value;
/* the maximum time difference till which clicks are recognized as multi clicks */
- (NSTimeInterval) maximumClickCountTimeDifference;
- (void) setMaximumClickCountTimeDifference: (NSTimeInterval) timeDiff;
/* When your application needs to much time on the main thread when processing an event other events
* may already be received which are put on a backlog. As soon as your main thread
* has some spare time this backlog is processed and may flood your delegate with calls.
* Backlog processing is turned off by default. */
- (BOOL) processesBacklog;
- (void) setProcessesBacklog: (BOOL) value;
/* Sets an NSApplication delegate which starts listening when application is becoming active
* and stops listening when application resigns being active.
* If an NSApplication delegate has been already set all method calls will be forwarded to this delegate, too. */
- (BOOL) listeningOnAppActivate;
- (void) setListeningOnAppActivate: (BOOL) value;
/* Simulating plus/minus hold does deactivate sending of individual requests for plus/minus pressed down/released.
* Instead special hold events are being triggered when the user is pressing and holding plus/minus for a small period.
* With simulating enabled the plus/minus buttons do behave as the left/right buttons */
- (BOOL) simulatesPlusMinusHold;
- (void) setSimulatesPlusMinusHold: (BOOL) value;
/* Delegates are not retained */
- (void) setDelegate: (id) delegate; - (void) setDelegate: (id) delegate;
- (id) delegate; - (id) delegate;
...@@ -116,15 +165,15 @@ typedef enum AppleRemoteEventIdentifier AppleRemoteEventIdentifier; ...@@ -116,15 +165,15 @@ typedef enum AppleRemoteEventIdentifier AppleRemoteEventIdentifier;
@end @end
/* Method definitions for the delegate of the AppleRemote class /* Method definitions for the delegate of the AppleRemote class */
*/
@interface NSObject(NSAppleRemoteDelegate) @interface NSObject(NSAppleRemoteDelegate)
- (void) appleRemoteButton: (AppleRemoteEventIdentifier)buttonIdentifier pressedDown: (BOOL) pressedDown; - (void) appleRemoteButton: (AppleRemoteEventIdentifier)buttonIdentifier pressedDown: (BOOL) pressedDown clickCount: (unsigned int) count;
@end @end
@interface AppleRemote (PrivateMethods) @interface AppleRemote (PrivateMethods)
- (void) setRemoteId: (int) aValue;
- (NSDictionary*) cookieToButtonMapping; - (NSDictionary*) cookieToButtonMapping;
- (IOHIDQueueInterface**) queue; - (IOHIDQueueInterface**) queue;
- (IOHIDDeviceInterface**) hidDeviceInterface; - (IOHIDDeviceInterface**) hidDeviceInterface;
...@@ -136,4 +185,15 @@ typedef enum AppleRemoteEventIdentifier AppleRemoteEventIdentifier; ...@@ -136,4 +185,15 @@ typedef enum AppleRemoteEventIdentifier AppleRemoteEventIdentifier;
- (IOHIDDeviceInterface**) createInterfaceForDevice: (io_object_t) hidDevice; - (IOHIDDeviceInterface**) createInterfaceForDevice: (io_object_t) hidDevice;
- (BOOL) initializeCookies; - (BOOL) initializeCookies;
- (BOOL) openDevice; - (BOOL) openDevice;
@end
/* A NSApplication delegate which is used to activate and deactivate listening to the remote control
* dependent on the activation state of your application.
* All events are delegated to the original NSApplication delegate if necessary */
@interface AppleRemoteApplicationDelegate : NSObject {
id applicationDelegate;
}
- (id) initWithApplicationDelegate: (id) delegate;
- (id) applicationDelegate;
@end @end
\ No newline at end of file
...@@ -55,141 +55,204 @@ ...@@ -55,141 +55,204 @@
const char* AppleRemoteDeviceName = "AppleIRController"; const char* AppleRemoteDeviceName = "AppleIRController";
const int REMOTE_SWITCH_COOKIE=19; const int REMOTE_SWITCH_COOKIE=19;
const NSTimeInterval DEFAULT_MAXIMUM_CLICK_TIME_DIFFERENCE=0.35;
const NSTimeInterval HOLD_RECOGNITION_TIME_INTERVAL=0.4;
@implementation AppleRemote @implementation AppleRemote
- (id) init #pragma public interface
{
self = [super init]; - (id) init {
if ( self = [super init] ) {
if ( self == [super init] ) { openInExclusiveMode = YES;
openInExclusiveMode = YES; queue = NULL;
queue = NULL; hidDeviceInterface = NULL;
hidDeviceInterface = NULL; cookieToButtonMapping = [[NSMutableDictionary alloc] init];
cookieToButtonMapping = [[NSMutableDictionary alloc] init];
[cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonVolume_Plus] forKey:@"14_12_11_6_"];
[cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonVolume_Plus] forKey:@"14_12_11_6_5_"]; [cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonVolume_Minus] forKey:@"14_13_11_6_"];
[cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonVolume_Minus] forKey:@"14_13_11_6_5_"]; [cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMenu] forKey:@"14_7_6_14_7_6_"];
[cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMenu] forKey:@"14_7_6_5_14_7_6_5_"]; [cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlay] forKey:@"14_8_6_14_8_6_"];
[cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlay] forKey:@"14_8_6_5_14_8_6_5_"]; [cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonRight] forKey:@"14_9_6_14_9_6_"];
[cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonRight] forKey:@"14_9_6_5_14_9_6_5_"]; [cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonLeft] forKey:@"14_10_6_14_10_6_"];
[cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonLeft] forKey:@"14_10_6_5_14_10_6_5_"]; [cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonRight_Hold] forKey:@"14_6_4_2_"];
[cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonRight_Hold] forKey:@"14_6_5_4_2_"]; [cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonLeft_Hold] forKey:@"14_6_3_2_"];
[cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonLeft_Hold] forKey:@"14_6_5_3_2_"]; [cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMenu_Hold] forKey:@"14_6_14_6_"];
[cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMenu_Hold] forKey:@"14_6_5_14_6_5_"]; [cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlay_Sleep] forKey:@"18_14_6_18_14_6_"];
[cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlay_Sleep] forKey:@"18_14_6_5_18_14_6_5_"]; [cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteControl_Switched] forKey:@"19_"];
[cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteControl_Switched] forKey:@"19_"];
} /* defaults */
[self setSimulatesPlusMinusHold: YES];
return self; maxClickTimeDifference = DEFAULT_MAXIMUM_CLICK_TIME_DIFFERENCE;
}
return self;
} }
- (void) dealloc { - (void) dealloc {
[self stopListening:self]; [self stopListening:self];
[cookieToButtonMapping release]; [cookieToButtonMapping release];
[super dealloc]; [super dealloc];
} }
- (void) setRemoteId: (int) value {
remoteId = value;
}
- (int) remoteId { - (int) remoteId {
return remoteId; return remoteId;
} }
- (BOOL) isRemoteAvailable { - (BOOL) isRemoteAvailable {
io_object_t hidDevice = [self findAppleRemoteDevice]; io_object_t hidDevice = [self findAppleRemoteDevice];
if (hidDevice != 0) { if (hidDevice != 0) {
IOObjectRelease(hidDevice); IOObjectRelease(hidDevice);
return YES; return YES;
} else { } else {
return NO; return NO;
} }
} }
- (BOOL) isListeningToRemote { - (BOOL) isListeningToRemote {
return (hidDeviceInterface != NULL && allCookies != NULL && queue != NULL); return (hidDeviceInterface != NULL && allCookies != NULL && queue != NULL);
} }
- (void) setListeningToRemote: (BOOL) value { - (void) setListeningToRemote: (BOOL) value {
if (value == NO) { if (value == NO) {
[self stopListening:self]; [self stopListening:self];
} else { } else {
[self startListening:self]; [self startListening:self];
} }
} }
/* Delegates are not retained!
* http://developer.apple.com/documentation/Cocoa/Conceptual/CocoaFundamentals/CommunicatingWithObjects/chapter_6_section_4.html
* Delegating objects do not (and should not) retain their delegates.
* However, clients of delegating objects (applications, usually) are responsible for ensuring that their delegates are around
* to receive delegation messages. To do this, they may have to retain the delegate. */
- (void) setDelegate: (id) _delegate { - (void) setDelegate: (id) _delegate {
if ([_delegate respondsToSelector:@selector(appleRemoteButton:pressedDown:)]==NO) return; if (_delegate && [_delegate respondsToSelector:@selector(appleRemoteButton:pressedDown:clickCount:)]==NO) return;
[_delegate retain]; delegate = _delegate;
[delegate release];
delegate = _delegate;
} }
- (id) delegate { - (id) delegate {
return delegate; return delegate;
} }
- (BOOL) isOpenInExclusiveMode { - (BOOL) isOpenInExclusiveMode {
return openInExclusiveMode; return openInExclusiveMode;
} }
- (void) setOpenInExclusiveMode: (BOOL) value { - (void) setOpenInExclusiveMode: (BOOL) value {
openInExclusiveMode = value; openInExclusiveMode = value;
} }
- (IBAction) startListening: (id) sender { - (BOOL) clickCountingEnabled {
if ([self isListeningToRemote]) return; return clickCountEnabledButtons != 0;
}
io_object_t hidDevice = [self findAppleRemoteDevice]; - (void) setClickCountingEnabled: (BOOL) value {
if (hidDevice == 0) return; if (value) {
[self setClickCountEnabledButtons: kRemoteButtonVolume_Plus | kRemoteButtonVolume_Minus | kRemoteButtonPlay | kRemoteButtonLeft | kRemoteButtonRight | kRemoteButtonMenu];
if ([self createInterfaceForDevice:hidDevice] == NULL) { } else {
goto error; [self setClickCountEnabledButtons: 0];
} }
}
if ([self initializeCookies]==NO) {
goto error; - (unsigned int) clickCountEnabledButtons {
} return clickCountEnabledButtons;
}
if ([self openDevice]==NO) { - (void) setClickCountEnabledButtons: (unsigned int)value {
goto error; clickCountEnabledButtons = value;
} }
goto cleanup;
- (NSTimeInterval) maximumClickCountTimeDifference {
return maxClickTimeDifference;
}
- (void) setMaximumClickCountTimeDifference: (NSTimeInterval) timeDiff {
maxClickTimeDifference = timeDiff;
}
- (BOOL) processesBacklog {
return processesBacklog;
}
- (void) setProcessesBacklog: (BOOL) value {
processesBacklog = value;
}
- (BOOL) listeningOnAppActivate {
id appDelegate = [NSApp delegate];
return (appDelegate!=nil && [appDelegate isKindOfClass: [AppleRemoteApplicationDelegate class]]);
}
- (void) setListeningOnAppActivate: (BOOL) value {
if (value) {
if ([self listeningOnAppActivate]) return;
AppleRemoteApplicationDelegate* appDelegate = [[AppleRemoteApplicationDelegate alloc] initWithApplicationDelegate: [NSApp delegate]];
/* NSApp does not retain its delegate therefore we keep retain count on 1 */
[NSApp setDelegate: appDelegate];
} else {
if ([self listeningOnAppActivate]==NO) return;
AppleRemoteApplicationDelegate* appDelegate = (AppleRemoteApplicationDelegate*)[NSApp delegate];
id previousAppDelegate = [appDelegate applicationDelegate];
[NSApp setDelegate: previousAppDelegate];
[appDelegate release];
}
}
- (BOOL) simulatesPlusMinusHold {
return simulatePlusMinusHold;
}
- (void) setSimulatesPlusMinusHold: (BOOL) value {
simulatePlusMinusHold = 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: error:
[self stopListening:self]; [self stopListening:self];
cleanup: cleanup:
IOObjectRelease(hidDevice); IOObjectRelease(hidDevice);
} }
- (IBAction) stopListening: (id) sender { - (IBAction) stopListening: (id) sender {
if (queue != NULL) { if (queue != NULL) {
(*queue)->stop(queue); (*queue)->stop(queue);
//dispose of queue //dispose of queue
(*queue)->dispose(queue); (*queue)->dispose(queue);
//release the queue we allocated //release the queue we allocated
(*queue)->Release(queue); (*queue)->Release(queue);
queue = NULL; queue = NULL;
} }
if (allCookies != nil) { if (allCookies != nil) {
[allCookies autorelease]; [allCookies autorelease];
allCookies = nil; allCookies = nil;
} }
if (hidDeviceInterface != NULL) { if (hidDeviceInterface != NULL) {
//close the device //close the device
(*hidDeviceInterface)->close(hidDeviceInterface); (*hidDeviceInterface)->close(hidDeviceInterface);
//release the interface //release the interface
(*hidDeviceInterface)->Release(hidDeviceInterface); (*hidDeviceInterface)->Release(hidDeviceInterface);
hidDeviceInterface = NULL; hidDeviceInterface = NULL;
} }
} }
@end @end
...@@ -198,20 +261,20 @@ cleanup: ...@@ -198,20 +261,20 @@ cleanup:
static AppleRemote* sharedInstance=nil; static AppleRemote* sharedInstance=nil;
+ (AppleRemote*) sharedRemote { + (AppleRemote*) sharedRemote {
@synchronized(self) { @synchronized(self) {
if (sharedInstance == nil) { if (sharedInstance == nil) {
sharedInstance = [[self alloc] init]; sharedInstance = [[self alloc] init];
} }
} }
return sharedInstance; return sharedInstance;
} }
+ (id)allocWithZone:(NSZone *)zone { + (id)allocWithZone:(NSZone *)zone {
@synchronized(self) { @synchronized(self) {
if (sharedInstance == nil) { if (sharedInstance == nil) {
return [super allocWithZone:zone]; return [super allocWithZone:zone];
} }
} }
return sharedInstance; return sharedInstance;
} }
- (id)copyWithZone:(NSZone *)zone { - (id)copyWithZone:(NSZone *)zone {
...@@ -234,217 +297,408 @@ static AppleRemote* sharedInstance=nil; ...@@ -234,217 +297,408 @@ static AppleRemote* sharedInstance=nil;
@implementation AppleRemote (PrivateMethods) @implementation AppleRemote (PrivateMethods)
- (void) setRemoteId: (int) value {
remoteId = value;
}
- (IOHIDQueueInterface**) queue { - (IOHIDQueueInterface**) queue {
return queue; return queue;
} }
- (IOHIDDeviceInterface**) hidDeviceInterface { - (IOHIDDeviceInterface**) hidDeviceInterface {
return hidDeviceInterface; return hidDeviceInterface;
} }
- (NSDictionary*) cookieToButtonMapping { - (NSDictionary*) cookieToButtonMapping {
return cookieToButtonMapping; return cookieToButtonMapping;
}
- (NSString*) validCookieSubstring: (NSString*) cookieString {
if (cookieString == nil || [cookieString length] == 0) return nil;
NSEnumerator* keyEnum = [[self cookieToButtonMapping] keyEnumerator];
NSString* key;
while(key = [keyEnum nextObject]) {
NSRange range = [cookieString rangeOfString:key];
if (range.location == 0) return key;
}
return nil;
}
- (void) sendSimulatedPlusMinusEvent: (id) time {
BOOL startSimulateHold = NO;
AppleRemoteEventIdentifier event = lastPlusMinusEvent;
@synchronized(self) {
startSimulateHold = (lastPlusMinusEvent>0 && lastPlusMinusEventTime == [time doubleValue]);
}
if (startSimulateHold) {
lastEventSimulatedHold = YES;
event = (event==kRemoteButtonVolume_Plus) ? kRemoteButtonVolume_Plus_Hold : kRemoteButtonVolume_Minus_Hold;
[delegate appleRemoteButton:event pressedDown: YES clickCount: 1];
}
}
- (void) sendRemoteButtonEvent: (AppleRemoteEventIdentifier) event pressedDown: (BOOL) pressedDown {
if (delegate) {
if (simulatePlusMinusHold) {
if (event == kRemoteButtonVolume_Plus || event == kRemoteButtonVolume_Minus) {
if (pressedDown) {
lastPlusMinusEvent = event;
lastPlusMinusEventTime = [NSDate timeIntervalSinceReferenceDate];
[self performSelector:@selector(sendSimulatedPlusMinusEvent:)
withObject:[NSNumber numberWithDouble:lastPlusMinusEventTime]
afterDelay:HOLD_RECOGNITION_TIME_INTERVAL];
return;
} else {
if (lastEventSimulatedHold) {
event = (event==kRemoteButtonVolume_Plus) ? kRemoteButtonVolume_Plus_Hold : kRemoteButtonVolume_Minus_Hold;
lastPlusMinusEvent = 0;
lastEventSimulatedHold = NO;
} else {
@synchronized(self) {
lastPlusMinusEvent = 0;
}
pressedDown = YES;
}
}
}
}
if (([self clickCountEnabledButtons] & event) == event) {
if (pressedDown==NO && (event == kRemoteButtonVolume_Minus || event == kRemoteButtonVolume_Plus)) {
return; // this one is triggered automatically by the handler
}
NSNumber* eventNumber;
NSNumber* timeNumber;
@synchronized(self) {
lastClickCountEventTime = [NSDate timeIntervalSinceReferenceDate];
if (lastClickCountEvent == event) {
eventClickCount = eventClickCount + 1;
} else {
eventClickCount = 1;
}
lastClickCountEvent = event;
timeNumber = [NSNumber numberWithDouble:lastClickCountEventTime];
eventNumber= [NSNumber numberWithUnsignedInt:event];
}
[self performSelector: @selector(executeClickCountEvent:)
withObject: [NSArray arrayWithObjects:eventNumber, timeNumber, nil]
afterDelay: maxClickTimeDifference];
} else {
[delegate appleRemoteButton:event pressedDown: pressedDown clickCount:1];
}
}
}
- (void) executeClickCountEvent: (NSArray*) values {
AppleRemoteEventIdentifier event = [[values objectAtIndex: 0] unsignedIntValue];
NSTimeInterval eventTimePoint = [[values objectAtIndex: 1] doubleValue];
BOOL finishedClicking = NO;
int finalClickCount = eventClickCount;
@synchronized(self) {
finishedClicking = (event != lastClickCountEvent || eventTimePoint == lastClickCountEventTime);
if (finishedClicking) eventClickCount = 0;
}
if (finishedClicking) {
[delegate appleRemoteButton:event pressedDown: YES clickCount:finalClickCount];
if ([self simulatesPlusMinusHold]==NO && (event == kRemoteButtonVolume_Minus || event == kRemoteButtonVolume_Plus)) {
// trigger a button release event, too
[NSThread sleepUntilDate: [NSDate dateWithTimeIntervalSinceNow:0.1]];
[delegate appleRemoteButton:event pressedDown: NO clickCount:finalClickCount];
}
}
} }
- (void) handleEventWithCookieString: (NSString*) cookieString sumOfValues: (SInt32) sumOfValues { - (void) handleEventWithCookieString: (NSString*) cookieString sumOfValues: (SInt32) sumOfValues {
NSNumber* buttonId = [[self cookieToButtonMapping] objectForKey: cookieString]; /*
if (buttonId != nil) { if (previousRemainingCookieString) {
if (delegate) { cookieString = [previousRemainingCookieString stringByAppendingString: cookieString];
[delegate appleRemoteButton:[buttonId intValue] pressedDown: (sumOfValues>0)]; NSLog(@"New cookie string is %@", cookieString);
} [previousRemainingCookieString release], previousRemainingCookieString=nil;
} else { }*/
NSLog(@"Unknown button for cookiestring %@", cookieString); if (cookieString == nil || [cookieString length] == 0) return;
} NSNumber* buttonId = [[self cookieToButtonMapping] objectForKey: cookieString];
if (buttonId != nil) {
[self sendRemoteButtonEvent: [buttonId intValue] pressedDown: (sumOfValues>0)];
} else {
// let's see if a number of events are stored in the cookie string. this does
// happen when the main thread is too busy to handle all incoming events in time.
NSString* subCookieString;
NSString* lastSubCookieString=nil;
while(subCookieString = [self validCookieSubstring: cookieString]) {
cookieString = [cookieString substringFromIndex: [subCookieString length]];
lastSubCookieString = subCookieString;
if (processesBacklog) [self handleEventWithCookieString: subCookieString sumOfValues:sumOfValues];
}
if (processesBacklog == NO && lastSubCookieString != nil) {
// process the last event of the backlog and assume that the button is not pressed down any longer.
// The events in the backlog do not seem to be in order and therefore (in rare cases) the last event might be
// a button pressed down event while in reality the user has released it.
// NSLog(@"processing last event of backlog");
[self handleEventWithCookieString: lastSubCookieString sumOfValues:0];
}
if ([cookieString length] > 0) {
NSLog(@"Unknown button for cookiestring %@", cookieString);
}
}
} }
@end @end
/* Callback method for the device queue /* Callback method for the device queue
Will be called for any event of any type (cookie) to which we subscribe 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) { static void QueueCallbackFunction(void* target, IOReturn result, void* refcon, void* sender) {
AppleRemote* remote = (AppleRemote*)target; AppleRemote* remote = (AppleRemote*)target;
IOHIDEventStruct event; IOHIDEventStruct event;
AbsoluteTime zeroTime = {0,0}; AbsoluteTime zeroTime = {0,0};
NSMutableString* cookieString = [NSMutableString string]; NSMutableString* cookieString = [NSMutableString string];
SInt32 sumOfValues = 0; SInt32 sumOfValues = 0;
while (result == kIOReturnSuccess) while (result == kIOReturnSuccess)
{ {
result = (*[remote queue])->getNextEvent([remote queue], &event, zeroTime, 0); result = (*[remote queue])->getNextEvent([remote queue], &event, zeroTime, 0);
if ( result != kIOReturnSuccess ) if ( result != kIOReturnSuccess )
continue; continue;
if (REMOTE_SWITCH_COOKIE == (int)event.elementCookie) { //printf("%d %d %d\n", event.elementCookie, event.value, event.longValue);
[remote setRemoteId: event.value];
[remote handleEventWithCookieString: @"19_" sumOfValues: 0]; if (REMOTE_SWITCH_COOKIE == (int)event.elementCookie) {
} else { [remote setRemoteId: event.value];
sumOfValues+=event.value; [remote handleEventWithCookieString: @"19_" sumOfValues: 0];
[cookieString appendString:[NSString stringWithFormat:@"%d_", event.elementCookie]]; } else {
} if (((int)event.elementCookie)!=5) {
sumOfValues+=event.value;
//printf("%d %d %d\n", event.elementCookie, event.value, event.longValue); [cookieString appendString:[NSString stringWithFormat:@"%d_", event.elementCookie]];
} }
}
[remote handleEventWithCookieString: cookieString sumOfValues: sumOfValues]; }
[remote handleEventWithCookieString: cookieString sumOfValues: sumOfValues];
} }
@implementation AppleRemote (IOKitMethods) @implementation AppleRemote (IOKitMethods)
- (IOHIDDeviceInterface**) createInterfaceForDevice: (io_object_t) hidDevice { - (IOHIDDeviceInterface**) createInterfaceForDevice: (io_object_t) hidDevice {
io_name_t className; io_name_t className;
IOCFPlugInInterface** plugInInterface = NULL; IOCFPlugInInterface** plugInInterface = NULL;
HRESULT plugInResult = S_OK; HRESULT plugInResult = S_OK;
SInt32 score = 0; SInt32 score = 0;
IOReturn ioReturnValue = kIOReturnSuccess; IOReturn ioReturnValue = kIOReturnSuccess;
hidDeviceInterface = NULL; hidDeviceInterface = NULL;
ioReturnValue = IOObjectGetClass(hidDevice, className); ioReturnValue = IOObjectGetClass(hidDevice, className);
if (ioReturnValue != kIOReturnSuccess) { if (ioReturnValue != kIOReturnSuccess) {
NSLog(@"Error: Failed to get class name."); NSLog(@"Error: Failed to get class name.");
return NULL; return NULL;
} }
ioReturnValue = IOCreatePlugInInterfaceForService(hidDevice, ioReturnValue = IOCreatePlugInInterfaceForService(hidDevice,
kIOHIDDeviceUserClientTypeID, kIOHIDDeviceUserClientTypeID,
kIOCFPlugInInterfaceID, kIOCFPlugInInterfaceID,
&plugInInterface, &plugInInterface,
&score); &score);
if (ioReturnValue == kIOReturnSuccess) if (ioReturnValue == kIOReturnSuccess)
{ {
//Call a method of the intermediate plug-in to create the device interface //Call a method of the intermediate plug-in to create the device interface
plugInResult = (*plugInInterface)->QueryInterface(plugInInterface, CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID), (LPVOID) &hidDeviceInterface); plugInResult = (*plugInInterface)->QueryInterface(plugInInterface, CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID), (LPVOID) &hidDeviceInterface);
if (plugInResult != S_OK) { if (plugInResult != S_OK) {
NSLog(@"Error: Couldn't create HID class device interface"); NSLog(@"Error: Couldn't create HID class device interface");
} }
// Release // Release
if (plugInInterface) (*plugInInterface)->Release(plugInInterface); if (plugInInterface) (*plugInInterface)->Release(plugInInterface);
} }
return hidDeviceInterface; return hidDeviceInterface;
} }
- (io_object_t) findAppleRemoteDevice { - (io_object_t) findAppleRemoteDevice {
CFMutableDictionaryRef hidMatchDictionary = NULL; CFMutableDictionaryRef hidMatchDictionary = NULL;
IOReturn ioReturnValue = kIOReturnSuccess; IOReturn ioReturnValue = kIOReturnSuccess;
io_iterator_t hidObjectIterator = 0; io_iterator_t hidObjectIterator = 0;
io_object_t hidDevice = 0; io_object_t hidDevice = 0;
// Set up a matching dictionary to search the I/O Registry by class // Set up a matching dictionary to search the I/O Registry by class
// name for all HID class devices // name for all HID class devices
hidMatchDictionary = IOServiceMatching(AppleRemoteDeviceName); hidMatchDictionary = IOServiceMatching(AppleRemoteDeviceName);
// Now search I/O Registry for matching devices. // Now search I/O Registry for matching devices.
ioReturnValue = IOServiceGetMatchingServices(kIOMasterPortDefault, hidMatchDictionary, &hidObjectIterator); ioReturnValue = IOServiceGetMatchingServices(kIOMasterPortDefault, hidMatchDictionary, &hidObjectIterator);
if ((ioReturnValue == kIOReturnSuccess) && (hidObjectIterator != 0)) { if ((ioReturnValue == kIOReturnSuccess) && (hidObjectIterator != 0)) {
hidDevice = IOIteratorNext(hidObjectIterator); hidDevice = IOIteratorNext(hidObjectIterator);
} }
// release the iterator // release the iterator
IOObjectRelease(hidObjectIterator); IOObjectRelease(hidObjectIterator);
return hidDevice; return hidDevice;
} }
- (BOOL) initializeCookies { - (BOOL) initializeCookies {
IOHIDDeviceInterface122** handle = (IOHIDDeviceInterface122**)hidDeviceInterface; IOHIDDeviceInterface122** handle = (IOHIDDeviceInterface122**)hidDeviceInterface;
IOHIDElementCookie cookie; IOHIDElementCookie cookie;
long usage; long usage;
long usagePage; long usagePage;
id object; id object;
NSArray* elements = nil; NSArray* elements = nil;
NSDictionary* element; NSDictionary* element;
IOReturn success; IOReturn success;
if (!handle || !(*handle)) return NO; if (!handle || !(*handle)) return NO;
// Copy all elements, since we're grabbing most of the elements /* Copy all elements, since we're grabbing most of the elements
// for this device anyway, and thus, it's faster to iterate them * for this device anyway, and thus, it's faster to iterate them
// ourselves. When grabbing only one or two elements, a matching * ourselves. When grabbing only one or two elements, a matching
// dictionary should be passed in here instead of NULL. * dictionary should be passed in here instead of NULL. */
success = (*handle)->copyMatchingElements(handle, NULL, (CFArrayRef*)&elements); success = (*handle)->copyMatchingElements(handle, NULL, (CFArrayRef*)&elements);
if (success == kIOReturnSuccess) { if (success == kIOReturnSuccess) {
[elements autorelease]; [elements autorelease];
/* /*
cookies = calloc(NUMBER_OF_APPLE_REMOTE_ACTIONS, sizeof(IOHIDElementCookie)); cookies = calloc(NUMBER_OF_APPLE_REMOTE_ACTIONS, sizeof(IOHIDElementCookie));
memset(cookies, 0, sizeof(IOHIDElementCookie) * NUMBER_OF_APPLE_REMOTE_ACTIONS); memset(cookies, 0, sizeof(IOHIDElementCookie) * NUMBER_OF_APPLE_REMOTE_ACTIONS);
*/ */
allCookies = [[NSMutableArray alloc] init]; allCookies = [[NSMutableArray alloc] init];
unsigned int i; int i;
for (i=0; i< [elements count]; i++) { for (i=0; i< [elements count]; i++) {
element = [elements objectAtIndex:i]; element = [elements objectAtIndex:i];
//Get cookie //Get cookie
object = [element valueForKey: (NSString*)CFSTR(kIOHIDElementCookieKey) ]; object = [element valueForKey: (NSString*)CFSTR(kIOHIDElementCookieKey) ];
if (object == nil || ![object isKindOfClass:[NSNumber class]]) continue; if (object == nil || ![object isKindOfClass:[NSNumber class]]) continue;
if (object == 0 || CFGetTypeID(object) != CFNumberGetTypeID()) continue; if (object == 0 || CFGetTypeID(object) != CFNumberGetTypeID()) continue;
cookie = (IOHIDElementCookie) [object longValue]; cookie = (IOHIDElementCookie) [object longValue];
//Get usage //Get usage
object = [element valueForKey: (NSString*)CFSTR(kIOHIDElementUsageKey) ]; object = [element valueForKey: (NSString*)CFSTR(kIOHIDElementUsageKey) ];
if (object == nil || ![object isKindOfClass:[NSNumber class]]) continue; if (object == nil || ![object isKindOfClass:[NSNumber class]]) continue;
usage = [object longValue]; usage = [object longValue];
//Get usage page //Get usage page
object = [element valueForKey: (NSString*)CFSTR(kIOHIDElementUsagePageKey) ]; object = [element valueForKey: (NSString*)CFSTR(kIOHIDElementUsagePageKey) ];
if (object == nil || ![object isKindOfClass:[NSNumber class]]) continue; if (object == nil || ![object isKindOfClass:[NSNumber class]]) continue;
usagePage = [object longValue]; usagePage = [object longValue];
[allCookies addObject: [NSNumber numberWithInt:(int)cookie]]; [allCookies addObject: [NSNumber numberWithInt:(int)cookie]];
} }
} else { } else {
return NO; return NO;
} }
return YES; return YES;
} }
- (BOOL) openDevice { - (BOOL) openDevice {
HRESULT result; HRESULT result;
IOHIDOptionsType openMode = kIOHIDOptionsTypeNone; IOHIDOptionsType openMode = kIOHIDOptionsTypeNone;
if ([self isOpenInExclusiveMode]) openMode = kIOHIDOptionsTypeSeizeDevice; if ([self isOpenInExclusiveMode]) openMode = kIOHIDOptionsTypeSeizeDevice;
IOReturn ioReturnValue = (*hidDeviceInterface)->open(hidDeviceInterface, openMode); IOReturn ioReturnValue = (*hidDeviceInterface)->open(hidDeviceInterface, openMode);
if (ioReturnValue == KERN_SUCCESS) { if (ioReturnValue == KERN_SUCCESS) {
queue = (*hidDeviceInterface)->allocQueue(hidDeviceInterface); queue = (*hidDeviceInterface)->allocQueue(hidDeviceInterface);
if (queue) { if (queue) {
result = (*queue)->create(queue, 0, 12); //depth: maximum number of elements in queue before oldest elements in queue begin to be lost. result = (*queue)->create(queue, 0, 12); //depth: maximum number of elements in queue before oldest elements in queue begin to be lost.
unsigned int i=0; int i=0;
for(i=0; i<[allCookies count]; i++) { for(i=0; i<[allCookies count]; i++) {
IOHIDElementCookie cookie = (IOHIDElementCookie)[[allCookies objectAtIndex:i] intValue]; IOHIDElementCookie cookie = (IOHIDElementCookie)[[allCookies objectAtIndex:i] intValue];
(*queue)->addElement(queue, cookie, 0); (*queue)->addElement(queue, cookie, 0);
} }
// add callback for async events // add callback for async events
CFRunLoopSourceRef eventSource; CFRunLoopSourceRef eventSource;
ioReturnValue = (*queue)->createAsyncEventSource(queue, &eventSource); ioReturnValue = (*queue)->createAsyncEventSource(queue, &eventSource);
if (ioReturnValue == KERN_SUCCESS) { if (ioReturnValue == KERN_SUCCESS) {
ioReturnValue = (*queue)->setEventCallout(queue,QueueCallbackFunction, self, NULL); ioReturnValue = (*queue)->setEventCallout(queue,QueueCallbackFunction, self, NULL);
if (ioReturnValue == KERN_SUCCESS) { if (ioReturnValue == KERN_SUCCESS) {
CFRunLoopAddSource(CFRunLoopGetCurrent(), eventSource, kCFRunLoopDefaultMode); CFRunLoopAddSource(CFRunLoopGetCurrent(), eventSource, kCFRunLoopDefaultMode);
//start data delivery to queue //start data delivery to queue
(*queue)->start(queue); (*queue)->start(queue);
return YES; return YES;
} else { } else {
NSLog(@"Error when setting event callout"); NSLog(@"Error when setting event callout");
} }
} else { } else {
NSLog(@"Error when creating async event source"); NSLog(@"Error when creating async event source");
} }
} else { } else {
NSLog(@"Error when opening device"); NSLog(@"Error when opening device");
} }
} }
return NO; return NO;
}
@end
@implementation AppleRemoteApplicationDelegate
- (id) initWithApplicationDelegate: (id) delegate {
if (self = [super init]) {
applicationDelegate = [delegate retain];
}
return self;
}
- (void) dealloc {
NSLog(@"Dealloc");
[applicationDelegate release];
[super dealloc];
}
- (id) applicationDelegate {
return applicationDelegate;
} }
- (void)applicationWillBecomeActive:(NSNotification *)aNotification {
if ([applicationDelegate respondsToSelector: @selector(applicationWillBecomeActive:)]) {
[applicationDelegate applicationWillBecomeActive: aNotification];
}
}
- (void)applicationDidBecomeActive:(NSNotification *)aNotification {
[[AppleRemote sharedRemote] setListeningToRemote: YES];
if ([applicationDelegate respondsToSelector: @selector(applicationDidBecomeActive:)]) {
[applicationDelegate applicationDidBecomeActive: aNotification];
}
}
- (void)applicationWillResignActive:(NSNotification *)aNotification {
[[AppleRemote sharedRemote] setListeningToRemote: NO];
if ([applicationDelegate respondsToSelector: @selector(applicationWillResignActive:)]) {
[applicationDelegate applicationWillResignActive: aNotification];
}
}
- (void)applicationDidResignActive:(NSNotification *)aNotification {
if ([applicationDelegate respondsToSelector: @selector(applicationDidResignActive:)]) {
[applicationDelegate applicationDidResignActive: aNotification];
}
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature* signature = [super methodSignatureForSelector: aSelector];
if (signature == nil && applicationDelegate != nil) {
signature = [applicationDelegate methodSignatureForSelector: aSelector];
}
return signature;
}
- (void)forwardInvocation:(NSInvocation *)invocation {
SEL aSelector = [invocation selector];
if (applicationDelegate==nil || [applicationDelegate respondsToSelector:aSelector]==NO) {
[super forwardInvocation: invocation];
return;
}
[invocation invokeWithTarget:applicationDelegate];
}
@end @end
\ No newline at end of file
...@@ -286,7 +286,7 @@ struct intf_sys_t ...@@ -286,7 +286,7 @@ struct intf_sys_t
int i_lastShownVolume; int i_lastShownVolume;
AppleRemote * o_remote; AppleRemote * o_remote;
BOOL b_left_right_remote_button_hold; /* true as long as the user holds the left or right button on the remote control */ BOOL b_remote_button_hold; /* true as long as the user holds the left,right,plus or minus on the remote control */
} }
+ (VLCMain *)sharedInstance; + (VLCMain *)sharedInstance;
......
...@@ -350,6 +350,7 @@ static VLCMain *_o_sharedMainInstance = nil; ...@@ -350,6 +350,7 @@ static VLCMain *_o_sharedMainInstance = nil;
i_lastShownVolume = -1; i_lastShownVolume = -1;
o_remote = [[AppleRemote alloc] init]; o_remote = [[AppleRemote alloc] init];
[o_remote setClickCountEnabledButtons: kRemoteButtonPlay];
[o_remote setDelegate: _o_sharedMainInstance]; [o_remote setDelegate: _o_sharedMainInstance];
return _o_sharedMainInstance; return _o_sharedMainInstance;
...@@ -706,52 +707,56 @@ static VLCMain *_o_sharedMainInstance = nil; ...@@ -706,52 +707,56 @@ static VLCMain *_o_sharedMainInstance = nil;
[o_remote stopListening: self]; [o_remote stopListening: self];
} }
/* Helper method for the remote control interface in order to trigger forward/backward as long /* Helper method for the remote control interface in order to trigger forward/backward and volume
as the user holds the left/right button */ increase/decrease as long as the user holds the left/right, plus/minus button */
- (void) triggerMovieStepForRemoteButton: (NSNumber*) buttonIdentifierNumber - (void) executeHoldActionForRemoteButton: (NSNumber*) buttonIdentifierNumber
{ {
if (b_left_right_remote_button_hold) { if (b_remote_button_hold)
switch([buttonIdentifierNumber intValue]) { {
switch([buttonIdentifierNumber intValue])
{
case kRemoteButtonRight_Hold: case kRemoteButtonRight_Hold:
[o_controls forward: self]; [o_controls forward: self];
break; break;
case kRemoteButtonLeft_Hold: case kRemoteButtonLeft_Hold:
[o_controls backward: self]; [o_controls backward: self];
break; break;
case kRemoteButtonVolume_Plus_Hold:
[o_controls volumeUp: self];
break;
case kRemoteButtonVolume_Minus_Hold:
[o_controls volumeDown: self];
break;
} }
if (b_left_right_remote_button_hold) { if (b_remote_button_hold)
{
/* trigger event */ /* trigger event */
[self performSelector:@selector(triggerMovieStepForRemoteButton:) [self performSelector:@selector(executeHoldActionForRemoteButton:)
withObject:buttonIdentifierNumber withObject:buttonIdentifierNumber
afterDelay:0.25]; afterDelay:0.25];
} }
} }
} }
/* Apple Remote callback */ /* Apple Remote callback */
- (void)appleRemoteButton:(AppleRemoteEventIdentifier)buttonIdentifier - (void) appleRemoteButton: (AppleRemoteEventIdentifier)buttonIdentifier
pressedDown:(BOOL)pressedDown pressedDown: (BOOL) pressedDown
clickCount: (unsigned int) count
{ {
switch( buttonIdentifier ) switch( buttonIdentifier )
{ {
case kRemoteButtonPlay: case kRemoteButtonPlay:
[o_controls play: self]; if (count >= 2) {
[o_controls toogleFullscreen:self];
} else {
[o_controls play: self];
}
break; break;
case kRemoteButtonVolume_Plus: case kRemoteButtonVolume_Plus:
/* there are two events when the plus or minus button is pressed [o_controls volumeUp: self];
one when the button is pressed down and one when the button is released */
if( pressedDown )
{
[o_controls volumeUp: self];
}
break; break;
case kRemoteButtonVolume_Minus: case kRemoteButtonVolume_Minus:
/* there are two events when the plus or minus button is pressed [o_controls volumeDown: self];
one when the button is pressed down and one when the button is released */
if( pressedDown )
{
[o_controls volumeDown: self];
}
break; break;
case kRemoteButtonRight: case kRemoteButtonRight:
[o_controls next: self]; [o_controls next: self];
...@@ -761,17 +766,19 @@ static VLCMain *_o_sharedMainInstance = nil; ...@@ -761,17 +766,19 @@ static VLCMain *_o_sharedMainInstance = nil;
break; break;
case kRemoteButtonRight_Hold: case kRemoteButtonRight_Hold:
case kRemoteButtonLeft_Hold: case kRemoteButtonLeft_Hold:
case kRemoteButtonVolume_Plus_Hold:
case kRemoteButtonVolume_Minus_Hold:
/* simulate an event as long as the user holds the button */ /* simulate an event as long as the user holds the button */
b_left_right_remote_button_hold = pressedDown; b_remote_button_hold = pressedDown;
if( pressedDown ) if( pressedDown )
{ {
NSNumber* buttonIdentifierNumber = [NSNumber numberWithInt: buttonIdentifier]; NSNumber* buttonIdentifierNumber = [NSNumber numberWithInt: buttonIdentifier];
[self performSelector:@selector(triggerMovieStepForRemoteButton:) [self performSelector:@selector(executeHoldActionForRemoteButton:)
withObject:buttonIdentifierNumber]; withObject:buttonIdentifierNumber];
} }
break; break;
case kRemoteButtonMenu: case kRemoteButtonMenu:
[o_controls windowAction: self]; [o_controls showPosition: self];
break; break;
default: default:
/* Add here whatever you want other buttons to do */ /* Add here whatever you want other buttons to do */
......
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