From 28011e57b9b05b52cb08980048b579b36e4c1555 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Wed, 24 May 2023 14:53:34 -0700 Subject: [PATCH 01/36] Allow declarations after statements The SDL coding standard is C89 plus C++ style comments and mixed code and declarations. --- CMakeLists.txt | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 76211e19bd73f..876530890bc73 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -570,32 +570,6 @@ if(USE_GCC OR USE_CLANG OR USE_INTELCC OR USE_QCC) target_compile_options(sdl-global-options INTERFACE "-fno-strict-aliasing") endif() - # command-line option ‘-Wdeclaration-after-statement’ is valid for C/ObjC but not for C++ - check_c_compiler_flag(-Wdeclaration-after-statement HAVE_GCC_WDECLARATION_AFTER_STATEMENT) - if(HAVE_GCC_WDECLARATION_AFTER_STATEMENT) - if(SDL_WERROR) - check_c_compiler_flag(-Werror=declaration-after-statement HAVE_GCC_WERROR_DECLARATION_AFTER_STATEMENT) - if(HAVE_GCC_WERROR_DECLARATION_AFTER_STATEMENT) - if(CMAKE_VERSION VERSION_LESS 3.3) - target_compile_options(sdl-global-options INTERFACE "-Werror=declaration-after-statement") - else() - target_compile_options(sdl-global-options INTERFACE "$<$:-Werror=declaration-after-statement>") - if(CMAKE_OBJC_COMPILER) - target_compile_options(sdl-global-options INTERFACE "$<$:-Werror=declaration-after-statement>") - endif() - endif() - endif() - endif() - if(CMAKE_VERSION VERSION_LESS 3.3) - target_compile_options(sdl-global-options INTERFACE "-Wdeclaration-after-statement") - else() - target_compile_options(sdl-global-options INTERFACE "$<$:-Wdeclaration-after-statement>") - if(CMAKE_OBJC_COMPILER) - target_compile_options(sdl-global-options INTERFACE "$<$:-Wdeclaration-after-statement>") - endif() - endif() - endif() - check_c_compiler_flag(-Wdocumentation HAVE_GCC_WDOCUMENTATION) if(HAVE_GCC_WDOCUMENTATION) if(SDL_WERROR) From a216ec45404c03421eebe6b95fec81aa3ba6793f Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Wed, 24 May 2023 08:14:47 -0700 Subject: [PATCH 02/36] Added SDL_wcstol() --- CMakeLists.txt | 22 +----- include/SDL3/SDL_stdinc.h | 1 + include/build_config/SDL_build_config.h.cmake | 1 + src/dynapi/SDL_dynapi.sym | 1 + src/dynapi/SDL_dynapi_overrides.h | 1 + src/dynapi/SDL_dynapi_procs.h | 1 + src/stdlib/SDL_string.c | 70 ++++++++++++++++++- 7 files changed, 76 insertions(+), 21 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 876530890bc73..1a992935cac03 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1106,16 +1106,7 @@ if(SDL_LIBC) set(${_HAVE_H} 1) endforeach() set(HAVE_SIGNAL_H 1) - foreach(_FN - malloc calloc realloc free bsearch qsort abs memset memcpy memmove memcmp - wcslen _wcsdup wcsdup wcsstr wcscmp wcsncmp _wcsicmp _wcsnicmp - strlen _strrev _strupr _strlwr strchr strrchr strstr itoa _ltoa - _ultoa strtol strtoul strtoll strtod atoi atof strcmp strncmp - _stricmp _strnicmp sscanf - acos acosf asin asinf atan atanf atan2 atan2f ceil ceilf - copysign copysignf cos cosf exp expf fabs fabsf floor floorf fmod fmodf - log logf log10 log10f lround lroundf modf modff pow powf round roundf - scalbn scalbnf sin sinf sqrt sqrtf tan tanf trunc truncf) + foreach(_FN abs acos acosf asin asinf atan atan2 atan2f atanf atof atoi bsearch calloc ceil ceilf copysign copysignf cos cosf exp expf fabs fabsf floor floorf fmod fmodf free itoa log log10 log10f logf lround lroundf _ltoa malloc memcmp memcpy memmove memset modf modff pow powf qsort realloc round roundf scalbn scalbnf sin sinf sqrt sqrtf sscanf strchr strcmp _stricmp strlen _strlwr strncmp _strnicmp strrchr _strrev strstr strtod strtol strtoll strtoul _strupr tan tanf trunc truncf _ultoa wcscmp _wcsdup wcsdup _wcsicmp wcslen wcsncmp _wcsnicmp wcsstr wcstol) string(TOUPPER ${_FN} _UPPER) set(HAVE_${_UPPER} 1) endforeach() @@ -1159,16 +1150,7 @@ if(SDL_LIBC) check_c_source_compiles("#include #include int main(void) { return 0; }" HAVE_MPROTECT) - foreach(_FN - strtod malloc calloc realloc free getenv setenv putenv unsetenv - bsearch qsort abs bcopy memset memcpy memmove memcmp strlen strlcpy strlcat - _strrev _strupr _strlwr index rindex strchr strrchr strstr strtok_r - itoa _ltoa _uitoa _ultoa strtol strtoul _i64toa _ui64toa strtoll strtoull - atoi atof strcmp strncmp _stricmp strcasecmp _strnicmp strncasecmp strcasestr - wcscmp _wcsdup wcsdup wcslcat wcslcpy wcslen wcsncmp wcsstr - wcscasecmp _wcsicmp wcsncasecmp _wcsnicmp - sscanf vsscanf vsnprintf fopen64 fseeko fseeko64 _Exit - ) + foreach(_FN abs atof atoi bcopy bsearch calloc _Exit fopen64 free fseeko fseeko64 getenv _i64toa index itoa _ltoa malloc memcmp memcpy memmove memset putenv qsort realloc rindex setenv sscanf strcasecmp strcasestr strchr strcmp _stricmp strlcat strlcpy strlen _strlwr strncasecmp strncmp _strnicmp strrchr _strrev strstr strtod strtok_r strtol strtoll strtoul strtoull _strupr _ui64toa _uitoa _ultoa unsetenv vsnprintf vsscanf wcscasecmp wcscmp _wcsdup wcsdup _wcsicmp wcslcat wcslcpy wcslen wcsncasecmp wcsncmp _wcsnicmp wcsstr wcstol) string(TOUPPER ${_FN} _UPPER) set(LIBC_HAS_VAR "LIBC_HAS_${_UPPER}") check_symbol_exists("${_FN}" "${STDC_HEADER_NAMES}" ${LIBC_HAS_VAR}) diff --git a/include/SDL3/SDL_stdinc.h b/include/SDL3/SDL_stdinc.h index 404e77920afbb..61fc27952f195 100644 --- a/include/SDL3/SDL_stdinc.h +++ b/include/SDL3/SDL_stdinc.h @@ -536,6 +536,7 @@ extern DECLSPEC int SDLCALL SDL_wcscmp(const wchar_t *str1, const wchar_t *str2) extern DECLSPEC int SDLCALL SDL_wcsncmp(const wchar_t *str1, const wchar_t *str2, size_t maxlen); extern DECLSPEC int SDLCALL SDL_wcscasecmp(const wchar_t *str1, const wchar_t *str2); extern DECLSPEC int SDLCALL SDL_wcsncasecmp(const wchar_t *str1, const wchar_t *str2, size_t len); +extern DECLSPEC long SDLCALL SDL_wcstol(const wchar_t *str, wchar_t **endp, int base); extern DECLSPEC size_t SDLCALL SDL_strlen(const char *str); extern DECLSPEC size_t SDLCALL SDL_strlcpy(SDL_OUT_Z_CAP(maxlen) char *dst, const char *src, size_t maxlen); diff --git a/include/build_config/SDL_build_config.h.cmake b/include/build_config/SDL_build_config.h.cmake index 13005525cb045..928360fdd3f58 100644 --- a/include/build_config/SDL_build_config.h.cmake +++ b/include/build_config/SDL_build_config.h.cmake @@ -102,6 +102,7 @@ #cmakedefine HAVE__WCSICMP 1 #cmakedefine HAVE_WCSNCASECMP 1 #cmakedefine HAVE__WCSNICMP 1 +#cmakedefine HAVE_WCSTOL 1 #cmakedefine HAVE_STRLEN 1 #cmakedefine HAVE_STRLCPY 1 #cmakedefine HAVE_STRLCAT 1 diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index 816913e4ed2dc..c5bf0b0bc3fc9 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -858,6 +858,7 @@ SDL3_0.0.0 { SDL_GetDisplayContentScale; SDL_GetWindowDisplayScale; SDL_GetWindowPixelDensity; + SDL_wcstol; # extra symbols go here (don't modify this line) local: *; }; diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index 020eecc5c8b38..40db83c912ced 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -884,3 +884,4 @@ #define SDL_GetDisplayContentScale SDL_GetDisplayContentScale_REAL #define SDL_GetWindowDisplayScale SDL_GetWindowDisplayScale_REAL #define SDL_GetWindowPixelDensity SDL_GetWindowPixelDensity_REAL +#define SDL_wcstol SDL_wcstol_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index fa6e6b6f43c11..fa68b89693137 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -929,3 +929,4 @@ SDL_DYNAPI_PROC(char*,SDL_GetPath,(SDL_Folder a),(a),return) SDL_DYNAPI_PROC(float,SDL_GetDisplayContentScale,(SDL_DisplayID a),(a),return) SDL_DYNAPI_PROC(float,SDL_GetWindowDisplayScale,(SDL_Window *a),(a),return) SDL_DYNAPI_PROC(float,SDL_GetWindowPixelDensity,(SDL_Window *a),(a),return) +SDL_DYNAPI_PROC(long,SDL_wcstol,(const wchar_t *a, wchar_t **b, int c),(a,b,c),return) diff --git a/src/stdlib/SDL_string.c b/src/stdlib/SDL_string.c index 6f7f1af7612e3..b6aae10f51c66 100644 --- a/src/stdlib/SDL_string.c +++ b/src/stdlib/SDL_string.c @@ -28,7 +28,7 @@ #include #endif -#if !defined(HAVE_VSSCANF) || !defined(HAVE_STRTOL) || !defined(HAVE_STRTOUL) || !defined(HAVE_STRTOD) || !defined(HAVE_STRTOLL) || !defined(HAVE_STRTOULL) +#if !defined(HAVE_VSSCANF) || !defined(HAVE_STRTOL) || !defined(HAVE_WCSTOL) || !defined(HAVE_STRTOUL) || !defined(HAVE_STRTOD) || !defined(HAVE_STRTOLL) || !defined(HAVE_STRTOULL) #define SDL_isupperhex(X) (((X) >= 'A') && ((X) <= 'F')) #define SDL_islowerhex(X) (((X) >= 'a') && ((X) <= 'f')) #endif @@ -93,6 +93,50 @@ static size_t SDL_ScanLong(const char *text, int count, int radix, long *valuep) } #endif +#if !defined(HAVE_WCSTOL) +static size_t SDL_ScanLongW(const wchar_t *text, int count, int radix, long *valuep) +{ + const wchar_t *textstart = text; + long value = 0; + SDL_bool negative = SDL_FALSE; + + if (*text == '-') { + negative = SDL_TRUE; + ++text; + } + if (radix == 16 && SDL_wcsncmp(text, L"0x", 2) == 0) { + text += 2; + } + for (;;) { + int v; + if (*text >= '0' && *text <= '9') { + v = *text - '0'; + } else if (radix == 16 && SDL_isupperhex(*text)) { + v = 10 + (*text - 'A'); + } else if (radix == 16 && SDL_islowerhex(*text)) { + v = 10 + (*text - 'a'); + } else { + break; + } + value *= radix; + value += v; + ++text; + + if (count > 0 && (text - textstart) == count) { + break; + } + } + if (valuep && text > textstart) { + if (negative && value) { + *valuep = -value; + } else { + *valuep = value; + } + } + return text - textstart; +} +#endif + #if !defined(HAVE_VSSCANF) || !defined(HAVE_STRTOUL) || !defined(HAVE_STRTOD) static size_t SDL_ScanUnsignedLong(const char *text, int count, int radix, unsigned long *valuep) { @@ -530,6 +574,30 @@ int SDL_wcsncasecmp(const wchar_t *str1, const wchar_t *str2, size_t maxlen) #endif /* HAVE__WCSNICMP */ } +long SDL_wcstol(const wchar_t *string, wchar_t **endp, int base) +{ +#ifdef HAVE_WCSTOL + return wcstol(string, endp, base); +#else + size_t len; + long value = 0; + + if (!base) { + if ((SDL_wcslen(string) > 2) && (SDL_wcsncmp(string, L"0x", 2) == 0)) { + base = 16; + } else { + base = 10; + } + } + + len = SDL_ScanLongW(string, 0, base, &value); + if (endp) { + *endp = (wchar_t *)string + len; + } + return value; +#endif /* HAVE_WCSTOL */ +} + size_t SDL_strlcpy(SDL_OUT_Z_CAP(maxlen) char *dst, const char *src, size_t maxlen) { #ifdef HAVE_STRLCPY From 0d87bb79c08bc9102ce63030faf94be79110ca7c Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Wed, 24 May 2023 09:41:22 -0700 Subject: [PATCH 03/36] Added SDL_swprintf() and SDL_vswprintf() --- include/SDL3/SDL_stdinc.h | 8 ++ src/dynapi/SDL_dynapi.c | 10 ++ src/dynapi/SDL_dynapi.sym | 2 + src/dynapi/SDL_dynapi_overrides.h | 2 + src/dynapi/SDL_dynapi_procs.h | 2 + src/dynapi/gendynapi.py | 1 + src/stdlib/SDL_string.c | 55 ++++++++++ test/testautomation_stdlib.c | 172 +++++++++++++++++++++++++++++- 8 files changed, 250 insertions(+), 2 deletions(-) diff --git a/include/SDL3/SDL_stdinc.h b/include/SDL3/SDL_stdinc.h index 61fc27952f195..7f82956d3fa59 100644 --- a/include/SDL3/SDL_stdinc.h +++ b/include/SDL3/SDL_stdinc.h @@ -286,6 +286,8 @@ typedef uint64_t Uint64; #define SDL_SCANF_FORMAT_STRING #define SDL_PRINTF_VARARG_FUNC( fmtargnumber ) #define SDL_SCANF_VARARG_FUNC( fmtargnumber ) +#define SDL_WPRINTF_VARARG_FUNC( fmtargnumber ) +#define SDL_WSCANF_VARARG_FUNC( fmtargnumber ) #else #if defined(_MSC_VER) && (_MSC_VER >= 1600) /* VS 2010 and above */ #include @@ -312,9 +314,13 @@ typedef uint64_t Uint64; #ifdef __GNUC__ #define SDL_PRINTF_VARARG_FUNC( fmtargnumber ) __attribute__ (( format( __printf__, fmtargnumber, fmtargnumber+1 ))) #define SDL_SCANF_VARARG_FUNC( fmtargnumber ) __attribute__ (( format( __scanf__, fmtargnumber, fmtargnumber+1 ))) +#define SDL_WPRINTF_VARARG_FUNC( fmtargnumber ) /* __attribute__ (( format( __wprintf__, fmtargnumber, fmtargnumber+1 ))) */ +#define SDL_WSCANF_VARARG_FUNC( fmtargnumber ) /* __attribute__ (( format( __wscanf__, fmtargnumber, fmtargnumber+1 ))) */ #else #define SDL_PRINTF_VARARG_FUNC( fmtargnumber ) #define SDL_SCANF_VARARG_FUNC( fmtargnumber ) +#define SDL_WPRINTF_VARARG_FUNC( fmtargnumber ) +#define SDL_WSCANF_VARARG_FUNC( fmtargnumber ) #endif #endif /* SDL_DISABLE_ANALYZE_MACROS */ @@ -577,7 +583,9 @@ extern DECLSPEC int SDLCALL SDL_strncasecmp(const char *str1, const char *str2, extern DECLSPEC int SDLCALL SDL_sscanf(const char *text, SDL_SCANF_FORMAT_STRING const char *fmt, ...) SDL_SCANF_VARARG_FUNC(2); extern DECLSPEC int SDLCALL SDL_vsscanf(const char *text, const char *fmt, va_list ap); extern DECLSPEC int SDLCALL SDL_snprintf(SDL_OUT_Z_CAP(maxlen) char *text, size_t maxlen, SDL_PRINTF_FORMAT_STRING const char *fmt, ... ) SDL_PRINTF_VARARG_FUNC(3); +extern DECLSPEC int SDLCALL SDL_swprintf(SDL_OUT_Z_CAP(maxlen) wchar_t *text, size_t maxlen, SDL_PRINTF_FORMAT_STRING const wchar_t *fmt, ... ) SDL_WPRINTF_VARARG_FUNC(3); extern DECLSPEC int SDLCALL SDL_vsnprintf(SDL_OUT_Z_CAP(maxlen) char *text, size_t maxlen, const char *fmt, va_list ap); +extern DECLSPEC int SDLCALL SDL_vswprintf(SDL_OUT_Z_CAP(maxlen) wchar_t *text, size_t maxlen, const wchar_t *fmt, va_list ap); extern DECLSPEC int SDLCALL SDL_asprintf(char **strp, SDL_PRINTF_FORMAT_STRING const char *fmt, ...) SDL_PRINTF_VARARG_FUNC(2); extern DECLSPEC int SDLCALL SDL_vasprintf(char **strp, const char *fmt, va_list ap); diff --git a/src/dynapi/SDL_dynapi.c b/src/dynapi/SDL_dynapi.c index cc97d232fc3b6..d09fe9e6ef088 100644 --- a/src/dynapi/SDL_dynapi.c +++ b/src/dynapi/SDL_dynapi.c @@ -123,6 +123,16 @@ static void SDL_InitDynamicAPI(void); va_end(ap); \ return retval; \ } \ + _static int SDLCALL SDL_swprintf##name(SDL_OUT_Z_CAP(maxlen) wchar_t *buf, size_t maxlen, SDL_PRINTF_FORMAT_STRING const wchar_t *fmt, ...) \ + { \ + int retval; \ + va_list ap; \ + initcall; \ + va_start(ap, fmt); \ + retval = jump_table.SDL_vswprintf(buf, maxlen, fmt, ap); \ + va_end(ap); \ + return retval; \ + } \ _static int SDLCALL SDL_asprintf##name(char **strp, SDL_PRINTF_FORMAT_STRING const char *fmt, ...) \ { \ int retval; \ diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index c5bf0b0bc3fc9..c306f532f758e 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -859,6 +859,8 @@ SDL3_0.0.0 { SDL_GetWindowDisplayScale; SDL_GetWindowPixelDensity; SDL_wcstol; + SDL_swprintf; + SDL_vswprintf; # extra symbols go here (don't modify this line) local: *; }; diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index 40db83c912ced..7da9a97e5722f 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -885,3 +885,5 @@ #define SDL_GetWindowDisplayScale SDL_GetWindowDisplayScale_REAL #define SDL_GetWindowPixelDensity SDL_GetWindowPixelDensity_REAL #define SDL_wcstol SDL_wcstol_REAL +#define SDL_swprintf SDL_swprintf_REAL +#define SDL_vswprintf SDL_vswprintf_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index fa68b89693137..9722e2169f7e2 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -41,6 +41,7 @@ SDL_DYNAPI_PROC(void,SDL_LogWarn,(int a, SDL_PRINTF_FORMAT_STRING const char *b, SDL_DYNAPI_PROC(int,SDL_SetError,(SDL_PRINTF_FORMAT_STRING const char *a, ...),(a),return) SDL_DYNAPI_PROC(int,SDL_asprintf,(char **a, SDL_PRINTF_FORMAT_STRING const char *b, ...),(a,b),return) SDL_DYNAPI_PROC(int,SDL_snprintf,(SDL_OUT_Z_CAP(b) char *a, size_t b, SDL_PRINTF_FORMAT_STRING const char *c, ...),(a,b,c),return) +SDL_DYNAPI_PROC(int,SDL_swprintf,(SDL_OUT_Z_CAP(b) wchar_t *a, size_t b, SDL_PRINTF_FORMAT_STRING const wchar_t *c, ...),(a,b,c),return) SDL_DYNAPI_PROC(int,SDL_sscanf,(const char *a, SDL_SCANF_FORMAT_STRING const char *b, ...),(a,b),return) #endif @@ -930,3 +931,4 @@ SDL_DYNAPI_PROC(float,SDL_GetDisplayContentScale,(SDL_DisplayID a),(a),return) SDL_DYNAPI_PROC(float,SDL_GetWindowDisplayScale,(SDL_Window *a),(a),return) SDL_DYNAPI_PROC(float,SDL_GetWindowPixelDensity,(SDL_Window *a),(a),return) SDL_DYNAPI_PROC(long,SDL_wcstol,(const wchar_t *a, wchar_t **b, int c),(a,b,c),return) +SDL_DYNAPI_PROC(int,SDL_vswprintf,(wchar_t *a, size_t b, const wchar_t *c, va_list d),(a,b,c,d),return) diff --git a/src/dynapi/gendynapi.py b/src/dynapi/gendynapi.py index ada149a826f3e..0f6284ab268e5 100755 --- a/src/dynapi/gendynapi.py +++ b/src/dynapi/gendynapi.py @@ -144,6 +144,7 @@ def main(): func = func.replace("SDL_PRINTF_VARARG_FUNC(1)", ""); func = func.replace("SDL_PRINTF_VARARG_FUNC(2)", ""); func = func.replace("SDL_PRINTF_VARARG_FUNC(3)", ""); + func = func.replace("SDL_WPRINTF_VARARG_FUNC(3)", ""); func = func.replace("SDL_SCANF_VARARG_FUNC(2)", ""); func = func.replace("__attribute__((analyzer_noreturn))", ""); func = func.replace("SDL_MALLOC", ""); diff --git a/src/stdlib/SDL_string.c b/src/stdlib/SDL_string.c index b6aae10f51c66..95c19fcab664b 100644 --- a/src/stdlib/SDL_string.c +++ b/src/stdlib/SDL_string.c @@ -1466,6 +1466,18 @@ int SDL_snprintf(SDL_OUT_Z_CAP(maxlen) char *text, size_t maxlen, SDL_PRINTF_FOR return retval; } +int SDL_swprintf(SDL_OUT_Z_CAP(maxlen) wchar_t *text, size_t maxlen, SDL_PRINTF_FORMAT_STRING const wchar_t *fmt, ...) +{ + va_list ap; + int retval; + + va_start(ap, fmt); + retval = SDL_vswprintf(text, maxlen, fmt, ap); + va_end(ap); + + return retval; +} + #if defined(HAVE_LIBC) && defined(__WATCOMC__) /* _vsnprintf() doesn't ensure nul termination */ int SDL_vsnprintf(SDL_OUT_Z_CAP(maxlen) char *text, size_t maxlen, const char *fmt, va_list ap) @@ -1979,6 +1991,49 @@ int SDL_vsnprintf(SDL_OUT_Z_CAP(maxlen) char *text, size_t maxlen, const char *f #undef TEXT_AND_LEN_ARGS #endif /* HAVE_VSNPRINTF */ +int SDL_vswprintf(SDL_OUT_Z_CAP(maxlen) wchar_t *text, size_t maxlen, SDL_PRINTF_FORMAT_STRING const wchar_t *fmt, va_list ap) +{ + char *text_utf8 = NULL, *fmt_utf8 = NULL; + int retval; + + if (fmt) { + fmt_utf8 = SDL_iconv_string("UTF-8", "WCHAR_T", (const char *)fmt, (SDL_wcslen(fmt) + 1) * sizeof(wchar_t)); + if (!fmt_utf8) { + return -1; + } + } + + if (!maxlen) { + /* We still need to generate the text to find the final text length */ + maxlen = 1024; + } + text_utf8 = (char *)SDL_malloc(maxlen * 4); + if (!text_utf8) { + SDL_free(fmt_utf8); + return -1; + } + + retval = SDL_vsnprintf(text_utf8, maxlen * 4, fmt_utf8, ap); + + if (retval >= 0) { + wchar_t *text_wchar = (wchar_t *)SDL_iconv_string("WCHAR_T", "UTF-8", text_utf8, SDL_strlen(text_utf8) + 1); + if (text_wchar) { + if (text) { + SDL_wcslcpy(text, text_wchar, maxlen); + } + retval = (int)SDL_wcslen(text_wchar); + SDL_free(text_wchar); + } else { + retval = -1; + } + } + + SDL_free(text_utf8); + SDL_free(fmt_utf8); + + return retval; +} + int SDL_asprintf(char **strp, SDL_PRINTF_FORMAT_STRING const char *fmt, ...) { va_list ap; diff --git a/test/testautomation_stdlib.c b/test/testautomation_stdlib.c index d4ab8d25dcb53..448853a36e224 100644 --- a/test/testautomation_stdlib.c +++ b/test/testautomation_stdlib.c @@ -205,6 +205,169 @@ static int stdlib_snprintf(void *arg) return TEST_COMPLETED; } +/** + * \brief Call to SDL_swprintf + */ +#undef SDL_swprintf +static int stdlib_swprintf(void *arg) +{ + int result; + int predicted; + wchar_t text[1024]; + const wchar_t *expected; + size_t size; + + result = SDL_swprintf(text, sizeof(text), L"%s", "foo"); + expected = L"foo"; + SDLTest_AssertPass("Call to SDL_swprintf(\"%%s\", \"foo\")"); + SDLTest_AssertCheck(SDL_wcscmp(text, expected) == 0, "Check text, expected: %s, got: %s", expected, text); + SDLTest_AssertCheck(result == SDL_wcslen(text), "Check result value, expected: %d, got: %d", (int)SDL_wcslen(text), result); + + result = SDL_swprintf(text, 2, L"%s", "foo"); + expected = L"f"; + SDLTest_AssertPass("Call to SDL_swprintf(\"%%s\", \"foo\") with buffer size 2"); + SDLTest_AssertCheck(SDL_wcscmp(text, expected) == 0, "Check text, expected: %s, got: %s", expected, text); + SDLTest_AssertCheck(result == 3, "Check result value, expected: 3, got: %d", result); + + result = SDL_swprintf(NULL, 0, L"%s", "foo"); + SDLTest_AssertPass("Call to SDL_swprintf(NULL, 0, \"%%s\", \"foo\")"); + SDLTest_AssertCheck(result == 3, "Check result value, expected: 3, got: %d", result); + + result = SDL_swprintf(text, 2, L"%s\n", "foo"); + expected = L"f"; + SDLTest_AssertPass("Call to SDL_swprintf(\"%%s\\n\", \"foo\") with buffer size 2"); + SDLTest_AssertCheck(SDL_wcscmp(text, expected) == 0, "Check text, expected: %s, got: %s", expected, text); + SDLTest_AssertCheck(result == 4, "Check result value, expected: 4, got: %d", result); + + result = SDL_swprintf(text, sizeof(text), L"%f", 0.0); + predicted = SDL_swprintf(NULL, 0, L"%f", 0.0); + expected = L"0.000000"; + SDLTest_AssertPass("Call to SDL_swprintf(\"%%f\", 0.0)"); + SDLTest_AssertCheck(SDL_wcscmp(text, expected) == 0, "Check text, expected: %s, got: %s", expected, text); + SDLTest_AssertCheck(result == SDL_wcslen(text), "Check result value, expected: %d, got: %d", (int)SDL_wcslen(text), result); + SDLTest_AssertCheck(predicted == result, "Check predicted value, expected: %d, got: %d", result, predicted); + + result = SDL_swprintf(text, sizeof(text), L"%f", 1.0); + predicted = SDL_swprintf(NULL, 0, L"%f", 1.0); + expected = L"1.000000"; + SDLTest_AssertPass("Call to SDL_swprintf(\"%%f\", 1.0)"); + SDLTest_AssertCheck(SDL_wcscmp(text, expected) == 0, "Check text, expected: %s, got: %s", expected, text); + SDLTest_AssertCheck(result == SDL_wcslen(text), "Check result value, expected: %d, got: %d", (int)SDL_wcslen(text), result); + SDLTest_AssertCheck(predicted == result, "Check predicted value, expected: %d, got: %d", result, predicted); + + result = SDL_swprintf(text, sizeof(text), L"%.f", 1.0); + predicted = SDL_swprintf(NULL, 0, L"%.f", 1.0); + expected = L"1"; + SDLTest_AssertPass("Call to SDL_swprintf(\"%%.f\", 1.0)"); + SDLTest_AssertCheck(SDL_wcscmp(text, expected) == 0, "Check text, expected: %s, got: %s", expected, text); + SDLTest_AssertCheck(result == SDL_wcslen(text), "Check result value, expected: %d, got: %d", (int)SDL_wcslen(text), result); + SDLTest_AssertCheck(predicted == result, "Check predicted value, expected: %d, got: %d", result, predicted); + + result = SDL_swprintf(text, sizeof(text), L"%#.f", 1.0); + predicted = SDL_swprintf(NULL, 0, L"%#.f", 1.0); + expected = L"1."; + SDLTest_AssertPass("Call to SDL_swprintf(\"%%#.f\", 1.0)"); + SDLTest_AssertCheck(SDL_wcscmp(text, expected) == 0, "Check text, expected: %s, got: %s", expected, text); + SDLTest_AssertCheck(result == SDL_wcslen(text), "Check result value, expected: %d, got: %d", (int)SDL_wcslen(text), result); + SDLTest_AssertCheck(predicted == result, "Check predicted value, expected: %d, got: %d", result, predicted); + + result = SDL_swprintf(text, sizeof(text), L"%f", 1.0 + 1.0 / 3.0); + predicted = SDL_swprintf(NULL, 0, L"%f", 1.0 + 1.0 / 3.0); + expected = L"1.333333"; + SDLTest_AssertPass("Call to SDL_swprintf(\"%%f\", 1.0 + 1.0 / 3.0)"); + SDLTest_AssertCheck(SDL_wcscmp(text, expected) == 0, "Check text, expected: %s, got: %s", expected, text); + SDLTest_AssertCheck(result == SDL_wcslen(text), "Check result value, expected: %d, got: %d", (int)SDL_wcslen(text), result); + SDLTest_AssertCheck(predicted == result, "Check predicted value, expected: %d, got: %d", result, predicted); + + result = SDL_swprintf(text, sizeof(text), L"%+f", 1.0 + 1.0 / 3.0); + predicted = SDL_swprintf(NULL, 0, L"%+f", 1.0 + 1.0 / 3.0); + expected = L"+1.333333"; + SDLTest_AssertPass("Call to SDL_swprintf(\"%%+f\", 1.0 + 1.0 / 3.0)"); + SDLTest_AssertCheck(SDL_wcscmp(text, expected) == 0, "Check text, expected: %s, got: %s", expected, text); + SDLTest_AssertCheck(result == SDL_wcslen(text), "Check result value, expected: %d, got: %d", (int)SDL_wcslen(text), result); + SDLTest_AssertCheck(predicted == result, "Check predicted value, expected: %d, got: %d", result, predicted); + + result = SDL_swprintf(text, sizeof(text), L"%.2f", 1.0 + 1.0 / 3.0); + predicted = SDL_swprintf(NULL, 0, L"%.2f", 1.0 + 1.0 / 3.0); + expected = L"1.33"; + SDLTest_AssertPass("Call to SDL_swprintf(\"%%.2f\", 1.0 + 1.0 / 3.0)"); + SDLTest_AssertCheck(SDL_wcscmp(text, expected) == 0, "Check text, expected: %s, got: %s", expected, text); + SDLTest_AssertCheck(result == SDL_wcslen(text), "Check result value, expected: %d, got: %d", (int)SDL_wcslen(text), result); + SDLTest_AssertCheck(predicted == result, "Check predicted value, expected: %d, got: %d", result, predicted); + + result = SDL_swprintf(text, sizeof(text), L"%6.2f", 1.0 + 1.0 / 3.0); + predicted = SDL_swprintf(NULL, 0, L"%6.2f", 1.0 + 1.0 / 3.0); + expected = L" 1.33"; + SDLTest_AssertPass("Call to SDL_swprintf(\"%%6.2f\", 1.0 + 1.0 / 3.0)"); + SDLTest_AssertCheck(SDL_wcscmp(text, expected) == 0, "Check text, expected: '%s', got: '%s'", expected, text); + SDLTest_AssertCheck(result == SDL_wcslen(text), "Check result value, expected: %d, got: %d", (int)SDL_wcslen(text), result); + SDLTest_AssertCheck(predicted == result, "Check predicted value, expected: %d, got: %d", result, predicted); + + result = SDL_swprintf(text, sizeof(text), L"%06.2f", 1.0 + 1.0 / 3.0); + predicted = SDL_swprintf(NULL, 0, L"%06.2f", 1.0 + 1.0 / 3.0); + expected = L"001.33"; + SDLTest_AssertPass("Call to SDL_swprintf(\"%%06.2f\", 1.0 + 1.0 / 3.0)"); + SDLTest_AssertCheck(SDL_wcscmp(text, expected) == 0, "Check text, expected: '%s', got: '%s'", expected, text); + SDLTest_AssertCheck(result == SDL_wcslen(text), "Check result value, expected: %d, got: %d", (int)SDL_wcslen(text), result); + SDLTest_AssertCheck(predicted == result, "Check predicted value, expected: %d, got: %d", result, predicted); + + result = SDL_swprintf(text, 5, L"%06.2f", 1.0 + 1.0 / 3.0); + expected = L"001."; + SDLTest_AssertPass("Call to SDL_swprintf(\"%%06.2f\", 1.0 + 1.0 / 3.0) with buffer size 5"); + SDLTest_AssertCheck(SDL_wcscmp(text, expected) == 0, "Check text, expected: '%s', got: '%s'", expected, text); + SDLTest_AssertCheck(result == 6, "Check result value, expected: 6, got: %d", result); + + { + static struct + { + float value; + const wchar_t *expected_f; + const wchar_t *expected_g; + } f_and_g_test_cases[] = { + { 100.0f, L"100.000000", L"100" }, + { -100.0f, L"-100.000000", L"-100" }, + { 100.75f, L"100.750000", L"100.75" }, + { -100.75f, L"-100.750000", L"-100.75" }, + { ((100 * 60 * 1000) / 1001) / 100.0f, L"59.939999", L"59.94" }, + { -((100 * 60 * 1000) / 1001) / 100.0f, L"-59.939999", L"-59.94" }, + { ((100 * 120 * 1000) / 1001) / 100.0f, L"119.879997", L"119.88" }, + { -((100 * 120 * 1000) / 1001) / 100.0f, L"-119.879997", L"-119.88" }, + { 9.9999999f, L"10.000000", L"10" }, + { -9.9999999f, L"-10.000000", L"-10" }, + }; + int i; + + for (i = 0; i < SDL_arraysize(f_and_g_test_cases); ++i) { + float value = f_and_g_test_cases[i].value; + + result = SDL_swprintf(text, sizeof(text), L"%f", value); + predicted = SDL_swprintf(NULL, 0, L"%f", value); + expected = f_and_g_test_cases[i].expected_f; + SDLTest_AssertPass("Call to SDL_swprintf(\"%%f\", %g)", value); + SDLTest_AssertCheck(SDL_wcscmp(text, expected) == 0, "Check text, expected: '%s', got: '%s'", expected, text); + SDLTest_AssertCheck(result == SDL_wcslen(expected), "Check result value, expected: %d, got: %d", (int)SDL_wcslen(expected), result); + SDLTest_AssertCheck(predicted == result, "Check predicted value, expected: %d, got: %d", result, predicted); + + result = SDL_swprintf(text, sizeof(text), L"%g", value); + predicted = SDL_swprintf(NULL, 0, L"%g", value); + expected = f_and_g_test_cases[i].expected_g; + SDLTest_AssertPass("Call to SDL_swprintf(\"%%g\", %g)", value); + SDLTest_AssertCheck(SDL_wcscmp(text, expected) == 0, "Check text, expected: '%s', got: '%s'", expected, text); + SDLTest_AssertCheck(result == SDL_wcslen(expected), "Check result value, expected: %d, got: %d", (int)SDL_wcslen(expected), result); + SDLTest_AssertCheck(predicted == result, "Check predicted value, expected: %d, got: %d", result, predicted); + } + } + + size = 64; + result = SDL_swprintf(text, sizeof(text), L"%zu %s", size, "test"); + expected = L"64 test"; + SDLTest_AssertPass("Call to SDL_swprintf(text, sizeof(text), \"%%zu %%s\", size, \"test\")"); + SDLTest_AssertCheck(SDL_wcscmp(text, expected) == 0, "Check text, expected: '%s', got: '%s'", expected, text); + SDLTest_AssertCheck(result == 7, "Check result value, expected: 7, got: %d", result); + + return TEST_COMPLETED; +} + #if defined(HAVE_WFORMAT) || defined(HAVE_WFORMAT_EXTRA_ARGS) #pragma GCC diagnostic pop #endif @@ -635,14 +798,18 @@ static const SDLTest_TestCaseReference stdlibTest2 = { }; static const SDLTest_TestCaseReference stdlibTest3 = { - stdlib_getsetenv, "stdlib_getsetenv", "Call to SDL_getenv and SDL_setenv", TEST_ENABLED + stdlib_swprintf, "stdlib_swprintf", "Call to SDL_swprintf", TEST_ENABLED }; static const SDLTest_TestCaseReference stdlibTest4 = { - stdlib_sscanf, "stdlib_sscanf", "Call to SDL_sscanf", TEST_ENABLED + stdlib_getsetenv, "stdlib_getsetenv", "Call to SDL_getenv and SDL_setenv", TEST_ENABLED }; static const SDLTest_TestCaseReference stdlibTest5 = { + stdlib_sscanf, "stdlib_sscanf", "Call to SDL_sscanf", TEST_ENABLED +}; + +static const SDLTest_TestCaseReference stdlibTest6 = { stdlib_aligned_alloc, "stdlib_aligned_alloc", "Call to SDL_aligned_alloc", TEST_ENABLED }; @@ -657,6 +824,7 @@ static const SDLTest_TestCaseReference *stdlibTests[] = { &stdlibTest3, &stdlibTest4, &stdlibTest5, + &stdlibTest6, &stdlibTestOverflow, NULL }; From 7bbbfb00038e70882402bada8e0022d11098b428 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Wed, 24 May 2023 10:39:43 -0700 Subject: [PATCH 04/36] Added udev_device_get_syspath() to udev context --- src/core/linux/SDL_udev.c | 1 + src/core/linux/SDL_udev.h | 1 + 2 files changed, 2 insertions(+) diff --git a/src/core/linux/SDL_udev.c b/src/core/linux/SDL_udev.c index 973e2da9dacc0..73d3e74735994 100644 --- a/src/core/linux/SDL_udev.c +++ b/src/core/linux/SDL_udev.c @@ -64,6 +64,7 @@ static int SDL_UDEV_load_syms(void) SDL_UDEV_SYM(udev_device_get_action); SDL_UDEV_SYM(udev_device_get_devnode); + SDL_UDEV_SYM(udev_device_get_syspath); SDL_UDEV_SYM(udev_device_get_subsystem); SDL_UDEV_SYM(udev_device_get_parent_with_subsystem_devtype); SDL_UDEV_SYM(udev_device_get_property_value); diff --git a/src/core/linux/SDL_udev.h b/src/core/linux/SDL_udev.h index f5e831410f6e8..2f44e929452c0 100644 --- a/src/core/linux/SDL_udev.h +++ b/src/core/linux/SDL_udev.h @@ -56,6 +56,7 @@ typedef struct SDL_UDEV_Symbols { const char *(*udev_device_get_action)(struct udev_device *); const char *(*udev_device_get_devnode)(struct udev_device *); + const char *(*udev_device_get_syspath)(struct udev_device *); const char *(*udev_device_get_subsystem)(struct udev_device *); struct udev_device *(*udev_device_get_parent_with_subsystem_devtype)(struct udev_device *udev_device, const char *subsystem, const char *devtype); const char *(*udev_device_get_property_value)(struct udev_device *, const char *); From 2378ed1a0180e3cd6c96ef927524532a097c16ae Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Wed, 24 May 2023 18:37:42 -0700 Subject: [PATCH 05/36] Fixed build warning on Xcode 14.3 --- src/audio/coreaudio/SDL_coreaudio.m | 4 ++-- src/video/uikit/SDL_uikitevents.m | 2 +- src/video/uikit/SDL_uikitmodes.m | 2 +- src/video/uikit/SDL_uikitvideo.m | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/audio/coreaudio/SDL_coreaudio.m b/src/audio/coreaudio/SDL_coreaudio.m index f740961ee111f..1628c2c49f244 100644 --- a/src/audio/coreaudio/SDL_coreaudio.m +++ b/src/audio/coreaudio/SDL_coreaudio.m @@ -291,7 +291,7 @@ static OSStatus device_list_changed(AudioObjectID systemObj, UInt32 num_addr, co static BOOL session_active = NO; -static void pause_audio_devices() +static void pause_audio_devices(void) { int i; @@ -307,7 +307,7 @@ static void pause_audio_devices() } } -static void resume_audio_devices() +static void resume_audio_devices(void) { int i; diff --git a/src/video/uikit/SDL_uikitevents.m b/src/video/uikit/SDL_uikitevents.m index 1c6fe13daf753..4bec9685a853e 100644 --- a/src/video/uikit/SDL_uikitevents.m +++ b/src/video/uikit/SDL_uikitevents.m @@ -274,7 +274,7 @@ void SDL_QuitGCKeyboard(void) static id mouse_disconnect_observer = nil; static bool mouse_relative_mode = SDL_FALSE; -static void UpdatePointerLock() +static void UpdatePointerLock(void) { SDL_VideoDevice *_this = SDL_GetVideoDevice(); SDL_Window *window; diff --git a/src/video/uikit/SDL_uikitmodes.m b/src/video/uikit/SDL_uikitmodes.m index 6d5ecd9ffb5ec..220589d10b33a 100644 --- a/src/video/uikit/SDL_uikitmodes.m +++ b/src/video/uikit/SDL_uikitmodes.m @@ -397,7 +397,7 @@ void UIKit_QuitModes(SDL_VideoDevice *_this) } #if !TARGET_OS_TV -void SDL_OnApplicationDidChangeStatusBarOrientation() +void SDL_OnApplicationDidChangeStatusBarOrientation(void) { BOOL isLandscape = UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation); SDL_VideoDisplay *display = SDL_GetVideoDisplay(SDL_GetPrimaryDisplay()); diff --git a/src/video/uikit/SDL_uikitvideo.m b/src/video/uikit/SDL_uikitvideo.m index 13e12e5da0ce1..2eccb1bec0d97 100644 --- a/src/video/uikit/SDL_uikitvideo.m +++ b/src/video/uikit/SDL_uikitvideo.m @@ -234,7 +234,7 @@ CGRect UIKit_ComputeViewFrame(SDL_Window *window, UIScreen *screen) return frame; } -void UIKit_ForceUpdateHomeIndicator() +void UIKit_ForceUpdateHomeIndicator(void) { #if !TARGET_OS_TV /* Force the main SDL window to re-evaluate home indicator state */ From ff94d6d8d020c833dd6152eded4f624686fd842a Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Thu, 25 May 2023 09:53:40 -0700 Subject: [PATCH 06/36] Fixed crash if trying to dump a packet larger than USB_PACKET_LENGTH --- src/joystick/hidapi/SDL_hidapijoystick.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/joystick/hidapi/SDL_hidapijoystick.c b/src/joystick/hidapi/SDL_hidapijoystick.c index 393524dbdd975..daea81cd56c7d 100644 --- a/src/joystick/hidapi/SDL_hidapijoystick.c +++ b/src/joystick/hidapi/SDL_hidapijoystick.c @@ -117,7 +117,7 @@ void HIDAPI_DumpPacket(const char *prefix, const Uint8 *data, int size) { int i; char *buffer; - size_t length = SDL_strlen(prefix) + 11 * (USB_PACKET_LENGTH / 8) + (5 * USB_PACKET_LENGTH * 2) + 1 + 1; + size_t length = SDL_strlen(prefix) + 11 * (size / 8) + (5 * size * 2) + 1 + 1; int start = 0, amount = size; size_t current_len; From 0bce0fa6e5d1f5ebf3ab6b7bf694c5dee81725a3 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Thu, 25 May 2023 15:58:32 -0700 Subject: [PATCH 07/36] Disable HIDAPI libusb support on FreeBSD in CI It looks like we're expecting a newer version of libusb than is installed on our VM image. Disabling pending further investigation. --- .github/workflows/vmactions.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/vmactions.yml b/.github/workflows/vmactions.yml index f701c8dbb0ea6..e24f9d0ad1df2 100644 --- a/.github/workflows/vmactions.yml +++ b/.github/workflows/vmactions.yml @@ -52,6 +52,7 @@ jobs: cmake -S . -B build -GNinja \ -Wdeprecated -Wdev -Werror \ -DCMAKE_BUILD_TYPE=Release \ + -DSDL_HIDAPI_LIBUSB=OFF \ -DSDL_CHECK_REQUIRED_INCLUDES="/usr/local/include" \ -DSDL_CHECK_REQUIRED_LINK_OPTIONS="-L/usr/local/lib" cmake --build build/ --config Release --verbose -- -j`sysctl -n hw.ncpu` From 8aafbbe65560ab8169cf642ddbf337612cf695a4 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Wed, 24 May 2023 07:28:55 -0700 Subject: [PATCH 08/36] Updated hidapi to 0.14.0 release Upstream: https://github.com/libusb/hidapi/releases/tag/hidapi-0.14.0 --- src/hidapi/.appveyor.yml | 31 + src/hidapi/.builds/freebsd.yml | 34 + src/hidapi/.builds/netbsd.yml | 18 + src/hidapi/.builds/openbsd.yml | 19 + src/hidapi/.cirrus.yml | 33 + src/hidapi/.gitattributes | 7 + src/hidapi/.github/workflows/builds.yml | 540 + src/hidapi/.github/workflows/checks.yml | 196 + src/hidapi/.github/workflows/docs.yaml | 58 + src/hidapi/.gitignore | 32 + src/hidapi/AUTHORS.txt | 4 +- src/hidapi/BUILD.autotools.md | 114 + src/hidapi/BUILD.cmake.md | 280 + src/hidapi/BUILD.md | 127 + src/hidapi/CMakeLists.txt | 105 + src/hidapi/HACKING.txt | 26 +- src/hidapi/Makefile.am | 10 +- src/hidapi/README.md | 196 + src/hidapi/README.txt | 339 - src/hidapi/VERSION | 1 + src/hidapi/android/hid.cpp | 1443 -- src/hidapi/android/hid.h | 39 - src/hidapi/android/jni/Android.mk | 16 - src/hidapi/android/jni/Application.mk | 2 - src/hidapi/android/project.properties | 14 - src/hidapi/configure.ac | 68 +- src/hidapi/dist/hidapi.podspec | 31 + .../documentation/cmake-gui-drop-down.png | Bin 0 -> 22316 bytes .../documentation/cmake-gui-highlights.png | Bin 0 -> 77327 bytes src/hidapi/doxygen/Doxyfile | 2832 ++-- src/hidapi/doxygen/main_page.md | 13 + src/hidapi/hidapi/hidapi.h | 319 +- src/hidapi/hidtest/.gitignore | 17 + src/hidapi/hidtest/CMakeLists.txt | 40 + src/hidapi/hidtest/Makefile.am | 14 +- src/hidapi/hidtest/hidtest.cpp | 194 - src/hidapi/hidtest/test.c | 316 + src/hidapi/ios/Makefile-manual | 32 - src/hidapi/ios/Makefile.am | 9 - src/hidapi/ios/hid.m | 996 -- src/hidapi/libusb/.gitignore | 8 + src/hidapi/libusb/CMakeLists.txt | 107 + src/hidapi/libusb/Makefile-manual | 4 + src/hidapi/libusb/Makefile.am | 9 +- src/hidapi/libusb/Makefile.freebsd | 15 +- src/hidapi/libusb/Makefile.haiku | 39 + src/hidapi/libusb/Makefile.linux | 17 +- src/hidapi/libusb/hid.c | 1317 +- src/hidapi/libusb/hidapi_libusb.h | 56 + src/hidapi/libusb/hidusb.cpp | 3 - src/hidapi/linux/.gitignore | 18 + src/hidapi/linux/CMakeLists.txt | 38 + src/hidapi/linux/Makefile-manual | 17 +- src/hidapi/linux/README.txt | 59 - src/hidapi/linux/hid.c | 1324 +- src/hidapi/linux/hidraw.cpp | 3 - src/hidapi/m4/.gitignore | 5 + src/hidapi/mac/.gitignore | 17 + src/hidapi/mac/CMakeLists.txt | 48 + src/hidapi/mac/Makefile-manual | 17 +- src/hidapi/mac/hid.c | 1597 ++- src/hidapi/mac/hidapi_darwin.h | 98 + src/hidapi/meson.build | 22 + src/hidapi/pc/.gitignore | 1 + src/hidapi/pc/hidapi-hidraw.pc.in | 1 + src/hidapi/pc/hidapi-libusb.pc.in | 1 + src/hidapi/pc/hidapi.pc.in | 1 + src/hidapi/src/CMakeLists.txt | 193 + src/hidapi/src/cmake/hidapi-config.cmake.in | 61 + src/hidapi/subprojects/README.md | 2 + .../hidapi_build_cmake/CMakeLists.txt | 10 + src/hidapi/testgui/.gitignore | 20 + src/hidapi/testgui/Makefile.mingw | 2 +- src/hidapi/testgui/copy_to_bundle.sh | 3 +- src/hidapi/testgui/mac_support.cpp | 134 - src/hidapi/testgui/mac_support_cocoa.m | 13 +- src/hidapi/testgui/start.sh | 2 - src/hidapi/testgui/testgui.sln | 40 +- src/hidapi/testgui/testgui.vcproj | 434 +- src/hidapi/udev/69-hid.rules | 36 + src/hidapi/udev/99-hid.rules | 33 - src/hidapi/windows/.gitignore | 17 + src/hidapi/windows/CMakeLists.txt | 63 + src/hidapi/windows/Makefile.am | 1 - src/hidapi/windows/Makefile.mingw | 17 +- src/hidapi/windows/ddk_build/hidapi.def | 17 - src/hidapi/windows/ddk_build/makefile | 49 - src/hidapi/windows/ddk_build/sources | 23 - src/hidapi/windows/hid.c | 1476 +- src/hidapi/windows/hidapi.rc | 35 + src/hidapi/windows/hidapi.sln | 70 +- src/hidapi/windows/hidapi.vcproj | 401 +- src/hidapi/windows/hidapi.vcxproj | 200 + src/hidapi/windows/hidapi_cfgmgr32.h | 30 +- .../windows/hidapi_descriptor_reconstruct.c | 987 ++ .../windows/hidapi_descriptor_reconstruct.h | 238 + src/hidapi/windows/hidapi_hidpi.h | 7 + src/hidapi/windows/hidapi_hidsdi.h | 3 +- src/hidapi/windows/hidapi_winapi.h | 74 + src/hidapi/windows/hidtest.vcproj | 392 +- src/hidapi/windows/hidtest.vcxproj | 176 + .../windows/pp_data_dump/CMakeLists.txt | 15 + src/hidapi/windows/pp_data_dump/README.md | 122 + .../windows/pp_data_dump/pp_data_dump.c | 238 + src/hidapi/windows/test/CMakeLists.txt | 76 + .../test/data/045E_02FF_0005_0001.pp_data | 420 + .../045E_02FF_0005_0001_expected.rpt_desc | 12 + .../data/045E_02FF_0005_0001_real.rpt_desc | 64 + .../test/data/046A_0011_0006_0001.pp_data | 183 + .../046A_0011_0006_0001_expected.rpt_desc | 7 + .../data/046A_0011_0006_0001_real.rpt_desc | 7 + .../test/data/046D_0A37_0001_000C.pp_data | 532 + .../046D_0A37_0001_000C_expected.rpt_desc | 16 + .../data/046D_0A37_0001_000C_real.rpt_desc | 61 + .../test/data/046D_B010_0001_000C.pp_data | 97 + .../046D_B010_0001_000C_expected.rpt_desc | 3 + .../data/046D_B010_0001_000C_real.rpt_desc | 38 + .../test/data/046D_B010_0001_FF00.pp_data | 139 + .../046D_B010_0001_FF00_expected.rpt_desc | 4 + .../data/046D_B010_0001_FF00_real.rpt_desc | 39 + .../test/data/046D_B010_0002_0001.pp_data | 302 + .../046D_B010_0002_0001_expected.rpt_desc | 8 + .../data/046D_B010_0002_0001_real.rpt_desc | 61 + .../test/data/046D_B010_0002_FF00.pp_data | 139 + .../046D_B010_0002_FF00_expected.rpt_desc | 4 + .../data/046D_B010_0002_FF00_real.rpt_desc | 39 + .../test/data/046D_B010_0006_0001.pp_data | 185 + .../046D_B010_0006_0001_expected.rpt_desc | 7 + .../data/046D_B010_0006_0001_real.rpt_desc | 58 + .../test/data/046D_C077_0002_0001.pp_data | 252 + .../046D_C077_0002_0001_expected.rpt_desc | 5 + .../data/046D_C077_0002_0001_real.rpt_desc | 24 + .../test/data/046D_C283_0004_0001.pp_data | 520 + .../046D_C283_0004_0001_expected.rpt_desc | 18 + .../data/046D_C283_0004_0001_real.rpt_desc | 18 + .../test/data/046D_C52F_0001_000C.pp_data | 93 + .../046D_C52F_0001_000C_expected.rpt_desc | 3 + .../data/046D_C52F_0001_000C_real.rpt_desc | 12 + .../test/data/046D_C52F_0001_FF00.pp_data | 139 + .../046D_C52F_0001_FF00_expected.rpt_desc | 4 + .../data/046D_C52F_0001_FF00_real.rpt_desc | 13 + .../test/data/046D_C52F_0002_0001.pp_data | 302 + .../046D_C52F_0002_0001_expected.rpt_desc | 8 + .../data/046D_C52F_0002_0001_real.rpt_desc | 33 + .../test/data/046D_C52F_0002_FF00.pp_data | 139 + .../046D_C52F_0002_FF00_expected.rpt_desc | 4 + .../data/046D_C52F_0002_FF00_real.rpt_desc | 13 + .../test/data/046D_C534_0001_000C.pp_data | 93 + .../046D_C534_0001_000C_expected.rpt_desc | 3 + .../data/046D_C534_0001_000C_real.rpt_desc | 18 + .../test/data/046D_C534_0001_FF00.pp_data | 139 + .../046D_C534_0001_FF00_expected.rpt_desc | 4 + .../data/046D_C534_0001_FF00_real.rpt_desc | 20 + .../test/data/046D_C534_0002_0001.pp_data | 302 + .../046D_C534_0002_0001_expected.rpt_desc | 8 + .../data/046D_C534_0002_0001_real.rpt_desc | 44 + .../test/data/046D_C534_0002_FF00.pp_data | 139 + .../046D_C534_0002_FF00_expected.rpt_desc | 4 + .../data/046D_C534_0002_FF00_real.rpt_desc | 22 + .../test/data/046D_C534_0006_0001.pp_data | 185 + .../046D_C534_0006_0001_expected.rpt_desc | 7 + .../data/046D_C534_0006_0001_real.rpt_desc | 42 + .../test/data/046D_C534_0080_0001.pp_data | 185 + .../046D_C534_0080_0001_expected.rpt_desc | 4 + .../data/046D_C534_0080_0001_real.rpt_desc | 22 + .../test/data/047F_C056_0001_000C.pp_data | 385 + .../047F_C056_0001_000C_expected.rpt_desc | 10 + .../data/047F_C056_0001_000C_real.rpt_desc | 47 + .../test/data/047F_C056_0003_FFA0.pp_data | 1255 ++ .../047F_C056_0003_FFA0_expected.rpt_desc | 24 + .../data/047F_C056_0003_FFA0_real.rpt_desc | 113 + .../test/data/047F_C056_0005_000B.pp_data | 461 + .../047F_C056_0005_000B_expected.rpt_desc | 17 + .../data/047F_C056_0005_000B_real.rpt_desc | 68 + .../test/data/17CC_1130_0000_FF01.pp_data | 11508 ++++++++++++++++ .../17CC_1130_0000_FF01_expected.rpt_desc | 75 + .../data/17CC_1130_0000_FF01_real.rpt_desc | 381 + .../test/hid_report_reconstructor_test.c | 561 + 178 files changed, 31624 insertions(+), 7664 deletions(-) create mode 100644 src/hidapi/.appveyor.yml create mode 100644 src/hidapi/.builds/freebsd.yml create mode 100644 src/hidapi/.builds/netbsd.yml create mode 100644 src/hidapi/.builds/openbsd.yml create mode 100644 src/hidapi/.cirrus.yml create mode 100644 src/hidapi/.gitattributes create mode 100644 src/hidapi/.github/workflows/builds.yml create mode 100644 src/hidapi/.github/workflows/checks.yml create mode 100644 src/hidapi/.github/workflows/docs.yaml create mode 100644 src/hidapi/.gitignore create mode 100644 src/hidapi/BUILD.autotools.md create mode 100644 src/hidapi/BUILD.cmake.md create mode 100644 src/hidapi/BUILD.md create mode 100644 src/hidapi/CMakeLists.txt create mode 100644 src/hidapi/README.md delete mode 100644 src/hidapi/README.txt create mode 100644 src/hidapi/VERSION delete mode 100644 src/hidapi/android/hid.cpp delete mode 100644 src/hidapi/android/hid.h delete mode 100644 src/hidapi/android/jni/Android.mk delete mode 100644 src/hidapi/android/jni/Application.mk delete mode 100644 src/hidapi/android/project.properties create mode 100644 src/hidapi/dist/hidapi.podspec create mode 100644 src/hidapi/documentation/cmake-gui-drop-down.png create mode 100644 src/hidapi/documentation/cmake-gui-highlights.png create mode 100644 src/hidapi/doxygen/main_page.md create mode 100644 src/hidapi/hidtest/.gitignore create mode 100644 src/hidapi/hidtest/CMakeLists.txt delete mode 100644 src/hidapi/hidtest/hidtest.cpp create mode 100644 src/hidapi/hidtest/test.c delete mode 100644 src/hidapi/ios/Makefile-manual delete mode 100644 src/hidapi/ios/Makefile.am delete mode 100644 src/hidapi/ios/hid.m create mode 100644 src/hidapi/libusb/.gitignore create mode 100644 src/hidapi/libusb/CMakeLists.txt create mode 100644 src/hidapi/libusb/Makefile.haiku create mode 100644 src/hidapi/libusb/hidapi_libusb.h delete mode 100644 src/hidapi/libusb/hidusb.cpp create mode 100644 src/hidapi/linux/.gitignore create mode 100644 src/hidapi/linux/CMakeLists.txt delete mode 100644 src/hidapi/linux/README.txt delete mode 100644 src/hidapi/linux/hidraw.cpp create mode 100644 src/hidapi/m4/.gitignore create mode 100644 src/hidapi/mac/.gitignore create mode 100644 src/hidapi/mac/CMakeLists.txt create mode 100644 src/hidapi/mac/hidapi_darwin.h create mode 100644 src/hidapi/meson.build create mode 100644 src/hidapi/pc/.gitignore create mode 100644 src/hidapi/src/CMakeLists.txt create mode 100644 src/hidapi/src/cmake/hidapi-config.cmake.in create mode 100644 src/hidapi/subprojects/README.md create mode 100644 src/hidapi/subprojects/hidapi_build_cmake/CMakeLists.txt create mode 100644 src/hidapi/testgui/.gitignore delete mode 100644 src/hidapi/testgui/mac_support.cpp delete mode 100755 src/hidapi/testgui/start.sh create mode 100644 src/hidapi/udev/69-hid.rules delete mode 100644 src/hidapi/udev/99-hid.rules create mode 100644 src/hidapi/windows/.gitignore create mode 100644 src/hidapi/windows/CMakeLists.txt delete mode 100644 src/hidapi/windows/ddk_build/hidapi.def delete mode 100644 src/hidapi/windows/ddk_build/makefile delete mode 100644 src/hidapi/windows/ddk_build/sources create mode 100644 src/hidapi/windows/hidapi.rc create mode 100644 src/hidapi/windows/hidapi.vcxproj create mode 100644 src/hidapi/windows/hidapi_descriptor_reconstruct.c create mode 100644 src/hidapi/windows/hidapi_descriptor_reconstruct.h create mode 100644 src/hidapi/windows/hidapi_winapi.h create mode 100644 src/hidapi/windows/hidtest.vcxproj create mode 100644 src/hidapi/windows/pp_data_dump/CMakeLists.txt create mode 100644 src/hidapi/windows/pp_data_dump/README.md create mode 100644 src/hidapi/windows/pp_data_dump/pp_data_dump.c create mode 100644 src/hidapi/windows/test/CMakeLists.txt create mode 100644 src/hidapi/windows/test/data/045E_02FF_0005_0001.pp_data create mode 100644 src/hidapi/windows/test/data/045E_02FF_0005_0001_expected.rpt_desc create mode 100644 src/hidapi/windows/test/data/045E_02FF_0005_0001_real.rpt_desc create mode 100644 src/hidapi/windows/test/data/046A_0011_0006_0001.pp_data create mode 100644 src/hidapi/windows/test/data/046A_0011_0006_0001_expected.rpt_desc create mode 100644 src/hidapi/windows/test/data/046A_0011_0006_0001_real.rpt_desc create mode 100644 src/hidapi/windows/test/data/046D_0A37_0001_000C.pp_data create mode 100644 src/hidapi/windows/test/data/046D_0A37_0001_000C_expected.rpt_desc create mode 100644 src/hidapi/windows/test/data/046D_0A37_0001_000C_real.rpt_desc create mode 100644 src/hidapi/windows/test/data/046D_B010_0001_000C.pp_data create mode 100644 src/hidapi/windows/test/data/046D_B010_0001_000C_expected.rpt_desc create mode 100644 src/hidapi/windows/test/data/046D_B010_0001_000C_real.rpt_desc create mode 100644 src/hidapi/windows/test/data/046D_B010_0001_FF00.pp_data create mode 100644 src/hidapi/windows/test/data/046D_B010_0001_FF00_expected.rpt_desc create mode 100644 src/hidapi/windows/test/data/046D_B010_0001_FF00_real.rpt_desc create mode 100644 src/hidapi/windows/test/data/046D_B010_0002_0001.pp_data create mode 100644 src/hidapi/windows/test/data/046D_B010_0002_0001_expected.rpt_desc create mode 100644 src/hidapi/windows/test/data/046D_B010_0002_0001_real.rpt_desc create mode 100644 src/hidapi/windows/test/data/046D_B010_0002_FF00.pp_data create mode 100644 src/hidapi/windows/test/data/046D_B010_0002_FF00_expected.rpt_desc create mode 100644 src/hidapi/windows/test/data/046D_B010_0002_FF00_real.rpt_desc create mode 100644 src/hidapi/windows/test/data/046D_B010_0006_0001.pp_data create mode 100644 src/hidapi/windows/test/data/046D_B010_0006_0001_expected.rpt_desc create mode 100644 src/hidapi/windows/test/data/046D_B010_0006_0001_real.rpt_desc create mode 100644 src/hidapi/windows/test/data/046D_C077_0002_0001.pp_data create mode 100644 src/hidapi/windows/test/data/046D_C077_0002_0001_expected.rpt_desc create mode 100644 src/hidapi/windows/test/data/046D_C077_0002_0001_real.rpt_desc create mode 100644 src/hidapi/windows/test/data/046D_C283_0004_0001.pp_data create mode 100644 src/hidapi/windows/test/data/046D_C283_0004_0001_expected.rpt_desc create mode 100644 src/hidapi/windows/test/data/046D_C283_0004_0001_real.rpt_desc create mode 100644 src/hidapi/windows/test/data/046D_C52F_0001_000C.pp_data create mode 100644 src/hidapi/windows/test/data/046D_C52F_0001_000C_expected.rpt_desc create mode 100644 src/hidapi/windows/test/data/046D_C52F_0001_000C_real.rpt_desc create mode 100644 src/hidapi/windows/test/data/046D_C52F_0001_FF00.pp_data create mode 100644 src/hidapi/windows/test/data/046D_C52F_0001_FF00_expected.rpt_desc create mode 100644 src/hidapi/windows/test/data/046D_C52F_0001_FF00_real.rpt_desc create mode 100644 src/hidapi/windows/test/data/046D_C52F_0002_0001.pp_data create mode 100644 src/hidapi/windows/test/data/046D_C52F_0002_0001_expected.rpt_desc create mode 100644 src/hidapi/windows/test/data/046D_C52F_0002_0001_real.rpt_desc create mode 100644 src/hidapi/windows/test/data/046D_C52F_0002_FF00.pp_data create mode 100644 src/hidapi/windows/test/data/046D_C52F_0002_FF00_expected.rpt_desc create mode 100644 src/hidapi/windows/test/data/046D_C52F_0002_FF00_real.rpt_desc create mode 100644 src/hidapi/windows/test/data/046D_C534_0001_000C.pp_data create mode 100644 src/hidapi/windows/test/data/046D_C534_0001_000C_expected.rpt_desc create mode 100644 src/hidapi/windows/test/data/046D_C534_0001_000C_real.rpt_desc create mode 100644 src/hidapi/windows/test/data/046D_C534_0001_FF00.pp_data create mode 100644 src/hidapi/windows/test/data/046D_C534_0001_FF00_expected.rpt_desc create mode 100644 src/hidapi/windows/test/data/046D_C534_0001_FF00_real.rpt_desc create mode 100644 src/hidapi/windows/test/data/046D_C534_0002_0001.pp_data create mode 100644 src/hidapi/windows/test/data/046D_C534_0002_0001_expected.rpt_desc create mode 100644 src/hidapi/windows/test/data/046D_C534_0002_0001_real.rpt_desc create mode 100644 src/hidapi/windows/test/data/046D_C534_0002_FF00.pp_data create mode 100644 src/hidapi/windows/test/data/046D_C534_0002_FF00_expected.rpt_desc create mode 100644 src/hidapi/windows/test/data/046D_C534_0002_FF00_real.rpt_desc create mode 100644 src/hidapi/windows/test/data/046D_C534_0006_0001.pp_data create mode 100644 src/hidapi/windows/test/data/046D_C534_0006_0001_expected.rpt_desc create mode 100644 src/hidapi/windows/test/data/046D_C534_0006_0001_real.rpt_desc create mode 100644 src/hidapi/windows/test/data/046D_C534_0080_0001.pp_data create mode 100644 src/hidapi/windows/test/data/046D_C534_0080_0001_expected.rpt_desc create mode 100644 src/hidapi/windows/test/data/046D_C534_0080_0001_real.rpt_desc create mode 100644 src/hidapi/windows/test/data/047F_C056_0001_000C.pp_data create mode 100644 src/hidapi/windows/test/data/047F_C056_0001_000C_expected.rpt_desc create mode 100644 src/hidapi/windows/test/data/047F_C056_0001_000C_real.rpt_desc create mode 100644 src/hidapi/windows/test/data/047F_C056_0003_FFA0.pp_data create mode 100644 src/hidapi/windows/test/data/047F_C056_0003_FFA0_expected.rpt_desc create mode 100644 src/hidapi/windows/test/data/047F_C056_0003_FFA0_real.rpt_desc create mode 100644 src/hidapi/windows/test/data/047F_C056_0005_000B.pp_data create mode 100644 src/hidapi/windows/test/data/047F_C056_0005_000B_expected.rpt_desc create mode 100644 src/hidapi/windows/test/data/047F_C056_0005_000B_real.rpt_desc create mode 100644 src/hidapi/windows/test/data/17CC_1130_0000_FF01.pp_data create mode 100644 src/hidapi/windows/test/data/17CC_1130_0000_FF01_expected.rpt_desc create mode 100644 src/hidapi/windows/test/data/17CC_1130_0000_FF01_real.rpt_desc create mode 100644 src/hidapi/windows/test/hid_report_reconstructor_test.c diff --git a/src/hidapi/.appveyor.yml b/src/hidapi/.appveyor.yml new file mode 100644 index 0000000000000..210b3fa4636ff --- /dev/null +++ b/src/hidapi/.appveyor.yml @@ -0,0 +1,31 @@ +environment: + matrix: + - BUILD_ENV: msbuild + arch: x64 + - BUILD_ENV: msbuild + arch: Win32 + - BUILD_ENV: cygwin + +for: + - + matrix: + only: + - BUILD_ENV: msbuild + + os: Visual Studio 2015 + + build_script: + - cmd: msbuild .\windows\hidapi.sln /p:Configuration=Release /p:Platform=%arch% /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" + + - + matrix: + only: + - BUILD_ENV: cygwin + + os: Visual Studio 2022 + + install: + - cmd: C:\cygwin64\setup-x86_64.exe --quiet-mode --no-shortcuts --upgrade-also --packages autoconf,automake + + build_script: + - cmd: C:\cygwin64\bin\bash -exlc "cd $APPVEYOR_BUILD_FOLDER; ./bootstrap; ./configure; make" diff --git a/src/hidapi/.builds/freebsd.yml b/src/hidapi/.builds/freebsd.yml new file mode 100644 index 0000000000000..1679b03c386b4 --- /dev/null +++ b/src/hidapi/.builds/freebsd.yml @@ -0,0 +1,34 @@ +image: freebsd/latest +packages: +- autoconf +- automake +- gmake +- libiconv +- libtool +- pkgconf +- cmake +- ninja +sources: +- https://github.com/libusb/hidapi +tasks: +- configure: | + cd hidapi + echo Configure Autotools build + ./bootstrap + ./configure + echo Configure CMake build + mkdir -p build install_cmake + cmake -GNinja -B build -S . -DCMAKE_INSTALL_PREFIX=install_cmake +- build-autotools: | + cd hidapi + make + make DESTDIR=$PWD/root install + make clean +- build-cmake: | + cd hidapi/build + ninja + ninja install + ninja clean +- build-manual: | + cd hidapi/libusb + gmake -f Makefile-manual diff --git a/src/hidapi/.builds/netbsd.yml b/src/hidapi/.builds/netbsd.yml new file mode 100644 index 0000000000000..413d91c020163 --- /dev/null +++ b/src/hidapi/.builds/netbsd.yml @@ -0,0 +1,18 @@ +image: netbsd/latest +packages: +- cmake +- pkgconf +- libusb1 +- libiconv +sources: +- https://github.com/libusb/hidapi +tasks: +- configure: | + cd hidapi + mkdir -p build install + cmake -B build -S . -DCMAKE_INSTALL_PREFIX=install +- build: | + cd hidapi/build + make + make install + make clean diff --git a/src/hidapi/.builds/openbsd.yml b/src/hidapi/.builds/openbsd.yml new file mode 100644 index 0000000000000..780df7f661a4c --- /dev/null +++ b/src/hidapi/.builds/openbsd.yml @@ -0,0 +1,19 @@ +image: openbsd/latest +packages: +- cmake +- pkgconf +- libusb1-- +- libiconv +- ninja +sources: +- https://github.com/libusb/hidapi +tasks: +- configure: | + cd hidapi + mkdir -p build install + cmake -GNinja -B build -S . -DCMAKE_INSTALL_PREFIX=install +- build: | + cd hidapi/build + ninja + ninja install + ninja clean diff --git a/src/hidapi/.cirrus.yml b/src/hidapi/.cirrus.yml new file mode 100644 index 0000000000000..b4cf201667fe8 --- /dev/null +++ b/src/hidapi/.cirrus.yml @@ -0,0 +1,33 @@ +alpine_task: + container: + image: alpine:latest + install_script: apk add autoconf automake g++ gcc libusb-dev libtool linux-headers eudev-dev make musl-dev + script: + - ./bootstrap + - ./configure || { cat config.log; exit 1; } + - make + - make install + +freebsd11_task: + freebsd_instance: + image: freebsd-11-2-release-amd64 + install_script: + - pkg install -y + autoconf automake libiconv libtool pkgconf + script: + - ./bootstrap + - ./configure || { cat config.log; exit 1; } + - make + - make install + +freebsd12_task: + freebsd_instance: + image: freebsd-12-1-release-amd64 + install_script: + - pkg install -y + autoconf automake libiconv libtool pkgconf + script: + - ./bootstrap + - ./configure || { cat config.log; exit 1; } + - make + - make install diff --git a/src/hidapi/.gitattributes b/src/hidapi/.gitattributes new file mode 100644 index 0000000000000..edb79febc569b --- /dev/null +++ b/src/hidapi/.gitattributes @@ -0,0 +1,7 @@ +* text=auto + +*.sln text eol=crlf +*.vcproj text eol=crlf + +bootstrap text eol=lf +configure.ac text eol=lf diff --git a/src/hidapi/.github/workflows/builds.yml b/src/hidapi/.github/workflows/builds.yml new file mode 100644 index 0000000000000..056df2cc08c81 --- /dev/null +++ b/src/hidapi/.github/workflows/builds.yml @@ -0,0 +1,540 @@ +name: GitHub Builds + +on: [push, pull_request] + +env: + NIX_COMPILE_FLAGS: -Wall -Wextra -pedantic -Werror + MSVC_COMPILE_FLAGS: /W4 /WX + +jobs: + macos-automake: + + runs-on: macos-latest + + steps: + - uses: actions/checkout@v3 + - name: Install build tools + run: brew install autoconf automake libtool + - name: Configure Automake + run: | + ./bootstrap + ./configure --prefix=$(pwd)/install + - name: Build Automake + run: | + make + make install + - name: Clean build + run: make clean + - name: Build Manual makefile + working-directory: mac + run: make -f Makefile-manual + + + macos-cmake: + + runs-on: macos-latest + + steps: + - uses: actions/checkout@v3 + with: + path: hidapisrc + - name: Install dependencies + run: brew install meson ninja + - name: Configure CMake + run: | + rm -rf build install + cmake -B build/shared -S hidapisrc -DCMAKE_BUILD_TYPE=RelWithDebInfo -DHIDAPI_ENABLE_ASAN=ON -DCMAKE_INSTALL_PREFIX=install/shared -DHIDAPI_BUILD_HIDTEST=ON "-DCMAKE_C_FLAGS=${NIX_COMPILE_FLAGS}" + cmake -B build/static -S hidapisrc -DCMAKE_BUILD_TYPE=RelWithDebInfo -DHIDAPI_ENABLE_ASAN=ON -DCMAKE_INSTALL_PREFIX=install/static -DBUILD_SHARED_LIBS=FALSE -DHIDAPI_BUILD_HIDTEST=ON "-DCMAKE_C_FLAGS=${NIX_COMPILE_FLAGS}" + cmake -B build/framework -S hidapisrc -DCMAKE_BUILD_TYPE=RelWithDebInfo -DHIDAPI_ENABLE_ASAN=ON -DCMAKE_INSTALL_PREFIX=install/framework -DCMAKE_FRAMEWORK=ON -DHIDAPI_BUILD_HIDTEST=ON "-DCMAKE_C_FLAGS=${NIX_COMPILE_FLAGS}" + - name: Build CMake Shared + working-directory: build/shared + run: make install + - name: Build CMake Static + working-directory: build/static + run: make install + - name: Build CMake Framework + working-directory: build/framework + run: make install + - name: Check artifacts + uses: andstor/file-existence-action@v2 + with: + files: "install/shared/lib/libhidapi.dylib, \ + install/shared/include/hidapi/hidapi.h, \ + install/shared/include/hidapi/hidapi_darwin.h, \ + install/static/lib/libhidapi.a, \ + install/static/include/hidapi/hidapi.h, \ + install/static/include/hidapi/hidapi_darwin.h, \ + install/framework/lib/hidapi.framework/hidapi, \ + install/framework/lib/hidapi.framework/Headers/hidapi.h, \ + install/framework/lib/hidapi.framework/Headers/hidapi_darwin.h" + fail: true + - name: Check CMake Export Package Shared + run: | + cmake \ + -B build/shared_test \ + -S hidapisrc/hidtest \ + -Dhidapi_ROOT=install/shared \ + -DCMAKE_INSTALL_PREFIX=install/shared_test \ + "-DCMAKE_C_FLAGS=${NIX_COMPILE_FLAGS}" + cd build/shared_test + make install + - name: Check CMake Export Package Static + run: | + cmake \ + -B build/static_test \ + -S hidapisrc/hidtest \ + -Dhidapi_ROOT=install/static \ + -DCMAKE_INSTALL_PREFIX=install/static_test \ + "-DCMAKE_C_FLAGS=${NIX_COMPILE_FLAGS}" + cd build/static_test + make install + + - name: Check Meson build + run: | + meson setup build_meson hidapisrc + cd build_meson + ninja + + + ubuntu-cmake: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + with: + path: hidapisrc + - name: Install dependencies + run: | + sudo apt update + sudo apt install libudev-dev libusb-1.0-0-dev python3-pip ninja-build + sudo -H pip3 install meson + - name: Configure CMake + run: | + rm -rf build install + cmake -B build/shared -S hidapisrc -DCMAKE_BUILD_TYPE=RelWithDebInfo -DHIDAPI_ENABLE_ASAN=ON -DCMAKE_INSTALL_PREFIX=install/shared -DHIDAPI_BUILD_HIDTEST=ON "-DCMAKE_C_FLAGS=${NIX_COMPILE_FLAGS}" + cmake -B build/static -S hidapisrc -DCMAKE_BUILD_TYPE=RelWithDebInfo -DHIDAPI_ENABLE_ASAN=ON -DCMAKE_INSTALL_PREFIX=install/static -DBUILD_SHARED_LIBS=FALSE -DHIDAPI_BUILD_HIDTEST=ON "-DCMAKE_C_FLAGS=${NIX_COMPILE_FLAGS}" + - name: Build CMake Shared + working-directory: build/shared + run: make install + - name: Build CMake Static + working-directory: build/static + run: make install + - name: Check artifacts + uses: andstor/file-existence-action@v2 + with: + files: "install/shared/lib/libhidapi-libusb.so, \ + install/shared/lib/libhidapi-hidraw.so, \ + install/shared/include/hidapi/hidapi.h, \ + install/shared/include/hidapi/hidapi_libusb.h, \ + install/static/lib/libhidapi-libusb.a, \ + install/static/lib/libhidapi-hidraw.a, \ + install/static/include/hidapi/hidapi.h, \ + install/static/include/hidapi/hidapi_libusb.h" + fail: true + - name: Check CMake Export Package Shared + run: | + cmake \ + -B build/shared_test \ + -S hidapisrc/hidtest \ + -Dhidapi_ROOT=install/shared \ + -DCMAKE_INSTALL_PREFIX=install/shared_test \ + "-DCMAKE_C_FLAGS=${NIX_COMPILE_FLAGS}" + cd build/shared_test + make install + - name: Check CMake Export Package Static + run: | + cmake \ + -B build/static_test \ + -S hidapisrc/hidtest \ + -Dhidapi_ROOT=install/static \ + -DCMAKE_INSTALL_PREFIX=install/static_test \ + "-DCMAKE_C_FLAGS=${NIX_COMPILE_FLAGS}" + cd build/static_test + make install + + - name: Check Meson build + run: | + meson setup build_meson hidapisrc + cd build_meson + ninja + + + windows-cmake: + + runs-on: windows-latest + + steps: + - uses: actions/checkout@v3 + with: + path: hidapisrc + - name: Install dependencies + run: | + choco install ninja + pip3 install meson + refreshenv + - name: Configure CMake MSVC + shell: cmd + run: | + cmake -B build\msvc -S hidapisrc -DCMAKE_BUILD_TYPE=RelWithDebInfo -DHIDAPI_WITH_TESTS=ON -DHIDAPI_BUILD_PP_DATA_DUMP=ON -DHIDAPI_ENABLE_ASAN=ON -DCMAKE_INSTALL_PREFIX=install\msvc -DHIDAPI_BUILD_HIDTEST=ON "-DCMAKE_C_FLAGS=%MSVC_COMPILE_FLAGS%" + - name: Build CMake MSVC + working-directory: build/msvc + run: cmake --build . --config RelWithDebInfo --target install + - name: Check artifacts MSVC + uses: andstor/file-existence-action@v2 + with: + files: "install/msvc/lib/hidapi.lib, \ + install/msvc/bin/hidapi.dll, \ + install/msvc/include/hidapi/hidapi.h, \ + install/msvc/include/hidapi/hidapi_winapi.h" + fail: true + - name: Check CMake Export Package + shell: cmd + run: | + cmake ^ + -B build\msvc_test ^ + -S hidapisrc\hidtest ^ + -Dhidapi_ROOT=install\msvc ^ + -DCMAKE_INSTALL_PREFIX=install\msvc_test ^ + "-DCMAKE_C_FLAGS=%MSVC_COMPILE_FLAGS%" + cd build\msvc_test + cmake --build . --target install + - name: Run CTest MSVC + shell: cmd + working-directory: build/msvc + run: ctest -C RelWithDebInfo --rerun-failed --output-on-failure + + - name: Configure CMake NMake + shell: cmd + run: | + call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" + cmake -G"NMake Makefiles" -B build\nmake -S hidapisrc -DCMAKE_BUILD_TYPE=RelWithDebInfo -DHIDAPI_WITH_TESTS=ON -DHIDAPI_BUILD_PP_DATA_DUMP=ON -DHIDAPI_ENABLE_ASAN=ON -DCMAKE_INSTALL_PREFIX=install\nmake -DHIDAPI_BUILD_HIDTEST=ON "-DCMAKE_C_FLAGS=%MSVC_COMPILE_FLAGS%" + - name: Build CMake NMake + working-directory: build\nmake + shell: cmd + run: | + call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" + nmake install + - name: Check artifacts NMake + uses: andstor/file-existence-action@v2 + with: + files: "install/nmake/lib/hidapi.lib, \ + install/nmake/bin/hidapi.dll, \ + install/nmake/include/hidapi/hidapi.h, \ + install/nmake/include/hidapi/hidapi_winapi.h" + fail: true + - name: Check CMake Export Package NMake + shell: cmd + run: | + call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" + cmake ^ + -G"NMake Makefiles" ^ + -B build\nmake_test ^ + -S hidapisrc\hidtest ^ + -Dhidapi_ROOT=install\nmake ^ + -DCMAKE_INSTALL_PREFIX=install\nmake_test ^ + "-DCMAKE_C_FLAGS=%MSVC_COMPILE_FLAGS%" + cd build\nmake_test + nmake install + - name: Run CTest NMake + working-directory: build\nmake + run: ctest --rerun-failed --output-on-failure + + - name: Configure CMake MinGW + shell: cmd + run: | + cmake -G"MinGW Makefiles" -B build\mingw -S hidapisrc -DCMAKE_BUILD_TYPE=RelWithDebInfo -DHIDAPI_WITH_TESTS=ON -DHIDAPI_BUILD_PP_DATA_DUMP=ON -DCMAKE_INSTALL_PREFIX=install\mingw -DHIDAPI_BUILD_HIDTEST=ON "-DCMAKE_C_FLAGS=%NIX_COMPILE_FLAGS%" + - name: Build CMake MinGW + working-directory: build\mingw + run: cmake --build . --target install + - name: Check artifacts MinGW + uses: andstor/file-existence-action@v2 + with: + files: "install/mingw/lib/libhidapi.dll.a, \ + install/mingw/bin/libhidapi.dll, \ + install/mingw/include/hidapi/hidapi.h, \ + install/mingw/include/hidapi/hidapi_winapi.h" + fail: true + - name: Check CMake Export Package MinGW + shell: cmd + run: | + cmake ^ + -G"MinGW Makefiles" ^ + -B build\mingw_test ^ + -S hidapisrc\hidtest ^ + -Dhidapi_ROOT=install\mingw ^ + -DCMAKE_INSTALL_PREFIX=install\mingw_test ^ + "-DCMAKE_C_FLAGS=%NIX_COMPILE_FLAGS%" + cd build\mingw_test + cmake --build . --target install + - name: Run CTest MinGW + working-directory: build\mingw + run: ctest --rerun-failed --output-on-failure + + - name: Check Meson build + shell: cmd + run: | + call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" + meson setup build_meson hidapisrc + cd build_meson + ninja + + + windows-msbuild: + + runs-on: windows-latest + + steps: + - uses: actions/checkout@v3 + - uses: microsoft/setup-msbuild@v1.1 + - name: MSBuild x86 + run: msbuild windows\hidapi.sln /p:Configuration=Release /p:Platform=Win32 + - name: Check artifacts x86 + uses: andstor/file-existence-action@v2 + with: + files: "windows/Release/hidapi.dll, windows/Release/hidapi.lib, windows/Release/hidapi.pdb" + fail: true + - name: MSBuild x64 + run: msbuild windows\hidapi.sln /p:Configuration=Release /p:Platform=x64 + - name: Check artifacts x64 + uses: andstor/file-existence-action@v2 + with: + files: "windows/x64/Release/hidapi.dll, windows/x64/Release/hidapi.lib, windows/x64/Release/hidapi.pdb" + fail: true + - name: Gather artifacts + run: | + md artifacts + md artifacts\x86 + md artifacts\x64 + md artifacts\include + Copy-Item "windows\Release\hidapi.dll","windows\Release\hidapi.lib","windows\Release\hidapi.pdb" -Destination "artifacts\x86" + Copy-Item "windows\x64\Release\hidapi.dll","windows\x64\Release\hidapi.lib","windows\x64\Release\hidapi.pdb" -Destination "artifacts\x64" + Copy-Item "hidapi\hidapi.h","windows\hidapi_winapi.h" -Destination "artifacts\include" + - name: Upload artifacts + uses: actions/upload-artifact@v3 + with: + name: hidapi-win + path: artifacts/ + retention-days: ${{ (github.event_name == 'pull_request' || github.ref_name != 'master') && 7 || 90 }} + + + fedora-mingw: + + runs-on: ubuntu-latest + container: fedora:latest + steps: + - uses: actions/checkout@v3 + with: + path: hidapisrc + - name: Install dependencies + run: sudo dnf install -y autoconf automake libtool mingw64-gcc cmake ninja-build make + - name: Configure CMake + run: | + rm -rf build install + mingw64-cmake -B build/shared-cmake -S hidapisrc -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX=install/shared-cmake -DHIDAPI_BUILD_HIDTEST=ON "-DCMAKE_C_FLAGS=${NIX_COMPILE_FLAGS}" + mingw64-cmake -B build/static-cmake -S hidapisrc -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX=install/static-cmake -DBUILD_SHARED_LIBS=FALSE -DHIDAPI_BUILD_HIDTEST=ON "-DCMAKE_C_FLAGS=${NIX_COMPILE_FLAGS}" + - name: Configure Automake + working-directory: hidapisrc + run: | + ./bootstrap + mingw64-configure + - name: Build CMake Shared + working-directory: build/shared-cmake + run: ninja install + - name: Build CMake Static + working-directory: build/static-cmake + run: ninja install + - name: Build Automake + working-directory: hidapisrc + run: | + make + make DESTDIR=$PWD/../install/automake install + make clean + - name: Build manual Makefile + working-directory: hidapisrc/windows + run: make -f Makefile-manual OS=MINGW CC=x86_64-w64-mingw32-gcc + - name: Check artifacts + uses: andstor/file-existence-action@v2 + with: + files: "install/shared-cmake/bin/libhidapi.dll, \ + install/shared-cmake/lib/libhidapi.dll.a, \ + install/shared-cmake/include/hidapi/hidapi.h, \ + install/shared-cmake/include/hidapi/hidapi_winapi.h, \ + install/static-cmake/lib/libhidapi.a, \ + install/static-cmake/include/hidapi/hidapi.h, \ + install/static-cmake/include/hidapi/hidapi_winapi.h" + fail: true + - name: Check CMake Export Package Shared + run: | + mingw64-cmake \ + -GNinja \ + -B build/shared_test \ + -S hidapisrc/hidtest \ + -Dhidapi_DIR=$PWD/install/shared-cmake/lib/cmake/hidapi \ + -DCMAKE_INSTALL_PREFIX=install/shared_test \ + "-DCMAKE_C_FLAGS=${NIX_COMPILE_FLAGS}" + cd build/shared_test + ninja install + - name: Check CMake Export Package Static + run: | + mingw64-cmake \ + -GNinja \ + -B build/static_test \ + -S hidapisrc/hidtest \ + -Dhidapi_DIR=$PWD/install/static-cmake/lib/cmake/hidapi \ + -DCMAKE_INSTALL_PREFIX=install/static_test \ + "-DCMAKE_C_FLAGS=${NIX_COMPILE_FLAGS}" + cd build/static_test + ninja install + + + archlinux: + + runs-on: ubuntu-latest + container: archlinux:latest + steps: + - uses: actions/checkout@v3 + with: + path: hidapisrc + - name: Install dependencies + run: | + pacman -Sy + pacman -S --noconfirm gcc pkg-config autoconf automake libtool libusb libudev0 cmake make + - name: Configure CMake + run: | + rm -rf build install + cmake -B build/shared-cmake -S hidapisrc -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX=install/shared-cmake -DHIDAPI_BUILD_HIDTEST=ON "-DCMAKE_C_FLAGS=${NIX_COMPILE_FLAGS}" + cmake -B build/static-cmake -S hidapisrc -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX=install/static-cmake -DBUILD_SHARED_LIBS=FALSE -DHIDAPI_BUILD_HIDTEST=ON "-DCMAKE_C_FLAGS=${NIX_COMPILE_FLAGS}" + - name: Configure Automake + working-directory: hidapisrc + run: | + ./bootstrap + ./configure + - name: Build CMake Shared + working-directory: build/shared-cmake + run: make install + - name: Build CMake Static + working-directory: build/static-cmake + run: make install + - name: Build Automake + working-directory: hidapisrc + run: | + make + make DESTDIR=$PWD/../install/automake install + make clean + - name: Build manual Makefile + run: | + cd hidapisrc/linux + make -f Makefile-manual + cd ../libusb + make -f Makefile-manual + - name: Check artifacts + uses: andstor/file-existence-action@v2 + with: + files: "install/shared-cmake/lib/libhidapi-libusb.so, \ + install/shared-cmake/lib/libhidapi-hidraw.so, \ + install/shared-cmake/include/hidapi/hidapi.h, \ + install/shared-cmake/include/hidapi/hidapi_libusb.h, \ + install/static-cmake/lib/libhidapi-libusb.a, \ + install/static-cmake/lib/libhidapi-hidraw.a, \ + install/static-cmake/include/hidapi/hidapi.h, \ + install/static-cmake/include/hidapi/hidapi_libusb.h" + fail: true + - name: Check CMake Export Package Shared + run: | + cmake \ + -B build/shared_test \ + -S hidapisrc/hidtest \ + -Dhidapi_ROOT=install/shared-cmake \ + -DCMAKE_INSTALL_PREFIX=install/shared_test \ + "-DCMAKE_C_FLAGS=${NIX_COMPILE_FLAGS}" + cd build/shared_test + make install + - name: Check CMake Export Package Static + run: | + cmake \ + -B build/static_test \ + -S hidapisrc/hidtest \ + -Dhidapi_ROOT=install/static-cmake \ + -DCMAKE_INSTALL_PREFIX=install/static_test \ + "-DCMAKE_C_FLAGS=${NIX_COMPILE_FLAGS}" + cd build/static_test + make install + + + alpine: + + runs-on: ubuntu-latest + container: alpine:edge + env: + # A bug in musl: https://www.openwall.com/lists/musl/2020/01/20/2 + ALPINE_COMPILE_FLAGS: ${NIX_COMPILE_FLAGS} -Wno-overflow + steps: + - uses: actions/checkout@v3 + with: + path: hidapisrc + - name: Install dependencies + run: | + apk add gcc musl-dev autoconf automake libtool eudev-dev libusb-dev linux-headers cmake ninja make + - name: Configure CMake + run: | + rm -rf build install + cmake -B build/shared-cmake -S hidapisrc -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX=install/shared-cmake -DHIDAPI_BUILD_HIDTEST=ON "-DCMAKE_C_FLAGS=${ALPINE_COMPILE_FLAGS}" + cmake -B build/static-cmake -S hidapisrc -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX=install/static-cmake -DBUILD_SHARED_LIBS=FALSE -DHIDAPI_BUILD_HIDTEST=ON "-DCMAKE_C_FLAGS=${ALPINE_COMPILE_FLAGS}" + - name: Configure Automake + working-directory: hidapisrc + run: | + ./bootstrap + ./configure + - name: Build CMake Shared + working-directory: build/shared-cmake + run: ninja install + - name: Build CMake Static + working-directory: build/static-cmake + run: ninja install + - name: Build Automake + working-directory: hidapisrc + run: | + make + make DESTDIR=$PWD/../install/automake install + make clean + - name: Build manual Makefile + run: | + cd hidapisrc/linux + make -f Makefile-manual + cd ../libusb + make -f Makefile-manual + - name: Check artifacts + uses: andstor/file-existence-action@v2 + with: + files: "install/shared-cmake/lib/libhidapi-libusb.so, \ + install/shared-cmake/lib/libhidapi-hidraw.so, \ + install/shared-cmake/include/hidapi/hidapi.h, \ + install/shared-cmake/include/hidapi/hidapi_libusb.h, \ + install/static-cmake/lib/libhidapi-libusb.a, \ + install/static-cmake/lib/libhidapi-hidraw.a, \ + install/static-cmake/include/hidapi/hidapi.h, \ + install/static-cmake/include/hidapi/hidapi_libusb.h" + fail: true + - name: Check CMake Export Package Shared + run: | + cmake \ + -GNinja \ + -B build/shared_test \ + -S hidapisrc/hidtest \ + -Dhidapi_ROOT=install/shared-cmake \ + -DCMAKE_INSTALL_PREFIX=install/shared_test \ + "-DCMAKE_C_FLAGS=${ALPINE_COMPILE_FLAGS}" + cd build/shared_test + ninja install + - name: Check CMake Export Package Static + run: | + cmake \ + -GNinja \ + -B build/static_test \ + -S hidapisrc/hidtest \ + -Dhidapi_ROOT=install/static-cmake \ + -DCMAKE_INSTALL_PREFIX=install/static_test \ + "-DCMAKE_C_FLAGS=${ALPINE_COMPILE_FLAGS}" + cd build/static_test + ninja install diff --git a/src/hidapi/.github/workflows/checks.yml b/src/hidapi/.github/workflows/checks.yml new file mode 100644 index 0000000000000..e03dc66ee20e0 --- /dev/null +++ b/src/hidapi/.github/workflows/checks.yml @@ -0,0 +1,196 @@ +name: Checks +run-name: Code checks for '${{ github.ref_name }}' + +# General comment: +# Coverity doesn't support merging or including reports from multible machine/platforms (at least not officially). +# But otherwise there is no good way to keep the issues from all platforms at Coverity Scans at once. +# This script uses undocumented (but appears to be working) hack: +# The build logs from one machine/platform gets moved to a next once, +# and "fixed" so that cov-build can append logs from the next platform. +# The "fix" is based on the fact, that Coverity perfectly allows appending logs from multiple builds +# that are done *on the same host* machine. + +on: + # On-demand run + workflow_dispatch: + # Weekly run + schedule: + - cron: '30 5 * * 0' + +jobs: + coverity-windows: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v3 + with: + path: src + - name: Setup MSVC + uses: TheMrMilchmann/setup-msvc-dev@v2.0.0 + with: + arch: x64 + - name: Configure + run: | + cmake -B build -S src -G"NMake Makefiles" -DCMAKE_BUILD_TYPE=RelWithDebInfo -DHIDAPI_WITH_TESTS=ON -DHIDAPI_BUILD_HIDTEST=ON + - name: Lookup Coverity Build Tool hash + id: coverity-cache-lookup + run: | + $coverity_hash=Invoke-RestMethod -Uri https://scan.coverity.com/download/cxx/win64 -Method Post -Body @{token='${{ secrets.COVERITY_SCAN_TOKEN }}';project='hidapi';md5=1} + echo "coverity_hash=$coverity_hash" >> $Env:GITHUB_OUTPUT + - name: Get cached Coverity Build Tool + id: cov-build-cache + uses: actions/cache@v3 + with: + path: cov-root + key: cov-root-cxx-win64-${{ steps.coverity-cache-lookup.outputs.coverity_hash }} + - name: Get and configure Coverity + if: steps.cov-build-cache.outputs.cache-hit != 'true' + run: | + Invoke-WebRequest -Uri https://scan.coverity.com/download/cxx/win64 -OutFile coverity.zip -Method Post -Body @{token='${{ secrets.COVERITY_SCAN_TOKEN }}';project='hidapi'} + Remove-Item 'cov-root' -Recurse -Force -ErrorAction SilentlyContinue + Expand-Archive coverity.zip -DestinationPath cov-root + + $cov_root=Get-ChildItem -Path 'cov-root' + $Env:PATH += ";$($cov_root.FullName)\bin" + cov-configure -msvc + - name: Make Coverity available in PATH + run: | + $cov_root=Get-ChildItem -Path 'cov-root' + echo "$($cov_root.FullName)\bin" >> $Env:GITHUB_PATH + - name: Build with Coverity + working-directory: build + run: | + cov-build --dir cov-int nmake + Rename-Item ".\cov-int\emit\$(hostname)" hostname + - name: Backup Coverity logs + uses: actions/upload-artifact@v3 + with: + name: coverity-logs-windows + path: build/cov-int + retention-days: 7 + + + coverity-macos: + runs-on: macos-latest + needs: [coverity-windows] + + steps: + - uses: actions/checkout@v3 + with: + path: src + - name: Install dependencies + run: brew install ninja + - name: Configure + run: | + cmake -B build -S src -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DHIDAPI_WITH_TESTS=ON -DHIDAPI_BUILD_HIDTEST=ON -DCMAKE_C_COMPILER=clang + - uses: actions/download-artifact@v3 + with: + name: coverity-logs-windows + path: build/cov-int + - name: Fixup cov-int + run: | + rm -f build/cov-int/emit/hostname/emit-db.lock build/cov-int/emit/hostname/emit-db.write-lock + mv build/cov-int/emit/hostname build/cov-int/emit/$(hostname) + - name: Lookup Coverity Build Tool hash + id: coverity-cache-lookup + shell: bash + run: | + hash=$(curl https://scan.coverity.com/download/cxx/Darwin --data "token=${{ secrets.COVERITY_SCAN_TOKEN }}&project=hidapi&md5=1") + echo "coverity_hash=${hash}" >> $GITHUB_OUTPUT + - name: Get cached Coverity Build Tool + id: cov-build-cache + uses: actions/cache@v3 + with: + path: cov-root + key: cov-root-cxx-Darwin-${{ steps.coverity-cache-lookup.outputs.coverity_hash }} + - name: Get and configure Coverity + if: steps.cov-build-cache.outputs.cache-hit != 'true' + run: | + curl https://scan.coverity.com/download/cxx/Darwin --output coverity.dmg --data "token=${{ secrets.COVERITY_SCAN_TOKEN }}&project=hidapi" + hdiutil attach coverity.dmg -mountroot coverity + export COV_DIR_NAME=$(ls -1 --color=never coverity) + rm -rf cov-root + mkdir cov-root + cp ./coverity/${COV_DIR_NAME}/${COV_DIR_NAME}.sh cov-root/ + cd cov-root/ + ./${COV_DIR_NAME}.sh + ./bin/cov-configure --clang + - name: Make Coverity available in PATH + run: echo "$(pwd)/cov-root/bin" >> $GITHUB_PATH + - name: Build with Coverity + working-directory: build + run: | + cov-build --dir cov-int --append-log ninja + mv cov-int/emit/$(hostname) cov-int/emit/hostname + - name: Backup Coverity logs + uses: actions/upload-artifact@v3 + with: + name: coverity-logs-windows-macos + path: build/cov-int + retention-days: 7 + + + coverity-ubuntu: + runs-on: ubuntu-latest + needs: [coverity-macos] + + steps: + - uses: actions/checkout@v3 + with: + path: src + - name: Install dependencies + run: sudo apt install libudev-dev libusb-1.0-0-dev ninja-build + - name: Configure + run: | + cmake -B build -S src -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DHIDAPI_WITH_TESTS=ON -DHIDAPI_BUILD_HIDTEST=ON -DCMAKE_C_COMPILER=gcc + - uses: actions/download-artifact@v3 + with: + name: coverity-logs-windows-macos + path: build/cov-int + - name: Fixup cov-int + run: | + rm -f build/cov-int/emit/hostname/emit-db.lock build/cov-int/emit/hostname/emit-db.write-lock + mv build/cov-int/emit/hostname build/cov-int/emit/$(hostname) + - name: Lookup Coverity Build Tool hash + id: coverity-cache-lookup + shell: bash + run: | + hash=$(curl https://scan.coverity.com/download/cxx/linux64 --data "token=${{ secrets.COVERITY_SCAN_TOKEN }}&project=hidapi&md5=1") + echo "coverity_hash=${hash}" >> $GITHUB_OUTPUT + - name: Get cached Coverity Build Tool + id: cov-build-cache + uses: actions/cache@v3 + with: + path: cov-root + key: cov-root-cxx-linux64-${{ steps.coverity-cache-lookup.outputs.coverity_hash }} + - name: Get and configure Coverity + if: steps.cov-build-cache.outputs.cache-hit != 'true' + run: | + curl https://scan.coverity.com/download/cxx/linux64 --output coverity.tar.gz --data "token=${{ secrets.COVERITY_SCAN_TOKEN }}&project=hidapi" + rm -rf cov-root + mkdir cov-root + tar -xzf coverity.tar.gz --strip 1 -C cov-root + ./cov-root/bin/cov-configure --gcc + - name: Make Coverity available in PATH + run: echo "$(pwd)/cov-root/bin" >> $GITHUB_PATH + - name: Build with Coverity + working-directory: build + run: | + cov-build --dir cov-int --append-log ninja + - name: Submit results to Coverity Scan + working-directory: build + run: | + tar -czf cov-int.tar.gz cov-int + curl --form token=${{ secrets.COVERITY_SCAN_TOKEN }} \ + --form email=${{ secrets.COVERITY_SCAN_EMAIL }} \ + --form file=@cov-int.tar.gz \ + --form version="$GITHUB_SHA" \ + --form description="Automatic HIDAPI build" \ + https://scan.coverity.com/builds?project=hidapi + mv cov-int/emit/$(hostname) cov-int/emit/hostname + - name: Backup Coverity logs + uses: actions/upload-artifact@v3 + with: + name: coverity-logs-windows-macos-linux + path: build/cov-int + retention-days: 7 diff --git a/src/hidapi/.github/workflows/docs.yaml b/src/hidapi/.github/workflows/docs.yaml new file mode 100644 index 0000000000000..4ec09a502ef96 --- /dev/null +++ b/src/hidapi/.github/workflows/docs.yaml @@ -0,0 +1,58 @@ +name: Docs + +on: + push: + branches: [master] + pull_request: + branches: [master] + +jobs: + build: + runs-on: ubuntu-latest + steps: + + - name: Install Doxygen static libclang deps + run: sudo apt-get install libclang1-12 libclang-cpp12 + + - name: Install Doxygen from SF binary archives + env: + DOXYGEN_VERSION: '1.9.6' + run: | + mkdir .doxygen && cd .doxygen + curl -L https://sourceforge.net/projects/doxygen/files/rel-$DOXYGEN_VERSION/doxygen-$DOXYGEN_VERSION.linux.bin.tar.gz > doxygen.tar.gz + gunzip doxygen.tar.gz + tar xf doxygen.tar + cd doxygen-$DOXYGEN_VERSION + sudo make install + + - uses: actions/checkout@v3 + + - run: doxygen + working-directory: doxygen + + - name: Save doxygen docs as artifact + uses: actions/upload-artifact@v3 + with: + name: HIDAPI_doxygen_docs + path: ${{ github.workspace }}/doxygen/html + + deploy-docs: + runs-on: ubuntu-latest + needs: [build] + if: github.ref_type == 'branch' && github.ref_name == 'master' + concurrency: + group: "github-pages-deploy" + cancel-in-progress: true + steps: + - name: downlod artifact + uses: actions/download-artifact@v3 + with: + name: HIDAPI_doxygen_docs + path: docs + + - name: upload to github pages + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./docs + force_orphan: true diff --git a/src/hidapi/.gitignore b/src/hidapi/.gitignore new file mode 100644 index 0000000000000..048900e287b5c --- /dev/null +++ b/src/hidapi/.gitignore @@ -0,0 +1,32 @@ + +# Autotools-added generated files +Makefile.in +aclocal.m4 +ar-lib +autom4te.cache/ +config.* +configure +configure~ +compile +depcomp +install-sh +libusb/Makefile.in +linux/Makefile.in +ltmain.sh +mac/Makefile.in +missing +testgui/Makefile.in +windows/Makefile.in + +Makefile +stamp-h1 +libtool + +# macOS +.DS_Store + +# Qt Creator +CMakeLists.txt.user + +# doxgen output +doxygen/html/ diff --git a/src/hidapi/AUTHORS.txt b/src/hidapi/AUTHORS.txt index f311978d3333d..7193d71e0b0fe 100644 --- a/src/hidapi/AUTHORS.txt +++ b/src/hidapi/AUTHORS.txt @@ -10,7 +10,9 @@ Ludovic Rousseau : Bug fixes Correctness fixes +libusb/hidapi Team: + Development/maintainance since June 4th 2019 For a comprehensive list of contributions, see the commit list at github: - https://github.com/libusb/hidapi/commits/master + https://github.com/libusb/hidapi/graphs/contributors diff --git a/src/hidapi/BUILD.autotools.md b/src/hidapi/BUILD.autotools.md new file mode 100644 index 0000000000000..24b20a5af2b94 --- /dev/null +++ b/src/hidapi/BUILD.autotools.md @@ -0,0 +1,114 @@ +# Building HIDAPI using Autotools (deprecated) + +--- +**NOTE**: for all intentions and purposes the Autotools build scripts for HIDAPI are _deprecated_ and going to be obsolete in the future. +HIDAPI Team recommends using CMake build for HIDAPI. +If you are already using Autotools build scripts provided by HIDAPI, +consider switching to CMake build scripts as soon as possible. + +--- + +To be able to use Autotools to build HIDAPI, it has to be [installed](#installing-autotools)/available in the system. + +Make sure you've checked [prerequisites](BUILD.md#prerequisites) and installed all required dependencies. + +## Installing Autotools + +HIDAPI uses few specific tools/packages from Autotools: `autoconf`, `automake`, `libtool`. + +On different platforms or package managers, those could be named a bit differently or packaged together. +You'll have to check the documentation/package list for your specific package manager. + +### Linux + +On Ubuntu the tools are available via APT: + +```sh +sudo apt install autoconf automake libtool +``` + +### FreeBSD + +FreeBSD Autotools can be installed as: + +```sh +pkg_add -r autotools +``` + +Additionally, on FreeBSD you will need to install GNU make: +```sh +pkg_add -r gmake +``` + +## Building HIDAPI with Autotools + +A simple command list, to build HIDAPI with Autotools as a _shared library_ and install in into your system: + +```sh +./bootstrap # this prepares the configure script +./configure +make # build the library +make install # as root, or using sudo, this will install hidapi into your system +``` + +`./configure` can take several arguments which control the build. A few commonly used options: +```sh + --enable-testgui + # Enable the build of Foxit-based Test GUI. This requires Fox toolkit to + # be installed/available. See README.md#test-gui for remarks. + + --prefix=/usr + # Specify where you want the output headers and libraries to + # be installed. The example above will put the headers in + # /usr/include and the binaries in /usr/lib. The default is to + # install into /usr/local which is fine on most systems. + + --disable-shared + # By default, both shared and static libraries are going to be built/installed. + # This option disables shared library build, if only static library is required. +``` + + +## Cross Compiling + +This section talks about cross compiling HIDAPI for Linux using Autotools. +This is useful for using HIDAPI on embedded Linux targets. These +instructions assume the most raw kind of embedded Linux build, where all +prerequisites will need to be built first. This process will of course vary +based on your embedded Linux build system if you are using one, such as +OpenEmbedded or Buildroot. + +For the purpose of this section, it will be assumed that the following +environment variables are exported. +```sh +$ export STAGING=$HOME/out +$ export HOST=arm-linux +``` + +`STAGING` and `HOST` can be modified to suit your setup. + +### Prerequisites + +Depending on what backend you want to cross-compile, you also need to prepare the dependencies: +`libusb` for libusb HIDAPI backend, or `libudev` for hidraw HIDAPI backend. + +An example of cross-compiling `libusb`. From `libusb` source directory, run: +```sh +./configure --host=$HOST --prefix=$STAGING +make +make install +``` + +An example of cross-comping `libudev` is not covered by this section. +Check `libudev`'s documentation for details. + +### Building HIDAPI + +Build HIDAPI: +```sh +PKG_CONFIG_DIR= \ +PKG_CONFIG_LIBDIR=$STAGING/lib/pkgconfig:$STAGING/share/pkgconfig \ +PKG_CONFIG_SYSROOT_DIR=$STAGING \ +./configure --host=$HOST --prefix=$STAGING +# make / make install - same as for a regular build +``` diff --git a/src/hidapi/BUILD.cmake.md b/src/hidapi/BUILD.cmake.md new file mode 100644 index 0000000000000..9c51d0ce0b207 --- /dev/null +++ b/src/hidapi/BUILD.cmake.md @@ -0,0 +1,280 @@ +# Building HIDAPI using CMake + +To build HIDAPI with CMake, it has to be [installed](#installing-cmake)/available in the system. + +Make sure you've checked [prerequisites](BUILD.md#prerequisites) and installed all required dependencies. + +HIDAPI CMake build system allows you to build HIDAPI in two generally different ways: +1) As a [standalone package/library](#standalone-package-build); +2) As [part of a larger CMake project](#hidapi-as-a-subdirectory). + +**TL;DR**: if you're experienced developer and have been working with CMake projects or have been written some of your own - +most of this document may not be of interest for you; just check variables names, its default values and the target names. + +## Installing CMake + +CMake can be installed either using your system's package manager, +or by downloading an installer/prebuilt version from the [official website](https://cmake.org/download/). + +On most \*nix systems, the prefered way to install CMake is via package manager, +e.g. `sudo apt install cmake`. + +On Windows CMake could be provided by your development environment (e.g. by Visual Studio Installer or MinGW installer), +or you may install it system-wise using the installer from the official website. + +On macOS CMake may be installed by Homebrew/MacPorts or using the installer from the official website. + +## Standalone package build + +To build HIDAPI as a standalone package, you follow [general steps](https://cmake.org/runningcmake/) of building any CMake project. + +An example of building HIDAPI with CMake: +```sh +# precondition: create a somewhere on the filesystem (preferably outside of the HIDAPI source) +# this is the place where all intermediate/build files are going to be located +cd +# configure the build +cmake +# build it! +cmake --build . +# install library; by default installs into /usr/local/ +cmake --build . --target install +# NOTE: you need to run install command as root, to be able to install into /usr/local/ +``` +Such invocation will use the default (as per CMake magic) compiler/build environment available in your system. + +You may pass some additional CMake variables to control the build configuration as `-D=value`. +E.g.: +```sh +# install command now would install things into /usr +cmake -DCMAKE_INSTALL_PREFIX=/usr +``` + +
+ Using a specific CMake generator + +An example of using `Ninja` as a CMake generator: + +```sh +cd +# configure the build +cmake -GNinja +# we know, that CMake has generated build files for Ninja, +# so we can use `ninja` directly, instead of `cmake --build .` +ninja +# install library +ninja install +``` + +`-G` here specifies a native build system CMake would generate build files for. +Check [CMake Documentation](https://cmake.org/cmake/help/latest/manual/cmake-generators.7.html) for a list of available generators (system-specific). + +

+ +Some of the [standard](https://cmake.org/cmake/help/latest/manual/cmake-variables.7.html) CMake variables you may want to use to configure a build: + +- [`CMAKE_INSTALL_PREFIX`](https://cmake.org/cmake/help/latest/variable/CMAKE_INSTALL_PREFIX.html) - prefix where `install` target would install the library(ies); +- [`CMAKE_BUILD_TYPE`](https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html) - standard possible values: `Debug`, `Release`, `RelWithDebInfo`, `MinSizeRel`; Defaults to `Release` for HIDAPI, if not specified; +- [`BUILD_SHARED_LIBS`](https://cmake.org/cmake/help/latest/variable/BUILD_SHARED_LIBS.html) - when set to TRUE, HIDAPI is built as a shared library, otherwise build statically; Defaults to `TRUE` for HIDAPI, if not specified; + +
+ macOS-specific variables + + - [`CMAKE_FRAMEWORK`](https://cmake.org/cmake/help/latest/variable/CMAKE_FRAMEWORK.html) - (since CMake 3.15) when set to TRUE, HIDAPI is built as a framework library, otherwise build as a regular static/shared library; Defaults to `FALSE` for HIDAPI, if not specified; + - [`CMAKE_OSX_DEPLOYMENT_TARGET`](https://cmake.org/cmake/help/latest/variable/CMAKE_OSX_DEPLOYMENT_TARGET.html) - minimum version of the target platform (e.g. macOS or iOS) on which the target binaries are to be deployed; defaults to a maximum supported target platform by currently used XCode/Toolchain; + +

+ +HIDAPI-specific CMake variables: + +- `HIDAPI_BUILD_HIDTEST` - when set to TRUE, build a small test application `hidtest`; +- `HIDAPI_WITH_TESTS` - when set to TRUE, build all (unit-)tests; +currently this option is only available on Windows, since only Windows backend has tests; + +
+ Linux-specific variables + + - `HIDAPI_WITH_HIDRAW` - when set to TRUE, build HIDRAW-based implementation of HIDAPI (`hidapi-hidraw`), otherwise don't build it; defaults to TRUE; + - `HIDAPI_WITH_LIBUSB` - when set to TRUE, build LIBUSB-based implementation of HIDAPI (`hidapi-libusb`), otherwise don't build it; defaults to TRUE; + + **NOTE**: at least one of `HIDAPI_WITH_HIDRAW` or `HIDAPI_WITH_LIBUSB` has to be set to TRUE. + +

+ +To see all most-useful CMake variables available for HIDAPI, one of the most convenient ways is too use [`cmake-gui`](https://cmake.org/cmake/help/latest/manual/cmake-gui.1.html) tool ([example](https://cmake.org/runningcmake/)). + +_NOTE_: HIDAPI packages built by CMake can be used with `pkg-config`, as if built with [Autotools](BUILD.autotools.md). + +### MSVC and Ninja +It is possible to build a CMake project (including HIDAPI) using MSVC compiler and Ninja (for medium and larger projects it is so much faster than msbuild). + +For that: +1) Open cmd.exe; +2) Setup MSVC build environment variables, e.g.: `vcvarsall.bat x64`, where: + - `vcvarsall.bat` is an environment setup script of your MSVC toolchain installation;
For MSVC 2019 Community edition it is located at: `C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\`; + - `x64` -a target architecture to build; +3) Follow general build steps, and use `Ninja` as a generator. + +### Using HIDAPI in a CMake project + +When HIDAPI is used as a standalone package (either installed into the system or built manually and installed elsewhere), the simplest way to use it is as showed in the example: + +```cmake +project(my_application) + +add_executable(my_application main.c) + +find_package(hidapi REQUIRED) +target_link_libraries(my_application PRIVATE hidapi::hidapi) +``` + +If HIDAPI isn't installed in your system, or `find_package` cannot find HIDAPI by default for any other reasons, +the recommended way manually specify which HIDAPI package to use is via `hidapi_ROOT` CMake variable, e.g.: +`-Dhidapi_ROOT=`. + +_NOTE_: usage of `hidapi_ROOT` is only possible (and recommended) with CMake 3.12 and higher. For older versions of CMake you'd need to specify [`CMAKE_PREFIX_PATH`](https://cmake.org/cmake/help/latest/variable/CMAKE_PREFIX_PATH.html#variable:CMAKE_PREFIX_PATH) instead. + +Check with [`find_package`](https://cmake.org/cmake/help/latest/command/find_package.html) documentation if you need more details. + +Available CMake targets after successful `find_package(hidapi)`: +- `hidapi::hidapi` - indented to be used in most cases; +- `hidapi::include` - if you need only to include `` but not link against the library; +- `hidapi::winapi` - same as `hidapi::hidapi` on Windows; available only on Windows; +- `hidapi::darwin` - same as `hidapi::hidapi` on macOS; available only on macOS; +- `hidapi::libusb` - available when libusb backend is used/available; +- `hidapi::hidraw` - available when hidraw backend is used/available on Linux; + +**NOTE**: on Linux often both `hidapi::libusb` and `hidapi::hidraw` backends are available; in that case `hidapi::hidapi` is an alias for **`hidapi::hidraw`**. The motivation is that `hidraw` backend is a native Linux kernel implementation of HID protocol, and supports various HID devices (USB, Bluetooth, I2C, etc.). If `hidraw` backend isn't built at all (`hidapi::libusb` is the only target) - `hidapi::hidapi` is an alias for `hidapi::libusb`. +If you're developing a cross-platform application and you are sure you need to use `libusb` backend on Linux, the simple way to achieve this is: +```cmake +if(TARGET hidapi::libusb) + target_link_libraries(my_project PRIVATE hidapi::libusb) +else() + target_link_libraries(my_project PRIVATE hidapi::hidapi) +endif() +``` + +## HIDAPI as a subdirectory + +HIDAPI can be easily used as a subdirectory of a larger CMake project: +```cmake +# root CMakeLists.txt +cmake_minimum_required(VERSION 3.4.3 FATAL_ERROR) + +add_subdirectory(hidapi) +add_subdirectory(my_application) + +# my_application/CMakeLists.txt +project(my_application) + +add_executable(my_application main.c) + +# NOTE: no `find_package` is required, since HIDAPI targets are already a part of the project tree +target_link_libraries(my_application PRIVATE hidapi::hidapi) +``` +Lets call this "larger project" a "host project". + +All of the variables described in [standalone build](#standalone-package-build) section can be used to control HIDAPI build in case of a subdirectory, e.g.: +```cmake +set(HIDAPI_WITH_LIBUSB FALSE) # surely will be used only on Linux +set(BUILD_SHARED_LIBS FALSE) # HIDAPI as static library on all platforms +add_subdirectory(hidapi) +``` + +
+ NOTE + + If you project happen to use `BUILD_SHARED_LIBS` as a `CACHE` variable globally for you project, setting it as simple variable, as showed above _will have not affect_ up until _CMake 3.13_. See [CMP0077](https://cmake.org/cmake/help/latest/policy/CMP0077.html) for details. +

+ +There are several important differences in the behavior of HIDAPI CMake build system when CMake is built as standalone package vs subdirectory build: + +1) In _standalone build_ a number of standard and HIDAPI-specific variables are marked as _cache variables_ or _options_. +This is done for convenience: when you're building HIDAPI as a standalone package and using tools like `cmake-gui` - those are highlighted as variables that can be changed and has some short description/documentation. E.g.: +![an example of highlighted variables in cmake-gui](documentation/cmake-gui-highlights.png "cmake-gui highlighted variables")
+E.g.2:
+![an example of drop-down menu in cmake-gui](documentation/cmake-gui-drop-down.png "cmake-gui drop-down menu")
+When HIDAPI is built as a _subdirectory_ - **_none of the variables are marked for cache or as options_** by HIDAPI. +This is done to let the host project's developer decide what is important (what needs to be highlighted) and what's not. + +2) The default behavior/default value for some of the variables is a bit different: + - by default, none of HIDAPI targets are [installed](https://cmake.org/cmake/help/latest/command/install.html); if required, HIDAPI targets can be installed by host project _after_ including HIDAPI subdirectory (requires CMake 3.13 or later); **or**, the default installation can be enabled by setting `HIDAPI_INSTALL_TARGETS` variable _before_ including HIDAPI subdirectory. + HIDAPI uses [GNUInstallDirs](https://cmake.org/cmake/help/latest/module/GNUInstallDirs.html) to specify install locations. Variables like `CMAKE_INSTALL_LIBDIR` can be used to control HIDAPI's installation locations. E.g.: + ```cmake + # enable the installation if you need it + set(HIDAPI_INSTALL_TARGETS ON) + # (optionally) change default installation locations if it makes sense for your target platform, etc. + set(CMAKE_INSTALL_LIBDIR "lib64") + add_subdirectory(hidapi) + ``` + - HIDAPI prints its version during the configuration when built as a standalone package; to enable this for subdirectory builds - set `HIDAPI_PRINT_VERSION` to TRUE before including HIDAPI; + +3) In a subdirectory build, HIDAPI _doesn't modify or set any of the CMake variables_ that may change the build behavior. + For instance, in a _standalone build_, if CMAKE_BUILD_TYPE or BUILD_SHARED_LIBS variables are not set, those are defaulted to "Release" and "TRUE" explicitly. + In a _subdirectory build_, even if not set, those variables remain unchanged, so a host project's developer has a full control over the HIDAPI build configuration. + +Available CMake targets after `add_subdirectory(hidapi)` _are the same as in case of [standalone build](#standalone-package-build)_, and a few additional ones: +- `hidapi_include` - the interface library; `hidapi::hidapi` is an alias of it; +- `hidapi_winapi` - library target on Windows; `hidapi::winapi` is an alias of it; +- `hidapi_darwin` - library target on macOS; `hidapi::darwin` is an alias of it; +- `hidapi_libusb` - library target for libusb backend; `hidapi::libusb` is an alias of it; +- `hidapi_hidraw` - library target for hidraw backend; `hidapi::hidraw` is an alias of it; +- `hidapi-libusb` - an alias of `hidapi_libusb` for compatibility with raw library name; +- `hidapi-hidraw` - an alias of `hidapi_hidraw` for compatibility with raw library name; +- `hidapi` - an alias of `hidapi_winapi` or `hidapi_darwin` on Windows or macOS respectfully. + +Advanced: +- Why would I need additional targets described in this section above, if I already have alias targets compatible with `find_package`? + - an example: + ```cmake + add_subdirectory(hidapi) + if(TARGET hidapi_libusb) + # see libusb/hid.c for usage of `NO_ICONV` + target_compile_definitions(hidapi_libusb PRIVATE NO_ICONV) + endif() + ``` + +## Both Shared and Static build + +If you're a former (or present) user of Autotools build scripts for HIDAPI, or you're a package manager maintainer and you're often working with those - you're likely asking how to build HIDAPI with CMake and get both Shared and Static libraries (as would be done by Autotools: `./configure --enable-static --enable-shared ...`). + +CMake doesn't have such option of-the-box and it is decided not to introduce any manual CMake-level workarounds for HIDAPI on this matter. + +If you want to mimic the Autotools behavior, it is possible by building/installing first the static version of the library and then shared version of the library. The installation folder (`CMAKE_INSTALL_PREFIX`) should point to the same directory for both variants, that way: +- both static and shared library binaries will be available and usable; +- a single header file(s) for both of them; +- Autotools/pkg-config (`.pc`) files will be generated and usable _as if_ generated by Autotools natively and build configured with both `-enable-static --enable-shared` options; +- CMake package scripts will be generated and fully usable, but _only the last build installed_, i.e. if the last was installed Shared version of the binary - CMake targets found by `find_package(hidapi)` would point to a Shared binaries. + +There is a historical discussion, why such solution is simplest/preferable: https://github.com/libusb/hidapi/issues/424 + +#### TL;DR/Sample + +```sh +# First - configure/build + +# Static libraries +cmake -S -B "/static" -DCMAKE_INSTALL_PREFIX= -DBUILD_SHARED_LIBS=FALSE +cmake --build "/static" +# Shared libraries +cmake -S -B "/shared" -DCMAKE_INSTALL_PREFIX= -DBUILD_SHARED_LIBS=TRUE +cmake --build "/shared" + +# (Optionally) change the installation destination. +# NOTE1: this is supported by CMake only on UNIX platforms +# See https://cmake.org/cmake/help/latest/envvar/DESTDIR.html +# NOTE2: this is not the same as `CMAKE_INSTALL_PREFIX` set above +# NOTE3: this is only required if you have a staging dir other than the final runtime dir, +# e.g. during cross-compilation +export DESTDIR="$STAGING_DIR" + +# +# Install the libraries +# NOTE: order of installation matters - install Shared variant *the last* + +# Static libraries +cmake --install "/static" +# Shared libraries +cmake --install "/shared" + +``` diff --git a/src/hidapi/BUILD.md b/src/hidapi/BUILD.md new file mode 100644 index 0000000000000..d7a3546f6a9f6 --- /dev/null +++ b/src/hidapi/BUILD.md @@ -0,0 +1,127 @@ +# Building HIDAPI from Source + +## Table of content + +* [Intro](#intro) +* [Prerequisites](#prerequisites) + * [Linux](#linux) + * [FreeBSD](#freebsd) + * [Mac](#mac) + * [Windows](#windows) +* [Embedding HIDAPI directly into your source tree](#embedding-hidapi-directly-into-your-source-tree) +* [Building the manual way on Unix platforms](#building-the-manual-way-on-unix-platforms) +* [Building on Windows](#building-on-windows) + +## Intro + +For various reasons, you may need to build HIDAPI on your own. + +It can be done in several different ways: +- using [CMake](BUILD.cmake.md); +- using [Autotools](BUILD.autotools.md) (deprecated); +- using [manual makefiles](#building-the-manual-way-on-unix-platforms); +- using `Meson` (requires CMake); + +**Autotools** build system is historically the first mature build system for +HIDAPI. The most common usage of it is in its separate README: [BUILD.autotools.md](BUILD.autotools.md).
+NOTE: for all intentions and purposes the Autotools build scripts for HIDAPI are _deprecated_ and going to be obsolete in the future. +HIDAPI Team recommends using CMake build for HIDAPI. + +**CMake** build system is de facto an industry standard for many open-source and proprietary projects and solutions. +HIDAPI is one of the projects which use the power of CMake to its advantage. +More documentation is available in its separate README: [BUILD.cmake.md](BUILD.cmake.md). + +**Meson** build system for HIDAPI is designed as a [wrapper](https://mesonbuild.com/CMake-module.html) over CMake build script. +It is present for the convenience of Meson users who need to use HIDAPI and need to be sure HIDAPI is built in accordance with officially supported build scripts.
+In the Meson script of your project you need a `hidapi = subproject('hidapi')` subproject, and `hidapi.get_variable('hidapi_dep')` as your dependency. +There are also backend/platform-specific dependencies available: `hidapi_winapi`, `hidapi_darwin`, `hidapi_hidraw`, `hidapi_libusb`. + +If you don't know where to start to build HIDAPI, we recommend starting with [CMake](BUILD.cmake.md) build. + +## Prerequisites: + +Regardless of what build system you choose to use, there are specific dependencies for each platform/backend. + +### Linux: + +Depending on which backend you're going to build, you'll need to install +additional development packages. For `linux/hidraw` backend, you need a +development package for `libudev`. For `libusb` backend, naturally, you need +`libusb` development package. + +On Debian/Ubuntu systems these can be installed by running: +```sh +# required only by hidraw backend +sudo apt install libudev-dev +# required only by libusb backend +sudo apt install libusb-1.0-0-dev +``` + +### FreeBSD: + +On FreeBSD, you will need to install libiconv. This is done by running +the following: +```sh +pkg_add -r libiconv +``` + +### Mac: + +Make sure you have XCode installed and its Command Line Tools. + +### Windows: + +You just need a compiler. You may use Visual Studio or Cygwin/MinGW, +depending on which environment is best for your needs. + +## Embedding HIDAPI directly into your source tree + +Instead of using one of the provided standalone build systems, +you may want to integrate HIDAPI directly into your source tree. + +--- +If your project uses CMake as a build system, it is safe to add HIDAPI as a [subdirectory](BUILD.cmake.md#hidapi-as-a-subdirectory). + +--- +If _the only option_ that works for you is adding HIDAPI sources directly +to your project's build system, then you need: +- include a _single source file_ into your project's build system, +depending on your platform and the backend you want to use: + - [`windows\hid.c`](windows/hid.c); + - [`linux/hid.c`](linux/hid.c); + - [`libusb/hid.c`](libusb/hid.c); + - [`mac/hid.c`](mac/hid.c); +- add a [`hidapi`](hidapi) folder to the include path when building `hid.c`; +- make the platform/backend specific [dependencies](#prerequisites) available during the compilation/linking, when building `hid.c`; + +NOTE: the above doesn't guarantee that having a copy of `/hid.c` and `hidapi/hidapi.h` is enough to build HIDAPI. +The only guarantee that `/hid.c` includes all necessary sources to compile it as a single file. + +Check the manual makefiles for a simple example/reference of what are the dependencies of each specific backend. + +## Building the manual way on Unix platforms + +Manual Makefiles are provided mostly to give the user an idea what it takes +to build a program which embeds HIDAPI directly inside of it. These should +really be used as examples only. If you want to build a system-wide shared +library, use one of the build systems mentioned above. + +To build HIDAPI using the manual Makefiles, change the directory +of your platform and run make. For example, on Linux run: +```sh +cd linux/ +make -f Makefile-manual +``` + +## Building on Windows + +To build the HIDAPI DLL on Windows using Visual Studio, build the `.sln` file +in the `windows/` directory. + +To build HIDAPI using MinGW or Cygwin using Autotools, use general Autotools + [instruction](BUILD.autotools.md). + +Any windows builds (MSVC or MinGW/Cygwin) are also supported by [CMake](BUILD.cmake.md). + +If you are looking for information regarding DDK build of HIDAPI: +- the build has been broken for a while and now the support files are obsolete. diff --git a/src/hidapi/CMakeLists.txt b/src/hidapi/CMakeLists.txt new file mode 100644 index 0000000000000..e18ee23be848b --- /dev/null +++ b/src/hidapi/CMakeLists.txt @@ -0,0 +1,105 @@ +cmake_minimum_required(VERSION 3.1.3 FATAL_ERROR) + +if(NOT CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) + add_subdirectory(src) + # compatibility with find_package() vs add_subdirectory + set(hidapi_VERSION "${hidapi_VERSION}" PARENT_SCOPE) + return() +endif() +# All of the below in this file is meant for a standalone build. +# When building as a subdirectory of a larger project, most of the options may not make sense for it, +# so it is up to developer to configure those, e.g.: +# +# # a subfolder of a master project, e.g.: 3rdparty/hidapi/CMakeLists.txt +# +# set(HIDAPI_WITH_HIDRAW OFF) +# set(CMAKE_FRAMEWORK ON) +# # and keep everything else to their defaults +# add_subdirectory(hidapi) +# + +set(DEFAULT_CMAKE_BUILD_TYPES "Debug" "Release" "MinSizeRel" "RelWithDebInfo") +if(NOT DEFINED CMAKE_BUILD_TYPE OR NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Release" CACHE STRING "${DEFAULT_CMAKE_BUILD_TYPES}" FORCE) +endif() +# This part is for convenience, when used one of the standard build types with cmake-gui +list(FIND DEFAULT_CMAKE_BUILD_TYPES "${CMAKE_BUILD_TYPE}" _build_type_index) +if(${_build_type_index} GREATER -1) + # set it optionally, so a custom CMAKE_BUILD_TYPE can be used as well, if needed + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS ${DEFAULT_CMAKE_BUILD_TYPES}) +endif() +unset(_build_type_index) +# + +project(hidapi LANGUAGES C) + +if(APPLE) + if(NOT CMAKE_VERSION VERSION_LESS "3.15") + option(CMAKE_FRAMEWORK "Build macOS/iOS Framework version of the library" OFF) + endif() +elseif(NOT WIN32) + if(CMAKE_SYSTEM_NAME MATCHES "Linux") + option(HIDAPI_WITH_HIDRAW "Build HIDRAW-based implementation of HIDAPI" ON) + option(HIDAPI_WITH_LIBUSB "Build LIBUSB-based implementation of HIDAPI" ON) + endif() +endif() + +option(BUILD_SHARED_LIBS "Build shared version of the libraries, otherwise build statically" ON) + +set(HIDAPI_INSTALL_TARGETS ON) +set(HIDAPI_PRINT_VERSION ON) + +set(IS_DEBUG_BUILD OFF) +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + set(IS_DEBUG_BUILD ON) +endif() + +option(HIDAPI_ENABLE_ASAN "Build HIDAPI with ASAN address sanitizer instrumentation" OFF) + +if(HIDAPI_ENABLE_ASAN) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address") + if(MSVC) + # the default is to have "/INCREMENTAL" which causes a warning when "-fsanitize=address" is present + set(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_DEBUG} /INCREMENTAL:NO") + set(CMAKE_SHARED_LINKER_FLAGS_DEBUG "${CMAKE_SHARED_LINKER_FLAGS_DEBUG} /INCREMENTAL:NO") + set(CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO} /INCREMENTAL:NO") + set(CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO} /INCREMENTAL:NO") + endif() +endif() + +if(WIN32) + # so far only Windows has tests + option(HIDAPI_WITH_TESTS "Build HIDAPI (unit-)tests" ${IS_DEBUG_BUILD}) +else() + set(HIDAPI_WITH_TESTS OFF) +endif() + +if(HIDAPI_WITH_TESTS) + enable_testing() +endif() + +if(WIN32) + option(HIDAPI_BUILD_PP_DATA_DUMP "Build small Windows console application pp_data_dump.exe" ${IS_DEBUG_BUILD}) +endif() + +add_subdirectory(src) + +option(HIDAPI_BUILD_HIDTEST "Build small console test application hidtest" ${IS_DEBUG_BUILD}) +if(HIDAPI_BUILD_HIDTEST) + add_subdirectory(hidtest) +endif() + +if(HIDAPI_ENABLE_ASAN) + if(NOT MSVC) + # MSVC doesn't recognize those options, other compilers - requiring it + foreach(HIDAPI_TARGET hidapi_winapi hidapi_darwin hidapi_hidraw hidapi_libusb hidtest_hidraw hidtest_libusb hidtest) + if(TARGET ${HIDAPI_TARGET}) + if(BUILD_SHARED_LIBS) + target_link_options(${HIDAPI_TARGET} PRIVATE -fsanitize=address) + else() + target_link_options(${HIDAPI_TARGET} PUBLIC -fsanitize=address) + endif() + endif() + endforeach() + endif() +endif() diff --git a/src/hidapi/HACKING.txt b/src/hidapi/HACKING.txt index 761d4b6550284..e06b533aa0ae7 100644 --- a/src/hidapi/HACKING.txt +++ b/src/hidapi/HACKING.txt @@ -1,15 +1,19 @@ This file is mostly for the maintainer. -1. Build hidapi.dll -2. Build hidtest.exe in DEBUG and RELEASE -3. Commit all +Updating a Version: +1. Update VERSION file. +2. HID_API_VERSION_MAJOR/HID_API_VERSION_MINOR/HID_API_VERSION_PATCH in hidapi.h. -4. Run the Following - export VERSION=0.1.0 - export TAG_NAME=hidapi-$VERSION - git tag $TAG_NAME - git archive --format zip --prefix $TAG_NAME/ $TAG_NAME >../$TAG_NAME.zip -5. Test the zip file. -6. Run the following: - git push origin $TAG_NAME +Before firing a new release: +1. Run the "Checks" Githtub Action +2. Make sure no defects are found at: https://scan.coverity.com/projects/hidapi +3. Fix if any +Firing a new release: +1. Update the Version (if not yet updated). +2. Prepare the Release Notes. +3. Store the Release Notes into a file. +4. Create locally an annotated git tag with release notes attached, e.g.: `git tag -aF ../hidapi_release_notes hidapi-` +5. Push newly created tag: `git push origin hidapi-` +6. Grab the hidapi-win.zip from Summary page of "GitHub Builds" Action for latest master build. +7. Create a Github Release with hidapi-win.zip attached, for newly created tag. diff --git a/src/hidapi/Makefile.am b/src/hidapi/Makefile.am index 3382a1f040bcd..00bcb73cf4119 100644 --- a/src/hidapi/Makefile.am +++ b/src/hidapi/Makefile.am @@ -23,10 +23,6 @@ if OS_DARWIN SUBDIRS += mac endif -if OS_IOS -SUBDIRS += ios -endif - if OS_FREEBSD SUBDIRS += libusb endif @@ -35,6 +31,10 @@ if OS_KFREEBSD SUBDIRS += libusb endif +if OS_HAIKU +SUBDIRS += libusb +endif + if OS_WINDOWS SUBDIRS += windows endif @@ -48,7 +48,7 @@ endif EXTRA_DIST = udev doxygen dist_doc_DATA = \ - README.txt \ + README.md \ AUTHORS.txt \ LICENSE-bsd.txt \ LICENSE-gpl3.txt \ diff --git a/src/hidapi/README.md b/src/hidapi/README.md new file mode 100644 index 0000000000000..257b9f340a579 --- /dev/null +++ b/src/hidapi/README.md @@ -0,0 +1,196 @@ +## HIDAPI library for Windows, Linux, FreeBSD and macOS + +| CI instance | Status | +|----------------------|--------| +| `Linux/macOS/Windows (master)` | [![GitHub Builds](https://github.com/libusb/hidapi/workflows/GitHub%20Builds/badge.svg?branch=master)](https://github.com/libusb/hidapi/actions/workflows/builds.yml?query=branch%3Amaster) | +| `Windows (master)` | [![Build status](https://ci.appveyor.com/api/projects/status/xfmr5fo8w0re8ded/branch/master?svg=true)](https://ci.appveyor.com/project/libusb/hidapi/branch/master) | +| `BSD, last build (branch/PR)` | [![builds.sr.ht status](https://builds.sr.ht/~z3ntu/hidapi.svg)](https://builds.sr.ht/~z3ntu/hidapi) | +| `Coverity Scan (last)` | ![Coverity Scan](https://scan.coverity.com/projects/583/badge.svg) | + +HIDAPI is a multi-platform library which allows an application to interface +with USB and Bluetooth HID-Class devices on Windows, Linux, FreeBSD, and macOS. +HIDAPI can be either built as a shared library (`.so`, `.dll` or `.dylib`) or +can be embedded directly into a target application by adding a _single source_ +file (per platform) and a single header.
+See [remarks](BUILD.md#embedding-hidapi-directly-into-your-source-tree) on embedding _directly_ into your build system. + +HIDAPI library was originally developed by Alan Ott ([signal11](https://github.com/signal11)). + +It was moved to [libusb/hidapi](https://github.com/libusb/hidapi) on June 4th, 2019, in order to merge important bugfixes and continue development of the library. + +## Table of Contents + +* [About](#about) + * [Test GUI](#test-gui) + * [Console Test App](#console-test-app) +* [What Does the API Look Like?](#what-does-the-api-look-like) +* [License](#license) +* [Installing HIDAPI](#installing-hidapi) +* [Build from Source](#build-from-source) + + +## About + +### HIDAPI has four back-ends: +* Windows (using `hid.dll`) +* Linux/hidraw (using the Kernel's hidraw driver) +* libusb (using libusb-1.0 - Linux/BSD/other UNIX-like systems) +* macOS (using IOHidManager) + +On Linux, either the hidraw or the libusb back-end can be used. There are +tradeoffs, and the functionality supported is slightly different. Both are +built by default. It is up to the application linking to hidapi to choose +the backend at link time by linking to either `libhidapi-libusb` or +`libhidapi-hidraw`. + +Note that you will need to install an udev rule file with your application +for unprivileged users to be able to access HID devices with hidapi. Refer +to the [69-hid.rules](udev/69-hid.rules) file in the `udev` directory +for an example. + +#### __Linux/hidraw__ (`linux/hid.c`): + +This back-end uses the hidraw interface in the Linux kernel, and supports +both USB and Bluetooth HID devices. It requires kernel version at least 2.6.39 +to build. In addition, it will only communicate with devices which have hidraw +nodes associated with them. +Keyboards, mice, and some other devices which are blacklisted from having +hidraw nodes will not work. Fortunately, for nearly all the uses of hidraw, +this is not a problem. + +#### __Linux/FreeBSD/libusb__ (`libusb/hid.c`): + +This back-end uses libusb-1.0 to communicate directly to a USB device. This +back-end will of course not work with Bluetooth devices. + +### Test GUI + +HIDAPI also comes with a Test GUI. The Test GUI is cross-platform and uses +Fox Toolkit . It will build on every platform +which HIDAPI supports. Since it relies on a 3rd party library, building it +is optional but it is useful when debugging hardware. + +NOTE: Test GUI based on Fox Toolkit is not actively developed nor supported +by HIDAPI team. It is kept as a historical artifact. It may even work sometime +or on some platforms, but it is not going to get any new features or bugfixes. + +Instructions for installing Fox-Toolkit on each platform is not provided. +Make sure to use Fox-Toolkit v1.6 if you choose to use it. + +### Console Test App + +If you want to play around with your HID device before starting +any development with HIDAPI and using a GUI app is not an option for you, you may try [`hidapitester`](https://github.com/todbot/hidapitester). + +This app has a console interface for most of the features supported +by HIDAPI library. + +## What Does the API Look Like? + +The API provides the most commonly used HID functions including sending +and receiving of input, output, and feature reports. The sample program, +which communicates with a heavily hacked up version of the Microchip USB +Generic HID sample looks like this (with error checking removed for +simplicity): + +**Warning: Only run the code you understand, and only when it conforms to the +device spec. Writing data (`hid_write`) at random to your HID devices can break them.** + +```c +#include // printf +#include // wchar_t + +#include + +#define MAX_STR 255 + +int main(int argc, char* argv[]) +{ + int res; + unsigned char buf[65]; + wchar_t wstr[MAX_STR]; + hid_device *handle; + int i; + + // Initialize the hidapi library + res = hid_init(); + + // Open the device using the VID, PID, + // and optionally the Serial number. + handle = hid_open(0x4d8, 0x3f, NULL); + if (!handle) { + printf("Unable to open device\n"); + hid_exit(); + return 1; + } + + // Read the Manufacturer String + res = hid_get_manufacturer_string(handle, wstr, MAX_STR); + printf("Manufacturer String: %ls\n", wstr); + + // Read the Product String + res = hid_get_product_string(handle, wstr, MAX_STR); + printf("Product String: %ls\n", wstr); + + // Read the Serial Number String + res = hid_get_serial_number_string(handle, wstr, MAX_STR); + printf("Serial Number String: (%d) %ls\n", wstr[0], wstr); + + // Read Indexed String 1 + res = hid_get_indexed_string(handle, 1, wstr, MAX_STR); + printf("Indexed String 1: %ls\n", wstr); + + // Toggle LED (cmd 0x80). The first byte is the report number (0x0). + buf[0] = 0x0; + buf[1] = 0x80; + res = hid_write(handle, buf, 65); + + // Request state (cmd 0x81). The first byte is the report number (0x0). + buf[0] = 0x0; + buf[1] = 0x81; + res = hid_write(handle, buf, 65); + + // Read requested state + res = hid_read(handle, buf, 65); + + // Print out the returned buffer. + for (i = 0; i < 4; i++) + printf("buf[%d]: %d\n", i, buf[i]); + + // Close the device + hid_close(handle); + + // Finalize the hidapi library + res = hid_exit(); + + return 0; +} +``` + +You can also use [hidtest/test.c](hidtest/test.c) +as a starting point for your applications. + + +## License + +HIDAPI may be used by one of three licenses as outlined in [LICENSE.txt](LICENSE.txt). + +## Installing HIDAPI + +If you want to build your own application that uses HID devices with HIDAPI, +you need to get HIDAPI development package. + +Depending on what your development environment is, HIDAPI likely to be provided +by your package manager. + +For instance on Ubuntu, HIDAPI is available via APT: +```sh +sudo apt install libhidapi-dev +``` + +HIDAPI package name for other systems/package managers may differ. +Check the documentation/package list of your package manager. + +## Build from Source + +Check [BUILD.md](BUILD.md) for details. diff --git a/src/hidapi/README.txt b/src/hidapi/README.txt deleted file mode 100644 index 756901ec52efe..0000000000000 --- a/src/hidapi/README.txt +++ /dev/null @@ -1,339 +0,0 @@ - HIDAPI library for Windows, Linux, FreeBSD and Mac OS X - ========================================================= - -About -====== - -HIDAPI is a multi-platform library which allows an application to interface -with USB and Bluetooth HID-Class devices on Windows, Linux, FreeBSD, and Mac -OS X. HIDAPI can be either built as a shared library (.so or .dll) or -can be embedded directly into a target application by adding a single source -file (per platform) and a single header. - -HIDAPI has four back-ends: - * Windows (using hid.dll) - * Linux/hidraw (using the Kernel's hidraw driver) - * Linux/libusb (using libusb-1.0) - * FreeBSD (using libusb-1.0) - * Mac (using IOHidManager) - -On Linux, either the hidraw or the libusb back-end can be used. There are -tradeoffs, and the functionality supported is slightly different. - -Linux/hidraw (linux/hid.c): -This back-end uses the hidraw interface in the Linux kernel. While this -back-end will support both USB and Bluetooth, it has some limitations on -kernels prior to 2.6.39, including the inability to send or receive feature -reports. In addition, it will only communicate with devices which have -hidraw nodes associated with them. Keyboards, mice, and some other devices -which are blacklisted from having hidraw nodes will not work. Fortunately, -for nearly all the uses of hidraw, this is not a problem. - -Linux/FreeBSD/libusb (libusb/hid.c): -This back-end uses libusb-1.0 to communicate directly to a USB device. This -back-end will of course not work with Bluetooth devices. - -HIDAPI also comes with a Test GUI. The Test GUI is cross-platform and uses -Fox Toolkit (http://www.fox-toolkit.org). It will build on every platform -which HIDAPI supports. Since it relies on a 3rd party library, building it -is optional but recommended because it is so useful when debugging hardware. - -What Does the API Look Like? -============================= -The API provides the the most commonly used HID functions including sending -and receiving of input, output, and feature reports. The sample program, -which communicates with a heavily hacked up version of the Microchip USB -Generic HID sample looks like this (with error checking removed for -simplicity): - -#ifdef WIN32 -#include -#endif -#include -#include -#include "hidapi.h" - -#define MAX_STR 255 - -int main(int argc, char* argv[]) -{ - int res; - unsigned char buf[65]; - wchar_t wstr[MAX_STR]; - hid_device *handle; - int i; - - // Initialize the hidapi library - res = hid_init(); - - // Open the device using the VID, PID, - // and optionally the Serial number. - handle = hid_open(0x4d8, 0x3f, NULL); - - // Read the Manufacturer String - res = hid_get_manufacturer_string(handle, wstr, MAX_STR); - wprintf(L"Manufacturer String: %s\n", wstr); - - // Read the Product String - res = hid_get_product_string(handle, wstr, MAX_STR); - wprintf(L"Product String: %s\n", wstr); - - // Read the Serial Number String - res = hid_get_serial_number_string(handle, wstr, MAX_STR); - wprintf(L"Serial Number String: (%d) %s\n", wstr[0], wstr); - - // Read Indexed String 1 - res = hid_get_indexed_string(handle, 1, wstr, MAX_STR); - wprintf(L"Indexed String 1: %s\n", wstr); - - // Toggle LED (cmd 0x80). The first byte is the report number (0x0). - buf[0] = 0x0; - buf[1] = 0x80; - res = hid_write(handle, buf, 65); - - // Request state (cmd 0x81). The first byte is the report number (0x0). - buf[0] = 0x0; - buf[1] = 0x81; - res = hid_write(handle, buf, 65); - - // Read requested state - res = hid_read(handle, buf, 65); - - // Print out the returned buffer. - for (i = 0; i < 4; i++) - printf("buf[%d]: %d\n", i, buf[i]); - - // Finalize the hidapi library - res = hid_exit(); - - return 0; -} - -If you have your own simple test programs which communicate with standard -hardware development boards (such as those from Microchip, TI, Atmel, -FreeScale and others), please consider sending me something like the above -for inclusion into the HIDAPI source. This will help others who have the -same hardware as you do. - -License -======== -HIDAPI may be used by one of three licenses as outlined in LICENSE.txt. - -Download -========= -HIDAPI can be downloaded from github - git clone git://github.com/libusb/hidapi.git - -Build Instructions -=================== - -This section is long. Don't be put off by this. It's not long because it's -complicated to build HIDAPI; it's quite the opposite. This section is long -because of the flexibility of HIDAPI and the large number of ways in which -it can be built and used. You will likely pick a single build method. - -HIDAPI can be built in several different ways. If you elect to build a -shared library, you will need to build it from the HIDAPI source -distribution. If you choose instead to embed HIDAPI directly into your -application, you can skip the building and look at the provided platform -Makefiles for guidance. These platform Makefiles are located in linux/ -libusb/ mac/ and windows/ and are called Makefile-manual. In addition, -Visual Studio projects are provided. Even if you're going to embed HIDAPI -into your project, it is still beneficial to build the example programs. - - -Prerequisites: ---------------- - - Linux: - ------- - On Linux, you will need to install development packages for libudev, - libusb and optionally Fox-toolkit (for the test GUI). On - Debian/Ubuntu systems these can be installed by running: - sudo apt-get install libudev-dev libusb-1.0-0-dev libfox-1.6-dev - - If you downloaded the source directly from the git repository (using - git clone), you'll need Autotools: - sudo apt-get install autotools-dev autoconf automake libtool - - FreeBSD: - --------- - On FreeBSD you will need to install GNU make, libiconv, and - optionally Fox-Toolkit (for the test GUI). This is done by running - the following: - pkg_add -r gmake libiconv fox16 - - If you downloaded the source directly from the git repository (using - git clone), you'll need Autotools: - pkg_add -r autotools - - Mac: - ----- - On Mac, you will need to install Fox-Toolkit if you wish to build - the Test GUI. There are two ways to do this, and each has a slight - complication. Which method you use depends on your use case. - - If you wish to build the Test GUI just for your own testing on your - own computer, then the easiest method is to install Fox-Toolkit - using ports: - sudo port install fox - - If you wish to build the TestGUI app bundle to redistribute to - others, you will need to install Fox-toolkit from source. This is - because the version of fox that gets installed using ports uses the - ports X11 libraries which are not compatible with the Apple X11 - libraries. If you install Fox with ports and then try to distribute - your built app bundle, it will simply fail to run on other systems. - To install Fox-Toolkit manually, download the source package from - http://www.fox-toolkit.org, extract it, and run the following from - within the extracted source: - ./configure && make && make install - - Windows: - --------- - On Windows, if you want to build the test GUI, you will need to get - the hidapi-externals.zip package from the download site. This - contains pre-built binaries for Fox-toolkit. Extract - hidapi-externals.zip just outside of hidapi, so that - hidapi-externals and hidapi are on the same level, as shown: - - Parent_Folder - | - +hidapi - +hidapi-externals - - Again, this step is not required if you do not wish to build the - test GUI. - - -Building HIDAPI into a shared library on Unix Platforms: ---------------------------------------------------------- - -On Unix-like systems such as Linux, FreeBSD, Mac, and even Windows, using -Mingw or Cygwin, the easiest way to build a standard system-installed shared -library is to use the GNU Autotools build system. If you checked out the -source from the git repository, run the following: - - ./bootstrap - ./configure - make - make install <----- as root, or using sudo - -If you downloaded a source package (ie: if you did not run git clone), you -can skip the ./bootstrap step. - -./configure can take several arguments which control the build. The two most -likely to be used are: - --enable-testgui - Enable build of the Test GUI. This requires Fox toolkit to - be installed. Instructions for installing Fox-Toolkit on - each platform are in the Prerequisites section above. - - --prefix=/usr - Specify where you want the output headers and libraries to - be installed. The example above will put the headers in - /usr/include and the binaries in /usr/lib. The default is to - install into /usr/local which is fine on most systems. - -Building the manual way on Unix platforms: -------------------------------------------- - -Manual Makefiles are provided mostly to give the user and idea what it takes -to build a program which embeds HIDAPI directly inside of it. These should -really be used as examples only. If you want to build a system-wide shared -library, use the Autotools method described above. - - To build HIDAPI using the manual makefiles, change to the directory - of your platform and run make. For example, on Linux run: - cd linux/ - make -f Makefile-manual - - To build the Test GUI using the manual makefiles: - cd testgui/ - make -f Makefile-manual - -Building on Windows: ---------------------- - -To build the HIDAPI DLL on Windows using Visual Studio, build the .sln file -in the windows/ directory. - -To build the Test GUI on windows using Visual Studio, build the .sln file in -the testgui/ directory. - -To build HIDAPI using MinGW or Cygwin using Autotools, use the instructions -in the section titled "Building HIDAPI into a shared library on Unix -Platforms" above. Note that building the Test GUI with MinGW or Cygwin will -require the Windows procedure in the Prerequisites section above (ie: -hidapi-externals.zip). - -To build HIDAPI using MinGW using the Manual Makefiles, see the section -"Building the manual way on Unix platforms" above. - -HIDAPI can also be built using the Windows DDK (now also called the Windows -Driver Kit or WDK). This method was originally required for the HIDAPI build -but not anymore. However, some users still prefer this method. It is not as -well supported anymore but should still work. Patches are welcome if it does -not. To build using the DDK: - - 1. Install the Windows Driver Kit (WDK) from Microsoft. - 2. From the Start menu, in the Windows Driver Kits folder, select Build - Environments, then your operating system, then the x86 Free Build - Environment (or one that is appropriate for your system). - 3. From the console, change directory to the windows/ddk_build/ directory, - which is part of the HIDAPI distribution. - 4. Type build. - 5. You can find the output files (DLL and LIB) in a subdirectory created - by the build system which is appropriate for your environment. On - Windows XP, this directory is objfre_wxp_x86/i386. - -Cross Compiling -================ - -This section talks about cross compiling HIDAPI for Linux using autotools. -This is useful for using HIDAPI on embedded Linux targets. These -instructions assume the most raw kind of embedded Linux build, where all -prerequisites will need to be built first. This process will of course vary -based on your embedded Linux build system if you are using one, such as -OpenEmbedded or Buildroot. - -For the purpose of this section, it will be assumed that the following -environment variables are exported. - - $ export STAGING=$HOME/out - $ export HOST=arm-linux - -STAGING and HOST can be modified to suit your setup. - -Prerequisites --------------- - -Note that the build of libudev is the very basic configuration. - -Build Libusb. From the libusb source directory, run: - ./configure --host=$HOST --prefix=$STAGING - make - make install - -Build libudev. From the libudev source directory, run: - ./configure --disable-gudev --disable-introspection --disable-hwdb \ - --host=$HOST --prefix=$STAGING - make - make install - -Building HIDAPI ----------------- - -Build HIDAPI: - - PKG_CONFIG_DIR= \ - PKG_CONFIG_LIBDIR=$STAGING/lib/pkgconfig:$STAGING/share/pkgconfig \ - PKG_CONFIG_SYSROOT_DIR=$STAGING \ - ./configure --host=$HOST --prefix=$STAGING - - -Signal 11 Software - 2010-04-11 - 2010-07-28 - 2011-09-10 - 2012-05-01 - 2012-07-03 diff --git a/src/hidapi/VERSION b/src/hidapi/VERSION new file mode 100644 index 0000000000000..0548fb4e9b2f2 --- /dev/null +++ b/src/hidapi/VERSION @@ -0,0 +1 @@ +0.14.0 \ No newline at end of file diff --git a/src/hidapi/android/hid.cpp b/src/hidapi/android/hid.cpp deleted file mode 100644 index 450cab2084043..0000000000000 --- a/src/hidapi/android/hid.cpp +++ /dev/null @@ -1,1443 +0,0 @@ -/* - Simple DirectMedia Layer - Copyright (C) 2022 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. - -#include "hid.h" - -// Common to stub version and non-stub version of functions -#include -#include - -#define TAG "hidapi" - -// Have error log always available -#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__) - -#ifdef DEBUG -#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, TAG, __VA_ARGS__) -#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__) -#else -#define LOGV(...) -#define LOGD(...) -#endif - -#define SDL_JAVA_PREFIX org_libsdl_app -#define CONCAT1(prefix, class, function) CONCAT2(prefix, class, function) -#define CONCAT2(prefix, class, function) Java_ ## prefix ## _ ## class ## _ ## function -#define HID_DEVICE_MANAGER_JAVA_INTERFACE(function) CONCAT1(SDL_JAVA_PREFIX, HIDDeviceManager, function) - - -#ifndef SDL_HIDAPI_DISABLED - -#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 -#include // For ETIMEDOUT and ECONNRESET -#include // For malloc() and free() - -#include "../hidapi/hidapi.h" - -typedef uint32_t uint32; -typedef uint64_t uint64; - - -struct hid_device_ -{ - int m_nId; - int m_nDeviceRefCount; -}; - -static JavaVM *g_JVM; -static pthread_key_t g_ThreadKey; - -template -class hid_device_ref -{ -public: - hid_device_ref( T *pObject = nullptr ) : m_pObject( nullptr ) - { - SetObject( pObject ); - } - - hid_device_ref( const hid_device_ref &rhs ) : m_pObject( nullptr ) - { - SetObject( rhs.GetObject() ); - } - - ~hid_device_ref() - { - SetObject( nullptr ); - } - - void SetObject( T *pObject ) - { - if ( m_pObject && m_pObject->DecrementRefCount() == 0 ) - { - delete m_pObject; - } - - m_pObject = pObject; - - if ( m_pObject ) - { - m_pObject->IncrementRefCount(); - } - } - - hid_device_ref &operator =( T *pObject ) - { - SetObject( pObject ); - return *this; - } - - hid_device_ref &operator =( const hid_device_ref &rhs ) - { - SetObject( rhs.GetObject() ); - return *this; - } - - T *GetObject() const - { - return m_pObject; - } - - T* operator->() const - { - return m_pObject; - } - - operator bool() const - { - return ( m_pObject != nullptr ); - } - -private: - T *m_pObject; -}; - -class hid_mutex_guard -{ -public: - hid_mutex_guard( pthread_mutex_t *pMutex ) : m_pMutex( pMutex ) - { - pthread_mutex_lock( m_pMutex ); - } - ~hid_mutex_guard() - { - pthread_mutex_unlock( m_pMutex ); - } - -private: - pthread_mutex_t *m_pMutex; -}; - -class hid_buffer -{ -public: - hid_buffer() : m_pData( nullptr ), m_nSize( 0 ), m_nAllocated( 0 ) - { - } - - hid_buffer( const uint8_t *pData, size_t nSize ) : m_pData( nullptr ), m_nSize( 0 ), m_nAllocated( 0 ) - { - assign( pData, nSize ); - } - - ~hid_buffer() - { - delete[] m_pData; - } - - void assign( const uint8_t *pData, size_t nSize ) - { - if ( nSize > m_nAllocated ) - { - delete[] m_pData; - m_pData = new uint8_t[ nSize ]; - m_nAllocated = nSize; - } - - m_nSize = nSize; - SDL_memcpy( m_pData, pData, nSize ); - } - - void clear() - { - m_nSize = 0; - } - - size_t size() const - { - return m_nSize; - } - - const uint8_t *data() const - { - return m_pData; - } - -private: - uint8_t *m_pData; - size_t m_nSize; - size_t m_nAllocated; -}; - -class hid_buffer_pool -{ -public: - hid_buffer_pool() : m_nSize( 0 ), m_pHead( nullptr ), m_pTail( nullptr ), m_pFree( nullptr ) - { - } - - ~hid_buffer_pool() - { - clear(); - - while ( m_pFree ) - { - hid_buffer_entry *pEntry = m_pFree; - m_pFree = m_pFree->m_pNext; - delete pEntry; - } - } - - size_t size() const { return m_nSize; } - - const hid_buffer &front() const { return m_pHead->m_buffer; } - - void pop_front() - { - hid_buffer_entry *pEntry = m_pHead; - if ( pEntry ) - { - m_pHead = pEntry->m_pNext; - if ( !m_pHead ) - { - m_pTail = nullptr; - } - pEntry->m_pNext = m_pFree; - m_pFree = pEntry; - --m_nSize; - } - } - - void emplace_back( const uint8_t *pData, size_t nSize ) - { - hid_buffer_entry *pEntry; - - if ( m_pFree ) - { - pEntry = m_pFree; - m_pFree = m_pFree->m_pNext; - } - else - { - pEntry = new hid_buffer_entry; - } - pEntry->m_pNext = nullptr; - - if ( m_pTail ) - { - m_pTail->m_pNext = pEntry; - } - else - { - m_pHead = pEntry; - } - m_pTail = pEntry; - - pEntry->m_buffer.assign( pData, nSize ); - ++m_nSize; - } - - void clear() - { - while ( size() > 0 ) - { - pop_front(); - } - } - -private: - struct hid_buffer_entry - { - hid_buffer m_buffer; - hid_buffer_entry *m_pNext; - }; - - size_t m_nSize; - hid_buffer_entry *m_pHead; - hid_buffer_entry *m_pTail; - hid_buffer_entry *m_pFree; -}; - -static jbyteArray NewByteArray( JNIEnv* env, const uint8_t *pData, size_t nDataLen ) -{ - jbyteArray array = env->NewByteArray( (jsize)nDataLen ); - jbyte *pBuf = env->GetByteArrayElements( array, NULL ); - SDL_memcpy( pBuf, pData, nDataLen ); - env->ReleaseByteArrayElements( array, pBuf, 0 ); - - return array; -} - -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 ); - SDL_memcpy( psString, pjChars, nLength ); - psString[ nLength ] = '\0'; - env->ReleaseStringUTFChars( sString, pjChars ); - return psString; -} - -static wchar_t *CreateWStringFromJString( JNIEnv *env, const jstring &sString ) -{ - size_t nLength = env->GetStringLength( sString ); - const jchar *pjChars = env->GetStringChars( sString, NULL ); - wchar_t *pwString = (wchar_t*)malloc( ( nLength + 1 ) * sizeof( wchar_t ) ); - wchar_t *pwChars = pwString; - for ( size_t iIndex = 0; iIndex < nLength; ++iIndex ) - { - pwChars[ iIndex ] = pjChars[ iIndex ]; - } - pwString[ nLength ] = '\0'; - env->ReleaseStringChars( sString, pjChars ); - return pwString; -} - -static wchar_t *CreateWStringFromWString( const wchar_t *pwSrc ) -{ - size_t nLength = SDL_wcslen( pwSrc ); - wchar_t *pwString = (wchar_t*)malloc( ( nLength + 1 ) * sizeof( wchar_t ) ); - SDL_memcpy( pwString, pwSrc, nLength * sizeof( wchar_t ) ); - pwString[ nLength ] = '\0'; - return pwString; -} - -static hid_device_info *CopyHIDDeviceInfo( const hid_device_info *pInfo ) -{ - hid_device_info *pCopy = new hid_device_info; - *pCopy = *pInfo; - 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 ); - return pCopy; -} - -static void FreeHIDDeviceInfo( hid_device_info *pInfo ) -{ - free( pInfo->path ); - free( pInfo->serial_number ); - free( pInfo->manufacturer_string ); - free( pInfo->product_string ); - delete 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: - CHIDDevice( int nDeviceID, hid_device_info *pInfo ) - { - m_nId = nDeviceID; - m_pInfo = pInfo; - - // The Bluetooth Steam Controller needs special handling - const int VALVE_USB_VID = 0x28DE; - const int D0G_BLE2_PID = 0x1106; - if ( pInfo->vendor_id == VALVE_USB_VID && pInfo->product_id == D0G_BLE2_PID ) - { - m_bIsBLESteamController = true; - } - } - - ~CHIDDevice() - { - FreeHIDDeviceInfo( m_pInfo ); - - // Note that we don't delete m_pDevice, as the app may still have a reference to it - } - - int IncrementRefCount() - { - int nValue; - pthread_mutex_lock( &m_refCountLock ); - nValue = ++m_nRefCount; - pthread_mutex_unlock( &m_refCountLock ); - return nValue; - } - - int DecrementRefCount() - { - int nValue; - pthread_mutex_lock( &m_refCountLock ); - nValue = --m_nRefCount; - pthread_mutex_unlock( &m_refCountLock ); - return nValue; - } - - int GetId() - { - return m_nId; - } - - const hid_device_info *GetDeviceInfo() - { - return m_pInfo; - } - - hid_device *GetDevice() - { - return m_pDevice; - } - - void ExceptionCheck( JNIEnv *env, const char *pszMethodName ) - { - ::ExceptionCheck( env, "CHIDDevice", pszMethodName ); - } - - bool BOpen() - { - // 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( "Device open without callback handler" ); - return false; - } - - m_bIsWaitingForOpen = false; - m_bOpenResult = env->CallBooleanMethod( g_HIDDeviceManagerCallbackHandler, g_midHIDDeviceManagerOpen, m_nId ); - ExceptionCheck( env, "BOpen" ); - - if ( m_bIsWaitingForOpen ) - { - hid_mutex_guard cvl( &m_cvLock ); - - const int OPEN_TIMEOUT_SECONDS = 60; - struct timespec ts, endtime; - clock_gettime( CLOCK_REALTIME, &ts ); - endtime = ts; - endtime.tv_sec += OPEN_TIMEOUT_SECONDS; - do - { - if ( pthread_cond_timedwait( &m_cv, &m_cvLock, &endtime ) != 0 ) - { - break; - } - } - while ( m_bIsWaitingForOpen && get_timespec_ms( ts ) < get_timespec_ms( endtime ) ); - } - - if ( !m_bOpenResult ) - { - if ( m_bIsWaitingForOpen ) - { - LOGV( "Device open failed - timed out waiting for device permission" ); - } - else - { - LOGV( "Device open failed" ); - } - return false; - } - - m_pDevice = new hid_device; - m_pDevice->m_nId = m_nId; - m_pDevice->m_nDeviceRefCount = 1; - LOGD("Creating device %d (%p), refCount = 1\n", m_pDevice->m_nId, m_pDevice); - return true; - } - - void SetOpenPending() - { - m_bIsWaitingForOpen = true; - } - - void SetOpenResult( bool bResult ) - { - if ( m_bIsWaitingForOpen ) - { - m_bOpenResult = bResult; - m_bIsWaitingForOpen = false; - pthread_cond_signal( &m_cv ); - } - } - - void ProcessInput( const uint8_t *pBuf, size_t nBufSize ) - { - hid_mutex_guard l( &m_dataLock ); - - size_t MAX_REPORT_QUEUE_SIZE = 16; - if ( m_vecData.size() >= MAX_REPORT_QUEUE_SIZE ) - { - m_vecData.pop_front(); - } - m_vecData.emplace_back( pBuf, nBufSize ); - } - - int GetInput( unsigned char *data, size_t length ) - { - hid_mutex_guard l( &m_dataLock ); - - if ( m_vecData.size() == 0 ) - { -// LOGV( "hid_read_timeout no data available" ); - return 0; - } - - const hid_buffer &buffer = m_vecData.front(); - size_t nDataLen = buffer.size() > length ? length : buffer.size(); - if ( m_bIsBLESteamController ) - { - data[0] = 0x03; - SDL_memcpy( data + 1, buffer.data(), nDataLen ); - ++nDataLen; - } - else - { - SDL_memcpy( data, buffer.data(), nDataLen ); - } - m_vecData.pop_front(); - -// LOGV("Read %u bytes", nDataLen); -// LOGV("%02x %02x %02x %02x %02x %02x %02x %02x ....", -// data[0], data[1], data[2], data[3], -// data[4], data[5], data[6], data[7]); - - return (int)nDataLen; - } - - int SendOutputReport( const unsigned char *pData, size_t nDataLen ) - { - // Make sure thread is attached to JVM/env - JNIEnv *env; - g_JVM->AttachCurrentThread( &env, NULL ); - pthread_setspecific( g_ThreadKey, (void*)env ); - - int nRet = -1; - if ( g_HIDDeviceManagerCallbackHandler ) - { - jbyteArray pBuf = NewByteArray( env, pData, nDataLen ); - nRet = env->CallIntMethod( g_HIDDeviceManagerCallbackHandler, g_midHIDDeviceManagerSendOutputReport, m_nId, pBuf ); - ExceptionCheck( env, "SendOutputReport" ); - env->DeleteLocalRef( pBuf ); - } - else - { - LOGV( "SendOutputReport without callback handler" ); - } - return nRet; - } - - int SendFeatureReport( const unsigned char *pData, size_t nDataLen ) - { - // Make sure thread is attached to JVM/env - JNIEnv *env; - g_JVM->AttachCurrentThread( &env, NULL ); - pthread_setspecific( g_ThreadKey, (void*)env ); - - int nRet = -1; - if ( g_HIDDeviceManagerCallbackHandler ) - { - jbyteArray pBuf = NewByteArray( env, pData, nDataLen ); - nRet = env->CallIntMethod( g_HIDDeviceManagerCallbackHandler, g_midHIDDeviceManagerSendFeatureReport, m_nId, pBuf ); - ExceptionCheck( env, "SendFeatureReport" ); - env->DeleteLocalRef( pBuf ); - } - else - { - LOGV( "SendFeatureReport without callback handler" ); - } - return nRet; - } - - void ProcessFeatureReport( const uint8_t *pBuf, size_t nBufSize ) - { - hid_mutex_guard cvl( &m_cvLock ); - if ( m_bIsWaitingForFeatureReport ) - { - m_featureReport.assign( pBuf, nBufSize ); - - m_bIsWaitingForFeatureReport = false; - m_nFeatureReportError = 0; - pthread_cond_signal( &m_cv ); - } - } - - int GetFeatureReport( unsigned char *pData, size_t nDataLen ) - { - // 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( "GetFeatureReport without callback handler" ); - return -1; - } - - { - hid_mutex_guard cvl( &m_cvLock ); - if ( m_bIsWaitingForFeatureReport ) - { - LOGV( "Get feature report already ongoing... bail" ); - return -1; // Read already ongoing, we currently do not serialize, TODO - } - m_bIsWaitingForFeatureReport = true; - } - - jbyteArray pBuf = NewByteArray( env, pData, nDataLen ); - int nRet = env->CallBooleanMethod( g_HIDDeviceManagerCallbackHandler, g_midHIDDeviceManagerGetFeatureReport, m_nId, pBuf ) ? 0 : -1; - ExceptionCheck( env, "GetFeatureReport" ); - env->DeleteLocalRef( pBuf ); - if ( nRet < 0 ) - { - LOGV( "GetFeatureReport failed" ); - m_bIsWaitingForFeatureReport = false; - return -1; - } - - { - hid_mutex_guard cvl( &m_cvLock ); - if ( m_bIsWaitingForFeatureReport ) - { - LOGV("=== Going to sleep" ); - // Wait in CV until we are no longer waiting for a feature report. - const int FEATURE_REPORT_TIMEOUT_SECONDS = 2; - struct timespec ts, endtime; - clock_gettime( CLOCK_REALTIME, &ts ); - endtime = ts; - endtime.tv_sec += FEATURE_REPORT_TIMEOUT_SECONDS; - do - { - if ( pthread_cond_timedwait( &m_cv, &m_cvLock, &endtime ) != 0 ) - { - break; - } - } - while ( m_bIsWaitingForFeatureReport && get_timespec_ms( ts ) < get_timespec_ms( endtime ) ); - - // We are back - if ( m_bIsWaitingForFeatureReport ) - { - m_nFeatureReportError = -ETIMEDOUT; - m_bIsWaitingForFeatureReport = false; - } - LOGV( "=== Got feature report err=%d", m_nFeatureReportError ); - if ( m_nFeatureReportError != 0 ) - { - return m_nFeatureReportError; - } - } - - size_t uBytesToCopy = m_featureReport.size() > nDataLen ? nDataLen : m_featureReport.size(); - SDL_memcpy( pData, m_featureReport.data(), uBytesToCopy ); - m_featureReport.clear(); - LOGV( "=== Got %u bytes", uBytesToCopy ); - - return (int)uBytesToCopy; - } - } - - void Close( bool bDeleteDevice ) - { - // Make sure thread is attached to JVM/env - JNIEnv *env; - g_JVM->AttachCurrentThread( &env, NULL ); - pthread_setspecific( g_ThreadKey, (void*)env ); - - if ( g_HIDDeviceManagerCallbackHandler ) - { - env->CallVoidMethod( g_HIDDeviceManagerCallbackHandler, g_midHIDDeviceManagerClose, m_nId ); - ExceptionCheck( env, "Close" ); - } - - hid_mutex_guard dataLock( &m_dataLock ); - m_vecData.clear(); - - // Clean and release pending feature report reads - hid_mutex_guard cvLock( &m_cvLock ); - m_featureReport.clear(); - m_bIsWaitingForFeatureReport = false; - m_nFeatureReportError = -ECONNRESET; - pthread_cond_broadcast( &m_cv ); - - if ( bDeleteDevice ) - { - delete m_pDevice; - m_pDevice = nullptr; - } - } - -private: - pthread_mutex_t m_refCountLock = PTHREAD_MUTEX_INITIALIZER; - int m_nRefCount = 0; - int m_nId = 0; - hid_device_info *m_pInfo = nullptr; - hid_device *m_pDevice = nullptr; - bool m_bIsBLESteamController = false; - - pthread_mutex_t m_dataLock = PTHREAD_MUTEX_INITIALIZER; // This lock has to be held to access m_vecData - hid_buffer_pool m_vecData; - - // For handling get_feature_report - pthread_mutex_t m_cvLock = PTHREAD_MUTEX_INITIALIZER; // This lock has to be held to access any variables below - pthread_cond_t m_cv = PTHREAD_COND_INITIALIZER; - bool m_bIsWaitingForOpen = false; - bool m_bOpenResult = false; - bool m_bIsWaitingForFeatureReport = false; - int m_nFeatureReportError = 0; - hid_buffer m_featureReport; - -public: - hid_device_ref next; -}; - -class CHIDDevice; -static pthread_mutex_t g_DevicesMutex = PTHREAD_MUTEX_INITIALIZER; -static pthread_mutex_t g_DevicesRefCountMutex = PTHREAD_MUTEX_INITIALIZER; -static hid_device_ref g_Devices; - -static hid_device_ref FindDevice( int nDeviceId ) -{ - hid_device_ref pDevice; - - hid_mutex_guard l( &g_DevicesMutex ); - for ( pDevice = g_Devices; pDevice; pDevice = pDevice->next ) - { - if ( pDevice->GetId() == nDeviceId ) - { - break; - } - } - return pDevice; -} - -static void ThreadDestroyed(void* value) -{ - /* The thread is being destroyed, detach it from the Java VM and set the g_ThreadKey value to NULL as required */ - JNIEnv *env = (JNIEnv*) value; - if (env != NULL) { - g_JVM->DetachCurrentThread(); - pthread_setspecific(g_ThreadKey, NULL); - } -} - - -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( "HIDDeviceRegisterCallback()"); - - env->GetJavaVM( &g_JVM ); - - /* - * Create mThreadKey so we can keep track of the JNIEnv assigned to each thread - * Refer to http://developer.android.com/guide/practices/design/jni.html for the rationale behind this - */ - if (pthread_key_create(&g_ThreadKey, ThreadDestroyed) != 0) { - __android_log_print(ANDROID_LOG_ERROR, TAG, "Error initializing pthread key"); - } - - if ( g_HIDDeviceManagerCallbackHandler != NULL ) - { - env->DeleteGlobalRef( g_HIDDeviceManagerCallbackClass ); - g_HIDDeviceManagerCallbackClass = NULL; - env->DeleteGlobalRef( g_HIDDeviceManagerCallbackHandler ); - g_HIDDeviceManagerCallbackHandler = NULL; - } - - g_HIDDeviceManagerCallbackHandler = env->NewGlobalRef( thiz ); - jclass objClass = env->GetObjectClass( thiz ); - 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 ) - { - __android_log_print(ANDROID_LOG_ERROR, TAG, "HIDDeviceRegisterCallback: callback class missing openDevice" ); - } - g_midHIDDeviceManagerSendOutputReport = env->GetMethodID( g_HIDDeviceManagerCallbackClass, "sendOutputReport", "(I[B)I" ); - if ( !g_midHIDDeviceManagerSendOutputReport ) - { - __android_log_print(ANDROID_LOG_ERROR, TAG, "HIDDeviceRegisterCallback: callback class missing sendOutputReport" ); - } - g_midHIDDeviceManagerSendFeatureReport = env->GetMethodID( g_HIDDeviceManagerCallbackClass, "sendFeatureReport", "(I[B)I" ); - if ( !g_midHIDDeviceManagerSendFeatureReport ) - { - __android_log_print(ANDROID_LOG_ERROR, TAG, "HIDDeviceRegisterCallback: callback class missing sendFeatureReport" ); - } - g_midHIDDeviceManagerGetFeatureReport = env->GetMethodID( g_HIDDeviceManagerCallbackClass, "getFeatureReport", "(I[B)Z" ); - if ( !g_midHIDDeviceManagerGetFeatureReport ) - { - __android_log_print(ANDROID_LOG_ERROR, TAG, "HIDDeviceRegisterCallback: callback class missing getFeatureReport" ); - } - g_midHIDDeviceManagerClose = env->GetMethodID( g_HIDDeviceManagerCallbackClass, "closeDevice", "(I)V" ); - if ( !g_midHIDDeviceManagerClose ) - { - __android_log_print(ANDROID_LOG_ERROR, TAG, "HIDDeviceRegisterCallback: callback class missing closeDevice" ); - } - env->DeleteLocalRef( objClass ); - } -} - -extern "C" -JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceReleaseCallback)(JNIEnv *env, jobject thiz) -{ - LOGV("HIDDeviceReleaseCallback"); - if ( env->IsSameObject( thiz, g_HIDDeviceManagerCallbackHandler ) ) - { - env->DeleteGlobalRef( g_HIDDeviceManagerCallbackClass ); - g_HIDDeviceManagerCallbackClass = NULL; - env->DeleteGlobalRef( g_HIDDeviceManagerCallbackHandler ); - g_HIDDeviceManagerCallbackHandler = NULL; - g_initialized = false; - } -} - -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( "HIDDeviceConnected() id=%d VID/PID = %.4x/%.4x, interface %d\n", nDeviceID, nVendorId, nProductId, nInterface ); - - hid_device_info *pInfo = new hid_device_info; - SDL_memset( pInfo, 0, sizeof( *pInfo ) ); - pInfo->path = CreateStringFromJString( env, sIdentifier ); - pInfo->vendor_id = nVendorId; - pInfo->product_id = nProductId; - pInfo->serial_number = CreateWStringFromJString( env, sSerialNumber ); - pInfo->release_number = nReleaseNumber; - pInfo->manufacturer_string = CreateWStringFromJString( env, sManufacturer ); - pInfo->product_string = CreateWStringFromJString( env, sProduct ); - pInfo->interface_number = nInterface; - pInfo->interface_class = nInterfaceClass; - pInfo->interface_subclass = nInterfaceSubclass; - pInfo->interface_protocol = nInterfaceProtocol; - - hid_device_ref pDevice( new CHIDDevice( nDeviceID, pInfo ) ); - - hid_mutex_guard l( &g_DevicesMutex ); - hid_device_ref pLast, pCurr; - for ( pCurr = g_Devices; pCurr; pLast = pCurr, pCurr = pCurr->next ) - { - continue; - } - if ( pLast ) - { - pLast->next = pDevice; - } - else - { - g_Devices = pDevice; - } -} - -extern "C" -JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceOpenPending)(JNIEnv *env, jobject thiz, int nDeviceID) -{ - LOGV( "HIDDeviceOpenPending() id=%d\n", nDeviceID ); - hid_device_ref pDevice = FindDevice( nDeviceID ); - if ( pDevice ) - { - pDevice->SetOpenPending(); - } -} - -extern "C" -JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceOpenResult)(JNIEnv *env, jobject thiz, int nDeviceID, bool bOpened) -{ - LOGV( "HIDDeviceOpenResult() id=%d, result=%s\n", nDeviceID, bOpened ? "true" : "false" ); - hid_device_ref pDevice = FindDevice( nDeviceID ); - if ( pDevice ) - { - pDevice->SetOpenResult( bOpened ); - } -} - -extern "C" -JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceDisconnected)(JNIEnv *env, jobject thiz, int nDeviceID) -{ - LOGV( "HIDDeviceDisconnected() id=%d\n", nDeviceID ); - hid_device_ref pDevice; - { - hid_mutex_guard l( &g_DevicesMutex ); - hid_device_ref pLast, pCurr; - for ( pCurr = g_Devices; pCurr; pLast = pCurr, pCurr = pCurr->next ) - { - if ( pCurr->GetId() == nDeviceID ) - { - pDevice = pCurr; - - if ( pLast ) - { - pLast->next = pCurr->next; - } - else - { - g_Devices = pCurr->next; - } - } - } - } - if ( pDevice ) - { - pDevice->Close( false ); - } -} - -extern "C" -JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceInputReport)(JNIEnv *env, jobject thiz, int nDeviceID, jbyteArray value) -{ - jbyte *pBuf = env->GetByteArrayElements(value, NULL); - jsize nBufSize = env->GetArrayLength(value); - -// LOGV( "HIDDeviceInput() id=%d len=%u\n", nDeviceID, nBufSize ); - hid_device_ref pDevice = FindDevice( nDeviceID ); - if ( pDevice ) - { - pDevice->ProcessInput( reinterpret_cast< const uint8_t* >( pBuf ), nBufSize ); - } - - env->ReleaseByteArrayElements(value, pBuf, 0); -} - -extern "C" -JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceFeatureReport)(JNIEnv *env, jobject thiz, int nDeviceID, jbyteArray value) -{ - jbyte *pBuf = env->GetByteArrayElements(value, NULL); - jsize nBufSize = env->GetArrayLength(value); - - LOGV( "HIDDeviceFeatureReport() id=%d len=%u\n", nDeviceID, nBufSize ); - hid_device_ref pDevice = FindDevice( nDeviceID ); - if ( pDevice ) - { - pDevice->ProcessFeatureReport( reinterpret_cast< const uint8_t* >( pBuf ), nBufSize ); - } - - env->ReleaseByteArrayElements(value, pBuf, 0); -} - -////////////////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -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; -} - -struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned short vendor_id, unsigned short product_id) -{ - struct hid_device_info *root = NULL; - const char *hint = SDL_GetHint(SDL_HINT_HIDAPI_IGNORE_DEVICES); - - hid_mutex_guard l( &g_DevicesMutex ); - for ( hid_device_ref pDevice = g_Devices; pDevice; pDevice = pDevice->next ) - { - const hid_device_info *info = pDevice->GetDeviceInfo(); - - /* See if there are any devices we should skip in enumeration */ - if (hint) { - char vendor_match[16], product_match[16]; - SDL_snprintf(vendor_match, sizeof(vendor_match), "0x%.4x/0x0000", info->vendor_id); - SDL_snprintf(product_match, sizeof(product_match), "0x%.4x/0x%.4x", info->vendor_id, info->product_id); - if (SDL_strcasestr(hint, vendor_match) || SDL_strcasestr(hint, product_match)) { - continue; - } - } - - if ( ( vendor_id == 0x0 || info->vendor_id == vendor_id ) && - ( product_id == 0x0 || info->product_id == product_id ) ) - { - hid_device_info *dev = CopyHIDDeviceInfo( info ); - dev->next = root; - root = dev; - } - } - return root; -} - -void HID_API_EXPORT HID_API_CALL hid_free_enumeration(struct hid_device_info *devs) -{ - while ( devs ) - { - struct hid_device_info *next = devs->next; - FreeHIDDeviceInfo( devs ); - devs = next; - } -} - -HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number) -{ - // TODO: Implement - return NULL; -} - -HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path, int bExclusive) -{ - LOGV( "hid_open_path( %s )", path ); - - hid_device_ref< CHIDDevice > pDevice; - { - hid_mutex_guard r( &g_DevicesRefCountMutex ); - hid_mutex_guard l( &g_DevicesMutex ); - for ( hid_device_ref pCurr = g_Devices; pCurr; pCurr = pCurr->next ) - { - if ( SDL_strcmp( pCurr->GetDeviceInfo()->path, path ) == 0 ) - { - hid_device *pValue = pCurr->GetDevice(); - if ( pValue ) - { - ++pValue->m_nDeviceRefCount; - LOGD("Incrementing device %d (%p), refCount = %d\n", pValue->m_nId, pValue, pValue->m_nDeviceRefCount); - return pValue; - } - - // Hold a shared pointer to the controller for the duration - pDevice = pCurr; - break; - } - } - } - if ( pDevice && pDevice->BOpen() ) - { - return pDevice->GetDevice(); - } - return NULL; -} - -int HID_API_EXPORT HID_API_CALL hid_write(hid_device *device, const unsigned char *data, size_t length) -{ - if ( device ) - { - LOGV( "hid_write id=%d length=%u", device->m_nId, length ); - hid_device_ref pDevice = FindDevice( device->m_nId ); - if ( pDevice ) - { - return pDevice->SendOutputReport( data, length ); - } - } - return -1; // Controller was disconnected -} - -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 ) - { -// LOGV( "hid_read_timeout id=%d length=%u timeout=%d", device->m_nId, length, milliseconds ); - hid_device_ref pDevice = FindDevice( device->m_nId ); - if ( pDevice ) - { - 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" ); - } - return -1; // Controller was disconnected -} - -// TODO: Implement blocking -int HID_API_EXPORT HID_API_CALL hid_read(hid_device *device, unsigned char *data, size_t length) -{ - LOGV( "hid_read id=%d length=%u", device->m_nId, length ); - return hid_read_timeout( device, data, length, 0 ); -} - -// TODO: Implement? -int HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *device, int nonblock) -{ - return -1; -} - -int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *device, const unsigned char *data, size_t length) -{ - if ( device ) - { - LOGV( "hid_send_feature_report id=%d length=%u", device->m_nId, length ); - hid_device_ref pDevice = FindDevice( device->m_nId ); - if ( pDevice ) - { - return pDevice->SendFeatureReport( data, length ); - } - } - return -1; // Controller was disconnected -} - - -// Synchronous operation. Will block until completed. -int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *device, unsigned char *data, size_t length) -{ - if ( device ) - { - LOGV( "hid_get_feature_report id=%d length=%u", device->m_nId, length ); - hid_device_ref pDevice = FindDevice( device->m_nId ); - if ( pDevice ) - { - return pDevice->GetFeatureReport( data, length ); - } - } - return -1; // Controller was disconnected -} - - -void HID_API_EXPORT HID_API_CALL hid_close(hid_device *device) -{ - if ( device ) - { - LOGV( "hid_close id=%d", device->m_nId ); - hid_mutex_guard r( &g_DevicesRefCountMutex ); - LOGD("Decrementing device %d (%p), refCount = %d\n", device->m_nId, device, device->m_nDeviceRefCount - 1); - if ( --device->m_nDeviceRefCount == 0 ) - { - hid_device_ref pDevice = FindDevice( device->m_nId ); - if ( pDevice ) - { - pDevice->Close( true ); - } - else - { - delete device; - } - LOGD("Deleted device %p\n", device); - } - } -} - -int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *device, wchar_t *string, size_t maxlen) -{ - if ( device ) - { - hid_device_ref pDevice = FindDevice( device->m_nId ); - if ( pDevice ) - { - wcsncpy( string, pDevice->GetDeviceInfo()->manufacturer_string, maxlen ); - return 0; - } - } - return -1; -} - -int HID_API_EXPORT_CALL hid_get_product_string(hid_device *device, wchar_t *string, size_t maxlen) -{ - if ( device ) - { - hid_device_ref pDevice = FindDevice( device->m_nId ); - if ( pDevice ) - { - wcsncpy( string, pDevice->GetDeviceInfo()->product_string, maxlen ); - return 0; - } - } - return -1; -} - -int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *device, wchar_t *string, size_t maxlen) -{ - if ( device ) - { - hid_device_ref pDevice = FindDevice( device->m_nId ); - if ( pDevice ) - { - wcsncpy( string, pDevice->GetDeviceInfo()->serial_number, maxlen ); - return 0; - } - } - return -1; -} - -int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *device, int string_index, wchar_t *string, size_t maxlen) -{ - return -1; -} - -HID_API_EXPORT const wchar_t* HID_API_CALL hid_error(hid_device *device) -{ - return NULL; -} - -int hid_exit(void) -{ - return 0; -} - -} - -#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 */ - -extern "C" -JNINativeMethod HIDDeviceManager_tab[8] = { - { "HIDDeviceRegisterCallback", "()V", (void*)HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceRegisterCallback) }, - { "HIDDeviceReleaseCallback", "()V", (void*)HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceReleaseCallback) }, - { "HIDDeviceConnected", "(ILjava/lang/String;IILjava/lang/String;ILjava/lang/String;Ljava/lang/String;IIII)V", (void*)HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceConnected) }, - { "HIDDeviceOpenPending", "(I)V", (void*)HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceOpenPending) }, - { "HIDDeviceOpenResult", "(IZ)V", (void*)HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceOpenResult) }, - { "HIDDeviceDisconnected", "(I)V", (void*)HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceDisconnected) }, - { "HIDDeviceInputReport", "(I[B)V", (void*)HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceInputReport) }, - { "HIDDeviceFeatureReport", "(I[B)V", (void*)HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceFeatureReport) } -}; diff --git a/src/hidapi/android/hid.h b/src/hidapi/android/hid.h deleted file mode 100644 index 5e6253bf8c239..0000000000000 --- a/src/hidapi/android/hid.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - Simple DirectMedia Layer - Copyright (C) 2022 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. -*/ - -// Purpose: Exporting table containing HIDDeviceManager native methods - -#ifndef SDL_android_hid_h_ -#define SDL_android_hid_h_ - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -extern JNINativeMethod HIDDeviceManager_tab[8]; - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/src/hidapi/android/jni/Android.mk b/src/hidapi/android/jni/Android.mk deleted file mode 100644 index 4462e88bf3ddd..0000000000000 --- a/src/hidapi/android/jni/Android.mk +++ /dev/null @@ -1,16 +0,0 @@ -LOCAL_PATH:= $(call my-dir) - -HIDAPI_ROOT_REL:= ../.. -HIDAPI_ROOT_ABS:= $(LOCAL_PATH)/../.. - -include $(CLEAR_VARS) - -LOCAL_CPPFLAGS += -std=c++11 - -LOCAL_SRC_FILES := \ - $(HIDAPI_ROOT_REL)/android/hid.cpp - -LOCAL_MODULE := libhidapi -LOCAL_LDLIBS := -llog - -include $(BUILD_SHARED_LIBRARY) diff --git a/src/hidapi/android/jni/Application.mk b/src/hidapi/android/jni/Application.mk deleted file mode 100644 index 4fc6ba50642f9..0000000000000 --- a/src/hidapi/android/jni/Application.mk +++ /dev/null @@ -1,2 +0,0 @@ -APP_STL := gnustl_static -APP_ABI := armeabi-v7a diff --git a/src/hidapi/android/project.properties b/src/hidapi/android/project.properties deleted file mode 100644 index 6e18427a424c3..0000000000000 --- a/src/hidapi/android/project.properties +++ /dev/null @@ -1,14 +0,0 @@ -# This file is automatically generated by Android Tools. -# Do not modify this file -- YOUR CHANGES WILL BE ERASED! -# -# This file must be checked in Version Control Systems. -# -# To customize properties used by the Ant build system edit -# "ant.properties", and override values to adapt the script to your -# project structure. -# -# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): -#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt - -# Project target. -target=android-21 diff --git a/src/hidapi/configure.ac b/src/hidapi/configure.ac index c6747f906ac48..cc7ceaca00a94 100644 --- a/src/hidapi/configure.ac +++ b/src/hidapi/configure.ac @@ -1,13 +1,9 @@ AC_PREREQ(2.63) -# Version number. This is currently the only place. -m4_define([HIDAPI_MAJOR], 0) -m4_define([HIDAPI_MINOR], 8) -m4_define([HIDAPI_RELEASE], 0) -m4_define([HIDAPI_RC], -rc1) -m4_define([VERSION_STRING], HIDAPI_MAJOR[.]HIDAPI_MINOR[.]HIDAPI_RELEASE[]HIDAPI_RC) +AC_INIT([hidapi],[m4_normalize(m4_builtin([include], VERSION))],[https://github.com/libusb/hidapi/issues]) -AC_INIT([hidapi],[VERSION_STRING],[alan@signal11.us]) +echo "This build script for HIDAPI is deprecated." +echo "Consider using CMake instead." # Library soname version # Follow the following rules (particularly the ones in the second link): @@ -20,7 +16,6 @@ LTLDFLAGS="-version-info ${lt_current}:${lt_revision}:${lt_age}" AC_CONFIG_MACRO_DIR([m4]) AM_INIT_AUTOMAKE([foreign -Wall -Werror]) -AC_CONFIG_MACRO_DIR([m4]) m4_ifdef([AM_PROG_AR], [AM_PROG_AR]) LT_INIT @@ -63,14 +58,14 @@ case $host in # HIDAPI/hidraw libs PKG_CHECK_MODULES([libudev], [libudev], true, [hidapi_lib_error libudev]) - LIBS_HIDRAW_PR+=" $libudev_LIBS" - CFLAGS_HIDRAW+=" $libudev_CFLAGS" + LIBS_HIDRAW_PR="${LIBS_HIDRAW_PR} $libudev_LIBS" + CFLAGS_HIDRAW="${CFLAGS_HIDRAW} $libudev_CFLAGS" # HIDAPI/libusb libs - AC_CHECK_LIB([rt], [clock_gettime], [LIBS_LIBUSB_PRIVATE+=" -lrt"], [hidapi_lib_error librt]) + AC_CHECK_LIB([rt], [clock_gettime], [LIBS_LIBUSB_PRIVATE="${LIBS_LIBUSB_PRIVATE} -lrt"], [hidapi_lib_error librt]) PKG_CHECK_MODULES([libusb], [libusb-1.0 >= 1.0.9], true, [hidapi_lib_error libusb-1.0]) - LIBS_LIBUSB_PRIVATE+=" $libusb_LIBS" - CFLAGS_LIBUSB+=" $libusb_CFLAGS" + LIBS_LIBUSB_PRIVATE="${LIBS_LIBUSB_PRIVATE} $libusb_LIBS" + CFLAGS_LIBUSB="${CFLAGS_LIBUSB} $libusb_CFLAGS" ;; *-darwin*) AC_MSG_RESULT([ (Mac OS X back-end)]) @@ -79,7 +74,7 @@ case $host in backend="mac" os="darwin" threads="pthreads" - LIBS="${LIBS} -framework IOKit -framework CoreFoundation" + LIBS="${LIBS} -framework IOKit -framework CoreFoundation -framework AppKit" ;; *-freebsd*) AC_MSG_RESULT([ (FreeBSD back-end)]) @@ -92,9 +87,10 @@ case $host in CFLAGS="$CFLAGS -I/usr/local/include" LDFLAGS="$LDFLAGS -L/usr/local/lib" LIBS="${LIBS}" - AC_CHECK_LIB([usb], [libusb_init], [LIBS_LIBUSB_PRIVATE="${LIBS_LIBUSB_PRIVATE} -lusb"], [hidapi_lib_error libusb]) + PKG_CHECK_MODULES([libusb], [libusb-1.0 >= 1.0.9], true, [hidapi_lib_error libusb-1.0]) + LIBS_LIBUSB_PRIVATE="${LIBS_LIBUSB_PRIVATE} $libusb_LIBS" + CFLAGS_LIBUSB="${CFLAGS_LIBUSB} $libusb_CFLAGS" AC_CHECK_LIB([iconv], [iconv_open], [LIBS_LIBUSB_PRIVATE="${LIBS_LIBUSB_PRIVATE} -liconv"], [hidapi_lib_error libiconv]) - echo libs_priv: $LIBS_LIBUSB_PRIVATE ;; *-kfreebsd*) AC_MSG_RESULT([ (kFreeBSD back-end)]) @@ -104,8 +100,22 @@ case $host in os="kfreebsd" threads="pthreads" - AC_CHECK_LIB([usb], [libusb_init], [LIBS_LIBUSB_PRIVATE="${LIBS_LIBUSB_PRIVATE} -lusb"], [hidapi_lib_error libusb]) - echo libs_priv: $LIBS_LIBUSB_PRIVATE + PKG_CHECK_MODULES([libusb], [libusb-1.0 >= 1.0.9], true, [hidapi_lib_error libusb-1.0]) + LIBS_LIBUSB_PRIVATE="${LIBS_LIBUSB_PRIVATE} $libusb_LIBS" + CFLAGS_LIBUSB="${CFLAGS_LIBUSB} $libusb_CFLAGS" + ;; +*-*-haiku) + AC_MSG_RESULT([ (Haiku back-end)]) + AC_DEFINE(OS_HAIKU, 1, [Haiku implementation]) + AC_SUBST(OS_HAIKU) + backend="libusb" + os="haiku" + threads="pthreads" + + PKG_CHECK_MODULES([libusb], [libusb-1.0 >= 1.0.9], true, [hidapi_lib_error libusb-1.0]) + LIBS_LIBUSB_PRIVATE="${LIBS_LIBUSB_PRIVATE} $libusb_LIBS" + CFLAGS_LIBUSB="${CFLAGS_LIBUSB} $libusb_CFLAGS" + AC_CHECK_LIB([iconv], [libiconv_open], [LIBS_LIBUSB_PRIVATE="${LIBS_LIBUSB_PRIVATE} -liconv"], [hidapi_lib_error libiconv]) ;; *-mingw*) AC_MSG_RESULT([ (Windows back-end, using MinGW)]) @@ -113,6 +123,15 @@ case $host in os="windows" threads="windows" win_implementation="mingw" + LDFLAGS="${LDFLAGS} -static-libgcc" + ;; +*-msys*) + AC_MSG_RESULT([ (Windows back-end, using MSYS2)]) + backend="windows" + os="windows" + threads="windows" + win_implementation="mingw" + LDFLAGS="${LDFLAGS} -static-libgcc" ;; *-cygwin*) AC_MSG_RESULT([ (Windows back-end, using Cygwin)]) @@ -136,7 +155,7 @@ if test "x$os" = xwindows; then AC_DEFINE(OS_WINDOWS, 1, [Windows implementations]) AC_SUBST(OS_WINDOWS) LDFLAGS="${LDFLAGS} -no-undefined" - LIBS="${LIBS} -lsetupapi" + LIBS="${LIBS}" fi if test "x$threads" = xpthreads; then @@ -175,15 +194,15 @@ mkdir testgui/TestGUI.app/Contents/MacOS/ if test "x$testgui_enabled" != "xno"; then if test "x$os" = xdarwin; then - # On Mac OS, don't use pkg-config. + # On Mac OS, do not use pkg-config. AC_CHECK_PROG([foxconfig], [fox-config], [fox-config], false) if test "x$foxconfig" = "xfalse"; then hidapi_prog_error fox-config "FOX Toolkit" fi - LIBS_TESTGUI+=`$foxconfig --libs` - LIBS_TESTGUI+=" -framework Cocoa -L/usr/X11R6/lib" - CFLAGS_TESTGUI+=`$foxconfig --cflags` - OBJCFLAGS+=" -x objective-c++" + LIBS_TESTGUI="${LIBS_TESTGUI} `$foxconfig --libs`" + LIBS_TESTGUI="${LIBS_TESTGUI} -framework Cocoa -L/usr/X11R6/lib" + CFLAGS_TESTGUI="${CFLAGS_TESTGUI} `$foxconfig --cflags`" + OBJCFLAGS="${OBJCFLAGS} -x objective-c++" elif test "x$os" = xwindows; then # On Windows, just set the paths for Fox toolkit if test "x$win_implementation" = xmingw; then @@ -213,6 +232,7 @@ AM_CONDITIONAL(OS_LINUX, test "x$os" = xlinux) AM_CONDITIONAL(OS_DARWIN, test "x$os" = xdarwin) AM_CONDITIONAL(OS_FREEBSD, test "x$os" = xfreebsd) AM_CONDITIONAL(OS_KFREEBSD, test "x$os" = xkfreebsd) +AM_CONDITIONAL(OS_HAIKU, test "x$os" = xhaiku) AM_CONDITIONAL(OS_WINDOWS, test "x$os" = xwindows) AC_CONFIG_HEADERS([config.h]) diff --git a/src/hidapi/dist/hidapi.podspec b/src/hidapi/dist/hidapi.podspec new file mode 100644 index 0000000000000..74642ef632e24 --- /dev/null +++ b/src/hidapi/dist/hidapi.podspec @@ -0,0 +1,31 @@ +Pod::Spec.new do |spec| + + spec.name = "hidapi" + spec.version = File.read('../VERSION') + spec.summary = "A Simple library for communicating with USB and Bluetooth HID devices on Linux, Mac and Windows." + + spec.description = <<-DESC + HIDAPI is a multi-platform library which allows an application to interface with USB and Bluetooth HID-Class devices on Windows, Linux, FreeBSD, and macOS. HIDAPI can be either built as a shared library (.so, .dll or .dylib) or can be embedded directly into a target application by adding a single source file (per platform) and a single header. + DESC + + spec.homepage = "https://github.com/libusb/hidapi" + + spec.license = { :type=> "GNU GPLv3 or BSD or HIDAPI original", :file => "LICENSE.txt" } + + spec.authors = { "Alan Ott" => "alan@signal11.us", + "Ludovic Rousseau" => "rousseau@debian.org", + "libusb/hidapi Team" => "https://github.com/libusb/hidapi/blob/master/AUTHORS.txt", + } + + spec.platform = :osx + spec.osx.deployment_target = "10.7" + + spec.source = { :git => "https://github.com/libusb/hidapi.git", :tag => "hidapi-#{spec.version}" } + + spec.source_files = "mac/hid.c", "hidapi/hidapi.h", "mac/hidapi_darwin.h" + + spec.public_header_files = "hidapi/hidapi.h", "mac/hidapi_darwin.h" + + spec.frameworks = "IOKit", "CoreFoundation", "AppKit" + +end diff --git a/src/hidapi/documentation/cmake-gui-drop-down.png b/src/hidapi/documentation/cmake-gui-drop-down.png new file mode 100644 index 0000000000000000000000000000000000000000..548abe8c1ea29d6a28fd94c1f29da043ded5b43c GIT binary patch literal 22316 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 LKetww$gF literal 0 HcmV?d00001 diff --git a/src/hidapi/documentation/cmake-gui-highlights.png b/src/hidapi/documentation/cmake-gui-highlights.png new file mode 100644 index 0000000000000000000000000000000000000000..228838ff7cf8449e4c0fa1f47c12e297d9039eca GIT binary patch literal 77327 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
aH8SwR7+bBRCtK#&Zx~(NZO^`an&0Q3^fq8fCk3 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