From: Felix Paul Kühne Date: Mon, 27 Nov 2006 21:20:23 +0000 (+0000) Subject: * various improvements to the AppleRemote support by Martin Kahr ... X-Git-Tag: 0.9.0-test0~9178 X-Git-Url: https://git.sesse.net/?a=commitdiff_plain;h=92c362106fb9166bfa16f79ec0b16a7de5b8b086;p=vlc * various improvements to the AppleRemote support by Martin Kahr 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. --- diff --git a/modules/gui/macosx/AppleRemote.h b/modules/gui/macosx/AppleRemote.h index d53225f84e..57b7010145 100644 --- a/modules/gui/macosx/AppleRemote.h +++ b/modules/gui/macosx/AppleRemote.h @@ -61,38 +61,51 @@ enum AppleRemoteEventIdentifier { - kRemoteButtonVolume_Plus=0, - kRemoteButtonVolume_Minus, - kRemoteButtonMenu, - kRemoteButtonPlay, - kRemoteButtonRight, - kRemoteButtonLeft, - kRemoteButtonRight_Hold, - kRemoteButtonLeft_Hold, - kRemoteButtonMenu_Hold, - kRemoteButtonPlay_Sleep, - kRemoteControl_Switched + kRemoteButtonVolume_Plus =1<<1, + kRemoteButtonVolume_Minus =1<<2, + kRemoteButtonMenu =1<<3, + kRemoteButtonPlay =1<<4, + kRemoteButtonRight =1<<5, + kRemoteButtonLeft =1<<6, + kRemoteButtonRight_Hold =1<<7, + kRemoteButtonLeft_Hold =1<<8, + kRemoteButtonMenu_Hold =1<<9, + kRemoteButtonPlay_Sleep =1<<10, + kRemoteControl_Switched =1<<11, + kRemoteButtonVolume_Plus_Hold =1<<12, + kRemoteButtonVolume_Minus_Hold =1<<13 }; 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 +/* 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; + IOHIDDeviceInterface** hidDeviceInterface; + IOHIDQueueInterface** queue; + NSMutableArray* allCookies; + NSMutableDictionary* cookieToButtonMapping; + + BOOL openInExclusiveMode; + BOOL simulatePlusMinusHold; + BOOL processesBacklog; + + /* 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; - (BOOL) isRemoteAvailable; @@ -103,6 +116,42 @@ typedef enum AppleRemoteEventIdentifier AppleRemoteEventIdentifier; - (BOOL) isOpenInExclusiveMode; - (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; - (id) delegate; @@ -116,15 +165,15 @@ typedef enum AppleRemoteEventIdentifier AppleRemoteEventIdentifier; @end -/* Method definitions for the delegate of the AppleRemote class -*/ +/* Method definitions for the delegate of the AppleRemote class */ @interface NSObject(NSAppleRemoteDelegate) -- (void) appleRemoteButton: (AppleRemoteEventIdentifier)buttonIdentifier pressedDown: (BOOL) pressedDown; +- (void) appleRemoteButton: (AppleRemoteEventIdentifier)buttonIdentifier pressedDown: (BOOL) pressedDown clickCount: (unsigned int) count; @end @interface AppleRemote (PrivateMethods) +- (void) setRemoteId: (int) aValue; - (NSDictionary*) cookieToButtonMapping; - (IOHIDQueueInterface**) queue; - (IOHIDDeviceInterface**) hidDeviceInterface; @@ -136,4 +185,15 @@ typedef enum AppleRemoteEventIdentifier AppleRemoteEventIdentifier; - (IOHIDDeviceInterface**) createInterfaceForDevice: (io_object_t) hidDevice; - (BOOL) initializeCookies; - (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 \ No newline at end of file diff --git a/modules/gui/macosx/AppleRemote.m b/modules/gui/macosx/AppleRemote.m index 1b5dafe6e5..fb81d6797c 100644 --- a/modules/gui/macosx/AppleRemote.m +++ b/modules/gui/macosx/AppleRemote.m @@ -55,141 +55,204 @@ const char* AppleRemoteDeviceName = "AppleIRController"; 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 -- (id) init -{ - self = [super 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; +#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_"]; + [cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonVolume_Minus] forKey:@"14_13_11_6_"]; + [cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMenu] forKey:@"14_7_6_14_7_6_"]; + [cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlay] forKey:@"14_8_6_14_8_6_"]; + [cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonRight] forKey:@"14_9_6_14_9_6_"]; + [cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonLeft] forKey:@"14_10_6_14_10_6_"]; + [cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonRight_Hold] forKey:@"14_6_4_2_"]; + [cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonLeft_Hold] forKey:@"14_6_3_2_"]; + [cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMenu_Hold] forKey:@"14_6_14_6_"]; + [cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlay_Sleep] forKey:@"18_14_6_18_14_6_"]; + [cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteControl_Switched] forKey:@"19_"]; + + /* defaults */ + [self setSimulatesPlusMinusHold: YES]; + maxClickTimeDifference = DEFAULT_MAXIMUM_CLICK_TIME_DIFFERENCE; + } + + return self; } - (void) dealloc { - [self stopListening:self]; - [cookieToButtonMapping release]; - [super dealloc]; + [self stopListening:self]; + [cookieToButtonMapping release]; + [super dealloc]; } -- (void) setRemoteId: (int) value { - remoteId = value; -} - (int) remoteId { - return remoteId; + return remoteId; } -- (BOOL) isRemoteAvailable { - io_object_t hidDevice = [self findAppleRemoteDevice]; - if (hidDevice != 0) { - IOObjectRelease(hidDevice); - return YES; - } else { - return NO; - } +- (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); + return (hidDeviceInterface != NULL && allCookies != NULL && queue != NULL); } - (void) setListeningToRemote: (BOOL) value { - if (value == NO) { - [self stopListening:self]; - } else { - [self startListening:self]; - } + if (value == NO) { + [self stopListening:self]; + } else { + [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 { - if ([_delegate respondsToSelector:@selector(appleRemoteButton:pressedDown:)]==NO) return; - - [_delegate retain]; - [delegate release]; - delegate = _delegate; + if (_delegate && [_delegate respondsToSelector:@selector(appleRemoteButton:pressedDown:clickCount:)]==NO) return; + + delegate = _delegate; } - (id) delegate { - return delegate; + return delegate; } - (BOOL) isOpenInExclusiveMode { - return openInExclusiveMode; + 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; - + openInExclusiveMode = value; +} + +- (BOOL) clickCountingEnabled { + return clickCountEnabledButtons != 0; +} +- (void) setClickCountingEnabled: (BOOL) value { + if (value) { + [self setClickCountEnabledButtons: kRemoteButtonVolume_Plus | kRemoteButtonVolume_Minus | kRemoteButtonPlay | kRemoteButtonLeft | kRemoteButtonRight | kRemoteButtonMenu]; + } else { + [self setClickCountEnabledButtons: 0]; + } +} + +- (unsigned int) clickCountEnabledButtons { + return clickCountEnabledButtons; +} +- (void) setClickCountEnabledButtons: (unsigned int)value { + clickCountEnabledButtons = value; +} + +- (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: - [self stopListening:self]; - -cleanup: - IOObjectRelease(hidDevice); + [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; - } + 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 @@ -198,20 +261,20 @@ cleanup: static AppleRemote* sharedInstance=nil; -+ (AppleRemote*) sharedRemote { - @synchronized(self) { ++ (AppleRemote*) sharedRemote { + @synchronized(self) { if (sharedInstance == nil) { sharedInstance = [[self alloc] init]; } } - return sharedInstance; + return sharedInstance; } + (id)allocWithZone:(NSZone *)zone { @synchronized(self) { if (sharedInstance == nil) { return [super allocWithZone:zone]; } - } + } return sharedInstance; } - (id)copyWithZone:(NSZone *)zone { @@ -234,217 +297,408 @@ static AppleRemote* sharedInstance=nil; @implementation AppleRemote (PrivateMethods) +- (void) setRemoteId: (int) value { + remoteId = value; +} + - (IOHIDQueueInterface**) queue { - return queue; + return queue; } - (IOHIDDeviceInterface**) hidDeviceInterface { - return hidDeviceInterface; + return hidDeviceInterface; } - (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 { - 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); - } + /* + if (previousRemainingCookieString) { + cookieString = [previousRemainingCookieString stringByAppendingString: cookieString]; + NSLog(@"New cookie string is %@", cookieString); + [previousRemainingCookieString release], previousRemainingCookieString=nil; + }*/ + 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 -/* 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 */ -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]; - +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; + + //printf("%d %d %d\n", event.elementCookie, event.value, event.longValue); + + if (REMOTE_SWITCH_COOKIE == (int)event.elementCookie) { + [remote setRemoteId: event.value]; + [remote handleEventWithCookieString: @"19_" sumOfValues: 0]; + } else { + if (((int)event.elementCookie)!=5) { + sumOfValues+=event.value; + [cookieString appendString:[NSString stringWithFormat:@"%d_", event.elementCookie]]; + } + } + } + + [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_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; + 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]; - unsigned 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; + 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. - - unsigned 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; + 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 + +@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 \ No newline at end of file diff --git a/modules/gui/macosx/intf.h b/modules/gui/macosx/intf.h index 4ba82f6461..91555d9aa4 100644 --- a/modules/gui/macosx/intf.h +++ b/modules/gui/macosx/intf.h @@ -286,7 +286,7 @@ struct intf_sys_t int i_lastShownVolume; 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; diff --git a/modules/gui/macosx/intf.m b/modules/gui/macosx/intf.m index 58fa96578f..fae21c97ad 100644 --- a/modules/gui/macosx/intf.m +++ b/modules/gui/macosx/intf.m @@ -350,6 +350,7 @@ static VLCMain *_o_sharedMainInstance = nil; i_lastShownVolume = -1; o_remote = [[AppleRemote alloc] init]; + [o_remote setClickCountEnabledButtons: kRemoteButtonPlay]; [o_remote setDelegate: _o_sharedMainInstance]; return _o_sharedMainInstance; @@ -706,52 +707,56 @@ static VLCMain *_o_sharedMainInstance = nil; [o_remote stopListening: self]; } -/* Helper method for the remote control interface in order to trigger forward/backward as long - as the user holds the left/right button */ -- (void) triggerMovieStepForRemoteButton: (NSNumber*) buttonIdentifierNumber +/* Helper method for the remote control interface in order to trigger forward/backward and volume + increase/decrease as long as the user holds the left/right, plus/minus button */ +- (void) executeHoldActionForRemoteButton: (NSNumber*) buttonIdentifierNumber { - if (b_left_right_remote_button_hold) { - switch([buttonIdentifierNumber intValue]) { + if (b_remote_button_hold) + { + switch([buttonIdentifierNumber intValue]) + { case kRemoteButtonRight_Hold: - [o_controls forward: self]; + [o_controls forward: self]; break; case kRemoteButtonLeft_Hold: - [o_controls backward: self]; - break; + [o_controls backward: self]; + 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 */ - [self performSelector:@selector(triggerMovieStepForRemoteButton:) - withObject:buttonIdentifierNumber - afterDelay:0.25]; + [self performSelector:@selector(executeHoldActionForRemoteButton:) + withObject:buttonIdentifierNumber + afterDelay:0.25]; } } } /* Apple Remote callback */ -- (void)appleRemoteButton:(AppleRemoteEventIdentifier)buttonIdentifier - pressedDown:(BOOL)pressedDown +- (void) appleRemoteButton: (AppleRemoteEventIdentifier)buttonIdentifier + pressedDown: (BOOL) pressedDown + clickCount: (unsigned int) count { switch( buttonIdentifier ) { case kRemoteButtonPlay: - [o_controls play: self]; + if (count >= 2) { + [o_controls toogleFullscreen:self]; + } else { + [o_controls play: self]; + } break; case kRemoteButtonVolume_Plus: - /* there are two events when the plus or minus button is pressed - one when the button is pressed down and one when the button is released */ - if( pressedDown ) - { - [o_controls volumeUp: self]; - } + [o_controls volumeUp: self]; break; case kRemoteButtonVolume_Minus: - /* there are two events when the plus or minus button is pressed - one when the button is pressed down and one when the button is released */ - if( pressedDown ) - { - [o_controls volumeDown: self]; - } + [o_controls volumeDown: self]; break; case kRemoteButtonRight: [o_controls next: self]; @@ -761,17 +766,19 @@ static VLCMain *_o_sharedMainInstance = nil; break; case kRemoteButtonRight_Hold: case kRemoteButtonLeft_Hold: + case kRemoteButtonVolume_Plus_Hold: + case kRemoteButtonVolume_Minus_Hold: /* 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 ) { - NSNumber* buttonIdentifierNumber = [NSNumber numberWithInt: buttonIdentifier]; - [self performSelector:@selector(triggerMovieStepForRemoteButton:) + NSNumber* buttonIdentifierNumber = [NSNumber numberWithInt: buttonIdentifier]; + [self performSelector:@selector(executeHoldActionForRemoteButton:) withObject:buttonIdentifierNumber]; } break; case kRemoteButtonMenu: - [o_controls windowAction: self]; + [o_controls showPosition: self]; break; default: /* Add here whatever you want other buttons to do */