// // WheelSupports.cpp // WheelSupportTools // // Created by Annop Prapasapong on 27/11/2012. // Copyright (c) 2012 Feral Interactive. All rights reserved. // #include <CoreFoundation/CFString.h> #include "WheelSupports.h" //============================================================================= // Mode strings const char *sGPLogitechModeRestricted = "RESTRICTED"; const char *sGPLogitechModeNative = "NATIVE"; //============================================================================= // SetApplierFunctionCopyToCFArray : Applier function for CFSetApplyFunction //----------------------------------------------------------------------------- void SetApplierFunctionCopyToCFArray(const void *value, void *context) { CFArrayAppendValue((CFMutableArrayRef) context, value); } //============================================================================= // AllocateHIDManager //----------------------------------------------------------------------------- IOHIDManagerRef AllocateHIDManager() { IOHIDManagerRef managerRef = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDManagerOptionNone); IOHIDManagerSetDeviceMatching(managerRef, NULL); IOHIDManagerOpen(managerRef, kIOHIDOptionsTypeSeizeDevice); return managerRef; } //============================================================================= // ConfigAllDevices : Scan connected devices and apply early configs as need // Return true if any change is made //----------------------------------------------------------------------------- bool ConfigAllDevices(const DeviceMode mode) { bool changed = false; // Obtain a copy of the list of connected devices IOHIDManagerRef managerRef = AllocateHIDManager(); CFSetRef deviceCFSetRef = IOHIDManagerCopyDevices(managerRef); CFIndex count = CFSetGetCount(deviceCFSetRef); CFArrayRef devCFArrayRef = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); CFSetApplyFunction(deviceCFSetRef, SetApplierFunctionCopyToCFArray, (void*)devCFArrayRef); // Going through the list, determining their ID and call the appropriate function; for(CFIndex i = 0; i < count; i++) { IOHIDDeviceRef hidDevice = (IOHIDDeviceRef) CFArrayGetValueAtIndex(devCFArrayRef, i); UInt32 vendorID = GetPropertyNumber(hidDevice, CFSTR(kIOHIDVendorIDKey)); UInt32 productID = GetPropertyNumber(hidDevice, CFSTR(kIOHIDProductIDKey)); DeviceID deviceID = MakeDeviceID(productID, vendorID); changed |= ConfigDevice(hidDevice, deviceID, mode); } // Release the list CFRelease(devCFArrayRef); CFRelease(deviceCFSetRef); return changed; } //============================================================================= // ConfigDevice : Config a device using current settngs //----------------------------------------------------------------------------- bool ConfigDevice(IOHIDDeviceRef hidDevice, DeviceID deviceID, const DeviceMode mode) { switch(deviceID) { case kGPLogitechWheelRestricted: return ConfigLogitechWheels(hidDevice, deviceID, false, mode); break; case kGPLogitechG25Native: printf("Logitech G25 Native mode enabled.\n"); return ConfigLogitechWheels(hidDevice, deviceID, true, mode); case kGPLogitechG27Native: printf("Logitech G27 Native mode enabled.\n"); return ConfigLogitechWheels(hidDevice, deviceID, true, mode); case kGPLogitechDFGTNative: printf("Logitech Driving Force GT Native mode enabled.\n"); return ConfigLogitechWheels(hidDevice, deviceID, true, mode); case kGPLogitechDFPNative: printf("Logitech Driving Force Pro Native mode enabled.\n"); return ConfigLogitechWheels(hidDevice, deviceID, true, mode); default: return false; } } //============================================================================= // OpenDevice : Open given device if it is not opened yet //----------------------------------------------------------------------------- IOReturn OpenDevice(IOHIDDeviceRef hidDevice) { IOReturn result = IOHIDDeviceOpen(hidDevice, kIOHIDOptionsTypeSeizeDevice); if(result != kIOReturnSuccess) { printf("ERROR: OpenDevice failed with result: %x\n", result); return result; } return kIOReturnSuccess; } //============================================================================= // CloseDevice : Close given device if it is opened //----------------------------------------------------------------------------- IOReturn CloseDevice(IOHIDDeviceRef hidDevice) { return IOHIDDeviceClose(hidDevice, 0); } //============================================================================= // SendCommands : Send given commands to the device //----------------------------------------------------------------------------- IOReturn SendCommands(IOHIDDeviceRef hidDevice, CCommands *commands) { for(int i=0; i<commands->count; ++i) { UInt8 *cmd = commands->cmds[i]; IOReturn result = IOHIDDeviceSetReport(hidDevice, kIOHIDReportTypeOutput, time(NULL), cmd, kGPCommandMaxLength); if (result != kIOReturnSuccess) { printf("WARNING: SendCommand failed with result: %x\n", result); return result; } } return kIOReturnSuccess; } //============================================================================= // GetPropertyString : Obtain the property string data of the device //----------------------------------------------------------------------------- CFStringRef GetPropertyString(IOHIDDeviceRef hidDevice, CFStringRef property) { // Obtain the data as untyped pointer and convert it to CString CFTypeRef dataRef = IOHIDDeviceGetProperty(hidDevice, property); return CFStringCreateCopy(kCFAllocatorDefault, CFStringRef(dataRef)); } //============================================================================= // GetPropertyString : Obtain the property number data of the device //----------------------------------------------------------------------------- UInt32 GetPropertyNumber(IOHIDDeviceRef hidDevice, CFStringRef property) { // Obtain the data as untyped pointer and convert it to CString CFTypeRef dataRef = IOHIDDeviceGetProperty(hidDevice, property); if(dataRef && (CFNumberGetTypeID() == CFGetTypeID(dataRef))) { UInt32 number; CFNumberGetValue((CFNumberRef)dataRef, kCFNumberSInt32Type, &number); return number; } return 0; } //============================================================================= // ConfigLogitechWheels : for Logitech wheels //----------------------------------------------------------------------------- bool ConfigLogitechWheels(IOHIDDeviceRef hidDevice, DeviceID deviceID, bool native, const DeviceMode targetMode) { if(targetMode == DeviceModeInfoOnly) { CFStringRef productID = GetPropertyString(hidDevice, CFSTR(kIOHIDProductKey)); const char *mode = native ? sGPLogitechModeNative : sGPLogitechModeRestricted; char sProductID[256]; CFStringGetCString(productID, sProductID, 256, kCFStringEncodingASCII); printf("Device ID=%x Product ID=%s (%s)\n", deviceID, sProductID, mode); return false; } if(!native) { // Don't do anything is the feature is not enabled if(targetMode == DeviceModeStandard) { return false; } // Determine proper native device ID from the device's Product ID string // As the restricted device ID are the same for all 4 supported devices CFStringRef productID = GetPropertyString(hidDevice, CFSTR(kIOHIDProductKey)); DeviceID targetDeviceID; if(CFStringFind(productID, CFSTR(kGPLogitechG25ProductID), 0).location == 0) { targetDeviceID = kGPLogitechG25Native; printf("Logitech G25 Native mode enabled.\n"); } else if(CFStringFind(productID, CFSTR(kGPLogitechG27ProductID), 0).location == 0) { targetDeviceID = kGPLogitechG27Native; printf("Logitech G27 Native mode enabled.\n"); } else if(CFStringFind(productID, CFSTR(kGPLogitechDFGTProductID), 0).location == 0) { targetDeviceID = kGPLogitechDFGTNative; printf("Logitech Driving Force GT Native mode enabled.\n"); } else if(CFStringFind(productID, CFSTR(kGPLogitechDFPProductID), 0).location == 0) { targetDeviceID = kGPLogitechDFPNative; printf("Logitech Driving Force Pro Native mode enabled.\n"); } else return false; // Activate full native and 900 degree mode if(OpenDevice(hidDevice) == kIOReturnSuccess) { CCommands commands; GetCmdLogitechWheelRange(&commands, targetDeviceID, kGPLogitechWheelRangeMax); SendCommands(hidDevice, &commands); printf("Calibrated full wheel range. (VendorID/DeviceID %x)\n", deviceID); GetCmdLogitechWheelNative(&commands, targetDeviceID); SendCommands(hidDevice, &commands); printf("Enabled native mode. (VendorID/DeviceID %x)\n", deviceID); CloseDevice(hidDevice); return true; } } else { if(OpenDevice(hidDevice) == kIOReturnSuccess) { CCommands commands; bool changed = false; if(targetMode == DeviceModeFull) { // Activate full 900 degree mode, as being in native mode doesn't guarantee the wheel range GetCmdLogitechWheelRange(&commands, deviceID, kGPLogitechWheelRangeMax); SendCommands(hidDevice, &commands); printf("Calibrated 900 degree wheel movement. (VendorID/DeviceID %x)\n", deviceID); changed = true; } else { // We don't know how to go back to restricted mode, but we can set the wheel back to 240 degree range GetCmdLogitechWheelRange(&commands, deviceID, kGPLogitechWheelRangeStandard); SendCommands(hidDevice, &commands); printf("Reset device to default 320 degree wheel movement. (VendorID/DeviceID %x)\n", deviceID); changed = true; } CloseDevice(hidDevice); return changed; } } return false; } //============================================================================= // GetCmdLogitechWheelNative : Activate native mode for given device ID //----------------------------------------------------------------------------- void GetCmdLogitechWheelNative(CCommands *c, const DeviceID deviceID) { // Fill the command array with null memset(c->cmds, 0, kGPCommandsDataSize); // Fill in the command value according to target deviceID switch (deviceID) { case kGPLogitechG25Native: c->cmds[0][0] = 0xf8; c->cmds[0][1] = 0x10; c->count = 1; break; case kGPLogitechG27Native: // Original commands // c->cmds[0][0] = 0xf8; // c->cmds[0][1] = 0x0a; // c->cmds[1][0] = 0xf8; // c->cmds[1][1] = 0x09; // c->cmds[1][2] = 0x04; // c->cmds[1][3] = 0x01; // c->count = 1; // https://github.com/TripleSpeeder/LTWheelConf/blob/master/wheels.c c->cmds[0][0] = 0xf8; c->cmds[0][1] = 0x0a; c->cmds[0][2] = 0x00; c->cmds[0][3] = 0x00; c->cmds[0][4] = 0x00; c->cmds[0][5] = 0x00; c->cmds[0][6] = 0x00; c->cmds[0][7] = 0x00; // Partial button mapping c->cmds[1][0] = 0xf8; c->cmds[1][1] = 0x01; c->cmds[1][2] = 0x00; c->cmds[1][3] = 0x00; c->cmds[1][4] = 0x00; c->cmds[1][5] = 0x00; c->cmds[1][6] = 0x00; c->cmds[1][7] = 0x00; // Full button mapping with clutch // c->cmds[1][0] = 0xf8; // c->cmds[1][1] = 0x09; // c->cmds[1][2] = 0x04; // c->cmds[1][3] = 0x01; // c->cmds[1][4] = 0x00; // c->cmds[1][5] = 0x00; // c->cmds[1][6] = 0x00; // c->cmds[1][7] = 0x00; c->count = 2; break; case kGPLogitechDFGTNative: c->cmds[0][0] = 0xf8; c->cmds[0][1] = 0x0a; c->cmds[1][0] = 0xf8; c->cmds[1][1] = 0x09; c->cmds[1][2] = 0x03; c->cmds[1][3] = 0x01; c->count = 2; break; case kGPLogitechDFPNative: c->cmds[0][0] = 0xf8; c->cmds[0][1] = 0x01; c->count = 1; break; default: c->count = 0; break; } } //============================================================================= // GetCmdLogitechWheelRange : Set wheel range for given device ID //----------------------------------------------------------------------------- void GetCmdLogitechWheelRange(CCommands *c, const DeviceID deviceID, int range) { // Fill the command array with null memset(c->cmds, 0, kGPCommandsDataSize); // Fill in the command value according to target deviceID switch (deviceID) { case kGPLogitechG25Native: case kGPLogitechG27Native: case kGPLogitechDFGTNative: { c->cmds[0][0] = 0xf8; c->cmds[0][1] = 0x81; c->cmds[0][2] = (range & 0x00ff); c->cmds[0][3] = (range & 0xff00) >> 8; c->cmds[0][4] = 0x00; c->cmds[0][5] = 0x00; c->cmds[0][6] = 0x00; c->cmds[0][7] = 0x00; // c->cmds[1][0] = 0xfe; // c->cmds[1][1] = 0x0d; // c->cmds[1][2] = 0 & 0x0f; // c->cmds[1][3] = 0 & 0x0f; // c->cmds[1][4] = 0 & 0xff; // c->cmds[1][5] = 0x00; // c->cmds[1][6] = 0x00; // c->cmds[1][7] = 0x00; c->count = 1; break; } case kGPLogitechDFPNative: { c->cmds[0][0] = 0xf8; c->cmds[1][0] = 0x81; c->cmds[1][1] = 0x0b; c->count = 2; // Determine the needs for range limiter int rampLeft; int rampRight; int fullRange = 0; if (range > 200) { c->cmds[0][1] = 0x03; fullRange = 900; } else { c->cmds[0][1] = 0x02; fullRange = 200; } // If target range less than full range; Apply limiter command if (range < fullRange) { rampLeft = (((fullRange - range + 1) * 2047) / fullRange); rampRight = 0xfff - rampLeft; c->cmds[1][2] = rampLeft >> 4; c->cmds[1][3] = rampRight >> 4; c->cmds[1][4] = 0xff; c->cmds[1][5] = (rampRight & 0xe) << 4 | (rampLeft & 0xe); c->cmds[1][6] = 0xff; } break; } default: c->count = 0; break; } }