X-Git-Url: https://git.yukkurigames.com/?p=enjoyable.git;a=blobdiff_plain;f=Classes%2FNJDeviceController.m;h=1b64aaf3f694e5dd26eb3c8324ebf0de77c82e04;hp=db821bf73b6a720825c3be814127f36421a67d74;hb=6cee2033d1c0fc0dacf444064305b9e7e87672a9;hpb=e860f52adf6efd7edf51f9b41add107d33bb483f diff --git a/Classes/NJDeviceController.m b/Classes/NJDeviceController.m index db821bf..1b64aaf 100644 --- a/Classes/NJDeviceController.m +++ b/Classes/NJDeviceController.m @@ -12,45 +12,77 @@ #import "NJDevice.h" #import "NJInput.h" #import "NJOutput.h" -#import "NJOutputController.h" #import "NJEvents.h" @implementation NJDeviceController { - IOHIDManagerRef hidManager; - NSTimer *continuousTimer; - NSMutableArray *runningOutputs; + NJHIDManager *_hidManager; + NSTimer *_continuousOutputsTick; + NSMutableArray *_continousOutputs; NSMutableArray *_devices; } +#define NSSTR(e) ((NSString *)CFSTR(e)) + - (id)init { if ((self = [super init])) { _devices = [[NSMutableArray alloc] initWithCapacity:16]; - runningOutputs = [[NSMutableArray alloc] initWithCapacity:32]; + _continousOutputs = [[NSMutableArray alloc] initWithCapacity:32]; + + _hidManager = [[NJHIDManager alloc] initWithCriteria:@[ + @{ NSSTR(kIOHIDDeviceUsagePageKey) : @(kHIDPage_GenericDesktop), + NSSTR(kIOHIDDeviceUsageKey) : @(kHIDUsage_GD_Joystick) }, + @{ NSSTR(kIOHIDDeviceUsagePageKey) : @(kHIDPage_GenericDesktop), + NSSTR(kIOHIDDeviceUsageKey) : @(kHIDUsage_GD_GamePad) }, + @{ NSSTR(kIOHIDDeviceUsagePageKey) : @(kHIDPage_GenericDesktop), + NSSTR(kIOHIDDeviceUsageKey) : @(kHIDUsage_GD_MultiAxisController) } + ] + delegate:self]; + + // 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 simulation 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(stopHidIfDisabled:) + name:NSApplicationDidResignActiveNotification + object:nil]; + [NSNotificationCenter.defaultCenter + addObserver:self + selector:@selector(startHid) + name:NSApplicationDidBecomeActiveNotification + object:nil]; } return self; } - (void)dealloc { - [continuousTimer invalidate]; - IOHIDManagerClose(hidManager, kIOHIDOptionsTypeNone); - CFRelease(hidManager); + [NSNotificationCenter.defaultCenter removeObserver:self]; + [_continuousOutputsTick invalidate]; } -- (void)expandRecursive:(id )pathElement { - if (pathElement) { - [self expandRecursive:pathElement.base]; - [outlineView expandItem:pathElement]; - } +- (NJDevice *)objectAtIndexedSubscript:(NSUInteger)idx { + return idx < _devices.count ? _devices[idx] : nil; +} + +- (NSUInteger)count { + return _devices.count; } - (void)addRunningOutput:(NJOutput *)output { - if (![runningOutputs containsObject:output]) { - [runningOutputs addObject:output]; - } - if (!continuousTimer) { - continuousTimer = [NSTimer scheduledTimerWithTimeInterval:1.f/60.f + // 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 - selector:@selector(updateContinuousInputs:) + selector:@selector(updateContinuousOutputs:) userInfo:nil repeats:YES]; } @@ -76,48 +108,39 @@ if (!handler) return; - [self expandRecursive:handler]; - [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:[outlineView rowForItem:handler]] byExtendingSelection: NO]; - [outputController focusKey]; + [self.delegate deviceController:self didInput:handler]; } -static void input_callback(void *ctx, IOReturn inResult, void *inSender, IOHIDValueRef value) { - NJDeviceController *controller = (__bridge NJDeviceController *)ctx; - IOHIDDeviceRef device = IOHIDQueueGetDevice(inSender); - - if (controller.translatingEvents) { - [controller runOutputForDevice:device value:value]; - } else if ([NSApplication sharedApplication].mainWindow.isVisible) { - [controller showOutputForDevice:device value:value]; +- (void)hidManager:(NJHIDManager *)manager + valueChanged:(IOHIDValueRef)value + fromDevice:(IOHIDDeviceRef)device { + if (self.simulatingEvents + && !NSApplication.sharedApplication.isActive) { + [self runOutputForDevice:device value:value]; + } else { + [self showOutputForDevice:device value:value]; } } -static int findAvailableIndex(NSArray *list, NJDevice *dev) { - for (int index = 1; ; index++) { - BOOL available = YES; - for (NJDevice *used in list) { - if ([used.productName isEqualToString:dev.productName] && used.index == index) { +- (void)addDevice:(NJDevice *)device { + BOOL available; + do { + available = YES; + for (NJDevice *used in _devices) { + if ([used isEqual:device]) { + device.index += 1; available = NO; - break; } } - if (available) - return index; - } -} - -- (void)addDeviceForDevice:(IOHIDDeviceRef)device { - IOHIDDeviceRegisterInputValueCallback(device, input_callback, (__bridge void*)self); - NJDevice *dev = [[NJDevice alloc] initWithDevice:device]; - dev.index = findAvailableIndex(_devices, dev); - [_devices addObject:dev]; - [outlineView reloadData]; - [connectDevicePrompt setHidden:!!_devices.count]; + } while (!available); + + [_devices addObject:device]; } -static void add_callback(void *ctx, IOReturn inResult, void *inSender, IOHIDDeviceRef device) { - NJDeviceController *controller = (__bridge NJDeviceController *)ctx; - [controller addDeviceForDevice:device]; +- (void)hidManager:(NJHIDManager *)manager deviceAdded:(IOHIDDeviceRef)device { + NJDevice *match = [[NJDevice alloc] initWithDevice:device]; + [self addDevice:match]; + [self.delegate deviceController:self didAddDevice:match]; } - (NJDevice *)findDeviceByRef:(IOHIDDeviceRef)device { @@ -127,117 +150,78 @@ static void add_callback(void *ctx, IOReturn inResult, void *inSender, IOHIDDevi return nil; } -static void remove_callback(void *ctx, IOReturn inResult, void *inSender, IOHIDDeviceRef device) { - NJDeviceController *controller = (__bridge NJDeviceController *)ctx; - [controller removeDeviceForDevice:device]; -} - -- (void)removeDeviceForDevice:(IOHIDDeviceRef)device { +- (void)hidManager:(NJHIDManager *)manager deviceRemoved:(IOHIDDeviceRef)device { NJDevice *match = [self findDeviceByRef:device]; - IOHIDDeviceRegisterInputValueCallback(device, NULL, NULL); if (match) { - [_devices removeObject:match]; - [outlineView reloadData]; - [connectDevicePrompt setHidden:!!_devices.count]; + NSInteger idx = [_devices indexOfObjectIdenticalTo:match]; + [_devices removeObjectAtIndex:idx]; + [self.delegate deviceController:self didRemoveDeviceAtIndex:idx]; } - } -- (void)updateContinuousInputs:(NSTimer *)timer { +- (void)updateContinuousOutputs:(NSTimer *)timer { self.mouseLoc = [NSEvent mouseLocation]; - for (NJOutput *output in [runningOutputs copy]) { + for (NJOutput *output in [_continousOutputs copy]) { if (![output update:self]) { - [runningOutputs removeObject:output]; + [_continousOutputs removeObject:output]; } } - if (!runningOutputs.count) { - [continuousTimer invalidate]; - continuousTimer = nil; + if (!_continousOutputs.count) { + [_continuousOutputsTick invalidate]; + _continuousOutputsTick = nil; } } -#define NSSTR(e) ((NSString *)CFSTR(e)) - -- (void)setup { - hidManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); - NSArray *criteria = @[ @{ NSSTR(kIOHIDDeviceUsagePageKey) : @(kHIDPage_GenericDesktop), - NSSTR(kIOHIDDeviceUsageKey) : @(kHIDUsage_GD_Joystick) }, - @{ NSSTR(kIOHIDDeviceUsagePageKey) : @(kHIDPage_GenericDesktop), - NSSTR(kIOHIDDeviceUsageKey) : @(kHIDUsage_GD_GamePad) }, - @{ NSSTR(kIOHIDDeviceUsagePageKey) : @(kHIDPage_GenericDesktop), - 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" - 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]; - } - - IOHIDManagerRegisterDeviceMatchingCallback(hidManager, add_callback, (__bridge void *)self); - IOHIDManagerRegisterDeviceRemovalCallback(hidManager, remove_callback, (__bridge void *)self); +- (void)hidManager:(NJHIDManager *)manager didError:(NSError *)error { + [self.delegate deviceController:self didError:error]; + self.simulatingEvents = NO; } -- (NJInput *)selectedInput { - id item = [outlineView itemAtRow:outlineView.selectedRow]; - return (!item.children && item.base) ? item : nil; +- (void)hidManagerDidStart:(NJHIDManager *)manager { + [self.delegate deviceControllerDidStartHID:self]; } -- (NSInteger)outlineView:(NSOutlineView *)outlineView - numberOfChildrenOfItem:(id )item { - return item ? item.children.count : _devices.count; +- (void)hidManagerDidStop:(NJHIDManager *)manager { + [_devices removeAllObjects]; + [self.delegate deviceControllerDidStopHID:self]; } -- (BOOL)outlineView:(NSOutlineView *)outlineView - isItemExpandable:(id )item { - return item ? [[item children] count] > 0: YES; +- (void)startHid { + [_hidManager start]; } -- (id)outlineView:(NSOutlineView *)outlineView - child:(NSInteger)index - ofItem:(id )item { - return item ? item.children[index] : _devices[index]; +- (void)stopHid { + [_hidManager stop]; } -- (id)outlineView:(NSOutlineView *)outlineView -objectValueForTableColumn:(NSTableColumn *)tableColumn - byItem:(id )item { - return item ? item.name : @"root"; -} - -- (void)outlineViewSelectionDidChange:(NSNotification *)notification { - - [outputController loadCurrent]; -} - -- (void)setTranslatingEvents:(BOOL)translatingEvents { - if (translatingEvents != _translatingEvents) { - _translatingEvents = translatingEvents; - NSInteger state = translatingEvents ? NSOnState : NSOffState; - translatingEventsButton.state = state; - translatingEventsMenu.title = translatingEvents ? @"Disable" : @"Enable"; - NSString *name = translatingEvents - ? NJEventTranslationActivated - : NJEventTranslationDeactivated; +- (void)setSimulatingEvents:(BOOL)simulatingEvents { + if (simulatingEvents != _simulatingEvents) { + _simulatingEvents = simulatingEvents; + NSString *name = simulatingEvents + ? NJEventSimulationStarted + : NJEventSimulationStopped; [NSNotificationCenter.defaultCenter postNotificationName:name object:self]; + + if (!simulatingEvents && !NSApplication.sharedApplication.isActive) + [self stopHid]; + else + [self startHid]; } } -- (IBAction)translatingEventsChanged:(NSButton *)sender { - self.translatingEvents = sender.state == NSOnState; +- (void)stopHidIfDisabled:(NSNotification *)application { + if (!self.simulatingEvents && !NSProcessInfo.processInfo.isBeingDebugged) + [self stopHid]; } +- (NJInputPathElement *)objectForKeyedSubscript:(NSString *)uid { + for (NJDevice *dev in _devices) { + id item = [dev elementForUID:uid]; + if (item) + return item; + } + return nil; +} @end