Introduction
I am working on getting full support for the PlayStation 4 Controller
using the HID interface in IOKit
. The controller is connected over Bluetooth
. I can open a connection to the controller and start receiving reports, however, once I send it a report it suddenly stops.
In order to get full control over the PS4 controller (rumble, trackpad, LEDs), I've been referencing the eleccelerator.com DualShock 4 page which contains information about the reports that can/are sent between the PS4 and DS4.
Code
First, I create an IOHIDManager
which is responsible for detecting controllers.
self.hidManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
// Make sure we detect ANY type of 'game controller'
NSArray *criteria = [NSArray arrayWithObjects:
CreateCriterion(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick),
CreateCriterion(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad),
CreateCriterion(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController),
nil];
IOHIDManagerSetDeviceMatchingMultiple(self.hidManager, (__bridge CFArrayRef)criteria);
IOHIDManagerScheduleWithRunLoop(self.hidManager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
IOHIDManagerOpen(self.hidManager, kIOHIDOptionsTypeNone);
// Register callbacks
IOHIDManagerRegisterDeviceMatchingCallback(self.hidManager, ControllerConnected, (__bridge void *)self);
IOHIDManagerRegisterDeviceRemovalCallback(self.hidManager, ControllerDisconnected, (__bridge void *)self);
Then after getting a call to ControllerConnected
, I create an instance of a joystick class, which initializes the IOHIDDeviceRef
.
- (void)registerDevice:(IOHIDDeviceRef)device {
self.device = device;
// Initialize the buffer
self.receivedPacketMaxSize = 552; // When using Bluetooth
self.receivedReport = (uint8_t *)calloc(self.receivedPacketMaxSize, sizeof(uint8_t));
// Register the device report callback
IOHIDDeviceScheduleWithRunLoop(self.device, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
IOHIDDeviceRegisterInputReportCallback(self.device, self.receivedReport, self.receivedPacketMaxSize, ControllerReport, (__bridge void *)self);
self.isValid = YES;
}
Now I DO get calls to ControllerReport
, however the reports I receive are of type 0x01 which are limited in what they contain (the do not have trackpad or accelerometer data), but I want to receive the packets of type 0x11 which contain all of the data from the controller.
In order to switch which packet the controller sends, the website states This report is sent once the GET REPORT FEATURE 0x02 is received.
So I send that request:
CFIndex len = self.receivedPacketMaxSize;
IOReturn featureMode2RequestError = IOHIDDeviceGetReport(self.device, kIOHIDReportTypeFeature, 0x02, self.receivedReport, &len);
if (featureMode2RequestError != kIOReturnSuccess) {
NSLog(@"Could switch the controller to mode 2 :(");
}
At this point, ControllerReport
stops being called with new reports from the controller.
I know I'm able to send data to the controller because I'm able to send a packet that changes the LED color, or sets the rumble speed.
Question
How can I get the longer 0x11 reports from the PS4 Controller?
What I've Tried
I've tried using IOHIDDeviceGetReportWithCallback
and IOHIDDeviceGetReport
, but I always get kIOReturnUnsupported
back.
I've also found some reference to this problem inside of the source of a linux driver for the controller in the form of ... it starts sending input reports in report 17. Since report 17 is undefined ...
, however I don't know how to tell IOKit to handle report 17 (0x11).
The problem is that IOHIDDeviceGetReport kIOHIDReportTypeOutput sends output reports over the interrupt endpoint; the DS4 expects it via the control endpoint. AFAIK there isn't any way on OS X to do a output GetReport via the ctrl endpoint (short of writing a kext to override it).