ckb-next  v0.2.8 at branch master
ckb-next driver for corsair devices
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
input_mac.c
Go to the documentation of this file.
1 #include "command.h"
2 #include "device.h"
3 #include "input.h"
4 
5 #ifdef OS_MAC
6 
7 // Numpad keys have an extra flag
8 #define IS_NUMPAD(scancode) ((scancode) >= kVK_ANSI_KeypadDecimal && (scancode) <= kVK_ANSI_Keypad9 && (scancode) != kVK_ANSI_KeypadClear && (scancode) != kVK_ANSI_KeypadEnter)
9 
10 pthread_mutex_t _euid_guard = PTHREAD_MUTEX_INITIALIZER;
11 
12 // Event helpers
13 static void postevent(io_connect_t event, UInt32 type, NXEventData* ev, IOOptionBits flags, IOOptionBits options, int silence_errors){
14  // Hack #1: IOHIDPostEvent will fail with kIOReturnNotPrivileged if the event doesn't originate from the UID that owns /dev/console
15  // You'd think being root would be good enough. You'd be wrong. ckb-daemon needs to run as root for other reasons though
16  // (namely, being able to seize the physical IOHIDDevices) so what we do instead is change our EUID to the appropriate owner,
17  // post the event, and then change it right back.
18  // Yeah...
19  uid_t uid = 0;
20  gid_t gid = 0;
21  struct stat file;
22  if(!stat("/dev/console", &file)){
23  uid = file.st_uid;
24  gid = file.st_gid;
25  }
26 
27  IOGPoint location = {0, 0};
28  if((options & kIOHIDSetRelativeCursorPosition) && type != NX_MOUSEMOVED){
29  // Hack #2: IOHIDPostEvent will not accept relative mouse coordinates for any event other than NX_MOUSEMOVED
30  // So we need to get the current absolute coordinates from CoreGraphics and then modify those...
31  CGEventRef cge = CGEventCreate(nil);
32  CGPoint loc = CGEventGetLocation(cge);
33  CFRelease(cge);
34  location.x = floor(loc.x + ev->mouseMove.dx);
35  location.y = floor(loc.y + ev->mouseMove.dy);
36  options = (options & ~kIOHIDSetRelativeCursorPosition) | kIOHIDSetCursorPosition;
37  }
38 
40  if(uid != 0)
41  seteuid(uid);
42  if(gid != 0)
43  setegid(gid);
44 
45  kern_return_t res = IOHIDPostEvent(event, type, location, ev, kNXEventDataVersion, flags | NX_NONCOALSESCEDMASK, options);
46  if(res != kIOReturnSuccess && !silence_errors)
47  ckb_warn("Post event failed: %x\n", res);
48 
49  if(uid != 0)
50  seteuid(0);
51  if(gid != 0)
52  setegid(0);
54 }
55 
56 // Keypress
57 #define aux_key_data(scancode, down, is_repeat) ((scancode) << 16 | ((down) ? 0x0a00 : 0x0b00) | !!(is_repeat))
58 static void postevent_kp(io_connect_t event, int kbflags, int scancode, int down, int is_flags, int is_repeat){
59  NXEventData kp;
60  memset(&kp, 0, sizeof(kp));
61  UInt32 type;
62  IOOptionBits flags = kbflags;
63  IOOptionBits options = 0;
64  if(scancode == KEY_CAPSLOCK){
65  // Caps lock emits NX_FLAGSCHANGED when pressed, but also NX_SYSDEFINED on both press and release
66  kp.compound.subType = NX_SUBTYPE_AUX_CONTROL_BUTTONS;
67  kp.compound.misc.L[0] = aux_key_data(NX_KEYTYPE_CAPS_LOCK, down, is_repeat);
68  postevent(event, NX_SYSDEFINED, &kp, flags, options, 1);
69  if(!down)
70  return;
71  memset(&kp, 0, sizeof(kp));
72  }
73  if(IS_MEDIA(scancode)){
74  kp.compound.subType = (scancode != KEY_POWER ? NX_SUBTYPE_AUX_CONTROL_BUTTONS : NX_SUBTYPE_POWER_KEY);
75  kp.compound.misc.L[0] = aux_key_data(scancode - KEY_MEDIA, down, is_repeat);
76  type = NX_SYSDEFINED;
77  } else {
78  if(is_flags){
79  // Modifier (shift, ctrl, etc)
80  type = NX_FLAGSCHANGED;
81  options = kIOHIDSetGlobalEventFlags;
82  } else
83  // Standard key
84  type = down ? NX_KEYDOWN : NX_KEYUP;
85  kp.key.repeat = is_repeat;
86  kp.key.keyCode = scancode;
87  kp.key.origCharSet = kp.key.charSet = NX_ASCIISET;
88  if(IS_NUMPAD(scancode))
89  flags |= NX_NUMERICPADMASK;
90  else if(scancode == kVK_Help)
91  flags |= NX_HELPMASK;
92  }
93  postevent(event, type, &kp, flags, options, !down || is_repeat);
94  // Don't print errors on key up or key repeat ^
95 }
96 
97 // Mouse button
98 static void postevent_mb(io_connect_t event, int button, int down){
99  NXEventData mb;
100  memset(&mb, 0, sizeof(mb));
101  mb.compound.subType = NX_SUBTYPE_AUX_MOUSE_BUTTONS;
102  mb.compound.misc.L[0] = (1 << button);
103  mb.compound.misc.L[1] = down ? (1 << button) : 0;
104  postevent(event, NX_SYSDEFINED, &mb, 0, 0, !down);
105  // Mouse presses actually generate two events, one with a bitfield of buttons, one with a button number
106  memset(&mb, 0, sizeof(mb));
107  UInt32 type;
108  mb.mouse.buttonNumber = button;
109  switch(button){
110  case 0:
111  type = down ? NX_LMOUSEDOWN : NX_LMOUSEUP;
112  break;
113  case 1:
114  type = down ? NX_RMOUSEDOWN : NX_RMOUSEUP;
115  break;
116  default:
117  type = down ? NX_OMOUSEDOWN : NX_OMOUSEUP;
118  }
119  if(down)
120  mb.mouse.pressure = 255;
121  postevent(event, type, &mb, 0, 0, 1);
122 }
123 
124 // input_mac_mouse.c
125 extern void wheel_accel(io_connect_t event, int* deltaAxis1, SInt32* fixedDeltaAxis1, SInt32* pointDeltaAxis1);
126 extern void mouse_accel(io_connect_t event, int* x, int* y);
127 
128 // Mouse wheel
129 static void postevent_wheel(io_connect_t event, int scroll_rate, int value){
130  NXEventData mm;
131  memset(&mm, 0, sizeof(mm));
132  if(scroll_rate == SCROLL_ACCELERATED){
133  wheel_accel(event, &value, &mm.scrollWheel.fixedDeltaAxis1, &mm.scrollWheel.pointDeltaAxis1);
134  mm.scrollWheel.deltaAxis1 = value;
135  } else {
136  // If acceleration is disabled, use a fixed delta
137  mm.scrollWheel.deltaAxis1 = value * scroll_rate;
138  }
139  postevent(event, NX_SCROLLWHEELMOVED, &mm, 0, 0, 0);
140 }
141 
142 // Mouse axis
143 static void postevent_mm(io_connect_t event, int x, int y, int use_accel, uchar buttons){
144  NXEventData mm;
145  memset(&mm, 0, sizeof(mm));
146  UInt32 type = NX_MOUSEMOVED;
147  if(use_accel)
148  mouse_accel(event, &x, &y);
149  mm.mouseMove.dx = x;
150  mm.mouseMove.dy = y;
151  if(buttons != 0){
152  // If a button is pressed, the event type changes
153  if(buttons & 1)
154  type = NX_LMOUSEDRAGGED;
155  else if(buttons & 2)
156  type = NX_RMOUSEDRAGGED;
157  else
158  type = NX_OMOUSEDRAGGED;
159  // Pick the button index based on the lowest-numbered button
160  int button = 0;
161  while(!(buttons & 1)){
162  button++;
163  buttons >>= 1;
164  }
165  mm.mouse.pressure = 255;
166  mm.mouse.buttonNumber = button;
167  }
168  postevent(event, type, &mm, 0, kIOHIDSetRelativeCursorPosition, 1);
169 }
170 
171 // Key repeat delay helper (result in ns)
172 long repeattime(io_connect_t event, int first){
173  long delay = 0;
174  IOByteCount actualSize = 0;
175  if(IOHIDGetParameter(event, first ? CFSTR(kIOHIDInitialKeyRepeatKey) : CFSTR(kIOHIDKeyRepeatKey), sizeof(long), &delay, &actualSize) != KERN_SUCCESS || actualSize == 0)
176  return -1;
177  return delay;
178 }
179 
180 // Send keyup events for every scancode in the keymap
181 void clearkeys(usbdevice* kb){
182  for(int key = 0; key < N_KEYS_INPUT; key++){
183  int scan = keymap[key].scan;
184  if((scan & SCAN_SILENT) || scan == BTN_WHEELUP || scan == BTN_WHEELDOWN || IS_MEDIA(scan))
185  continue;
186  postevent_kp(kb->event, 0, scan, 0, 0, 0);
187  }
188 }
189 
190 // Opens HID service. Returns kIOReturnSuccess on success.
191 static int open_iohid(io_connect_t* connection){
192  io_iterator_t iter;
193  io_service_t service;
194  // Open master port (if not done yet)
195  static mach_port_t master = 0;
196  kern_return_t res;
197  if(!master && (res = IOMasterPort(bootstrap_port, &master)) != kIOReturnSuccess){
198  master = 0;
199  ckb_err("Unable to open master port: 0x%08x\n", res);
200  goto failure;
201  }
202  // Open the HID service
203  if((res = IOServiceGetMatchingServices(master, IOServiceMatching(kIOHIDSystemClass), &iter)) != kIOReturnSuccess)
204  goto failure;
205  service = IOIteratorNext(iter);
206  if(!service){
207  res = kIOReturnNotOpen;
208  goto failure_release_iter;
209  }
210  if((res = IOServiceOpen(service, mach_task_self(), kIOHIDParamConnectType, connection)) != kIOReturnSuccess){
211  *connection = 0;
212  goto failure_release_iter;
213  }
214  // Finished; release objects and return success
215  IOObjectRelease(service);
216  failure_release_iter:
217  IOObjectRelease(iter);
218  failure:
219  return res;
220 }
221 
222 int os_inputopen(usbdevice* kb){
223  // The IO service isn't always ready at startup, so if it's not, wait until it is
224  IOReturn res;
225  while((res = open_iohid(&kb->event)) != kIOReturnSuccess){
226  if(res != kIOReturnNotOpen){
227  // If this is a more serious error, at least print a warning
228  ckb_err("Unable to open HID system: 0x%08x\n", res);
229  sleep(1);
230  continue;
231  }
232  clock_nanosleep(CLOCK_MONOTONIC, 0, &(struct timespec) {.tv_nsec = 10000000}, NULL);
233  }
234 
235  clearkeys(kb);
236  return 0;
237 }
238 
239 void os_inputclose(usbdevice* kb){
240  if(kb->event){
241  clearkeys(kb);
242  IOServiceClose(kb->event);
243  kb->event = 0;
244  }
245 }
246 
247 // Starts/stops key-repeat timer as appropriate
248 static void setkrtimer(usbdevice* kb){
249  if(kb->lastkeypress == KEY_NONE)
250  CFRunLoopRemoveTimer(kb->input_loop, kb->krtimer, kCFRunLoopCommonModes);
251  else
252  CFRunLoopAddTimer(kb->input_loop, kb->krtimer, kCFRunLoopCommonModes);
253 }
254 
255 // Retrigger the last-pressed key
256 static void _keyretrigger(usbdevice* kb){
257  int scancode = kb->lastkeypress;
258  postevent_kp(kb->event, kb->modifiers, scancode, 1, 0, 1);
259  // Set next key repeat time
260  long repeat = repeattime(kb->event, 0);
261  if(repeat > 0)
262  timespec_add(&kb->keyrepeat, repeat);
263  else
264  kb->lastkeypress = KEY_NONE;
265 }
266 
267 void keyretrigger(CFRunLoopTimerRef timer, void* info){
268  usbdevice* kb = info;
269  // Repeat the key as many times as needed to catch up
270  struct timespec time;
271  clock_gettime(CLOCK_MONOTONIC, &time);
272  while(!(kb->lastkeypress & (SCAN_SILENT | SCAN_MOUSE)) && timespec_ge(time, kb->keyrepeat))
273  _keyretrigger(kb);
274  setkrtimer(kb);
275 }
276 
277 // Unlike Linux, OSX keyboards have independent caps lock states. This means they're set by the driver itself so we don't poll for external events.
278 // However, updating indicator state requires locking dmutex and we never want to do that in the input thread.
279 // Instead, we launch a single-shot thread to update the state.
280 static void* indicator_update(void* context){
281  usbdevice* kb = context;
282  pthread_mutex_lock(dmutex(kb));
283  {
284  pthread_mutex_lock(imutex(kb));
285  IOOptionBits modifiers = kb->modifiers;
286  // Allow the thread to be spawned again
287  kb->indicthread = 0;
288  pthread_mutex_unlock(imutex(kb));
289  // Num lock on, Caps dependent on modifier state
290  uchar ileds = 1 | !!(modifiers & NX_ALPHASHIFTMASK) << 1;
291  kb->hw_ileds = ileds;
292  kb->vtable->updateindicators(kb, 0);
293  }
294  pthread_mutex_unlock(dmutex(kb));
295  return 0;
296 }
297 
298 void os_keypress(usbdevice* kb, int scancode, int down){
299  // Trigger any pending repeats first
300  keyretrigger(NULL, kb);
301  // Key/button pressed
302  if(scancode & SCAN_MOUSE){
303  if(scancode == BTN_WHEELUP){
304  if(down)
305  postevent_wheel(kb->event, kb->scroll_rate, 1);
306  return;
307  } else if(scancode == BTN_WHEELDOWN){
308  if(down)
309  postevent_wheel(kb->event, kb->scroll_rate, -1);
310  return;
311  }
312  int button = scancode & ~SCAN_MOUSE;
313  // Reverse or collapse left/right buttons if the system preferences say so
314  int mode;
315  if(IOHIDGetMouseButtonMode(kb->event, &mode) == kIOReturnSuccess){
316  if(mode == kIOHIDButtonMode_ReverseLeftRightClicks && button == 0)
317  button = 1;
318  else if(mode != kIOHIDButtonMode_EnableRightClick && button == 1)
319  button = 0;
320  }
321  postevent_mb(kb->event, button, down);
322  if(down)
323  kb->mousestate |= (1 << button);
324  else
325  kb->mousestate &= ~(1 << button);
326  return;
327  }
328  // Some boneheaded Apple engineers decided to reverse kVK_ANSI_Grave and kVK_ISO_Section on the 105-key layouts...
329  if(!HAS_ANY_FEATURE(kb, FEAT_LMASK)){
330  // If the layout hasn't been set yet, it can be auto-detected from certain keys
331  if(scancode == KEY_BACKSLASH_ISO || scancode == KEY_102ND)
332  kb->features |= FEAT_ISO;
333  else if(scancode == KEY_BACKSLASH)
334  kb->features |= FEAT_ANSI;
335  }
336  if(scancode == KEY_BACKSLASH_ISO)
337  scancode = KEY_BACKSLASH;
338  if(HAS_FEATURES(kb, FEAT_ISO)){
339  // Compensate for key reversal
340  if(scancode == KEY_GRAVE)
341  scancode = KEY_102ND;
342  else if(scancode == KEY_102ND)
343  scancode = KEY_GRAVE;
344  }
345  // Check for modifier keys and update flags
346  int isMod = 0;
347  IOOptionBits mod = 0;
348  if(scancode == KEY_CAPSLOCK){
349  if(down)
350  kb->modifiers ^= NX_ALPHASHIFTMASK;
351  isMod = 1;
352  // Detach a thread to update the indicator state
353  if(!kb->indicthread){
354  // The thread is only spawned if kb->indicthread is null.
355  // Due to the logic inside the thread, this means that it could theoretically be spawned twice, but never a third time.
356  // Moreover, if it is spawned more than once, the indicator state will remain correct due to dmutex staying locked.
357  if(!pthread_create(&kb->indicthread, 0, indicator_update, kb))
358  pthread_detach(kb->indicthread);
359  }
360  }
361  else if(scancode == KEY_LEFTSHIFT) mod = NX_DEVICELSHIFTKEYMASK;
362  else if(scancode == KEY_RIGHTSHIFT) mod = NX_DEVICERSHIFTKEYMASK;
363  else if(scancode == KEY_LEFTCTRL) mod = NX_DEVICELCTLKEYMASK;
364  else if(scancode == KEY_RIGHTCTRL) mod = NX_DEVICERCTLKEYMASK;
365  else if(scancode == KEY_LEFTMETA) mod = NX_DEVICELCMDKEYMASK;
366  else if(scancode == KEY_RIGHTMETA) mod = NX_DEVICERCMDKEYMASK;
367  else if(scancode == KEY_LEFTALT) mod = NX_DEVICELALTKEYMASK;
368  else if(scancode == KEY_RIGHTALT) mod = NX_DEVICERALTKEYMASK;
369  else if(scancode == KEY_FN) mod = NX_SECONDARYFNMASK;
370  if(mod){
371  // Update global modifiers
372  if(down)
373  mod |= kb->modifiers;
374  else
375  mod = kb->modifiers & ~mod;
376  if((mod & NX_DEVICELSHIFTKEYMASK) || (mod & NX_DEVICERSHIFTKEYMASK)) mod |= NX_SHIFTMASK; else mod &= ~NX_SHIFTMASK;
377  if((mod & NX_DEVICELCTLKEYMASK) || (mod & NX_DEVICERCTLKEYMASK)) mod |= NX_CONTROLMASK; else mod &= ~NX_CONTROLMASK;
378  if((mod & NX_DEVICELCMDKEYMASK) || (mod & NX_DEVICERCMDKEYMASK)) mod |= NX_COMMANDMASK; else mod &= ~NX_COMMANDMASK;
379  if((mod & NX_DEVICELALTKEYMASK) || (mod & NX_DEVICERALTKEYMASK)) mod |= NX_ALTERNATEMASK; else mod &= ~NX_ALTERNATEMASK;
380  kb->modifiers = mod;
381  kb->lastkeypress = KEY_NONE;
382  isMod = 1;
383  } else if(!isMod){
384  // For any other key, trigger key repeat
385  if(down){
386  long repeat = repeattime(kb->event, 1);
387  if(repeat > 0){
388  kb->lastkeypress = scancode;
389  clock_gettime(CLOCK_MONOTONIC, &kb->keyrepeat);
390  timespec_add(&kb->keyrepeat, repeat);
391  } else
392  kb->lastkeypress = KEY_NONE;
393  } else
394  kb->lastkeypress = KEY_NONE;
395  }
396 
397  postevent_kp(kb->event, kb->modifiers, scancode, down, isMod, 0);
398  setkrtimer(kb);
399 }
400 
401 void os_mousemove(usbdevice* kb, int x, int y){
402  postevent_mm(kb->event, x, y, HAS_FEATURES(kb, FEAT_MOUSEACCEL), kb->mousestate);
403 }
404 
406  // Set NumLock on permanently
407  kb->hw_ileds = kb->hw_ileds_old = kb->ileds = 1;
408  return 0;
409 }
410 
411 #endif
#define FEAT_MOUSEACCEL
Definition: structures.h:148
float y
Definition: main.c:66
short scan
Definition: keymap.h:52
const key keymap[(((152+22+12)+25)+12)]
Definition: keymap.c:5
#define SCROLL_ACCELERATED
Definition: structures.h:164
#define ckb_err(fmt, args...)
Definition: includes.h:49
float x
Definition: main.c:66
#define KEY_BACKSLASH_ISO
Definition: keymap.h:20
void os_mousemove(usbdevice *kb, int x, int y)
Definition: input_linux.c:143
uchar hw_ileds_old
Definition: structures.h:247
#define FEAT_ANSI
Definition: structures.h:146
#define SCAN_SILENT
Definition: keymap.h:56
Definition: keymap.h:49
uchar ileds
Definition: structures.h:247
#define BTN_WHEELUP
Definition: keymap.h:12
void os_keypress(usbdevice *kb, int scancode, int down)
Definition: input_linux.c:118
unsigned char uchar
Definition: includes.h:24
#define KEY_NONE
Definition: keymap.h:7
int os_inputopen(usbdevice *kb)
os_inputopen
Definition: input_linux.c:55
long gid
Group ID for the control nodes. -1 to give read/write access to everybody.
Definition: devnode.c:16
#define ckb_warn(fmt, args...)
Definition: includes.h:52
#define euid_guard_start
Definition: os.h:40
#define timespec_ge(left, right)
Definition: includes.h:61
const union devcmd * vtable
Definition: structures.h:180
int os_setupindicators(usbdevice *kb)
Definition: input_linux.c:189
#define imutex(kb)
Definition: device.h:22
#define HAS_ANY_FEATURE(kb, feat)
Definition: structures.h:158
#define FEAT_ISO
Definition: structures.h:147
#define N_KEYS_INPUT
Definition: keymap.h:36
void os_inputclose(usbdevice *kb)
Definition: input_linux.c:76
ushort features
Definition: structures.h:229
#define HAS_FEATURES(kb, feat)
Definition: structures.h:157
#define FEAT_LMASK
Definition: structures.h:154
uchar hw_ileds
Definition: structures.h:247
#define SCAN_MOUSE
Definition: keymap.h:58
#define dmutex(kb)
Definition: device.h:18
#define euid_guard_stop
Definition: os.h:41
#define BTN_WHEELDOWN
Definition: keymap.h:13
void timespec_add(struct timespec *timespec, long nanoseconds)
Definition: main.c:19