@ -0,0 +1,155 @@ | |||
syntax:glob | |||
aclocal.m4 | |||
autom4te* | |||
config.cache | |||
config.log | |||
config.status | |||
libtool | |||
Makefile | |||
Makefile.rules | |||
sdl2-config | |||
sdl2-config.cmake | |||
sdl2.pc | |||
SDL2.spec | |||
build | |||
gen | |||
Build | |||
# for CMake | |||
CMakeFiles/ | |||
CMakeCache.txt | |||
cmake_install.cmake | |||
cmake_uninstall.cmake | |||
SDL2ConfigVersion.cmake | |||
*.a | |||
*.la | |||
*.so | |||
*.so.* | |||
.ninja_* | |||
*.ninja | |||
# for CLion | |||
.idea | |||
cmake-build-* | |||
# for Xcode | |||
*.orig | |||
*.swp | |||
*.tmp | |||
*.rej | |||
*~ | |||
*.o | |||
*.mode1* | |||
*.perspective* | |||
*.pbxuser | |||
(^|/)build($|/) | |||
.DS_Store | |||
xcuserdata | |||
*.xcworkspace | |||
# for Visual C++ | |||
Debug | |||
Release | |||
*.user | |||
*.ncb | |||
*.suo | |||
*.sdf | |||
VisualC/tests/loopwave/sample.wav | |||
VisualC/tests/testautomation/CompareSurfaces0001_Reference.bmp | |||
VisualC/tests/testautomation/CompareSurfaces0001_TestOutput.bmp | |||
VisualC/tests/testgamecontroller/axis.bmp | |||
VisualC/tests/testgamecontroller/button.bmp | |||
VisualC/tests/testgamecontroller/controllermap.bmp | |||
VisualC/tests/testoverlay2/moose.dat | |||
VisualC/tests/testrendertarget/icon.bmp | |||
VisualC/tests/testrendertarget/sample.bmp | |||
VisualC/tests/testscale/icon.bmp | |||
VisualC/tests/testscale/sample.bmp | |||
VisualC/tests/testsprite2/icon.bmp | |||
VisualC/visualtest/icon.bmp | |||
VisualC/visualtest/testquit.actions | |||
VisualC/visualtest/testquit.config | |||
VisualC/visualtest/testquit.exe | |||
VisualC/visualtest/testquit.parameters | |||
VisualC/visualtest/testsprite2.exe | |||
VisualC/visualtest/testsprite2_sample.actions | |||
VisualC/visualtest/testsprite2_sample.config | |||
VisualC/visualtest/testsprite2_sample.parameters | |||
# for Android | |||
android-project/local.properties | |||
test/aclocal.m4 | |||
test/autom4te* | |||
test/config.cache | |||
test/config.log | |||
test/config.status | |||
test/Makefile | |||
test/SDL2.dll | |||
test/checkkeys | |||
test/controllermap | |||
test/loopwave | |||
test/loopwavequeue | |||
test/testatomic | |||
test/testaudiocapture | |||
test/testaudiohotplug | |||
test/testaudioinfo | |||
test/testautomation | |||
test/testbounds | |||
test/testcustomcursor | |||
test/testdisplayinfo | |||
test/testdraw2 | |||
test/testdrawchessboard | |||
test/testdropfile | |||
test/testerror | |||
test/testfile | |||
test/testfilesystem | |||
test/testgamecontroller | |||
test/testgesture | |||
test/testgl2 | |||
test/testgles | |||
test/testgles2 | |||
test/testhaptic | |||
test/testhittesting | |||
test/testhotplug | |||
test/testiconv | |||
test/testime | |||
test/testintersections | |||
test/testjoystick | |||
test/testkeys | |||
test/testloadso | |||
test/testlock | |||
test/testmessage | |||
test/testmultiaudio | |||
test/testnative | |||
test/testoverlay2 | |||
test/testplatform | |||
test/testpower | |||
test/testqsort | |||
test/testrelative | |||
test/testrendercopyex | |||
test/testrendertarget | |||
test/testresample | |||
test/testrumble | |||
test/testscale | |||
test/testsem | |||
test/testsensor | |||
test/testshader | |||
test/testshape | |||
test/testsprite2 | |||
test/testspriteminimal | |||
test/teststreaming | |||
test/testthread | |||
test/testtimer | |||
test/testver | |||
test/testviewport | |||
test/testvulkan | |||
test/testwm2 | |||
test/testyuv | |||
test/torturethread | |||
test/*.exe | |||
test/*,e1f | |||
test/*,ff8 | |||
test/*.dSYM | |||
buildbot | |||
test/buildbot |
@ -0,0 +1,40 @@ | |||
0afe0e38e02cf2048e93582f01c52fbb91d3c7bb release-1.2.7 | |||
230b156829ed13b31134d96f689c917981f57b84 release-1.2.5 | |||
27cab50ec9c746e886ce0f3fdaa0b0cdc55a594f release-1.2.11 | |||
2fe3fbd2bff50165b3cad33bf40d70b3bb3c9fd0 release-1.2.3 | |||
3c052d3bcc76c899dfd4846be76243a78e8c7180 release-1.2.4 | |||
3c5eed71a3320962551af3b3dfbee0c99fcf0086 release-1.2.10 | |||
4867f7f7dd3426d1dbbeef48b3f3b3aa19590cc4 release-1.2.12 | |||
6e28dae59e3baf4447c83e833a8d2ac912536f5b release-1.2.1 | |||
7c2589fb8d4df54c6faabd3faebd0c0e73f67879 release-1.2.13 | |||
86de11faf082881ad9b73a1a1d78733ca07f8db8 release-1.2.6 | |||
bb051fa871aa0b53ea57df56a446cec3bb85924c release-1.2.2 | |||
cfcb2e1c36ebe9809577adf768b0ec53e8768af9 release-1.2.8 | |||
e044e7c70a50a2f54d14ee20d0933e904e5853b6 release-1.2.9 | |||
f14cf9d71233934811774f941d0de121d5f96ccf release-1.2.14 | |||
39c22a953456f6c9e2c8993c8ff973824104102a pre-touch-removal | |||
ccf5fbfa2afabab429ad911308f362201a94d810 macosx_10_4_supported | |||
d6a8fa507a45d9de7258e51585eab3e45c415149 release-2.0.0 | |||
a8bd63b33636715f2cf6e7d36ab7201acbd478fe release-2.0.1 | |||
a8bd63b33636715f2cf6e7d36ab7201acbd478fe release-2.0.1 | |||
715a01415ac9305b9f8ec72b99fcf8cc9dd64dde release-2.0.1 | |||
715a01415ac9305b9f8ec72b99fcf8cc9dd64dde release-2.0.1 | |||
9ec71e56071cc80eda6691a3f8719ed5395dfcfb release-2.0.1 | |||
9ec71e56071cc80eda6691a3f8719ed5395dfcfb release-2.0.1 | |||
0000000000000000000000000000000000000000 release-2.0.1 | |||
0000000000000000000000000000000000000000 release-2.0.1 | |||
b9663c77f5c95ebf05f3c18e80619caae8ae1460 release-2.0.1 | |||
be2102f000d0d2d9bab75e9703a1d503d0f6bb33 release-2.0.2 | |||
f285b9487756ff681f76c85644222c03a7bfa1c7 release-2.0.3 | |||
f285b9487756ff681f76c85644222c03a7bfa1c7 release-2.0.3 | |||
704a0bfecf754e4e1383f83c7d5118b00cae26ea release-2.0.3 | |||
e12c387305129c847b3928a123300b113782fe3f release-2.0.4 | |||
007dfe83abf81b1ff5df40186f65e8e64987b825 release-2.0.5 | |||
8df7a59b55283aa09889522369a2b32674c048de release-2.0.6 | |||
2088cd828335797d73d151e3288d899f77204862 release-2.0.7 | |||
f1084c419f33610cf274e309a8b2798d2ae665c7 release-2.0.8 | |||
8feb5da6f2fb75703bde2c06813375af984a57f0 release-2.0.9 | |||
bc90ce38f1e27ace54b83bebf987993002504f7f release-2.0.10 | |||
78d0bb6f3b8f9b8f2a76cb357a407bc7ace57234 release-2.0.12 | |||
78d0bb6f3b8f9b8f2a76cb357a407bc7ace57234 release-2.0.12 | |||
355a4f94a782747a990b2fedaebc7bebd280e153 release-2.0.12 |
@ -1,119 +0,0 @@ | |||
Summary: Simple DirectMedia Layer | |||
Name: SDL2 | |||
Version: 2.0.10 | |||
Release: 2 | |||
Source: http://www.libsdl.org/release/%{name}-%{version}.tar.gz | |||
URL: http://www.libsdl.org/ | |||
License: zlib | |||
Group: System Environment/Libraries | |||
BuildRoot: %{_tmppath}/%{name}-%{version}-buildroot | |||
Prefix: %{_prefix} | |||
%ifos linux | |||
Provides: libSDL2-2.0.so.0 | |||
%endif | |||
%define __defattr %defattr(-,root,root) | |||
%define __soext so | |||
%description | |||
This is the Simple DirectMedia Layer, a generic API that provides low | |||
level access to audio, keyboard, mouse, and display framebuffer across | |||
multiple platforms. | |||
%package devel | |||
Summary: Libraries, includes and more to develop SDL applications. | |||
Group: Development/Libraries | |||
Requires: %{name} = %{version} | |||
%description devel | |||
This is the Simple DirectMedia Layer, a generic API that provides low | |||
level access to audio, keyboard, mouse, and display framebuffer across | |||
multiple platforms. | |||
This is the libraries, include files and other resources you can use | |||
to develop SDL applications. | |||
%prep | |||
%setup -q | |||
%build | |||
%ifos linux | |||
CFLAGS="$RPM_OPT_FLAGS" ./configure --prefix=%{prefix} --disable-video-directfb | |||
%else | |||
%configure | |||
%endif | |||
make | |||
%install | |||
rm -rf $RPM_BUILD_ROOT | |||
%ifos linux | |||
make install prefix=$RPM_BUILD_ROOT%{prefix} \ | |||
bindir=$RPM_BUILD_ROOT%{_bindir} \ | |||
libdir=$RPM_BUILD_ROOT%{_libdir} \ | |||
includedir=$RPM_BUILD_ROOT%{_includedir} \ | |||
datadir=$RPM_BUILD_ROOT%{_datadir} \ | |||
mandir=$RPM_BUILD_ROOT%{_mandir} | |||
%else | |||
%makeinstall | |||
%endif | |||
%clean | |||
rm -rf $RPM_BUILD_ROOT | |||
%files | |||
%{__defattr} | |||
%doc README*.txt COPYING.txt CREDITS.txt BUGS.txt | |||
%{_libdir}/lib*.%{__soext}.* | |||
%files devel | |||
%{__defattr} | |||
%doc docs/README*.md | |||
%{_bindir}/*-config | |||
%{_libdir}/lib*.a | |||
%{_libdir}/lib*.la | |||
%{_libdir}/lib*.%{__soext} | |||
%{_includedir}/*/*.h | |||
%{_libdir}/cmake/* | |||
%{_libdir}/pkgconfig/SDL2/* | |||
%{_datadir}/aclocal/* | |||
%changelog | |||
* Thu Jun 04 2015 Ryan C. Gordon <icculus@icculus.org> | |||
- Fixed README paths. | |||
* Sun Dec 07 2014 Simone Contini <s.contini@oltrelinux.com> | |||
- Fixed changelog date issue and docs filenames | |||
* Sun Jan 22 2012 Sam Lantinga <slouken@libsdl.org> | |||
- Updated for SDL 2.0 | |||
* Tue May 16 2006 Sam Lantinga <slouken@libsdl.org> | |||
- Removed support for Darwin, due to build problems on ps2linux | |||
* Sat Jan 03 2004 Anders Bjorklund <afb@algonet.se> | |||
- Added support for Darwin, updated spec file | |||
* Wed Jan 19 2000 Sam Lantinga <slouken@libsdl.org> | |||
- Re-integrated spec file into SDL distribution | |||
- 'name' and 'version' come from configure | |||
- Some of the documentation is devel specific | |||
- Removed SMP support from %build - it doesn't work with libtool anyway | |||
* Tue Jan 18 2000 Hakan Tandogan <hakan@iconsult.com> | |||
- Hacked Mandrake sdl spec to build 1.1 | |||
* Sun Dec 19 1999 John Buswell <johnb@mandrakesoft.com> | |||
- Build Release | |||
* Sat Dec 18 1999 John Buswell <johnb@mandrakesoft.com> | |||
- Add symlink for libSDL-1.0.so.0 required by sdlbomber | |||
- Added docs | |||
* Thu Dec 09 1999 Lenny Cartier <lenny@mandrakesoft.com> | |||
- v 1.0.0 | |||
* Mon Nov 1 1999 Chmouel Boudjnah <chmouel@mandrakesoft.com> | |||
- First spec file for Mandrake distribution. | |||
# end of file |
@ -0,0 +1,199 @@ | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | |||
<ItemGroup Label="ProjectConfigurations"> | |||
<ProjectConfiguration Include="Debug|Win32"> | |||
<Configuration>Debug</Configuration> | |||
<Platform>Win32</Platform> | |||
</ProjectConfiguration> | |||
<ProjectConfiguration Include="Debug|x64"> | |||
<Configuration>Debug</Configuration> | |||
<Platform>x64</Platform> | |||
</ProjectConfiguration> | |||
<ProjectConfiguration Include="Release|Win32"> | |||
<Configuration>Release</Configuration> | |||
<Platform>Win32</Platform> | |||
</ProjectConfiguration> | |||
<ProjectConfiguration Include="Release|x64"> | |||
<Configuration>Release</Configuration> | |||
<Platform>x64</Platform> | |||
</ProjectConfiguration> | |||
</ItemGroup> | |||
<PropertyGroup Label="Globals"> | |||
<ProjectGuid>{C4E04D18-EF76-4B42-B4C2-16A1BACDC0A4}</ProjectGuid> | |||
<RootNamespace>testsensor</RootNamespace> | |||
</PropertyGroup> | |||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> | |||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> | |||
<ConfigurationType>Application</ConfigurationType> | |||
</PropertyGroup> | |||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> | |||
<ConfigurationType>Application</ConfigurationType> | |||
</PropertyGroup> | |||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration"> | |||
<ConfigurationType>Application</ConfigurationType> | |||
</PropertyGroup> | |||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration"> | |||
<ConfigurationType>Application</ConfigurationType> | |||
</PropertyGroup> | |||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> | |||
<ImportGroup Label="ExtensionSettings"> | |||
</ImportGroup> | |||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets"> | |||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> | |||
<Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC70.props" /> | |||
</ImportGroup> | |||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets"> | |||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> | |||
<Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC70.props" /> | |||
</ImportGroup> | |||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets"> | |||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> | |||
<Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC70.props" /> | |||
</ImportGroup> | |||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets"> | |||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> | |||
<Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC70.props" /> | |||
</ImportGroup> | |||
<PropertyGroup Label="UserMacros" /> | |||
<PropertyGroup> | |||
<_ProjectFileVersion>10.0.40219.1</_ProjectFileVersion> | |||
<OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(SolutionDir)$(Platform)\$(Configuration)\</OutDir> | |||
<IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(Platform)\$(Configuration)\</IntDir> | |||
<OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(SolutionDir)$(Platform)\$(Configuration)\</OutDir> | |||
<IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(Platform)\$(Configuration)\</IntDir> | |||
<OutDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(SolutionDir)$(Platform)\$(Configuration)\</OutDir> | |||
<IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(Platform)\$(Configuration)\</IntDir> | |||
<OutDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(SolutionDir)$(Platform)\$(Configuration)\</OutDir> | |||
<IntDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(Platform)\$(Configuration)\</IntDir> | |||
<CodeAnalysisRuleSet Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">AllRules.ruleset</CodeAnalysisRuleSet> | |||
<CodeAnalysisRules Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" /> | |||
<CodeAnalysisRuleAssemblies Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" /> | |||
<CodeAnalysisRuleSet Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">AllRules.ruleset</CodeAnalysisRuleSet> | |||
<CodeAnalysisRules Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" /> | |||
<CodeAnalysisRuleAssemblies Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" /> | |||
<CodeAnalysisRuleSet Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">AllRules.ruleset</CodeAnalysisRuleSet> | |||
<CodeAnalysisRules Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" /> | |||
<CodeAnalysisRuleAssemblies Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" /> | |||
<CodeAnalysisRuleSet Condition="'$(Configuration)|$(Platform)'=='Release|x64'">AllRules.ruleset</CodeAnalysisRuleSet> | |||
<CodeAnalysisRules Condition="'$(Configuration)|$(Platform)'=='Release|x64'" /> | |||
<CodeAnalysisRuleAssemblies Condition="'$(Configuration)|$(Platform)'=='Release|x64'" /> | |||
</PropertyGroup> | |||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> | |||
<Midl> | |||
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> | |||
<MkTypLibCompatible>true</MkTypLibCompatible> | |||
<SuppressStartupBanner>true</SuppressStartupBanner> | |||
<TargetEnvironment>Win32</TargetEnvironment> | |||
<TypeLibraryName>.\Debug/testsensor.tlb</TypeLibraryName> | |||
</Midl> | |||
<ClCompile> | |||
<Optimization>Disabled</Optimization> | |||
<AdditionalIncludeDirectories>$(SolutionDir)/../include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> | |||
<AdditionalUsingDirectories>%(AdditionalUsingDirectories)</AdditionalUsingDirectories> | |||
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions> | |||
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> | |||
<WarningLevel>Level3</WarningLevel> | |||
<DebugInformationFormat>OldStyle</DebugInformationFormat> | |||
</ClCompile> | |||
<ResourceCompile> | |||
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> | |||
<Culture>0x0409</Culture> | |||
</ResourceCompile> | |||
<Link> | |||
<GenerateDebugInformation>true</GenerateDebugInformation> | |||
<SubSystem>Windows</SubSystem> | |||
</Link> | |||
</ItemDefinitionGroup> | |||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> | |||
<Midl> | |||
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> | |||
<MkTypLibCompatible>true</MkTypLibCompatible> | |||
<SuppressStartupBanner>true</SuppressStartupBanner> | |||
<TargetEnvironment>X64</TargetEnvironment> | |||
<TypeLibraryName>.\Debug/testsensor.tlb</TypeLibraryName> | |||
</Midl> | |||
<ClCompile> | |||
<Optimization>Disabled</Optimization> | |||
<AdditionalIncludeDirectories>$(SolutionDir)/../include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> | |||
<AdditionalUsingDirectories>%(AdditionalUsingDirectories)</AdditionalUsingDirectories> | |||
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions> | |||
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> | |||
<WarningLevel>Level3</WarningLevel> | |||
<DebugInformationFormat>OldStyle</DebugInformationFormat> | |||
</ClCompile> | |||
<ResourceCompile> | |||
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> | |||
<Culture>0x0409</Culture> | |||
</ResourceCompile> | |||
<Link> | |||
<GenerateDebugInformation>true</GenerateDebugInformation> | |||
<SubSystem>Windows</SubSystem> | |||
</Link> | |||
</ItemDefinitionGroup> | |||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> | |||
<Midl> | |||
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> | |||
<MkTypLibCompatible>true</MkTypLibCompatible> | |||
<SuppressStartupBanner>true</SuppressStartupBanner> | |||
<TargetEnvironment>Win32</TargetEnvironment> | |||
<TypeLibraryName>.\Release/testsensor.tlb</TypeLibraryName> | |||
</Midl> | |||
<ClCompile> | |||
<AdditionalIncludeDirectories>$(SolutionDir)/../include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> | |||
<AdditionalUsingDirectories>%(AdditionalUsingDirectories)</AdditionalUsingDirectories> | |||
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions> | |||
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> | |||
<WarningLevel>Level3</WarningLevel> | |||
</ClCompile> | |||
<ResourceCompile> | |||
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> | |||
<Culture>0x0409</Culture> | |||
</ResourceCompile> | |||
<Link> | |||
<SubSystem>Windows</SubSystem> | |||
</Link> | |||
</ItemDefinitionGroup> | |||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> | |||
<Midl> | |||
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> | |||
<MkTypLibCompatible>true</MkTypLibCompatible> | |||
<SuppressStartupBanner>true</SuppressStartupBanner> | |||
<TargetEnvironment>X64</TargetEnvironment> | |||
<TypeLibraryName>.\Release/testsensor.tlb</TypeLibraryName> | |||
</Midl> | |||
<ClCompile> | |||
<AdditionalIncludeDirectories>$(SolutionDir)/../include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> | |||
<AdditionalUsingDirectories>%(AdditionalUsingDirectories)</AdditionalUsingDirectories> | |||
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions> | |||
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> | |||
<WarningLevel>Level3</WarningLevel> | |||
</ClCompile> | |||
<ResourceCompile> | |||
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> | |||
<Culture>0x0409</Culture> | |||
</ResourceCompile> | |||
<Link> | |||
<SubSystem>Windows</SubSystem> | |||
</Link> | |||
</ItemDefinitionGroup> | |||
<ItemGroup> | |||
<ProjectReference Include="..\..\SDL\SDL.vcxproj"> | |||
<Project>{81ce8daf-ebb2-4761-8e45-b71abcca8c68}</Project> | |||
<Private>false</Private> | |||
<CopyLocalSatelliteAssemblies>false</CopyLocalSatelliteAssemblies> | |||
<ReferenceOutputAssembly>true</ReferenceOutputAssembly> | |||
</ProjectReference> | |||
<ProjectReference Include="..\..\SDLmain\SDLmain.vcxproj"> | |||
<Project>{da956fd3-e142-46f2-9dd5-c78bebb56b7a}</Project> | |||
<Private>false</Private> | |||
<CopyLocalSatelliteAssemblies>false</CopyLocalSatelliteAssemblies> | |||
<ReferenceOutputAssembly>true</ReferenceOutputAssembly> | |||
</ProjectReference> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<ClCompile Include="..\..\..\test\testsensor.c" /> | |||
</ItemGroup> | |||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> | |||
<ImportGroup Label="ExtensionTargets"> | |||
</ImportGroup> | |||
</Project> |
@ -0,0 +1,22 @@ | |||
<?xml version="1.0" encoding="UTF-8"?> | |||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | |||
<plist version="1.0"> | |||
<dict> | |||
<key>CFBundleDevelopmentRegion</key> | |||
<string>$(DEVELOPMENT_LANGUAGE)</string> | |||
<key>CFBundleExecutable</key> | |||
<string>$(EXECUTABLE_NAME)</string> | |||
<key>CFBundleIdentifier</key> | |||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> | |||
<key>CFBundleInfoDictionaryVersion</key> | |||
<string>6.0</string> | |||
<key>CFBundleName</key> | |||
<string>$(PRODUCT_NAME)</string> | |||
<key>CFBundlePackageType</key> | |||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string> | |||
<key>CFBundleShortVersionString</key> | |||
<string>$(CURRENT_PROJECT_VERSION)</string> | |||
<key>CFBundleVersion</key> | |||
<string>$(CURRENT_PROJECT_VERSION)</string> | |||
</dict> | |||
</plist> |
@ -0,0 +1,90 @@ | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<!-- Replace com.test.game with the identifier of your game below, e.g. | |||
com.gamemaker.game | |||
--> | |||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" | |||
package="org.libsdl.app" | |||
android:versionCode="1" | |||
android:versionName="1.0" | |||
android:installLocation="auto"> | |||
<!-- OpenGL ES 2.0 --> | |||
<uses-feature android:glEsVersion="0x00020000" /> | |||
<!-- Touchscreen support --> | |||
<uses-feature | |||
android:name="android.hardware.touchscreen" | |||
android:required="false" /> | |||
<!-- Game controller support --> | |||
<uses-feature | |||
android:name="android.hardware.bluetooth" | |||
android:required="false" /> | |||
<uses-feature | |||
android:name="android.hardware.gamepad" | |||
android:required="false" /> | |||
<uses-feature | |||
android:name="android.hardware.usb.host" | |||
android:required="false" /> | |||
<!-- External mouse input events --> | |||
<uses-feature | |||
android:name="android.hardware.type.pc" | |||
android:required="false" /> | |||
<!-- Audio recording support --> | |||
<!-- if you want to capture audio, uncomment this. --> | |||
<!-- <uses-feature | |||
android:name="android.hardware.microphone" | |||
android:required="false" /> --> | |||
<!-- Allow writing to external storage --> | |||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> | |||
<!-- Allow access to Bluetooth devices --> | |||
<uses-permission android:name="android.permission.BLUETOOTH" /> | |||
<!-- Allow access to the vibrator --> | |||
<uses-permission android:name="android.permission.VIBRATE" /> | |||
<!-- if you want to capture audio, uncomment this. --> | |||
<!-- <uses-permission android:name="android.permission.RECORD_AUDIO" /> --> | |||
<!-- Create a Java class extending SDLActivity and place it in a | |||
directory under app/src/main/java matching the package, e.g. app/src/main/java/com/gamemaker/game/MyGame.java | |||
then replace "SDLActivity" with the name of your class (e.g. "MyGame") | |||
in the XML below. | |||
An example Java class can be found in README-android.md | |||
--> | |||
<application android:label="@string/app_name" | |||
android:icon="@mipmap/ic_launcher" | |||
android:allowBackup="true" | |||
android:theme="@android:style/Theme.NoTitleBar.Fullscreen" | |||
android:hardwareAccelerated="true" > | |||
<!-- Example of setting SDL hints from AndroidManifest.xml: | |||
<meta-data android:name="SDL_ENV.SDL_ACCELEROMETER_AS_JOYSTICK" android:value="0"/> | |||
--> | |||
<activity android:name="SDLActivity" | |||
android:label="@string/app_name" | |||
android:alwaysRetainTaskState="true" | |||
android:launchMode="singleInstance" | |||
android:configChanges="layoutDirection|locale|orientation|uiMode|screenLayout|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation" | |||
> | |||
<intent-filter> | |||
<action android:name="android.intent.action.MAIN" /> | |||
<category android:name="android.intent.category.LAUNCHER" /> | |||
</intent-filter> | |||
<!-- Drop file event --> | |||
<!-- | |||
<intent-filter> | |||
<action android:name="android.intent.action.VIEW" /> | |||
<category android:name="android.intent.category.DEFAULT" /> | |||
<data android:mimeType="*/*" /> | |||
</intent-filter> | |||
--> | |||
</activity> | |||
</application> | |||
</manifest> |
@ -0,0 +1,17 @@ | |||
# This file is used to override default values used by the Ant build system. | |||
# | |||
# This file must be checked into Version Control Systems, as it is | |||
# integral to the build system of your project. | |||
# This file is only used by the Ant script. | |||
# You can use this to override default values such as | |||
# 'source.dir' for the location of your java source folder and | |||
# 'out.dir' for the location of your output folder. | |||
# You can also use it define how the release builds are signed by declaring | |||
# the following properties: | |||
# 'key.store' for the location of your keystore and | |||
# 'key.alias' for the name of the key to use. | |||
# The password will be asked during the build when you use the 'release' target. | |||
@ -0,0 +1,17 @@ | |||
# This file is used to override default values used by the Ant build system. | |||
# | |||
# This file must be checked in Version Control Systems, as it is | |||
# integral to the build system of your project. | |||
# This file is only used by the Ant script. | |||
# You can use this to override default values such as | |||
# 'source.dir' for the location of your java source folder and | |||
# 'out.dir' for the location of your output folder. | |||
# You can also use it define how the release builds are signed by declaring | |||
# the following properties: | |||
# 'key.store' for the location of your keystore and | |||
# 'key.alias' for the name of the key to use. | |||
# The password will be asked during the build when you use the 'release' target. | |||
@ -0,0 +1,93 @@ | |||
<?xml version="1.0" encoding="UTF-8"?> | |||
<!-- This should be changed to the name of your project --> | |||
<project name="SDLActivity" default="help"> | |||
<!-- The local.properties file is created and updated by the 'android' tool. | |||
It contains the path to the SDK. It should *NOT* be checked into | |||
Version Control Systems. --> | |||
<property file="local.properties" /> | |||
<!-- The ant.properties file can be created by you. It is only edited by the | |||
'android' tool to add properties to it. | |||
This is the place to change some Ant specific build properties. | |||
Here are some properties you may want to change/update: | |||
source.dir | |||
The name of the source directory. Default is 'src'. | |||
out.dir | |||
The name of the output directory. Default is 'bin'. | |||
For other overridable properties, look at the beginning of the rules | |||
files in the SDK, at tools/ant/build.xml | |||
Properties related to the SDK location or the project target should | |||
be updated using the 'android' tool with the 'update' action. | |||
This file is an integral part of the build system for your | |||
application and should be checked into Version Control Systems. | |||
--> | |||
<property file="ant.properties" /> | |||
<!-- if sdk.dir was not set from one of the property file, then | |||
get it from the ANDROID_HOME env var. | |||
This must be done before we load project.properties since | |||
the proguard config can use sdk.dir --> | |||
<property environment="env" /> | |||
<condition property="sdk.dir" value="${env.ANDROID_HOME}"> | |||
<isset property="env.ANDROID_HOME" /> | |||
</condition> | |||
<!-- The project.properties file is created and updated by the 'android' | |||
tool, as well as ADT. | |||
This contains project specific properties such as project target, and library | |||
dependencies. Lower level build properties are stored in ant.properties | |||
(or in .classpath for Eclipse projects). | |||
This file is an integral part of the build system for your | |||
application and should be checked into Version Control Systems. --> | |||
<loadproperties srcFile="project.properties" /> | |||
<!-- quick check on sdk.dir --> | |||
<fail | |||
message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable." | |||
unless="sdk.dir" | |||
/> | |||
<!-- | |||
Import per project custom build rules if present at the root of the project. | |||
This is the place to put custom intermediary targets such as: | |||
-pre-build | |||
-pre-compile | |||
-post-compile (This is typically used for code obfuscation. | |||
Compiled code location: ${out.classes.absolute.dir} | |||
If this is not done in place, override ${out.dex.input.absolute.dir}) | |||
-post-package | |||
-post-build | |||
-pre-clean | |||
--> | |||
<import file="custom_rules.xml" optional="true" /> | |||
<!-- Import the actual build file. | |||
To customize existing targets, there are two options: | |||
- Customize only one target: | |||
- copy/paste the target into this file, *before* the | |||
<import> task. | |||
- customize it to your needs. | |||
- Customize the whole content of build.xml | |||
- copy/paste the content of the rules files (minus the top node) | |||
into this file, replacing the <import> task. | |||
- customize to your needs. | |||
*********************** | |||
****** IMPORTANT ****** | |||
*********************** | |||
In all cases you must update the value of version-tag below to read 'custom' instead of an integer, | |||
in order to avoid having your file be overridden by tools such as "android update project" | |||
--> | |||
<!-- version-tag: 1 --> | |||
<import file="${sdk.dir}/tools/ant/build.xml" /> | |||
</project> |
@ -0,0 +1,11 @@ | |||
# 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 use, | |||
# "build.properties", and override values to adapt the script to your | |||
# project structure. | |||
# Project target. | |||
target=android-16 |
@ -0,0 +1 @@ | |||
include $(call all-subdir-makefiles) |
@ -0,0 +1,10 @@ | |||
# Uncomment this if you're using STL in your project | |||
# See CPLUSPLUS-SUPPORT.html in the NDK documentation for more information | |||
# APP_STL := stlport_static | |||
APP_ABI := armeabi armeabi-v7a x86 | |||
# Min SDK level | |||
APP_PLATFORM=android-10 | |||
@ -0,0 +1,18 @@ | |||
LOCAL_PATH := $(call my-dir) | |||
include $(CLEAR_VARS) | |||
LOCAL_MODULE := main | |||
SDL_PATH := ../SDL | |||
LOCAL_C_INCLUDES := $(LOCAL_PATH)/$(SDL_PATH)/include | |||
# Add your application source files here... | |||
LOCAL_SRC_FILES := YourSourceHere.c | |||
LOCAL_SHARED_LIBRARIES := SDL2 | |||
LOCAL_LDLIBS := -lGLESv1_CM -lGLESv2 -llog | |||
include $(BUILD_SHARED_LIBRARY) |
@ -0,0 +1,12 @@ | |||
LOCAL_PATH := $(call my-dir) | |||
include $(CLEAR_VARS) | |||
LOCAL_MODULE := main | |||
LOCAL_SRC_FILES := YourSourceHere.c | |||
LOCAL_STATIC_LIBRARIES := SDL2_static | |||
include $(BUILD_SHARED_LIBRARY) | |||
$(call import-module,SDL)LOCAL_PATH := $(call my-dir) |
@ -0,0 +1,20 @@ | |||
# To enable ProGuard in your project, edit project.properties | |||
# to define the proguard.config property as described in that file. | |||
# | |||
# Add project specific ProGuard rules here. | |||
# By default, the flags in this file are appended to flags specified | |||
# in ${sdk.dir}/tools/proguard/proguard-android.txt | |||
# You can edit the include path and order by changing the ProGuard | |||
# include property in project.properties. | |||
# | |||
# For more details, see | |||
# http://developer.android.com/guide/developing/tools/proguard.html | |||
# Add any project specific keep options here: | |||
# If your project uses WebView with JS, uncomment the following | |||
# and specify the fully qualified class name to the JavaScript interface | |||
# class: | |||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview { | |||
# public *; | |||
#} |
@ -0,0 +1,14 @@ | |||
# 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-16 |
@ -0,0 +1,13 @@ | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | |||
android:orientation="vertical" | |||
android:layout_width="fill_parent" | |||
android:layout_height="fill_parent" | |||
> | |||
<TextView | |||
android:layout_width="fill_parent" | |||
android:layout_height="wrap_content" | |||
android:text="Hello World, SDLActivity" | |||
/> | |||
</LinearLayout> | |||
@ -0,0 +1,4 @@ | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<resources> | |||
<string name="app_name">SDL App</string> | |||
</resources> |
@ -0,0 +1,22 @@ | |||
package org.libsdl.app; | |||
import android.hardware.usb.UsbDevice; | |||
interface HIDDevice | |||
{ | |||
public int getId(); | |||
public int getVendorId(); | |||
public int getProductId(); | |||
public String getSerialNumber(); | |||
public int getVersion(); | |||
public String getManufacturerName(); | |||
public String getProductName(); | |||
public UsbDevice getDevice(); | |||
public boolean open(); | |||
public int sendFeatureReport(byte[] report); | |||
public int sendOutputReport(byte[] report); | |||
public boolean getFeatureReport(byte[] report); | |||
public void setFrozen(boolean frozen); | |||
public void close(); | |||
public void shutdown(); | |||
} |
@ -0,0 +1,650 @@ | |||
package org.libsdl.app; | |||
import android.content.Context; | |||
import android.bluetooth.BluetoothDevice; | |||
import android.bluetooth.BluetoothGatt; | |||
import android.bluetooth.BluetoothGattCallback; | |||
import android.bluetooth.BluetoothGattCharacteristic; | |||
import android.bluetooth.BluetoothGattDescriptor; | |||
import android.bluetooth.BluetoothManager; | |||
import android.bluetooth.BluetoothProfile; | |||
import android.bluetooth.BluetoothGattService; | |||
import android.hardware.usb.UsbDevice; | |||
import android.os.Handler; | |||
import android.os.Looper; | |||
import android.util.Log; | |||
import android.os.*; | |||
//import com.android.internal.util.HexDump; | |||
import java.lang.Runnable; | |||
import java.util.Arrays; | |||
import java.util.LinkedList; | |||
import java.util.UUID; | |||
class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDevice { | |||
private static final String TAG = "hidapi"; | |||
private HIDDeviceManager mManager; | |||
private BluetoothDevice mDevice; | |||
private int mDeviceId; | |||
private BluetoothGatt mGatt; | |||
private boolean mIsRegistered = false; | |||
private boolean mIsConnected = false; | |||
private boolean mIsChromebook = false; | |||
private boolean mIsReconnecting = false; | |||
private boolean mFrozen = false; | |||
private LinkedList<GattOperation> mOperations; | |||
GattOperation mCurrentOperation = null; | |||
private Handler mHandler; | |||
private static final int TRANSPORT_AUTO = 0; | |||
private static final int TRANSPORT_BREDR = 1; | |||
private static final int TRANSPORT_LE = 2; | |||
private static final int CHROMEBOOK_CONNECTION_CHECK_INTERVAL = 10000; | |||
static public final UUID steamControllerService = UUID.fromString("100F6C32-1735-4313-B402-38567131E5F3"); | |||
static public final UUID inputCharacteristic = UUID.fromString("100F6C33-1735-4313-B402-38567131E5F3"); | |||
static public final UUID reportCharacteristic = UUID.fromString("100F6C34-1735-4313-B402-38567131E5F3"); | |||
static private final byte[] enterValveMode = new byte[] { (byte)0xC0, (byte)0x87, 0x03, 0x08, 0x07, 0x00 }; | |||
static class GattOperation { | |||
private enum Operation { | |||
CHR_READ, | |||
CHR_WRITE, | |||
ENABLE_NOTIFICATION | |||
} | |||
Operation mOp; | |||
UUID mUuid; | |||
byte[] mValue; | |||
BluetoothGatt mGatt; | |||
boolean mResult = true; | |||
private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid) { | |||
mGatt = gatt; | |||
mOp = operation; | |||
mUuid = uuid; | |||
} | |||
private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid, byte[] value) { | |||
mGatt = gatt; | |||
mOp = operation; | |||
mUuid = uuid; | |||
mValue = value; | |||
} | |||
public void run() { | |||
// This is executed in main thread | |||
BluetoothGattCharacteristic chr; | |||
switch (mOp) { | |||
case CHR_READ: | |||
chr = getCharacteristic(mUuid); | |||
//Log.v(TAG, "Reading characteristic " + chr.getUuid()); | |||
if (!mGatt.readCharacteristic(chr)) { | |||
Log.e(TAG, "Unable to read characteristic " + mUuid.toString()); | |||
mResult = false; | |||
break; | |||
} | |||
mResult = true; | |||
break; | |||
case CHR_WRITE: | |||
chr = getCharacteristic(mUuid); | |||
//Log.v(TAG, "Writing characteristic " + chr.getUuid() + " value=" + HexDump.toHexString(value)); | |||
chr.setValue(mValue); | |||
if (!mGatt.writeCharacteristic(chr)) { | |||
Log.e(TAG, "Unable to write characteristic " + mUuid.toString()); | |||
mResult = false; | |||
break; | |||
} | |||
mResult = true; | |||
break; | |||
case ENABLE_NOTIFICATION: | |||
chr = getCharacteristic(mUuid); | |||
//Log.v(TAG, "Writing descriptor of " + chr.getUuid()); | |||
if (chr != null) { | |||
BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")); | |||
if (cccd != null) { | |||
int properties = chr.getProperties(); | |||
byte[] value; | |||
if ((properties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) == BluetoothGattCharacteristic.PROPERTY_NOTIFY) { | |||
value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE; | |||
} else if ((properties & BluetoothGattCharacteristic.PROPERTY_INDICATE) == BluetoothGattCharacteristic.PROPERTY_INDICATE) { | |||
value = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE; | |||
} else { | |||
Log.e(TAG, "Unable to start notifications on input characteristic"); | |||
mResult = false; | |||
return; | |||
} | |||
mGatt.setCharacteristicNotification(chr, true); | |||
cccd.setValue(value); | |||
if (!mGatt.writeDescriptor(cccd)) { | |||
Log.e(TAG, "Unable to write descriptor " + mUuid.toString()); | |||
mResult = false; | |||
return; | |||
} | |||
mResult = true; | |||
} | |||
} | |||
} | |||
} | |||
public boolean finish() { | |||
return mResult; | |||
} | |||
private BluetoothGattCharacteristic getCharacteristic(UUID uuid) { | |||
BluetoothGattService valveService = mGatt.getService(steamControllerService); | |||
if (valveService == null) | |||
return null; | |||
return valveService.getCharacteristic(uuid); | |||
} | |||
static public GattOperation readCharacteristic(BluetoothGatt gatt, UUID uuid) { | |||
return new GattOperation(gatt, Operation.CHR_READ, uuid); | |||
} | |||
static public GattOperation writeCharacteristic(BluetoothGatt gatt, UUID uuid, byte[] value) { | |||
return new GattOperation(gatt, Operation.CHR_WRITE, uuid, value); | |||
} | |||
static public GattOperation enableNotification(BluetoothGatt gatt, UUID uuid) { | |||
return new GattOperation(gatt, Operation.ENABLE_NOTIFICATION, uuid); | |||
} | |||
} | |||
public HIDDeviceBLESteamController(HIDDeviceManager manager, BluetoothDevice device) { | |||
mManager = manager; | |||
mDevice = device; | |||
mDeviceId = mManager.getDeviceIDForIdentifier(getIdentifier()); | |||
mIsRegistered = false; | |||
mIsChromebook = mManager.getContext().getPackageManager().hasSystemFeature("org.chromium.arc.device_management"); | |||
mOperations = new LinkedList<GattOperation>(); | |||
mHandler = new Handler(Looper.getMainLooper()); | |||
mGatt = connectGatt(); | |||
// final HIDDeviceBLESteamController finalThis = this; | |||
// mHandler.postDelayed(new Runnable() { | |||
// @Override | |||
// public void run() { | |||
// finalThis.checkConnectionForChromebookIssue(); | |||
// } | |||
// }, CHROMEBOOK_CONNECTION_CHECK_INTERVAL); | |||
} | |||
public String getIdentifier() { | |||
return String.format("SteamController.%s", mDevice.getAddress()); | |||
} | |||
public BluetoothGatt getGatt() { | |||
return mGatt; | |||
} | |||
// Because on Chromebooks we show up as a dual-mode device, it will attempt to connect TRANSPORT_AUTO, which will use TRANSPORT_BREDR instead | |||
// of TRANSPORT_LE. Let's force ourselves to connect low energy. | |||
private BluetoothGatt connectGatt(boolean managed) { | |||
if (Build.VERSION.SDK_INT >= 23) { | |||
try { | |||
return mDevice.connectGatt(mManager.getContext(), managed, this, TRANSPORT_LE); | |||
} catch (Exception e) { | |||
return mDevice.connectGatt(mManager.getContext(), managed, this); | |||
} | |||
} else { | |||
return mDevice.connectGatt(mManager.getContext(), managed, this); | |||
} | |||
} | |||
private BluetoothGatt connectGatt() { | |||
return connectGatt(false); | |||
} | |||
protected int getConnectionState() { | |||
Context context = mManager.getContext(); | |||
if (context == null) { | |||
// We are lacking any context to get our Bluetooth information. We'll just assume disconnected. | |||
return BluetoothProfile.STATE_DISCONNECTED; | |||
} | |||
BluetoothManager btManager = (BluetoothManager)context.getSystemService(Context.BLUETOOTH_SERVICE); | |||
if (btManager == null) { | |||
// This device doesn't support Bluetooth. We should never be here, because how did | |||
// we instantiate a device to start with? | |||
return BluetoothProfile.STATE_DISCONNECTED; | |||
} | |||
return btManager.getConnectionState(mDevice, BluetoothProfile.GATT); | |||
} | |||
public void reconnect() { | |||
if (getConnectionState() != BluetoothProfile.STATE_CONNECTED) { | |||
mGatt.disconnect(); | |||
mGatt = connectGatt(); | |||
} | |||
} | |||
protected void checkConnectionForChromebookIssue() { | |||
if (!mIsChromebook) { | |||
// We only do this on Chromebooks, because otherwise it's really annoying to just attempt | |||
// over and over. | |||
return; | |||
} | |||
int connectionState = getConnectionState(); | |||
switch (connectionState) { | |||
case BluetoothProfile.STATE_CONNECTED: | |||
if (!mIsConnected) { | |||
// We are in the Bad Chromebook Place. We can force a disconnect | |||
// to try to recover. | |||
Log.v(TAG, "Chromebook: We are in a very bad state; the controller shows as connected in the underlying Bluetooth layer, but we never received a callback. Forcing a reconnect."); | |||
mIsReconnecting = true; | |||
mGatt.disconnect(); | |||
mGatt = connectGatt(false); | |||
break; | |||
} | |||
else if (!isRegistered()) { | |||
if (mGatt.getServices().size() > 0) { | |||
Log.v(TAG, "Chromebook: We are connected to a controller, but never got our registration. Trying to recover."); | |||
probeService(this); | |||
} | |||
else { | |||
Log.v(TAG, "Chromebook: We are connected to a controller, but never discovered services. Trying to recover."); | |||
mIsReconnecting = true; | |||
mGatt.disconnect(); | |||
mGatt = connectGatt(false); | |||
break; | |||
} | |||
} | |||
else { | |||
Log.v(TAG, "Chromebook: We are connected, and registered. Everything's good!"); | |||
return; | |||
} | |||
break; | |||
case BluetoothProfile.STATE_DISCONNECTED: | |||
Log.v(TAG, "Chromebook: We have either been disconnected, or the Chromebook BtGatt.ContextMap bug has bitten us. Attempting a disconnect/reconnect, but we may not be able to recover."); | |||
mIsReconnecting = true; | |||
mGatt.disconnect(); | |||
mGatt = connectGatt(false); | |||
break; | |||
case BluetoothProfile.STATE_CONNECTING: | |||
Log.v(TAG, "Chromebook: We're still trying to connect. Waiting a bit longer."); | |||
break; | |||
} | |||
final HIDDeviceBLESteamController finalThis = this; | |||
mHandler.postDelayed(new Runnable() { | |||
@Override | |||
public void run() { | |||
finalThis.checkConnectionForChromebookIssue(); | |||
} | |||
}, CHROMEBOOK_CONNECTION_CHECK_INTERVAL); | |||
} | |||
private boolean isRegistered() { | |||
return mIsRegistered; | |||
} | |||
private void setRegistered() { | |||
mIsRegistered = true; | |||
} | |||
private boolean probeService(HIDDeviceBLESteamController controller) { | |||
if (isRegistered()) { | |||
return true; | |||
} | |||
if (!mIsConnected) { | |||
return false; | |||
} | |||
Log.v(TAG, "probeService controller=" + controller); | |||
for (BluetoothGattService service : mGatt.getServices()) { | |||
if (service.getUuid().equals(steamControllerService)) { | |||
Log.v(TAG, "Found Valve steam controller service " + service.getUuid()); | |||
for (BluetoothGattCharacteristic chr : service.getCharacteristics()) { | |||
if (chr.getUuid().equals(inputCharacteristic)) { | |||
Log.v(TAG, "Found input characteristic"); | |||
// Start notifications | |||
BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")); | |||
if (cccd != null) { | |||
enableNotification(chr.getUuid()); | |||
} | |||
} | |||
} | |||
return true; | |||
} | |||
} | |||
if ((mGatt.getServices().size() == 0) && mIsChromebook && !mIsReconnecting) { | |||
Log.e(TAG, "Chromebook: Discovered services were empty; this almost certainly means the BtGatt.ContextMap bug has bitten us."); | |||
mIsConnected = false; | |||
mIsReconnecting = true; | |||
mGatt.disconnect(); | |||
mGatt = connectGatt(false); | |||
} | |||
return false; | |||
} | |||
////////////////////////////////////////////////////////////////////////////////////////////////////// | |||
////////////////////////////////////////////////////////////////////////////////////////////////////// | |||
////////////////////////////////////////////////////////////////////////////////////////////////////// | |||
private void finishCurrentGattOperation() { | |||
GattOperation op = null; | |||
synchronized (mOperations) { | |||
if (mCurrentOperation != null) { | |||
op = mCurrentOperation; | |||
mCurrentOperation = null; | |||
} | |||
} | |||
if (op != null) { | |||
boolean result = op.finish(); // TODO: Maybe in main thread as well? | |||
// Our operation failed, let's add it back to the beginning of our queue. | |||
if (!result) { | |||
mOperations.addFirst(op); | |||
} | |||
} | |||
executeNextGattOperation(); | |||
} | |||
private void executeNextGattOperation() { | |||
synchronized (mOperations) { | |||
if (mCurrentOperation != null) | |||
return; | |||
if (mOperations.isEmpty()) | |||
return; | |||
mCurrentOperation = mOperations.removeFirst(); | |||
} | |||
// Run in main thread | |||
mHandler.post(new Runnable() { | |||
@Override | |||
public void run() { | |||
synchronized (mOperations) { | |||
if (mCurrentOperation == null) { | |||
Log.e(TAG, "Current operation null in executor?"); | |||
return; | |||
} | |||
mCurrentOperation.run(); | |||
// now wait for the GATT callback and when it comes, finish this operation | |||
} | |||
} | |||
}); | |||
} | |||
private void queueGattOperation(GattOperation op) { | |||
synchronized (mOperations) { | |||
mOperations.add(op); | |||
} | |||
executeNextGattOperation(); | |||
} | |||
private void enableNotification(UUID chrUuid) { | |||
GattOperation op = HIDDeviceBLESteamController.GattOperation.enableNotification(mGatt, chrUuid); | |||
queueGattOperation(op); | |||
} | |||
public void writeCharacteristic(UUID uuid, byte[] value) { | |||
GattOperation op = HIDDeviceBLESteamController.GattOperation.writeCharacteristic(mGatt, uuid, value); | |||
queueGattOperation(op); | |||
} | |||
public void readCharacteristic(UUID uuid) { | |||
GattOperation op = HIDDeviceBLESteamController.GattOperation.readCharacteristic(mGatt, uuid); | |||
queueGattOperation(op); | |||
} | |||
////////////////////////////////////////////////////////////////////////////////////////////////////// | |||
////////////// BluetoothGattCallback overridden methods | |||
////////////////////////////////////////////////////////////////////////////////////////////////////// | |||
public void onConnectionStateChange(BluetoothGatt g, int status, int newState) { | |||
//Log.v(TAG, "onConnectionStateChange status=" + status + " newState=" + newState); | |||
mIsReconnecting = false; | |||
if (newState == 2) { | |||
mIsConnected = true; | |||
// Run directly, without GattOperation | |||
if (!isRegistered()) { | |||
mHandler.post(new Runnable() { | |||
@Override | |||
public void run() { | |||
mGatt.discoverServices(); | |||
} | |||
}); | |||
} | |||
} | |||
else if (newState == 0) { | |||
mIsConnected = false; | |||
} | |||
// Disconnection is handled in SteamLink using the ACTION_ACL_DISCONNECTED Intent. | |||
} | |||
public void onServicesDiscovered(BluetoothGatt gatt, int status) { | |||
//Log.v(TAG, "onServicesDiscovered status=" + status); | |||
if (status == 0) { | |||
if (gatt.getServices().size() == 0) { | |||
Log.v(TAG, "onServicesDiscovered returned zero services; something has gone horribly wrong down in Android's Bluetooth stack."); | |||
mIsReconnecting = true; | |||
mIsConnected = false; | |||
gatt.disconnect(); | |||
mGatt = connectGatt(false); | |||
} | |||
else { | |||
probeService(this); | |||
} | |||
} | |||
} | |||
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { | |||
//Log.v(TAG, "onCharacteristicRead status=" + status + " uuid=" + characteristic.getUuid()); | |||
if (characteristic.getUuid().equals(reportCharacteristic) && !mFrozen) { | |||
mManager.HIDDeviceFeatureReport(getId(), characteristic.getValue()); | |||
} | |||
finishCurrentGattOperation(); | |||
} | |||
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { | |||
//Log.v(TAG, "onCharacteristicWrite status=" + status + " uuid=" + characteristic.getUuid()); | |||
if (characteristic.getUuid().equals(reportCharacteristic)) { | |||
// Only register controller with the native side once it has been fully configured | |||
if (!isRegistered()) { | |||
Log.v(TAG, "Registering Steam Controller with ID: " + getId()); | |||
mManager.HIDDeviceConnected(getId(), getIdentifier(), getVendorId(), getProductId(), getSerialNumber(), getVersion(), getManufacturerName(), getProductName(), 0, 0, 0, 0); | |||
setRegistered(); | |||
} | |||
} | |||
finishCurrentGattOperation(); | |||
} | |||
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { | |||
// Enable this for verbose logging of controller input reports | |||
//Log.v(TAG, "onCharacteristicChanged uuid=" + characteristic.getUuid() + " data=" + HexDump.dumpHexString(characteristic.getValue())); | |||
if (characteristic.getUuid().equals(inputCharacteristic) && !mFrozen) { | |||
mManager.HIDDeviceInputReport(getId(), characteristic.getValue()); | |||
} | |||
} | |||
public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { | |||
//Log.v(TAG, "onDescriptorRead status=" + status); | |||
} | |||
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { | |||
BluetoothGattCharacteristic chr = descriptor.getCharacteristic(); | |||
//Log.v(TAG, "onDescriptorWrite status=" + status + " uuid=" + chr.getUuid() + " descriptor=" + descriptor.getUuid()); | |||
if (chr.getUuid().equals(inputCharacteristic)) { | |||
boolean hasWrittenInputDescriptor = true; | |||
BluetoothGattCharacteristic reportChr = chr.getService().getCharacteristic(reportCharacteristic); | |||
if (reportChr != null) { | |||
Log.v(TAG, "Writing report characteristic to enter valve mode"); | |||
reportChr.setValue(enterValveMode); | |||
gatt.writeCharacteristic(reportChr); | |||
} | |||
} | |||
finishCurrentGattOperation(); | |||
} | |||
public void onReliableWriteCompleted(BluetoothGatt gatt, int status) { | |||
//Log.v(TAG, "onReliableWriteCompleted status=" + status); | |||
} | |||
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) { | |||
//Log.v(TAG, "onReadRemoteRssi status=" + status); | |||
} | |||
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) { | |||
//Log.v(TAG, "onMtuChanged status=" + status); | |||
} | |||
////////////////////////////////////////////////////////////////////////////////////////////////////// | |||
//////// Public API | |||
////////////////////////////////////////////////////////////////////////////////////////////////////// | |||
@Override | |||
public int getId() { | |||
return mDeviceId; | |||
} | |||
@Override | |||
public int getVendorId() { | |||
// Valve Corporation | |||
final int VALVE_USB_VID = 0x28DE; | |||
return VALVE_USB_VID; | |||
} | |||
@Override | |||
public int getProductId() { | |||
// We don't have an easy way to query from the Bluetooth device, but we know what it is | |||
final int D0G_BLE2_PID = 0x1106; | |||
return D0G_BLE2_PID; | |||
} | |||
@Override | |||
public String getSerialNumber() { | |||
// This will be read later via feature report by Steam | |||
return "12345"; | |||
} | |||
@Override | |||
public int getVersion() { | |||
return 0; | |||
} | |||
@Override | |||
public String getManufacturerName() { | |||
return "Valve Corporation"; | |||
} | |||
@Override | |||
public String getProductName() { | |||
return "Steam Controller"; | |||
} | |||
@Override | |||
public UsbDevice getDevice() { | |||
return null; | |||
} | |||
@Override | |||
public boolean open() { | |||
return true; | |||
} | |||
@Override | |||
public int sendFeatureReport(byte[] report) { | |||
if (!isRegistered()) { | |||
Log.e(TAG, "Attempted sendFeatureReport before Steam Controller is registered!"); | |||
if (mIsConnected) { | |||
probeService(this); | |||
} | |||
return -1; | |||
} | |||
// We need to skip the first byte, as that doesn't go over the air | |||
byte[] actual_report = Arrays.copyOfRange(report, 1, report.length - 1); | |||
//Log.v(TAG, "sendFeatureReport " + HexDump.dumpHexString(actual_report)); | |||
writeCharacteristic(reportCharacteristic, actual_report); | |||
return report.length; | |||
} | |||
@Override | |||
public int sendOutputReport(byte[] report) { | |||
if (!isRegistered()) { | |||
Log.e(TAG, "Attempted sendOutputReport before Steam Controller is registered!"); | |||
if (mIsConnected) { | |||
probeService(this); | |||
} | |||
return -1; | |||
} | |||
//Log.v(TAG, "sendFeatureReport " + HexDump.dumpHexString(report)); | |||
writeCharacteristic(reportCharacteristic, report); | |||
return report.length; | |||
} | |||
@Override | |||
public boolean getFeatureReport(byte[] report) { | |||
if (!isRegistered()) { | |||
Log.e(TAG, "Attempted getFeatureReport before Steam Controller is registered!"); | |||
if (mIsConnected) { | |||
probeService(this); | |||
} | |||
return false; | |||
} | |||
//Log.v(TAG, "getFeatureReport"); | |||
readCharacteristic(reportCharacteristic); | |||
return true; | |||
} | |||
@Override | |||
public void close() { | |||
} | |||
@Override | |||
public void setFrozen(boolean frozen) { | |||
mFrozen = frozen; | |||
} | |||
@Override | |||
public void shutdown() { | |||
close(); | |||
BluetoothGatt g = mGatt; | |||
if (g != null) { | |||
g.disconnect(); | |||
g.close(); | |||
mGatt = null; | |||
} | |||
mManager = null; | |||
mIsRegistered = false; | |||
mIsConnected = false; | |||
mOperations.clear(); | |||
} | |||
} | |||
@ -0,0 +1,669 @@ | |||
package org.libsdl.app; | |||
import android.app.Activity; | |||
import android.app.AlertDialog; | |||
import android.app.PendingIntent; | |||
import android.bluetooth.BluetoothAdapter; | |||
import android.bluetooth.BluetoothDevice; | |||
import android.bluetooth.BluetoothManager; | |||
import android.bluetooth.BluetoothProfile; | |||
import android.util.Log; | |||
import android.content.BroadcastReceiver; | |||
import android.content.Context; | |||
import android.content.DialogInterface; | |||
import android.content.Intent; | |||
import android.content.IntentFilter; | |||
import android.content.SharedPreferences; | |||
import android.content.pm.PackageManager; | |||
import android.hardware.usb.*; | |||
import android.os.Handler; | |||
import android.os.Looper; | |||
import java.util.ArrayList; | |||
import java.util.HashMap; | |||
import java.util.Iterator; | |||
import java.util.List; | |||
public class HIDDeviceManager { | |||
private static final String TAG = "hidapi"; | |||
private static final String ACTION_USB_PERMISSION = "org.libsdl.app.USB_PERMISSION"; | |||
private static HIDDeviceManager sManager; | |||
private static int sManagerRefCount = 0; | |||
public static HIDDeviceManager acquire(Context context) { | |||
if (sManagerRefCount == 0) { | |||
sManager = new HIDDeviceManager(context); | |||
} | |||
++sManagerRefCount; | |||
return sManager; | |||
} | |||
public static void release(HIDDeviceManager manager) { | |||
if (manager == sManager) { | |||
--sManagerRefCount; | |||
if (sManagerRefCount == 0) { | |||
sManager.close(); | |||
sManager = null; | |||
} | |||
} | |||
} | |||
private Context mContext; | |||
private HashMap<Integer, HIDDevice> mDevicesById = new HashMap<Integer, HIDDevice>(); | |||
private HashMap<BluetoothDevice, HIDDeviceBLESteamController> mBluetoothDevices = new HashMap<BluetoothDevice, HIDDeviceBLESteamController>(); | |||
private int mNextDeviceId = 0; | |||
private SharedPreferences mSharedPreferences = null; | |||
private boolean mIsChromebook = false; | |||
private UsbManager mUsbManager; | |||
private Handler mHandler; | |||
private BluetoothManager mBluetoothManager; | |||
private List<BluetoothDevice> mLastBluetoothDevices; | |||
private final BroadcastReceiver mUsbBroadcast = new BroadcastReceiver() { | |||
@Override | |||
public void onReceive(Context context, Intent intent) { | |||
String action = intent.getAction(); | |||
if (action.equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) { | |||
UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); | |||
handleUsbDeviceAttached(usbDevice); | |||
} else if (action.equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) { | |||
UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); | |||
handleUsbDeviceDetached(usbDevice); | |||
} else if (action.equals(HIDDeviceManager.ACTION_USB_PERMISSION)) { | |||
UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); | |||
handleUsbDevicePermission(usbDevice, intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)); | |||
} | |||
} | |||
}; | |||
private final BroadcastReceiver mBluetoothBroadcast = new BroadcastReceiver() { | |||
@Override | |||
public void onReceive(Context context, Intent intent) { | |||
String action = intent.getAction(); | |||
// Bluetooth device was connected. If it was a Steam Controller, handle it | |||
if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) { | |||
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); | |||
Log.d(TAG, "Bluetooth device connected: " + device); | |||
if (isSteamController(device)) { | |||
connectBluetoothDevice(device); | |||
} | |||
} | |||
// Bluetooth device was disconnected, remove from controller manager (if any) | |||
if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) { | |||
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); | |||
Log.d(TAG, "Bluetooth device disconnected: " + device); | |||
disconnectBluetoothDevice(device); | |||
} | |||
} | |||
}; | |||
private HIDDeviceManager(final Context context) { | |||
mContext = context; | |||
// Make sure we have the HIDAPI library loaded with the native functions | |||
try { | |||
SDL.loadLibrary("hidapi"); | |||
} catch (Throwable e) { | |||
Log.w(TAG, "Couldn't load hidapi: " + e.toString()); | |||
AlertDialog.Builder builder = new AlertDialog.Builder(context); | |||
builder.setCancelable(false); | |||
builder.setTitle("SDL HIDAPI Error"); | |||
builder.setMessage("Please report the following error to the SDL maintainers: " + e.getMessage()); | |||
builder.setNegativeButton("Quit", new DialogInterface.OnClickListener() { | |||
@Override | |||
public void onClick(DialogInterface dialog, int which) { | |||
try { | |||
// If our context is an activity, exit rather than crashing when we can't | |||
// call our native functions. | |||
Activity activity = (Activity)context; | |||
activity.finish(); | |||
} | |||
catch (ClassCastException cce) { | |||
// Context wasn't an activity, there's nothing we can do. Give up and return. | |||
} | |||
} | |||
}); | |||
builder.show(); | |||
return; | |||
} | |||
HIDDeviceRegisterCallback(); | |||
mSharedPreferences = mContext.getSharedPreferences("hidapi", Context.MODE_PRIVATE); | |||
mIsChromebook = mContext.getPackageManager().hasSystemFeature("org.chromium.arc.device_management"); | |||
// if (shouldClear) { | |||
// SharedPreferences.Editor spedit = mSharedPreferences.edit(); | |||
// spedit.clear(); | |||
// spedit.commit(); | |||
// } | |||
// else | |||
{ | |||
mNextDeviceId = mSharedPreferences.getInt("next_device_id", 0); | |||
} | |||
initializeUSB(); | |||
initializeBluetooth(); | |||
} | |||
public Context getContext() { | |||
return mContext; | |||
} | |||
public int getDeviceIDForIdentifier(String identifier) { | |||
SharedPreferences.Editor spedit = mSharedPreferences.edit(); | |||
int result = mSharedPreferences.getInt(identifier, 0); | |||
if (result == 0) { | |||
result = mNextDeviceId++; | |||
spedit.putInt("next_device_id", mNextDeviceId); | |||
} | |||
spedit.putInt(identifier, result); | |||
spedit.commit(); | |||
return result; | |||
} | |||
private void initializeUSB() { | |||
mUsbManager = (UsbManager)mContext.getSystemService(Context.USB_SERVICE); | |||
/* | |||
// Logging | |||
for (UsbDevice device : mUsbManager.getDeviceList().values()) { | |||
Log.i(TAG,"Path: " + device.getDeviceName()); | |||
Log.i(TAG,"Manufacturer: " + device.getManufacturerName()); | |||
Log.i(TAG,"Product: " + device.getProductName()); | |||
Log.i(TAG,"ID: " + device.getDeviceId()); | |||
Log.i(TAG,"Class: " + device.getDeviceClass()); | |||
Log.i(TAG,"Protocol: " + device.getDeviceProtocol()); | |||
Log.i(TAG,"Vendor ID " + device.getVendorId()); | |||
Log.i(TAG,"Product ID: " + device.getProductId()); | |||
Log.i(TAG,"Interface count: " + device.getInterfaceCount()); | |||
Log.i(TAG,"---------------------------------------"); | |||
// Get interface details | |||
for (int index = 0; index < device.getInterfaceCount(); index++) { | |||
UsbInterface mUsbInterface = device.getInterface(index); | |||
Log.i(TAG," ***** *****"); | |||
Log.i(TAG," Interface index: " + index); | |||
Log.i(TAG," Interface ID: " + mUsbInterface.getId()); | |||
Log.i(TAG," Interface class: " + mUsbInterface.getInterfaceClass()); | |||
Log.i(TAG," Interface subclass: " + mUsbInterface.getInterfaceSubclass()); | |||
Log.i(TAG," Interface protocol: " + mUsbInterface.getInterfaceProtocol()); | |||
Log.i(TAG," Endpoint count: " + mUsbInterface.getEndpointCount()); | |||
// Get endpoint details | |||
for (int epi = 0; epi < mUsbInterface.getEndpointCount(); epi++) | |||
{ | |||
UsbEndpoint mEndpoint = mUsbInterface.getEndpoint(epi); | |||
Log.i(TAG," ++++ ++++ ++++"); | |||
Log.i(TAG," Endpoint index: " + epi); | |||
Log.i(TAG," Attributes: " + mEndpoint.getAttributes()); | |||
Log.i(TAG," Direction: " + mEndpoint.getDirection()); | |||
Log.i(TAG," Number: " + mEndpoint.getEndpointNumber()); | |||
Log.i(TAG," Interval: " + mEndpoint.getInterval()); | |||
Log.i(TAG," Packet size: " + mEndpoint.getMaxPacketSize()); | |||
Log.i(TAG," Type: " + mEndpoint.getType()); | |||
} | |||
} | |||
} | |||
Log.i(TAG," No more devices connected."); | |||
*/ | |||
// Register for USB broadcasts and permission completions | |||
IntentFilter filter = new IntentFilter(); | |||
filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED); | |||
filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); | |||
filter.addAction(HIDDeviceManager.ACTION_USB_PERMISSION); | |||
mContext.registerReceiver(mUsbBroadcast, filter); | |||
for (UsbDevice usbDevice : mUsbManager.getDeviceList().values()) { | |||
handleUsbDeviceAttached(usbDevice); | |||
} | |||
} | |||
UsbManager getUSBManager() { | |||
return mUsbManager; | |||
} | |||
private void shutdownUSB() { | |||
try { | |||
mContext.unregisterReceiver(mUsbBroadcast); | |||
} catch (Exception e) { | |||
// We may not have registered, that's okay | |||
} | |||
} | |||
private boolean isHIDDeviceInterface(UsbDevice usbDevice, UsbInterface usbInterface) { | |||
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_HID) { | |||
return true; | |||
} | |||
if (isXbox360Controller(usbDevice, usbInterface) || isXboxOneController(usbDevice, usbInterface)) { | |||
return true; | |||
} | |||
return false; | |||
} | |||
private boolean isXbox360Controller(UsbDevice usbDevice, UsbInterface usbInterface) { | |||
final int XB360_IFACE_SUBCLASS = 93; | |||
final int XB360_IFACE_PROTOCOL = 1; // Wired | |||
final int XB360W_IFACE_PROTOCOL = 129; // Wireless | |||
final int[] SUPPORTED_VENDORS = { | |||
0x0079, // GPD Win 2 | |||
0x044f, // Thrustmaster | |||
0x045e, // Microsoft | |||
0x046d, // Logitech | |||
0x056e, // Elecom | |||
0x06a3, // Saitek | |||
0x0738, // Mad Catz | |||
0x07ff, // Mad Catz | |||
0x0e6f, // PDP | |||
0x0f0d, // Hori | |||
0x1038, // SteelSeries | |||
0x11c9, // Nacon | |||
0x12ab, // Unknown | |||
0x1430, // RedOctane | |||
0x146b, // BigBen | |||
0x1532, // Razer Sabertooth | |||
0x15e4, // Numark | |||
0x162e, // Joytech | |||
0x1689, // Razer Onza | |||
0x1bad, // Harmonix | |||
0x24c6, // PowerA | |||
}; | |||
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC && | |||
usbInterface.getInterfaceSubclass() == XB360_IFACE_SUBCLASS && | |||
(usbInterface.getInterfaceProtocol() == XB360_IFACE_PROTOCOL || | |||
usbInterface.getInterfaceProtocol() == XB360W_IFACE_PROTOCOL)) { | |||
int vendor_id = usbDevice.getVendorId(); | |||
for (int supportedVid : SUPPORTED_VENDORS) { | |||
if (vendor_id == supportedVid) { | |||
return true; | |||
} | |||
} | |||
} | |||
return false; | |||
} | |||
private boolean isXboxOneController(UsbDevice usbDevice, UsbInterface usbInterface) { | |||
final int XB1_IFACE_SUBCLASS = 71; | |||
final int XB1_IFACE_PROTOCOL = 208; | |||
final int[] SUPPORTED_VENDORS = { | |||
0x045e, // Microsoft | |||
0x0738, // Mad Catz | |||
0x0e6f, // PDP | |||
0x0f0d, // Hori | |||
0x1532, // Razer Wildcat | |||
0x24c6, // PowerA | |||
0x2e24, // Hyperkin | |||
}; | |||
if (usbInterface.getId() == 0 && | |||
usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC && | |||
usbInterface.getInterfaceSubclass() == XB1_IFACE_SUBCLASS && | |||
usbInterface.getInterfaceProtocol() == XB1_IFACE_PROTOCOL) { | |||
int vendor_id = usbDevice.getVendorId(); | |||
for (int supportedVid : SUPPORTED_VENDORS) { | |||
if (vendor_id == supportedVid) { | |||
return true; | |||
} | |||
} | |||
} | |||
return false; | |||
} | |||
private void handleUsbDeviceAttached(UsbDevice usbDevice) { | |||
connectHIDDeviceUSB(usbDevice); | |||
} | |||
private void handleUsbDeviceDetached(UsbDevice usbDevice) { | |||
List<Integer> devices = new ArrayList<Integer>(); | |||
for (HIDDevice device : mDevicesById.values()) { | |||
if (usbDevice.equals(device.getDevice())) { | |||
devices.add(device.getId()); | |||
} | |||
} | |||
for (int id : devices) { | |||
HIDDevice device = mDevicesById.get(id); | |||
mDevicesById.remove(id); | |||
device.shutdown(); | |||
HIDDeviceDisconnected(id); | |||
} | |||
} | |||
private void handleUsbDevicePermission(UsbDevice usbDevice, boolean permission_granted) { | |||
for (HIDDevice device : mDevicesById.values()) { | |||
if (usbDevice.equals(device.getDevice())) { | |||
boolean opened = false; | |||
if (permission_granted) { | |||
opened = device.open(); | |||
} | |||
HIDDeviceOpenResult(device.getId(), opened); | |||
} | |||
} | |||
} | |||
private void connectHIDDeviceUSB(UsbDevice usbDevice) { | |||
synchronized (this) { | |||
for (int interface_index = 0; interface_index < usbDevice.getInterfaceCount(); interface_index++) { | |||
UsbInterface usbInterface = usbDevice.getInterface(interface_index); | |||
if (isHIDDeviceInterface(usbDevice, usbInterface)) { | |||
HIDDeviceUSB device = new HIDDeviceUSB(this, usbDevice, interface_index); | |||
int id = device.getId(); | |||
mDevicesById.put(id, device); | |||
HIDDeviceConnected(id, device.getIdentifier(), device.getVendorId(), device.getProductId(), device.getSerialNumber(), device.getVersion(), device.getManufacturerName(), device.getProductName(), usbInterface.getId(), usbInterface.getInterfaceClass(), usbInterface.getInterfaceSubclass(), usbInterface.getInterfaceProtocol()); | |||
} | |||
} | |||
} | |||
} | |||
private void initializeBluetooth() { | |||
Log.d(TAG, "Initializing Bluetooth"); | |||
if (mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) { | |||
Log.d(TAG, "Couldn't initialize Bluetooth, missing android.permission.BLUETOOTH"); | |||
return; | |||
} | |||
// Find bonded bluetooth controllers and create SteamControllers for them | |||
mBluetoothManager = (BluetoothManager)mContext.getSystemService(Context.BLUETOOTH_SERVICE); | |||
if (mBluetoothManager == null) { | |||
// This device doesn't support Bluetooth. | |||
return; | |||
} | |||
BluetoothAdapter btAdapter = mBluetoothManager.getAdapter(); | |||
if (btAdapter == null) { | |||
// This device has Bluetooth support in the codebase, but has no available adapters. | |||
return; | |||
} | |||
// Get our bonded devices. | |||
for (BluetoothDevice device : btAdapter.getBondedDevices()) { | |||
Log.d(TAG, "Bluetooth device available: " + device); | |||
if (isSteamController(device)) { | |||
connectBluetoothDevice(device); | |||
} | |||
} | |||
// NOTE: These don't work on Chromebooks, to my undying dismay. | |||
IntentFilter filter = new IntentFilter(); | |||
filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED); | |||
filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); | |||
mContext.registerReceiver(mBluetoothBroadcast, filter); | |||
if (mIsChromebook) { | |||
mHandler = new Handler(Looper.getMainLooper()); | |||
mLastBluetoothDevices = new ArrayList<BluetoothDevice>(); | |||
// final HIDDeviceManager finalThis = this; | |||
// mHandler.postDelayed(new Runnable() { | |||
// @Override | |||
// public void run() { | |||
// finalThis.chromebookConnectionHandler(); | |||
// } | |||
// }, 5000); | |||
} | |||
} | |||
private void shutdownBluetooth() { | |||
try { | |||
mContext.unregisterReceiver(mBluetoothBroadcast); | |||
} catch (Exception e) { | |||
// We may not have registered, that's okay | |||
} | |||
} | |||
// Chromebooks do not pass along ACTION_ACL_CONNECTED / ACTION_ACL_DISCONNECTED properly. | |||
// This function provides a sort of dummy version of that, watching for changes in the | |||
// connected devices and attempting to add controllers as things change. | |||
public void chromebookConnectionHandler() { | |||
if (!mIsChromebook) { | |||
return; | |||
} | |||
ArrayList<BluetoothDevice> disconnected = new ArrayList<BluetoothDevice>(); | |||
ArrayList<BluetoothDevice> connected = new ArrayList<BluetoothDevice>(); | |||
List<BluetoothDevice> currentConnected = mBluetoothManager.getConnectedDevices(BluetoothProfile.GATT); | |||
for (BluetoothDevice bluetoothDevice : currentConnected) { | |||
if (!mLastBluetoothDevices.contains(bluetoothDevice)) { | |||
connected.add(bluetoothDevice); | |||
} | |||
} | |||
for (BluetoothDevice bluetoothDevice : mLastBluetoothDevices) { | |||
if (!currentConnected.contains(bluetoothDevice)) { | |||
disconnected.add(bluetoothDevice); | |||
} | |||
} | |||
mLastBluetoothDevices = currentConnected; | |||
for (BluetoothDevice bluetoothDevice : disconnected) { | |||
disconnectBluetoothDevice(bluetoothDevice); | |||
} | |||
for (BluetoothDevice bluetoothDevice : connected) { | |||
connectBluetoothDevice(bluetoothDevice); | |||
} | |||
final HIDDeviceManager finalThis = this; | |||
mHandler.postDelayed(new Runnable() { | |||
@Override | |||
public void run() { | |||
finalThis.chromebookConnectionHandler(); | |||
} | |||
}, 10000); | |||
} | |||
public boolean connectBluetoothDevice(BluetoothDevice bluetoothDevice) { | |||
Log.v(TAG, "connectBluetoothDevice device=" + bluetoothDevice); | |||
synchronized (this) { | |||
if (mBluetoothDevices.containsKey(bluetoothDevice)) { | |||
Log.v(TAG, "Steam controller with address " + bluetoothDevice + " already exists, attempting reconnect"); | |||
HIDDeviceBLESteamController device = mBluetoothDevices.get(bluetoothDevice); | |||
device.reconnect(); | |||
return false; | |||
} | |||
HIDDeviceBLESteamController device = new HIDDeviceBLESteamController(this, bluetoothDevice); | |||
int id = device.getId(); | |||
mBluetoothDevices.put(bluetoothDevice, device); | |||
mDevicesById.put(id, device); | |||
// The Steam Controller will mark itself connected once initialization is complete | |||
} | |||
return true; | |||
} | |||
public void disconnectBluetoothDevice(BluetoothDevice bluetoothDevice) { | |||
synchronized (this) { | |||
HIDDeviceBLESteamController device = mBluetoothDevices.get(bluetoothDevice); | |||
if (device == null) | |||
return; | |||
int id = device.getId(); | |||
mBluetoothDevices.remove(bluetoothDevice); | |||
mDevicesById.remove(id); | |||
device.shutdown(); | |||
HIDDeviceDisconnected(id); | |||
} | |||
} | |||
public boolean isSteamController(BluetoothDevice bluetoothDevice) { | |||
// Sanity check. If you pass in a null device, by definition it is never a Steam Controller. | |||
if (bluetoothDevice == null) { | |||
return false; | |||
} | |||
// If the device has no local name, we really don't want to try an equality check against it. | |||
if (bluetoothDevice.getName() == null) { | |||
return false; | |||
} | |||
return bluetoothDevice.getName().equals("SteamController") && ((bluetoothDevice.getType() & BluetoothDevice.DEVICE_TYPE_LE) != 0); | |||
} | |||
private void close() { | |||
shutdownUSB(); | |||
shutdownBluetooth(); | |||
synchronized (this) { | |||
for (HIDDevice device : mDevicesById.values()) { | |||
device.shutdown(); | |||
} | |||
mDevicesById.clear(); | |||
mBluetoothDevices.clear(); | |||
HIDDeviceReleaseCallback(); | |||
} | |||
} | |||
public void setFrozen(boolean frozen) { | |||
synchronized (this) { | |||
for (HIDDevice device : mDevicesById.values()) { | |||
device.setFrozen(frozen); | |||
} | |||
} | |||
} | |||
////////////////////////////////////////////////////////////////////////////////////////////////////// | |||
////////////////////////////////////////////////////////////////////////////////////////////////////// | |||
////////////////////////////////////////////////////////////////////////////////////////////////////// | |||
private HIDDevice getDevice(int id) { | |||
synchronized (this) { | |||
HIDDevice result = mDevicesById.get(id); | |||
if (result == null) { | |||
Log.v(TAG, "No device for id: " + id); | |||
Log.v(TAG, "Available devices: " + mDevicesById.keySet()); | |||
} | |||
return result; | |||
} | |||
} | |||
////////////////////////////////////////////////////////////////////////////////////////////////////// | |||
////////// JNI interface functions | |||
////////////////////////////////////////////////////////////////////////////////////////////////////// | |||
public boolean openDevice(int deviceID) { | |||
Log.v(TAG, "openDevice deviceID=" + deviceID); | |||
HIDDevice device = getDevice(deviceID); | |||
if (device == null) { | |||
HIDDeviceDisconnected(deviceID); | |||
return false; | |||
} | |||
// Look to see if this is a USB device and we have permission to access it | |||
UsbDevice usbDevice = device.getDevice(); | |||
if (usbDevice != null && !mUsbManager.hasPermission(usbDevice)) { | |||
HIDDeviceOpenPending(deviceID); | |||
try { | |||
mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, new Intent(HIDDeviceManager.ACTION_USB_PERMISSION), 0)); | |||
} catch (Exception e) { | |||
Log.v(TAG, "Couldn't request permission for USB device " + usbDevice); | |||
HIDDeviceOpenResult(deviceID, false); | |||
} | |||
return false; | |||
} | |||
try { | |||
return device.open(); | |||
} catch (Exception e) { | |||
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e)); | |||
} | |||
return false; | |||
} | |||
public int sendOutputReport(int deviceID, byte[] report) { | |||
try { | |||
//Log.v(TAG, "sendOutputReport deviceID=" + deviceID + " length=" + report.length); | |||
HIDDevice device; | |||
device = getDevice(deviceID); | |||
if (device == null) { | |||
HIDDeviceDisconnected(deviceID); | |||
return -1; | |||
} | |||
return device.sendOutputReport(report); | |||
} catch (Exception e) { | |||
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e)); | |||
} | |||
return -1; | |||
} | |||
public int sendFeatureReport(int deviceID, byte[] report) { | |||
try { | |||
//Log.v(TAG, "sendFeatureReport deviceID=" + deviceID + " length=" + report.length); | |||
HIDDevice device; | |||
device = getDevice(deviceID); | |||
if (device == null) { | |||
HIDDeviceDisconnected(deviceID); | |||
return -1; | |||
} | |||
return device.sendFeatureReport(report); | |||
} catch (Exception e) { | |||
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e)); | |||
} | |||
return -1; | |||
} | |||
public boolean getFeatureReport(int deviceID, byte[] report) { | |||
try { | |||
//Log.v(TAG, "getFeatureReport deviceID=" + deviceID); | |||
HIDDevice device; | |||
device = getDevice(deviceID); | |||
if (device == null) { | |||
HIDDeviceDisconnected(deviceID); | |||
return false; | |||
} | |||
return device.getFeatureReport(report); | |||
} catch (Exception e) { | |||
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e)); | |||
} | |||
return false; | |||
} | |||
public void closeDevice(int deviceID) { | |||
try { | |||
Log.v(TAG, "closeDevice deviceID=" + deviceID); | |||
HIDDevice device; | |||
device = getDevice(deviceID); | |||
if (device == null) { | |||
HIDDeviceDisconnected(deviceID); | |||
return; | |||
} | |||
device.close(); | |||
} catch (Exception e) { | |||
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e)); | |||
} | |||
} | |||
////////////////////////////////////////////////////////////////////////////////////////////////////// | |||
/////////////// Native methods | |||
////////////////////////////////////////////////////////////////////////////////////////////////////// | |||
private native void HIDDeviceRegisterCallback(); | |||
private native void HIDDeviceReleaseCallback(); | |||
native void HIDDeviceConnected(int deviceID, String identifier, int vendorId, int productId, String serial_number, int release_number, String manufacturer_string, String product_string, int interface_number, int interface_class, int interface_subclass, int interface_protocol); | |||
native void HIDDeviceOpenPending(int deviceID); | |||
native void HIDDeviceOpenResult(int deviceID, boolean opened); | |||
native void HIDDeviceDisconnected(int deviceID); | |||
native void HIDDeviceInputReport(int deviceID, byte[] report); | |||
native void HIDDeviceFeatureReport(int deviceID, byte[] report); | |||
} |
@ -0,0 +1,309 @@ | |||
package org.libsdl.app; | |||
import android.hardware.usb.*; | |||
import android.os.Build; | |||
import android.util.Log; | |||
import java.util.Arrays; | |||
class HIDDeviceUSB implements HIDDevice { | |||
private static final String TAG = "hidapi"; | |||
protected HIDDeviceManager mManager; | |||
protected UsbDevice mDevice; | |||
protected int mInterfaceIndex; | |||
protected int mInterface; | |||
protected int mDeviceId; | |||
protected UsbDeviceConnection mConnection; | |||
protected UsbEndpoint mInputEndpoint; | |||
protected UsbEndpoint mOutputEndpoint; | |||
protected InputThread mInputThread; | |||
protected boolean mRunning; | |||
protected boolean mFrozen; | |||
public HIDDeviceUSB(HIDDeviceManager manager, UsbDevice usbDevice, int interface_index) { | |||
mManager = manager; | |||
mDevice = usbDevice; | |||
mInterfaceIndex = interface_index; | |||
mInterface = mDevice.getInterface(mInterfaceIndex).getId(); | |||
mDeviceId = manager.getDeviceIDForIdentifier(getIdentifier()); | |||
mRunning = false; | |||
} | |||
public String getIdentifier() { | |||
return String.format("%s/%x/%x/%d", mDevice.getDeviceName(), mDevice.getVendorId(), mDevice.getProductId(), mInterfaceIndex); | |||
} | |||
@Override | |||
public int getId() { | |||
return mDeviceId; | |||
} | |||
@Override | |||
public int getVendorId() { | |||
return mDevice.getVendorId(); | |||
} | |||
@Override | |||
public int getProductId() { | |||
return mDevice.getProductId(); | |||
} | |||
@Override | |||
public String getSerialNumber() { | |||
String result = null; | |||
if (Build.VERSION.SDK_INT >= 21) { | |||
try { | |||
result = mDevice.getSerialNumber(); | |||
} | |||
catch (SecurityException exception) { | |||
//Log.w(TAG, "App permissions mean we cannot get serial number for device " + getDeviceName() + " message: " + exception.getMessage()); | |||
} | |||
} | |||
if (result == null) { | |||
result = ""; | |||
} | |||
return result; | |||
} | |||
@Override | |||
public int getVersion() { | |||
return 0; | |||
} | |||
@Override | |||
public String getManufacturerName() { | |||
String result = null; | |||
if (Build.VERSION.SDK_INT >= 21) { | |||
result = mDevice.getManufacturerName(); | |||
} | |||
if (result == null) { | |||
result = String.format("%x", getVendorId()); | |||
} | |||
return result; | |||
} | |||
@Override | |||
public String getProductName() { | |||
String result = null; | |||
if (Build.VERSION.SDK_INT >= 21) { | |||
result = mDevice.getProductName(); | |||
} | |||
if (result == null) { | |||
result = String.format("%x", getProductId()); | |||
} | |||
return result; | |||
} | |||
@Override | |||
public UsbDevice getDevice() { | |||
return mDevice; | |||
} | |||
public String getDeviceName() { | |||
return getManufacturerName() + " " + getProductName() + "(0x" + String.format("%x", getVendorId()) + "/0x" + String.format("%x", getProductId()) + ")"; | |||
} | |||
@Override | |||
public boolean open() { | |||
mConnection = mManager.getUSBManager().openDevice(mDevice); | |||
if (mConnection == null) { | |||
Log.w(TAG, "Unable to open USB device " + getDeviceName()); | |||
return false; | |||
} | |||
// Force claim our interface | |||
UsbInterface iface = mDevice.getInterface(mInterfaceIndex); | |||
if (!mConnection.claimInterface(iface, true)) { | |||
Log.w(TAG, "Failed to claim interfaces on USB device " + getDeviceName()); | |||
close(); | |||
return false; | |||
} | |||
// Find the endpoints | |||
for (int j = 0; j < iface.getEndpointCount(); j++) { | |||
UsbEndpoint endpt = iface.getEndpoint(j); | |||
switch (endpt.getDirection()) { | |||
case UsbConstants.USB_DIR_IN: | |||
if (mInputEndpoint == null) { | |||
mInputEndpoint = endpt; | |||
} | |||
break; | |||
case UsbConstants.USB_DIR_OUT: | |||
if (mOutputEndpoint == null) { | |||
mOutputEndpoint = endpt; | |||
} | |||
break; | |||
} | |||
} | |||
// Make sure the required endpoints were present | |||
if (mInputEndpoint == null || mOutputEndpoint == null) { | |||
Log.w(TAG, "Missing required endpoint on USB device " + getDeviceName()); | |||
close(); | |||
return false; | |||
} | |||
// Start listening for input | |||
mRunning = true; | |||
mInputThread = new InputThread(); | |||
mInputThread.start(); | |||
return true; | |||
} | |||
@Override | |||
public int sendFeatureReport(byte[] report) { | |||
int res = -1; | |||
int offset = 0; | |||
int length = report.length; | |||
boolean skipped_report_id = false; | |||
byte report_number = report[0]; | |||
if (report_number == 0x0) { | |||
++offset; | |||
--length; | |||
skipped_report_id = true; | |||
} | |||
res = mConnection.controlTransfer( | |||
UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_OUT, | |||
0x09/*HID set_report*/, | |||
(3/*HID feature*/ << 8) | report_number, | |||
mInterface, | |||
report, offset, length, | |||
1000/*timeout millis*/); | |||
if (res < 0) { | |||
Log.w(TAG, "sendFeatureReport() returned " + res + " on device " + getDeviceName()); | |||
return -1; | |||
} | |||
if (skipped_report_id) { | |||
++length; | |||
} | |||
return length; | |||
} | |||
@Override | |||
public int sendOutputReport(byte[] report) { | |||
int r = mConnection.bulkTransfer(mOutputEndpoint, report, report.length, 1000); | |||
if (r != report.length) { | |||
Log.w(TAG, "sendOutputReport() returned " + r + " on device " + getDeviceName()); | |||
} | |||
return r; | |||
} | |||
@Override | |||
public boolean getFeatureReport(byte[] report) { | |||
int res = -1; | |||
int offset = 0; | |||
int length = report.length; | |||
boolean skipped_report_id = false; | |||
byte report_number = report[0]; | |||
if (report_number == 0x0) { | |||
/* Offset the return buffer by 1, so that the report ID | |||
will remain in byte 0. */ | |||
++offset; | |||
--length; | |||
skipped_report_id = true; | |||
} | |||
res = mConnection.controlTransfer( | |||
UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_IN, | |||
0x01/*HID get_report*/, | |||
(3/*HID feature*/ << 8) | report_number, | |||
mInterface, | |||
report, offset, length, | |||
1000/*timeout millis*/); | |||
if (res < 0) { | |||
Log.w(TAG, "getFeatureReport() returned " + res + " on device " + getDeviceName()); | |||
return false; | |||
} | |||
if (skipped_report_id) { | |||
++res; | |||
++length; | |||
} | |||
byte[] data; | |||
if (res == length) { | |||
data = report; | |||
} else { | |||
data = Arrays.copyOfRange(report, 0, res); | |||
} | |||
mManager.HIDDeviceFeatureReport(mDeviceId, data); | |||
return true; | |||
} | |||
@Override | |||
public void close() { | |||
mRunning = false; | |||
if (mInputThread != null) { | |||
while (mInputThread.isAlive()) { | |||
mInputThread.interrupt(); | |||
try { | |||
mInputThread.join(); | |||
} catch (InterruptedException e) { | |||
// Keep trying until we're done | |||
} | |||
} | |||
mInputThread = null; | |||
} | |||
if (mConnection != null) { | |||
UsbInterface iface = mDevice.getInterface(mInterfaceIndex); | |||
mConnection.releaseInterface(iface); | |||
mConnection.close(); | |||
mConnection = null; | |||
} | |||
} | |||
@Override | |||
public void shutdown() { | |||
close(); | |||
mManager = null; | |||
} | |||
@Override | |||
public void setFrozen(boolean frozen) { | |||
mFrozen = frozen; | |||
} | |||
protected class InputThread extends Thread { | |||
@Override | |||
public void run() { | |||
int packetSize = mInputEndpoint.getMaxPacketSize(); | |||
byte[] packet = new byte[packetSize]; | |||
while (mRunning) { | |||
int r; | |||
try | |||
{ | |||
r = mConnection.bulkTransfer(mInputEndpoint, packet, packetSize, 1000); | |||
} | |||
catch (Exception e) | |||
{ | |||
Log.v(TAG, "Exception in UsbDeviceConnection bulktransfer: " + e); | |||
break; | |||
} | |||
if (r < 0) { | |||
// Could be a timeout or an I/O error | |||
} | |||
if (r > 0) { | |||
byte[] data; | |||
if (r == packetSize) { | |||
data = packet; | |||
} else { | |||
data = Arrays.copyOfRange(packet, 0, r); | |||
} | |||
if (!mFrozen) { | |||
mManager.HIDDeviceInputReport(mDeviceId, data); | |||
} | |||
} | |||
} | |||
} | |||
} | |||
} |
@ -0,0 +1,84 @@ | |||
package org.libsdl.app; | |||
import android.content.Context; | |||
import java.lang.reflect.*; | |||
/** | |||
SDL library initialization | |||
*/ | |||
public class SDL { | |||
// This function should be called first and sets up the native code | |||
// so it can call into the Java classes | |||
public static void setupJNI() { | |||
SDLActivity.nativeSetupJNI(); | |||
SDLAudioManager.nativeSetupJNI(); | |||
SDLControllerManager.nativeSetupJNI(); | |||
} | |||
// This function should be called each time the activity is started | |||
public static void initialize() { | |||
setContext(null); | |||
SDLActivity.initialize(); | |||
SDLAudioManager.initialize(); | |||
SDLControllerManager.initialize(); | |||
} | |||
// This function stores the current activity (SDL or not) | |||
public static void setContext(Context context) { | |||
mContext = context; | |||
} | |||
public static Context getContext() { | |||
return mContext; | |||
} | |||
public static void loadLibrary(String libraryName) throws UnsatisfiedLinkError, SecurityException, NullPointerException { | |||
if (libraryName == null) { | |||
throw new NullPointerException("No library name provided."); | |||
} | |||
try { | |||
// Let's see if we have ReLinker available in the project. This is necessary for | |||
// some projects that have huge numbers of local libraries bundled, and thus may | |||
// trip a bug in Android's native library loader which ReLinker works around. (If | |||
// loadLibrary works properly, ReLinker will simply use the normal Android method | |||
// internally.) | |||
// | |||
// To use ReLinker, just add it as a dependency. For more information, see | |||
// https://github.com/KeepSafe/ReLinker for ReLinker's repository. | |||
// | |||
Class relinkClass = mContext.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker"); | |||
Class relinkListenerClass = mContext.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker$LoadListener"); | |||
Class contextClass = mContext.getClassLoader().loadClass("android.content.Context"); | |||
Class stringClass = mContext.getClassLoader().loadClass("java.lang.String"); | |||
// Get a 'force' instance of the ReLinker, so we can ensure libraries are reinstalled if | |||
// they've changed during updates. | |||
Method forceMethod = relinkClass.getDeclaredMethod("force"); | |||
Object relinkInstance = forceMethod.invoke(null); | |||
Class relinkInstanceClass = relinkInstance.getClass(); | |||
// Actually load the library! | |||
Method loadMethod = relinkInstanceClass.getDeclaredMethod("loadLibrary", contextClass, stringClass, stringClass, relinkListenerClass); | |||
loadMethod.invoke(relinkInstance, mContext, libraryName, null, null); | |||
} | |||
catch (final Throwable e) { | |||
// Fall back | |||
try { | |||
System.loadLibrary(libraryName); | |||
} | |||
catch (final UnsatisfiedLinkError ule) { | |||
throw ule; | |||
} | |||
catch (final SecurityException se) { | |||
throw se; | |||
} | |||
} | |||
} | |||
protected static Context mContext; | |||
} |
@ -0,0 +1,387 @@ | |||
package org.libsdl.app; | |||
import android.media.*; | |||
import android.os.Build; | |||
import android.util.Log; | |||
public class SDLAudioManager | |||
{ | |||
protected static final String TAG = "SDLAudio"; | |||
protected static AudioTrack mAudioTrack; | |||
protected static AudioRecord mAudioRecord; | |||
public static void initialize() { | |||
mAudioTrack = null; | |||
mAudioRecord = null; | |||
} | |||
// Audio | |||
protected static String getAudioFormatString(int audioFormat) { | |||
switch (audioFormat) { | |||
case AudioFormat.ENCODING_PCM_8BIT: | |||
return "8-bit"; | |||
case AudioFormat.ENCODING_PCM_16BIT: | |||
return "16-bit"; | |||
case AudioFormat.ENCODING_PCM_FLOAT: | |||
return "float"; | |||
default: | |||
return Integer.toString(audioFormat); | |||
} | |||
} | |||
protected static int[] open(boolean isCapture, int sampleRate, int audioFormat, int desiredChannels, int desiredFrames) { | |||
int channelConfig; | |||
int sampleSize; | |||
int frameSize; | |||
Log.v(TAG, "Opening " + (isCapture ? "capture" : "playback") + ", requested " + desiredFrames + " frames of " + desiredChannels + " channel " + getAudioFormatString(audioFormat) + " audio at " + sampleRate + " Hz"); | |||
/* On older devices let's use known good settings */ | |||
if (Build.VERSION.SDK_INT < 21) { | |||
if (desiredChannels > 2) { | |||
desiredChannels = 2; | |||
} | |||
if (sampleRate < 8000) { | |||
sampleRate = 8000; | |||
} else if (sampleRate > 48000) { | |||
sampleRate = 48000; | |||
} | |||
} | |||
if (audioFormat == AudioFormat.ENCODING_PCM_FLOAT) { | |||
int minSDKVersion = (isCapture ? 23 : 21); | |||
if (Build.VERSION.SDK_INT < minSDKVersion) { | |||
audioFormat = AudioFormat.ENCODING_PCM_16BIT; | |||
} | |||
} | |||
switch (audioFormat) | |||
{ | |||
case AudioFormat.ENCODING_PCM_8BIT: | |||
sampleSize = 1; | |||
break; | |||
case AudioFormat.ENCODING_PCM_16BIT: | |||
sampleSize = 2; | |||
break; | |||
case AudioFormat.ENCODING_PCM_FLOAT: | |||
sampleSize = 4; | |||
break; | |||
default: | |||
Log.v(TAG, "Requested format " + audioFormat + ", getting ENCODING_PCM_16BIT"); | |||
audioFormat = AudioFormat.ENCODING_PCM_16BIT; | |||
sampleSize = 2; | |||
break; | |||
} | |||
if (isCapture) { | |||
switch (desiredChannels) { | |||
case 1: | |||
channelConfig = AudioFormat.CHANNEL_IN_MONO; | |||
break; | |||
case 2: | |||
channelConfig = AudioFormat.CHANNEL_IN_STEREO; | |||
break; | |||
default: | |||
Log.v(TAG, "Requested " + desiredChannels + " channels, getting stereo"); | |||
desiredChannels = 2; | |||
channelConfig = AudioFormat.CHANNEL_IN_STEREO; | |||
break; | |||
} | |||
} else { | |||
switch (desiredChannels) { | |||
case 1: | |||
channelConfig = AudioFormat.CHANNEL_OUT_MONO; | |||
break; | |||
case 2: | |||
channelConfig = AudioFormat.CHANNEL_OUT_STEREO; | |||
break; | |||
case 3: | |||
channelConfig = AudioFormat.CHANNEL_OUT_STEREO | AudioFormat.CHANNEL_OUT_FRONT_CENTER; | |||
break; | |||
case 4: | |||
channelConfig = AudioFormat.CHANNEL_OUT_QUAD; | |||
break; | |||
case 5: | |||
channelConfig = AudioFormat.CHANNEL_OUT_QUAD | AudioFormat.CHANNEL_OUT_FRONT_CENTER; | |||
break; | |||
case 6: | |||
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1; | |||
break; | |||
case 7: | |||
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1 | AudioFormat.CHANNEL_OUT_BACK_CENTER; | |||
break; | |||
case 8: | |||
if (Build.VERSION.SDK_INT >= 23) { | |||
channelConfig = AudioFormat.CHANNEL_OUT_7POINT1_SURROUND; | |||
} else { | |||
Log.v(TAG, "Requested " + desiredChannels + " channels, getting 5.1 surround"); | |||
desiredChannels = 6; | |||
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1; | |||
} | |||
break; | |||
default: | |||
Log.v(TAG, "Requested " + desiredChannels + " channels, getting stereo"); | |||
desiredChannels = 2; | |||
channelConfig = AudioFormat.CHANNEL_OUT_STEREO; | |||
break; | |||
} | |||
/* | |||
Log.v(TAG, "Speaker configuration (and order of channels):"); | |||
if ((channelConfig & 0x00000004) != 0) { | |||
Log.v(TAG, " CHANNEL_OUT_FRONT_LEFT"); | |||
} | |||
if ((channelConfig & 0x00000008) != 0) { | |||
Log.v(TAG, " CHANNEL_OUT_FRONT_RIGHT"); | |||
} | |||
if ((channelConfig & 0x00000010) != 0) { | |||
Log.v(TAG, " CHANNEL_OUT_FRONT_CENTER"); | |||
} | |||
if ((channelConfig & 0x00000020) != 0) { | |||
Log.v(TAG, " CHANNEL_OUT_LOW_FREQUENCY"); | |||
} | |||
if ((channelConfig & 0x00000040) != 0) { | |||
Log.v(TAG, " CHANNEL_OUT_BACK_LEFT"); | |||
} | |||
if ((channelConfig & 0x00000080) != 0) { | |||
Log.v(TAG, " CHANNEL_OUT_BACK_RIGHT"); | |||
} | |||
if ((channelConfig & 0x00000100) != 0) { | |||
Log.v(TAG, " CHANNEL_OUT_FRONT_LEFT_OF_CENTER"); | |||
} | |||
if ((channelConfig & 0x00000200) != 0) { | |||
Log.v(TAG, " CHANNEL_OUT_FRONT_RIGHT_OF_CENTER"); | |||
} | |||
if ((channelConfig & 0x00000400) != 0) { | |||
Log.v(TAG, " CHANNEL_OUT_BACK_CENTER"); | |||
} | |||
if ((channelConfig & 0x00000800) != 0) { | |||
Log.v(TAG, " CHANNEL_OUT_SIDE_LEFT"); | |||
} | |||
if ((channelConfig & 0x00001000) != 0) { | |||
Log.v(TAG, " CHANNEL_OUT_SIDE_RIGHT"); | |||
} | |||
*/ | |||
} | |||
frameSize = (sampleSize * desiredChannels); | |||
// Let the user pick a larger buffer if they really want -- but ye | |||
// gods they probably shouldn't, the minimums are horrifyingly high | |||
// latency already | |||
int minBufferSize; | |||
if (isCapture) { | |||
minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat); | |||
} else { | |||
minBufferSize = AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat); | |||
} | |||
desiredFrames = Math.max(desiredFrames, (minBufferSize + frameSize - 1) / frameSize); | |||
int[] results = new int[4]; | |||
if (isCapture) { | |||
if (mAudioRecord == null) { | |||
mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, sampleRate, | |||
channelConfig, audioFormat, desiredFrames * frameSize); | |||
// see notes about AudioTrack state in audioOpen(), above. Probably also applies here. | |||
if (mAudioRecord.getState() != AudioRecord.STATE_INITIALIZED) { | |||
Log.e(TAG, "Failed during initialization of AudioRecord"); | |||
mAudioRecord.release(); | |||
mAudioRecord = null; | |||
return null; | |||
} | |||
mAudioRecord.startRecording(); | |||
} | |||
results[0] = mAudioRecord.getSampleRate(); | |||
results[1] = mAudioRecord.getAudioFormat(); | |||
results[2] = mAudioRecord.getChannelCount(); | |||
results[3] = desiredFrames; | |||
} else { | |||
if (mAudioTrack == null) { | |||
mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, channelConfig, audioFormat, desiredFrames * frameSize, AudioTrack.MODE_STREAM); | |||
// Instantiating AudioTrack can "succeed" without an exception and the track may still be invalid | |||
// Ref: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/media/java/android/media/AudioTrack.java | |||
// Ref: http://developer.android.com/reference/android/media/AudioTrack.html#getState() | |||
if (mAudioTrack.getState() != AudioTrack.STATE_INITIALIZED) { | |||
/* Try again, with safer values */ | |||
Log.e(TAG, "Failed during initialization of Audio Track"); | |||
mAudioTrack.release(); | |||
mAudioTrack = null; | |||
return null; | |||
} | |||
mAudioTrack.play(); | |||
} | |||
results[0] = mAudioTrack.getSampleRate(); | |||
results[1] = mAudioTrack.getAudioFormat(); | |||
results[2] = mAudioTrack.getChannelCount(); | |||
results[3] = desiredFrames; | |||
} | |||
Log.v(TAG, "Opening " + (isCapture ? "capture" : "playback") + ", got " + results[3] + " frames of " + results[2] + " channel " + getAudioFormatString(results[1]) + " audio at " + results[0] + " Hz"); | |||
return results; | |||
} | |||
/** | |||
* This method is called by SDL using JNI. | |||
*/ | |||
public static int[] audioOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames) { | |||
return open(false, sampleRate, audioFormat, desiredChannels, desiredFrames); | |||
} | |||
/** | |||
* This method is called by SDL using JNI. | |||
*/ | |||
public static void audioWriteFloatBuffer(float[] buffer) { | |||
if (mAudioTrack == null) { | |||
Log.e(TAG, "Attempted to make audio call with uninitialized audio!"); | |||
return; | |||
} | |||
for (int i = 0; i < buffer.length;) { | |||
int result = mAudioTrack.write(buffer, i, buffer.length - i, AudioTrack.WRITE_BLOCKING); | |||
if (result > 0) { | |||
i += result; | |||
} else if (result == 0) { | |||
try { | |||
Thread.sleep(1); | |||
} catch(InterruptedException e) { | |||
// Nom nom | |||
} | |||
} else { | |||
Log.w(TAG, "SDL audio: error return from write(float)"); | |||
return; | |||
} | |||
} | |||
} | |||
/** | |||
* This method is called by SDL using JNI. | |||
*/ | |||
public static void audioWriteShortBuffer(short[] buffer) { | |||
if (mAudioTrack == null) { | |||
Log.e(TAG, "Attempted to make audio call with uninitialized audio!"); | |||
return; | |||
} | |||
for (int i = 0; i < buffer.length;) { | |||
int result = mAudioTrack.write(buffer, i, buffer.length - i); | |||
if (result > 0) { | |||
i += result; | |||
} else if (result == 0) { | |||
try { | |||
Thread.sleep(1); | |||
} catch(InterruptedException e) { | |||
// Nom nom | |||
} | |||
} else { | |||
Log.w(TAG, "SDL audio: error return from write(short)"); | |||
return; | |||
} | |||
} | |||
} | |||
/** | |||
* This method is called by SDL using JNI. | |||
*/ | |||
public static void audioWriteByteBuffer(byte[] buffer) { | |||
if (mAudioTrack == null) { | |||
Log.e(TAG, "Attempted to make audio call with uninitialized audio!"); | |||
return; | |||
} | |||
for (int i = 0; i < buffer.length; ) { | |||
int result = mAudioTrack.write(buffer, i, buffer.length - i); | |||
if (result > 0) { | |||
i += result; | |||
} else if (result == 0) { | |||
try { | |||
Thread.sleep(1); | |||
} catch(InterruptedException e) { | |||
// Nom nom | |||
} | |||
} else { | |||
Log.w(TAG, "SDL audio: error return from write(byte)"); | |||
return; | |||
} | |||
} | |||
} | |||
/** | |||
* This method is called by SDL using JNI. | |||
*/ | |||
public static int[] captureOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames) { | |||
return open(true, sampleRate, audioFormat, desiredChannels, desiredFrames); | |||
} | |||
/** This method is called by SDL using JNI. */ | |||
public static int captureReadFloatBuffer(float[] buffer, boolean blocking) { | |||
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING); | |||
} | |||
/** This method is called by SDL using JNI. */ | |||
public static int captureReadShortBuffer(short[] buffer, boolean blocking) { | |||
if (Build.VERSION.SDK_INT < 23) { | |||
return mAudioRecord.read(buffer, 0, buffer.length); | |||
} else { | |||
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING); | |||
} | |||
} | |||
/** This method is called by SDL using JNI. */ | |||
public static int captureReadByteBuffer(byte[] buffer, boolean blocking) { | |||
if (Build.VERSION.SDK_INT < 23) { | |||
return mAudioRecord.read(buffer, 0, buffer.length); | |||
} else { | |||
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING); | |||
} | |||
} | |||
/** This method is called by SDL using JNI. */ | |||
public static void audioClose() { | |||
if (mAudioTrack != null) { | |||
mAudioTrack.stop(); | |||
mAudioTrack.release(); | |||
mAudioTrack = null; | |||
} | |||
} | |||
/** This method is called by SDL using JNI. */ | |||
public static void captureClose() { | |||
if (mAudioRecord != null) { | |||
mAudioRecord.stop(); | |||
mAudioRecord.release(); | |||
mAudioRecord = null; | |||
} | |||
} | |||
/** This method is called by SDL using JNI. */ | |||
public static void audioSetThreadPriority(boolean iscapture, int device_id) { | |||
try { | |||
/* Set thread name */ | |||
if (iscapture) { | |||
Thread.currentThread().setName("SDLAudioC" + device_id); | |||
} else { | |||
Thread.currentThread().setName("SDLAudioP" + device_id); | |||
} | |||
/* Set thread priority */ | |||
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_AUDIO); | |||
} catch (Exception e) { | |||
Log.v(TAG, "modify thread properties failed " + e.toString()); | |||
} | |||
} | |||
public static native int nativeSetupJNI(); | |||
} |
@ -0,0 +1,788 @@ | |||
package org.libsdl.app; | |||
import java.util.ArrayList; | |||
import java.util.Collections; | |||
import java.util.Comparator; | |||
import java.util.List; | |||
import android.content.Context; | |||
import android.os.*; | |||
import android.view.*; | |||
import android.util.Log; | |||
public class SDLControllerManager | |||
{ | |||
public static native int nativeSetupJNI(); | |||
public static native int nativeAddJoystick(int device_id, String name, String desc, | |||
int vendor_id, int product_id, | |||
boolean is_accelerometer, int button_mask, | |||
int naxes, int nhats, int nballs); | |||
public static native int nativeRemoveJoystick(int device_id); | |||
public static native int nativeAddHaptic(int device_id, String name); | |||
public static native int nativeRemoveHaptic(int device_id); | |||
public static native int onNativePadDown(int device_id, int keycode); | |||
public static native int onNativePadUp(int device_id, int keycode); | |||
public static native void onNativeJoy(int device_id, int axis, | |||
float value); | |||
public static native void onNativeHat(int device_id, int hat_id, | |||
int x, int y); | |||
protected static SDLJoystickHandler mJoystickHandler; | |||
protected static SDLHapticHandler mHapticHandler; | |||
private static final String TAG = "SDLControllerManager"; | |||
public static void initialize() { | |||
if (mJoystickHandler == null) { | |||
if (Build.VERSION.SDK_INT >= 19) { | |||
mJoystickHandler = new SDLJoystickHandler_API19(); | |||
} else { | |||
mJoystickHandler = new SDLJoystickHandler_API16(); | |||
} | |||
} | |||
if (mHapticHandler == null) { | |||
if (Build.VERSION.SDK_INT >= 26) { | |||
mHapticHandler = new SDLHapticHandler_API26(); | |||
} else { | |||
mHapticHandler = new SDLHapticHandler(); | |||
} | |||
} | |||
} | |||
// Joystick glue code, just a series of stubs that redirect to the SDLJoystickHandler instance | |||
public static boolean handleJoystickMotionEvent(MotionEvent event) { | |||
return mJoystickHandler.handleMotionEvent(event); | |||
} | |||
/** | |||
* This method is called by SDL using JNI. | |||
*/ | |||
public static void pollInputDevices() { | |||
mJoystickHandler.pollInputDevices(); | |||
} | |||
/** | |||
* This method is called by SDL using JNI. | |||
*/ | |||
public static void pollHapticDevices() { | |||
mHapticHandler.pollHapticDevices(); | |||
} | |||
/** | |||
* This method is called by SDL using JNI. | |||
*/ | |||
public static void hapticRun(int device_id, float intensity, int length) { | |||
mHapticHandler.run(device_id, intensity, length); | |||
} | |||
/** | |||
* This method is called by SDL using JNI. | |||
*/ | |||
public static void hapticStop(int device_id) | |||
{ | |||
mHapticHandler.stop(device_id); | |||
} | |||
// Check if a given device is considered a possible SDL joystick | |||
public static boolean isDeviceSDLJoystick(int deviceId) { | |||
InputDevice device = InputDevice.getDevice(deviceId); | |||
// We cannot use InputDevice.isVirtual before API 16, so let's accept | |||
// only nonnegative device ids (VIRTUAL_KEYBOARD equals -1) | |||
if ((device == null) || (deviceId < 0)) { | |||
return false; | |||
} | |||
int sources = device.getSources(); | |||
/* This is called for every button press, so let's not spam the logs */ | |||
/** | |||
if ((sources & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) { | |||
Log.v(TAG, "Input device " + device.getName() + " has class joystick."); | |||
} | |||
if ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) { | |||
Log.v(TAG, "Input device " + device.getName() + " is a dpad."); | |||
} | |||
if ((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) { | |||
Log.v(TAG, "Input device " + device.getName() + " is a gamepad."); | |||
} | |||
**/ | |||
return ((sources & InputDevice.SOURCE_CLASS_JOYSTICK) != 0 || | |||
((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) || | |||
((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) | |||
); | |||
} | |||
} | |||
class SDLJoystickHandler { | |||
/** | |||
* Handles given MotionEvent. | |||
* @param event the event to be handled. | |||
* @return if given event was processed. | |||
*/ | |||
public boolean handleMotionEvent(MotionEvent event) { | |||
return false; | |||
} | |||
/** | |||
* Handles adding and removing of input devices. | |||
*/ | |||
public void pollInputDevices() { | |||
} | |||
} | |||
/* Actual joystick functionality available for API >= 12 devices */ | |||
class SDLJoystickHandler_API16 extends SDLJoystickHandler { | |||
static class SDLJoystick { | |||
public int device_id; | |||
public String name; | |||
public String desc; | |||
public ArrayList<InputDevice.MotionRange> axes; | |||
public ArrayList<InputDevice.MotionRange> hats; | |||
} | |||
static class RangeComparator implements Comparator<InputDevice.MotionRange> { | |||
@Override | |||
public int compare(InputDevice.MotionRange arg0, InputDevice.MotionRange arg1) { | |||
// Some controllers, like the Moga Pro 2, return AXIS_GAS (22) for right trigger and AXIS_BRAKE (23) for left trigger - swap them so they're sorted in the right order for SDL | |||
int arg0Axis = arg0.getAxis(); | |||
int arg1Axis = arg1.getAxis(); | |||
if (arg0Axis == MotionEvent.AXIS_GAS) { | |||
arg0Axis = MotionEvent.AXIS_BRAKE; | |||
} else if (arg0Axis == MotionEvent.AXIS_BRAKE) { | |||
arg0Axis = MotionEvent.AXIS_GAS; | |||
} | |||
if (arg1Axis == MotionEvent.AXIS_GAS) { | |||
arg1Axis = MotionEvent.AXIS_BRAKE; | |||
} else if (arg1Axis == MotionEvent.AXIS_BRAKE) { | |||
arg1Axis = MotionEvent.AXIS_GAS; | |||
} | |||
return arg0Axis - arg1Axis; | |||
} | |||
} | |||
private ArrayList<SDLJoystick> mJoysticks; | |||
public SDLJoystickHandler_API16() { | |||
mJoysticks = new ArrayList<SDLJoystick>(); | |||
} | |||
@Override | |||
public void pollInputDevices() { | |||
int[] deviceIds = InputDevice.getDeviceIds(); | |||
for(int i=0; i < deviceIds.length; ++i) { | |||
SDLJoystick joystick = getJoystick(deviceIds[i]); | |||
if (joystick == null) { | |||
joystick = new SDLJoystick(); | |||
InputDevice joystickDevice = InputDevice.getDevice(deviceIds[i]); | |||
if (SDLControllerManager.isDeviceSDLJoystick(deviceIds[i])) { | |||
joystick.device_id = deviceIds[i]; | |||
joystick.name = joystickDevice.getName(); | |||
joystick.desc = getJoystickDescriptor(joystickDevice); | |||
joystick.axes = new ArrayList<InputDevice.MotionRange>(); | |||
joystick.hats = new ArrayList<InputDevice.MotionRange>(); | |||
List<InputDevice.MotionRange> ranges = joystickDevice.getMotionRanges(); | |||
Collections.sort(ranges, new RangeComparator()); | |||
for (InputDevice.MotionRange range : ranges ) { | |||
if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) { | |||
if (range.getAxis() == MotionEvent.AXIS_HAT_X || | |||
range.getAxis() == MotionEvent.AXIS_HAT_Y) { | |||
joystick.hats.add(range); | |||
} | |||
else { | |||
joystick.axes.add(range); | |||
} | |||
} | |||
} | |||
mJoysticks.add(joystick); | |||
SDLControllerManager.nativeAddJoystick(joystick.device_id, joystick.name, joystick.desc, getVendorId(joystickDevice), getProductId(joystickDevice), false, getButtonMask(joystickDevice), joystick.axes.size(), joystick.hats.size()/2, 0); | |||
} | |||
} | |||
} | |||
/* Check removed devices */ | |||
ArrayList<Integer> removedDevices = new ArrayList<Integer>(); | |||
for(int i=0; i < mJoysticks.size(); i++) { | |||
int device_id = mJoysticks.get(i).device_id; | |||
int j; | |||
for (j=0; j < deviceIds.length; j++) { | |||
if (device_id == deviceIds[j]) break; | |||
} | |||
if (j == deviceIds.length) { | |||
removedDevices.add(Integer.valueOf(device_id)); | |||
} | |||
} | |||
for(int i=0; i < removedDevices.size(); i++) { | |||
int device_id = removedDevices.get(i).intValue(); | |||
SDLControllerManager.nativeRemoveJoystick(device_id); | |||
for (int j=0; j < mJoysticks.size(); j++) { | |||
if (mJoysticks.get(j).device_id == device_id) { | |||
mJoysticks.remove(j); | |||
break; | |||
} | |||
} | |||
} | |||
} | |||
protected SDLJoystick getJoystick(int device_id) { | |||
for(int i=0; i < mJoysticks.size(); i++) { | |||
if (mJoysticks.get(i).device_id == device_id) { | |||
return mJoysticks.get(i); | |||
} | |||
} | |||
return null; | |||
} | |||
@Override | |||
public boolean handleMotionEvent(MotionEvent event) { | |||
if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) != 0) { | |||
int actionPointerIndex = event.getActionIndex(); | |||
int action = event.getActionMasked(); | |||
switch(action) { | |||
case MotionEvent.ACTION_MOVE: | |||
SDLJoystick joystick = getJoystick(event.getDeviceId()); | |||
if ( joystick != null ) { | |||
for (int i = 0; i < joystick.axes.size(); i++) { | |||
InputDevice.MotionRange range = joystick.axes.get(i); | |||
/* Normalize the value to -1...1 */ | |||
float value = ( event.getAxisValue( range.getAxis(), actionPointerIndex) - range.getMin() ) / range.getRange() * 2.0f - 1.0f; | |||
SDLControllerManager.onNativeJoy(joystick.device_id, i, value ); | |||
} | |||
for (int i = 0; i < joystick.hats.size(); i+=2) { | |||
int hatX = Math.round(event.getAxisValue( joystick.hats.get(i).getAxis(), actionPointerIndex ) ); | |||
int hatY = Math.round(event.getAxisValue( joystick.hats.get(i+1).getAxis(), actionPointerIndex ) ); | |||
SDLControllerManager.onNativeHat(joystick.device_id, i/2, hatX, hatY ); | |||
} | |||
} | |||
break; | |||
default: | |||
break; | |||
} | |||
} | |||
return true; | |||
} | |||
public String getJoystickDescriptor(InputDevice joystickDevice) { | |||
String desc = joystickDevice.getDescriptor(); | |||
if (desc != null && !desc.isEmpty()) { | |||
return desc; | |||
} | |||
return joystickDevice.getName(); | |||
} | |||
public int getProductId(InputDevice joystickDevice) { | |||
return 0; | |||
} | |||
public int getVendorId(InputDevice joystickDevice) { | |||
return 0; | |||
} | |||
public int getButtonMask(InputDevice joystickDevice) { | |||
return -1; | |||
} | |||
} | |||
class SDLJoystickHandler_API19 extends SDLJoystickHandler_API16 { | |||
@Override | |||
public int getProductId(InputDevice joystickDevice) { | |||
return joystickDevice.getProductId(); | |||
} | |||
@Override | |||
public int getVendorId(InputDevice joystickDevice) { | |||
return joystickDevice.getVendorId(); | |||
} | |||
@Override | |||
public int getButtonMask(InputDevice joystickDevice) { | |||
int button_mask = 0; | |||
int[] keys = new int[] { | |||
KeyEvent.KEYCODE_BUTTON_A, | |||
KeyEvent.KEYCODE_BUTTON_B, | |||
KeyEvent.KEYCODE_BUTTON_X, | |||
KeyEvent.KEYCODE_BUTTON_Y, | |||
KeyEvent.KEYCODE_BACK, | |||
KeyEvent.KEYCODE_BUTTON_MODE, | |||
KeyEvent.KEYCODE_BUTTON_START, | |||
KeyEvent.KEYCODE_BUTTON_THUMBL, | |||
KeyEvent.KEYCODE_BUTTON_THUMBR, | |||
KeyEvent.KEYCODE_BUTTON_L1, | |||
KeyEvent.KEYCODE_BUTTON_R1, | |||
KeyEvent.KEYCODE_DPAD_UP, | |||
KeyEvent.KEYCODE_DPAD_DOWN, | |||
KeyEvent.KEYCODE_DPAD_LEFT, | |||
KeyEvent.KEYCODE_DPAD_RIGHT, | |||
KeyEvent.KEYCODE_BUTTON_SELECT, | |||
KeyEvent.KEYCODE_DPAD_CENTER, | |||
// These don't map into any SDL controller buttons directly | |||
KeyEvent.KEYCODE_BUTTON_L2, | |||
KeyEvent.KEYCODE_BUTTON_R2, | |||
KeyEvent.KEYCODE_BUTTON_C, | |||
KeyEvent.KEYCODE_BUTTON_Z, | |||
KeyEvent.KEYCODE_BUTTON_1, | |||
KeyEvent.KEYCODE_BUTTON_2, | |||
KeyEvent.KEYCODE_BUTTON_3, | |||
KeyEvent.KEYCODE_BUTTON_4, | |||
KeyEvent.KEYCODE_BUTTON_5, | |||
KeyEvent.KEYCODE_BUTTON_6, | |||
KeyEvent.KEYCODE_BUTTON_7, | |||
KeyEvent.KEYCODE_BUTTON_8, | |||
KeyEvent.KEYCODE_BUTTON_9, | |||
KeyEvent.KEYCODE_BUTTON_10, | |||
KeyEvent.KEYCODE_BUTTON_11, | |||
KeyEvent.KEYCODE_BUTTON_12, | |||
KeyEvent.KEYCODE_BUTTON_13, | |||
KeyEvent.KEYCODE_BUTTON_14, | |||
KeyEvent.KEYCODE_BUTTON_15, | |||
KeyEvent.KEYCODE_BUTTON_16, | |||
}; | |||
int[] masks = new int[] { | |||
(1 << 0), // A -> A | |||
(1 << 1), // B -> B | |||
(1 << 2), // X -> X | |||
(1 << 3), // Y -> Y | |||
(1 << 4), // BACK -> BACK | |||
(1 << 5), // MODE -> GUIDE | |||
(1 << 6), // START -> START | |||
(1 << 7), // THUMBL -> LEFTSTICK | |||
(1 << 8), // THUMBR -> RIGHTSTICK | |||
(1 << 9), // L1 -> LEFTSHOULDER | |||
(1 << 10), // R1 -> RIGHTSHOULDER | |||
(1 << 11), // DPAD_UP -> DPAD_UP | |||
(1 << 12), // DPAD_DOWN -> DPAD_DOWN | |||
(1 << 13), // DPAD_LEFT -> DPAD_LEFT | |||
(1 << 14), // DPAD_RIGHT -> DPAD_RIGHT | |||
(1 << 4), // SELECT -> BACK | |||
(1 << 0), // DPAD_CENTER -> A | |||
(1 << 15), // L2 -> ?? | |||
(1 << 16), // R2 -> ?? | |||
(1 << 17), // C -> ?? | |||
(1 << 18), // Z -> ?? | |||
(1 << 20), // 1 -> ?? | |||
(1 << 21), // 2 -> ?? | |||
(1 << 22), // 3 -> ?? | |||
(1 << 23), // 4 -> ?? | |||
(1 << 24), // 5 -> ?? | |||
(1 << 25), // 6 -> ?? | |||
(1 << 26), // 7 -> ?? | |||
(1 << 27), // 8 -> ?? | |||
(1 << 28), // 9 -> ?? | |||
(1 << 29), // 10 -> ?? | |||
(1 << 30), // 11 -> ?? | |||
(1 << 31), // 12 -> ?? | |||
// We're out of room... | |||
0xFFFFFFFF, // 13 -> ?? | |||
0xFFFFFFFF, // 14 -> ?? | |||
0xFFFFFFFF, // 15 -> ?? | |||
0xFFFFFFFF, // 16 -> ?? | |||
}; | |||
boolean[] has_keys = joystickDevice.hasKeys(keys); | |||
for (int i = 0; i < keys.length; ++i) { | |||
if (has_keys[i]) { | |||
button_mask |= masks[i]; | |||
} | |||
} | |||
return button_mask; | |||
} | |||
} | |||
class SDLHapticHandler_API26 extends SDLHapticHandler { | |||
@Override | |||
public void run(int device_id, float intensity, int length) { | |||
SDLHaptic haptic = getHaptic(device_id); | |||
if (haptic != null) { | |||
Log.d("SDL", "Rtest: Vibe with intensity " + intensity + " for " + length); | |||
if (intensity == 0.0f) { | |||
stop(device_id); | |||
return; | |||
} | |||
int vibeValue = Math.round(intensity * 255); | |||
if (vibeValue > 255) { | |||
vibeValue = 255; | |||
} | |||
if (vibeValue < 1) { | |||
stop(device_id); | |||
return; | |||
} | |||
try { | |||
haptic.vib.vibrate(VibrationEffect.createOneShot(length, vibeValue)); | |||
} | |||
catch (Exception e) { | |||
// Fall back to the generic method, which uses DEFAULT_AMPLITUDE, but works even if | |||
// something went horribly wrong with the Android 8.0 APIs. | |||
haptic.vib.vibrate(length); | |||
} | |||
} | |||
} | |||
} | |||
class SDLHapticHandler { | |||
class SDLHaptic { | |||
public int device_id; | |||
public String name; | |||
public Vibrator vib; | |||
} | |||
private ArrayList<SDLHaptic> mHaptics; | |||
public SDLHapticHandler() { | |||
mHaptics = new ArrayList<SDLHaptic>(); | |||
} | |||
public void run(int device_id, float intensity, int length) { | |||
SDLHaptic haptic = getHaptic(device_id); | |||
if (haptic != null) { | |||
haptic.vib.vibrate(length); | |||
} | |||
} | |||
public void stop(int device_id) { | |||
SDLHaptic haptic = getHaptic(device_id); | |||
if (haptic != null) { | |||
haptic.vib.cancel(); | |||
} | |||
} | |||
public void pollHapticDevices() { | |||
final int deviceId_VIBRATOR_SERVICE = 999999; | |||
boolean hasVibratorService = false; | |||
int[] deviceIds = InputDevice.getDeviceIds(); | |||
// It helps processing the device ids in reverse order | |||
// For example, in the case of the XBox 360 wireless dongle, | |||
// so the first controller seen by SDL matches what the receiver | |||
// considers to be the first controller | |||
for (int i = deviceIds.length - 1; i > -1; i--) { | |||
SDLHaptic haptic = getHaptic(deviceIds[i]); | |||
if (haptic == null) { | |||
InputDevice device = InputDevice.getDevice(deviceIds[i]); | |||
Vibrator vib = device.getVibrator(); | |||
if (vib.hasVibrator()) { | |||
haptic = new SDLHaptic(); | |||
haptic.device_id = deviceIds[i]; | |||
haptic.name = device.getName(); | |||
haptic.vib = vib; | |||
mHaptics.add(haptic); | |||
SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name); | |||
} | |||
} | |||
} | |||
/* Check VIBRATOR_SERVICE */ | |||
Vibrator vib = (Vibrator) SDL.getContext().getSystemService(Context.VIBRATOR_SERVICE); | |||
if (vib != null) { | |||
hasVibratorService = vib.hasVibrator(); | |||
if (hasVibratorService) { | |||
SDLHaptic haptic = getHaptic(deviceId_VIBRATOR_SERVICE); | |||
if (haptic == null) { | |||
haptic = new SDLHaptic(); | |||
haptic.device_id = deviceId_VIBRATOR_SERVICE; | |||
haptic.name = "VIBRATOR_SERVICE"; | |||
haptic.vib = vib; | |||
mHaptics.add(haptic); | |||
SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name); | |||
} | |||
} | |||
} | |||
/* Check removed devices */ | |||
ArrayList<Integer> removedDevices = new ArrayList<Integer>(); | |||
for(int i=0; i < mHaptics.size(); i++) { | |||
int device_id = mHaptics.get(i).device_id; | |||
int j; | |||
for (j=0; j < deviceIds.length; j++) { | |||
if (device_id == deviceIds[j]) break; | |||
} | |||
if (device_id == deviceId_VIBRATOR_SERVICE && hasVibratorService) { | |||
// don't remove the vibrator if it is still present | |||
} else if (j == deviceIds.length) { | |||
removedDevices.add(device_id); | |||
} | |||
} | |||
for(int i=0; i < removedDevices.size(); i++) { | |||
int device_id = removedDevices.get(i); | |||
SDLControllerManager.nativeRemoveHaptic(device_id); | |||
for (int j=0; j < mHaptics.size(); j++) { | |||
if (mHaptics.get(j).device_id == device_id) { | |||
mHaptics.remove(j); | |||
break; | |||
} | |||
} | |||
} | |||
} | |||
protected SDLHaptic getHaptic(int device_id) { | |||
for(int i=0; i < mHaptics.size(); i++) { | |||
if (mHaptics.get(i).device_id == device_id) { | |||
return mHaptics.get(i); | |||
} | |||
} | |||
return null; | |||
} | |||
} | |||
class SDLGenericMotionListener_API12 implements View.OnGenericMotionListener { | |||
// Generic Motion (mouse hover, joystick...) events go here | |||
@Override | |||
public boolean onGenericMotion(View v, MotionEvent event) { | |||
float x, y; | |||
int action; | |||
switch ( event.getSource() ) { | |||
case InputDevice.SOURCE_JOYSTICK: | |||
case InputDevice.SOURCE_GAMEPAD: | |||
case InputDevice.SOURCE_DPAD: | |||
return SDLControllerManager.handleJoystickMotionEvent(event); | |||
case InputDevice.SOURCE_MOUSE: | |||
action = event.getActionMasked(); | |||
switch (action) { | |||
case MotionEvent.ACTION_SCROLL: | |||
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0); | |||
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0); | |||
SDLActivity.onNativeMouse(0, action, x, y, false); | |||
return true; | |||
case MotionEvent.ACTION_HOVER_MOVE: | |||
x = event.getX(0); | |||
y = event.getY(0); | |||
SDLActivity.onNativeMouse(0, action, x, y, false); | |||
return true; | |||
default: | |||
break; | |||
} | |||
break; | |||
default: | |||
break; | |||
} | |||
// Event was not managed | |||
return false; | |||
} | |||
public boolean supportsRelativeMouse() { | |||
return false; | |||
} | |||
public boolean inRelativeMode() { | |||
return false; | |||
} | |||
public boolean setRelativeMouseEnabled(boolean enabled) { | |||
return false; | |||
} | |||
public void reclaimRelativeMouseModeIfNeeded() | |||
{ | |||
} | |||
public float getEventX(MotionEvent event) { | |||
return event.getX(0); | |||
} | |||
public float getEventY(MotionEvent event) { | |||
return event.getY(0); | |||
} | |||
} | |||
class SDLGenericMotionListener_API24 extends SDLGenericMotionListener_API12 { | |||
// Generic Motion (mouse hover, joystick...) events go here | |||
private boolean mRelativeModeEnabled; | |||
@Override | |||
public boolean onGenericMotion(View v, MotionEvent event) { | |||
// Handle relative mouse mode | |||
if (mRelativeModeEnabled) { | |||
if (event.getSource() == InputDevice.SOURCE_MOUSE) { | |||
int action = event.getActionMasked(); | |||
if (action == MotionEvent.ACTION_HOVER_MOVE) { | |||
float x = event.getAxisValue(MotionEvent.AXIS_RELATIVE_X); | |||
float y = event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y); | |||
SDLActivity.onNativeMouse(0, action, x, y, true); | |||
return true; | |||
} | |||
} | |||
} | |||
// Event was not managed, call SDLGenericMotionListener_API12 method | |||
return super.onGenericMotion(v, event); | |||
} | |||
@Override | |||
public boolean supportsRelativeMouse() { | |||
return true; | |||
} | |||
@Override | |||
public boolean inRelativeMode() { | |||
return mRelativeModeEnabled; | |||
} | |||
@Override | |||
public boolean setRelativeMouseEnabled(boolean enabled) { | |||
mRelativeModeEnabled = enabled; | |||
return true; | |||
} | |||
@Override | |||
public float getEventX(MotionEvent event) { | |||
if (mRelativeModeEnabled) { | |||
return event.getAxisValue(MotionEvent.AXIS_RELATIVE_X); | |||
} | |||
else { | |||
return event.getX(0); | |||
} | |||
} | |||
@Override | |||
public float getEventY(MotionEvent event) { | |||
if (mRelativeModeEnabled) { | |||
return event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y); | |||
} | |||
else { | |||
return event.getY(0); | |||
} | |||
} | |||
} | |||
class SDLGenericMotionListener_API26 extends SDLGenericMotionListener_API24 { | |||
// Generic Motion (mouse hover, joystick...) events go here | |||
private boolean mRelativeModeEnabled; | |||
@Override | |||
public boolean onGenericMotion(View v, MotionEvent event) { | |||
float x, y; | |||
int action; | |||
switch ( event.getSource() ) { | |||
case InputDevice.SOURCE_JOYSTICK: | |||
case InputDevice.SOURCE_GAMEPAD: | |||
case InputDevice.SOURCE_DPAD: | |||
return SDLControllerManager.handleJoystickMotionEvent(event); | |||
case InputDevice.SOURCE_MOUSE: | |||
// DeX desktop mouse cursor is a separate non-standard input type. | |||
case InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_TOUCHSCREEN: | |||
action = event.getActionMasked(); | |||
switch (action) { | |||
case MotionEvent.ACTION_SCROLL: | |||
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0); | |||
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0); | |||
SDLActivity.onNativeMouse(0, action, x, y, false); | |||
return true; | |||
case MotionEvent.ACTION_HOVER_MOVE: | |||
x = event.getX(0); | |||
y = event.getY(0); | |||
SDLActivity.onNativeMouse(0, action, x, y, false); | |||
return true; | |||
default: | |||
break; | |||
} | |||
break; | |||
case InputDevice.SOURCE_MOUSE_RELATIVE: | |||
action = event.getActionMasked(); | |||
switch (action) { | |||
case MotionEvent.ACTION_SCROLL: | |||
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0); | |||
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0); | |||
SDLActivity.onNativeMouse(0, action, x, y, false); | |||
return true; | |||
case MotionEvent.ACTION_HOVER_MOVE: | |||
x = event.getX(0); | |||
y = event.getY(0); | |||
SDLActivity.onNativeMouse(0, action, x, y, true); | |||
return true; | |||
default: | |||
break; | |||
} | |||
break; | |||
default: | |||
break; | |||
} | |||
// Event was not managed | |||
return false; | |||
} | |||
@Override | |||
public boolean supportsRelativeMouse() { | |||
return (!SDLActivity.isDeXMode() || (Build.VERSION.SDK_INT >= 27)); | |||
} | |||
@Override | |||
public boolean inRelativeMode() { | |||
return mRelativeModeEnabled; | |||
} | |||
@Override | |||
public boolean setRelativeMouseEnabled(boolean enabled) { | |||
if (!SDLActivity.isDeXMode() || (Build.VERSION.SDK_INT >= 27)) { | |||
if (enabled) { | |||
SDLActivity.getContentView().requestPointerCapture(); | |||
} | |||
else { | |||
SDLActivity.getContentView().releasePointerCapture(); | |||
} | |||
mRelativeModeEnabled = enabled; | |||
return true; | |||
} | |||
else | |||
{ | |||
return false; | |||
} | |||
} | |||
@Override | |||
public void reclaimRelativeMouseModeIfNeeded() | |||
{ | |||
if (mRelativeModeEnabled && !SDLActivity.isDeXMode()) { | |||
SDLActivity.getContentView().requestPointerCapture(); | |||
} | |||
} | |||
@Override | |||
public float getEventX(MotionEvent event) { | |||
// Relative mouse in capture mode will only have relative for X/Y | |||
return event.getX(0); | |||
} | |||
@Override | |||
public float getEventY(MotionEvent event) { | |||
// Relative mouse in capture mode will only have relative for X/Y | |||
return event.getY(0); | |||
} | |||
} |
@ -0,0 +1,20 @@ | |||
cmake_minimum_required(VERSION 3.6) | |||
project(GAME) | |||
# armeabi-v7a requires cpufeatures library | |||
# include(AndroidNdkModules) | |||
# android_ndk_import_module_cpufeatures() | |||
# SDL sources are in a subfolder named "SDL" | |||
add_subdirectory(SDL) | |||
# Compilation of companion libraries | |||
#add_subdirectory(SDL_image) | |||
#add_subdirectory(SDL_mixer) | |||
#add_subdirectory(SDL_ttf) | |||
# Your game and its CMakeLists.txt are in a subfolder named "src" | |||
add_subdirectory(src) | |||
@ -0,0 +1,13 @@ | |||
cmake_minimum_required(VERSION 3.6) | |||
project(MY_APP) | |||
find_library(SDL2 SDL2) | |||
add_library(main SHARED) | |||
target_sources(main PRIVATE YourSourceHere.c) | |||
target_link_libraries(main SDL2) | |||
@ -0,0 +1,78 @@ | |||
# This file was generated by Autom4te Thu Apr 10 10:06:43 UTC 2014. | |||
# It contains the lists of macros which have been traced. | |||
# It can be safely removed. | |||
@request = ( | |||
bless( [ | |||
'0', | |||
1, | |||
[ | |||
'/usr/share/autoconf' | |||
], | |||
[ | |||
'/usr/share/autoconf/autoconf/autoconf.m4f', | |||
'aclocal.m4', | |||
'configure.in' | |||
], | |||
{ | |||
'AC_DEFINE_TRACE_LITERAL' => 1, | |||
'AM_PROG_CC_C_O' => 1, | |||
'AC_CANONICAL_TARGET' => 1, | |||
'AM_PROG_MOC' => 1, | |||
'AM_PROG_CXX_C_O' => 1, | |||
'LT_CONFIG_LTDL_DIR' => 1, | |||
'_m4_warn' => 1, | |||
'AC_FC_PP_SRCEXT' => 1, | |||
'LT_SUPPORTED_TAG' => 1, | |||
'AM_PATH_GUILE' => 1, | |||
'AC_CONFIG_SUBDIRS' => 1, | |||
'AM_MAKEFILE_INCLUDE' => 1, | |||
'AH_OUTPUT' => 1, | |||
'm4_pattern_allow' => 1, | |||
'_AM_SUBST_NOTMAKE' => 1, | |||
'AC_INIT' => 1, | |||
'_AM_COND_ENDIF' => 1, | |||
'AM_AUTOMAKE_VERSION' => 1, | |||
'AM_NLS' => 1, | |||
'm4_sinclude' => 1, | |||
'AC_FC_SRCEXT' => 1, | |||
'_LT_AC_TAGCONFIG' => 1, | |||
'AC_FC_FREEFORM' => 1, | |||
'AC_CANONICAL_BUILD' => 1, | |||
'AM_SILENT_RULES' => 1, | |||
'AM_PROG_AR' => 1, | |||
'AC_SUBST' => 1, | |||
'AC_CONFIG_AUX_DIR' => 1, | |||
'AM_PROG_FC_C_O' => 1, | |||
'AM_CONDITIONAL' => 1, | |||
'AM_INIT_AUTOMAKE' => 1, | |||
'AC_CANONICAL_HOST' => 1, | |||
'AM_ENABLE_MULTILIB' => 1, | |||
'AC_PROG_LIBTOOL' => 1, | |||
'AC_REQUIRE_AUX_FILE' => 1, | |||
'AM_GNU_GETTEXT_INTL_SUBDIR' => 1, | |||
'AC_CONFIG_FILES' => 1, | |||
'AM_MAINTAINER_MODE' => 1, | |||
'_AM_COND_ELSE' => 1, | |||
'_AM_MAKEFILE_INCLUDE' => 1, | |||
'AC_FC_PP_DEFINE' => 1, | |||
'AC_CONFIG_HEADERS' => 1, | |||
'_AM_COND_IF' => 1, | |||
'sinclude' => 1, | |||
'm4_include' => 1, | |||
'm4_pattern_forbid' => 1, | |||
'AM_POT_TOOLS' => 1, | |||
'AC_CONFIG_LIBOBJ_DIR' => 1, | |||
'LT_INIT' => 1, | |||
'AM_XGETTEXT_OPTION' => 1, | |||
'AM_PROG_F77_C_O' => 1, | |||
'AM_GNU_GETTEXT' => 1, | |||
'AC_CONFIG_LINKS' => 1, | |||
'AC_LIBSOURCE' => 1, | |||
'include' => 1, | |||
'AC_SUBST_TRACE' => 1, | |||
'AC_CANONICAL_SYSTEM' => 1 | |||
} | |||
], 'Autom4te::Request' ) | |||
); | |||
@ -0,0 +1,25 @@ | |||
#!/bin/sh | |||
# | |||
# Print the current source revision, if available | |||
SDL_ROOT=$(dirname $0)/.. | |||
cd $SDL_ROOT | |||
if test -x "$(command -v hg)"; then | |||
rev="$(hg parents --template 'hg-{rev}:{node|short}' 2>/dev/null)" | |||
if test $? = 0; then | |||
echo $rev | |||
exit 0 | |||
fi | |||
fi | |||
if test -x "$(command -v p4)"; then | |||
rev="$(p4 changes -m1 ./...\#have 2>/dev/null| awk '{print $2}')" | |||
if test $? = 0; then | |||
echo $rev | |||
exit 0 | |||
fi | |||
fi | |||
echo "hg-0:baadf00d" | |||
exit 1 |