This commit is contained in:
AzaezelX 2022-03-23 01:43:08 -05:00
parent ee4253c982
commit 2614274639
1225 changed files with 148950 additions and 51674 deletions

View file

@ -1,17 +1,33 @@
//=================== Copyright Valve Corporation, All rights reserved. =======
//
/*
Simple DirectMedia Layer
Copyright (C) 2021 Valve Corporation
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "../../SDL_internal.h"
// Purpose: A wrapper implementing "HID" API for Android
//
// This layer glues the hidapi API to Android's USB and BLE stack.
//
//=============================================================================
// Common to stub version and non-stub version of functions
#include <jni.h>
#include <android/log.h>
#include <pthread.h>
#include <errno.h> // For ETIMEDOUT and ECONNRESET
#include <stdlib.h> // For malloc() and free()
#include <string.h> // For memcpy()
#define TAG "hidapi"
@ -31,6 +47,35 @@
#define CONCAT2(prefix, class, function) Java_ ## prefix ## _ ## class ## _ ## function
#define HID_DEVICE_MANAGER_JAVA_INTERFACE(function) CONCAT1(SDL_JAVA_PREFIX, HIDDeviceManager, function)
#if !SDL_HIDAPI_DISABLED
#include "SDL_hints.h"
#include "../../core/android/SDL_android.h"
#define hid_init PLATFORM_hid_init
#define hid_exit PLATFORM_hid_exit
#define hid_enumerate PLATFORM_hid_enumerate
#define hid_free_enumeration PLATFORM_hid_free_enumeration
#define hid_open PLATFORM_hid_open
#define hid_open_path PLATFORM_hid_open_path
#define hid_write PLATFORM_hid_write
#define hid_read_timeout PLATFORM_hid_read_timeout
#define hid_read PLATFORM_hid_read
#define hid_set_nonblocking PLATFORM_hid_set_nonblocking
#define hid_send_feature_report PLATFORM_hid_send_feature_report
#define hid_get_feature_report PLATFORM_hid_get_feature_report
#define hid_close PLATFORM_hid_close
#define hid_get_manufacturer_string PLATFORM_hid_get_manufacturer_string
#define hid_get_product_string PLATFORM_hid_get_product_string
#define hid_get_serial_number_string PLATFORM_hid_get_serial_number_string
#define hid_get_indexed_string PLATFORM_hid_get_indexed_string
#define hid_error PLATFORM_hid_error
#include <pthread.h>
#include <errno.h> // For ETIMEDOUT and ECONNRESET
#include <stdlib.h> // For malloc() and free()
#include "../hidapi/hidapi.h"
typedef uint32_t uint32;
@ -154,7 +199,7 @@ public:
}
m_nSize = nSize;
memcpy( m_pData, pData, nSize );
SDL_memcpy( m_pData, pData, nSize );
}
void clear()
@ -269,9 +314,9 @@ private:
static jbyteArray NewByteArray( JNIEnv* env, const uint8_t *pData, size_t nDataLen )
{
jbyteArray array = env->NewByteArray( nDataLen );
jbyteArray array = env->NewByteArray( (jsize)nDataLen );
jbyte *pBuf = env->GetByteArrayElements( array, NULL );
memcpy( pBuf, pData, nDataLen );
SDL_memcpy( pBuf, pData, nDataLen );
env->ReleaseByteArrayElements( array, pBuf, 0 );
return array;
@ -282,7 +327,7 @@ static char *CreateStringFromJString( JNIEnv *env, const jstring &sString )
size_t nLength = env->GetStringUTFLength( sString );
const char *pjChars = env->GetStringUTFChars( sString, NULL );
char *psString = (char*)malloc( nLength + 1 );
memcpy( psString, pjChars, nLength );
SDL_memcpy( psString, pjChars, nLength );
psString[ nLength ] = '\0';
env->ReleaseStringUTFChars( sString, pjChars );
return psString;
@ -305,9 +350,9 @@ static wchar_t *CreateWStringFromJString( JNIEnv *env, const jstring &sString )
static wchar_t *CreateWStringFromWString( const wchar_t *pwSrc )
{
size_t nLength = wcslen( pwSrc );
size_t nLength = SDL_wcslen( pwSrc );
wchar_t *pwString = (wchar_t*)malloc( ( nLength + 1 ) * sizeof( wchar_t ) );
memcpy( pwString, pwSrc, nLength * sizeof( wchar_t ) );
SDL_memcpy( pwString, pwSrc, nLength * sizeof( wchar_t ) );
pwString[ nLength ] = '\0';
return pwString;
}
@ -316,7 +361,7 @@ static hid_device_info *CopyHIDDeviceInfo( const hid_device_info *pInfo )
{
hid_device_info *pCopy = new hid_device_info;
*pCopy = *pInfo;
pCopy->path = strdup( pInfo->path );
pCopy->path = SDL_strdup( pInfo->path );
pCopy->product_string = CreateWStringFromWString( pInfo->product_string );
pCopy->manufacturer_string = CreateWStringFromWString( pInfo->manufacturer_string );
pCopy->serial_number = CreateWStringFromWString( pInfo->serial_number );
@ -334,17 +379,49 @@ static void FreeHIDDeviceInfo( hid_device_info *pInfo )
static jclass g_HIDDeviceManagerCallbackClass;
static jobject g_HIDDeviceManagerCallbackHandler;
static jmethodID g_midHIDDeviceManagerInitialize;
static jmethodID g_midHIDDeviceManagerOpen;
static jmethodID g_midHIDDeviceManagerSendOutputReport;
static jmethodID g_midHIDDeviceManagerSendFeatureReport;
static jmethodID g_midHIDDeviceManagerGetFeatureReport;
static jmethodID g_midHIDDeviceManagerClose;
static bool g_initialized = false;
static uint64_t get_timespec_ms( const struct timespec &ts )
{
return (uint64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
}
static void ExceptionCheck( JNIEnv *env, const char *pszClassName, const char *pszMethodName )
{
if ( env->ExceptionCheck() )
{
// Get our exception
jthrowable jExcept = env->ExceptionOccurred();
// Clear the exception so we can call JNI again
env->ExceptionClear();
// Get our exception message
jclass jExceptClass = env->GetObjectClass( jExcept );
jmethodID jMessageMethod = env->GetMethodID( jExceptClass, "getMessage", "()Ljava/lang/String;" );
jstring jMessage = (jstring)( env->CallObjectMethod( jExcept, jMessageMethod ) );
const char *pszMessage = env->GetStringUTFChars( jMessage, NULL );
// ...and log it.
LOGE( "%s%s%s threw an exception: %s",
pszClassName ? pszClassName : "",
pszClassName ? "::" : "",
pszMethodName, pszMessage );
// Cleanup
env->ReleaseStringUTFChars( jMessage, pszMessage );
env->DeleteLocalRef( jMessage );
env->DeleteLocalRef( jExceptClass );
env->DeleteLocalRef( jExcept );
}
}
class CHIDDevice
{
public:
@ -404,29 +481,7 @@ public:
void ExceptionCheck( JNIEnv *env, const char *pszMethodName )
{
if ( env->ExceptionCheck() )
{
// Get our exception
jthrowable jExcept = env->ExceptionOccurred();
// Clear the exception so we can call JNI again
env->ExceptionClear();
// Get our exception message
jclass jExceptClass = env->GetObjectClass( jExcept );
jmethodID jMessageMethod = env->GetMethodID( jExceptClass, "getMessage", "()Ljava/lang/String;" );
jstring jMessage = (jstring)( env->CallObjectMethod( jExcept, jMessageMethod ) );
const char *pszMessage = env->GetStringUTFChars( jMessage, NULL );
// ...and log it.
LOGE( "CHIDDevice::%s threw an exception: %s", pszMethodName, pszMessage );
// Cleanup
env->ReleaseStringUTFChars( jMessage, pszMessage );
env->DeleteLocalRef( jMessage );
env->DeleteLocalRef( jExceptClass );
env->DeleteLocalRef( jExcept );
}
::ExceptionCheck( env, "CHIDDevice", pszMethodName );
}
bool BOpen()
@ -527,12 +582,12 @@ public:
if ( m_bIsBLESteamController )
{
data[0] = 0x03;
memcpy( data + 1, buffer.data(), nDataLen );
SDL_memcpy( data + 1, buffer.data(), nDataLen );
++nDataLen;
}
else
{
memcpy( data, buffer.data(), nDataLen );
SDL_memcpy( data, buffer.data(), nDataLen );
}
m_vecData.pop_front();
@ -541,7 +596,7 @@ public:
// data[0], data[1], data[2], data[3],
// data[4], data[5], data[6], data[7]);
return nDataLen;
return (int)nDataLen;
}
int SendOutputReport( const unsigned char *pData, size_t nDataLen )
@ -669,11 +724,11 @@ public:
}
size_t uBytesToCopy = m_featureReport.size() > nDataLen ? nDataLen : m_featureReport.size();
memcpy( pData, m_featureReport.data(), uBytesToCopy );
SDL_memcpy( pData, m_featureReport.data(), uBytesToCopy );
m_featureReport.clear();
LOGV( "=== Got %u bytes", uBytesToCopy );
return uBytesToCopy;
return (int)uBytesToCopy;
}
}
@ -815,6 +870,11 @@ JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceRegisterCallba
if ( objClass )
{
g_HIDDeviceManagerCallbackClass = reinterpret_cast< jclass >( env->NewGlobalRef( objClass ) );
g_midHIDDeviceManagerInitialize = env->GetMethodID( g_HIDDeviceManagerCallbackClass, "initialize", "(ZZ)Z" );
if ( !g_midHIDDeviceManagerInitialize )
{
__android_log_print(ANDROID_LOG_ERROR, TAG, "HIDDeviceRegisterCallback: callback class missing initialize" );
}
g_midHIDDeviceManagerOpen = env->GetMethodID( g_HIDDeviceManagerCallbackClass, "openDevice", "(I)Z" );
if ( !g_midHIDDeviceManagerOpen )
{
@ -854,6 +914,7 @@ JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceReleaseCallbac
g_HIDDeviceManagerCallbackClass = NULL;
env->DeleteGlobalRef( g_HIDDeviceManagerCallbackHandler );
g_HIDDeviceManagerCallbackHandler = NULL;
g_initialized = false;
}
}
@ -863,7 +924,7 @@ JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceConnected)(JNI
LOGV( "HIDDeviceConnected() id=%d VID/PID = %.4x/%.4x, interface %d\n", nDeviceID, nVendorId, nProductId, nInterface );
hid_device_info *pInfo = new hid_device_info;
memset( pInfo, 0, sizeof( *pInfo ) );
SDL_memset( pInfo, 0, sizeof( *pInfo ) );
pInfo->path = CreateStringFromJString( env, sIdentifier );
pInfo->vendor_id = nVendorId;
pInfo->product_id = nProductId;
@ -988,6 +1049,36 @@ extern "C"
int hid_init(void)
{
if ( !g_initialized )
{
// HIDAPI doesn't work well with Android < 4.3
if (SDL_GetAndroidSDKVersion() >= 18) {
// Make sure thread is attached to JVM/env
JNIEnv *env;
g_JVM->AttachCurrentThread( &env, NULL );
pthread_setspecific( g_ThreadKey, (void*)env );
if ( !g_HIDDeviceManagerCallbackHandler )
{
LOGV( "hid_init() without callback handler" );
return -1;
}
// Bluetooth is currently only used for Steam Controllers, so check that hint
// before initializing Bluetooth, which will prompt the user for permission.
bool init_usb = true;
bool init_bluetooth = false;
if (SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_STEAM, SDL_FALSE)) {
if (SDL_GetAndroidSDKVersion() < 31 ||
Android_JNI_RequestPermission("android.permission.BLUETOOTH_CONNECT")) {
init_bluetooth = true;
}
}
env->CallBooleanMethod( g_HIDDeviceManagerCallbackHandler, g_midHIDDeviceManagerInitialize, init_usb, init_bluetooth );
ExceptionCheck( env, NULL, "hid_init" );
}
g_initialized = true; // Regardless of result, so it's only called once
}
return 0;
}
@ -1035,7 +1126,7 @@ HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path, int bEx
hid_mutex_guard l( &g_DevicesMutex );
for ( hid_device_ref<CHIDDevice> pCurr = g_Devices; pCurr; pCurr = pCurr->next )
{
if ( strcmp( pCurr->GetDeviceInfo()->path, path ) == 0 )
if ( SDL_strcmp( pCurr->GetDeviceInfo()->path, path ) == 0 )
{
hid_device *pValue = pCurr->GetDevice();
if ( pValue )
@ -1072,7 +1163,32 @@ int HID_API_EXPORT HID_API_CALL hid_write(hid_device *device, const unsigned ch
return -1; // Controller was disconnected
}
// TODO: Implement timeout?
static uint32_t getms()
{
struct timeval now;
gettimeofday(&now, NULL);
return (uint32_t)(now.tv_sec * 1000 + now.tv_usec / 1000);
}
static void delayms(uint32_t ms)
{
int was_error;
struct timespec elapsed, tv;
/* Set the timeout interval */
elapsed.tv_sec = ms / 1000;
elapsed.tv_nsec = (ms % 1000) * 1000000;
do {
errno = 0;
tv.tv_sec = elapsed.tv_sec;
tv.tv_nsec = elapsed.tv_nsec;
was_error = nanosleep(&tv, &elapsed);
} while (was_error && (errno == EINTR));
}
int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *device, unsigned char *data, size_t length, int milliseconds)
{
if ( device )
@ -1081,7 +1197,17 @@ int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *device, unsigned ch
hid_device_ref<CHIDDevice> pDevice = FindDevice( device->m_nId );
if ( pDevice )
{
return pDevice->GetInput( data, length );
int nResult = pDevice->GetInput( data, length );
if ( nResult == 0 && milliseconds > 0 )
{
uint32_t start = getms();
do
{
delayms( 1 );
nResult = pDevice->GetInput( data, length );
} while ( nResult == 0 && ( getms() - start ) < milliseconds );
}
return nResult;
}
LOGV( "controller was disconnected" );
}
@ -1213,3 +1339,80 @@ int hid_exit(void)
}
}
#else
extern "C"
JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceRegisterCallback)(JNIEnv *env, jobject thiz);
extern "C"
JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceReleaseCallback)(JNIEnv *env, jobject thiz);
extern "C"
JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceConnected)(JNIEnv *env, jobject thiz, int nDeviceID, jstring sIdentifier, int nVendorId, int nProductId, jstring sSerialNumber, int nReleaseNumber, jstring sManufacturer, jstring sProduct, int nInterface, int nInterfaceClass, int nInterfaceSubclass, int nInterfaceProtocol );
extern "C"
JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceOpenPending)(JNIEnv *env, jobject thiz, int nDeviceID);
extern "C"
JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceOpenResult)(JNIEnv *env, jobject thiz, int nDeviceID, bool bOpened);
extern "C"
JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceDisconnected)(JNIEnv *env, jobject thiz, int nDeviceID);
extern "C"
JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceInputReport)(JNIEnv *env, jobject thiz, int nDeviceID, jbyteArray value);
extern "C"
JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceFeatureReport)(JNIEnv *env, jobject thiz, int nDeviceID, jbyteArray value);
extern "C"
JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceRegisterCallback)(JNIEnv *env, jobject thiz )
{
LOGV("Stub HIDDeviceRegisterCallback()");
}
extern "C"
JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceReleaseCallback)(JNIEnv *env, jobject thiz)
{
LOGV("Stub HIDDeviceReleaseCallback()");
}
extern "C"
JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceConnected)(JNIEnv *env, jobject thiz, int nDeviceID, jstring sIdentifier, int nVendorId, int nProductId, jstring sSerialNumber, int nReleaseNumber, jstring sManufacturer, jstring sProduct, int nInterface, int nInterfaceClass, int nInterfaceSubclass, int nInterfaceProtocol )
{
LOGV("Stub HIDDeviceConnected() id=%d VID/PID = %.4x/%.4x, interface %d\n", nDeviceID, nVendorId, nProductId, nInterface);
}
extern "C"
JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceOpenPending)(JNIEnv *env, jobject thiz, int nDeviceID)
{
LOGV("Stub HIDDeviceOpenPending() id=%d\n", nDeviceID);
}
extern "C"
JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceOpenResult)(JNIEnv *env, jobject thiz, int nDeviceID, bool bOpened)
{
LOGV("Stub HIDDeviceOpenResult() id=%d, result=%s\n", nDeviceID, bOpened ? "true" : "false");
}
extern "C"
JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceDisconnected)(JNIEnv *env, jobject thiz, int nDeviceID)
{
LOGV("Stub HIDDeviceDisconnected() id=%d\n", nDeviceID);
}
extern "C"
JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceInputReport)(JNIEnv *env, jobject thiz, int nDeviceID, jbyteArray value)
{
LOGV("Stub HIDDeviceInput() id=%d len=%u\n", nDeviceID, nBufSize);
}
extern "C"
JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceFeatureReport)(JNIEnv *env, jobject thiz, int nDeviceID, jbyteArray value)
{
LOGV("Stub HIDDeviceFeatureReport() id=%d len=%u\n", nDeviceID, nBufSize);
}
#endif /* SDL_HIDAPI_DISABLED */