mirror of
https://github.com/Ed94/HandmadeHero.git
synced 2024-11-14 05:04:51 -08:00
1165 lines
31 KiB
C++
1165 lines
31 KiB
C++
// 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("\tending cancelled thread\n");
|
|
}
|
|
else
|
|
{
|
|
printf("\ttiming out thread\n");
|
|
}
|
|
|
|
// remove
|
|
if (jc->remove_on_finish)
|
|
{
|
|
printf("\t\tremoving 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\tdeleting 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 ||
|
|
cur_dev->product_id == DS_USB;
|
|
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("\topened new handle\n");
|
|
if (isSameController)
|
|
{
|
|
printf("\tending 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("\tinitialising 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("\tcreating 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("\tdeinitialising 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("\tending 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("\tstarting 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();
|
|
}
|
|
}
|