Skip to content

Instantly share code, notes, and snippets.

@kylawl
Last active June 5, 2025 17:33
Show Gist options
  • Save kylawl/470e86a0ac0c3da8f598948b1930db4e to your computer and use it in GitHub Desktop.
Save kylawl/470e86a0ac0c3da8f598948b1930db4e to your computer and use it in GitHub Desktop.
DS4U - Playstation DualShock 4 Windows Driver
// Copyright Luminawesome Games Ltd. 2015
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software
// and associated documentation files (the “Software”), to deal in the Software without
// restriction, including without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
#include <new>
#include <memory.h>
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <thread>
#include <mutex>
#include <atomic>
#include "DS4U.hpp"
// http://eleccelerator.com/wiki/index.php?title=DualShock_4
// http://www.psdevwiki.com/ps4/DualShock_4
// https://gist.github.com/johndrinkwater/7708901
// https://github.com/ehd/node-ds4
// http://forums.pcsx2.net/Thread-DS4-To-XInput-Wrapper
//////////////////////////////////////////////////////////////////////////////////////////
// //
// Forward Decl //
// //
//////////////////////////////////////////////////////////////////////////////////////////
static void DS4UInitPlat(DS4UContext* ds4u);
static void DS4UClosePlat(DS4UContext* ds4u);
static void DS4WriteOutputStatePlat(uint64_t device_id, uint8_t* packet, size_t packet_length, bool is_bluetooth);
//////////////////////////////////////////////////////////////////////////////////////////
// //
// DS4UInternal //
// //
//////////////////////////////////////////////////////////////////////////////////////////
struct DS4UGamepadDevice
{
bool mIsUSBDevice;
uint32_t mPacketID;
DS4UGamepadState mGamepadState;
};
struct DS4UOuputState
{
bool mIsUSB;
uint64_t mDeviceID;
uint8_t mRed;
uint8_t mGreen;
uint8_t mBlue;
uint8_t mLeftMotor;
uint8_t mRightMotor;
};
struct DS4UContext
{
DS4UAllocFunc mAlloc;
DS4UFreeFunc mFree;
DS4UInputEventHandlerFunc mInputEventHandler;
void* mInputEventContext;
uint64_t* mDeviceIDs;
DS4UGamepadDevice* mDevices;
DS4UOuputState* mOutputStates[2];
int32_t mOutputStatesIdx;
int32_t mMaxGamepadCount;
void* mHIDInterfaceHandle;
std::atomic<int32_t> mRunning;
std::thread mOutputReportThread;
std::mutex mOutputStatesMutex;
DS4UContext();
~DS4UContext();
int32_t GetGamepadIndex (uint64_t device_id);
int32_t RegisterGamepad (uint64_t device_id);
void UnregisterGamepad (uint64_t device_id);
uint64_t GetDeviceID (int32_t gamepad_index);
void DecodeInputReport (uint8_t* report, uint64_t device_id, bool usb_report);
bool IsGamepadConnected (int32_t gamepad_index);
void StartOutputReportThread ();
void StopOutputReportThread ();
static void DecodeTouch (DS4UTouchState* dest, uint8_t* buffer);
static void SetButton (DS4UGamepadState* state, DS4UButton button, bool set);
static void SendOutputReport (DS4UContext* ds4u_internal);
};
DS4UContext::DS4UContext()
{
mAlloc = nullptr;
mFree = nullptr;
mInputEventHandler = nullptr;
mInputEventContext = nullptr;
mDevices = nullptr;
mOutputStates[0] = nullptr;
mOutputStates[1] = nullptr;
mMaxGamepadCount = 0;
mOutputStatesIdx = 0;
mHIDInterfaceHandle = nullptr;
}
DS4UContext::~DS4UContext()
{
mFree(mOutputStates[0]);
mFree(mOutputStates[1]);
mFree(mDeviceIDs);
mFree(mDevices);
}
bool DS4UContext::IsGamepadConnected(int32_t gamepad_index)
{
if( ((gamepad_index < 0) | (gamepad_index >= mMaxGamepadCount)) )
return false;
return mDeviceIDs[gamepad_index] != 0;
}
void DS4UContext::StartOutputReportThread()
{
mRunning = 1;
mOutputReportThread = std::thread(&DS4UContext::SendOutputReport, this);
}
void DS4UContext::StopOutputReportThread()
{
mRunning--;
mOutputReportThread.join();
}
uint64_t DS4UContext::GetDeviceID(int32_t gamepad_index)
{
if( ((gamepad_index < 0) | (gamepad_index >= mMaxGamepadCount)) )
return 0;
return mDeviceIDs[gamepad_index];
}
int32_t DS4UContext::GetGamepadIndex(uint64_t device_id)
{
for( int8_t i = 0; i < mMaxGamepadCount; ++i )
{
if( mDeviceIDs[i] == device_id )
{
return i;
}
}
return -1;
}
int32_t DS4UContext::RegisterGamepad(uint64_t device_id)
{
// Validate we don't already have a gamepad with this id
int32_t gamepad_index = GetGamepadIndex(device_id);
assert(gamepad_index == -1);
gamepad_index = GetGamepadIndex(0);
if( gamepad_index > -1 )
{
printf("Registering device: %llu with index: %d\n", device_id, gamepad_index);
mDeviceIDs[gamepad_index] = device_id;
const uint32_t gamepad_colors[4] =
{
DS4UGamepadColor_Gamepad0,
DS4UGamepadColor_Gamepad1,
DS4UGamepadColor_Gamepad2,
DS4UGamepadColor_Gamepad3
};
DS4USetGamepadState( this, gamepad_index, 0, 0, gamepad_colors[gamepad_index % 4] );
if( mInputEventHandler != nullptr )
{
mInputEventHandler( gamepad_index, DS4UEventID_Connected, nullptr, mInputEventContext );
}
}
else
{
printf("Exceeded device limit\n");
}
return gamepad_index;
}
void DS4UContext::UnregisterGamepad(uint64_t device_id)
{
int32_t gamepad_index = GetGamepadIndex(device_id);
if( gamepad_index > -1 )
{
mDeviceIDs[gamepad_index] = 0;
if( mInputEventHandler != nullptr )
{
mInputEventHandler( gamepad_index, DS4UEventID_Disconnected, nullptr, mInputEventContext );
}
printf("Unregistering device: %llu at index: %d\n", device_id, gamepad_index);
}
}
void DS4UContext::SetButton(DS4UGamepadState* state, DS4UButton button, bool set)
{
if( set )
{
state->mButtons = state->mButtons | uint32_t(button);
}
else
{
state->mButtons = state->mButtons & (~uint32_t(button));
}
}
void DS4UContext::SendOutputReport(DS4UContext* ds4u_internal)
{
while( ds4u_internal->mRunning > 0 )
{
int32_t read_index = 0;
{
std::lock_guard<std::mutex> output_guard(ds4u_internal->mOutputStatesMutex);
read_index = ds4u_internal->mOutputStatesIdx;
ds4u_internal->mOutputStatesIdx = (read_index + 1) % 2;
}
DS4UOuputState* output_states = ds4u_internal->mOutputStates[ds4u_internal->mOutputStatesIdx];
for( int32_t i = 0; i < ds4u_internal->mMaxGamepadCount; ++i )
{
if( output_states[i].mDeviceID == 0 )
continue;
DS4UOuputState* output_state = &output_states[i];
if( output_state->mIsUSB )
{
uint8_t packet[32] ={ 0 };
packet[0] = 0x05;
packet[1] = 0xff;
packet[4] = output_state->mRightMotor; // right_motor_strength
packet[5] = output_state->mLeftMotor; // left_motor_strength
packet[6] = output_state->mRed; // led_red_level
packet[7] = output_state->mGreen; // led_green_level
packet[8] = output_state->mBlue; // led_blue_level
packet[9] = 255; // Flash Duration On
packet[10] = 0; // Flash Duration Off
DS4WriteOutputStatePlat(output_state->mDeviceID, packet, sizeof(packet), true);
}
else
{
uint8_t packet[78] ={ 0 };
packet[0] = 0x11;
packet[1] = 0x80;
packet[3] = 0xff; // 0xf0 disables the rumble motors, 0xf3 enables them
packet[6] = output_state->mRightMotor; // right_motor_strength
packet[7] = output_state->mLeftMotor; // left_motor_strength
packet[8] = output_state->mRed; // led_red_level
packet[9] = output_state->mGreen; // led_green_level
packet[10] = output_state->mBlue; // led_blue_level
packet[11] = 255; // Flash Duration On
packet[12] = 0; // Flash Duration Off
DS4WriteOutputStatePlat(output_state->mDeviceID, packet, sizeof(packet), false);
}
}
memset(output_states, 0, sizeof(DS4UOuputState) * ds4u_internal->mMaxGamepadCount);
std::this_thread::sleep_for( std::chrono::milliseconds(34) );
}
}
void DS4UContext::DecodeTouch(DS4UTouchState* dest, uint8_t* buffer)
{
dest->mId = ( buffer[0] & 0x7f);
dest->mActive = ( buffer[0] >> 7) == 0;
dest->mX = ((buffer[2] & 0x0f) << 8 ) | ( buffer[1] );
dest->mY = ( buffer[3] << 4 ) | ((buffer[2] & 0xf0) >> 4);
}
void DS4UContext::DecodeInputReport(uint8_t* report, uint64_t device_id, bool is_usb_report)
{
int32_t gamepad_index = GetGamepadIndex(device_id);
// There can be more gamepads plugged in that we care about
if( gamepad_index == -1 )
return;
//
// Decode
//
// uint8_t controller_id = report[0];
int8_t left_stick_x = report[1] - 128;
int8_t left_stick_y = report[2] - 128;
int8_t right_stick_x = report[3] - 128;
int8_t right_stick_y = report[4] - 128;
uint8_t dpad = report[5] & 15;
bool dpad_up = dpad == 0 || dpad == 1 || dpad == 7;
bool dpad_right = dpad == 1 || dpad == 2 || dpad == 3;
bool dpad_down = dpad == 3 || dpad == 4 || dpad == 5;
bool dpad_left = dpad == 5 || dpad == 6 || dpad == 7;
bool square = (report[5] & 16) != 0;
bool cross = (report[5] & 32) != 0;
bool circle = (report[5] & 64) != 0;
bool triangle = (report[5] & 128) != 0;
bool l1 = (report[6] & 0x01) != 0;
bool l2 = (report[6] & 0x04) != 0;
bool r1 = (report[6] & 0x02) != 0;
bool r2 = (report[6] & 0x08) != 0;
bool l3 = (report[6] & 0x40) != 0;
bool r3 = (report[6] & 0x80) != 0;
bool share = (report[6] & 0x10) != 0;
bool options = (report[6] & 0x20) != 0;
bool tpad_button = (report[7] & 2) != 0;
bool ps_button = (report[7] & 1) != 0;
// uint8_t event_id = (report[7] >> 2); // Incriments per report
uint8_t l2_analog = report[8];
uint8_t r2_analog = report[9];
// Seems to be a timestamp. A common increment value between two reports is 188
// (at full rate the report period is 1.25ms).
// This timestamp is used by the PS4 to process acceleration and gyroscope data.
// uint8_t unknown10 = report[10]; // seems to count downwards, non-random pattern
// uint8_t unknown11 = report[11]; // seems to count upwards by 3, but by 2 when [10] underflows
uint8_t battery_level = report[12];
// int16_t motionY = (*(int16_t*)(&report[13]));
// int16_t motionX = -(*(int16_t*)(&report[15]));
// int16_t motionZ = -(*(int16_t*)(&report[17]));
// int16_t orientationRoll = -(*(int16_t*)(&report[19]));
// int16_t orientationYaw = (*(int16_t*)(&report[21]));
// int16_t orientationPitch = (*(int16_t*)(&report[23]));
// Unknown (seems to be always 0x00)
// uint8_t unknown25 = report[25];
// uint8_t unknown26 = report[26];
// uint8_t unknown27 = report[27];
// uint8_t unknown28 = report[28];
// uint8_t unknown29 = report[29];
// bool unknown_flag30 = (report[30] & 0xf0) != 0;
// bool phones_connected = (report[30] & 0x40) != 0;
// bool mic_connected = (report[30] & 0x20) != 0;
// bool usb_connected = (report[30] & 0x10) != 0;
// uint8_t battery_level2 = (report[30] & 0x0f) != 0; // Why are there 2 battery levels?
// uint8_t unknown31 = report[31]; // Unknown (seems to be always 0x00)
// uint8_t unknown32 = report[32]; // Unknown (seems to be always 0x00)
// uint8_t unknown33 = report[33]; // number of trackpad packets (0x00 to 0x04)
// uint8_t packet_counter0 = report[34]; // packet counter
uint32_t touch0 = *((uint32_t*)(&report[35])); // 4 Bytes
uint32_t touch1 = *((uint32_t*)(&report[39])); // 4 Bytes
// This stuff is no good
// uint8_t packet_counter1 = report[43]; // packet counter
// DS4UTouch prev_touch0 = DS4UDecodeTouch( report + 44 ); // Reads 4 bytes
// DS4UTouch prev_touch1 = DS4UDecodeTouch( report + 48 ); // Reads 4
//
// Fill out input state
//
DS4UGamepadState gamepad_state = {0};
SetButton( &gamepad_state, DS4UButton_DPadLeft , dpad_left );
SetButton( &gamepad_state, DS4UButton_DPadDown , dpad_down );
SetButton( &gamepad_state, DS4UButton_DPadRight , dpad_right );
SetButton( &gamepad_state, DS4UButton_DPadUp , dpad_up );
SetButton( &gamepad_state, DS4UButton_Square , square );
SetButton( &gamepad_state, DS4UButton_Cross , cross );
SetButton( &gamepad_state, DS4UButton_Circle , circle );
SetButton( &gamepad_state, DS4UButton_Triangle , triangle );
SetButton( &gamepad_state, DS4UButton_L1 , l1 );
SetButton( &gamepad_state, DS4UButton_L2 , l2 );
SetButton( &gamepad_state, DS4UButton_R1 , r1 );
SetButton( &gamepad_state, DS4UButton_R2 , r2 );
SetButton( &gamepad_state, DS4UButton_L3 , l3 );
SetButton( &gamepad_state, DS4UButton_R3 , r3 );
SetButton( &gamepad_state, DS4UButton_Share , share );
SetButton( &gamepad_state, DS4UButton_Options , options );
SetButton( &gamepad_state, DS4UButton_TouchPad , tpad_button );
SetButton( &gamepad_state, DS4UButton_Playstation , ps_button );
gamepad_state.mStickLeftX = left_stick_x;
gamepad_state.mStickLeftY = left_stick_y;
gamepad_state.mStickRightX = right_stick_x;
gamepad_state.mStickRightY = right_stick_y;
gamepad_state.mTriggerL2 = l2_analog;
gamepad_state.mTriggerR2 = r2_analog;
gamepad_state.mBatteryLevel = battery_level;
DecodeTouch( &gamepad_state.mTouchStates[0], (uint8_t*)&touch0 );
DecodeTouch( &gamepad_state.mTouchStates[1], (uint8_t*)&touch1 );
DS4UGamepadDevice* device = &mDevices[gamepad_index];
device->mIsUSBDevice = is_usb_report;
// Set the previous packet index so that the mem-compare is correct
gamepad_state.mPacketID = device->mPacketID;
device->mPacketID += 1;
if( memcmp(&gamepad_state, &device->mGamepadState, sizeof(DS4UGamepadState)) != 0 )
{
gamepad_state.mPacketID = device->mPacketID;
device->mGamepadState = gamepad_state;
if( mInputEventHandler != nullptr )
{
mInputEventHandler( gamepad_index, DS4UEventID_GamepadState, &gamepad_state, mInputEventContext );
}
}
#if defined(DEBUG_DECODING)
printf("\033[2J");
printf("ControllerID: %d\n", controller_id);
printf("LeftStickX: %d\n", left_stick_x);
printf("LeftStickY: %d\n", left_stick_y);
printf("RightStickX: %d\n", right_stick_x);
printf("RightStickY: %d\n", right_stick_y);
printf("DPad: %s %s %s %s\n", dpad_up ? "up" : "--", dpad_right ? "right" : "-----", dpad_down ? "down" : "----", dpad_left ? "left" : "----" );
printf("FaceButtons: %s %s %s %s\n", cross ? "cross" : "-----", circle ? "circle" : "------", square ? "square" : "------", triangle ? "triangle" : "--------" );
printf("LButtons: %s %s %s %s %s %s\n", l1 ? "l1" : "--", l2 ? "l2" : "--", l3 ? "l3" : "--", r1 ? "r1" : "--", r2 ? "r2" : "--", r3 ? "r3" : "--" );
printf("Share: %s\n", share ? "true" : "false");
printf("Options: %s\n", options ? "true" : "false");
printf("TrackPadButton: %s\n", tpad_button ? "true" : "false");
printf("PSButton: %s\n", ps_button ? "true" : "false");
printf("EventID: %d\n", event_id);
printf("L2Analog: %d\n", l2_analog);
printf("R2Analog: %d\n", r2_analog);
printf("Unknown10: %d\n", unknown10);
printf("Unknown11: %d\n", unknown11);
printf("BatteryLevel: %d\n", battery_level);
printf("AccelX: %d\n", motionX);
printf("AccelY: %d\n", motionY);
printf("AccelZ: %d\n", motionZ);
printf("GyroRoll: %d\n", orientationRoll);
printf("GyroYaw: %d\n", orientationYaw);
printf("GyroPitch: %d\n", orientationPitch);
printf("Unknown25: %d\n", unknown25);
printf("Unknown26: %d\n", unknown26);
printf("Unknown27: %d\n", unknown27);
printf("Unknown28: %d\n", unknown28);
printf("Unknown29: %d\n", unknown29);
printf("unknown_flag30: %s\n", unknown_flag30 ? "true" : "false");
printf("HeadphonesConnected: %s\n", phones_connected ? "true" : "false");
printf("MicrophoneConnected: %s\n", mic_connected ? "true" : "false");
printf("UsbConnected: %s\n", usb_connected ? "true" : "false");
printf("BatteryLevel2: %d\n", battery_level2);
printf("Unknown31: %d\n", unknown31);
printf("Unknown32: %d\n", unknown32);
printf("Unknown33: %d\n", unknown33);
printf("packet_counter0: %d\n", packet_counter0);
printf("TrackPadTouch0ID: %d\n", touch0.mId);
printf("TrackPadTouch0Active: %s\n", touch0.mActive ? "true" : "false" );
printf("TrackPadTouch0X: %d\n", touch0.mX);
printf("TrackPadTouch0Y: %d\n", touch0.mY);
printf("TrackPadTouch1ID: %d\n", touch1.mId);
printf("TrackPadTouch1Active: %s\n", touch1.mActive ? "true" : "false" );
printf("TrackPadTouch1X: %d\n", touch1.mX);
printf("TrackPadTouch1Y: %d\n", touch1.mY);
#endif
}
//////////////////////////////////////////////////////////////////////////////////////////
// //
// API Implementation //
// //
//////////////////////////////////////////////////////////////////////////////////////////
DS4UContext* DS4UInit(int32_t max_gamepad_count, DS4UInputEventHandlerFunc input_event_handler, void* context)
{
max_gamepad_count = max_gamepad_count >= 0 ? max_gamepad_count : 0;
DS4UContext* ds4u = new (malloc(sizeof(DS4UContext))) DS4UContext();
ds4u->mAlloc = &malloc;
ds4u->mFree = &free;
ds4u->mMaxGamepadCount = max_gamepad_count;
ds4u->mInputEventHandler = input_event_handler;
ds4u->mInputEventContext = context;
size_t input_states_size = sizeof(DS4UGamepadDevice) * max_gamepad_count;
size_t device_ids_size = sizeof(uint64_t) * max_gamepad_count;
size_t output_states_size = sizeof(DS4UOuputState) * max_gamepad_count;
ds4u->mDevices = (DS4UGamepadDevice*) ds4u->mAlloc( input_states_size );
ds4u->mDeviceIDs = (uint64_t*) ds4u->mAlloc( device_ids_size );
ds4u->mOutputStates[0] = (DS4UOuputState*) ds4u->mAlloc( output_states_size );
ds4u->mOutputStates[1] = (DS4UOuputState*) ds4u->mAlloc( output_states_size );
memset( ds4u->mDevices, 0, input_states_size );
memset( ds4u->mDeviceIDs, 0, device_ids_size );
memset( ds4u->mOutputStates[0], 0, output_states_size );
memset( ds4u->mOutputStates[1], 0, output_states_size );
DS4UInitPlat(ds4u);
ds4u->StartOutputReportThread();
return ds4u;
}
void DS4UClose(DS4UContext* ds4u)
{
ds4u->StopOutputReportThread();
// We don't want to dispatch any messages that might be generated during shutdown
ds4u->mInputEventHandler = nullptr;
DS4UClosePlat(ds4u);
DS4UFreeFunc ds4u_free = ds4u->mFree;
ds4u->~DS4UContext();
ds4u_free(ds4u);
ds4u = nullptr;
}
bool DS4UIsGamepadConnected(DS4UContext* ds4u, int32_t gamepad_index)
{
return ds4u->IsGamepadConnected( gamepad_index );
}
void DS4UGetGamepadState(DS4UContext* ds4u, int32_t gamepad_index, DS4UGamepadState& out_state)
{
if( ds4u->IsGamepadConnected(gamepad_index) )
{
out_state = ds4u->mDevices[gamepad_index].mGamepadState;
}
else
{
out_state = DS4UGamepadState { 0 };
}
}
void DS4USetGamepadState(DS4UContext* ds4u, int32_t gamepad_index, uint8_t motor_speed_left, uint8_t motor_speed_right, uint32_t lightbar_rgb)
{
std::lock_guard<std::mutex> output_mutex( ds4u->mOutputStatesMutex );
uint64_t device_id = ds4u->GetDeviceID(gamepad_index);
if( device_id == 0 )
return;
DS4UOuputState* output_states = ds4u->mOutputStates[ds4u->mOutputStatesIdx];
DS4UGamepadDevice* device = &ds4u->mDevices[gamepad_index];
output_states->mIsUSB = device->mIsUSBDevice;
output_states->mDeviceID = device_id;
output_states->mRightMotor = motor_speed_right;
output_states->mLeftMotor = motor_speed_left;
output_states->mRed = uint8_t(lightbar_rgb >> 16 );
output_states->mGreen = uint8_t(lightbar_rgb >> 8 );
output_states->mBlue = uint8_t(lightbar_rgb );
}
//////////////////////////////////////////////////////////////////////////////////////////
// //
// Platform Implementations //
// //
//////////////////////////////////////////////////////////////////////////////////////////
#define HID_VENDOR_ID_SONY uint16_t(0x054c)
#define HID_PRODUCT_ID_DS4 uint16_t(0x05c4)
//////////////////////////////////////////////////////////////////////////////////////////
// //
// Windows //
// //
//////////////////////////////////////////////////////////////////////////////////////////
#if defined(WIN32) || defined(_WIN32)
#include <windows.h>
// #include <winuser.h>
#include <hidsdi.h>
#include <winusb.h>
#include <Strsafe.h>
// Size of raw input message, we can safely reject any messages with a different size
#define RAW_INPUT_BUFF_SIZE 640
#define RAW_INPUT_BT_MESSAGE_SIZE 579
#define RAW_INPUT_USB_MESSAGE_SIZE 96
bool DS4UIsDS4(HANDLE hDevice)
{
RID_DEVICE_INFO info = { 0 };
UINT info_size = sizeof(RID_DEVICE_INFO);
if( SUCCEEDED(GetRawInputDeviceInfo(hDevice, RIDI_DEVICEINFO, &info, &info_size)) )
{
return (info.hid.dwVendorId == HID_VENDOR_ID_SONY) & (info.hid.dwProductId == HID_PRODUCT_ID_DS4);
}
return false;
}
void DS4WriteOutputStatePlat( uint64_t device_id, uint8_t* packet, size_t packet_length, bool is_usb )
{
TCHAR device_name[512];
UINT dwSize = 512;
GetRawInputDeviceInfo( (HANDLE)device_id, RIDI_DEVICENAME, &device_name, &dwSize);
HANDLE file_handle = CreateFile( device_name, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr );
HRESULT result = E_FAIL;
if( is_usb )
{
result = WriteFile( file_handle, packet, (DWORD)packet_length, nullptr, nullptr );
}
else
{
result = HidD_SetOutputReport( file_handle, packet, UINT(packet_length) );
}
CloseHandle( file_handle );
}
LRESULT DS4UWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
DS4UContext* ds4u = (DS4UContext*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
if( uMsg == WM_INPUT )
{
// Get the raw input data's size
UINT raw_input_size = 0;
GetRawInputData( (HRAWINPUT)lParam, RID_INPUT, nullptr, &raw_input_size, sizeof(RAWINPUTHEADER) );
if( (raw_input_size > RAW_INPUT_BUFF_SIZE) )
{
return 0;
}
// Get the actual raw input data data
uint8_t raw_input_buffer[RAW_INPUT_BUFF_SIZE];
RAWINPUT* raw_input = (RAWINPUT*)raw_input_buffer;
GetRawInputData( (HRAWINPUT)lParam, RID_INPUT, raw_input, &raw_input_size, sizeof(RAWINPUTHEADER) );
if( (DS4UIsDS4(raw_input->header.hDevice) == false) )
{
return 0;
}
uint8_t* report_start = raw_input->data.hid.bRawData;
if( (raw_input_size == RAW_INPUT_BT_MESSAGE_SIZE) )
{
if( report_start[0] != 0x11 )
{
return 0;
}
report_start += 2; // in blutooth mode, jump forward 2 bytes to find the report
}
// We found a Dual Shock 4!
ds4u->DecodeInputReport( report_start, uint64_t(raw_input->header.hDevice), raw_input_size != RAW_INPUT_BT_MESSAGE_SIZE );
return 0;
}
else if( uMsg == WM_INPUT_DEVICE_CHANGE )
{
HANDLE hDevice = (HANDLE)lParam;
if( wParam == GIDC_ARRIVAL && DS4UIsDS4(hDevice) )
{
// GetRawInputData()
ds4u->RegisterGamepad((uint64_t)hDevice);
}
else if( wParam == GIDC_REMOVAL )
{
ds4u->UnregisterGamepad((uint64_t)hDevice);
}
return 0;
}
return DefWindowProc( hwnd, uMsg, wParam, lParam );
}
void DS4UInitPlat(DS4UContext* ds4u)
{
//
// Setup Window Class
//
WNDCLASSEX wcx;
memset( &wcx, 0, sizeof(WNDCLASSEX) );
wcx.cbSize = sizeof(WNDCLASSEX); // size of structure
wcx.lpfnWndProc = &DS4UWindowProc; // points to window procedure
wcx.hInstance = GetModuleHandle(nullptr); // handle to instance
wcx.lpszClassName = TEXT("DS4UMessageHandlerWindowClass"); // name of window class
RegisterClassEx( &wcx );
// Create the window
HWND hWnd = CreateWindowEx(0,
TEXT("DS4UMessageHandlerWindowClass"),
TEXT("DS4UMessageHandlerWindow"),
0, 0, 0, 0, 0, HWND_MESSAGE, nullptr, nullptr, nullptr);
SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)ds4u);
ds4u->mHIDInterfaceHandle = hWnd;
// Register rawinput device
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms645565%28v=vs.85%29.aspx
//
// RIDEV_INPUTSINK - Input occurred while the application was not in the foreground. The application must call DefWindowProc so the system can perform the cleanup
// RIDEV_NOHOTKEYS - Disables windows key. Not documented but works. See http://the-witness.net/news/2012/12/finding-and-fixing-a-five-second-stall/
//
RAWINPUTDEVICE device;
device.usUsagePage = HID_USAGE_PAGE_GENERIC;
device.usUsage = HID_USAGE_GENERIC_GAMEPAD;
device.dwFlags = RIDEV_INPUTSINK | RIDEV_DEVNOTIFY;// | RIDEV_NOLEGACY;
device.hwndTarget = hWnd;
RegisterRawInputDevices( &device, 1, sizeof(RAWINPUTDEVICE) );
}
void ErrorExit(LPTSTR lpszFunction)
{
// Retrieve the system error message for the last-error code
LPVOID lpMsgBuf;
LPVOID lpDisplayBuf;
DWORD dw = GetLastError();
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
dw,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &lpMsgBuf,
0, NULL );
// Display the error message and exit the process
lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT,
(lstrlen((LPCTSTR)lpMsgBuf) + lstrlen((LPCTSTR)lpszFunction) + 40) * sizeof(TCHAR));
StringCchPrintf((LPTSTR)lpDisplayBuf,
LocalSize(lpDisplayBuf) / sizeof(TCHAR),
TEXT("%s failed with error %d: %s"),
lpszFunction, dw, lpMsgBuf);
MessageBox(NULL, (LPCTSTR)lpDisplayBuf, TEXT("Error"), MB_OK);
LocalFree(lpMsgBuf);
LocalFree(lpDisplayBuf);
// ExitProcess(dw);
}
void DS4UClosePlat(DS4UContext* ds4u)
{
HWND hWnd = (HWND)ds4u->mHIDInterfaceHandle;
DestroyWindow( hWnd );
UnregisterClass( TEXT("DS4UMessageHandlerWindowClass"), GetModuleHandle(nullptr) );
}
#endif // Windows
//////////////////////////////////////////////////////////////////////////////////////////
// //
// OSX //
// //
//////////////////////////////////////////////////////////////////////////////////////////
#if defined(__APPLE__)
#include <CoreFoundation/CFBase.h>
#include <CoreFoundation/CFNumber.h>
#include <CoreFoundation/CFDictionary.h>
#include <IOKit/hid/IOHIDManager.h>
#include <IOKit/hid/IOHIDKeys.h>
// this will be called when the HID Manager matches a new (hot plugged) HID device
static void Handle_DeviceMatchingCallback(
void * inContext, // context from IOHIDManagerRegisterDeviceMatchingCallback
IOReturn inResult, // the result of the matching operation
void * inSender, // the IOHIDManagerRef for the new device
IOHIDDeviceRef inIOHIDDeviceRef // the new HID device
)
{
ds4u->RegisterGamepad((uint64_t)inIOHIDDeviceRef);
}
// this will be called when a HID device is removed (unplugged)
static void Handle_RemovalCallback(
void * inContext, // context from IOHIDManagerRegisterDeviceMatchingCallback
IOReturn inResult, // the result of the removing operation
void * inSender, // the IOHIDManagerRef for the device being removed
IOHIDDeviceRef inIOHIDDeviceRef) // the removed HID device
{
ds4u->UnregisterGamepad((uint64_t)inIOHIDDeviceRef);
}
static void Handle_IOHIDDeviceIOHIDReportCallback(
void * inContext, // context from IOHIDDeviceRegisterInputReportCallback
IOReturn inResult, // completion result for the input report operation
void * inSender, // IOHIDDeviceRef of the device this report is from
IOHIDReportType inType, // the report type
uint32_t inReportID, // the report ID
uint8_t * inReport, // pointer to the report data
CFIndex inReportLength) // the actual size of the input report
{
ds4u->DecodeInputReport(inReport, (uint64_t)inSender);
}
void DS4WriteOutputStatePlat(uint64_t device_id, uint8_t* packet, size_t packet_length)
{
IOHIDDeviceSetReport((IOHIDDeviceRef)device_id, kIOHIDReportTypeOutput, 0, packet, packet_length);
}
void DS4UInitPlat()
{
IOHIDManagerRef hid_manager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
ds4u->mHIDInterfaceHandle = hid_manager;
const CFStringRef keys[]
{
CFStringCreateWithCString(kCFAllocatorDefault, kIOHIDVendorIDKey, kCFStringEncodingASCII),
CFStringCreateWithCString(kCFAllocatorDefault, kIOHIDProductIDKey, kCFStringEncodingASCII),
CFStringCreateWithCString(kCFAllocatorDefault, kIOHIDPrimaryUsageKey, kCFStringEncodingASCII),
CFStringCreateWithCString(kCFAllocatorDefault, kIOHIDPrimaryUsagePageKey, kCFStringEncodingASCII),
};
UInt32 vendor_id = HID_VENDOR_ID_SONY;
UInt32 product_id = HID_PRODUCT_ID_DS4;
UInt32 usage_id = 0x00000005; // GamePad
UInt32 usage_page = 0x00000001; // Generic
const CFNumberRef values[]
{
CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &vendor_id),
CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &product_id),
CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage_id),
CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage_page)
};
CFDictionaryRef device_match_dictionary = CFDictionaryCreate(kCFAllocatorDefault, (const void**)keys, (const void**)values, 4, nullptr, nullptr);
IOHIDManagerRegisterDeviceMatchingCallback (hid_manager, &Handle_DeviceMatchingCallback, nullptr);
IOHIDManagerRegisterDeviceRemovalCallback (hid_manager, &Handle_RemovalCallback, nullptr);
IOHIDManagerSetDeviceMatching (hid_manager, device_match_dictionary);
IOHIDManagerScheduleWithRunLoop (hid_manager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
IOHIDManagerOpen (hid_manager, kIOHIDOptionsTypeNone);
IOHIDManagerRegisterInputReportCallback (hid_manager, Handle_IOHIDDeviceIOHIDReportCallback, nullptr);
// Release Resources
CFRelease( device_match_dictionary );
for( CFNumberRef number : values )
{
CFRelease( number );
}
}
void DS4UClosePlat()
{
IOHIDManagerRef hid_manager = (IOHIDManagerRef)ds4u->mHIDInterfaceHandle;
IOHIDManagerRegisterInputReportCallback (hid_manager, nullptr, nullptr);
IOHIDManagerClose (hid_manager, kIOHIDOptionsTypeNone);
IOHIDManagerUnscheduleFromRunLoop (hid_manager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
IOHIDManagerRegisterDeviceMatchingCallback (hid_manager, nullptr, nullptr);
IOHIDManagerRegisterDeviceRemovalCallback (hid_manager, nullptr, nullptr);
CFRelease(hid_manager);
}
#endif // OSX
// Copyright Luminawesome Games Ltd. 2015
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software
// and associated documentation files (the “Software”), to deal in the Software without
// restriction, including without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
#pragma once
#include <cstddef>
#include <cstdint>
enum DS4UButton
{
DS4UButton_None = 0,
DS4UButton_Square = 1 << 0,
DS4UButton_Cross = 1 << 1,
DS4UButton_Circle = 1 << 2,
DS4UButton_Triangle = 1 << 3,
DS4UButton_L1 = 1 << 4,
DS4UButton_R1 = 1 << 5,
DS4UButton_L2 = 1 << 6,
DS4UButton_R2 = 1 << 7,
DS4UButton_Options = 1 << 8,
DS4UButton_Share = 1 << 9,
DS4UButton_L3 = 1 << 10,
DS4UButton_R3 = 1 << 11,
DS4UButton_Playstation = 1 << 12,
DS4UButton_TouchPad = 1 << 13,
DS4UButton_DPadLeft = 1 << 14,
DS4UButton_DPadDown = 1 << 15,
DS4UButton_DPadRight = 1 << 16,
DS4UButton_DPadUp = 1 << 17,
};
enum DS4UEventID
{
DS4UEventID_Connected,
DS4UEventID_Disconnected,
DS4UEventID_GamepadState
};
enum DS4UGamepadColor
{
DS4UGamepadColor_Gamepad0 = 0x000000ff,
DS4UGamepadColor_Gamepad1 = 0x00ff0000,
DS4UGamepadColor_Gamepad2 = 0x0000ff00,
DS4UGamepadColor_Gamepad3 = 0x00ff69b4,
};
struct DS4UTouchState
{
uint8_t mId;
bool mActive;
uint16_t mX;
uint16_t mY;
};
struct DS4UGamepadState
{
uint32_t mButtons;
int8_t mStickLeftX;
int8_t mStickLeftY;
int8_t mStickRightX;
int8_t mStickRightY;
uint8_t mTriggerL2;
uint8_t mTriggerR2;
uint8_t mBatteryLevel;
DS4UTouchState mTouchStates[2];
uint32_t mPacketID;
};
struct DS4UContext;
typedef void* (*DS4UAllocFunc) (size_t size);
typedef void (*DS4UFreeFunc) (void* ptr);
typedef void (*DS4UInputEventHandlerFunc) (int32_t gamepad_index, DS4UEventID event_id, const DS4UGamepadState* gamepad_state, void* context);
extern "C" DS4UContext* DS4UInit (int32_t max_gamepad_count, DS4UInputEventHandlerFunc input_event_handler, void* context);
extern "C" void DS4UClose (DS4UContext* ds4u);
extern "C" bool DS4UIsGamepadConnected (DS4UContext* ds4u, int32_t gamepad_index);
extern "C" void DS4UGetGamepadState (DS4UContext* ds4u, int32_t gamepad_index, DS4UGamepadState& out_state);
extern "C" void DS4USetGamepadState (DS4UContext* ds4u, int32_t gamepad_index, uint8_t motor_speed_left, uint8_t motor_speed_right, uint32_t lightbar_rgb);
//////////////////////////////////////////////////////////////////////////////////////////
// //
// Inline //
// //
//////////////////////////////////////////////////////////////////////////////////////////
// Copyright Luminawesome Games Ltd. 2015
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software
// and associated documentation files (the “Software”), to deal in the Software without
// restriction, including without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
#include <iostream>
#include <stdio.h>
#if defined(WIN32)
#include <windows.h>
#elif defined(__APPLE__)
#include <CoreFoundation/CFRunLoop.h>
#endif
#include "DS4U.hpp"
void HandleInputEvent(int32_t gamepad_index, DS4UEventID event_id, const DS4UGamepadState* input_state, void* context)
{
// printf("-------------------------------\n");
// printf("Controller : %d\n", input_state->mIndex);
// printf("LeftStickX : %d\n", input_state->mStickLeftX);
// printf("LeftStickY : %d\n", input_state->mStickLeftY);
// printf("RightStickX : %d\n", input_state->mStickRightX);
// printf("RightStickY : %d\n", input_state->mStickRightY);
// printf("TriggerL2 : %d\n", input_state->mTriggerL2);
// printf("TriggerR2 : %d\n", input_state->mTriggerR2);
// printf("TrackPadTouch0ID : %d\n", input_state->mTouchStates[0].mId);
// printf("TrackPadTouch0Active : %s\n", input_state->mTouchStates[0].mActive ? "true" : "false" );
// printf("TrackPadTouch0X : %d\n", input_state->mTouchStates[0].mX);
// printf("TrackPadTouch0Y : %d\n", input_state->mTouchStates[0].mY);
// printf("TrackPadTouch1ID : %d\n", input_state->mTouchStates[1].mId);
// printf("TrackPadTouch1Active : %s\n", input_state->mTouchStates[1].mActive ? "true" : "false" );
// printf("TrackPadTouch1X : %d\n", input_state->mTouchStates[1].mX);
// printf("TrackPadTouch1Y : %d\n", input_state->mTouchStates[1].mY);
// Only send a controller state every so often because it's damn slow
static int32_t counter = 0;
if( (counter++ % 30) == 0)
{
DS4USetGamepadState(
gamepad_index,
input_state->mTriggerL2,
input_state->mTriggerR2,
input_state->mStickRightX,
input_state->mStickRightY,
input_state->mStickRightX);
}
}
int main(int argc, const char* args[])
{
DS4UInit(2, &HandleInputEvent, nullptr);
#if defined(WIN32)
//
// Message Loop
//
MSG msg = {0};
while( WM_QUIT != msg.message )
{
if( PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) )
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
#elif defined(__APPLE__)
CFRunLoopRun();
#endif
DS4UClose();
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment