6 #import "NJInputController.h"
14 #import <CoreVideo/CoreVideo.h>
16 @interface NJInputController ()
18 - (void)updateContinuousOutputs;
22 static CVReturn _updateDL(CVDisplayLinkRef displayLink,
23 const CVTimeStamp *inNow,
24 const CVTimeStamp *inOutputTime,
25 CVOptionFlags flagsIn,
26 CVOptionFlags *flagsOut,
28 NJInputController *manager = (__bridge NJInputController *)ctxManager;
29 [manager performSelectorOnMainThread:@selector(updateContinuousOutputs)
32 return kCVReturnSuccess;
35 @implementation NJInputController {
36 NJHIDManager *_HIDManager;
37 NSMutableArray *_continousOutputs;
38 NSMutableArray *_devices;
39 NSMutableArray *_mappings;
40 NJMapping *_manualMapping;
41 CVDisplayLinkRef _displayLink;
44 #define NSSTR(e) ((NSString *)CFSTR(e))
47 if ((self = [super init])) {
48 _devices = [[NSMutableArray alloc] initWithCapacity:16];
49 _continousOutputs = [[NSMutableArray alloc] initWithCapacity:32];
51 CVReturn cvErr = CVDisplayLinkCreateWithActiveCGDisplays(&_displayLink);
53 NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain
56 [self.delegate inputController:self didError:error];
57 NSLog(@"DisplayLink failed creation with error: %@", error);
60 CVDisplayLinkSetOutputCallback(_displayLink, _updateDL, (__bridge void *)self);
62 _HIDManager = [[NJHIDManager alloc] initWithCriteria:@[
63 @{ NSSTR(kIOHIDDeviceUsagePageKey) : @(kHIDPage_GenericDesktop),
64 NSSTR(kIOHIDDeviceUsageKey) : @(kHIDUsage_GD_Joystick) },
65 @{ NSSTR(kIOHIDDeviceUsagePageKey) : @(kHIDPage_GenericDesktop),
66 NSSTR(kIOHIDDeviceUsageKey) : @(kHIDUsage_GD_GamePad) },
67 @{ NSSTR(kIOHIDDeviceUsagePageKey) : @(kHIDPage_GenericDesktop),
68 NSSTR(kIOHIDDeviceUsageKey) : @(kHIDUsage_GD_MultiAxisController) }
72 _mappings = [[NSMutableArray alloc] init];
73 _currentMapping = [[NJMapping alloc] initWithName:
74 NSLocalizedString(@"(default)", @"default name for first the mapping")];
75 _manualMapping = _currentMapping;
76 [_mappings addObject:_currentMapping];
78 // The HID manager uses 5-10ms per second doing basically
79 // nothing if a noisy device is plugged in (the % of that
80 // spent in input_callback is negligible, so it's not
81 // something we can make faster). I don't really think that's
82 // acceptable, CPU/power wise. So if simulation is disabled
83 // and the window is closed, just switch off the HID manager
84 // entirely. This probably also has some marginal benefits for
85 // compatibility with other applications that want exclusive
87 [NSNotificationCenter.defaultCenter
89 selector:@selector(stopHidIfDisabled:)
90 name:NSApplicationDidResignActiveNotification
92 [NSNotificationCenter.defaultCenter
94 selector:@selector(startHid)
95 name:NSApplicationDidBecomeActiveNotification
102 [NSNotificationCenter.defaultCenter removeObserver:self];
104 CVDisplayLinkStop(_displayLink);
105 CVDisplayLinkRelease(_displayLink);
109 - (void)addRunningOutput:(NJOutput *)output {
110 // Axis events will trigger every small movement, don't keep
111 // re-adding them or they trigger multiple times each time.
112 if (![_continousOutputs containsObject:output])
113 [_continousOutputs addObject:output];
114 if (_displayLink && !CVDisplayLinkIsRunning(_displayLink))
115 CVDisplayLinkStart(_displayLink);
118 - (void)runOutputForDevice:(IOHIDDeviceRef)device value:(IOHIDValueRef)value {
119 NJDevice *dev = [self findDeviceByRef:device];
120 NJInput *mainInput = [dev inputForEvent:value];
121 [mainInput notifyEvent:value];
122 NSArray *children = mainInput.children ? mainInput.children : mainInput ? @[mainInput] : @[];
123 for (NJInput *subInput in children) {
124 NJOutput *output = self.currentMapping[subInput];
125 output.magnitude = subInput.magnitude;
126 output.running = subInput.active;
127 if ((output.running || output.magnitude) && output.isContinuous)
128 [self addRunningOutput:output];
132 - (void)showOutputForDevice:(IOHIDDeviceRef)device value:(IOHIDValueRef)value {
133 NJDevice *dev = [self findDeviceByRef:device];
134 NJInput *handler = [dev handlerForEvent:value];
138 [self.delegate inputController:self didInput:handler];
141 - (void)HIDManager:(NJHIDManager *)manager
142 valueChanged:(IOHIDValueRef)value
143 fromDevice:(IOHIDDeviceRef)device {
144 if (self.simulatingEvents
145 && !NSApplication.sharedApplication.isActive) {
146 [self runOutputForDevice:device value:value];
148 [self showOutputForDevice:device value:value];
152 - (void)addDevice:(NJDevice *)device {
156 for (NJDevice *used in _devices) {
157 if ([used isEqual:device]) {
162 } while (!available);
164 [_devices addObject:device];
167 - (void)HIDManager:(NJHIDManager *)manager deviceAdded:(IOHIDDeviceRef)device {
168 NJDevice *match = [[NJDevice alloc] initWithDevice:device];
169 [self addDevice:match];
170 [self.delegate inputController:self didAddDevice:match];
173 - (NJDevice *)findDeviceByRef:(IOHIDDeviceRef)device {
174 for (NJDevice *dev in _devices)
175 if (dev.device == device)
180 - (void)HIDManager:(NJHIDManager *)manager deviceRemoved:(IOHIDDeviceRef)device {
181 NJDevice *match = [self findDeviceByRef:device];
183 NSInteger idx = [_devices indexOfObjectIdenticalTo:match];
184 [_devices removeObjectAtIndex:idx];
185 [self.delegate inputController:self didRemoveDeviceAtIndex:idx];
189 - (void)updateContinuousOutputs {
190 self.mouseLoc = [NSEvent mouseLocation];
191 for (NJOutput *output in [_continousOutputs copy]) {
192 if (![output update:self]) {
193 [_continousOutputs removeObject:output];
196 if (!_continousOutputs.count && _displayLink) {
197 CVDisplayLinkStop(_displayLink);
201 - (void)HIDManager:(NJHIDManager *)manager didError:(NSError *)error {
202 [self.delegate inputController:self didError:error];
203 self.simulatingEvents = NO;
205 CVDisplayLinkStop(_displayLink);
208 - (void)HIDManagerDidStart:(NJHIDManager *)manager {
209 [self.delegate inputControllerDidStartHID:self];
212 - (void)HIDManagerDidStop:(NJHIDManager *)manager {
213 [_devices removeAllObjects];
215 CVDisplayLinkStop(_displayLink);
216 [self.delegate inputControllerDidStopHID:self];
227 - (void)setSimulatingEvents:(BOOL)simulatingEvents {
228 if (simulatingEvents != _simulatingEvents) {
229 _simulatingEvents = simulatingEvents;
230 NSString *name = simulatingEvents
231 ? NJEventSimulationStarted
232 : NJEventSimulationStopped;
233 [NSNotificationCenter.defaultCenter postNotificationName:name
236 if (!simulatingEvents && !NSApplication.sharedApplication.isActive)
243 - (void)stopHidIfDisabled:(NSNotification *)application {
244 if (!self.simulatingEvents && !NSProcessInfo.processInfo.isBeingDebugged)
248 - (NJInputPathElement *)elementForUID:(NSString *)uid {
249 for (NJDevice *dev in _devices) {
250 id item = [dev elementForUID:uid];
257 - (NJMapping *)mappingForKey:(NSString *)name {
258 for (NJMapping *mapping in _mappings)
259 if ([name isEqualToString:mapping.name])
264 - (void)mappingsSet {
265 [self postLoadProcess];
266 [NSNotificationCenter.defaultCenter
267 postNotificationName:NJEventMappingListChanged
269 userInfo:@{ NJMappingListKey: _mappings,
270 NJMappingKey: _currentMapping }];
273 - (void)mappingsChanged {
278 - (void)activateMappingForProcess:(NSRunningApplication *)app {
279 NJMapping *oldMapping = _manualMapping;
280 NSArray *names = app.possibleMappingNames;
282 for (NSString *name in names) {
283 NJMapping *mapping = [self mappingForKey:name];
285 [self activateMapping:mapping];
292 [self activateMapping:oldMapping];
293 if ([oldMapping.name.lowercaseString isEqualToString:@"@application"]
294 || [oldMapping.name.lowercaseString isEqualToString:
295 NSLocalizedString(@"@Application", nil).lowercaseString]) {
296 [self renameMapping:oldMapping to:app.bestMappingName];
299 _manualMapping = oldMapping;
302 - (void)activateMappingForcibly:(NJMapping *)mapping {
303 NSLog(@"Switching to mapping %@.", mapping.name);
304 _currentMapping = mapping;
305 NSUInteger idx = [self indexOfMapping:_currentMapping];
306 [NSNotificationCenter.defaultCenter
307 postNotificationName:NJEventMappingChanged
309 userInfo:@{ NJMappingKey : _currentMapping,
310 NJMappingIndexKey: @(idx) }];
313 - (void)activateMapping:(NJMapping *)mapping {
315 mapping = _manualMapping;
316 if (mapping == _currentMapping)
318 _manualMapping = mapping;
319 [self activateMappingForcibly:mapping];
323 NSLog(@"Saving mappings to defaults.");
324 NSMutableArray *ary = [[NSMutableArray alloc] initWithCapacity:_mappings.count];
325 for (NJMapping *mapping in _mappings)
326 [ary addObject:[mapping serialize]];
327 [NSUserDefaults.standardUserDefaults setObject:ary forKey:@"mappings"];
330 - (void)postLoadProcess {
331 for (NJMapping *mapping in self.mappings)
332 [mapping postLoadProcess:self.mappings];
336 NSUInteger selected = [NSUserDefaults.standardUserDefaults integerForKey:@"selected"];
337 NSArray *storedMappings = [NSUserDefaults.standardUserDefaults arrayForKey:@"mappings"];
338 NSMutableArray* newMappings = [[NSMutableArray alloc] initWithCapacity:storedMappings.count];
340 for (NSDictionary *serialization in storedMappings)
341 [newMappings addObject:
342 [[NJMapping alloc] initWithSerialization:serialization]];
344 if (newMappings.count) {
345 _mappings = newMappings;
346 if (selected >= newMappings.count)
348 [self activateMapping:_mappings[selected]];
353 - (NSInteger)indexOfMapping:(NJMapping *)mapping {
354 return [_mappings indexOfObjectIdenticalTo:mapping];
357 - (void)mergeMapping:(NJMapping *)mapping intoMapping:(NJMapping *)existing {
358 [existing mergeEntriesFrom:mapping];
359 [self mappingsChanged];
360 if (existing == _currentMapping)
361 [self activateMappingForcibly:mapping];
364 - (void)renameMapping:(NJMapping *)mapping to:(NSString *)name {
366 [self mappingsChanged];
367 if (mapping == _currentMapping)
368 [self activateMappingForcibly:mapping];
371 - (void)addMapping:(NJMapping *)mapping {
372 [self insertMapping:mapping atIndex:_mappings.count];
375 - (void)insertMapping:(NJMapping *)mapping atIndex:(NSInteger)idx {
376 [_mappings insertObject:mapping atIndex:idx];
377 [self mappingsChanged];
380 - (void)removeMappingAtIndex:(NSInteger)idx {
381 NSInteger currentIdx = [self indexOfMapping:_currentMapping];
382 [_mappings removeObjectAtIndex:idx];
383 NSInteger activeIdx = MIN(currentIdx, (NSInteger)_mappings.count - 1);
384 [self activateMapping:self.mappings[activeIdx]];
385 [self mappingsChanged];
388 - (void)moveMoveMappingFromIndex:(NSInteger)fromIdx toIndex:(NSInteger)toIdx {
389 [_mappings moveObjectAtIndex:fromIdx toIndex:toIdx];
390 [self mappingsChanged];