diff --git a/desmume/src/cocoa/cocoa_hid.mm b/desmume/src/cocoa/cocoa_hid.mm index 18e3ca728..8a4edc521 100644 --- a/desmume/src/cocoa/cocoa_hid.mm +++ b/desmume/src/cocoa/cocoa_hid.mm @@ -323,6 +323,15 @@ static NSDictionary *hidUsageTable = nil; IOHIDElementRef hidElementRef = IOHIDValueGetElement(hidValueRef); NSInteger elementUsagePage = IOHIDElementGetUsagePage(hidElementRef); NSInteger elementUsage = IOHIDElementGetUsage(hidElementRef); + + // IOHIDValueGetIntegerValue() will crash if the value length is too large. + // Do a bounds check here to prevent crashing. This workaround makes the PS3 + // controller usable, since it returns a length of 39 on some elements. + if(IOHIDValueGetLength(hidValueRef) > 2) + { + return inputAttributesList; + } + NSInteger logicalValue = IOHIDValueGetIntegerValue(hidValueRef); NSInteger logicalMin = IOHIDElementGetLogicalMin(hidElementRef); NSInteger logicalMax = IOHIDElementGetLogicalMax(hidElementRef); @@ -335,8 +344,8 @@ static NSDictionary *hidUsageTable = nil; } else { - NSInteger lowerThreshold = ((logicalMax - logicalMin) / 4) + logicalMin; - NSInteger upperThreshold = (((logicalMax - logicalMin) * 3) / 4) + logicalMin; + NSInteger lowerThreshold = ((logicalMax - logicalMin) / 3) + logicalMin; + NSInteger upperThreshold = (((logicalMax - logicalMin) * 2) / 3) + logicalMin; NSNumber *onState = [NSNumber numberWithBool:YES]; NSNumber *offState = [NSNumber numberWithBool:NO]; NSString *elementCodeLowerThreshold = [NSString stringWithFormat:@"0x%04X/0x%04X/LowerThreshold", elementUsagePage, elementUsage]; @@ -829,11 +838,14 @@ void HandleQueueValueAvailableCallback(void *inContext, IOReturn inResult, void inputAttributesList = [CocoaHIDDevice inputArrayFromHIDValue:hidValueRef]; } - // HID input devices don't register events, so we need to manually prevent - // sleep and screensaver whenever we detect an input. - UpdateSystemActivity(UsrActivity); - - [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadName:@"com.DeSmuME.DeSmuME.hidInputDetected" object:inputAttributesList userInfo:nil]; + if (inputAttributesList != nil) + { + // HID input devices don't register events, so we need to manually prevent + // sleep and screensaver whenever we detect an input. + UpdateSystemActivity(UsrActivity); + + [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadName:@"com.DeSmuME.DeSmuME.hidInputDetected" object:inputAttributesList userInfo:nil]; + } CFRelease(hidValueRef); } while (1); diff --git a/desmume/src/cocoa/cocoa_input.mm b/desmume/src/cocoa/cocoa_input.mm index d375a277f..15bbc9e53 100644 --- a/desmume/src/cocoa/cocoa_input.mm +++ b/desmume/src/cocoa/cocoa_input.mm @@ -72,6 +72,21 @@ - (void) set:(NSString *)elementCode attributes:(NSDictionary *)mappingAttributes { NSString *keyString = [[self.deviceCode stringByAppendingString:@":"] stringByAppendingString:elementCode]; + + // Remove any input mappings with the same DS control name as the new mapping. + // We're doing this check since the current UI doesn't support multiple bindings per control at this time. + // When the UI is updated to support multiple bindings per control, this check will need to be removed. + NSArray *mappingKeys = [self.map allKeys]; + for (NSString *key in mappingKeys) + { + NSDictionary *inputMapping = (NSDictionary *)[self.map valueForKey:key]; + NSString *dsControlName = (NSString *)[inputMapping valueForKey:@"name"]; + if ([dsControlName isEqualToString:(NSString *)[mappingAttributes valueForKey:@"name"]]) + { + [self.map setValue:nil forKey:key]; + } + } + [self.map setValue:mappingAttributes forKey:keyString]; } diff --git a/desmume/src/cocoa/userinterface/inputPrefsView.h b/desmume/src/cocoa/userinterface/inputPrefsView.h index 2e4fb3809..da23ec633 100644 --- a/desmume/src/cocoa/userinterface/inputPrefsView.h +++ b/desmume/src/cocoa/userinterface/inputPrefsView.h @@ -26,6 +26,7 @@ NSWindow *prefWindow; NSButton *lastConfigButton; NSInteger configInput; + NSMutableDictionary *configInputList; NSDictionary *keyNameTable; CocoaDSController *cdsController; diff --git a/desmume/src/cocoa/userinterface/inputPrefsView.mm b/desmume/src/cocoa/userinterface/inputPrefsView.mm index 8ea736fdb..60799cd41 100644 --- a/desmume/src/cocoa/userinterface/inputPrefsView.mm +++ b/desmume/src/cocoa/userinterface/inputPrefsView.mm @@ -22,6 +22,8 @@ #import "cocoa_globals.h" #import "cocoa_input.h" +#define INPUT_HOLD_TIME 0.1 // Time in seconds to hold a button in its on state when mapping an input. + @implementation InputPrefsView @synthesize prefWindow; @@ -38,6 +40,7 @@ lastConfigButton = nil; configInput = 0; + configInputList = [[NSMutableDictionary alloc] initWithCapacity:32]; keyNameTable = [[NSDictionary alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"KeyNames" ofType:@"plist"]]; cdsController = nil; @@ -53,6 +56,7 @@ { [[NSNotificationCenter defaultCenter] removeObserver:self]; + [configInputList release]; [keyNameTable release]; [super dealloc]; @@ -348,6 +352,7 @@ if ([theButton state] == NSOnState) { lastConfigButton = theButton; + [configInputList removeAllObjects]; configInput = [theButton tag]; } else @@ -398,17 +403,39 @@ NSNumber *onState = (NSNumber *)[inputProperties valueForKey:@"on"]; if (onState != nil) { + NSString *deviceCode = (NSString *)[inputProperties valueForKey:@"deviceCode"]; + NSString *elementCode = (NSString *)[inputProperties valueForKey:@"elementCode"]; + NSString *deviceElementCode = [[deviceCode stringByAppendingString:@":"] stringByAppendingString:elementCode]; + NSDate *inputOnDate = [configInputList valueForKey:deviceElementCode]; + inputOn = [onState boolValue]; if (inputOn) { - NSString *deviceCode = (NSString *)[inputProperties valueForKey:@"deviceCode"]; - NSString *deviceName = (NSString *)[inputProperties valueForKey:@"deviceName"]; - NSString *elementCode = (NSString *)[inputProperties valueForKey:@"elementCode"]; - NSString *elementName = (NSString *)[inputProperties valueForKey:@"elementName"]; - - [self addMappingById:configInput deviceCode:deviceCode deviceName:deviceName elementCode:elementCode elementName:elementName]; - [self inputButtonCancelConfig]; - break; + if (inputOnDate == nil) + { + [configInputList setValue:[NSDate date] forKey:deviceElementCode]; + } + } + else + { + if (inputOnDate != nil) + { + if (([inputOnDate timeIntervalSinceNow] * -1.0) < INPUT_HOLD_TIME) + { + // If the button isn't held for at least INPUT_HOLD_TIME seconds, then reject the input. + [configInputList setValue:nil forKey:deviceElementCode]; + } + else + { + // Add the input mapping. + NSString *deviceName = (NSString *)[inputProperties valueForKey:@"deviceName"]; + NSString *elementName = (NSString *)[inputProperties valueForKey:@"elementName"]; + + [self addMappingById:configInput deviceCode:deviceCode deviceName:deviceName elementCode:elementCode elementName:elementName]; + [self inputButtonCancelConfig]; + break; + } + } } } }