Merge branch 'fw2' into bugfix

This commit is contained in:
ccMSC
2016-04-05 01:07:20 -06:00
11 changed files with 687 additions and 268 deletions
+8 -8
View File
@@ -14,18 +14,18 @@ Hash: SHA256
Corsair STRAFE 1.33 http://www3.corsair.com/software/HID/STRAFE.zip 0.2.2 STRAFE_app133_RC.bin a6ea43354810afab6eea1ef41537178c545acbedbd4ebe48dd9d18c61b45c9c1
Corsair STRAFERGB 1.33 http://www3.corsair.com/software/HID/STRAFE_RGB.zip 0.2.2 STRAFE_RGB_app133_RC.bin 8526070e4f118137c0bea30805dbdbce5b26beecc3237d65f7d3c10ad7494f1d
Corsair M65RGB 1.19 http://www3.corsair.com/software/HID/M65RGB.zip 0.2.0 M65RGB_app119.bin be214ab1f0cc1fc7f785c926c117ef270597f80d1fad9133a730022834267f4f
Corsair SCIMITARRGB 1.06 http://www3.corsair.com/software/HID/Scimitar.zip 0.2.2 Scimitar_V106.bin fdc9bc14f6320ff7be47303cae543f1fbe406acb6590333084817da54cf3eb60
Corsair SCIMITARRGB 2.01 http://www3.corsair.com/software/HID/Scimitar.zip 0.2.5 Scimitar_App_V201.bin f0e13a5cabdc2bfae00b51d50a369376d0cc19d8e201dcdc4d4ef1d5ac284c53
!END FW ENTRIES
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v2
iQEcBAEBCAAGBQJWVtkQAAoJEBXtkzSNyNMJHNEIAJieZ1pwU2WJu6GD14lwZEXI
/carx6KogdtGbFvs5aE0BnYwq6iL/Q7GvY7KpVmYorJHl9PnwcDPoLe4H3IVYTbi
nd0dLaBSGsGFhyR2Uz4aGknEhCQH9qybqrRk6znC0nvksiH/OcvFysqG/sPr9AWN
679064VyXwMLUlqCRJtmfIMsb+BH6O/VNsvliIfC0VKsiIyuqaQqZptgpll9lkHL
LDBLD514Vkg8z6c/dvUOL8Ey3v4qSGvvfBS36y/qh49Q5EvI7v0xVvNOxudLZ+7k
or1PTCFFi03qzeI77XsF7mAyKmh1S2BjkEfAmxPfpl4s/WtkO1opPAtQdprxeDw=
=D4qQ
iQEcBAEBCAAGBQJWzUYDAAoJEBXtkzSNyNMJBkUH/jY6AgU1b19kEs5ub572cF8H
t/IsMdbKKkVj9WYvWXcNW3c72rEN3IzO3Aa5+k/fvgGzkuz1/zHxyWJ6u0q2uKkz
B2X7afHgTOZ/vFiuIcJZb2dzA/yTfWk2doUl6trN+nE3u2r2v8xmgiQGH/NaRD27
7DS2/Vn8zm6epdUxO/vwyAO3x/oeNpCseWRRMZp/LbgZF6MCcWQ2cVNqcH4Z2z7s
nQ4987+v6EX1WHsX2fq4km77wCe35dpCdKSpO2d3xxMkK9FKrftr9f5VhkyY4Yk3
eJlHGZ2PAHlUkRs9WVCqoUQjC3HbJbTY6pqyX4VBv87+Wem/SnqASd41lej/yxs=
=0giP
-----END PGP SIGNATURE-----
+1 -1
View File
@@ -1 +1 @@
beta-v0.2.4+b03
beta-v0.2.4+b04
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.ckb.daemon</string>
<key>ProgramArguments</key>
<array>
<string>/Applications/ckb.app/Contents/Resources/ckb-daemon</string>
</array>
<key>KeepAlive</key>
<true/>
<key>ProcessType</key>
<string>Interactive</string>
<key>LegacyTimers</key>
<true/>
<key>StandardOutPath</key>
<string>/Applications/ckb.app/Contents/ckb-daemon.log</string>
</dict>
</plist>
+22 -12
View File
@@ -26,23 +26,33 @@ typedef unsigned short ushort;
// Gets the index of an object within an array
#define INDEX_OF(entry, array) (int)(entry - array)
// Compile with -DCKB_OUTPUT_TO_STDERR if you want to separate error messages from normal status updates.
// (probably not useful because the errors don't mean much without context)
#ifdef CKB_OUTPUT_TO_STDERR
#define ckb_s_out stdout
#define ckb_s_err stderr
#else
#define ckb_s_out stdout
#define ckb_s_err stdout
#endif
// Better __FILE__ macro
#define __FILE_NOPATH__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)
// Output helpers
// Use ckb_* to output info or ckb_*_fn to override the file/line numbers (useful when describing where a function was invoked from)
#define ckb_fatal_nofile(fmt, args...) fprintf(stderr, "[F] " fmt, ## args)
#define ckb_fatal_fn(fmt, file, line, args...) fprintf(stderr, "[F] %s (via %s:%d): " fmt, __func__, file, line, ## args)
#define ckb_fatal(fmt, args...) fprintf(stderr, "[F] %s (%s:%d): " fmt, __func__, __FILE_NOPATH__, __LINE__, ## args)
#define ckb_err_nofile(fmt, args...) fprintf(stderr, "[E] " fmt, ## args)
#define ckb_err_fn(fmt, file, line, args...) fprintf(stderr, "[E] %s (via %s:%d): " fmt, __func__, file, line, ## args)
#define ckb_err(fmt, args...) fprintf(stderr, "[E] %s (%s:%d): " fmt, __func__, __FILE_NOPATH__, __LINE__, ## args)
#define ckb_warn_nofile(fmt, args...) printf("[W] " fmt, ## args)
#define ckb_warn_fn(fmt, file, line, args...) printf("[W] %s (via %s:%d): " fmt, __func__, file, line, ## args)
#define ckb_warn(fmt, args...) printf("[W] %s (%s:%d): " fmt, __func__, __FILE_NOPATH__, __LINE__, ## args)
#define ckb_info_nofile(fmt, args...) printf("[I] " fmt, ## args)
#define ckb_info_fn(fmt, file, line, args...) printf("[I] " fmt, ## args)
#define ckb_info(fmt, args...) printf("[I] " fmt, ## args)
#define ckb_fatal_nofile(fmt, args...) fprintf(ckb_s_err, "[F] " fmt, ## args)
#define ckb_fatal_fn(fmt, file, line, args...) fprintf(ckb_s_err, "[F] %s (via %s:%d): " fmt, __func__, file, line, ## args)
#define ckb_fatal(fmt, args...) fprintf(ckb_s_err, "[F] %s (%s:%d): " fmt, __func__, __FILE_NOPATH__, __LINE__, ## args)
#define ckb_err_nofile(fmt, args...) fprintf(ckb_s_err, "[E] " fmt, ## args)
#define ckb_err_fn(fmt, file, line, args...) fprintf(ckb_s_err, "[E] %s (via %s:%d): " fmt, __func__, file, line, ## args)
#define ckb_err(fmt, args...) fprintf(ckb_s_err, "[E] %s (%s:%d): " fmt, __func__, __FILE_NOPATH__, __LINE__, ## args)
#define ckb_warn_nofile(fmt, args...) fprintf(ckb_s_out, "[W] " fmt, ## args)
#define ckb_warn_fn(fmt, file, line, args...) fprintf(ckb_s_out, "[W] %s (via %s:%d): " fmt, __func__, file, line, ## args)
#define ckb_warn(fmt, args...) fprintf(ckb_s_out, "[W] %s (%s:%d): " fmt, __func__, __FILE_NOPATH__, __LINE__, ## args)
#define ckb_info_nofile(fmt, args...) fprintf(ckb_s_out, "[I] " fmt, ## args)
#define ckb_info_fn(fmt, file, line, args...) fprintf(ckb_s_out, "[I] " fmt, ## args)
#define ckb_info(fmt, args...) fprintf(ckb_s_out, "[I] " fmt, ## args)
// Timespec utilities
void timespec_add(struct timespec* timespec, long nanoseconds);
+6 -1
View File
@@ -395,7 +395,12 @@ void corsair_kbcopy(unsigned char* kbinput, const unsigned char* urbinput){
memcpy(kbinput, urbinput, N_KEYBYTES_HW);
}
void corsair_mousecopy(unsigned char* kbinput, const unsigned char* urbinput){
void corsair_mousecopy(unsigned char* kbinput, int endpoint, const unsigned char* urbinput){
if(endpoint == 2){
if(urbinput[0] != 3)
return;
urbinput++;
}
for(int bit = BUTTON_HID_COUNT; bit < N_BUTTONS_HW; bit++){
int byte = bit / 8;
uchar test = 1 << (bit % 8);
+1 -1
View File
@@ -68,6 +68,6 @@ void hid_mouse_translate(unsigned char* kbinput, short* xaxis, short* yaxis, int
// Copies input from Corsair reports
void corsair_kbcopy(unsigned char* kbinput, const unsigned char* urbinput);
void corsair_mousecopy(unsigned char* kbinput, const unsigned char* urbinput);
void corsair_mousecopy(unsigned char* kbinput, int endpoint, const unsigned char* urbinput);
#endif // KEYMAP_H
+4 -1
View File
@@ -66,8 +66,11 @@ void localecase(char* dst, size_t length, const char* src){
}
int main(int argc, char** argv){
printf(" ckb: Corsair RGB driver %s\n", CKB_VERSION_STR);
// Set output pipes to buffer on newlines, if they weren't set that way already
setlinebuf(stdout);
setlinebuf(stderr);
printf(" ckb: Corsair RGB driver %s\n", CKB_VERSION_STR);
// If --help occurs anywhere in the command-line, don't launch the program but instead print usage
for(int i = 1; i < argc; i++){
if(!strcmp(argv[i], "--help")){
+4 -1
View File
@@ -50,9 +50,12 @@
#include <IOKit/hid/IOHIDLib.h>
#include <IOKit/hidsystem/IOHIDLib.h>
#include <IOKit/hidsystem/ev_keymap.h>
#include <IOKit/usb/IOUSBLib.h>
#include <IOKit/usb/USB.h>
typedef IOHIDDeviceDeviceInterface** hid_dev_t;
typedef IOHIDDeviceDeviceInterface** hid_dev_t;
typedef IOUSBDeviceInterface182** usb_dev_t;
typedef IOUSBInterfaceInterface183** usb_iface_t;
// The OSX process needs to change its EUID to post events, so thread safety must be ensured
// On Linux the EUID is always root
+21 -8
View File
@@ -173,6 +173,7 @@ extern const union devcmd vtable_mouse;
#define KB_NAME_LEN 34
#define SERIAL_LEN 34
#define MSG_SIZE 64
#define IFACE_MAX 4
typedef struct {
// Function table (see command.h)
const union devcmd* vtable;
@@ -180,25 +181,37 @@ typedef struct {
// Note that FDs have 1 added to them, because these are initialized to zero when the program starts but zero is technically a valid FD
// So the actual value is (fd - 1)
#ifdef OS_LINUX
// USB device
struct udev_device* udev;
int handle;
// uinput handles
int uinput_kb, uinput_mouse;
#else
// USB identifier
long location_id;
// Device handles
usb_dev_t handle;
usb_iface_t ifusb[IFACE_MAX];
hid_dev_t ifhid[IFACE_MAX];
io_object_t rm_notify[IFACE_MAX * 2 + 1]; // one for each: handle, ifusb, ifhid
int epcount_hid, epcount_usb;
// Result code from the last USB transfer
kern_return_t lastresult;
// Input handle
io_connect_t event;
// Key-repeat info
CFRunLoopRef input_loop;
CFRunLoopTimerRef krtimer;
struct timespec keyrepeat;
long location_id;
hid_dev_t handle;
hid_dev_t handles[4];
io_object_t rm_notify[4];
kern_return_t lastresult;
io_connect_t event;
IOOptionBits modifiers;
short lastkeypress;
// Modifier/mouse button state
pthread_t indicthread;
IOOptionBits modifiers;
uchar mousestate;
char scroll_rate;
pthread_t indicthread;
#endif
// Number of endpoints on the USB device
int epcount;
// Thread used for USB/devnode communication. To close: lock mutexes, set handle to zero, unlock, then wait for thread to stop
pthread_t thread;
// Thread for device input. Will close on its own when the device is terminated.
+36 -27
View File
@@ -13,13 +13,13 @@ int os_usbsend(usbdevice* kb, const uchar* out_msg, int is_recv, const char* fil
if(kb->fwversion >= 0x120 && !is_recv){
struct usbdevfs_bulktransfer transfer;
memset(&transfer, 0, sizeof(transfer));
transfer.ep = (kb->fwversion >= 0x130) ? 4 : 3;
transfer.ep = (kb->fwversion >= 0x130 && !IS_MOUSE_DEV(kb)) ? 4 : 3;
transfer.len = MSG_SIZE;
transfer.timeout = 5000;
transfer.data = (void*)out_msg;
res = ioctl(kb->handle - 1, USBDEVFS_BULK, &transfer);
} else {
struct usbdevfs_ctrltransfer transfer = { 0x21, 0x09, 0x0300, 0x03, MSG_SIZE, 5000, (void*)out_msg };
struct usbdevfs_ctrltransfer transfer = { 0x21, 0x09, 0x0200, kb->epcount - 1, MSG_SIZE, 5000, (void*)out_msg };
res = ioctl(kb->handle - 1, USBDEVFS_CONTROL, &transfer);
}
if(res <= 0){
@@ -51,11 +51,11 @@ int os_usbrecv(usbdevice* kb, uchar* in_msg, const char* file, int line){
transfer.data = in_msg;
res = ioctl(kb->handle - 1, USBDEVFS_BULK, &transfer);
} else {*/
struct usbdevfs_ctrltransfer transfer = { 0xa1, 0x01, 0x0300, 0x03, MSG_SIZE, 5000, in_msg };
struct usbdevfs_ctrltransfer transfer = { 0xa1, 0x01, 0x0300, kb->epcount - 1, MSG_SIZE, 5000, in_msg };
res = ioctl(kb->handle - 1, USBDEVFS_CONTROL, &transfer);
//}
if(res <= 0){
ckb_err_fn("%s\n", file, line, res ? strerror(errno) : "No data written");
ckb_err_fn("%s\n", file, line, res ? strerror(errno) : "No data read");
if(res == -1 && errno == ETIMEDOUT)
return -1;
else
@@ -91,8 +91,9 @@ void* os_inputmain(void* context){
int index = INDEX_OF(kb, keyboard);
ckb_info("Starting input thread for %s%d\n", devpath, index);
// Monitor input transfers on all endpoints
const int urbcount = 3;
// Monitor input transfers on all endpoints for non-RGB devices
// For RGB, monitor all but the last, as it's used for input/output
int urbcount = IS_RGB(vendor, product) ? (kb->epcount - 1) : kb->epcount;
struct usbdevfs_urb urbs[urbcount];
memset(urbs, 0, sizeof(urbs));
urbs[0].buffer_length = 8;
@@ -102,6 +103,8 @@ void* os_inputmain(void* context){
else
urbs[1].buffer_length = 21;
urbs[2].buffer_length = MSG_SIZE;
if(urbcount != 3)
urbs[urbcount - 1].buffer_length = MSG_SIZE;
} else {
urbs[1].buffer_length = 4;
urbs[2].buffer_length = 15;
@@ -133,13 +136,16 @@ void* os_inputmain(void* context){
// Process input (if any)
pthread_mutex_lock(imutex(kb));
if(IS_MOUSE(vendor, product)){
switch(urb->endpoint){
case 0x82:
// RGB mouse input
switch(urb->actual_length){
case 8:
case 10:
case 11:
// HID mouse input
hid_mouse_translate(kb->input.keys, &kb->input.rel_x, &kb->input.rel_y, -(urb->endpoint & 0xF), urb->actual_length, urb->buffer);
break;
case 0x83:
corsair_mousecopy(kb->input.keys, urb->buffer);
case MSG_SIZE:
// Corsair mouse input
corsair_mousecopy(kb->input.keys, urb->endpoint & 0xF, urb->buffer);
break;
}
} else if(IS_RGB(vendor, product)){
@@ -177,9 +183,9 @@ void* os_inputmain(void* context){
return 0;
}
int usbunclaim(usbdevice* kb, int resetting, int rgb){
int usbunclaim(usbdevice* kb, int resetting){
int handle = kb->handle - 1;
int count = (rgb) ? 4 : 3;
int count = kb->epcount;
for(int i = 0; i < count; i++)
ioctl(handle, USBDEVFS_RELEASEINTERFACE, &i);
// For RGB keyboards, the kernel driver should only be reconnected to interfaces 0 and 1 (HID), and only if we're not about to do a USB reset.
@@ -189,10 +195,8 @@ int usbunclaim(usbdevice* kb, int resetting, int rgb){
ioctl(handle, USBDEVFS_IOCTL, &ctl);
ctl.ifno = 1;
ioctl(handle, USBDEVFS_IOCTL, &ctl);
// Reconnect all handles for non-RGB keyboards
// Also reconnect iface #2 (HID) for non-RGB keyboards
if(!HAS_FEATURES(kb, FEAT_RGB)){
ctl.ifno = 0;
ioctl(handle, USBDEVFS_IOCTL, &ctl);
ctl.ifno = 2;
ioctl(handle, USBDEVFS_IOCTL, &ctl);
}
@@ -202,7 +206,7 @@ int usbunclaim(usbdevice* kb, int resetting, int rgb){
void os_closeusb(usbdevice* kb){
if(kb->handle){
usbunclaim(kb, 0, HAS_FEATURES(kb, FEAT_RGB));
usbunclaim(kb, 0);
close(kb->handle - 1);
}
if(kb->udev)
@@ -212,12 +216,8 @@ void os_closeusb(usbdevice* kb){
kbsyspath[INDEX_OF(kb, keyboard)][0] = 0;
}
int usbclaim(usbdevice* kb, int rgb){
// 0 is for BIOS mode/non-RGB key input
// 1 is for standard HID key input
// 2 is for Corsair input
// 3 is for the LED and board controller (not present on non-RGB models)
int count = (rgb) ? 4 : 3;
int usbclaim(usbdevice* kb){
int count = kb->epcount;
for(int i = 0; i < count; i++){
struct usbdevfs_ioctl ctl = { i, USBDEVFS_DISCONNECT, 0 };
if((ioctl(kb->handle - 1, USBDEVFS_IOCTL, &ctl) && errno != ENODATA)
@@ -236,9 +236,9 @@ int usbclaim(usbdevice* kb, int rgb){
}
int os_resetusb(usbdevice* kb, const char* file, int line){
TEST_RESET(usbunclaim(kb, 1, HAS_FEATURES(kb, FEAT_RGB)));
TEST_RESET(usbunclaim(kb, 1));
TEST_RESET(ioctl(kb->handle - 1, USBDEVFS_RESET));
TEST_RESET(usbclaim(kb, HAS_FEATURES(kb, FEAT_RGB)));
TEST_RESET(usbclaim(kb));
// Success!
return 0;
}
@@ -282,8 +282,17 @@ int os_setupusb(usbdevice* kb){
ckb_info("Connecting %s at %s%d\n", kb->name, devpath, index);
// Claim the USB interfaces
if(usbclaim(kb, HAS_FEATURES(kb, FEAT_RGB))){
ckb_err("Failed to claim interface: %s\n", strerror(errno));
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;
+564 -208
View File
@@ -6,16 +6,104 @@
#ifdef OS_MAC
#define INCOMPLETE (hid_dev_t)-1l
static CFRunLoopRef mainloop = 0;
static IONotificationPortRef notify = 0;
static long hidgetlong(hid_dev_t handle, CFStringRef key){
long raw = 0;
CFTypeRef cf;
if((*handle)->getProperty(handle, key, &cf) != kIOReturnSuccess)
return 0;
if(!cf || CFGetTypeID(cf) != CFNumberGetTypeID() || !CFNumberGetValue(cf, kCFNumberLongType, &raw))
return 0;
return raw;
}
static void hidgetstr(hid_dev_t handle, CFStringRef key, char* output, int out_len){
CFTypeRef cf;
if((*handle)->getProperty(handle, key, &cf) != kIOReturnSuccess){
output[0] = 0;
return;
}
if(!cf || CFGetTypeID(cf) != CFStringGetTypeID() || !CFStringGetCString(cf, output, out_len, kCFStringEncodingASCII))
output[0] = 0;
}
// profile.c
void u16dec(ushort* in, char* out, size_t* srclen, size_t* dstlen);
static void usbgetstr(usb_dev_t handle, uint8 string_index, char* output, int out_len){
// Make a temporary buffer so the request won't fail if too large
char buffer[256] = { 0 };
IOUSBDevRequest rq = { 0x80, 0x06, 0x0300 | string_index, 0x0409, sizeof(buffer) - 2, buffer, 0};
kern_return_t res = (*handle)->DeviceRequest(handle, &rq);
if(res != kIOReturnSuccess){
output[0] = 0;
return;
}
size_t inl = sizeof(buffer) / 2, outl = out_len;
u16dec((ushort*)buffer + 1, output, &inl, &outl);
output[out_len - 1] = 0;
}
#define INCOMPLETE (usb_dev_t)-1l
#define HAS_ALL_HANDLES(kb) ((kb)->epcount > 0 && (kb)->epcount_hid + (kb)->epcount_usb >= (kb)->epcount)
// Hacky way of trying something over and over again until it works. 100ms intervals, max 1s
#define wait_loop(error, operation) do { \
int trial = 0; \
while(((error) = (operation)) != kIOReturnSuccess){ \
if(++trial == 10) \
break; \
usleep(100000); \
} } while(0)
#define IS_TEMP_FAILURE(res) ((res) == kIOUSBTransactionTimeout || (res) == kIOUSBTransactionReturned || (res) == kIOUSBPipeStalled)
#define IS_DISCONNECT_FAILURE(res) ((res) == kIOReturnBadArgument || (res) == kIOReturnNoDevice || (res) == kIOReturnNotOpen || (res) == kIOReturnNotAttached || (res) == kIOReturnExclusiveAccess)
// When reading/writing USB handles we have to ensure we select the correct pipe, else it will fail
static int get_pipe_index(usb_iface_t handle, int desired_direction){
uchar count;
if((*handle)->GetNumEndpoints(handle, &count) != kIOReturnSuccess)
return 1;
for(int i = 0; i < count; i++){
uchar direction, number, transfertype, interval;
ushort maxpacketsize;
if((*handle)->GetPipeProperties(handle, i + 1, &direction, &number, &transfertype, &maxpacketsize, &interval) == kIOReturnSuccess){
if(direction == desired_direction)
return i + 1;
}
}
// Not found - default to 1
return 1;
}
int os_usbsend(usbdevice* kb, const uchar* out_msg, int is_recv, const char* file, int line){
// Firmware versions above 1.20 use Output instead of Feature reports for improved performance
// It doesn't work well when receiving data, however
IOHIDReportType type = (kb->fwversion >= 0x120 && !is_recv ? kIOHIDReportTypeOutput : kIOHIDReportTypeFeature);
kern_return_t res = (*kb->handle)->setReport(kb->handle, type, 0, out_msg, MSG_SIZE, 5000, 0, 0, 0);
kern_return_t res = kIOReturnSuccess;
if(kb->fwversion < 0x120 || is_recv){
int ep = kb->epcount;
// For old devices, or for receiving data, use control transfers
IOUSBDevRequestTO rq = { 0x21, 0x09, 0x0200, ep - 1, MSG_SIZE, (void*)out_msg, 0, 5000, 5000 };
res = (*kb->handle)->DeviceRequestTO(kb->handle, &rq);
if(res == kIOReturnNotOpen){
// Device handle not open - try to send directly to the endpoint instead
usb_iface_t h_usb = kb->ifusb[ep - 1];
hid_dev_t h_hid = kb->ifhid[ep - 1];
if(h_usb)
res = (*h_usb)->ControlRequestTO(h_usb, 0, &rq);
else if(h_hid)
res = (*h_hid)->setReport(h_hid, kIOHIDReportTypeFeature, 0, out_msg, MSG_SIZE, 5000, 0, 0, 0);
}
} else {
// For newer devices, use interrupt transfers
int ep = (kb->fwversion >= 0x130 && !IS_MOUSE_DEV(kb)) ? 4 : 3;
usb_iface_t h_usb = kb->ifusb[ep - 1];
hid_dev_t h_hid = kb->ifhid[ep - 1];
if(h_usb)
res = (*h_usb)->WritePipe(h_usb, get_pipe_index(h_usb, kUSBOut), (void*)out_msg, MSG_SIZE);
else if(h_hid)
res = (*h_hid)->setReport(h_hid, kIOHIDReportTypeOutput, 0, out_msg, MSG_SIZE, 5000, 0, 0, 0);
}
kb->lastresult = res;
if(res != kIOReturnSuccess){
ckb_err_fn("Got return value 0x%x\n", file, line, res);
@@ -28,8 +116,19 @@ int os_usbsend(usbdevice* kb, const uchar* out_msg, int is_recv, const char* fil
}
int os_usbrecv(usbdevice* kb, uchar* in_msg, const char* file, int line){
CFIndex length = MSG_SIZE;
kern_return_t res = (*kb->handle)->getReport(kb->handle, kIOHIDReportTypeFeature, 0, in_msg, &length, 5000, 0, 0, 0);
int ep = kb->epcount;
IOUSBDevRequestTO rq = { 0xa1, 0x01, 0x0200, ep - 1, MSG_SIZE, in_msg, 0, 5000, 5000 };
kern_return_t res = (*kb->handle)->DeviceRequestTO(kb->handle, &rq);
CFIndex length = rq.wLenDone;
if(res == kIOReturnNotOpen){
// Device handle not open - try to send directly to the endpoint instead
usb_iface_t h_usb = kb->ifusb[ep - 1];
hid_dev_t h_hid = kb->ifhid[ep - 1];
if(h_usb)
res = (*h_usb)->ControlRequestTO(h_usb, 0, &rq);
else if(h_hid)
res = (*h_hid)->getReport(h_hid, kIOHIDReportTypeFeature, 0, in_msg, &length, 5000, 0, 0, 0);
}
kb->lastresult = res;
if(res != kIOReturnSuccess){
ckb_err_fn("Got return value 0x%x\n", file, line, res);
@@ -39,38 +138,53 @@ int os_usbrecv(usbdevice* kb, uchar* in_msg, const char* file, int line){
return 0;
}
if(length != MSG_SIZE)
ckb_err_fn("Read %d bytes (expected %d)\n", file, line, (int)length, MSG_SIZE);
ckb_err_fn("Read %ld bytes (expected %d)\n", file, line, length, MSG_SIZE);
return length;
}
int _nk95cmd(usbdevice* kb, uchar bRequest, ushort wValue, const char* file, int line){
// TODO: stub
IOUSBDevRequestTO rq = { 0x40, bRequest, wValue, 0, 0, 0, 0, 5000, 5000 };
kern_return_t res = (*kb->handle)->DeviceRequestTO(kb->handle, &rq);
if(res != kIOReturnSuccess){
ckb_err_fn("Got return value 0x%x\n", file, line, res);
return 1;
}
return 0;
}
void os_sendindicators(usbdevice* kb){
uchar ileds = kb->ileds;
// Get a list of LED elements from handle 0
long ledpage = kHIDPage_LEDs;
const void* keys[] = { CFSTR(kIOHIDElementUsagePageKey) };
const void* values[] = { CFNumberCreate(kCFAllocatorDefault, kCFNumberLongType, &ledpage) };
CFDictionaryRef matching = CFDictionaryCreate(kCFAllocatorDefault, keys, values, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFRelease(values[0]);
CFArrayRef leds;
kern_return_t res = (*kb->handles[0])->copyMatchingElements(kb->handles[0], matching, &leds, 0);
CFRelease(matching);
if(res != kIOReturnSuccess)
return;
// Iterate through them and update the LEDs which have changed
CFIndex count = CFArrayGetCount(leds);
for(CFIndex i = 0; i < count; i++){
IOHIDElementRef led = (void*)CFArrayGetValueAtIndex(leds, i);
uint32_t usage = IOHIDElementGetUsage(led);
IOHIDValueRef value = IOHIDValueCreateWithIntegerValue(kCFAllocatorDefault, led, 0, !!(ileds & (1 << (usage - 1))));
(*kb->handles[0])->setValue(kb->handles[0], led, value, 5000, 0, 0, 0);
CFRelease(value);
IOUSBDevRequestTO rq = { 0x21, 0x09, 0x0200, 0x00, 1, &kb->ileds, 0, 500, 500 };
kern_return_t res = (*kb->handle)->DeviceRequestTO(kb->handle, &rq);
if(res == kIOReturnNotOpen){
// Handle not open - try to go through the HID system instead
hid_dev_t handle = kb->ifhid[0];
if(handle){
uchar ileds = kb->ileds;
// Get a list of LED elements from handle 0
long ledpage = kHIDPage_LEDs;
const void* keys[] = { CFSTR(kIOHIDElementUsagePageKey) };
const void* values[] = { CFNumberCreate(kCFAllocatorDefault, kCFNumberLongType, &ledpage) };
CFDictionaryRef matching = CFDictionaryCreate(kCFAllocatorDefault, keys, values, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFRelease(values[0]);
CFArrayRef leds;
kern_return_t res = (*handle)->copyMatchingElements(handle, matching, &leds, 0);
CFRelease(matching);
if(res != kIOReturnSuccess)
return;
// Iterate through them and update the LEDs which have changed
CFIndex count = CFArrayGetCount(leds);
for(CFIndex i = 0; i < count; i++){
IOHIDElementRef led = (void*)CFArrayGetValueAtIndex(leds, i);
uint32_t usage = IOHIDElementGetUsage(led);
IOHIDValueRef value = IOHIDValueCreateWithIntegerValue(kCFAllocatorDefault, led, 0, !!(ileds & (1 << (usage - 1))));
(*handle)->setValue(handle, led, value, 5000, 0, 0, 0);
CFRelease(value);
}
CFRelease(leds);
}
}
CFRelease(leds);
if(res != kIOReturnSuccess)
ckb_err("Got return value 0x%x\n", res);
}
int os_resetusb(usbdevice* kb, const char* file, int line){
@@ -78,12 +192,8 @@ int os_resetusb(usbdevice* kb, const char* file, int line){
if(IS_DISCONNECT_FAILURE(res))
// Don't try if the keyboard was disconnected
return -2;
// Reset all handles
int count = IS_RGB(kb->vendor, kb->product) ? 4 : 3;
for(int i = 0; i < count; i++){
if((*kb->handles[i])->setProperty(kb->handles[i], CFSTR(kIOHIDResetKey), kCFBooleanTrue) != kIOReturnSuccess)
return -1;
}
// Otherwise, just wait and try again
usleep(100000);
return 0;
}
@@ -91,10 +201,17 @@ static void intreport(void* context, IOReturn result, void* sender, IOHIDReportT
usbdevice* kb = context;
pthread_mutex_lock(imutex(kb));
if(IS_MOUSE(kb->vendor, kb->product)){
if(length == 10)
switch(length){
case 7:
case 8:
case 10:
case 11:
hid_mouse_translate(kb->input.keys, &kb->input.rel_x, &kb->input.rel_y, -2, length, data);
else if(length == MSG_SIZE)
corsair_mousecopy(kb->input.keys, data);
break;
case MSG_SIZE:
corsair_mousecopy(kb->input.keys, kb->ifhid[3] ? 3 : 2, data);
break;
}
} else if(HAS_FEATURES(kb, FEAT_RGB)){
switch(length){
case 8:
@@ -132,18 +249,48 @@ static void intreport(void* context, IOReturn result, void* sender, IOHIDReportT
pthread_mutex_unlock(imutex(kb));
}
typedef struct {
usbdevice* kb;
uchar* buffer;
int index;
int pipe;
int maxsize;
} urbctx;
static void pipecomplete(void* refcon, IOReturn result, void* arg0){
if(result != kIOReturnSuccess)
return;
intptr_t length = (intptr_t)arg0;
urbctx* ctx = refcon;
// Wrap the URB to the interrupt report handler
usbdevice* kb = ctx->kb;
uchar* buffer = ctx->buffer;
intreport(kb, result, 0, 0, 0, buffer, length);
// Re-submit the URB to read the next packet
usb_iface_t handle = kb->ifusb[ctx->index];
(*handle)->ReadPipeAsync(handle, ctx->pipe, buffer, ctx->maxsize, pipecomplete, ctx);
}
// input_mac.c
extern void keyretrigger(CFRunLoopTimerRef timer, void* info);
void* os_inputmain(void* context){
usbdevice* kb = context;
int index = INDEX_OF(kb, keyboard);
// Monitor input transfers on all endpoints for non-RGB devices
// For RGB, monitor all but the last, as it's used for input/output
int count = IS_RGB_DEV(kb) ? (kb->epcount - 1) : kb->epcount;
// Schedule async events for the device on this thread
CFRunLoopRef runloop = kb->input_loop = 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);
kern_return_t res;
if(kb->ifusb[i])
res = (*kb->ifusb[i])->CreateInterfaceAsyncEventSource(kb->ifusb[i], (CFRunLoopSourceRef*)&eventsource);
else if(kb->ifhid[i])
res = (*kb->ifhid[i])->getAsyncEventSource(kb->ifhid[i], &eventsource);
else
continue;
if(res != kIOReturnSuccess){
ckb_err("Failed to start input thread for %s%d: %x\n", devpath, index, res);
return 0;
@@ -156,14 +303,33 @@ void* os_inputmain(void* context){
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);
urbctx input[IFACE_MAX];
memset(input, 0, sizeof(input));
for(int i = 0; i < count; i++){
if(kb->ifusb[i]){
ckb_info("Reading ckb%d.ifusb[%d]\n", index, i);
usb_iface_t handle = kb->ifusb[i];
int pipe = get_pipe_index(handle, kUSBIn);
uchar direction, number, transfertype, interval;
ushort maxpacketsize;
(*handle)->GetPipeProperties(handle, pipe, &direction, &number, &transfertype, &maxpacketsize, &interval);
if(direction != kUSBIn && direction != kUSBAnyDirn)
continue;
uchar* buffer = malloc(maxpacketsize);
input[i].kb = kb;
input[i].buffer = buffer;
input[i].index = i;
input[i].pipe = pipe;
input[i].maxsize = maxpacketsize;
(*handle)->ReadPipeAsync(handle, 1, buffer, maxpacketsize, pipecomplete, input + i);
} else if(kb->ifhid[i]){
ckb_info("Reading ckb%d.ifhid[%d]\n", index, i);
hid_dev_t handle = kb->ifhid[i];
long maxsize = hidgetlong(handle, CFSTR(kIOHIDMaxInputReportSizeKey));
uchar* buffer = malloc(maxsize);
input[i].buffer = buffer;
(*handle)->setInputReportCallback(handle, buffer, maxsize, intreport, kb, 0);
}
}
// Start a timer for key repeat broadcasts
@@ -190,154 +356,54 @@ void* os_inputmain(void* context){
// Clean up
ckb_info("Stopping input thread for %s%d\n", devpath, index);
free(urbinput[0]);
free(urbinput[1]);
free(urbinput[2]);
for(int i = 0; i < count; i++)
free(input[i].buffer);
return 0;
}
static long usbgetlong(hid_dev_t handle, CFStringRef key){
long raw = 0;
CFTypeRef cf;
if((*handle)->getProperty(handle, key, &cf) != kIOReturnSuccess)
return 0;
if(!cf || CFGetTypeID(cf) != CFNumberGetTypeID() || !CFNumberGetValue(cf, kCFNumberLongType, &raw))
return 0;
return raw;
}
static void usbgetstr(hid_dev_t handle, CFStringRef key, char* output, int out_len){
CFTypeRef cf;
if((*handle)->getProperty(handle, key, &cf) != kIOReturnSuccess){
output[0] = 0;
return;
}
if(!cf || CFGetTypeID(cf) != CFStringGetTypeID() || !CFStringGetCString(cf, output, out_len, kCFStringEncodingASCII))
output[0] = 0;
}
int os_setupusb(usbdevice* kb){
kb->lastkeypress = KEY_NONE;
// Handle 3 is the control handle
if(IS_RGB(kb->vendor, kb->product))
kb->handle = kb->handles[3];
else
// Non RGB keyboards don't have one, so just use 0
kb->handle = kb->handles[0];
// Get the device firmware version
long fwversion = usbgetlong(kb->handle, CFSTR(kIOHIDVersionNumberKey));
kb->fwversion = fwversion;
(*kb->handle)->GetDeviceReleaseNumber(kb->handle, &kb->fwversion);
return 0;
}
void os_closeusb(usbdevice* kb){
kb->handle = 0;
int count = (IS_RGB(kb->vendor, kb->product)) ? 4 : 3;
// Close HID handles
int count = kb->epcount_hid;
for(int i = 0; i < count; i++){
(*kb->handles[i])->close(kb->handles[i], kIOHIDOptionsTypeNone);
(*kb->handles[i])->Release(kb->handles[i]);
kb->handles[i] = 0;
hid_dev_t iface = kb->ifhid[i];
if(iface){
(*iface)->close(iface, kIOHIDOptionsTypeNone);
(*iface)->Release(iface);
kb->ifhid[i] = 0;
}
}
kb->epcount_hid = 0;
// Close USB handles
count = kb->epcount_usb;
for(int i = 0; i < count; i++){
usb_iface_t iface = kb->ifusb[i];
if(iface){
(*iface)->USBInterfaceClose(iface);
(*iface)->Release(iface);
kb->ifusb[i] = 0;
}
}
kb->epcount_usb = 0;
usb_dev_t iface = kb->handle;
if(iface){
(*iface)->USBDeviceClose(iface);
(*iface)->Release(iface);
kb->handle = 0;
}
// Close input thread
if(kb->input_loop){
CFRunLoopStop(kb->input_loop);
kb->input_loop = 0;
}
}
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 at %s%d\n", keyboard[i].name, devpath, i);
// 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;
}
static CFRunLoopRef mainloop = 0;
static IONotificationPortRef notify = 0;
static void remove_device(void* context, io_service_t device, uint32_t message_type, void* message_argument){
if(message_type != kIOMessageServiceIsTerminated)
return;
@@ -351,10 +417,43 @@ static void remove_device(void* context, io_service_t device, uint32_t message_t
IOObjectRelease(device);
}
static int seize_wait(hid_dev_t handle){
// HACK: We shouldn't seize the HID device until it's successfully added to the service registry.
// Finds a USB device by location ID. Returns a new device if none was found or -1 if no devices available.
// If successful, devmutex[index] will be locked when the function returns. Unlock it when finished.
static int find_device(long idvendor, long idproduct, long location){
// Look for any partially-set up boards matching this device
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].vendor == idvendor && keyboard[i].product == idproduct
&& keyboard[i].location_id == location){
// Matched; continue setting up this device
// Device mutex remains locked
return i;
}
pthread_mutex_unlock(devmutex + i);
}
// If none was found, grab the first free device
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
keyboard[i].handle = INCOMPLETE;
keyboard[i].location_id = location;
keyboard[i].vendor = idvendor;
keyboard[i].product = idproduct;
// Device mutex remains locked
return i;
}
pthread_mutex_unlock(devmutex + i);
}
return -1;
}
static int seize_wait(long location){
// HACK: We shouldn't seize the device until it's successfully added to the service registry.
// Otherwise, OSX might think there's no keyboard/mouse connected.
long location = usbgetlong(handle, CFSTR(kIOHIDLocationIDKey));
char location_var[18], location_fixed[18];
snprintf(location_var, sizeof(location_var), "@%lx", location);
snprintf(location_fixed, sizeof(location_fixed), "@%08x", (int)location);
@@ -366,9 +465,9 @@ static int seize_wait(hid_dev_t handle){
ckb_warn("Unable to open master port: 0x%08x\n", res);
return -1;
}
const int max_tries = 50; // give up after ~5s
const int max_tries = 20; // give up after ~2s
for(int try = 0; try < max_tries; try++){
usleep(100);
usleep(100000);
// Iterate the whole IOService registry
io_iterator_t child_iter;
if((res = IORegistryCreateIterator(master, kIOServicePlane, kIORegistryIterateRecursively, &child_iter)) != kIOReturnSuccess)
@@ -391,16 +490,235 @@ static int seize_wait(hid_dev_t handle){
return -3;
}
// Hacky way of trying something over and over again until it works. 500ms intervals, max 5s
#define wait_loop(error, operation) do { \
int trial = 0; \
while(((error) = (operation)) != kIOReturnSuccess){ \
if(++trial == 10) \
break; \
usleep(500000); \
} } while(0)
static usbdevice* add_usb(usb_dev_t handle, io_object_t** rm_notify){
int iface_count = 0, iface_success = 0;
io_iterator_t iterator = 0;
io_service_t iface = 0;
// Get device properties
UInt16 idvendor, idproduct;
UInt32 location;
(*handle)->GetDeviceVendor(handle, &idvendor);
(*handle)->GetDeviceProduct(handle, &idproduct);
(*handle)->GetLocationID(handle, &location);
// Use the location ID key to group the USB handle with the HID handles
int index = find_device(idvendor, idproduct, location);
if(index == -1){
ckb_err("No free devices\n");
return 0;
}
usbdevice* kb = keyboard + index;
static void iterate_devices(void* context, io_iterator_t iterator){
// Set the handle for the keyboard
if(kb->handle && kb->handle != INCOMPLETE){
// This should never happen
ckb_warn("Tried to set up handle for device ckb%d, but it was already set up. Skipping...\n", index);
goto error;
}
kb->handle = handle;
// Read the serial number and name (if not done yet)
if(!kb->serial[0] && !kb->name[0]){
UInt8 serial_idx, product_idx;
if((*handle)->USBGetSerialNumberStringIndex(handle, &serial_idx) == kIOReturnSuccess)
usbgetstr(handle, serial_idx, kb->serial, SERIAL_LEN);
if((*handle)->USBGetProductStringIndex(handle, &product_idx) == kIOReturnSuccess)
usbgetstr(handle, product_idx, kb->name, KB_NAME_LEN);
ckb_info("Connecting %s at %s%d\n", keyboard[index].name, devpath, index);
}
// Iterate through the USB interfaces. Most of these will fail to open because they're already grabbed by the HID system.
if(seize_wait(location))
ckb_warn("seize_wait failed, connecting anyway...\n");
IOUSBFindInterfaceRequest interfaceRequest;
interfaceRequest.bInterfaceClass = kIOUSBFindInterfaceDontCare;
interfaceRequest.bInterfaceSubClass = kIOUSBFindInterfaceDontCare;
interfaceRequest.bInterfaceProtocol = kIOUSBFindInterfaceDontCare;
interfaceRequest.bAlternateSetting = kIOUSBFindInterfaceDontCare;
(*handle)->CreateInterfaceIterator(handle, &interfaceRequest, &iterator);
// Count the total number of interfaces as well as the number successfully opened.
while((iface = IOIteratorNext(iterator)) != 0){
if(iface_count >= IFACE_MAX){
ckb_warn("Too many interfaces. Dropping the rest.\n");
IOObjectRelease(iface);
break;
}
// Get device interface
IOCFPlugInInterface** plugin = 0;
SInt32 score = 0;
kern_return_t err;
wait_loop(err, IOCreatePlugInInterfaceForService(iface, kIOUSBInterfaceUserClientTypeID, kIOCFPlugInInterfaceID, &plugin, &score));
if(err != kIOReturnSuccess){
ckb_err("Failed to create interface plugin: %x\n", err);
goto release;
}
usb_iface_t if_handle;
wait_loop(err, (*plugin)->QueryInterface(plugin, CFUUIDGetUUIDBytes(kIOUSBInterfaceInterfaceID183), (LPVOID)&if_handle));
if(err != kIOReturnSuccess){
ckb_err("QueryInterface failed: %x\n", err);
goto release;
}
// Plugin is no longer needed
IODestroyPlugInInterface(plugin);
// Try to open the interface. If it succeeds, add it to the device's interface list.
err = (*if_handle)->USBInterfaceOpenSeize(if_handle); // no wait_loop here because this is expected to fail
if(err == kIOReturnSuccess){
kb->ifusb[iface_count] = if_handle;
iface_success++;
// Register for removal notification
IOServiceAddInterestNotification(notify, iface, kIOGeneralInterest, remove_device, kb, kb->rm_notify + 1 + iface_count);
ckb_info("Adding ckb%d.ifusb[%d]\n", index, iface_count);
} else {
kb->ifusb[iface_count] = 0;
(*if_handle)->Release(if_handle);
}
release:
iface_count++;
IOObjectRelease(iface);
}
if(iface_count == 0){
// This shouldn't happen, but if it does, assume EP count based on what the device is supposed to have
iface_count = (HAS_FEATURES(kb, FEAT_RGB) ? 4 : 3);
ckb_warn("Unable to count endpoints, assuming %d...\n", iface_count);
}
kb->epcount = iface_count;
kb->epcount_usb = iface_success;
// If the HID handles are already opened, set up the device
if(HAS_ALL_HANDLES(kb))
setupusb(kb);
else
pthread_mutex_unlock(devmutex + index);
*rm_notify = kb->rm_notify;
return kb;
error:
pthread_mutex_unlock(devmutex + index);
return 0;
}
static void iterate_devices_usb(void* context, io_iterator_t iterator){
io_service_t device;
euid_guard_start;
while((device = IOIteratorNext(iterator)) != 0){
IOCFPlugInInterface** plugin = 0;
SInt32 score = 0;
kern_return_t err;
wait_loop(err, IOCreatePlugInInterfaceForService(device, kIOUSBDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &plugin, &score));
if(err != kIOReturnSuccess){
ckb_err("Failed to create device plugin: %x\n", err);
goto release;
}
// Get the device interface
usb_dev_t handle;
wait_loop(err, (*plugin)->QueryInterface(plugin, CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID182), (LPVOID*)&handle));
if(err != kIOReturnSuccess){
ckb_err("QueryInterface failed: %x\n", err);
goto release;
}
// Plugin is no longer needed
IODestroyPlugInInterface(plugin);
err = (*handle)->USBDeviceOpenSeize(handle);
if(err == kIOReturnExclusiveAccess){
// We can't send control transfers but most of the other functions should work
ckb_warn("Unable to seize USB handle, continuing anyway...\n");
} else if(err != kIOReturnSuccess){
ckb_err("USBDeviceOpen failed: %x\n", err);
continue;
}
// Connect it
io_object_t* rm_notify = 0;
usbdevice* kb = add_usb(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)->USBDeviceClose(handle);
release:
IOObjectRelease(device);
}
euid_guard_stop;
}
static usbdevice* add_hid(hid_dev_t handle, io_object_t** rm_notify){
// Get the model and serial number
long idvendor = hidgetlong(handle, CFSTR(kIOHIDVendorIDKey)), idproduct = hidgetlong(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 = hidgetlong(handle, CFSTR(kIOHIDLocationIDKey));
int index = find_device(idvendor, idproduct, location);
if(index == -1){
ckb_err("No free devices\n");
return 0;
}
usbdevice* kb = keyboard + index;
// Read the serial number and name (if not done yet)
if(!keyboard[index].serial[0] && !keyboard[index].name[0]){
hidgetstr(handle, CFSTR(kIOHIDSerialNumberKey), keyboard[index].serial, SERIAL_LEN);
hidgetstr(handle, CFSTR(kIOHIDProductKey), keyboard[index].name, KB_NAME_LEN);
ckb_info("Connecting %s at %s%d\n", keyboard[index].name, devpath, 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 = hidgetlong(handle, CFSTR(kIOHIDMaxInputReportSizeKey));
long output = hidgetlong(handle, CFSTR(kIOHIDMaxOutputReportSizeKey));
long feature = hidgetlong(handle, CFSTR(kIOHIDMaxFeatureReportSizeKey));
int handle_idx;
// Handle 3 is for controlling the device (only exists for RGB)
if(feature == 64)
handle_idx = 3;
// Handle 2 is for Corsair inputs, unused on non-RGB
else if(feature == 0 &&
(((input == 64 ||
input == 15) && output == 0) ||
(input == 64 && output == 64) || // FW >= 1.20
(input <= 1 && output == 64))) // FW >= 2.00 (Scimitar)
handle_idx = 2;
// Handle 0 is for BIOS mode input (RGB) or non-RGB key input
else if(output <= 1 && feature <= 1 &&
(input == 8 || // Keyboards
input == 7)) // Mice
handle_idx = 0;
// Handle 1 is for standard HID input (RGB) or media keys (non-RGB)
else if(output <= 1 && feature <= 1 &&
(input == 21 || input == 10 ||
input == 4 ||
input == 64)) // FW >= 2.00 (Scimitar)
handle_idx = 1;
else {
ckb_warn("Got unknown handle (I: %d, O: %d, F: %d)\n", (int)input, (int)output, (int)feature);
goto error;
}
// Set the handle
if(kb->ifhid[handle_idx]){
// This should never happen
ckb_warn("Tried to set up ifhid[%d] for device ckb%d, but it was already set up. Skipping...\n", handle_idx, index);
goto error;
}
ckb_info("Adding ckb%d.ifhid[%d]\n", index, handle_idx);
kb->ifhid[handle_idx] = handle;
kb->epcount_hid++;
if(HAS_ALL_HANDLES(kb))
// If all handles have been opened, we're ready to set up the device
setupusb(kb);
else
// Otherwise, return and keep going
pthread_mutex_unlock(devmutex + index);
*rm_notify = kb->rm_notify + IFACE_MAX + 1 + handle_idx;
return kb;
error:
pthread_mutex_unlock(devmutex + index);
return 0;
}
static void iterate_devices_hid(void* context, io_iterator_t iterator){
io_service_t device;
euid_guard_start;
while((device = IOIteratorNext(iterator)) != 0){
@@ -423,23 +741,23 @@ static void iterate_devices(void* context, io_iterator_t iterator){
// Plugin is no longer needed
IODestroyPlugInInterface(plugin);
// Seize the device handle
if(seize_wait(handle))
if(seize_wait(hidgetlong(handle, CFSTR(kIOHIDLocationIDKey))))
ckb_warn("seize_wait failed, connecting anyway...\n");
err = (*handle)->open(handle, kIOHIDOptionsTypeSeizeDevice);
wait_loop(err, (*handle)->open(handle, kIOHIDOptionsTypeSeizeDevice));
if(err != kIOReturnSuccess){
ckb_err("Failed to seize device: %x\n", err);
ckb_warn("Failed to open device: %x\n", err);
goto release;
}
// Connect it
io_object_t* rm_notify = 0;
usbdevice* kb = usbadd(handle, &rm_notify);
usbdevice* kb = add_hid(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, kIOHIDOptionsTypeSeizeDevice);
release:
release:
IOObjectRelease(device);
}
euid_guard_stop;
@@ -453,10 +771,23 @@ int usbmain(){
// Mice
P_M65, P_SABRE_O, P_SABRE_L, P_SCIMITAR
};
// Tell IOService which type of devices we want (IOHIDDevices matching the supported vendor/products)
CFMutableDictionaryRef match = IOServiceMatching(kIOHIDDeviceKey);
// Setup global variables
notify = IONotificationPortCreate(kIOMasterPortDefault);
mainloop = CFRunLoopGetCurrent();
CFRunLoopAddSource(mainloop, IONotificationPortGetRunLoopSource(notify), kCFRunLoopDefaultMode);
// This gets really complicated.
// The USB interfaces are *usually* grabbed by the HID system, which makes them inaccessible to ckb.
// In this case, we have to get the HID handles and communicate through them.
// But sometimes, the interfaces are not grabbed. In this case, we have to talk to the USB device directly.
// So what do we do? Grab both HID and USB handles for all devices, try to set them up, and determine which ones to use
// based on which interfaces were grabbed.
// Let's start by searching for USB devices...
CFMutableDictionaryRef match = IOServiceMatching(kIOUSBDeviceClassName);
CFNumberRef cfvendor = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &vendor);
CFDictionarySetValue(match, CFSTR(kIOHIDVendorIDKey), cfvendor);
CFDictionarySetValue(match, CFSTR(kUSBVendorName), cfvendor);
CFRelease(cfvendor);
CFMutableArrayRef cfproducts = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
for(uint i = 0; i < sizeof(products) / sizeof(int); i++){
@@ -465,20 +796,45 @@ int usbmain(){
CFArrayAppendValue(cfproducts, cfproduct);
CFRelease(cfproduct);
}
CFDictionarySetValue(match, CFSTR(kUSBProductIdsArrayName), cfproducts);
CFRelease(cfproducts);
io_iterator_t iterator_usb = 0;
IOReturn res = IOServiceAddMatchingNotification(notify, kIOMatchedNotification, match, iterate_devices_usb, 0, &iterator_usb);
if(res != kIOReturnSuccess){
ckb_fatal("Failed to list USB devices: %x\n", res);
return -1;
}
// Iterate existing devices
if(iterator_usb)
iterate_devices_usb(0, iterator_usb);
// Now move on to HID devices
// It is in fact necessary to recreate the matching dictionary, as the keys are different
match = IOServiceMatching(kIOHIDDeviceKey);
cfvendor = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &vendor);
CFDictionarySetValue(match, CFSTR(kIOHIDVendorIDKey), cfvendor);
CFRelease(cfvendor);
cfproducts = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
for(uint i = 0; i < sizeof(products) / sizeof(int); i++){
int product = products[i];
CFNumberRef cfproduct = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &product);
CFArrayAppendValue(cfproducts, cfproduct);
CFRelease(cfproduct);
}
CFDictionarySetValue(match, CFSTR(kIOHIDProductIDArrayKey), cfproducts);
CFRelease(cfproducts);
notify = IONotificationPortCreate(kIOMasterPortDefault);
CFRunLoopAddSource(mainloop = CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(notify), kCFRunLoopDefaultMode);
io_iterator_t iterator = 0;
IOReturn res = IOServiceAddMatchingNotification(notify, kIOMatchedNotification, match, iterate_devices, 0, &iterator);
io_iterator_t iterator_hid = 0;
res = IOServiceAddMatchingNotification(notify, kIOMatchedNotification, match, iterate_devices_hid, 0, &iterator_hid);
if(res != kIOReturnSuccess){
ckb_fatal("Failed to list devices: %x\n", res);
ckb_fatal("Failed to list HID devices: %x\n", res);
return -1;
}
// Iterate existing devices
if(iterator)
iterate_devices(0, iterator);
if(iterator_hid)
iterate_devices_hid(0, iterator_hid);
// Enter loop to scan/connect new devices
CFRunLoopRun();
return 0;