X-Git-Url: https://git.yukkurigames.com/?p=enjoyable.git;a=blobdiff_plain;f=Classes%2FNJDeviceController.m;h=0b0fe9289c30fa9267f6335e3eed9ff410b206e0;hp=a1e58682ca497b547a34453f2ef8bfbe477f4b72;hb=6dddafbcf505939751ddb2d66ffb586d150aaaa8;hpb=8f3613a31e2012374e00a6973f221421e0b770f0 diff --git a/Classes/NJDeviceController.m b/Classes/NJDeviceController.m index a1e5868..0b0fe92 100644 --- a/Classes/NJDeviceController.m +++ b/Classes/NJDeviceController.m @@ -16,7 +16,7 @@ #import "NJEvents.h" @implementation NJDeviceController { - IOHIDManagerRef _hidManager; + NJHIDManager *_hidManager; NSTimer *_continuousOutputsTick; NSMutableArray *_continousOutputs; NSMutableArray *_devices; @@ -24,6 +24,7 @@ } #define EXPANDED_MEMORY_MAX_SIZE 100 +#define NSSTR(e) ((NSString *)CFSTR(e)) - (id)init { if ((self = [super init])) { @@ -35,10 +36,20 @@ expanded = @[]; _expanded = [[NSMutableArray alloc] initWithCapacity:MAX(16, _expanded.count)]; [_expanded addObjectsFromArray:expanded]; + + _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]; [NSNotificationCenter.defaultCenter addObserver:self - selector:@selector(applicationDidFinishLaunching:) + selector:@selector(startHid) name:NSApplicationDidFinishLaunchingNotification object:nil]; @@ -53,12 +64,12 @@ // grabs. [NSNotificationCenter.defaultCenter addObserver:self - selector:@selector(closeHidIfDisabled:) + selector:@selector(stopHidIfDisabled:) name:NSApplicationDidResignActiveNotification object:nil]; [NSNotificationCenter.defaultCenter addObserver:self - selector:@selector(openHid) + selector:@selector(startHid) name:NSApplicationDidBecomeActiveNotification object:nil]; } @@ -68,22 +79,26 @@ - (void)dealloc { [NSNotificationCenter.defaultCenter removeObserver:self]; [_continuousOutputsTick invalidate]; - [self closeHid]; } -- (void)expandRecursive:(id )pathElement { +- (void)expandRecursive:(NJInputPathElement *)pathElement { if (pathElement) { - [self expandRecursive:pathElement.base]; + [self expandRecursive:pathElement.parent]; [outlineView expandItem:pathElement]; } } -- (void)expandRecursiveByUID:(NSString *)uid { +- (id)elementForUID:(NSString *)uid { for (NJDevice *dev in _devices) { id item = [dev elementForUID:uid]; if (item) - [self expandRecursive:item]; + return item; } + return nil; +} + +- (void)expandRecursiveByUID:(NSString *)uid { + [self expandRecursive:[self elementForUID:uid]]; } - (void)addRunningOutput:(NJOutput *)output { @@ -123,17 +138,18 @@ [self expandRecursive:handler]; [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:[outlineView rowForItem:handler]] byExtendingSelection: NO]; - [outputController focusKey]; + if (!self.simulatingEvents) + [outputController focusKey]; } -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]; } } @@ -141,7 +157,8 @@ 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) { + if ([used.productName isEqualToString:dev.productName] + && used.index == index) { available = NO; break; } @@ -151,21 +168,16 @@ static int findAvailableIndex(NSArray *list, NJDevice *dev) { } } -- (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]; +- (void)hidManager:(NJHIDManager *)manager deviceAdded:(IOHIDDeviceRef)device { + NJDevice *match = [[NJDevice alloc] initWithDevice:device]; + match.index = findAvailableIndex(_devices, match); + [_devices addObject:match]; [outlineView reloadData]; [self reexpandAll]; + hidSleepingPrompt.hidden = YES; connectDevicePrompt.hidden = !!_devices.count; } -static void add_callback(void *ctx, IOReturn inResult, void *inSender, IOHIDDeviceRef device) { - NJDeviceController *controller = (__bridge NJDeviceController *)ctx; - [controller addDeviceForDevice:device]; -} - - (NJDevice *)findDeviceByRef:(IOHIDDeviceRef)device { for (NJDevice *dev in _devices) if (dev.device == device) @@ -173,18 +185,14 @@ 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.hidden = !!_devices.count; + hidSleepingPrompt.hidden = YES; } if (_devices.count == 1) [outlineView expandItem:_devices[0]]; @@ -203,98 +211,93 @@ static void remove_callback(void *ctx, IOReturn inResult, void *inSender, IOHIDD } } -#define NSSTR(e) ((NSString *)CFSTR(e)) - -- (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), - NSSTR(kIOHIDDeviceUsageKey) : @(kHIDUsage_GD_GamePad) }, - @{ NSSTR(kIOHIDDeviceUsagePageKey) : @(kHIDPage_GenericDesktop), - NSSTR(kIOHIDDeviceUsageKey) : @(kHIDUsage_GD_MultiAxisController) } - ]; - IOHIDManagerSetDeviceMatchingMultiple(_hidManager, (__bridge CFArrayRef)criteria); - IOReturn ret = IOHIDManagerOpen(_hidManager, kIOHIDOptionsTypeNone); - if (ret != kIOReturnSuccess) { - [[NSAlert alertWithMessageText:NSLocalizedString(@"input devices unavailable", - @"error title when devices can't be read") - defaultButton:nil - alternateButton:nil - otherButton: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); +- (void)hidManager:(NJHIDManager *)manager didError:(NSError *)error { + // Since the error shows the window, it can trigger another attempt + // to re-open the HID manager, which will also probably fail and error, + // so don't bother repeating ourselves. + if (!outlineView.window.attachedSheet) { + [NSApplication.sharedApplication activateIgnoringOtherApps:YES]; + [outlineView.window makeKeyAndOrderFront:nil]; + [outlineView.window presentError:error + modalForWindow:outlineView.window + delegate:nil + didPresentSelector:nil + contextInfo:nil]; } + self.simulatingEvents = NO; + if (manager.running) + [self hidManagerDidStart:manager]; + else + [self hidManagerDidStop:manager]; } -- (void)closeHid { - if (_hidManager) { - NSLog(@"Closing HID manager."); - IOHIDManagerUnscheduleFromRunLoop(_hidManager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); - IOHIDManagerClose(_hidManager, kIOHIDOptionsTypeNone); - CFRelease(_hidManager); - _hidManager = NULL; - } +- (void)hidManagerDidStart:(NJHIDManager *)manager { + hidSleepingPrompt.hidden = YES; + connectDevicePrompt.hidden = !!_devices.count; +} + +- (void)hidManagerDidStop:(NJHIDManager *)manager { [_devices removeAllObjects]; [outlineView reloadData]; - connectDevicePrompt.hidden = !!_devices.count; + hidSleepingPrompt.hidden = NO; + connectDevicePrompt.hidden = YES; +} + +- (void)startHid { + [_hidManager start]; +} + +- (void)stopHid { + [_hidManager stop]; } - (NJInput *)selectedInput { - id item = [outlineView itemAtRow:outlineView.selectedRow]; - return (!item.children && item.base) ? item : nil; + NJInputPathElement *item = [outlineView itemAtRow:outlineView.selectedRow]; + return (NJInput *)((!item.children && item.parent) ? item : nil); } - (NSInteger)outlineView:(NSOutlineView *)outlineView - numberOfChildrenOfItem:(id )item { + numberOfChildrenOfItem:(NJInputPathElement *)item { return item ? item.children.count : _devices.count; } - (BOOL)outlineView:(NSOutlineView *)outlineView - isItemExpandable:(id )item { + isItemExpandable:(NJInputPathElement *)item { return item ? [[item children] count] > 0: YES; } - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index - ofItem:(id )item { + ofItem:(NJInputPathElement *)item { return item ? item.children[index] : _devices[index]; } - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn - byItem:(id )item { + byItem:(NJInputPathElement *)item { return item ? item.name : @"root"; } - (void)outlineViewSelectionDidChange:(NSNotification *)notification { + NJInputPathElement *item = [outlineView itemAtRow:outlineView.selectedRow]; + if (item) + [NSUserDefaults.standardUserDefaults setObject:item.uid + forKey:@"selected input"]; [outputController loadCurrent]; } - (BOOL)outlineView:(NSOutlineView *)outlineView - isGroupItem:(id )item { + isGroupItem:(NJInputPathElement *)item { return [item isKindOfClass:NJDevice.class]; } - (BOOL)outlineView:(NSOutlineView *)outlineView_ - shouldSelectItem:(id )item { + shouldSelectItem:(NJInputPathElement *)item { return ![self outlineView:outlineView_ isGroupItem:item]; } - (void)outlineViewItemDidExpand:(NSNotification *)notification { - id item = notification.userInfo[@"NSObject"]; + NJInputPathElement *item = notification.userInfo[@"NSObject"]; NSString *uid = item.uid; if (![_expanded containsObject:uid]) [_expanded addObject:uid]; @@ -305,49 +308,49 @@ objectValueForTableColumn:(NSTableColumn *)tableColumn } - (void)outlineViewItemDidCollapse:(NSNotification *)notification { - id item = notification.userInfo[@"NSObject"]; + NJInputPathElement *item = notification.userInfo[@"NSObject"]; [_expanded removeObject:item.uid]; [NSUserDefaults.standardUserDefaults setObject:_expanded forKey:@"expanded rows"]; } -- (void)setTranslatingEvents:(BOOL)translatingEvents { - if (translatingEvents != _translatingEvents) { - _translatingEvents = translatingEvents; - NSInteger state = translatingEvents ? NSOnState : NSOffState; +- (void)setSimulatingEvents:(BOOL)simulatingEvents { + if (simulatingEvents != _simulatingEvents) { + _simulatingEvents = simulatingEvents; + NSInteger state = simulatingEvents ? NSOnState : NSOffState; translatingEventsButton.state = state; - NSString *name = translatingEvents - ? NJEventTranslationActivated - : NJEventTranslationDeactivated; + NSString *name = simulatingEvents + ? NJEventSimulationStarted + : NJEventSimulationStopped; [NSNotificationCenter.defaultCenter postNotificationName:name object:self]; - if (!translatingEvents && !NSApplication.sharedApplication.isActive) - [self closeHid]; - else if (translatingEvents || NSApplication.sharedApplication.isActive) - [self openHid]; + if (!simulatingEvents && !NSApplication.sharedApplication.isActive) + [self stopHid]; + else + [self startHid]; } } - (void)reexpandAll { for (NSString *uid in [_expanded copy]) [self expandRecursiveByUID:uid]; + if (outlineView.selectedRow == -1) { + NSString *selectedUid = [NSUserDefaults.standardUserDefaults objectForKey:@"selected input"]; + id item = [self elementForUID:selectedUid]; + NSInteger row = [outlineView rowForItem:item]; + if (row >= 0) + [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO]; + } } -- (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]; +- (void)stopHidIfDisabled:(NSNotification *)application { + if (!self.simulatingEvents) + [self stopHid]; } -- (IBAction)translatingEventsChanged:(NSButton *)sender { - self.translatingEvents = sender.state == NSOnState; +- (IBAction)simulatingEventsChanged:(NSButton *)sender { + self.simulatingEvents = sender.state == NSOnState; } @end