2 // NJDeviceController.m
5 // Created by Sam McCall on 4/05/09.
8 #import "NJDeviceController.h"
11 #import "NJMappingsController.h"
17 @implementation NJDeviceController {
18 NJHIDManager *_hidManager;
19 NSTimer *_continuousOutputsTick;
20 NSMutableArray *_continousOutputs;
21 NSMutableArray *_devices;
24 #define NSSTR(e) ((NSString *)CFSTR(e))
27 if ((self = [super init])) {
28 _devices = [[NSMutableArray alloc] initWithCapacity:16];
29 _continousOutputs = [[NSMutableArray alloc] initWithCapacity:32];
31 _hidManager = [[NJHIDManager alloc] initWithCriteria:@[
32 @{ NSSTR(kIOHIDDeviceUsagePageKey) : @(kHIDPage_GenericDesktop),
33 NSSTR(kIOHIDDeviceUsageKey) : @(kHIDUsage_GD_Joystick) },
34 @{ NSSTR(kIOHIDDeviceUsagePageKey) : @(kHIDPage_GenericDesktop),
35 NSSTR(kIOHIDDeviceUsageKey) : @(kHIDUsage_GD_GamePad) },
36 @{ NSSTR(kIOHIDDeviceUsagePageKey) : @(kHIDPage_GenericDesktop),
37 NSSTR(kIOHIDDeviceUsageKey) : @(kHIDUsage_GD_MultiAxisController) }
41 // The HID manager uses 5-10ms per second doing basically
42 // nothing if a noisy device is plugged in (the % of that
43 // spent in input_callback is negligible, so it's not
44 // something we can make faster). I don't really think that's
45 // acceptable, CPU/power wise. So if simulation is disabled
46 // and the window is closed, just switch off the HID manager
47 // entirely. This probably also has some marginal benefits for
48 // compatibility with other applications that want exclusive
50 [NSNotificationCenter.defaultCenter
52 selector:@selector(stopHidIfDisabled:)
53 name:NSApplicationDidResignActiveNotification
55 [NSNotificationCenter.defaultCenter
57 selector:@selector(startHid)
58 name:NSApplicationDidBecomeActiveNotification
65 [NSNotificationCenter.defaultCenter removeObserver:self];
66 [_continuousOutputsTick invalidate];
69 - (NJDevice *)objectAtIndexedSubscript:(NSUInteger)idx {
70 return idx < _devices.count ? _devices[idx] : nil;
74 return _devices.count;
77 - (void)addRunningOutput:(NJOutput *)output {
78 // Axis events will trigger every small movement, don't keep
79 // re-adding them or they trigger multiple times each time.
80 if (![_continousOutputs containsObject:output])
81 [_continousOutputs addObject:output];
82 if (!_continuousOutputsTick) {
83 _continuousOutputsTick = [NSTimer scheduledTimerWithTimeInterval:1.0/60.0
85 selector:@selector(updateContinuousOutputs:)
91 - (void)runOutputForDevice:(IOHIDDeviceRef)device value:(IOHIDValueRef)value {
92 NJDevice *dev = [self findDeviceByRef:device];
93 NJInput *mainInput = [dev inputForEvent:value];
94 [mainInput notifyEvent:value];
95 NSArray *children = mainInput.children ? mainInput.children : mainInput ? @[mainInput] : @[];
96 for (NJInput *subInput in children) {
97 NJOutput *output = mappingsController.currentMapping[subInput];
98 output.magnitude = subInput.magnitude;
99 output.running = subInput.active;
100 if ((output.running || output.magnitude) && output.isContinuous)
101 [self addRunningOutput:output];
105 - (void)showOutputForDevice:(IOHIDDeviceRef)device value:(IOHIDValueRef)value {
106 NJDevice *dev = [self findDeviceByRef:device];
107 NJInput *handler = [dev handlerForEvent:value];
111 [self.delegate deviceController:self didInput:handler];
114 - (void)hidManager:(NJHIDManager *)manager
115 valueChanged:(IOHIDValueRef)value
116 fromDevice:(IOHIDDeviceRef)device {
117 if (self.simulatingEvents
118 && !NSApplication.sharedApplication.isActive) {
119 [self runOutputForDevice:device value:value];
121 [self showOutputForDevice:device value:value];
125 - (void)addDevice:(NJDevice *)device {
129 for (NJDevice *used in _devices) {
130 if ([used isEqual:device]) {
135 } while (!available);
137 [_devices addObject:device];
140 - (void)hidManager:(NJHIDManager *)manager deviceAdded:(IOHIDDeviceRef)device {
141 NJDevice *match = [[NJDevice alloc] initWithDevice:device];
142 [self addDevice:match];
143 [self.delegate deviceController:self didAddDevice:match];
146 - (NJDevice *)findDeviceByRef:(IOHIDDeviceRef)device {
147 for (NJDevice *dev in _devices)
148 if (dev.device == device)
153 - (void)hidManager:(NJHIDManager *)manager deviceRemoved:(IOHIDDeviceRef)device {
154 NJDevice *match = [self findDeviceByRef:device];
156 NSInteger idx = [_devices indexOfObjectIdenticalTo:match];
157 [_devices removeObjectAtIndex:idx];
158 [self.delegate deviceController:self didRemoveDeviceAtIndex:idx];
162 - (void)updateContinuousOutputs:(NSTimer *)timer {
163 self.mouseLoc = [NSEvent mouseLocation];
164 for (NJOutput *output in [_continousOutputs copy]) {
165 if (![output update:self]) {
166 [_continousOutputs removeObject:output];
169 if (!_continousOutputs.count) {
170 [_continuousOutputsTick invalidate];
171 _continuousOutputsTick = nil;
175 - (void)hidManager:(NJHIDManager *)manager didError:(NSError *)error {
176 [self.delegate deviceController:self didError:error];
177 self.simulatingEvents = NO;
180 - (void)hidManagerDidStart:(NJHIDManager *)manager {
181 [self.delegate deviceControllerDidStartHID:self];
184 - (void)hidManagerDidStop:(NJHIDManager *)manager {
185 [_devices removeAllObjects];
186 [self.delegate deviceControllerDidStopHID:self];
197 - (void)setSimulatingEvents:(BOOL)simulatingEvents {
198 if (simulatingEvents != _simulatingEvents) {
199 _simulatingEvents = simulatingEvents;
200 NSString *name = simulatingEvents
201 ? NJEventSimulationStarted
202 : NJEventSimulationStopped;
203 [NSNotificationCenter.defaultCenter postNotificationName:name
206 if (!simulatingEvents && !NSApplication.sharedApplication.isActive)
213 - (void)stopHidIfDisabled:(NSNotification *)application {
214 if (!self.simulatingEvents && !NSProcessInfo.processInfo.isBeingDebugged)
218 - (NJInputPathElement *)objectForKeyedSubscript:(NSString *)uid {
219 for (NJDevice *dev in _devices) {
220 id item = [dev elementForUID:uid];