void os_inputclose(usbdevice* kb){ if(kb->uinput_kb <= 0 || kb->uinput_mouse <= 0) return; // Set all keys released struct input_event event; memset(&event, 0, sizeof(event)); event.type = EV_KEY; for(int key = 0; key < KEY_CNT; key++){ event.code = key; if(write(kb->uinput_kb - 1, &event, sizeof(event)) <= 0) ckb_warn("uinput write failed: %s\n", strerror(errno)); if(write(kb->uinput_mouse - 1, &event, sizeof(event)) <= 0) ckb_warn("uinput write failed: %s\n", strerror(errno)); } event.type = EV_SYN; event.code = SYN_REPORT; if(write(kb->uinput_kb - 1, &event, sizeof(event)) <= 0) ckb_warn("uinput write failed: %s\n", strerror(errno)); if(write(kb->uinput_mouse - 1, &event, sizeof(event)) <= 0) ckb_warn("uinput write failed: %s\n", strerror(errno)); // Close the keyboard ioctl(kb->uinput_kb - 1, UI_DEV_DESTROY); close(kb->uinput_kb - 1); kb->uinput_kb = 0; // Close the mouse ioctl(kb->uinput_mouse - 1, UI_DEV_DESTROY); close(kb->uinput_mouse - 1); kb->uinput_mouse = 0; }
// Generate SYN reports to synchronize device static void isync(usbdevice* kb){ struct input_event event; memset(&event, 0, sizeof(event)); event.type = EV_SYN; event.code = SYN_REPORT; if(write(kb->uinput_kb - 1, &event, sizeof(event)) <= 0) ckb_warn("uinput write failed: %s\n", strerror(errno)); if(write(kb->uinput_mouse - 1, &event, sizeof(event)) <= 0) ckb_warn("uinput write failed: %s\n", strerror(errno)); }
void os_mousemove(usbdevice* kb, int x, int y){ struct input_event event; memset(&event, 0, sizeof(event)); event.type = EV_REL; if(x != 0){ event.code = REL_X; event.value = x; if(write(kb->uinput_mouse - 1, &event, sizeof(event)) <= 0) ckb_warn("uinput write failed: %s\n", strerror(errno)); else isync(kb); } if(y != 0){ event.code = REL_Y; event.value = y; if(write(kb->uinput_mouse - 1, &event, sizeof(event)) <= 0) ckb_warn("uinput write failed: %s\n", strerror(errno)); else isync(kb); } }
int usbmain(){ // Load the uinput module (if it's not loaded already) if(system("modprobe uinput") != 0) ckb_warn("Failed to load uinput module\n"); // Create the udev object if(!(udev = udev_new())){ ckb_fatal("Failed to initialize udev\n"); return -1; } // Enumerate all currently connected devices udev_enum(); // Done scanning. Enter a loop to poll for device updates struct udev_monitor* monitor = udev_monitor_new_from_netlink(udev, "udev"); udev_monitor_filter_add_match_subsystem_devtype(monitor, "usb", 0); udev_monitor_enable_receiving(monitor); // Get an fd for the monitor int fd = udev_monitor_get_fd(monitor); fd_set fds; while(udev){ FD_ZERO(&fds); FD_SET(fd, &fds); // Block until an event is read if(select(fd + 1, &fds, 0, 0, 0) > 0 && FD_ISSET(fd, &fds)){ struct udev_device* dev = udev_monitor_receive_device(monitor); if(!dev) continue; const char* action = udev_device_get_action(dev); if(!action){ udev_device_unref(dev); continue; } // Add/remove device if(!strcmp(action, "add")){ int res = usb_add_device(dev); if(res == 0) continue; // If the device matched but the handle wasn't opened correctly, re-enumerate (this sometimes solves the problem) if(res == -1) udev_enum(); } else if(!strcmp(action, "remove")) usb_rm_device(dev); udev_device_unref(dev); } } udev_monitor_unref(monitor); return 0; }
// Event helpers static void postevent(io_connect_t event, UInt32 type, NXEventData* ev, IOOptionBits flags, IOOptionBits options, int silence_errors){ // Hack #1: IOHIDPostEvent will fail with kIOReturnNotPrivileged if the event doesn't originate from the UID that owns /dev/console // You'd think being root would be good enough. You'd be wrong. ckb-daemon needs to run as root for other reasons though // (namely, being able to seize the physical IOHIDDevices) so what we do instead is change our EUID to the appropriate owner, // post the event, and then change it right back. // Yeah... uid_t uid = 0; gid_t gid = 0; struct stat file; if(!stat("/dev/console", &file)){ uid = file.st_uid; gid = file.st_gid; } euid_guard_start; if(uid != 0) seteuid(uid); if(gid != 0) setegid(gid); IOGPoint location = {0, 0}; if((options & kIOHIDSetRelativeCursorPosition) && type != NX_MOUSEMOVED){ // Hack #2: IOHIDPostEvent will not accept relative mouse coordinates for any event other than NX_MOUSEMOVED // So we need to get the current absolute coordinates from CoreGraphics and then modify those... CGEventRef cge = CGEventCreate(nil); CGPoint loc = CGEventGetLocation(cge); CFRelease(cge); location.x = floor(loc.x + ev->mouseMove.dx); location.y = floor(loc.y + ev->mouseMove.dy); options = (options & ~kIOHIDSetRelativeCursorPosition) | kIOHIDSetCursorPosition; } kern_return_t res = IOHIDPostEvent(event, type, location, ev, kNXEventDataVersion, flags | NX_NONCOALSESCEDMASK, options); if(res != kIOReturnSuccess && !silence_errors) ckb_warn("Post event failed: %x\n", res); if(uid != 0) seteuid(0); if(gid != 0) setegid(0); euid_guard_stop; }
// Xorg has buggy handling of combined keyboard + mouse devices, so instead we should create two separate devices: // One for keyboard events, one for mouse. int uinputopen(struct uinput_user_dev* indev, int mouse){ int fd = open("/dev/uinput", O_RDWR); if(fd < 0){ // If that didn't work, try /dev/input/uinput instead fd = open("/dev/input/uinput", O_RDWR); if(fd < 0){ ckb_err("Failed to open uinput: %s\n", strerror(errno)); return 0; } } // Enable all keys and mouse buttons ioctl(fd, UI_SET_EVBIT, EV_KEY); for(int i = 0; i < KEY_CNT; i++) ioctl(fd, UI_SET_KEYBIT, i); if(mouse){ // Enable mouse axes ioctl(fd, UI_SET_EVBIT, EV_REL); for(int i = 0; i < REL_CNT; i++) ioctl(fd, UI_SET_RELBIT, i); } else { // Enable LEDs ioctl(fd, UI_SET_EVBIT, EV_LED); for(int i = 0; i < LED_CNT; i++) ioctl(fd, UI_SET_LEDBIT, i); // Eanble autorepeat ioctl(fd, UI_SET_EVBIT, EV_REP); } // Enable sychronization ioctl(fd, UI_SET_EVBIT, EV_SYN); // Create the device if(write(fd, indev, sizeof(*indev)) <= 0) ckb_warn("uinput write failed: %s\n", strerror(errno)); if(ioctl(fd, UI_DEV_CREATE)){ ckb_err("Failed to create uinput device: %s\n", strerror(errno)); close(fd); return 0; } return fd + 1; }
void os_keypress(usbdevice* kb, int scancode, int down){ struct input_event event; memset(&event, 0, sizeof(event)); int is_mouse = 0; if(scancode == BTN_WHEELUP || scancode == BTN_WHEELDOWN){ // The mouse wheel is a relative axis if(!down) return; event.type = EV_REL; event.code = REL_WHEEL; event.value = (scancode == BTN_WHEELUP ? 1 : -1); is_mouse = 1; } else { // Mouse buttons and key events are both EV_KEY. The scancodes are already correct, just remove the ckb bit event.type = EV_KEY; event.code = scancode & ~SCAN_MOUSE; event.value = down; is_mouse = !!(scancode & SCAN_MOUSE); } if(write((is_mouse ? kb->uinput_mouse : kb->uinput_kb) - 1, &event, sizeof(event)) <= 0) ckb_warn("uinput write failed: %s\n", strerror(errno)); else isync(kb); }
int os_setupusb(usbdevice* kb){ // Copy device description and serial struct udev_device* dev = kb->udev; const char* name = udev_device_get_sysattr_value(dev, "product"); if(name) strncpy(kb->name, name, KB_NAME_LEN); strtrim(kb->name); const char* serial = udev_device_get_sysattr_value(dev, "serial"); if(serial) strncpy(kb->serial, serial, SERIAL_LEN); strtrim(kb->serial); // Copy firmware version (needed to determine USB protocol) const char* firmware = udev_device_get_sysattr_value(dev, "bcdDevice"); if(firmware) sscanf(firmware, "%hx", &kb->fwversion); else kb->fwversion = 0; int index = INDEX_OF(kb, keyboard); ckb_info("Connecting %s at %s%d\n", kb->name, devpath, index); // Claim the USB interfaces const char* ep_str = udev_device_get_sysattr_value(dev, "bNumInterfaces"); kb->epcount = 0; if(ep_str) sscanf(ep_str, "%d", &kb->epcount); if(kb->epcount == 0){ // This shouldn't happen, but if it does, assume EP count based on what the device is supposed to have kb->epcount = (HAS_FEATURES(kb, FEAT_RGB) ? 4 : 3); ckb_warn("Unable to read endpoint count from udev, assuming %d...\n", kb->epcount); } if(usbclaim(kb)){ ckb_err("Failed to claim interfaces: %s\n", strerror(errno)); return -1; } return 0; }
void hid_kb_translate(unsigned char* kbinput, int endpoint, int length, const unsigned char* urbinput){ if(length < 1) return; // LUT for HID -> Corsair scancodes (-1 for no scan code, -2 for currently unsupported) // Modified from Linux drivers/hid/usbhid/usbkbd.c, key codes replaced with array indices and K95 keys added static const short hid_codes[256] = { -1, -1, -1, -1, 37, 54, 52, 39, 27, 40, 41, 42, 32, 43, 44, 45, 56, 55, 33, 34, 25, 28, 38, 29, 31, 53, 26, 51, 30, 50, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 82, 0, 86, 24, 64, 23, 84, 35, 79, 80, 81, 46, 47, 12, 57, 58, 59, 36, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 72, 73, 74, 75, 76, 77, 78, 87, 88, 89, 95, 93, 94, 92, 102, 103, 104, 105, 106, 107, 115, 116, 117, 112, 113, 114, 108, 109, 110, 118, 119, 49, 69, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, 98, -2, -2, -2, -2, -2, -2, 97, 130, 131, -1, -1, -1, -2, -1, -2, -2, -2, -2, -2, -2, -1, -1, -1, -2, -2, -2, -2, -2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -3, -1, -1, -1, // <- -3 = non-RGB program key 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 136, 137, 138, 139, 140, 141, 60, 48, 62, 61, 91, 90, 67, 68, 142, 143, 99, 101, -2, 130, 131, 97, -2, 133, 134, 135, -2, 96, -2, 132, -2, -2, 71, 71, 71, 71, -1, -1, }; switch(endpoint){ case 1: case -1: // EP 1: 6KRO input (RGB and non-RGB) // Clear previous input for(int i = 0; i < 256; i++){ if(hid_codes[i] >= 0) CLEAR_KEYBIT(kbinput, hid_codes[i]); } // Set new input for(int i = 0; i < 8; i++){ if((urbinput[0] >> i) & 1) SET_KEYBIT(kbinput, hid_codes[i + 224]); } for(int i = 2; i < length; i++){ if(urbinput[i] > 3){ int scan = hid_codes[urbinput[i]]; if(scan >= 0) SET_KEYBIT(kbinput, scan); else ckb_warn("Got unknown key press %d on EP 1\n", urbinput[i]); } } break; case -2: // EP 2 RGB: NKRO input if(urbinput[0] == 1){ // Type 1: standard key if(length != 21) return; for(int bit = 0; bit < 8; bit++){ if((urbinput[1] >> bit) & 1) SET_KEYBIT(kbinput, hid_codes[bit + 224]); else CLEAR_KEYBIT(kbinput, hid_codes[bit + 224]); } for(int byte = 0; byte < 19; byte++){ char input = urbinput[byte + 2]; for(int bit = 0; bit < 8; bit++){ int keybit = byte * 8 + bit; int scan = hid_codes[keybit]; if((input >> bit) & 1){ if(scan >= 0) SET_KEYBIT(kbinput, hid_codes[keybit]); else ckb_warn("Got unknown key press %d on EP 2\n", keybit); } else if(scan >= 0) CLEAR_KEYBIT(kbinput, hid_codes[keybit]); } } break; } else if(urbinput[0] == 2)
usbdevice* usbadd(hid_dev_t handle, io_object_t** rm_notify){ // Get the model and serial number long idvendor = V_CORSAIR, idproduct = usbgetlong(handle, CFSTR(kIOHIDProductIDKey)); // Each keyboard generates multiple match events (one for each endpoint) // Use the location ID key to group the handles together long location = usbgetlong(handle, CFSTR(kIOHIDLocationIDKey)); // Look for any partially-set up boards matching this device int index = -1; for(int i = 1; i < DEV_MAX; i++){ if(pthread_mutex_trylock(devmutex + i)) // If the mutex is locked then the device is obviously set up already, keep going continue; if(keyboard[i].handle == INCOMPLETE && keyboard[i].vendor == idvendor && keyboard[i].product == idproduct && keyboard[i].location_id == location){ // Matched; continue setting up this device index = i; // Device mutex remains locked break; } pthread_mutex_unlock(devmutex + i); } // If none was found, grab the first free device if(index == -1){ for(int i = 1; i < DEV_MAX; i++){ if(pthread_mutex_trylock(devmutex + i)) continue; if(!keyboard[i].handle){ // Mark the device as in use and print out a message index = i; keyboard[i].handle = INCOMPLETE; keyboard[i].location_id = location; keyboard[i].vendor = idvendor; keyboard[i].product = idproduct; // Read the serial number and name usbgetstr(handle, CFSTR(kIOHIDSerialNumberKey), keyboard[i].serial, SERIAL_LEN); usbgetstr(handle, CFSTR(kIOHIDProductKey), keyboard[i].name, KB_NAME_LEN); ckb_info("Connecting %s (S/N: %s)\n", keyboard[i].name, keyboard[i].serial); // Device mutex remains locked break; } pthread_mutex_unlock(devmutex + i); } } if(index == -1){ ckb_err("No free devices\n"); return 0; } usbdevice* kb = keyboard + index; // There's no direct way to tell which of the endpoints this is, but there's a workaround // Each handle has a unique maximum packet size combination, so use that to place them long input = usbgetlong(handle, CFSTR(kIOHIDMaxInputReportSizeKey)); long output = usbgetlong(handle, CFSTR(kIOHIDMaxOutputReportSizeKey)); long feature = usbgetlong(handle, CFSTR(kIOHIDMaxFeatureReportSizeKey)); int handle_idx; // Handle 0 is for BIOS mode input (RGB) or non-RGB key input if(((input == 8 && output == 1) // Keyboards || (input == 7 && output == 0)) // Mice && feature == 0) handle_idx = 0; // Handle 1 is for standard HID input (RGB) or media keys (non-RGB) else if(((input == 21 || input == 10) && output == 1 && feature == 1) || (input == 4 && output == 0 && feature == 0)) handle_idx = 1; // Handle 2 is for Corsair inputs, unused on non-RGB else if(((input == 64 || input == 15) && output == 0 && feature == 0) || (input == 64 && output == 64 && feature == 0)) // FW >= 1.20 handle_idx = 2; // Handle 3 is for controlling the device (only exists for RGB) else if((input == 0 && output == 0 && feature == 64) || (input == 64 && output == 64 && feature == 64)) // FW >= 1.20 handle_idx = 3; else { ckb_warn("Got unknown handle (I: %d, O: %d, F: %d)\n", (int)input, (int)output, (int)feature); return 0; } // If all handles have been set up, finish initializing the keyboard kb->handles[handle_idx] = handle; if(kb->handles[0] && kb->handles[1] && kb->handles[2] && (kb->handles[3] || !IS_RGB(idvendor, idproduct))) setupusb(kb); else pthread_mutex_unlock(devmutex + index); *rm_notify = kb->rm_notify + handle_idx; return kb; }