6 #import "NJInputController.h"
14 #import <CoreVideo/CoreVideo.h>
16 @interface NJInputController ()
18 - (void)updateContinuousOutputs;
22 static CVReturn displayLink_update_cb(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;
45 #define NSSTR(e) ((NSString *)CFSTR(e))
48 if ((self = [super init])) {
49 _devices = [[NSMutableArray alloc] initWithCapacity:16];
50 _continousOutputs = [[NSMutableArray alloc] initWithCapacity:32];
52 CVReturn error = CVDisplayLinkCreateWithActiveCGDisplays(&displayLink);
54 [self.delegate inputController:self
55 didError:[NSError errorWithDomain:NSCocoaErrorDomain
58 NSLog(@"DisplayLink failed creation with error: %d.", error);
61 CVDisplayLinkSetOutputCallback(displayLink, displayLink_update_cb, (__bridge void *)self);
63 _HIDManager = [[NJHIDManager alloc] initWithCriteria:@[
64 @{ NSSTR(kIOHIDDeviceUsagePageKey) : @(kHIDPage_GenericDesktop),
65 NSSTR(kIOHIDDeviceUsageKey) : @(kHIDUsage_GD_Joystick) },
66 @{ NSSTR(kIOHIDDeviceUsagePageKey) : @(kHIDPage_GenericDesktop),
67 NSSTR(kIOHIDDeviceUsageKey) : @(kHIDUsage_GD_GamePad) },
68 @{ NSSTR(kIOHIDDeviceUsagePageKey) : @(kHIDPage_GenericDesktop),
69 NSSTR(kIOHIDDeviceUsageKey) : @(kHIDUsage_GD_MultiAxisController) }
73 _mappings = [[NSMutableArray alloc] init];
74 _currentMapping = [[NJMapping alloc] initWithName:
75 NSLocalizedString(@"(default)", @"default name for first the mapping")];
76 _manualMapping = _currentMapping;
77 [_mappings addObject:_currentMapping];
79 // The HID manager uses 5-10ms per second doing basically
80 // nothing if a noisy device is plugged in (the % of that
81 // spent in input_callback is negligible, so it's not
82 // something we can make faster). I don't really think that's
83 // acceptable, CPU/power wise. So if simulation is disabled
84 // and the window is closed, just switch off the HID manager
85 // entirely. This probably also has some marginal benefits for
86 // compatibility with other applications that want exclusive
88 [NSNotificationCenter.defaultCenter
90 selector:@selector(stopHidIfDisabled:)
91 name:NSApplicationDidResignActiveNotification
93 [NSNotificationCenter.defaultCenter
95 selector:@selector(startHid)
96 name:NSApplicationDidBecomeActiveNotification
103 [NSNotificationCenter.defaultCenter removeObserver:self];
105 CVDisplayLinkStop(displayLink);
106 CVDisplayLinkRelease(displayLink);
110 - (void)addRunningOutput:(NJOutput *)output {
111 // Axis events will trigger every small movement, don't keep
112 // re-adding them or they trigger multiple times each time.
113 if (![_continousOutputs containsObject:output])
114 [_continousOutputs addObject:output];
115 if (displayLink && !CVDisplayLinkIsRunning(displayLink)) {
116 CVDisplayLinkStart(displayLink);
120 - (void)runOutputForDevice:(IOHIDDeviceRef)device value:(IOHIDValueRef)value {
121 NJDevice *dev = [self findDeviceByRef:device];
122 NJInput *mainInput = [dev inputForEvent:value];
123 [mainInput notifyEvent:value];
124 NSArray *children = mainInput.children ? mainInput.children : mainInput ? @[mainInput] : @[];
125 for (NJInput *subInput in children) {
126 NJOutput *output = self.currentMapping[subInput];
127 output.magnitude = subInput.magnitude;
128 output.running = subInput.active;
129 if ((output.running || output.magnitude) && output.isContinuous)
130 [self addRunningOutput:output];
134 - (void)showOutputForDevice:(IOHIDDeviceRef)device value:(IOHIDValueRef)value {
135 NJDevice *dev = [self findDeviceByRef:device];
136 NJInput *handler = [dev handlerForEvent:value];
140 [self.delegate inputController:self didInput:handler];
143 - (void)HIDManager:(NJHIDManager *)manager
144 valueChanged:(IOHIDValueRef)value
145 fromDevice:(IOHIDDeviceRef)device {
146 if (self.simulatingEvents
147 && !NSApplication.sharedApplication.isActive) {
148 [self runOutputForDevice:device value:value];
150 [self showOutputForDevice:device value:value];
154 - (void)addDevice:(NJDevice *)device {
158 for (NJDevice *used in _devices) {
159 if ([used isEqual:device]) {
164 } while (!available);
166 [_devices addObject:device];
169 - (void)HIDManager:(NJHIDManager *)manager deviceAdded:(IOHIDDeviceRef)device {
170 NJDevice *match = [[NJDevice alloc] initWithDevice:device];
171 [self addDevice:match];
172 [self.delegate inputController:self didAddDevice:match];
175 - (NJDevice *)findDeviceByRef:(IOHIDDeviceRef)device {
176 for (NJDevice *dev in _devices)
177 if (dev.device == device)
182 - (void)HIDManager:(NJHIDManager *)manager deviceRemoved:(IOHIDDeviceRef)device {
183 NJDevice *match = [self findDeviceByRef:device];
185 NSInteger idx = [_devices indexOfObjectIdenticalTo:match];
186 [_devices removeObjectAtIndex:idx];
187 [self.delegate inputController:self didRemoveDeviceAtIndex:idx];
191 - (void)updateContinuousOutputs {
192 self.mouseLoc = [NSEvent mouseLocation];
193 for (NJOutput *output in [_continousOutputs copy]) {
194 if (![output update:self]) {
195 [_continousOutputs removeObject:output];
198 if (!_continousOutputs.count && displayLink) {
199 CVDisplayLinkStop(displayLink);
203 - (void)HIDManager:(NJHIDManager *)manager didError:(NSError *)error {
204 [self.delegate inputController:self didError:error];
205 self.simulatingEvents = NO;
207 CVDisplayLinkStop(displayLink);
210 - (void)HIDManagerDidStart:(NJHIDManager *)manager {
211 [self.delegate inputControllerDidStartHID:self];
214 - (void)HIDManagerDidStop:(NJHIDManager *)manager {
215 [_devices removeAllObjects];
217 CVDisplayLinkStop(displayLink);
218 [self.delegate inputControllerDidStopHID:self];
229 - (void)setSimulatingEvents:(BOOL)simulatingEvents {
230 if (simulatingEvents != _simulatingEvents) {
231 _simulatingEvents = simulatingEvents;
232 NSString *name = simulatingEvents
233 ? NJEventSimulationStarted
234 : NJEventSimulationStopped;
235 [NSNotificationCenter.defaultCenter postNotificationName:name
238 if (!simulatingEvents && !NSApplication.sharedApplication.isActive)
245 - (void)stopHidIfDisabled:(NSNotification *)application {
246 if (!self.simulatingEvents && !NSProcessInfo.processInfo.isBeingDebugged)
250 - (NJInputPathElement *)elementForUID:(NSString *)uid {
251 for (NJDevice *dev in _devices) {
252 id item = [dev elementForUID:uid];
259 - (NJMapping *)mappingForKey:(NSString *)name {
260 for (NJMapping *mapping in _mappings)
261 if ([name isEqualToString:mapping.name])
266 - (void)mappingsSet {
267 [self postLoadProcess];
268 [NSNotificationCenter.defaultCenter
269 postNotificationName:NJEventMappingListChanged
271 userInfo:@{ NJMappingListKey: _mappings,
272 NJMappingKey: _currentMapping }];
275 - (void)mappingsChanged {
280 - (void)activateMappingForProcess:(NSRunningApplication *)app {
281 NJMapping *oldMapping = _manualMapping;
282 NSArray *names = app.possibleMappingNames;
284 for (NSString *name in names) {
285 NJMapping *mapping = [self mappingForKey:name];
287 [self activateMapping:mapping];
294 [self activateMapping:oldMapping];
295 if ([oldMapping.name.lowercaseString isEqualToString:@"@application"]
296 || [oldMapping.name.lowercaseString isEqualToString:
297 NSLocalizedString(@"@Application", nil).lowercaseString]) {
298 oldMapping.name = app.bestMappingName;
299 [self mappingsChanged];
302 _manualMapping = oldMapping;
305 - (void)activateMappingForcibly:(NJMapping *)mapping {
306 NSLog(@"Switching to mapping %@.", mapping.name);
307 _currentMapping = mapping;
308 NSUInteger idx = [self indexOfMapping:_currentMapping];
309 [NSNotificationCenter.defaultCenter
310 postNotificationName:NJEventMappingChanged
312 userInfo:@{ NJMappingKey : _currentMapping,
313 NJMappingIndexKey: @(idx) }];
316 - (void)activateMapping:(NJMapping *)mapping {
318 mapping = _manualMapping;
319 if (mapping == _currentMapping)
321 _manualMapping = mapping;
322 [self activateMappingForcibly:mapping];
326 NSLog(@"Saving mappings to defaults.");
327 NSMutableArray *ary = [[NSMutableArray alloc] initWithCapacity:_mappings.count];
328 for (NJMapping *mapping in _mappings)
329 [ary addObject:[mapping serialize]];
330 [NSUserDefaults.standardUserDefaults setObject:ary forKey:@"mappings"];
333 - (void)postLoadProcess {
334 for (NJMapping *mapping in self.mappings)
335 [mapping postLoadProcess:self.mappings];
339 NSUInteger selected = [NSUserDefaults.standardUserDefaults integerForKey:@"selected"];
340 NSArray *storedMappings = [NSUserDefaults.standardUserDefaults arrayForKey:@"mappings"];
341 NSMutableArray* newMappings = [[NSMutableArray alloc] initWithCapacity:storedMappings.count];
343 for (NSDictionary *serialization in storedMappings)
344 [newMappings addObject:
345 [[NJMapping alloc] initWithSerialization:serialization]];
347 if (newMappings.count) {
348 _mappings = newMappings;
349 if (selected >= newMappings.count)
351 [self activateMapping:_mappings[selected]];
356 - (NSInteger)indexOfMapping:(NJMapping *)mapping {
357 return [_mappings indexOfObjectIdenticalTo:mapping];
360 - (void)mergeMapping:(NJMapping *)mapping intoMapping:(NJMapping *)existing {
361 [existing mergeEntriesFrom:mapping];
362 [self mappingsChanged];
363 if (existing == _currentMapping)
364 [self activateMappingForcibly:mapping];
367 - (void)renameMapping:(NJMapping *)mapping to:(NSString *)name {
369 [self mappingsChanged];
370 if (mapping == _currentMapping)
371 [self activateMappingForcibly:mapping];
374 - (void)addMapping:(NJMapping *)mapping {
375 [self insertMapping:mapping atIndex:_mappings.count];
378 - (void)insertMapping:(NJMapping *)mapping atIndex:(NSInteger)idx {
379 [_mappings insertObject:mapping atIndex:idx];
380 [self mappingsChanged];
383 - (void)removeMappingAtIndex:(NSInteger)idx {
384 NSInteger currentIdx = [self indexOfMapping:_currentMapping];
385 [_mappings removeObjectAtIndex:idx];
386 [self activateMapping:self.mappings[MIN(currentIdx, _mappings.count - 1)]];
387 [self mappingsChanged];
390 - (void)moveMoveMappingFromIndex:(NSInteger)fromIdx toIndex:(NSInteger)toIdx {
391 [_mappings moveObjectAtIndex:fromIdx toIndex:toIdx];
392 [self mappingsChanged];