X-Git-Url: https://git.yukkurigames.com/?p=enjoyable.git;a=blobdiff_plain;f=Classes%2FNJDeviceController.m;h=2ce3fa93c8f72bb2f6acb4f32b71d9540f5bc76e;hp=5a6603c9bac55f5ffc92572e41971d2232d290c8;hb=235c087385a6e959ba7edefe4a3cbbbc00b2a534;hpb=1d9578185de7fb08cf1f4b1e42812e87d8e18040 diff --git a/Classes/NJDeviceController.m b/Classes/NJDeviceController.m index 5a6603c..2ce3fa9 100644 --- a/Classes/NJDeviceController.m +++ b/Classes/NJDeviceController.m @@ -26,19 +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(setup) - name:NSApplicationDidFinishLaunchingNotification + 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 { @@ -49,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 @@ -163,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), @@ -173,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 { @@ -240,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