bdd1bbf9e0c21f382f861c97bf9ec95c0877b3e7
[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 "NJOutputController.h"
16 #import "NJEvents.h"
17 #import "NJDeviceViewController.h"
18
19 @implementation NJDeviceController {
20 NJHIDManager *_hidManager;
21 NSTimer *_continuousOutputsTick;
22 NSMutableArray *_continousOutputs;
23 NSMutableArray *_devices;
24 }
25
26 #define NSSTR(e) ((NSString *)CFSTR(e))
27
28 - (id)init {
29 if ((self = [super init])) {
30 _devices = [[NSMutableArray alloc] initWithCapacity:16];
31 _continousOutputs = [[NSMutableArray alloc] initWithCapacity:32];
32
33 _hidManager = [[NJHIDManager alloc] initWithCriteria:@[
34 @{ NSSTR(kIOHIDDeviceUsagePageKey) : @(kHIDPage_GenericDesktop),
35 NSSTR(kIOHIDDeviceUsageKey) : @(kHIDUsage_GD_Joystick) },
36 @{ NSSTR(kIOHIDDeviceUsagePageKey) : @(kHIDPage_GenericDesktop),
37 NSSTR(kIOHIDDeviceUsageKey) : @(kHIDUsage_GD_GamePad) },
38 @{ NSSTR(kIOHIDDeviceUsagePageKey) : @(kHIDPage_GenericDesktop),
39 NSSTR(kIOHIDDeviceUsageKey) : @(kHIDUsage_GD_MultiAxisController) }
40 ]
41 delegate:self];
42
43 // The HID manager uses 5-10ms per second doing basically
44 // nothing if a noisy device is plugged in (the % of that
45 // spent in input_callback is negligible, so it's not
46 // something we can make faster). I don't really think that's
47 // acceptable, CPU/power wise. So if simulation is disabled
48 // and the window is closed, just switch off the HID manager
49 // entirely. This probably also has some marginal benefits for
50 // compatibility with other applications that want exclusive
51 // grabs.
52 [NSNotificationCenter.defaultCenter
53 addObserver:self
54 selector:@selector(stopHidIfDisabled:)
55 name:NSApplicationDidResignActiveNotification
56 object:nil];
57 [NSNotificationCenter.defaultCenter
58 addObserver:self
59 selector:@selector(startHid)
60 name:NSApplicationDidBecomeActiveNotification
61 object:nil];
62 }
63 return self;
64 }
65
66 - (void)dealloc {
67 [NSNotificationCenter.defaultCenter removeObserver:self];
68 [_continuousOutputsTick invalidate];
69 }
70
71 - (void)addRunningOutput:(NJOutput *)output {
72 // Axis events will trigger every small movement, don't keep
73 // re-adding them or they trigger multiple times each time.
74 if (![_continousOutputs containsObject:output])
75 [_continousOutputs addObject:output];
76 if (!_continuousOutputsTick) {
77 _continuousOutputsTick = [NSTimer scheduledTimerWithTimeInterval:1.0/60.0
78 target:self
79 selector:@selector(updateContinuousOutputs:)
80 userInfo:nil
81 repeats:YES];
82 }
83 }
84
85 - (void)runOutputForDevice:(IOHIDDeviceRef)device value:(IOHIDValueRef)value {
86 NJDevice *dev = [self findDeviceByRef:device];
87 NJInput *mainInput = [dev inputForEvent:value];
88 [mainInput notifyEvent:value];
89 NSArray *children = mainInput.children ? mainInput.children : mainInput ? @[mainInput] : @[];
90 for (NJInput *subInput in children) {
91 NJOutput *output = mappingsController.currentMapping[subInput];
92 output.magnitude = subInput.magnitude;
93 output.running = subInput.active;
94 if ((output.running || output.magnitude) && output.isContinuous)
95 [self addRunningOutput:output];
96 }
97 }
98
99 - (void)showOutputForDevice:(IOHIDDeviceRef)device value:(IOHIDValueRef)value {
100 NJDevice *dev = [self findDeviceByRef:device];
101 NJInput *handler = [dev handlerForEvent:value];
102 if (!handler)
103 return;
104
105 [devicesViewController expandAndSelectItem:handler];
106 [outputController focusKey];
107 }
108
109 - (void)hidManager:(NJHIDManager *)manager
110 valueChanged:(IOHIDValueRef)value
111 fromDevice:(IOHIDDeviceRef)device {
112 if (self.simulatingEvents
113 && !NSApplication.sharedApplication.isActive) {
114 [self runOutputForDevice:device value:value];
115 } else {
116 [self showOutputForDevice:device value:value];
117 }
118 }
119
120 - (void)addDevice:(NJDevice *)device {
121 BOOL available;
122 do {
123 available = YES;
124 for (NJDevice *used in _devices) {
125 if ([used isEqual:device]) {
126 device.index += 1;
127 available = NO;
128 }
129 }
130 } while (!available);
131
132 [_devices addObject:device];
133 }
134
135 - (void)hidManager:(NJHIDManager *)manager deviceAdded:(IOHIDDeviceRef)device {
136 NJDevice *match = [[NJDevice alloc] initWithDevice:device];
137 [devicesViewController beginUpdates];
138 [self addDevice:match];
139 [devicesViewController addedDevice:match atIndex:_devices.count - 1];
140 [devicesViewController endUpdates];
141 }
142
143 - (NJDevice *)findDeviceByRef:(IOHIDDeviceRef)device {
144 for (NJDevice *dev in _devices)
145 if (dev.device == device)
146 return dev;
147 return nil;
148 }
149
150 - (void)hidManager:(NJHIDManager *)manager deviceRemoved:(IOHIDDeviceRef)device {
151 NJDevice *match = [self findDeviceByRef:device];
152 IOHIDDeviceRegisterInputValueCallback(device, NULL, NULL);
153 if (match) {
154 NSInteger idx = [_devices indexOfObjectIdenticalTo:match];
155 [devicesViewController beginUpdates];
156 [_devices removeObjectAtIndex:idx];
157 [devicesViewController removedDevice:match atIndex:idx];
158 [devicesViewController endUpdates];
159 }
160 }
161
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];
167 }
168 }
169 if (!_continousOutputs.count) {
170 [_continuousOutputsTick invalidate];
171 _continuousOutputsTick = nil;
172 }
173 }
174
175 - (void)hidManager:(NJHIDManager *)manager didError:(NSError *)error {
176 // Since the error shows the window, it can trigger another attempt
177 // to re-open the HID manager, which will also probably fail and error,
178 // so don't bother repeating ourselves.
179 if (!simulatingEventsButton.window.attachedSheet) {
180 [NSApplication.sharedApplication activateIgnoringOtherApps:YES];
181 [simulatingEventsButton.window makeKeyAndOrderFront:nil];
182 [simulatingEventsButton.window presentError:error
183 modalForWindow:simulatingEventsButton.window
184 delegate:nil
185 didPresentSelector:nil
186 contextInfo:nil];
187 }
188 self.simulatingEvents = NO;
189 }
190
191 - (void)hidManagerDidStart:(NJHIDManager *)manager {
192 [devicesViewController hidStarted];
193 }
194
195 - (void)hidManagerDidStop:(NJHIDManager *)manager {
196 [_devices removeAllObjects];
197 [devicesViewController hidStopped];
198 }
199
200 - (void)startHid {
201 [_hidManager start];
202 }
203
204 - (void)stopHid {
205 [_hidManager stop];
206 }
207
208 - (NJInput *)selectedInput {
209 return (NJInput *)devicesViewController.selectedHandler;
210 }
211
212 - (void)setSimulatingEvents:(BOOL)simulatingEvents {
213 if (simulatingEvents != _simulatingEvents) {
214 _simulatingEvents = simulatingEvents;
215 NSInteger state = simulatingEvents ? NSOnState : NSOffState;
216 simulatingEventsButton.state = state;
217 NSString *name = simulatingEvents
218 ? NJEventSimulationStarted
219 : NJEventSimulationStopped;
220 [NSNotificationCenter.defaultCenter postNotificationName:name
221 object:self];
222
223 if (!simulatingEvents && !NSApplication.sharedApplication.isActive)
224 [self stopHid];
225 else
226 [self startHid];
227 }
228 }
229
230 - (void)stopHidIfDisabled:(NSNotification *)application {
231 if (!self.simulatingEvents && !NSProcessInfo.processInfo.isBeingDebugged)
232 [self stopHid];
233 }
234
235 - (IBAction)simulatingEventsChanged:(NSButton *)sender {
236 self.simulatingEvents = sender.state == NSOnState;
237 }
238
239 - (NSInteger)numberOfDevicesInDeviceList:(NJDeviceViewController *)dvc {
240 return _devices.count;
241 }
242
243 - (NJDevice *)deviceViewController:(NJDeviceViewController *)dvc
244 deviceForIndex:(NSUInteger)idx {
245 return _devices[idx];
246 }
247
248 - (id)deviceViewController:(NJDeviceViewController *)dvc
249 elementForUID:(NSString *)uid {
250 for (NJDevice *dev in _devices) {
251 id item = [dev elementForUID:uid];
252 if (item)
253 return item;
254 }
255 return nil;
256 }
257
258 - (void)deviceViewControllerDidSelectNothing:(NJDeviceViewController *)dvc {
259 [outputController loadCurrent];
260 }
261
262 - (void)deviceViewController:(NJDeviceViewController *)dvc
263 didSelectBranch:(NJInputPathElement *)handler {
264 [outputController loadCurrent];
265 }
266
267 - (void)deviceViewController:(NJDeviceViewController *)dvc
268 didSelectHandler:(NJInputPathElement *)handler {
269 [outputController loadCurrent];
270 }
271
272 - (void)deviceViewController:(NJDeviceViewController *)dvc
273 didSelectDevice:(NJInputPathElement *)device {
274 [outputController loadCurrent];
275 }
276
277 @end