2023-09-09 17:14:40 -07:00
// JoyShockLibrary.cpp : Defines the exported functions for the DLL application.
//
# include "JoyShockLibrary.h"
# include <bitset>
# include "hidapi.h"
# include <chrono>
# include <thread>
# include <shared_mutex>
# include <string>
# include <unordered_map>
# include <atomic>
# include "GamepadMotion.hpp"
# include "JoyShock.cpp"
# include "InputHelpers.cpp"
std : : shared_timed_mutex _callbackLock ;
std : : shared_timed_mutex _connectedLock ;
void ( * _pollCallback ) ( int , JOY_SHOCK_STATE , JOY_SHOCK_STATE , IMU_STATE , IMU_STATE , float ) = nullptr ;
void ( * _pollTouchCallback ) ( int , TOUCH_STATE , TOUCH_STATE , float ) = nullptr ;
void ( * _connectCallback ) ( int ) = nullptr ;
void ( * _disconnectCallback ) ( int , bool ) = nullptr ;
std : : unordered_map < int , JoyShock * > _joyshocks ;
std : : unordered_map < std : : string , JoyShock * > _byPath ;
std : : mutex _pathHandleLock ;
std : : unordered_map < std : : string , int > _pathHandle ;
// https://stackoverflow.com/questions/41206861/atomic-increment-and-return-counter
static std : : atomic < int > _joyshockHandleCounter ;
static int GetUniqueHandle ( const std : : string & path )
{
_pathHandleLock . lock ( ) ;
auto iter = _pathHandle . find ( path ) ;
if ( iter ! = _pathHandle . end ( ) )
{
_pathHandleLock . unlock ( ) ;
return iter - > second ;
}
const int handle = _joyshockHandleCounter + + ;
_pathHandle . emplace ( path , handle ) ;
_pathHandleLock . unlock ( ) ;
return handle ;
}
// https://stackoverflow.com/questions/25144887/map-unordered-map-prefer-find-and-then-at-or-try-at-catch-out-of-range
// not thread-safe -- because you probably want to do something with the object you get out of it, I've left locking to the caller
static JoyShock * GetJoyShockFromHandle ( int handle ) {
auto iter = _joyshocks . find ( handle ) ;
if ( iter ! = _joyshocks . end ( ) )
{
return iter - > second ;
// iter is item pair in the map. The value will be accessible as `iter->second`.
}
return nullptr ;
}
void pollIndividualLoop ( JoyShock * jc ) {
if ( ! jc - > handle ) { return ; }
hid_set_nonblocking ( jc - > handle , 0 ) ;
//hid_set_nonblocking(jc->handle, 1); // temporary, to see if it helps. this means we'll have a crazy spin
int numTimeOuts = 0 ;
int numNoIMU = 0 ;
bool hasIMU = false ;
bool lockedThread = false ;
int noIMULimit ;
switch ( jc - > controller_type )
{
case ControllerType : : s_ds4 :
noIMULimit = 250 ;
break ;
case ControllerType : : s_ds :
noIMULimit = 250 ;
break ;
case ControllerType : : n_switch :
default :
noIMULimit = 67 ;
}
float wakeupTimer = 0.0f ;
while ( ! jc - > cancel_thread ) {
// get input:
unsigned char buf [ 64 ] ;
memset ( buf , 0 , 64 ) ;
// 10 seconds of no signal means forget this controller
int reuseCounter = jc - > reuse_counter ;
int res = hid_read_timeout ( jc - > handle , buf , 64 , 1000 ) ;
if ( res = = - 1 )
{
// disconnected!
printf ( " Controller %d disconnected \n " , jc - > intHandle ) ;
_connectedLock . lock ( ) ;
lockedThread = true ;
const bool gettingReused = jc - > reuse_counter ! = reuseCounter ;
jc - > delete_on_finish = true ;
if ( gettingReused )
{
jc - > remove_on_finish = false ;
jc - > delete_on_finish = false ;
lockedThread = false ;
_connectedLock . unlock ( ) ;
}
break ;
}
if ( res = = 0 )
{
numTimeOuts + + ;
if ( numTimeOuts > = 10 )
{
printf ( " Controller %d timed out \n " , jc - > intHandle ) ;
// just make sure we get this thing deleted before someone else tries to start a new connection
_connectedLock . lock ( ) ;
lockedThread = true ;
const bool gettingReused = jc - > reuse_counter ! = reuseCounter ;
jc - > delete_on_finish = true ;
if ( gettingReused )
{
jc - > remove_on_finish = false ;
jc - > delete_on_finish = false ;
lockedThread = false ;
_connectedLock . unlock ( ) ;
}
break ;
}
else
{
// try wake up the controller with the appropriate message
if ( jc - > controller_type ! = ControllerType : : n_switch )
{
// TODO
}
else
{
if ( jc - > is_usb )
{
printf ( " Attempting to re-initialise controller %d \n " , jc - > intHandle ) ;
if ( jc - > init_usb ( ) )
{
numTimeOuts = 0 ;
}
}
else
{
printf ( " Attempting to re-initialise controller %d \n " , jc - > intHandle ) ;
if ( jc - > init_bt ( ) )
{
numTimeOuts = 0 ;
}
}
}
}
}
else
{
numTimeOuts = 0 ;
// we want to be able to do these check-and-calls without fear of interruption by another thread. there could be many threads (as many as connected controllers),
// and the callback could be time-consuming (up to the user), so we use a readers-writer-lock.
if ( handle_input ( jc , buf , 64 , hasIMU ) ) { // but the user won't necessarily have a callback at all, so we'll skip the lock altogether in that case
// accumulate gyro
IMU_STATE imuState = jc - > get_transformed_imu_state ( jc - > imu_state ) ;
jc - > push_cumulative_gyro ( imuState . gyroX , imuState . gyroY , imuState . gyroZ ) ;
if ( _pollCallback ! = nullptr | | _pollTouchCallback ! = nullptr )
{
std : : shared_lock < std : : shared_timed_mutex > lock ( _callbackLock ) ;
if ( _pollCallback ! = nullptr ) {
_pollCallback ( jc - > intHandle , jc - > simple_state , jc - > last_simple_state , imuState , jc - > get_transformed_imu_state ( jc - > last_imu_state ) , jc - > delta_time ) ;
}
// touchpad will have its own callback so that it doesn't change the existing api
if ( jc - > controller_type ! = ControllerType : : n_switch & & _pollTouchCallback ! = nullptr ) {
_pollTouchCallback ( jc - > intHandle , jc - > touch_state , jc - > last_touch_state , jc - > delta_time ) ;
}
}
// count how many have no IMU result. We want to periodically attempt to enable IMU if it's not present
if ( ! hasIMU )
{
numNoIMU + + ;
if ( numNoIMU = = noIMULimit )
{
memset ( buf , 0 , 64 ) ;
jc - > enable_IMU ( buf , 64 ) ;
numNoIMU = 0 ;
}
}
else
{
numNoIMU = 0 ;
}
// dualshock 4 bluetooth might need waking up
if ( jc - > controller_type = = ControllerType : : s_ds4 & & ! jc - > is_usb )
{
wakeupTimer + = jc - > delta_time ;
if ( wakeupTimer > 30.0f )
{
jc - > init_ds4_bt ( ) ;
wakeupTimer = 0.0f ;
}
}
}
}
}
if ( jc - > cancel_thread )
{
printf ( " \t ending cancelled thread \n " ) ;
}
else
{
printf ( " \t timing out thread \n " ) ;
}
// remove
if ( jc - > remove_on_finish )
{
printf ( " \t \t removing jc \n " ) ;
if ( ! lockedThread )
{
_connectedLock . lock ( ) ;
}
_joyshocks . erase ( jc - > intHandle ) ;
_byPath . erase ( jc - > path ) ;
if ( ! lockedThread )
{
_connectedLock . unlock ( ) ;
}
}
const int intHandle = jc - > intHandle ;
// disconnect this device
if ( jc - > delete_on_finish )
{
printf ( " \t \t deleting jc \n " ) ;
delete jc ;
}
if ( lockedThread )
{
_connectedLock . unlock ( ) ;
}
// notify that we disconnected this device, and say whether or not it was a timeout (if not a timeout, then an explicit disconnect)
{
std : : shared_lock < std : : shared_timed_mutex > lock ( _callbackLock ) ;
if ( _disconnectCallback ! = nullptr )
{
_disconnectCallback ( intHandle , numTimeOuts > = 10 ) ;
}
}
}
int JslConnectDevices ( )
{
// for writing to console:
//freopen("CONOUT$", "w", stdout);
// most of the joycon and pro controller stuff here is thanks to mfosse's vjoy feeder
// Enumerate and print the HID devices on the system
struct hid_device_info * devs , * cur_dev ;
std : : vector < int > createdIds ;
_connectedLock . lock ( ) ;
int res = hid_init ( ) ;
devs = hid_enumerate ( 0x0 , 0x0 ) ;
cur_dev = devs ;
while ( cur_dev ) {
bool isSupported = false ;
bool isSwitch = false ;
switch ( cur_dev - > vendor_id )
{
case JOYCON_VENDOR :
isSupported = cur_dev - > product_id = = JOYCON_L_BT | |
cur_dev - > product_id = = JOYCON_R_BT | |
cur_dev - > product_id = = PRO_CONTROLLER | |
cur_dev - > product_id = = JOYCON_CHARGING_GRIP ;
isSwitch = true ;
break ;
case DS_VENDOR :
isSupported = cur_dev - > product_id = = DS4_USB | |
cur_dev - > product_id = = DS4_USB_V2 | |
cur_dev - > product_id = = DS4_USB_DONGLE | |
cur_dev - > product_id = = DS4_BT | |
2023-09-16 15:41:07 -07:00
cur_dev - > product_id = = DS_USB | |
cur_dev - > product_id = = DS_USB_V2 ;
2023-09-09 17:14:40 -07:00
break ;
case BROOK_DS4_VENDOR :
isSupported = cur_dev - > product_id = = BROOK_DS4_USB ;
break ;
default :
break ;
}
if ( ! isSupported )
{
cur_dev = cur_dev - > next ;
continue ;
}
const std : : string path = std : : string ( cur_dev - > path ) ;
auto iter = _byPath . find ( path ) ;
JoyShock * currentJc = nullptr ;
bool isSameController = false ;
if ( iter ! = _byPath . end ( ) )
{
currentJc = iter - > second ;
isSameController = isSwitch = = ( currentJc - > controller_type = = ControllerType : : n_switch ) ;
}
printf ( " path: %s \n " , cur_dev - > path ) ;
hid_device * handle = hid_open_path ( cur_dev - > path ) ;
if ( handle ! = nullptr )
{
printf ( " \t opened new handle \n " ) ;
if ( isSameController )
{
printf ( " \t ending old thread and joining \n " ) ;
// we'll get a new thread, so we need to delete the old one, but we need to actually wait for it, I think, because it'll be affected by init and all that...
// but we can't wait for it! That could deadlock if it happens to be about to disconnect or time out!
std : : thread * thread = currentJc - > thread ;
currentJc - > delete_on_finish = false ;
currentJc - > remove_on_finish = false ;
currentJc - > cancel_thread = true ;
currentJc - > reuse_counter + + ;
// finishing thread may be about to hit a lock
_connectedLock . unlock ( ) ;
thread - > join ( ) ;
_connectedLock . lock ( ) ;
delete thread ;
currentJc - > thread = nullptr ;
// don't immediately cancel the next thread:
currentJc - > cancel_thread = false ;
currentJc - > delete_on_finish = false ;
currentJc - > remove_on_finish = true ;
printf ( " \t initialising with new handle \n " ) ;
// keep calibration stuff, but reset other stuff just in case it's actually a new controller
currentJc - > init ( cur_dev , handle , GetUniqueHandle ( path ) , path ) ;
currentJc = nullptr ;
}
else
{
printf ( " \t creating new JoyShock \n " ) ;
JoyShock * jc = new JoyShock ( cur_dev , handle , GetUniqueHandle ( path ) , path ) ;
_joyshocks . emplace ( jc - > intHandle , jc ) ;
_byPath . emplace ( path , jc ) ;
createdIds . push_back ( jc - > intHandle ) ;
}
if ( currentJc ! = nullptr )
{
printf ( " \t deinitialising old controller \n " ) ;
// it's been replaced! get rid of it
if ( currentJc - > controller_type = = ControllerType : : s_ds4 ) {
if ( currentJc - > is_usb ) {
currentJc - > deinit_ds4_usb ( ) ;
}
else {
currentJc - > deinit_ds4_bt ( ) ;
}
}
else if ( currentJc - > controller_type = = ControllerType : : s_ds ) {
}
else if ( currentJc - > is_usb ) {
currentJc - > deinit_usb ( ) ;
}
printf ( " \t ending old thread \n " ) ;
std : : thread * thread = currentJc - > thread ;
currentJc - > delete_on_finish = true ;
currentJc - > remove_on_finish = false ;
currentJc - > cancel_thread = true ;
thread - > detach ( ) ;
delete thread ;
currentJc - > thread = nullptr ;
}
}
cur_dev = cur_dev - > next ;
}
hid_free_enumeration ( devs ) ;
// init joyshocks:
for ( std : : pair < int , JoyShock * > pair : _joyshocks )
{
JoyShock * jc = pair . second ;
if ( jc - > initialised )
{
continue ;
}
if ( jc - > controller_type = = ControllerType : : s_ds4 ) {
if ( ! jc - > is_usb ) {
jc - > init_ds4_bt ( ) ;
}
else {
jc - > init_ds4_usb ( ) ;
}
} // dualsense
else if ( jc - > controller_type = = ControllerType : : s_ds )
{
jc - > initialised = true ;
} // charging grip
else if ( jc - > is_usb ) {
//printf("USB\n");
jc - > init_usb ( ) ;
}
else {
//printf("BT\n");
jc - > init_bt ( ) ;
}
// all get time now for polling
jc - > last_polled = std : : chrono : : steady_clock : : now ( ) ;
jc - > delta_time = 0.0 ;
jc - > deviceNumber = 0 ; // left
}
unsigned char buf [ 64 ] ;
// set lights:
//printf("setting LEDs...\n");
int switchIndex = 1 ;
int dualSenseIndex = 1 ;
for ( std : : pair < int , JoyShock * > pair : _joyshocks )
{
JoyShock * jc = pair . second ;
// restore colours if we have them set for this controller
switch ( jc - > controller_type )
{
case ControllerType : : s_ds4 :
jc - > set_ds4_rumble_light ( 0 , 0 , jc - > led_r , jc - > led_g , jc - > led_b ) ;
break ;
case ControllerType : : s_ds :
jc - > set_ds5_rumble_light ( 0 , 0 , jc - > led_r , jc - > led_g , jc - > led_b , dualSenseIndex + + ) ;
break ;
case ControllerType : : n_switch :
jc - > player_number = switchIndex + + ;
memset ( buf , 0x00 , 0x40 ) ;
buf [ 0 ] = ( unsigned char ) jc - > player_number ;
jc - > send_subcommand ( 0x01 , 0x30 , buf , 1 ) ;
break ;
}
// threads for polling
if ( jc - > thread = = nullptr )
{
printf ( " \t starting new thread \n " ) ;
jc - > thread = new std : : thread ( pollIndividualLoop , jc ) ;
}
}
const int totalDevices = ( int ) _joyshocks . size ( ) ;
_connectedLock . unlock ( ) ;
// notify that we created the new object (now that we're not in a lock that might prevent reading data)
{
std : : shared_lock < std : : shared_timed_mutex > lock ( _callbackLock ) ;
if ( _connectCallback ! = nullptr )
{
for ( int newConnectionHandle : createdIds )
{
_connectCallback ( newConnectionHandle ) ;
}
}
}
return totalDevices ;
}
int JslGetConnectedDeviceHandles ( int * deviceHandleArray , int size )
{
int i = 0 ;
_connectedLock . lock_shared ( ) ;
for ( std : : pair < int , JoyShock * > pair : _joyshocks )
{
if ( i > = size ) {
break ;
}
deviceHandleArray [ i ] = pair . first ;
i + + ;
}
_connectedLock . unlock_shared ( ) ;
return i ; // return num actually found
}
void JslDisconnectAndDisposeAll ( )
{
// no more callback
JslSetCallback ( nullptr ) ;
JslSetTouchCallback ( nullptr ) ;
_connectedLock . lock ( ) ;
for ( std : : pair < int , JoyShock * > pair : _joyshocks )
{
JoyShock * jc = pair . second ;
if ( jc - > controller_type = = ControllerType : : s_ds4 ) {
if ( jc - > is_usb ) {
jc - > deinit_ds4_usb ( ) ;
}
else {
jc - > deinit_ds4_bt ( ) ;
}
}
else if ( jc - > controller_type = = ControllerType : : s_ds ) {
} // TODO: Charging grip? bluetooth?
else if ( jc - > is_usb ) {
jc - > deinit_usb ( ) ;
}
// cleanup
std : : thread * thread = jc - > thread ;
jc - > delete_on_finish = true ;
jc - > remove_on_finish = false ;
jc - > cancel_thread = true ;
thread - > detach ( ) ;
delete thread ;
}
_joyshocks . clear ( ) ;
_byPath . clear ( ) ;
_connectedLock . unlock ( ) ;
// Finalize the hidapi library
int res = hid_exit ( ) ;
}
bool JslStillConnected ( int deviceId )
{
std : : shared_lock < std : : shared_timed_mutex > lock ( _connectedLock ) ;
return GetJoyShockFromHandle ( deviceId ) ! = nullptr ;
}
// get buttons as bits in the following order, using North South East West to name face buttons to avoid ambiguity between Xbox and Nintendo layouts:
// 0x00001: up
// 0x00002: down
// 0x00004: left
// 0x00008: right
// 0x00010: plus
// 0x00020: minus
// 0x00040: left stick click
// 0x00080: right stick click
// 0x00100: L
// 0x00200: R
// ZL and ZR are reported as analogue inputs (GetLeftTrigger, GetRightTrigger), because DS4 and XBox controllers use analogue triggers, but we also have them as raw buttons
// 0x00400: ZL
// 0x00800: ZR
// 0x01000: S
// 0x02000: E
// 0x04000: W
// 0x08000: N
// 0x10000: home / PS
// 0x20000: capture / touchpad-click
// 0x40000: SL
// 0x80000: SR
// if you want the whole state, this is the best way to do it
JOY_SHOCK_STATE JslGetSimpleState ( int deviceId )
{
std : : shared_lock < std : : shared_timed_mutex > lock ( _connectedLock ) ;
JoyShock * jc = GetJoyShockFromHandle ( deviceId ) ;
if ( jc ! = nullptr ) {
return jc - > simple_state ;
}
return { } ;
}
IMU_STATE JslGetIMUState ( int deviceId )
{
std : : shared_lock < std : : shared_timed_mutex > lock ( _connectedLock ) ;
JoyShock * jc = GetJoyShockFromHandle ( deviceId ) ;
if ( jc ! = nullptr ) {
return jc - > get_transformed_imu_state ( jc - > imu_state ) ;
}
return { } ;
}
MOTION_STATE JslGetMotionState ( int deviceId )
{
std : : shared_lock < std : : shared_timed_mutex > lock ( _connectedLock ) ;
JoyShock * jc = GetJoyShockFromHandle ( deviceId ) ;
if ( jc ! = nullptr ) {
return jc - > get_motion_state ( ) ;
}
return { } ;
}
TOUCH_STATE JslGetTouchState ( int deviceId , bool previous )
{
std : : shared_lock < std : : shared_timed_mutex > lock ( _connectedLock ) ;
JoyShock * jc = GetJoyShockFromHandle ( deviceId ) ;
if ( jc ! = nullptr ) {
return previous ? jc - > last_touch_state : jc - > touch_state ;
}
return { } ;
}
bool JslGetTouchpadDimension ( int deviceId , int & sizeX , int & sizeY )
{
std : : shared_lock < std : : shared_timed_mutex > lock ( _connectedLock ) ;
// I am assuming a single touchpad (or all touchpads are the same dimension)?
JoyShock * jc = GetJoyShockFromHandle ( deviceId ) ;
if ( jc ! = nullptr )
{
switch ( jc - > controller_type )
{
case ControllerType : : s_ds4 :
case ControllerType : : s_ds :
sizeX = 1920 ;
sizeY = 943 ;
break ;
default :
sizeX = 0 ;
sizeY = 0 ;
break ;
}
return true ;
}
return false ;
}
int JslGetButtons ( int deviceId )
{
std : : shared_lock < std : : shared_timed_mutex > lock ( _connectedLock ) ;
JoyShock * jc = GetJoyShockFromHandle ( deviceId ) ;
if ( jc ! = nullptr ) {
return jc - > simple_state . buttons ;
}
return 0 ;
}
// get thumbsticks
float JslGetLeftX ( int deviceId )
{
std : : shared_lock < std : : shared_timed_mutex > lock ( _connectedLock ) ;
JoyShock * jc = GetJoyShockFromHandle ( deviceId ) ;
if ( jc ! = nullptr ) {
return jc - > simple_state . stickLX ;
}
return 0.0f ;
}
float JslGetLeftY ( int deviceId )
{
std : : shared_lock < std : : shared_timed_mutex > lock ( _connectedLock ) ;
JoyShock * jc = GetJoyShockFromHandle ( deviceId ) ;
if ( jc ! = nullptr ) {
return jc - > simple_state . stickLY ;
}
return 0.0f ;
}
float JslGetRightX ( int deviceId )
{
std : : shared_lock < std : : shared_timed_mutex > lock ( _connectedLock ) ;
JoyShock * jc = GetJoyShockFromHandle ( deviceId ) ;
if ( jc ! = nullptr ) {
return jc - > simple_state . stickRX ;
}
return 0.0f ;
}
float JslGetRightY ( int deviceId )
{
std : : shared_lock < std : : shared_timed_mutex > lock ( _connectedLock ) ;
JoyShock * jc = GetJoyShockFromHandle ( deviceId ) ;
if ( jc ! = nullptr ) {
return jc - > simple_state . stickRY ;
}
return 0.0f ;
}
// get triggers. Switch controllers don't have analogue triggers, but will report 0.0 or 1.0 so they can be used in the same way as others
float JslGetLeftTrigger ( int deviceId )
{
std : : shared_lock < std : : shared_timed_mutex > lock ( _connectedLock ) ;
JoyShock * jc = GetJoyShockFromHandle ( deviceId ) ;
if ( jc ! = nullptr ) {
return jc - > simple_state . lTrigger ;
}
return 0.0f ;
}
float JslGetRightTrigger ( int deviceId )
{
std : : shared_lock < std : : shared_timed_mutex > lock ( _connectedLock ) ;
JoyShock * jc = GetJoyShockFromHandle ( deviceId ) ;
if ( jc ! = nullptr ) {
return jc - > simple_state . rTrigger ;
}
return 0.0f ;
}
// get gyro
float JslGetGyroX ( int deviceId )
{
std : : shared_lock < std : : shared_timed_mutex > lock ( _connectedLock ) ;
JoyShock * jc = GetJoyShockFromHandle ( deviceId ) ;
if ( jc ! = nullptr ) {
return jc - > get_transformed_imu_state ( jc - > imu_state ) . gyroX ;
}
return 0.0f ;
}
float JslGetGyroY ( int deviceId )
{
std : : shared_lock < std : : shared_timed_mutex > lock ( _connectedLock ) ;
JoyShock * jc = GetJoyShockFromHandle ( deviceId ) ;
if ( jc ! = nullptr ) {
return jc - > get_transformed_imu_state ( jc - > imu_state ) . gyroY ;
}
return 0.0f ;
}
float JslGetGyroZ ( int deviceId )
{
std : : shared_lock < std : : shared_timed_mutex > lock ( _connectedLock ) ;
JoyShock * jc = GetJoyShockFromHandle ( deviceId ) ;
if ( jc ! = nullptr ) {
return jc - > get_transformed_imu_state ( jc - > imu_state ) . gyroZ ;
}
return 0.0f ;
}
void JslGetAndFlushAccumulatedGyro ( int deviceId , float & gyroX , float & gyroY , float & gyroZ )
{
std : : shared_lock < std : : shared_timed_mutex > lock ( _connectedLock ) ;
JoyShock * jc = GetJoyShockFromHandle ( deviceId ) ;
if ( jc ! = nullptr ) {
jc - > get_and_flush_cumulative_gyro ( gyroX , gyroY , gyroZ ) ;
return ;
}
gyroX = gyroY = gyroZ = 0.f ;
}
void JslSetGyroSpace ( int deviceId , int gyroSpace )
{
if ( gyroSpace < 0 ) {
gyroSpace = 0 ;
}
if ( gyroSpace > 2 ) {
gyroSpace = 2 ;
}
std : : shared_lock < std : : shared_timed_mutex > lock ( _connectedLock ) ;
JoyShock * jc = GetJoyShockFromHandle ( deviceId ) ;
if ( jc ! = nullptr ) {
jc - > modifying_lock . lock ( ) ;
jc - > gyroSpace = gyroSpace ;
jc - > modifying_lock . unlock ( ) ;
}
}
// get accelerometor
float JslGetAccelX ( int deviceId )
{
std : : shared_lock < std : : shared_timed_mutex > lock ( _connectedLock ) ;
JoyShock * jc = GetJoyShockFromHandle ( deviceId ) ;
if ( jc ! = nullptr ) {
return jc - > imu_state . accelX ;
}
return 0.0f ;
}
float JslGetAccelY ( int deviceId )
{
std : : shared_lock < std : : shared_timed_mutex > lock ( _connectedLock ) ;
JoyShock * jc = GetJoyShockFromHandle ( deviceId ) ;
if ( jc ! = nullptr ) {
return jc - > imu_state . accelY ;
}
return 0.0f ;
}
float JslGetAccelZ ( int deviceId )
{
std : : shared_lock < std : : shared_timed_mutex > lock ( _connectedLock ) ;
JoyShock * jc = GetJoyShockFromHandle ( deviceId ) ;
if ( jc ! = nullptr ) {
return jc - > imu_state . accelZ ;
}
return 0.0f ;
}
// get touchpad
int JslGetTouchId ( int deviceId , bool secondTouch )
{
std : : shared_lock < std : : shared_timed_mutex > lock ( _connectedLock ) ;
JoyShock * jc = GetJoyShockFromHandle ( deviceId ) ;
if ( jc ! = nullptr ) {
if ( ! secondTouch ) {
return jc - > touch_state . t0Id ;
}
else {
return jc - > touch_state . t1Id ;
}
}
return false ;
}
bool JslGetTouchDown ( int deviceId , bool secondTouch )
{
std : : shared_lock < std : : shared_timed_mutex > lock ( _connectedLock ) ;
JoyShock * jc = GetJoyShockFromHandle ( deviceId ) ;
if ( jc ! = nullptr ) {
if ( ! secondTouch ) {
return jc - > touch_state . t0Down ;
}
else {
return jc - > touch_state . t1Down ;
}
}
return false ;
}
float JslGetTouchX ( int deviceId , bool secondTouch )
{
std : : shared_lock < std : : shared_timed_mutex > lock ( _connectedLock ) ;
JoyShock * jc = GetJoyShockFromHandle ( deviceId ) ;
if ( jc ! = nullptr ) {
if ( ! secondTouch ) {
return jc - > touch_state . t0X ;
}
else {
return jc - > touch_state . t1X ;
}
}
return 0.0f ;
}
float JslGetTouchY ( int deviceId , bool secondTouch )
{
std : : shared_lock < std : : shared_timed_mutex > lock ( _connectedLock ) ;
JoyShock * jc = GetJoyShockFromHandle ( deviceId ) ;
if ( jc ! = nullptr ) {
if ( ! secondTouch ) {
return jc - > touch_state . t0Y ;
}
else {
return jc - > touch_state . t1Y ;
}
}
return 0.0f ;
}
// analog parameters have different resolutions depending on device
float JslGetStickStep ( int deviceId )
{
std : : shared_lock < std : : shared_timed_mutex > lock ( _connectedLock ) ;
JoyShock * jc = GetJoyShockFromHandle ( deviceId ) ;
if ( jc ! = nullptr ) {
if ( jc - > controller_type ! = ControllerType : : n_switch ) {
return 1.0f / 128.0 ;
}
else {
if ( jc - > left_right = = 2 ) // right joycon has no calibration for left stick
{
return 1.0f / ( jc - > stick_cal_x_r [ 2 ] - jc - > stick_cal_x_r [ 1 ] ) ;
}
else {
return 1.0f / ( jc - > stick_cal_x_l [ 2 ] - jc - > stick_cal_x_l [ 1 ] ) ;
}
}
}
return 0.0f ;
}
float JslGetTriggerStep ( int deviceId )
{
std : : shared_lock < std : : shared_timed_mutex > lock ( _connectedLock ) ;
JoyShock * jc = GetJoyShockFromHandle ( deviceId ) ;
if ( jc ! = nullptr ) {
return jc - > controller_type ! = ControllerType : : n_switch ? 1 / 256.0f : 1.0f ;
}
return 1.0f ;
}
float JslGetPollRate ( int deviceId )
{
std : : shared_lock < std : : shared_timed_mutex > lock ( _connectedLock ) ;
JoyShock * jc = GetJoyShockFromHandle ( deviceId ) ;
if ( jc ! = nullptr ) {
return jc - > controller_type ! = ControllerType : : n_switch ? 250.0f : 66.6667f ;
}
return 0.0f ;
}
float JslGetTimeSinceLastUpdate ( int deviceId )
{
std : : shared_lock < std : : shared_timed_mutex > lock ( _connectedLock ) ;
JoyShock * jc = GetJoyShockFromHandle ( deviceId ) ;
if ( jc ! = nullptr ) {
auto time_now = std : : chrono : : steady_clock : : now ( ) ;
return ( float ) ( std : : chrono : : duration_cast < std : : chrono : : microseconds > ( time_now - jc - > last_polled ) . count ( ) / 1000000.0 ) ;
}
return 0.0f ;
}
// calibration
void JslResetContinuousCalibration ( int deviceId ) {
std : : shared_lock < std : : shared_timed_mutex > lock ( _connectedLock ) ;
JoyShock * jc = GetJoyShockFromHandle ( deviceId ) ;
if ( jc ! = nullptr ) {
jc - > reset_continuous_calibration ( ) ;
}
}
void JslStartContinuousCalibration ( int deviceId ) {
std : : shared_lock < std : : shared_timed_mutex > lock ( _connectedLock ) ;
JoyShock * jc = GetJoyShockFromHandle ( deviceId ) ;
if ( jc ! = nullptr ) {
jc - > use_continuous_calibration = true ;
jc - > cue_motion_reset = true ;
}
}
void JslPauseContinuousCalibration ( int deviceId ) {
std : : shared_lock < std : : shared_timed_mutex > lock ( _connectedLock ) ;
JoyShock * jc = GetJoyShockFromHandle ( deviceId ) ;
if ( jc ! = nullptr ) {
jc - > use_continuous_calibration = false ;
}
}
void JslSetAutomaticCalibration ( int deviceId , bool enabled ) {
std : : shared_lock < std : : shared_timed_mutex > lock ( _connectedLock ) ;
JoyShock * jc = GetJoyShockFromHandle ( deviceId ) ;
if ( jc ! = nullptr ) {
jc - > modifying_lock . lock ( ) ;
jc - > motion . SetCalibrationMode ( enabled ? GamepadMotionHelpers : : CalibrationMode : : SensorFusion | GamepadMotionHelpers : : CalibrationMode : : Stillness : GamepadMotionHelpers : : CalibrationMode : : Manual ) ;
jc - > modifying_lock . unlock ( ) ;
}
}
void JslGetCalibrationOffset ( int deviceId , float & xOffset , float & yOffset , float & zOffset ) {
std : : shared_lock < std : : shared_timed_mutex > lock ( _connectedLock ) ;
JoyShock * jc = GetJoyShockFromHandle ( deviceId ) ;
if ( jc ! = nullptr ) {
// not technically modifying, but also not a simple getter
jc - > modifying_lock . lock ( ) ;
jc - > motion . GetCalibrationOffset ( xOffset , yOffset , zOffset ) ;
jc - > modifying_lock . unlock ( ) ;
}
}
void JslSetCalibrationOffset ( int deviceId , float xOffset , float yOffset , float zOffset ) {
std : : shared_lock < std : : shared_timed_mutex > lock ( _connectedLock ) ;
JoyShock * jc = GetJoyShockFromHandle ( deviceId ) ;
if ( jc ! = nullptr ) {
jc - > modifying_lock . lock ( ) ;
jc - > motion . SetCalibrationOffset ( xOffset , yOffset , zOffset , 1 ) ;
jc - > modifying_lock . unlock ( ) ;
}
}
JSL_AUTO_CALIBRATION JslGetAutoCalibrationStatus ( int deviceId ) {
std : : shared_lock < std : : shared_timed_mutex > lock ( _connectedLock ) ;
JoyShock * jc = GetJoyShockFromHandle ( deviceId ) ;
if ( jc ! = nullptr ) {
JSL_AUTO_CALIBRATION calibration ;
calibration . autoCalibrationEnabled = jc - > motion . GetCalibrationMode ( ) ! = GamepadMotionHelpers : : CalibrationMode : : Manual ;
calibration . confidence = jc - > motion . GetAutoCalibrationConfidence ( ) ;
calibration . isSteady = jc - > motion . GetAutoCalibrationIsSteady ( ) ;
return calibration ;
}
return { } ;
}
// this function will get called for each input event from each controller
void JslSetCallback ( void ( * callback ) ( int , JOY_SHOCK_STATE , JOY_SHOCK_STATE , IMU_STATE , IMU_STATE , float ) ) {
// exclusive lock
_callbackLock . lock ( ) ;
_pollCallback = callback ;
_callbackLock . unlock ( ) ;
}
// this function will get called for each input event, even if touch data didn't update
void JslSetTouchCallback ( void ( * callback ) ( int , TOUCH_STATE , TOUCH_STATE , float ) ) {
_callbackLock . lock ( ) ;
_pollTouchCallback = callback ;
_callbackLock . unlock ( ) ;
}
// this function will get called for each device when it is newly connected
void JslSetConnectCallback ( void ( * callback ) ( int ) ) {
// exclusive lock
_callbackLock . lock ( ) ;
_connectCallback = callback ;
_callbackLock . unlock ( ) ;
}
// this function will get called for each device when it is disconnected (and whether it was a timeout (true))
void JslSetDisconnectCallback ( void ( * callback ) ( int , bool ) ) {
// exclusive lock
_callbackLock . lock ( ) ;
_disconnectCallback = callback ;
_callbackLock . unlock ( ) ;
}
// super-getter for reading a whole lot of state at once
JSL_SETTINGS JslGetControllerInfoAndSettings ( int deviceId )
{
std : : shared_lock < std : : shared_timed_mutex > lock ( _connectedLock ) ;
JoyShock * jc = GetJoyShockFromHandle ( deviceId ) ;
if ( jc ! = nullptr ) {
JSL_SETTINGS settings ;
settings . gyroSpace = jc - > gyroSpace ;
settings . playerNumber = jc - > player_number ;
settings . splitType = jc - > left_right ;
settings . isConnected = true ;
settings . isCalibrating = jc - > use_continuous_calibration ;
settings . autoCalibrationEnabled = jc - > motion . GetCalibrationMode ( ) ! = GamepadMotionHelpers : : CalibrationMode : : Manual ;
switch ( jc - > controller_type )
{
case ControllerType : : s_ds4 :
settings . controllerType = JS_TYPE_DS4 ;
break ;
case ControllerType : : s_ds :
settings . controllerType = JS_TYPE_DS ;
break ;
default :
case ControllerType : : n_switch :
settings . controllerType = jc - > left_right ;
settings . colour = jc - > body_colour ;
break ;
}
if ( jc - > controller_type ! = ControllerType : : n_switch )
{
// get led colour
settings . colour = ( int ) ( jc - > led_b ) | ( ( int ) ( jc - > led_g ) < < 8 ) | ( ( int ) ( jc - > led_r ) < < 16 ) ;
}
return settings ;
}
return { } ;
}
// what split type of controller is this?
int JslGetControllerType ( int deviceId )
{
std : : shared_lock < std : : shared_timed_mutex > lock ( _connectedLock ) ;
JoyShock * jc = GetJoyShockFromHandle ( deviceId ) ;
if ( jc ! = nullptr ) {
switch ( jc - > controller_type )
{
case ControllerType : : s_ds4 :
return JS_TYPE_DS4 ;
case ControllerType : : s_ds :
return JS_TYPE_DS ;
default :
case ControllerType : : n_switch :
return jc - > left_right ;
}
}
return 0 ;
}
// what split type of controller is this?
int JslGetControllerSplitType ( int deviceId )
{
std : : shared_lock < std : : shared_timed_mutex > lock ( _connectedLock ) ;
JoyShock * jc = GetJoyShockFromHandle ( deviceId ) ;
if ( jc ! = nullptr ) {
return jc - > left_right ;
}
return 0 ;
}
// what colour is the controller (not all controllers support this; those that don't will report white)
int JslGetControllerColour ( int deviceId )
{
std : : shared_lock < std : : shared_timed_mutex > lock ( _connectedLock ) ;
// this just reports body colour. Switch controllers also give buttons colour, and in Pro's case, left and right grips
JoyShock * jc = GetJoyShockFromHandle ( deviceId ) ;
if ( jc ! = nullptr ) {
return jc - > body_colour ;
}
return 0xFFFFFF ;
}
// set controller light colour (not all controllers have a light whose colour can be set, but that just means nothing will be done when this is called -- no harm)
void JslSetLightColour ( int deviceId , int colour )
{
std : : shared_lock < std : : shared_timed_mutex > lock ( _connectedLock ) ;
JoyShock * jc = GetJoyShockFromHandle ( deviceId ) ;
if ( jc ! = nullptr & & jc - > controller_type = = ControllerType : : s_ds4 ) {
jc - > modifying_lock . lock ( ) ;
jc - > led_r = ( colour > > 16 ) & 0xff ;
jc - > led_g = ( colour > > 8 ) & 0xff ;
jc - > led_b = colour & 0xff ;
jc - > set_ds4_rumble_light (
jc - > small_rumble ,
jc - > big_rumble ,
jc - > led_r ,
jc - > led_g ,
jc - > led_b ) ;
jc - > modifying_lock . unlock ( ) ;
}
else if ( jc ! = nullptr & & jc - > controller_type = = ControllerType : : s_ds ) {
jc - > modifying_lock . lock ( ) ;
jc - > led_r = ( colour > > 16 ) & 0xff ;
jc - > led_g = ( colour > > 8 ) & 0xff ;
jc - > led_b = colour & 0xff ;
jc - > set_ds5_rumble_light (
jc - > small_rumble ,
jc - > big_rumble ,
jc - > led_r ,
jc - > led_g ,
jc - > led_b ,
jc - > player_number ) ;
jc - > modifying_lock . unlock ( ) ;
}
}
// set controller rumble
void JslSetRumble ( int deviceId , int smallRumble , int bigRumble )
{
std : : shared_lock < std : : shared_timed_mutex > lock ( _connectedLock ) ;
JoyShock * jc = GetJoyShockFromHandle ( deviceId ) ;
if ( jc ! = nullptr & & jc - > controller_type = = ControllerType : : s_ds4 ) {
jc - > modifying_lock . lock ( ) ;
jc - > small_rumble = smallRumble ;
jc - > big_rumble = bigRumble ;
jc - > set_ds4_rumble_light (
jc - > small_rumble ,
jc - > big_rumble ,
jc - > led_r ,
jc - > led_g ,
jc - > led_b ) ;
jc - > modifying_lock . unlock ( ) ;
}
else if ( jc ! = nullptr & & jc - > controller_type = = ControllerType : : s_ds ) {
jc - > modifying_lock . lock ( ) ;
jc - > small_rumble = smallRumble ;
jc - > big_rumble = bigRumble ;
jc - > set_ds5_rumble_light (
jc - > small_rumble ,
jc - > big_rumble ,
jc - > led_r ,
jc - > led_g ,
jc - > led_b ,
jc - > player_number ) ;
jc - > modifying_lock . unlock ( ) ;
}
}
// set controller player number indicator (not all controllers have a number indicator which can be set, but that just means nothing will be done when this is called -- no harm)
void JslSetPlayerNumber ( int deviceId , int number )
{
std : : shared_lock < std : : shared_timed_mutex > lock ( _connectedLock ) ;
JoyShock * jc = GetJoyShockFromHandle ( deviceId ) ;
if ( jc ! = nullptr & & jc - > controller_type = = ControllerType : : n_switch ) {
jc - > modifying_lock . lock ( ) ;
jc - > player_number = number ;
unsigned char buf [ 64 ] ;
memset ( buf , 0x00 , 0x40 ) ;
buf [ 0 ] = ( unsigned char ) number ;
jc - > send_subcommand ( 0x01 , 0x30 , buf , 1 ) ;
jc - > modifying_lock . unlock ( ) ;
}
else if ( jc ! = nullptr & & jc - > controller_type = = ControllerType : : s_ds ) {
jc - > modifying_lock . lock ( ) ;
jc - > player_number = number ;
jc - > set_ds5_rumble_light (
jc - > small_rumble ,
jc - > big_rumble ,
jc - > led_r ,
jc - > led_g ,
jc - > led_b ,
jc - > player_number ) ;
jc - > modifying_lock . unlock ( ) ;
}
}