X-Git-Url: https://git.yukkurigames.com/?p=enjoyable.git;a=blobdiff_plain;f=Classes%2FNJDeviceController.m;h=2ce3fa93c8f72bb2f6acb4f32b71d9540f5bc76e;hp=8417927dbb5ddb029c7a530150d842bef13ebd2c;hb=235c087385a6e959ba7edefe4a3cbbbc00b2a534;hpb=53afd7c25f4423020c805c3587875cdce99ba3f4 diff --git a/Classes/NJDeviceController.m b/Classes/NJDeviceController.m index 8417927..2ce3fa9 100644 --- a/Classes/NJDeviceController.m +++ b/Classes/NJDeviceController.m @@ -26,14 +26,39 @@ if ((self = [super init])) { _devices = [[NSMutableArray alloc] initWithCapacity:16]; _continousOutputs = [[NSMutableArray alloc] initWithCapacity:32]; + [NSNotificationCenter.defaultCenter + addObserver:self + selector:@selector(applicationDidFinishLaunching:) + name:NSApplicationDidFinishLaunchingNotification + object:nil]; + + // The HID manager uses 5-10ms per second doing basically + // nothing if a noisy device is plugged in (the % of that + // spent in input_callback is negligible, so it's not + // something we can make faster). I don't really think that's + // acceptable, CPU/power wise. So if translation is disabled + // and the window is closed, just switch off the HID manager + // entirely. This probably also has some marginal benefits for + // compatibility with other applications that want exclusive + // grabs. + [NSNotificationCenter.defaultCenter + addObserver:self + selector:@selector(closeHidIfDisabled:) + name:NSApplicationDidResignActiveNotification + object:nil]; + [NSNotificationCenter.defaultCenter + addObserver:self + selector:@selector(openHid) + name:NSApplicationDidBecomeActiveNotification + object:nil]; } return self; } - (void)dealloc { + [NSNotificationCenter.defaultCenter removeObserver:self]; [_continuousOutputsTick invalidate]; - IOHIDManagerClose(_hidManager, kIOHIDOptionsTypeNone); - CFRelease(_hidManager); + [self closeHid]; } - (void)expandRecursive:(id )pathElement { @@ -44,7 +69,10 @@ } - (void)addRunningOutput:(NJOutput *)output { - [_continousOutputs addObject:output]; + // Axis events will trigger every small movement, don't keep + // re-adding them or they trigger multiple times each time. + if (![_continousOutputs containsObject:output]) + [_continousOutputs addObject:output]; if (!_continuousOutputsTick) { _continuousOutputsTick = [NSTimer scheduledTimerWithTimeInterval:1.0/60.0 target:self @@ -158,8 +186,12 @@ static void remove_callback(void *ctx, IOReturn inResult, void *inSender, IOHIDD #define NSSTR(e) ((NSString *)CFSTR(e)) -- (void)setup { +- (void)openHid { + if (_hidManager) + return; + NSLog(@"Opening HID manager."); _hidManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); + IOHIDManagerScheduleWithRunLoop(_hidManager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); NSArray *criteria = @[ @{ NSSTR(kIOHIDDeviceUsagePageKey) : @(kHIDPage_GenericDesktop), NSSTR(kIOHIDDeviceUsageKey) : @(kHIDUsage_GD_Joystick) }, @{ NSSTR(kIOHIDDeviceUsagePageKey) : @(kHIDPage_GenericDesktop), @@ -168,25 +200,37 @@ static void remove_callback(void *ctx, IOReturn inResult, void *inSender, IOHIDD NSSTR(kIOHIDDeviceUsageKey) : @(kHIDUsage_GD_MultiAxisController) } ]; IOHIDManagerSetDeviceMatchingMultiple(_hidManager, (__bridge CFArrayRef)criteria); - - IOHIDManagerScheduleWithRunLoop(_hidManager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); IOReturn ret = IOHIDManagerOpen(_hidManager, kIOHIDOptionsTypeNone); if (ret != kIOReturnSuccess) { - [[NSAlert alertWithMessageText:@"Input devices are unavailable" + [[NSAlert alertWithMessageText:NSLocalizedString(@"input devices unavailable", + @"error title when devices can't be read") defaultButton:nil alternateButton:nil otherButton:nil - informativeTextWithFormat:@"Error 0x%08x occured trying to access your devices. " - @"Input may not be correctly detected or mapped.", - ret] - beginSheetModalForWindow:outlineView.window - modalDelegate:nil - didEndSelector:nil - contextInfo:nil]; + informativeTextWithFormat:NSLocalizedString(@"input error 0x%08x occurred", + @"message containing IOReturn failure code when devices can't be read"), ret] + beginSheetModalForWindow:outlineView.window + modalDelegate:nil + didEndSelector:nil + contextInfo:nil]; + [self closeHid]; + } else { + IOHIDManagerRegisterDeviceMatchingCallback(_hidManager, add_callback, (__bridge void *)self); + IOHIDManagerRegisterDeviceRemovalCallback(_hidManager, remove_callback, (__bridge void *)self); } - - IOHIDManagerRegisterDeviceMatchingCallback(_hidManager, add_callback, (__bridge void *)self); - IOHIDManagerRegisterDeviceRemovalCallback(_hidManager, remove_callback, (__bridge void *)self); +} + +- (void)closeHid { + if (_hidManager) { + NSLog(@"Closing HID manager."); + IOHIDManagerUnscheduleFromRunLoop(_hidManager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); + IOHIDManagerClose(_hidManager, kIOHIDOptionsTypeNone); + CFRelease(_hidManager); + _hidManager = NULL; + } + [_devices removeAllObjects]; + [outlineView reloadData]; + connectDevicePrompt.hidden = !!_devices.count; } - (NJInput *)selectedInput { @@ -235,18 +279,33 @@ objectValueForTableColumn:(NSTableColumn *)tableColumn _translatingEvents = translatingEvents; NSInteger state = translatingEvents ? NSOnState : NSOffState; translatingEventsButton.state = state; - translatingEventsMenu.title = translatingEvents ? @"Disable" : @"Enable"; NSString *name = translatingEvents ? NJEventTranslationActivated : NJEventTranslationDeactivated; [NSNotificationCenter.defaultCenter postNotificationName:name object:self]; + + if (!translatingEvents && !NSApplication.sharedApplication.isActive) + [self closeHid]; + else if (translatingEvents || NSApplication.sharedApplication.isActive) + [self openHid]; } } +- (void)closeHidIfDisabled:(NSNotification *)application { + if (!self.translatingEvents) + [self closeHid]; +} + +- (void)applicationDidFinishLaunching:(NSNotification *)application { + // NSApplicationWillBecomeActiveNotification occurs just slightly + // too late - there's one tick where the UI is showing "No + // devices" even with a device plugged in. + [self openHid]; +} + - (IBAction)translatingEventsChanged:(NSButton *)sender { self.translatingEvents = sender.state == NSOnState; } - @end