int os_inputopen(usbdevice* kb){ // Open master port (if not done yet) static mach_port_t master = 0; kern_return_t res; if(!master && (res = IOMasterPort(bootstrap_port, &master)) != KERN_SUCCESS){ master = 0; ckb_err("Unable to open master port: 0x%08x\n", res); return -1; } // Open an HID service io_iterator_t iter; if((res = IOServiceGetMatchingServices(master, IOServiceMatching(kIOHIDSystemClass), &iter)) != KERN_SUCCESS){ ckb_err("Unable to get input service iterator: 0x%08x\n", res); return -2; } io_service_t service = IOIteratorNext(iter); if((res = IOServiceOpen(service, mach_task_self(), kIOHIDParamConnectType, &kb->event)) != KERN_SUCCESS){ IOObjectRelease(iter); ckb_err("Unable to open IO service: 0x%08x\n", res); kb->event = 0; return -3; } IOObjectRelease(iter); clearkeys(kb); return 0; }
int loadrgb_mouse(usbdevice* kb, lighting* light, int mode){ uchar data_pkt[MSG_SIZE] = { 0x0e, 0x13, 0x10, 1, 0 }; uchar in_pkt[MSG_SIZE] = { 0 }; // Zone 1 if(!usbrecv(kb, data_pkt, in_pkt)) return -1; if(memcmp(in_pkt, data_pkt, 4)){ ckb_err("Bad input header\n"); return -2; } // Copy data light->r[LED_MOUSE] = in_pkt[4]; light->g[LED_MOUSE] = in_pkt[5]; light->b[LED_MOUSE] = in_pkt[6]; // Zone 2 data_pkt[2]++; if(!usbrecv(kb, data_pkt, in_pkt)) return -1; if(memcmp(in_pkt, data_pkt, 4)){ ckb_err("Bad input header\n"); return -2; } // Copy data light->r[LED_MOUSE + 1] = in_pkt[4]; light->g[LED_MOUSE + 1] = in_pkt[5]; light->b[LED_MOUSE + 1] = in_pkt[6]; // TODO: zone 4 for Sabre? return 0; }
// Opens HID service. Returns kIOReturnSuccess on success. static int open_iohid(io_connect_t* connection){ io_iterator_t iter; io_service_t service; // Open master port (if not done yet) static mach_port_t master = 0; kern_return_t res; if(!master && (res = IOMasterPort(bootstrap_port, &master)) != kIOReturnSuccess){ master = 0; ckb_err("Unable to open master port: 0x%08x\n", res); goto failure; } // Open the HID service if((res = IOServiceGetMatchingServices(master, IOServiceMatching(kIOHIDSystemClass), &iter)) != kIOReturnSuccess) goto failure; service = IOIteratorNext(iter); if(!service){ res = kIOReturnNotOpen; goto failure_release_iter; } if((res = IOServiceOpen(service, mach_task_self(), kIOHIDParamConnectType, connection)) != kIOReturnSuccess){ *connection = 0; goto failure_release_iter; } // Finished; release objects and return success IOObjectRelease(service); failure_release_iter: IOObjectRelease(iter); failure: return res; }
int loadrgb_mouse(usbdevice* kb, lighting* light, int mode){ (void)mode; uchar data_pkt[MSG_SIZE] = { 0x0e, 0x13, 0x10, 1, 0 }; uchar in_pkt[MSG_SIZE] = { 0 }; // Load each RGB zone int zonecount = IS_SCIMITAR(kb) ? 4 : IS_SABRE(kb) ? 3 : 2; for(int i = 0; i < zonecount; i++){ if(!usbrecv(kb, data_pkt, in_pkt)) return -1; if(memcmp(in_pkt, data_pkt, 4)){ ckb_err("Bad input header\n"); return -2; } // Copy data int led = LED_MOUSE + i; if(led >= LED_DPI) led++; // Skip DPI light light->r[led] = in_pkt[4]; light->g[led] = in_pkt[5]; light->b[led] = in_pkt[6]; // Set packet for next zone data_pkt[2]++; } return 0; }
int usbadd(struct udev_device* dev, short vendor, short product){ const char* path = udev_device_get_devnode(dev); const char* syspath = udev_device_get_syspath(dev); if(!path || !syspath || path[0] == 0 || syspath[0] == 0){ ckb_err("Failed to get device path\n"); return -1; } // Find a free USB slot for(int index = 1; index < DEV_MAX; index++){ usbdevice* kb = keyboard + index; if(pthread_mutex_trylock(dmutex(kb))){ // If the mutex is locked then the device is obviously in use, so keep going if(!strcmp(syspath, kbsyspath[index])){ // Make sure this existing keyboard doesn't have the same syspath (this shouldn't happen) return 0; } continue; } if(!IS_CONNECTED(kb)){ // Open the sysfs device kb->handle = open(path, O_RDWR); if(kb->handle <= 0){ ckb_err("Failed to open USB device: %s\n", strerror(errno)); kb->handle = 0; pthread_mutex_unlock(dmutex(kb)); return -1; } else { // Set up device kb->udev = dev; kb->vendor = vendor; kb->product = product; strncpy(kbsyspath[index], syspath, FILENAME_MAX); // Mutex remains locked setupusb(kb); return 0; } } pthread_mutex_unlock(dmutex(kb)); } ckb_err("No free devices\n"); return -1; }
static void iterate_devices(void* context, io_iterator_t iterator){ io_service_t device; while((device = IOIteratorNext(iterator)) != 0){ // Get the plugin interface for the device IOCFPlugInInterface** plugin; SInt32 score; kern_return_t err = IOCreatePlugInInterfaceForService(device, kIOHIDDeviceTypeID, kIOCFPlugInInterfaceID, &plugin, &score); if(err != kIOReturnSuccess){ ckb_err("Failed to create device plugin: %x\n", err); continue; } // Get the device interface hid_dev_t handle; err = (*plugin)->QueryInterface(plugin, CFUUIDGetUUIDBytes(kIOHIDDeviceDeviceInterfaceID), (LPVOID*)&handle); if(err != kIOReturnSuccess){ ckb_err("QueryInterface failed: %x\n", err); continue; } // Plugin is no longer needed IODestroyPlugInInterface(plugin); // Seize the device handle euid_guard_start; err = (*handle)->open(handle, kIOHIDOptionsTypeSeizeDevice); euid_guard_stop; if(err != kIOReturnSuccess){ ckb_err("Failed to seize device: %x\n", err); continue; } // Connect it io_object_t* rm_notify = 0; usbdevice* kb = usbadd(handle, &rm_notify); if(kb) // If successful, register for removal notification IOServiceAddInterestNotification(notify, device, kIOGeneralInterest, remove_device, kb, rm_notify); else { // Otherwise, release it now (*handle)->close(handle, kIOHIDOptionsTypeNone); remove_device(0, device, kIOMessageServiceIsTerminated, 0); } } }
// 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_inputmain(void* context){ usbdevice* kb = context; int index = INDEX_OF(kb, keyboard); // Schedule async events for the device on this thread CFRunLoopRef runloop = CFRunLoopGetCurrent(); int count = (IS_RGB(kb->vendor, kb->product)) ? 4 : 3; for(int i = 0; i < count; i++){ CFTypeRef eventsource; kern_return_t res = (*kb->handles[i])->getAsyncEventSource(kb->handles[i], &eventsource); if(res != kIOReturnSuccess){ ckb_err("Failed to start input thread for %s%d: %x\n", devpath, index, res); return 0; } if(CFGetTypeID(eventsource) == CFRunLoopSourceGetTypeID()) CFRunLoopAddSource(runloop, (CFRunLoopSourceRef)eventsource, kCFRunLoopDefaultMode); else if(CFGetTypeID(eventsource) == CFRunLoopTimerGetTypeID()) CFRunLoopAddTimer(runloop, (CFRunLoopTimerRef)eventsource, kCFRunLoopDefaultMode); } ckb_info("Starting input thread for %s%d\n", devpath, index); // Start getting reports uint8_t* urbinput[] = { malloc(8), malloc(32), malloc(MSG_SIZE) }; (*kb->handles[0])->setInputReportCallback(kb->handles[0], urbinput[0], 8, intreport, kb, 0); if(IS_RGB(kb->vendor, kb->product)){ (*kb->handles[1])->setInputReportCallback(kb->handles[1], urbinput[1], 21, intreport, kb, 0); (*kb->handles[2])->setInputReportCallback(kb->handles[2], urbinput[2], MSG_SIZE, intreport, kb, 0); } else { (*kb->handles[1])->setInputReportCallback(kb->handles[1], urbinput[1], 4, intreport, kb, 0); (*kb->handles[2])->setInputReportCallback(kb->handles[2], urbinput[2], 15, intreport, kb, 0); } // Run the run loop for up to 2ms at a time, then check for key repeats while(1){ CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.002, false); pthread_mutex_lock(imutex(kb)); if(!IS_CONNECTED(kb)){ // Make sure the device hasn't disconnected pthread_mutex_unlock(imutex(kb)); break; } keyretrigger(kb); pthread_mutex_unlock(imutex(kb)); } // Clean up ckb_info("Stopping input thread for %s%d\n", devpath, index); free(urbinput[0]); free(urbinput[1]); free(urbinput[2]); return 0; }
int os_inputopen(usbdevice* kb){ // The IO service isn't always ready at startup, so if it's not, wait until it is IOReturn res; while((res = open_iohid(&kb->event)) != kIOReturnSuccess){ if(res != kIOReturnNotOpen){ // If this is a more serious error, at least print a warning ckb_err("Unable to open HID system: 0x%08x\n", res); sleep(1); continue; } usleep(10000); } clearkeys(kb); return 0; }
int usb_tryreset(usbdevice* kb){ if(reset_stop) return -1; ckb_info("Attempting reset...\n"); while(1){ int res = resetusb(kb); if(!res){ ckb_info("Reset success\n"); return 0; } if(res == -2 || reset_stop) break; } ckb_err("Reset failed. Disconnecting.\n"); return -1; }
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); const char* serial = udev_device_get_sysattr_value(dev, "serial"); if(serial) strncpy(kb->serial, serial, SERIAL_LEN); // 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; ckb_info("Connecting %s (S/N: %s)\n", kb->name, kb->serial); // Claim the USB interfaces if(usbclaim(kb, HAS_FEATURES(kb, FEAT_RGB))){ ckb_err("Failed to claim interface: %s\n", strerror(errno)); return -1; } return 0; }
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 setupusb(usbdevice* kb){ pthread_mutex_lock(imutex(kb)); if(pthread_create(&kb->thread, 0, _setupusb, kb)) ckb_err("Failed to create USB thread\n"); }
void os_sendindicators(usbdevice* kb){ struct usbdevfs_ctrltransfer transfer = { 0x21, 0x09, 0x0200, 0x00, 1, 500, &kb->ileds }; int res = ioctl(kb->handle - 1, USBDEVFS_CONTROL, &transfer); if(res <= 0) ckb_err("%s\n", res ? strerror(errno) : "No data written"); }
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; }