611 lines
25 KiB
Objective-C
611 lines
25 KiB
Objective-C
#import "HSRazerDevice.h"
|
|
|
|
#include <IOKit/usb/IOUSBLib.h>
|
|
|
|
double getSecondsSinceEpoch(void) {
|
|
struct timeval v;
|
|
gettimeofday(&v, (struct timezone *) NULL);
|
|
return v.tv_sec + v.tv_usec/1.0e6;
|
|
}
|
|
|
|
// HSRazerResult:
|
|
@implementation HSRazerResult
|
|
- (id)init {
|
|
if ((self = [super init])) {
|
|
self.success = NO;
|
|
}
|
|
return self;
|
|
}
|
|
@end
|
|
|
|
// HSRazerDevice:
|
|
@interface HSRazerDevice ()
|
|
@end
|
|
|
|
@implementation HSRazerDevice
|
|
- (id)initWithDevice:(IOHIDDeviceRef)device manager:(id)manager {
|
|
self = [super init];
|
|
if (self) {
|
|
self.device = device;
|
|
self.isValid = YES;
|
|
self.manager = manager;
|
|
self.buttonCallbackRef = LUA_NOREF;
|
|
self.selfRefCount = 0;
|
|
|
|
// These defaults are not necessary, all base classes will override them, but if we miss something, these are chosen to try and provoke a crash where possible, so we notice the lack of an override.
|
|
self.name = @"Unknown";
|
|
self.scrollWheelPressed = NO;
|
|
self.lastScrollWheelEvent = getSecondsSinceEpoch();
|
|
|
|
//NSLog(@"[hs.razer] Added new Razer device %p with IOKit device %p from manager %p", (__bridge void *)self, (void*)self.device, (__bridge void *)self.manager);
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)invalidate {
|
|
self.isValid = NO;
|
|
[self destroyEventTap];
|
|
}
|
|
|
|
#pragma mark - Button Callbacks
|
|
|
|
- (void)deviceButtonPress:(NSString*)scancodeString pressed:(long)pressed {
|
|
//NSLog(@"Tartarus V2 deviceButtonPress!");
|
|
//NSLog(@"scancode: %@", scancodeString);
|
|
//NSLog(@"pressed: %ld", pressed);
|
|
|
|
// Abort if the device is no longer valid:
|
|
if (!self.isValid) {
|
|
//NSLog(@"[hs.razer] The Razer device is no longer valid.");
|
|
return;
|
|
}
|
|
|
|
// Get button name from device dictonary:
|
|
NSString *buttonName = [self.buttonNames valueForKey:scancodeString];
|
|
|
|
// Abort if there's no button name:
|
|
if (!buttonName) {
|
|
//NSLog(@"[hs.razer] There's no button assigned for: %@", scancodeString);
|
|
return;
|
|
}
|
|
|
|
// Process the button action:
|
|
NSString *buttonAction = @"";
|
|
NSString *scrollWheelID = [NSString stringWithFormat:@"%d", self.scrollWheelID];
|
|
if ([scancodeString isEqualToString:scrollWheelID]) {
|
|
// Scroll Wheel:
|
|
if (pressed == 1){
|
|
buttonAction = @"up";
|
|
self.lastScrollWheelEvent = getSecondsSinceEpoch();
|
|
}
|
|
else if (pressed == -1) {
|
|
buttonAction = @"down";
|
|
self.lastScrollWheelEvent = getSecondsSinceEpoch();
|
|
}
|
|
else if (pressed == 0) {
|
|
if (self.scrollWheelPressed) {
|
|
buttonAction = @"released";
|
|
self.scrollWheelPressed = NO;
|
|
} else {
|
|
buttonAction = @"pressed";
|
|
self.scrollWheelPressed = YES;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Buttons:
|
|
if (pressed == 1){
|
|
buttonAction = @"pressed";
|
|
}
|
|
else {
|
|
buttonAction = @"released";
|
|
}
|
|
}
|
|
|
|
// Trigger the Lua callback:
|
|
if (self.buttonCallbackRef != LUA_NOREF) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:NULL];
|
|
|
|
if (![skin checkGCCanary:self.lsCanary]) {
|
|
return;
|
|
}
|
|
|
|
_lua_stackguard_entry(skin.L);
|
|
[skin pushLuaRef:razerRefTable ref:self.buttonCallbackRef];
|
|
[skin pushNSObject:self];
|
|
[skin pushNSObject:buttonName];
|
|
[skin pushNSObject:buttonAction];
|
|
[skin protectedCallAndError:@"hs.razer:callback" nargs:3 nresults:0];
|
|
_lua_stackguard_exit(skin.L);
|
|
}
|
|
|
|
}
|
|
|
|
#pragma mark - Event Tap for Scroll Wheel
|
|
|
|
// We need to use an eventtap to stop the scroll wheel on the Razer Tartarus V2 from actually scrolling.
|
|
|
|
static CGEventRef eventTapCallback(CGEventTapProxy proxy,
|
|
CGEventType type,
|
|
CGEventRef event,
|
|
void* refcon) {
|
|
|
|
//NSLog(@"[hs.razer] Event Tap Callback Triggered");
|
|
|
|
// Prevent a crash when doing garbage collection:
|
|
if (type == kCGEventTapDisabledByUserInput) {
|
|
//NSLog(@"[hs.razer] Aborting Event Tap Callback, because event tap disabled by user input.");
|
|
return event;
|
|
}
|
|
|
|
HSRazerDevice *manager = (__bridge HSRazerDevice *)refcon;
|
|
|
|
// Make sure the manager still exists:
|
|
if (manager == nil) {
|
|
//NSLog(@"[hs.razer] Aborting Event Tap Callback, because manager no longer exists.");
|
|
return event; // Allow the event to pass through unmodified
|
|
}
|
|
|
|
// Restart event tap if it times out:
|
|
if (type == kCGEventTapDisabledByTimeout) {
|
|
//NSLog(@"[hs.razer] Aborting Event Tap Callback, because event tap disabled by timeout.");
|
|
CGEventTapEnable(manager.eventTap, true);
|
|
return event; // Allow the event to pass through unmodified
|
|
}
|
|
|
|
// Guard against this callback being delivered at a point where LuaSkin has been reset and our references wouldn't make sense anymore:
|
|
LuaSkin *skin = [LuaSkin sharedWithState:NULL];
|
|
if (![skin checkGCCanary:manager.lsCanary]) {
|
|
//NSLog(@"[hs.razer] Aborting Event Tap Callback, because canary test failed.");
|
|
return event; // Allow the event to pass through unmodified
|
|
}
|
|
|
|
// Throw away the event if we recently scrolled with the Razer Device:
|
|
double currentTime = getSecondsSinceEpoch() - 0.1;
|
|
if (currentTime < manager.lastScrollWheelEvent) {
|
|
return NULL;
|
|
} else {
|
|
return event;
|
|
}
|
|
}
|
|
|
|
- (void)setupEventTap
|
|
{
|
|
if (!self.scrollWheelID) {
|
|
NSLog(@"[hs.razer] The device does not have a scroll wheel ID, so aborting event tap setup.");
|
|
return;
|
|
}
|
|
|
|
//NSLog(@"[hs.razer] Setting up Event Tap.");
|
|
|
|
CGEventTapLocation location = kCGHIDEventTap;
|
|
|
|
CGEventMask mask = CGEventMaskBit(kCGEventScrollWheel);
|
|
|
|
self.eventTap = CGEventTapCreate(location,
|
|
kCGTailAppendEventTap,
|
|
kCGEventTapOptionDefault,
|
|
mask,
|
|
eventTapCallback,
|
|
(__bridge void*)(self));
|
|
|
|
if (!self.eventTap) {
|
|
NSLog(@"[hs.razer] Failed to create the event tap.");
|
|
} else {
|
|
CFRunLoopSourceRef source = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, self.eventTap, 0);
|
|
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
|
|
CFRelease(source);
|
|
|
|
CGEventTapEnable(self.eventTap, true);
|
|
}
|
|
}
|
|
|
|
- (void)destroyEventTap
|
|
{
|
|
if (self.eventTap){
|
|
if (CGEventTapIsEnabled(self.eventTap)) {
|
|
CGEventTapEnable(self.eventTap, false);
|
|
}
|
|
CFRelease(self.eventTap);
|
|
}
|
|
}
|
|
|
|
#pragma mark - hs.razer: Keyboard Backlight Placeholders
|
|
|
|
- (HSRazerResult*)setBacklightToStaticColor:(NSColor*)color {
|
|
NSException *exception = [NSException exceptionWithName:@"HSRazerDeviceUnimplemented"
|
|
reason:@"setBacklightToStaticColor method not implemented"
|
|
userInfo:nil];
|
|
[exception raise];
|
|
return [[HSRazerResult alloc] init];
|
|
}
|
|
- (HSRazerResult*)setBacklightToOff {
|
|
NSException *exception = [NSException exceptionWithName:@"HSRazerDeviceUnimplemented"
|
|
reason:@"setBacklightToOff method not implemented"
|
|
userInfo:nil];
|
|
[exception raise];
|
|
return [[HSRazerResult alloc] init];
|
|
}
|
|
|
|
- (HSRazerResult*)setBacklightToWaveWithSpeed:(NSNumber*)speed direction:(NSString*)direction {
|
|
NSException *exception = [NSException exceptionWithName:@"HSRazerDeviceUnimplemented"
|
|
reason:@"setBacklightToWaveWithSpeed method not implemented"
|
|
userInfo:nil];
|
|
[exception raise];
|
|
return [[HSRazerResult alloc] init];
|
|
}
|
|
- (HSRazerResult*)setBacklightToSpectrum {
|
|
NSException *exception = [NSException exceptionWithName:@"HSRazerDeviceUnimplemented"
|
|
reason:@"setBacklightToSpectrum method not implemented"
|
|
userInfo:nil];
|
|
[exception raise];
|
|
return [[HSRazerResult alloc] init];
|
|
}
|
|
|
|
- (HSRazerResult*)setBacklightToReactiveWithColor:(NSColor*)color speed:(NSNumber*)speed {
|
|
NSException *exception = [NSException exceptionWithName:@"HSRazerDeviceUnimplemented"
|
|
reason:@"setBacklightToReactiveWithColor method not implemented"
|
|
userInfo:nil];
|
|
[exception raise];
|
|
return [[HSRazerResult alloc] init];
|
|
}
|
|
|
|
- (HSRazerResult*)setBacklightToStarlightWithColor:(NSColor*)color secondaryColor:(NSColor*)secondaryColor speed:(NSNumber*)speed {
|
|
NSException *exception = [NSException exceptionWithName:@"HSRazerDeviceUnimplemented"
|
|
reason:@"setBacklightToStarlightWithColor method not implemented"
|
|
userInfo:nil];
|
|
[exception raise];
|
|
return [[HSRazerResult alloc] init];
|
|
}
|
|
|
|
- (HSRazerResult*)setBacklightToBreathingWithColor:(NSColor*)color secondaryColor:(NSColor*)secondaryColor {
|
|
NSException *exception = [NSException exceptionWithName:@"HSRazerDeviceUnimplemented"
|
|
reason:@"setBacklightToBreathWithColor method not implemented"
|
|
userInfo:nil];
|
|
[exception raise];
|
|
return [[HSRazerResult alloc] init];
|
|
}
|
|
|
|
- (HSRazerResult*)setBacklightToCustomWithColors:(NSMutableDictionary *)customColors {
|
|
NSException *exception = [NSException exceptionWithName:@"HSRazerDeviceUnimplemented"
|
|
reason:@"setBacklightToCustomWithColors method not implemented"
|
|
userInfo:nil];
|
|
[exception raise];
|
|
return [[HSRazerResult alloc] init];
|
|
}
|
|
|
|
#pragma mark - hs.razer: Brightness Placeholders
|
|
|
|
- (HSRazerResult*)getBrightness {
|
|
NSException *exception = [NSException exceptionWithName:@"HSRazerDeviceUnimplemented"
|
|
reason:@"getBrightness method not implemented"
|
|
userInfo:nil];
|
|
[exception raise];
|
|
return [[HSRazerResult alloc] init];
|
|
}
|
|
|
|
- (HSRazerResult*)setBrightness:(NSNumber *)brightness {
|
|
NSException *exception = [NSException exceptionWithName:@"HSRazerDeviceUnimplemented"
|
|
reason:@"setBrightness method not implemented"
|
|
userInfo:nil];
|
|
[exception raise];
|
|
return [[HSRazerResult alloc] init];
|
|
}
|
|
|
|
#pragma mark - hs.razer: Status Light Placeholders
|
|
|
|
- (HSRazerResult*)getOrangeStatusLight {
|
|
NSException *exception = [NSException exceptionWithName:@"HSRazerDeviceUnimplemented"
|
|
reason:@"getOrangeStatusLight method not implemented"
|
|
userInfo:nil];
|
|
[exception raise];
|
|
return [[HSRazerResult alloc] init];
|
|
}
|
|
|
|
- (HSRazerResult*)setOrangeStatusLight:(BOOL)active {
|
|
NSException *exception = [NSException exceptionWithName:@"HSRazerDeviceUnimplemented"
|
|
reason:@"setOrangeStatusLight method not implemented"
|
|
userInfo:nil];
|
|
[exception raise];
|
|
return [[HSRazerResult alloc] init];
|
|
}
|
|
|
|
- (HSRazerResult*)getGreenStatusLight {
|
|
NSException *exception = [NSException exceptionWithName:@"HSRazerDeviceUnimplemented"
|
|
reason:@"getGreenStatusLight method not implemented"
|
|
userInfo:nil];
|
|
[exception raise];
|
|
return [[HSRazerResult alloc] init];
|
|
}
|
|
|
|
- (HSRazerResult*)setGreenStatusLight:(BOOL)active {
|
|
NSException *exception = [NSException exceptionWithName:@"HSRazerDeviceUnimplemented"
|
|
reason:@"setGreenStatusLight method not implemented"
|
|
userInfo:nil];
|
|
[exception raise];
|
|
return [[HSRazerResult alloc] init];
|
|
}
|
|
|
|
- (HSRazerResult*)getBlueStatusLight {
|
|
NSException *exception = [NSException exceptionWithName:@"HSRazerDeviceUnimplemented"
|
|
reason:@"getBlueStatusLight method not implemented"
|
|
userInfo:nil];
|
|
[exception raise];
|
|
return [[HSRazerResult alloc] init];
|
|
}
|
|
|
|
- (HSRazerResult*)setBlueStatusLight:(BOOL)active {
|
|
NSException *exception = [NSException exceptionWithName:@"HSRazerDeviceUnimplemented"
|
|
reason:@"setBlueStatusLight method not implemented"
|
|
userInfo:nil];
|
|
[exception raise];
|
|
return [[HSRazerResult alloc] init];
|
|
}
|
|
|
|
#pragma mark - hs.razer: USB Device Methods
|
|
|
|
- (IOUSBDeviceInterface**)getUSBRazerDevice {
|
|
|
|
//NSLog(@"[hs.razer] Getting Razer USB Device");
|
|
|
|
CFMutableDictionaryRef matchingDict = IOServiceMatching(kIOUSBDeviceClassName);
|
|
|
|
// Get all the USB devices:
|
|
io_iterator_t iter;
|
|
kern_return_t kReturn = IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDict, &iter);
|
|
|
|
// Abort if something goes wrong:
|
|
if (kReturn != kIOReturnSuccess) {
|
|
//NSLog(@"[hs.razer] Failed to get any USB devices: %d", kReturn);
|
|
return NULL;
|
|
}
|
|
|
|
// Check each USB device to see if there's a match:
|
|
io_service_t usbDevice;
|
|
while ((usbDevice = IOIteratorNext(iter)))
|
|
{
|
|
IOCFPlugInInterface **plugInInterface = NULL;
|
|
SInt32 score;
|
|
|
|
// Create a new device plugin:
|
|
kReturn = IOCreatePlugInInterfaceForService(usbDevice, kIOUSBDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &plugInInterface, &score);
|
|
|
|
// Clean up unnecessary object:
|
|
IOObjectRelease(usbDevice);
|
|
|
|
// Skip the current USB device if can't create plugin:
|
|
if ((kReturn != kIOReturnSuccess) || plugInInterface == NULL) {
|
|
//NSLog(@"[hs.razer] Failed to create plugin: %d", kReturn);
|
|
continue;
|
|
}
|
|
|
|
// Create a new device interface:
|
|
IOUSBDeviceInterface **dev = NULL;
|
|
HRESULT hResult = (*plugInInterface)->QueryInterface(plugInInterface, CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID), (LPVOID *)&dev);
|
|
|
|
// Clean up unnecessary object:
|
|
(*plugInInterface)->Release(plugInInterface);
|
|
|
|
// Skip the current USB device if can't create interface:
|
|
if (hResult || !dev) {
|
|
//NSLog(@"[hs.razer] Failed to create device interface: %d", (int)hResult);
|
|
continue;
|
|
}
|
|
|
|
// Clean up unnecessary object:
|
|
kern_return_t kr;
|
|
|
|
// Make sure the location ID matches:
|
|
UInt32 locationID;
|
|
kr = (*dev)->GetLocationID(dev, &locationID);
|
|
if (kr != kIOReturnSuccess || locationID != [self.locationID unsignedIntValue]) {
|
|
//NSLog(@"[hs.razer] The location ID for the IOHID Device doesn't match the USB Device.");
|
|
continue;
|
|
}
|
|
|
|
// Make sure the vendor matches (just for safety):
|
|
UInt16 vendor;
|
|
kr = (*dev)->GetDeviceVendor(dev, &vendor);
|
|
if (kr != kIOReturnSuccess || vendor != USB_VID_RAZER) {
|
|
//NSLog(@"[hs.razer] Vendor is not Razer: %hu", vendor);
|
|
continue;
|
|
}
|
|
|
|
// Make sure the product matches (just for safety):
|
|
UInt16 product;
|
|
kr = (*dev)->GetDeviceProduct(dev, &product);
|
|
if (kr != kIOReturnSuccess || product != self.productID) {
|
|
//NSLog(@"[hs.razer] Product is not a Taratus V2: %hu", product);
|
|
continue;
|
|
}
|
|
|
|
// Open the device:
|
|
kReturn = (*dev)->USBDeviceOpen(dev);
|
|
if (kReturn != kIOReturnSuccess) {
|
|
//NSLog(@"[hs.razer] Failed to open USB device: %d", kReturn);
|
|
(*dev)->Release(dev);
|
|
continue;
|
|
}
|
|
|
|
// Clean up unnecessary object:
|
|
IOObjectRelease(iter);
|
|
|
|
// Party time! We found the Razer USB device.
|
|
//NSLog(@"[hs.razer] Found a device!");
|
|
return dev;
|
|
}
|
|
|
|
// No device found:
|
|
//NSLog(@"[hs.razer] No Razer USB Devices found that match IOHID Device.");
|
|
return NULL;
|
|
}
|
|
|
|
- (HSRazerResult*)sendRazerReportToDeviceWithTransactionID:(int)transactionID commandClass:(int)commandClass commandID:(int)commandID arguments:(NSDictionary*)arguments {
|
|
/*
|
|
Handy Resources:
|
|
|
|
- Information on USB Packets:
|
|
https://www.beyondlogic.org/usbnutshell/usb6.shtml
|
|
|
|
- AppleUSBDefinitions.h:
|
|
https://lab.qaq.wiki/Lakr233/IOKit-deploy/-/blob/master/IOKit/usb/AppleUSBDefinitions.h
|
|
*/
|
|
|
|
// Setup the result we'll eventually return:
|
|
HSRazerResult *result = [[HSRazerResult alloc] init];
|
|
|
|
// The wValue and wIndex fields allow parameters to be passed with the request:
|
|
int wValue = 0x300; // wValue = 16 bit parameter for request, low byte first.
|
|
int wIndex = 0x01; // wIndex = 16 bit parameter for request, low byte first.
|
|
|
|
// wLength is used the specify the number of bytes to be transferred should there be a data phase:
|
|
int wLength = 90; // wLength = Length of data part of request, 16 bits, low byte first. A Razer Report is always 90 bytes.
|
|
|
|
// Setup an empty Razor Report:
|
|
struct HSRazerReport report = {0}; // Setup an empty Razer Report
|
|
|
|
// Determine the data size based on the amount of arguments provided:
|
|
int dataSize = (int)[arguments count];
|
|
|
|
// Now fill it with data:
|
|
report.status = 0x00; // Always 0x00 for a New Command
|
|
report.transaction_id.id = transactionID; // Allows you to group requests if using multiple devices
|
|
report.remaining_packets = 0x00; // Remaning Packets (using Big Endian Byte Order)
|
|
report.protocol_type = 0x00; // Always seems to be 0x00
|
|
report.data_size = dataSize; // How many arguments
|
|
report.command_class = commandClass; // The type of command being triggered
|
|
report.command_id.id = commandID; // The ID of the command being triggered
|
|
report.reserved = 0x00; // A reserved byte - always 0x00
|
|
|
|
// Process the arguments:
|
|
for (unsigned long x = 0; x < [arguments count]; x++)
|
|
{
|
|
id argument = [arguments objectForKey:[NSNumber numberWithUnsignedLong:x]];
|
|
if (argument) {
|
|
report.arguments[x] = [argument integerValue];
|
|
}
|
|
}
|
|
|
|
// Razer uses a CRC as a simple checksum to make sure the report data is correct and valid. You just XOR all the bytes.
|
|
unsigned char crc = 0;
|
|
unsigned char *crcReport = (unsigned char*)&report;
|
|
for(unsigned int i = 2; i < 88; i++) { crc ^= crcReport[i]; }
|
|
report.crc = crc;
|
|
|
|
// Parameter block for control requests, using a simple pointer for the data to be transferred:
|
|
IOUSBDevRequest request;
|
|
|
|
// The bmRequestType field will determine the direction of the request, type of request and designated recipient:
|
|
request.bmRequestType = kIOUSBDeviceRequestDirectionOut | kIOUSBDeviceRequestTypeClass | kIOUSBDeviceRequestRecipientValueInterface;
|
|
|
|
// The bRequest field determines the request being made:
|
|
request.bRequest = kIOUSBDeviceRequestSetConfiguration;
|
|
|
|
// The wValue and wIndex fields allow parameters to be passed with the request:
|
|
request.wValue = wValue; // wValue = 16 bit parameter for request, low byte first.
|
|
request.wIndex = wIndex; // wIndex = 16 bit parameter for request, low byte first.
|
|
|
|
// wLength is used the specify the number of bytes to be transferred should there be a data phase:
|
|
request.wLength = wLength; // wLength = Length of data part of request, 16 bits, low byte first.
|
|
|
|
// wData is the actual data to send:
|
|
request.pData = (void*)&report; // pData = Pointer to data for request.
|
|
|
|
// Get the Razer USB device:
|
|
IOUSBDeviceInterface **razerDevice = self.getUSBRazerDevice;
|
|
|
|
// If no device could be found, abort:
|
|
if (!razerDevice) {
|
|
result.errorMessage = @"Failed to create a Razer device for the initial report.";
|
|
return result;
|
|
}
|
|
|
|
// Send the report to the device:
|
|
IOReturn deviceRequestResult = (*razerDevice)->DeviceRequest(razerDevice, &request);
|
|
|
|
// Opps! Something has gone wrong:
|
|
if (deviceRequestResult != kIOReturnSuccess) {
|
|
// Close & Release the USB Device:
|
|
(*razerDevice)->USBDeviceClose(razerDevice);
|
|
(*razerDevice)->Release(razerDevice);
|
|
|
|
result.errorMessage = [NSString stringWithFormat:@"Failed to send Device Request: %d", deviceRequestResult];
|
|
return result;
|
|
}
|
|
|
|
// Wait for a response back...
|
|
usleep(500); // Standard Device requests with a data stage must start to return data 500ms after the request.
|
|
|
|
// Parameter block for control requests, using a simple pointer for the data to be transferred:
|
|
IOUSBDevRequest responseRequest;
|
|
|
|
// Setup an empty Razor Report for the response back:
|
|
struct HSRazerReport responseReport = {0};
|
|
|
|
// The bmRequestType field will determine the direction of the request, type of request and designated recipient:
|
|
responseRequest.bmRequestType = kIOUSBDeviceRequestDirectionIn | kIOUSBDeviceRequestTypeClass | kIOUSBDeviceRequestRecipientValueInterface;
|
|
|
|
// The bRequest field determines the request being made:
|
|
responseRequest.bRequest = kIOUSBDeviceRequestClearFeature;
|
|
|
|
// The wValue and wIndex fields allow parameters to be passed with the request:
|
|
responseRequest.wValue = wValue; // wValue = 16 bit parameter for request, low byte first.
|
|
responseRequest.wIndex = wIndex; // wIndex = 16 bit parameter for request, low byte first.
|
|
|
|
// wLength is used the specify the number of bytes to be transferred should there be a data phase:
|
|
responseRequest.wLength = wLength; // wLength = Length of data part of request, 16 bits, low byte first.
|
|
|
|
// wData is the actual data to send:
|
|
responseRequest.pData = &responseReport; // pData = Pointer to data for request.
|
|
|
|
// Send the report to the device:
|
|
IOReturn responseResult = (*razerDevice)->DeviceRequest(razerDevice, &responseRequest);
|
|
|
|
// Close & Release the USB Device:
|
|
(*razerDevice)->USBDeviceClose(razerDevice);
|
|
(*razerDevice)->Release(razerDevice);
|
|
|
|
// Process the response:
|
|
if(responseResult != kIOReturnSuccess) {
|
|
result.errorMessage = [NSString stringWithFormat:@"Failed to get a response back from the Razer Device: %s", mach_error_string(responseResult)];
|
|
} else {
|
|
if (responseReport.remaining_packets != report.remaining_packets) {
|
|
result.errorMessage = @"The sent report remaining packets don't match the response remaining packets.";
|
|
} else if (responseReport.command_class != report.command_class) {
|
|
result.errorMessage = @"The sent report command class doesn't match the response command class.";
|
|
} else if (responseReport.command_id.id != report.command_id.id) {
|
|
result.errorMessage = @"The sent report command ID doesn't match the response command ID.";
|
|
} else if (responseReport.status == 0x01) {
|
|
// NOTE:
|
|
// It seems that the Razer device sends back a "busy" response quite a bit, but
|
|
// still successfully executes the command, so not sure what's going on there.
|
|
// We'll just assume that "busy" actually means slightly delayed, but still successful.
|
|
|
|
//result.errorMessage = @"Razer device is busy.";
|
|
|
|
// Victory!
|
|
result.success = YES;
|
|
} else if (responseReport.status == 0x02) {
|
|
// Victory!
|
|
result.success = YES;
|
|
} else if (responseReport.status == 0x03) {
|
|
result.errorMessage = @"The command sent to the Razer device failed.";
|
|
} else if (responseReport.status == 0x04) {
|
|
result.errorMessage = @"The command sent to the Razer device timed out.";
|
|
} else if (responseReport.status == 0x05) {
|
|
result.errorMessage = @"The command sent to the Razer device is not supported.";
|
|
} else {
|
|
result.errorMessage = [NSString stringWithFormat:@"Unexpected status back from the Razer device: %c", responseReport.status];
|
|
}
|
|
}
|
|
|
|
// Put any useful arguments into the result:
|
|
result.argumentTwo = responseReport.arguments[2];
|
|
|
|
// Something went wrong:
|
|
return result;
|
|
}
|
|
|
|
@end
|