JoyShockLibrary
Read DualSense, DualShock 4, JoyCon, and Pro Controller input on PC -- compiled for Windows, but code should work on other platforms.
Install / Use
/learn @JibbSmart/JoyShockLibraryREADME
JoyShockLibrary
The Sony PlayStation's DualShock 4, DualSense, Nintendo Switch Joy-Cons (used in pairs), and Nintendo Switch Pro Controller have much in common. They have many of the features expected of modern game controllers. They also have an incredibly versatile and underutilised input that their biggest rival (Microsoft's Xbox One controller) doesn't have: the gyro.
My goal with JoyShockLibrary is to enable game developers to support DS4, DS, Joy-Cons, and Pro Controllers natively in PC games. I've compiled the library for Windows, but it uses platform-agnostic tools, and my hope is that other developers would be able to get it working on other platforms (such as Linux or Mac) without too much trouble.
Contents
- Releases
- Reference
- Known and Perceived Issues
- Backwards Compatibility
- Credits
- Helpful Resources
- License
Releases
The latest version of JoyShockLibrary can always be found here. Included is a 64-bit dll and a 32-bit dll, both for Windows, and JoyShockLibrary.h and JoyShockLibrary.cs for using the dll in C/C++ and C# (Unity), respectively. The .cs file isn't up to date with the latest features, but should provide a starting point for your use.
Reference
JoyShockLibrary.h has everything you need to use the library, but here's a breakdown of everything in there.
Structs
struct JOY_SHOCK_STATE - This struct contains the state for all the sticks, buttons, and triggers on the controller. If you're just using JoyShockLibrary to be able to use Joy-Cons, Pro Controllers, DualSenses, and DualShock 4s similarly to how you'd use other devices, this has everything you need to know.
- int buttons contains the states of all the controller's buttons with the following masks:
0x00001- d-padup0x00002- d-paddown0x00004- d-padleft0x00008- d-padright0x00010-+on Nintendo devices,Optionson DS40x00020--on Nintendo devices,Shareon DS40x00040-left-stick clickon Nintendo devices,L3on DS40x00080-right-stick clickon Nintendo devices,R3on DS40x00100-Lon Nintendo devices,L1on DS40x00200-Ron Nintendo devices,R1on DS40x00400-ZLon Nintendo devices,L2on DS40x00800-ZRon Nintendo devices,R2on DS40x01000- the South face-button:Bon Nintendo devices,⨉on DS40x02000- the East face-button:Aon Nintendo devices,○on DS40x04000- the West face-button:Yon Nintendo devices,□on DS40x08000- the North face-button:Xon Nintendo devices,△on DS40x10000-Homeon Nintendo devices,PSon DS40x20000-Captureon Nintendo devices,touchpad clickon DS4,Createon DS50x40000-SLon Nintendo Joy-Cons,Micon DS50x80000-SRon Nintendo Joy-Cons
- float lTrigger - how far has the left trigger been pressed? This will be 1 or 0 on Nintendo devices, which don't have analog triggers
- float rTrigger - how far has the right trigger been pressed? This will be 1 or 0 on Nintendo devices, which don't have analog triggers
- float stickLX, stickLY - left-stick X axis and Y axis, respectively, from -1 to 1
- float stickRX, stickRX - right-stick X axis and Y axis, respectively, from -1 to 1
struct IMU_STATE - Each supported device contains an IMU which has a 3-axis accelerometer and a 3-axis gyroscope. IMU_STATE is where you find that info.
- float accelX, accelY, accelZ - accelerometer X axis, Y axis, and Z axis, respectively, in g (g-force).
- float gyroX, gyroY, gyroZ - gyroscope angular velocity X, Y, and Z, respectively, in dps (degrees per second), when correctly calibrated.
struct MOTION_STATE - The MOTION_STATE reports the orientation of the device as calculated using a sensor fusion solution to combine gyro and accelerometer data.
- float quatW, quatX, quatY, quatZ - a quaternion representing the orientation of the device.
- float accelX, accelY, accelZ - local acceleration after accounting for and removing the effect of gravity.
- float gravX, gravY, gravZ - local gravity direction.
Tip
Quaternions are useful if you want to try and represent the device's 3D orientation in-game, with one major limitation: "yaw drift". Small errors will accumulate over time so that the quaternion orientation no longer matches the controller's real orientation. The gravity direction is used to counter this error in the roll and pitch axes, but there's nothing for countering the error in the yaw axis.
Quaternions are not recommended for mouse-like aiming or cursor control. The gravity correction applied to the quaternion is not useful for these cases, and only introduces more error. For these, it's much better to use the calibrated gyro angular velocities. To make sure you account for every motion sensor update, either set a callback where you can respond to new motion input as it comes in, or poll the controller using JslGetAndFlushAccumulatedGyro to get a good average angular velocity since you last called this function.
Functions
All these functions should be thread-safe, and none of them should cause any harm if given the wrong or out-of-date deviceId. So even if a device gets disconnected between calling "JslStillConnected" and "JslGetSimpleState", the latter will just report all the sticks, triggers, and buttons untouched, and you'll detect the disconnection next time you call "JslStillConnected".
int JslConnectDevices() - Register any connected devices. Returns the number of devices connected, which is helpful for getting the handles for those devices with the next function. As of version 3, this will not interrupt current connections. So you can call this function at any time to check for new connections. To only call it when necessary, only do this when your OS notifies you of a new connection (eg WM_DEVICECHANGE on Windows).
int JslGetConnectedDeviceHandles(int* deviceHandleArray, int size) - Fills the array deviceHandleArray of size size with the handles for all connected devices, up to the length of the array. Use the length returned by JslConnectDevices to make sure you've got all connected devices' handles.
void JslDisconnectAndDisposeAll() - Disconnect devices, no longer polling them for input.
bool JslStillConnected(int deviceId) - Returns true if the controller with the given id is still connected.
JOY_SHOCK_STATE JslGetSimpleState(int deviceId) - Get the latest button + trigger + stick state for the controller with the given id.
IMU_STATE JslGetIMUState(int deviceId) - Get the latest accelerometer + gyroscope state for the controller with the given id.
MOTION_STATE JslGetMotionState(int deviceId) - Get the latest motion state for the controller with the given id.
TOUCH_STATE JslGetTouchState(int deviceId, bool previous = false) - Get the latest or previous touchpad state for the controller with the given id. Only DualShock 4 and DualSense support this.
bool JslGetTouchpadDimension(int deviceId, int &sizeX, int &sizeY) - Get the dimension of the touchpad. This is useful to abstract the resolution of different touchpads.
int JslGetButtons(int deviceId) - Get the latest button state for the controller with the given id. If you want more than just the buttons, it's more efficient to use JslGetSimpleState.
float JslGetLeftX/JslGetLeftY/JslGetRightX/JslGetRightY(int deviceId) - Get the latest stick state for the controller with the given id. If you want more than just a single stick axis, it's more efficient to use JslGetSimpleState.
float JslGetLeftTrigger/JslGetRightTrigger(int deviceId) - Get the latest trigger state for the controller with the given id. If you want more than just a single trigger, it's more efficient to use JslGetSimpleState.
float JslGetGyroX/JslGetGyroY/JslGetGyroZ(int deviceId) - Get the latest angular velocity for a given gyroscope axis. If you want more than just a single gyroscope axis velocity, it's more efficient to use JslGetIMUState.
void JslGetAndFlushAccumulatedGyro(int deviceId, float& gyroX, float& gyroY, float& gyroZ) - Get gyro input that has accumulated since you last called this function on the device with the given id. This is an average angular velocity. Highly recommended if you're polling controllers instead of using the callbacks (JslSetCallback) to get new gyro data the moment it is received, to give a better account of how the controller has been turned since you last polled it. If no new motion data has come in since you last called it, it will repeat the same values, to avoid stuttery motion input when your game's framerate is higher than the controller's reporting rate (common with Switch controllers on high end PCs, for example).
void JslSetGyroSpace(int deviceId, int gyroSpace) - Players have different ideas about what the front of the controller is, or what the controller's rest position should be. This can make it hard to decide whether to use the controller's yaw or roll axis for turning in-game or moving a cursor horizontally. To address this, there are 3 popular "spaces" for gyro controls:
- 0 = Local Space - just pick yaw or roll and potentially give the player the option to change it. Or add them together. Up to you. It's not doing anything, so this is the default.
- 1 = World Space - use the gravity calculation to figure out which way is "
