Rename methods uniquely between mapping/device controllers.
[enjoyable.git] / Classes / NJDeviceController.m
1 //
2 // NJDeviceController.m
3 // Enjoy
4 //
5 // Created by Sam McCall on 4/05/09.
6 //
7
8 #import "NJDeviceController.h"
9
10 #import "NJMapping.h"
11 #import "NJMappingsController.h"
12 #import "NJDevice.h"
13 #import "NJInput.h"
14 #import "NJOutput.h"
15 #import "NJEvents.h"
16
17 @implementation NJDeviceController {
18 NJHIDManager *_hidManager;
19 NSTimer *_continuousOutputsTick;
20 NSMutableArray *_continousOutputs;
21 NSMutableArray *_devices;
22 }
23
24 #define NSSTR(e) ((NSString *)CFSTR(e))
25
26 - (id)init {
27 if ((self = [super init])) {
28 _devices = [[NSMutableArray alloc] initWithCapacity:16];
29 _continousOutputs = [[NSMutableArray alloc] initWithCapacity:32];
30
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) }
38 ]
39 delegate:self];
40
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
49 // grabs.
50 [NSNotificationCenter.defaultCenter
51 addObserver:self
52 selector:@selector(stopHidIfDisabled:)
53 name:NSApplicationDidResignActiveNotification
54 object:nil];
55 [NSNotificationCenter.defaultCenter
56 addObserver:self
57 selector:@selector(startHid)
58 name:NSApplicationDidBecomeActiveNotification
59 object:nil];
60 }
61 return self;
62 }
63
64 - (void)dealloc {
65 [NSNotificationCenter.defaultCenter removeObserver:self];
66 [_continuousOutputsTick invalidate];
67 }
68
69 - (void)addRunningOutput:(NJOutput *)output {
70 // Axis events will trigger every small movement, don't keep
71 // re-adding them or they trigger multiple times each time.
72 if (![_continousOutputs containsObject:output])
73 [_continousOutputs addObject:output];
74 if (!_continuousOutputsTick) {
75 _continuousOutputsTick = [NSTimer scheduledTimerWithTimeInterval:1.0/60.0
76 target:self
77 selector:@selector(updateContinuousOutputs:)
78 userInfo:nil
79 repeats:YES];
80 }
81 }
82
83 - (void)runOutputForDevice:(IOHIDDeviceRef)device value:(IOHIDValueRef)value {
84 NJDevice *dev = [self findDeviceByRef:device];
85 NJInput *mainInput = [dev inputForEvent:value];
86 [mainInput notifyEvent:value];
87 NSArray *children = mainInput.children ? mainInput.children : mainInput ? @[mainInput] : @[];
88 for (NJInput *subInput in children) {
89 NJOutput *output = mappingsController.currentMapping[subInput];
90 output.magnitude = subInput.magnitude;
91 output.running = subInput.active;
92 if ((output.running || output.magnitude) && output.isContinuous)
93 [self addRunningOutput:output];
94 }
95 }
96
97 - (void)showOutputForDevice:(IOHIDDeviceRef)device value:(IOHIDValueRef)value {
98 NJDevice *dev = [self findDeviceByRef:device];
99 NJInput *handler = [dev handlerForEvent:value];
100 if (!handler)
101 return;
102
103 [self.delegate deviceController:self didInput:handler];
104 }
105
106 - (void)hidManager:(NJHIDManager *)manager
107 valueChanged:(IOHIDValueRef)value
108 fromDevice:(IOHIDDeviceRef)device {
109 if (self.simulatingEvents
110 && !NSApplication.sharedApplication.isActive) {
111 [self runOutputForDevice:device value:value];
112 } else {
113 [self showOutputForDevice:device value:value];
114 }
115 }
116
117 - (void)addDevice:(NJDevice *)device {
118 BOOL available;
119 do {
120 available = YES;
121 for (NJDevice *used in _devices) {
122 if ([used isEqual:device]) {
123 device.index += 1;
124 available = NO;
125 }
126 }
127 } while (!available);
128
129 [_devices addObject:device];
130 }
131
132 - (void)hidManager:(NJHIDManager *)manager deviceAdded:(IOHIDDeviceRef)device {
133 NJDevice *match = [[NJDevice alloc] initWithDevice:device];
134 [self addDevice:match];
135 [self.delegate deviceController:self didAddDevice:match];
136 }
137
138 - (NJDevice *)findDeviceByRef:(IOHIDDeviceRef)device {
139 for (NJDevice *dev in _devices)
140 if (dev.device == device)
141 return dev;
142 return nil;
143 }
144
145 - (void)hidManager:(NJHIDManager *)manager deviceRemoved:(IOHIDDeviceRef)device {
146 NJDevice *match = [self findDeviceByRef:device];
147 if (match) {
148 NSInteger idx = [_devices indexOfObjectIdenticalTo:match];
149 [_devices removeObjectAtIndex:idx];
150 [self.delegate deviceController:self didRemoveDeviceAtIndex:idx];
151 }
152 }
153
154 - (void)updateContinuousOutputs:(NSTimer *)timer {
155 self.mouseLoc = [NSEvent mouseLocation];
156 for (NJOutput *output in [_continousOutputs copy]) {
157 if (![output update:self]) {
158 [_continousOutputs removeObject:output];
159 }
160 }
161 if (!_continousOutputs.count) {
162 [_continuousOutputsTick invalidate];
163 _continuousOutputsTick = nil;
164 }
165 }
166
167 - (void)hidManager:(NJHIDManager *)manager didError:(NSError *)error {
168 [self.delegate deviceController:self didError:error];
169 self.simulatingEvents = NO;
170 }
171
172 - (void)hidManagerDidStart:(NJHIDManager *)manager {
173 [self.delegate deviceControllerDidStartHID:self];
174 }
175
176 - (void)hidManagerDidStop:(NJHIDManager *)manager {
177 [_devices removeAllObjects];
178 [self.delegate deviceControllerDidStopHID:self];
179 }
180
181 - (void)startHid {
182 [_hidManager start];
183 }
184
185 - (void)stopHid {
186 [_hidManager stop];
187 }
188
189 - (void)setSimulatingEvents:(BOOL)simulatingEvents {
190 if (simulatingEvents != _simulatingEvents) {
191 _simulatingEvents = simulatingEvents;
192 NSString *name = simulatingEvents
193 ? NJEventSimulationStarted
194 : NJEventSimulationStopped;
195 [NSNotificationCenter.defaultCenter postNotificationName:name
196 object:self];
197
198 if (!simulatingEvents && !NSApplication.sharedApplication.isActive)
199 [self stopHid];
200 else
201 [self startHid];
202 }
203 }
204
205 - (void)stopHidIfDisabled:(NSNotification *)application {
206 if (!self.simulatingEvents && !NSProcessInfo.processInfo.isBeingDebugged)
207 [self stopHid];
208 }
209
210 - (NJInputPathElement *)elementForUID:(NSString *)uid {
211 for (NJDevice *dev in _devices) {
212 id item = [dev elementForUID:uid];
213 if (item)
214 return item;
215 }
216 return nil;
217 }
218
219 @end