mirror of
https://github.com/elasota/Aerofoil.git
synced 2025-09-22 22:45:39 +00:00
Compare commits
273 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
032e44d981 | ||
|
7961ca3af7 | ||
|
6851025147 | ||
|
f0b1d6fff9 | ||
|
70e0948847 | ||
|
a698286087 | ||
|
b75313fd7b | ||
|
cab862ed8b | ||
|
553e343abe | ||
|
0aa36b27a9 | ||
|
f6185b1c78 | ||
|
ff29d5b92c | ||
|
964c9b8858 | ||
|
8a48726b2e | ||
|
de06669239 | ||
|
c0abd77dc4 | ||
|
754b988f09 | ||
|
1c57a51316 | ||
|
80abb498af | ||
|
29cc376438 | ||
|
ef12c471a7 | ||
|
8f4ecfafe1 | ||
|
3c3f9e3675 | ||
|
5c640b72eb | ||
|
9d0f2c35b3 | ||
|
7df624d9b1 | ||
|
29fbe83e8d | ||
|
ad3a878a16 | ||
|
f0e7379db6 | ||
|
e34fec38a2 | ||
|
76db422456 | ||
|
396d107608 | ||
|
30b39c6991 | ||
|
9dafba1092 | ||
|
801408077a | ||
|
2c073937c3 | ||
|
bbd147e1ab | ||
|
66a111dd23 | ||
|
2febed5d2a | ||
|
b47813330a | ||
|
0f630a74a2 | ||
|
edc43e0bed | ||
|
2aca0b6b28 | ||
|
f9a101486c | ||
|
dbf3303145 | ||
|
a28a4cd73d | ||
|
4c6e646133 | ||
|
a13f90bd71 | ||
|
3d0e457008 | ||
|
1bded36339 | ||
|
14b0afbdd2 | ||
|
6986dd5528 | ||
|
3a327a27e7 | ||
|
0d304e8a96 | ||
|
43cfb7ea6b | ||
|
3b5f222d98 | ||
|
02bccda8a0 | ||
|
e727e462d8 | ||
|
47e23fbc71 | ||
|
98c217d0bb | ||
|
3b91d0492e | ||
|
2b3a9f1669 | ||
|
0d5db76492 | ||
|
2ab1416eef | ||
|
c0f71ca1af | ||
|
64a2d712e3 | ||
|
10884f4c1a | ||
|
6d0d2f86d3 | ||
|
b11ff6fdd4 | ||
|
475b8d21fb | ||
|
47be9d73e3 | ||
|
2f32c4f434 | ||
|
232a98a380 | ||
|
b6cb8535a5 | ||
|
a07b7d41e4 | ||
|
96867a3ed8 | ||
|
4a4dcc995f | ||
|
f2052b835b | ||
|
b983d11009 | ||
|
b9c600860d | ||
|
5bdbcc9147 | ||
|
f9c794efee | ||
|
a774320324 | ||
|
71c152026c | ||
|
b50508f85f | ||
|
37fc11a023 | ||
|
eb1f59afbd | ||
|
47eaf75778 | ||
|
b9ea9450f1 | ||
|
4f70ce1c9f | ||
|
0452e61043 | ||
|
26c423bb58 | ||
|
daebba7d47 | ||
|
3f6f540bf5 | ||
|
36eb111d26 | ||
|
23b69cf0ee | ||
|
f26f631ae2 | ||
|
1b4754ae13 | ||
|
47b742ed45 | ||
|
c96a1ab251 | ||
|
1da35d45ae | ||
|
30b07a77a9 | ||
|
842f070ff5 | ||
|
0f1ed5d10a | ||
|
fbf73fd832 | ||
|
8031f66226 | ||
|
37499638fc | ||
|
7c025337dc | ||
|
10a5b85edb | ||
|
bece23dc8c | ||
|
babf7cce89 | ||
|
7aa45cc9d6 | ||
|
bfb2c727ea | ||
|
ad5a0795f4 | ||
|
febd0e05ac | ||
|
e1c83d7b47 | ||
|
1e1c91915a | ||
|
ad1912379a | ||
|
64ce75caa8 | ||
|
b682004f29 | ||
|
7858aff6cd | ||
|
3347e94b88 | ||
|
e4cddda183 | ||
|
62e234c777 | ||
|
780f4842b0 | ||
|
c13677122c | ||
|
14f7a5beed | ||
|
aeef313a8a | ||
|
aa2f6dfd2c | ||
|
b03561c76b | ||
|
dd269d52e0 | ||
|
1ecef6f8ef | ||
|
ec56bdace2 | ||
|
3b6ae1dba5 | ||
|
5fbf3f5df0 | ||
|
522e99afb9 | ||
|
184daefe7b | ||
|
5c98783bbb | ||
|
a2f19f5ccb | ||
|
3a736296ce | ||
|
9be2885a1e | ||
|
1b497cb1f7 | ||
|
15282f2ab2 | ||
|
4445ea5770 | ||
|
9cb60af2b3 | ||
|
2400d68aeb | ||
|
e88c9e7112 | ||
|
db54e92425 | ||
|
c3b1f45f96 | ||
|
45aa5b4cba | ||
|
da3cdb3a17 | ||
|
9669f6039d | ||
|
1fb135a6d2 | ||
|
b23bb93506 | ||
|
8518d01c70 | ||
|
1b215e891f | ||
|
987a1dea75 | ||
|
f07137b52d | ||
|
480a4b6098 | ||
|
5ab7a675a3 | ||
|
1d3c84a473 | ||
|
4a78f10975 | ||
|
69f279cc64 | ||
|
1efb34f710 | ||
|
84d5c46506 | ||
|
c95be907de | ||
|
3fd7fabb98 | ||
|
53ecdabb43 | ||
|
7bf6147fa1 | ||
|
a77e1d868f | ||
|
c14d904ca7 | ||
|
ec275fcefd | ||
|
550465088e | ||
|
68444a7240 | ||
|
7db0f8d7eb | ||
|
1fc846f7d8 | ||
|
d978267c3e | ||
|
e05f37a28d | ||
|
7069f4bf82 | ||
|
b197a9fb4b | ||
|
62427de092 | ||
|
037994d0f7 | ||
|
7cdfc40ad9 | ||
|
c53275d3fb | ||
|
8d58d22731 | ||
|
58ad634085 | ||
|
00ea1b2982 | ||
|
87bab26d47 | ||
|
3dbb231c5a | ||
|
a2e8d414e8 | ||
|
137f00b657 | ||
|
7fec622e51 | ||
|
05604e5604 | ||
|
ed9e0fec97 | ||
|
82b93c627e | ||
|
c204b5ef7f | ||
|
343a8a0f04 | ||
|
9d5adb9bb6 | ||
|
3bf689664f | ||
|
ccb7dd2edb | ||
|
20acb4ef6b | ||
|
3c662845b4 | ||
|
1981320afe | ||
|
1c15ea5940 | ||
|
4920781619 | ||
|
24f43b973a | ||
|
649d78a61b | ||
|
836fc95f4a | ||
|
5b95fd8c6b | ||
|
b55a508686 | ||
|
47291cbf1d | ||
|
01e6ff4f5d | ||
|
d828eacd38 | ||
|
b37b0a4f8a | ||
|
4c3ccbd7fa | ||
|
9624c283c8 | ||
|
cfb66d9c9b | ||
|
c185c8d9ec | ||
|
7636fd6fa8 | ||
|
6c48debecd | ||
|
3f55eedcf0 | ||
|
856c7d5297 | ||
|
d7301402c5 | ||
|
2a98bfbc8c | ||
|
eac923c475 | ||
|
6fe0f2d964 | ||
|
a4abb0d95f | ||
|
c856607f46 | ||
|
611f53ef91 | ||
|
98afd82d64 | ||
|
efae9cacd8 | ||
|
5184d1594f | ||
|
42e124a90c | ||
|
11628ddd93 | ||
|
7d5f844fd4 | ||
|
f5ff8eb013 | ||
|
482487d81c | ||
|
2d8b6a29aa | ||
|
8f433c11e8 | ||
|
9c32a6fdd4 | ||
|
bd9676be40 | ||
|
392c5d0583 | ||
|
14cc4b43df | ||
|
de342cb368 | ||
|
b68cfab6d8 | ||
|
5869571747 | ||
|
432cdbcc3a | ||
|
438e7b2138 | ||
|
a1c45d4fc8 | ||
|
66fc278ce9 | ||
|
f53dc21475 | ||
|
49c438b088 | ||
|
5e6ecaf0fa | ||
|
47e36f1c3c | ||
|
d7a769e397 | ||
|
1abb542301 | ||
|
afb9474340 | ||
|
50ab5f5bdb | ||
|
b12151f665 | ||
|
8135c68c49 | ||
|
5c07ce08bb | ||
|
ea16d0ffca | ||
|
f590613f83 | ||
|
c0878fe66d | ||
|
9c18a2ba55 | ||
|
0b8a5cb38c | ||
|
fde390ac73 | ||
|
153213e079 | ||
|
35308e41f3 | ||
|
931d7e0f30 | ||
|
539af1f9b5 | ||
|
e052628ed3 | ||
|
e52735ae7f |
6
.gitignore
vendored
6
.gitignore
vendored
@@ -28,5 +28,11 @@ InstallerPackages/*
|
||||
*.msi
|
||||
*.wixpdb
|
||||
*.wixobj
|
||||
*.CopyComplete
|
||||
*.lnk
|
||||
ReleasePackageInstaller/obj/*
|
||||
ReleasePackageInstaller/bin/*
|
||||
ReleasePackageInstaller/AerofoilPackageDefs.wxi
|
||||
ReleasePackageInstaller/AerofoilPackageVersion.wxi
|
||||
packages/*
|
||||
!SDL2-2.0.12/lib/x64/*
|
||||
|
@@ -1,30 +0,0 @@
|
||||
Open Sans font:
|
||||
(c)2011 Google
|
||||
Distributed under the Apache License (see Resources/Fonts/OpenSans/LICENSE)
|
||||
|
||||
|
||||
stb_image_write:
|
||||
Created by Sean Barrett
|
||||
|
||||
|
||||
MacRomanConversion uses a table from LIBICONV:
|
||||
Copyright (C) 1999-2001, 2016 Free Software Foundation, Inc.
|
||||
|
||||
Distributed under the LGPLv2 license (See MacRomanConversion/LICENSE.txt)
|
||||
|
||||
|
||||
RapidJSON:
|
||||
Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved.
|
||||
|
||||
Distributed under the MIT license (See rapidjson/license.txt)
|
||||
|
||||
|
||||
zlib:
|
||||
(C) 1995-2017 Jean-loup Gailly and Mark Adler
|
||||
Distributed under zlib license (See zlib/README)
|
||||
|
||||
|
||||
The Unarchiver:
|
||||
Copyright (C) 2006-2013 Dag Ågren
|
||||
|
||||
Distributed under the LGPLv2 license (See unpacktool/LICENSE.txt)
|
43
Aerofoil.sln
43
Aerofoil.sln
@@ -13,8 +13,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "GpApp", "GpApp\GpApp.vcxpro
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "hqx2gp", "hqx2gp\hqx2gp.vcxproj", "{5FDE4822-C771-46A5-B6B2-FD12BACE86BF}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PictChecker", "PictChecker\PictChecker.vcxproj", "{99549E56-2B3A-4B0C-9A1F-FBA6395BC96C}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "GpAudioDriver_XAudio2", "GpAudioDriver_XAudio2\GpAudioDriver_XAudio2.vcxproj", "{E3BDC783-8646-433E-ADF0-8B6390D36669}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FTagData", "FTagData\FTagData.vcxproj", "{A8FCDC5E-729C-4A80-BF9F-B669C52B2AE3}"
|
||||
@@ -46,6 +44,21 @@ EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "unpacktool", "unpacktool\unpacktool.vcxproj", "{A778D062-DE76-49F6-8D05-EB26852DD605}"
|
||||
EndProject
|
||||
Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "ReleasePackageInstaller", "ReleasePackageInstaller\ReleasePackageInstaller.wixproj", "{D26BD501-28A7-4849-8130-FB5EA0A2B82F}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{7EFF1E21-C375-45EA-A069-4E2232C8A72B} = {7EFF1E21-C375-45EA-A069-4E2232C8A72B}
|
||||
{B852D549-4020-4477-8BFB-E199FF78B047} = {B852D549-4020-4477-8BFB-E199FF78B047}
|
||||
{2FF15659-5C72-48B8-B55B-3C658E4125B5} = {2FF15659-5C72-48B8-B55B-3C658E4125B5}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WindowsUnicodeToolShim", "WindowsUnicodeToolShim\WindowsUnicodeToolShim.vcxproj", "{15009625-1120-405E-8BBA-69A16CD6713D}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EmitWiXVersion", "EmitWiXVersion\EmitWiXVersion.vcxproj", "{7EFF1E21-C375-45EA-A069-4E2232C8A72B}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "GpShell", "GpShell\GpShell.vcxproj", "{10CF9B5F-61D0-4B5B-89F4-810B58FC053D}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "GpFontHandler_FreeType2", "GpFontHandler_FreeType2\GpFontHandler_FreeType2.vcxproj", "{4B564030-8985-4975-91E1-E1B2C16AE2A1}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AerofoilSDL", "AerofoilSDL\AerofoilSDL.vcxproj", "{33542FF0-0473-4802-BC79-3B8261790F65}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@@ -73,10 +86,6 @@ Global
|
||||
{5FDE4822-C771-46A5-B6B2-FD12BACE86BF}.Debug|x64.Build.0 = Debug|x64
|
||||
{5FDE4822-C771-46A5-B6B2-FD12BACE86BF}.Release|x64.ActiveCfg = Release|x64
|
||||
{5FDE4822-C771-46A5-B6B2-FD12BACE86BF}.Release|x64.Build.0 = Release|x64
|
||||
{99549E56-2B3A-4B0C-9A1F-FBA6395BC96C}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{99549E56-2B3A-4B0C-9A1F-FBA6395BC96C}.Debug|x64.Build.0 = Debug|x64
|
||||
{99549E56-2B3A-4B0C-9A1F-FBA6395BC96C}.Release|x64.ActiveCfg = Release|x64
|
||||
{99549E56-2B3A-4B0C-9A1F-FBA6395BC96C}.Release|x64.Build.0 = Release|x64
|
||||
{E3BDC783-8646-433E-ADF0-8B6390D36669}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{E3BDC783-8646-433E-ADF0-8B6390D36669}.Debug|x64.Build.0 = Debug|x64
|
||||
{E3BDC783-8646-433E-ADF0-8B6390D36669}.Release|x64.ActiveCfg = Release|x64
|
||||
@@ -138,9 +147,27 @@ Global
|
||||
{A778D062-DE76-49F6-8D05-EB26852DD605}.Release|x64.ActiveCfg = Release|x64
|
||||
{A778D062-DE76-49F6-8D05-EB26852DD605}.Release|x64.Build.0 = Release|x64
|
||||
{D26BD501-28A7-4849-8130-FB5EA0A2B82F}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{D26BD501-28A7-4849-8130-FB5EA0A2B82F}.Debug|x64.Build.0 = Debug|x64
|
||||
{D26BD501-28A7-4849-8130-FB5EA0A2B82F}.Release|x64.ActiveCfg = Release|x64
|
||||
{D26BD501-28A7-4849-8130-FB5EA0A2B82F}.Release|x64.Build.0 = Release|x64
|
||||
{15009625-1120-405E-8BBA-69A16CD6713D}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{15009625-1120-405E-8BBA-69A16CD6713D}.Debug|x64.Build.0 = Debug|x64
|
||||
{15009625-1120-405E-8BBA-69A16CD6713D}.Release|x64.ActiveCfg = Release|x64
|
||||
{15009625-1120-405E-8BBA-69A16CD6713D}.Release|x64.Build.0 = Release|x64
|
||||
{7EFF1E21-C375-45EA-A069-4E2232C8A72B}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{7EFF1E21-C375-45EA-A069-4E2232C8A72B}.Debug|x64.Build.0 = Debug|x64
|
||||
{7EFF1E21-C375-45EA-A069-4E2232C8A72B}.Release|x64.ActiveCfg = Release|x64
|
||||
{7EFF1E21-C375-45EA-A069-4E2232C8A72B}.Release|x64.Build.0 = Release|x64
|
||||
{10CF9B5F-61D0-4B5B-89F4-810B58FC053D}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{10CF9B5F-61D0-4B5B-89F4-810B58FC053D}.Debug|x64.Build.0 = Debug|x64
|
||||
{10CF9B5F-61D0-4B5B-89F4-810B58FC053D}.Release|x64.ActiveCfg = Release|x64
|
||||
{10CF9B5F-61D0-4B5B-89F4-810B58FC053D}.Release|x64.Build.0 = Release|x64
|
||||
{4B564030-8985-4975-91E1-E1B2C16AE2A1}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{4B564030-8985-4975-91E1-E1B2C16AE2A1}.Debug|x64.Build.0 = Debug|x64
|
||||
{4B564030-8985-4975-91E1-E1B2C16AE2A1}.Release|x64.ActiveCfg = Release|x64
|
||||
{4B564030-8985-4975-91E1-E1B2C16AE2A1}.Release|x64.Build.0 = Release|x64
|
||||
{33542FF0-0473-4802-BC79-3B8261790F65}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{33542FF0-0473-4802-BC79-3B8261790F65}.Debug|x64.Build.0 = Debug|x64
|
||||
{33542FF0-0473-4802-BC79-3B8261790F65}.Release|x64.ActiveCfg = Release|x64
|
||||
{33542FF0-0473-4802-BC79-3B8261790F65}.Release|x64.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@@ -1,14 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="15.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="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
@@ -25,19 +17,6 @@
|
||||
<WindowsTargetPlatformVersion>10.0.17763.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<CharacterSet>MultiByte</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>MultiByte</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
@@ -56,33 +35,14 @@
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
<Import Project="..\PortabilityLayer.props" />
|
||||
<Import Project="..\Common.props" />
|
||||
<Import Project="..\GpCommon.props" />
|
||||
<Import Project="..\GpMainApp.props" />
|
||||
<Import Project="..\FreeTypePublic.props" />
|
||||
<Import Project="..\FreeTypeImport.props" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
<Import Project="..\PortabilityLayer.props" />
|
||||
<Import Project="..\Common.props" />
|
||||
<Import Project="..\GpCommon.props" />
|
||||
<Import Project="..\GpMainApp.props" />
|
||||
<Import Project="..\FreeTypePublic.props" />
|
||||
<Import Project="..\FreeTypeImport.props" />
|
||||
<Import Project="..\Release.props" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
<Import Project="..\PortabilityLayer.props" />
|
||||
<Import Project="..\Common.props" />
|
||||
<Import Project="..\GpCommon.props" />
|
||||
<Import Project="..\GpMainApp.props" />
|
||||
<Import Project="..\FreeTypePublic.props" />
|
||||
<Import Project="..\FreeTypeImport.props" />
|
||||
<Import Project="..\GpShell.props" />
|
||||
<Import Project="..\Debug.props" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
@@ -90,9 +50,8 @@
|
||||
<Import Project="..\Common.props" />
|
||||
<Import Project="..\GpCommon.props" />
|
||||
<Import Project="..\GpMainApp.props" />
|
||||
<Import Project="..\FreeTypePublic.props" />
|
||||
<Import Project="..\FreeTypeImport.props" />
|
||||
<Import Project="..\Release.props" />
|
||||
<Import Project="..\GpShell.props" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup />
|
||||
@@ -107,28 +66,6 @@
|
||||
<AdditionalDependencies>shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
@@ -145,68 +82,42 @@
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="GpAppEnvironment.cpp" />
|
||||
<ClCompile Include="GpAudioDriverFactory.cpp" />
|
||||
<ClCompile Include="GpBWCursor_Win32.cpp" />
|
||||
<ClCompile Include="GpColorCursor_Win32.cpp" />
|
||||
<ClCompile Include="GpDisplayDriverFactory.cpp" />
|
||||
<ClCompile Include="GpFiber_Win32.cpp" />
|
||||
<ClCompile Include="GpFileStream_Win32.cpp" />
|
||||
<ClCompile Include="GpFileSystem_Win32.cpp" />
|
||||
<ClCompile Include="GpFontHandlerFactory.cpp" />
|
||||
<ClCompile Include="GpFontHandler_FreeType2.cpp" />
|
||||
<ClCompile Include="GpGlobalConfig.cpp" />
|
||||
<ClCompile Include="GpInputDriverFactory.cpp" />
|
||||
<ClCompile Include="GpMain.cpp" />
|
||||
<ClCompile Include="GpLogDriver_Win32.cpp" />
|
||||
<ClCompile Include="GpMain_Win32.cpp" />
|
||||
<ClCompile Include="GpMemoryBuffer.cpp" />
|
||||
<ClCompile Include="GpMutex_Win32.cpp" />
|
||||
<ClCompile Include="GpSystemServices_Win32.cpp" />
|
||||
<ClCompile Include="GpFiberStarter_Win32.cpp" />
|
||||
<ClCompile Include="GpThreadEvent_Win32.cpp" />
|
||||
<ClCompile Include="GpVOSEventQueue.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\GpCommon\EGpInputDriverType.h" />
|
||||
<ClInclude Include="..\GpCommon\EGpStandardCursor.h" />
|
||||
<ClInclude Include="..\GpCommon\GpApplicationName.h" />
|
||||
<ClInclude Include="..\GpCommon\GpBuildVersion.h" />
|
||||
<ClInclude Include="..\GpCommon\GpDisplayDriverTickStatus.h" />
|
||||
<ClInclude Include="..\GpCommon\GpFileCreationDisposition.h" />
|
||||
<ClInclude Include="..\GpCommon\GpInputDriverProperties.h" />
|
||||
<ClInclude Include="..\GpCommon\IGpCursor.h" />
|
||||
<ClInclude Include="..\GpCommon\IGpAudioChannelCallbacks.h" />
|
||||
<ClInclude Include="..\GpCommon\GpColorCursor_Win32.h" />
|
||||
<ClInclude Include="..\GpCommon\IGpDisplayDriverSurface.h" />
|
||||
<ClInclude Include="GpAppEnvironment.h" />
|
||||
<ClInclude Include="GpAudioDriverFactory.h" />
|
||||
<ClInclude Include="GpComPtr.h" />
|
||||
<ClInclude Include="GpCoreDefs.h" />
|
||||
<ClInclude Include="GpDisplayDriverFactory.h" />
|
||||
<ClInclude Include="GpFiber.h" />
|
||||
<ClInclude Include="..\GpCommon\IGpLogDriver.h" />
|
||||
<ClInclude Include="..\GpCommon\IGpPrefsHandler.h" />
|
||||
<ClInclude Include="GpBWCursor_Win32.h" />
|
||||
<ClInclude Include="GpFiber_Win32.h" />
|
||||
<ClInclude Include="GpFileStream_Win32.h" />
|
||||
<ClInclude Include="GpFileSystem_Win32.h" />
|
||||
<ClInclude Include="GpFontHandler_FreeType2.h" />
|
||||
<ClInclude Include="GpFontHandlerFactory.h" />
|
||||
<ClInclude Include="GpGlobalConfig.h" />
|
||||
<ClInclude Include="GpInputDriverFactory.h" />
|
||||
<ClInclude Include="GpMain.h" />
|
||||
<ClInclude Include="GpMemoryBuffer.h" />
|
||||
<ClInclude Include="GpLogDriver_Win32.h" />
|
||||
<ClInclude Include="GpMutex_Win32.h" />
|
||||
<ClInclude Include="GpRingBuffer.h" />
|
||||
<ClInclude Include="GpSystemServices_Win32.h" />
|
||||
<ClInclude Include="GpFiberStarter.h" />
|
||||
<ClInclude Include="GpThreadEvent_Win32.h" />
|
||||
<ClInclude Include="GpVOSEventQueue.h" />
|
||||
<ClInclude Include="GpWindows.h" />
|
||||
<ClInclude Include="IGpAudioChannel.h" />
|
||||
<ClInclude Include="IGpAudioDriver.h" />
|
||||
<ClInclude Include="IGpDisplayDriver.h" />
|
||||
<ClInclude Include="resource.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\FreeType\FreeType.vcxproj">
|
||||
<Project>{487216d8-16ba-4b4c-b5bf-43feedfee03a}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\GpApp\GpApp.vcxproj">
|
||||
<Project>{6233c3f2-5781-488e-b190-4fa8836f5a77}</Project>
|
||||
</ProjectReference>
|
||||
@@ -216,9 +127,15 @@
|
||||
<ProjectReference Include="..\GpDisplayDriver_D3D11\GpDisplayDriver_D3D11.vcxproj">
|
||||
<Project>{ffc961ac-55b4-4a38-a83e-06ae98f59acc}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\GpFontHandler_FreeType2\GpFontHandler_FreeType2.vcxproj">
|
||||
<Project>{4b564030-8985-4975-91e1-e1b2c16ae2a1}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\GpInputDriver_XInput\GpInputDriver_XInput.vcxproj">
|
||||
<Project>{17b96f07-ef92-47cd-95a5-8e6ee38ab564}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\GpShell\GpShell.vcxproj">
|
||||
<Project>{10cf9b5f-61d0-4b5b-89f4-810b58fc053d}</Project>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="Aerofoil.rc" />
|
||||
|
127
Aerofoil/Aerofoil.vcxproj.filters
Normal file
127
Aerofoil/Aerofoil.vcxproj.filters
Normal file
@@ -0,0 +1,127 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<ClCompile Include="GpColorCursor_Win32.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="GpFiber_Win32.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="GpFiberStarter_Win32.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="GpFileStream_Win32.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="GpFileSystem_Win32.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="GpMain_Win32.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="GpMutex_Win32.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="GpSystemServices_Win32.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="GpThreadEvent_Win32.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="GpLogDriver_Win32.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="GpBWCursor_Win32.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\GpCommon\EGpInputDriverType.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\GpCommon\EGpStandardCursor.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\GpCommon\GpApplicationName.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\GpCommon\GpDisplayDriverTickStatus.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="GpFiber_Win32.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\GpCommon\GpFileCreationDisposition.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="GpFileStream_Win32.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="GpFileSystem_Win32.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\GpCommon\GpInputDriverProperties.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="GpMutex_Win32.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="GpSystemServices_Win32.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="GpThreadEvent_Win32.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\GpCommon\IGpAudioChannelCallbacks.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\GpCommon\IGpCursor.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\GpCommon\IGpDisplayDriverSurface.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="resource.h">
|
||||
<Filter>Resource Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\GpCommon\IGpLogDriver.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="GpLogDriver_Win32.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\GpCommon\IGpPrefsHandler.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\GpCommon\GpBuildVersion.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="GpBWCursor_Win32.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Image Include="ConvertedResources\Large128.ico">
|
||||
<Filter>Resource Files</Filter>
|
||||
</Image>
|
||||
<Image Include="ConvertedResources\Small128.ico">
|
||||
<Filter>Resource Files</Filter>
|
||||
</Image>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{0db467fa-83af-4c89-b36b-2478899f4f9e}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{bdb8c57b-c9f7-443a-be30-89718b8ca3e5}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{8ed8ebed-2aea-4f6d-8f2f-c18a64eb6e20}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="Aerofoil.rc">
|
||||
<Filter>Resource Files</Filter>
|
||||
</ResourceCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
110
Aerofoil/GpBWCursor_Win32.cpp
Normal file
110
Aerofoil/GpBWCursor_Win32.cpp
Normal file
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
Portions of this file based on Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2020 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
#include "GpBWCursor_Win32.h"
|
||||
#include "GpWindows.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <new>
|
||||
#include <algorithm>
|
||||
|
||||
extern GpWindowsGlobals g_gpWindowsGlobals;
|
||||
|
||||
void GpBWCursor_Win32::Destroy()
|
||||
{
|
||||
this->DecRef();
|
||||
}
|
||||
|
||||
IGpCursor_Win32 *GpBWCursor_Win32::Create(size_t width, size_t height, const void *pixelData, const void *maskData, size_t hotSpotX, size_t hotSpotY)
|
||||
{
|
||||
size_t numBits = width * height;
|
||||
size_t numBytes = (width * height + 7) / 8;
|
||||
uint8_t *convertedAndData = static_cast<uint8_t*>(malloc(numBytes));
|
||||
uint8_t *convertedXorData = static_cast<uint8_t*>(malloc(numBytes));
|
||||
|
||||
if (!convertedAndData || !convertedXorData)
|
||||
{
|
||||
if (convertedAndData)
|
||||
free(convertedAndData);
|
||||
if (convertedXorData)
|
||||
free(convertedXorData);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < numBytes; i++)
|
||||
{
|
||||
// MacPx0 MacPx1
|
||||
// MacMask0 1a 0x 1a 1x
|
||||
// MacMask1 0a 1x 0a 0x
|
||||
convertedAndData[i] = static_cast<const uint8_t*>(maskData)[i] ^ 0xff;
|
||||
convertedXorData[i] = static_cast<const uint8_t*>(maskData)[i] ^ static_cast<const uint8_t*>(pixelData)[i];
|
||||
}
|
||||
|
||||
HCURSOR hcursor = CreateCursor(g_gpWindowsGlobals.m_hInstance, static_cast<int>(hotSpotX), static_cast<int>(hotSpotY), static_cast<int>(width), static_cast<int>(height), convertedAndData, convertedXorData);
|
||||
|
||||
free(convertedAndData);
|
||||
free(convertedXorData);
|
||||
|
||||
if (!hcursor)
|
||||
return nullptr;
|
||||
|
||||
void *storage = malloc(sizeof(GpBWCursor_Win32));
|
||||
if (!storage)
|
||||
{
|
||||
DestroyCursor(hcursor);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return new (storage) GpBWCursor_Win32(hcursor);
|
||||
}
|
||||
|
||||
GpBWCursor_Win32::GpBWCursor_Win32(HCURSOR cursor)
|
||||
: m_cursor(cursor)
|
||||
, m_refCount(1)
|
||||
{
|
||||
}
|
||||
|
||||
GpBWCursor_Win32::~GpBWCursor_Win32()
|
||||
{
|
||||
DestroyCursor(m_cursor);
|
||||
}
|
||||
|
||||
const HCURSOR &GpBWCursor_Win32::GetHCursor() const
|
||||
{
|
||||
return m_cursor;
|
||||
}
|
||||
|
||||
void GpBWCursor_Win32::IncRef()
|
||||
{
|
||||
m_refCount++;
|
||||
}
|
||||
|
||||
void GpBWCursor_Win32::DecRef()
|
||||
{
|
||||
m_refCount--;
|
||||
if (m_refCount == 0)
|
||||
{
|
||||
this->~GpBWCursor_Win32();
|
||||
free(this);
|
||||
}
|
||||
}
|
24
Aerofoil/GpBWCursor_Win32.h
Normal file
24
Aerofoil/GpBWCursor_Win32.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "IGpCursor_Win32.h"
|
||||
#include "GpWindows.h"
|
||||
|
||||
class GpBWCursor_Win32 final : public IGpCursor_Win32
|
||||
{
|
||||
public:
|
||||
void Destroy() override;
|
||||
|
||||
const HCURSOR &GetHCursor() const override;
|
||||
|
||||
void IncRef() override;
|
||||
void DecRef() override;
|
||||
|
||||
static IGpCursor_Win32 *Create(size_t width, size_t height, const void *pixelData, const void *maskData, size_t hotSpotX, size_t hotSpotY);
|
||||
|
||||
private:
|
||||
GpBWCursor_Win32(HCURSOR cursor);
|
||||
~GpBWCursor_Win32();
|
||||
|
||||
HCURSOR m_cursor;
|
||||
int m_refCount;
|
||||
};
|
@@ -1,58 +1,132 @@
|
||||
#include "GpCursor_Win32.h"
|
||||
/*
|
||||
Portions of this file based on Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2020 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
#include "GpColorCursor_Win32.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <new>
|
||||
#include <algorithm>
|
||||
|
||||
void GpCursor_Win32::Destroy()
|
||||
void GpColorCursor_Win32::Destroy()
|
||||
{
|
||||
this->DecRef();
|
||||
}
|
||||
|
||||
IGpCursor_Win32 *GpCursor_Win32::Load(const wchar_t *path)
|
||||
IGpCursor_Win32 *GpColorCursor_Win32::Create(size_t width, size_t height, const void *pixelDataRGBA, size_t hotSpotX, size_t hotSpotY)
|
||||
{
|
||||
HANDLE imageH = LoadImageW(nullptr, path, IMAGE_CURSOR, 0, 0, LR_LOADFROMFILE);
|
||||
const size_t paddingBits = (sizeof(void*) * 8);
|
||||
|
||||
if (imageH == nullptr)
|
||||
BITMAPV4HEADER bmp;
|
||||
|
||||
memset(&bmp, 0, sizeof(bmp));
|
||||
bmp.bV4Size = sizeof(bmp);
|
||||
bmp.bV4Width = width;
|
||||
bmp.bV4Height = -static_cast<LONG>(height);
|
||||
bmp.bV4Planes = 1;
|
||||
bmp.bV4BitCount = 32;
|
||||
bmp.bV4V4Compression = BI_BITFIELDS;
|
||||
bmp.bV4AlphaMask = 0xFF000000;
|
||||
bmp.bV4RedMask = 0x00FF0000;
|
||||
bmp.bV4GreenMask = 0x0000FF00;
|
||||
bmp.bV4BlueMask = 0x000000FF;
|
||||
|
||||
size_t maskPitch = width + paddingBits - 1;
|
||||
maskPitch -= maskPitch % paddingBits;
|
||||
|
||||
LPVOID maskBits = malloc(maskPitch * height);
|
||||
if (!maskBits)
|
||||
return nullptr;
|
||||
|
||||
HCURSOR cursor = reinterpret_cast<HCURSOR>(imageH);
|
||||
void *storage = malloc(sizeof(GpCursor_Win32));
|
||||
memset(maskBits, 0xff, maskPitch * height);
|
||||
|
||||
HDC hdc = GetDC(NULL);
|
||||
|
||||
LPVOID pixels;
|
||||
|
||||
ICONINFO ii;
|
||||
memset(&ii, 0, sizeof(ii));
|
||||
ii.fIcon = FALSE;
|
||||
ii.xHotspot = (DWORD)hotSpotX;
|
||||
ii.yHotspot = (DWORD)hotSpotY;
|
||||
ii.hbmColor = CreateDIBSection(hdc, (BITMAPINFO*)&bmp, DIB_RGB_COLORS, &pixels, NULL, 0);
|
||||
ii.hbmMask = CreateBitmap(width, height, 1, 1, maskBits);
|
||||
ReleaseDC(NULL, hdc);
|
||||
|
||||
size_t cursorPitch = width * 4;
|
||||
|
||||
size_t numPixels = width * height;
|
||||
memcpy(pixels, pixelDataRGBA, numPixels * 4);
|
||||
|
||||
for (size_t i = 0; i < numPixels; i++)
|
||||
{
|
||||
uint8_t *pixel = static_cast<uint8_t*>(pixels) + i * 4;
|
||||
std::swap(pixel[0], pixel[2]);
|
||||
}
|
||||
|
||||
HICON hicon = CreateIconIndirect(&ii);
|
||||
|
||||
DeleteObject(ii.hbmColor);
|
||||
DeleteObject(ii.hbmMask);
|
||||
|
||||
if (!hicon)
|
||||
return nullptr;
|
||||
|
||||
void *storage = malloc(sizeof(GpColorCursor_Win32));
|
||||
if (!storage)
|
||||
{
|
||||
DestroyCursor(cursor);
|
||||
DestroyIcon(hicon);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return new (storage) GpCursor_Win32(reinterpret_cast<HCURSOR>(cursor));
|
||||
return new (storage) GpColorCursor_Win32(reinterpret_cast<HCURSOR>(hicon));
|
||||
}
|
||||
|
||||
GpCursor_Win32::GpCursor_Win32(HCURSOR cursor)
|
||||
GpColorCursor_Win32::GpColorCursor_Win32(HCURSOR cursor)
|
||||
: m_cursor(cursor)
|
||||
, m_refCount(1)
|
||||
{
|
||||
}
|
||||
|
||||
GpCursor_Win32::~GpCursor_Win32()
|
||||
GpColorCursor_Win32::~GpColorCursor_Win32()
|
||||
{
|
||||
DestroyCursor(m_cursor);
|
||||
DestroyIcon(m_cursor);
|
||||
}
|
||||
|
||||
const HCURSOR &GpCursor_Win32::GetHCursor() const
|
||||
const HCURSOR &GpColorCursor_Win32::GetHCursor() const
|
||||
{
|
||||
return m_cursor;
|
||||
}
|
||||
|
||||
void GpCursor_Win32::IncRef()
|
||||
void GpColorCursor_Win32::IncRef()
|
||||
{
|
||||
m_refCount++;
|
||||
}
|
||||
|
||||
void GpCursor_Win32::DecRef()
|
||||
void GpColorCursor_Win32::DecRef()
|
||||
{
|
||||
m_refCount--;
|
||||
if (m_refCount == 0)
|
||||
{
|
||||
this->~GpCursor_Win32();
|
||||
free(this);
|
||||
}
|
||||
{
|
||||
this->~GpColorCursor_Win32();
|
||||
free(this);
|
||||
}
|
||||
}
|
||||
|
@@ -1,18 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "IGpColorCursor.h"
|
||||
#include "IGpCursor_Win32.h"
|
||||
#include "GpWindows.h"
|
||||
|
||||
struct IGpColorCursor_Win32 : public IGpColorCursor
|
||||
{
|
||||
virtual const HCURSOR &GetHCursor() const = 0;
|
||||
|
||||
virtual void IncRef() = 0;
|
||||
virtual void DecRef() = 0;
|
||||
};
|
||||
|
||||
|
||||
class GpColorCursor_Win32 final : public IGpColorCursor_Win32
|
||||
class GpColorCursor_Win32 final : public IGpCursor_Win32
|
||||
{
|
||||
public:
|
||||
void Destroy() override;
|
||||
@@ -22,7 +14,7 @@ public:
|
||||
void IncRef() override;
|
||||
void DecRef() override;
|
||||
|
||||
static IGpColorCursor_Win32 *Load(const wchar_t *path);
|
||||
static IGpCursor_Win32 *Create(size_t width, size_t height, const void *pixelDataRGBA, size_t hotSpotX, size_t hotSpotY);
|
||||
|
||||
private:
|
||||
GpColorCursor_Win32(HCURSOR cursor);
|
||||
|
@@ -1,11 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
struct IGpFiber;
|
||||
|
||||
class GpFiberStarter
|
||||
{
|
||||
public:
|
||||
typedef void(*ThreadFunc_t)(void *context);
|
||||
|
||||
static IGpFiber *StartFiber(ThreadFunc_t threadFunc, void *context, IGpFiber *creatingFiber);
|
||||
};
|
@@ -1,56 +1,56 @@
|
||||
#include "GpFiberStarter.h"
|
||||
#include "GpFiber_Win32.h"
|
||||
#include "GpWindows.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
namespace GpFiberStarter_Win32
|
||||
{
|
||||
struct FiberStartState
|
||||
{
|
||||
GpFiberStarter::ThreadFunc_t m_threadFunc;
|
||||
IGpFiber *m_creatingFiber;
|
||||
void *m_context;
|
||||
};
|
||||
|
||||
static VOID WINAPI FiberStartRoutine(LPVOID lpThreadParameter)
|
||||
{
|
||||
const FiberStartState *tss = static_cast<const FiberStartState*>(lpThreadParameter);
|
||||
|
||||
GpFiberStarter::ThreadFunc_t threadFunc = tss->m_threadFunc;
|
||||
IGpFiber *creatingFiber = tss->m_creatingFiber;
|
||||
void *context = tss->m_context;
|
||||
creatingFiber->YieldTo();
|
||||
|
||||
threadFunc(context);
|
||||
|
||||
assert(!"Fiber function exited");
|
||||
}
|
||||
}
|
||||
|
||||
IGpFiber *GpFiberStarter::StartFiber(ThreadFunc_t threadFunc, void *context, IGpFiber *creatingFiber)
|
||||
{
|
||||
ULONG_PTR lowLimit;
|
||||
ULONG_PTR highLimit;
|
||||
#include "GpFiberStarter.h"
|
||||
#include "GpFiber_Win32.h"
|
||||
#include "GpWindows.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
namespace GpFiberStarter_Win32
|
||||
{
|
||||
struct FiberStartState
|
||||
{
|
||||
GpFiberStarter::ThreadFunc_t m_threadFunc;
|
||||
IGpFiber *m_creatingFiber;
|
||||
void *m_context;
|
||||
};
|
||||
|
||||
static VOID WINAPI FiberStartRoutine(LPVOID lpThreadParameter)
|
||||
{
|
||||
const FiberStartState *tss = static_cast<const FiberStartState*>(lpThreadParameter);
|
||||
|
||||
GpFiberStarter::ThreadFunc_t threadFunc = tss->m_threadFunc;
|
||||
IGpFiber *creatingFiber = tss->m_creatingFiber;
|
||||
void *context = tss->m_context;
|
||||
SwitchToFiber(static_cast<GpFiber_Win32*>(creatingFiber)->GetFiber());
|
||||
|
||||
threadFunc(context);
|
||||
|
||||
assert(!"Fiber function exited");
|
||||
}
|
||||
}
|
||||
|
||||
IGpFiber *GpFiberStarter::StartFiber(IGpSystemServices *systemServices, ThreadFunc_t threadFunc, void *context, IGpFiber *creatingFiber)
|
||||
{
|
||||
ULONG_PTR lowLimit;
|
||||
ULONG_PTR highLimit;
|
||||
|
||||
#if 0
|
||||
GetCurrentThreadStackLimits(&lowLimit, &highLimit);
|
||||
|
||||
#if 0
|
||||
GetCurrentThreadStackLimits(&lowLimit, &highLimit);
|
||||
|
||||
ULONG_PTR stackSize = highLimit - lowLimit;
|
||||
#else
|
||||
ULONG_PTR stackSize = 1024 * 1024;
|
||||
#endif
|
||||
|
||||
GpFiberStarter_Win32::FiberStartState startState;
|
||||
startState.m_context = context;
|
||||
startState.m_creatingFiber = creatingFiber;
|
||||
startState.m_threadFunc = threadFunc;
|
||||
|
||||
void *fiber = CreateFiber(static_cast<SIZE_T>(stackSize), GpFiberStarter_Win32::FiberStartRoutine, &startState);
|
||||
if (!fiber)
|
||||
return nullptr;
|
||||
|
||||
SwitchToFiber(fiber);
|
||||
|
||||
return GpFiber_Win32::Create(fiber);
|
||||
}
|
||||
#endif
|
||||
|
||||
GpFiberStarter_Win32::FiberStartState startState;
|
||||
startState.m_context = context;
|
||||
startState.m_creatingFiber = creatingFiber;
|
||||
startState.m_threadFunc = threadFunc;
|
||||
|
||||
void *fiber = CreateFiber(static_cast<SIZE_T>(stackSize), GpFiberStarter_Win32::FiberStartRoutine, &startState);
|
||||
if (!fiber)
|
||||
return nullptr;
|
||||
|
||||
SwitchToFiber(fiber);
|
||||
|
||||
return GpFiber_Win32::Create(fiber);
|
||||
}
|
||||
|
@@ -1,32 +1,37 @@
|
||||
#include "GpFiber_Win32.h"
|
||||
#include <new>
|
||||
|
||||
GpFiber_Win32::GpFiber_Win32(LPVOID fiber)
|
||||
: m_fiber(fiber)
|
||||
{
|
||||
}
|
||||
|
||||
void GpFiber_Win32::YieldTo()
|
||||
{
|
||||
SwitchToFiber(m_fiber);
|
||||
}
|
||||
|
||||
void GpFiber_Win32::Destroy()
|
||||
#include <new>
|
||||
|
||||
GpFiber_Win32::GpFiber_Win32(LPVOID fiber)
|
||||
: m_fiber(fiber)
|
||||
{
|
||||
}
|
||||
|
||||
void GpFiber_Win32::YieldTo(IGpFiber *toFiber)
|
||||
{
|
||||
SwitchToFiber(static_cast<GpFiber_Win32*>(toFiber)->m_fiber);
|
||||
}
|
||||
|
||||
void GpFiber_Win32::YieldToTerminal(IGpFiber *toFiber)
|
||||
{
|
||||
YieldTo(toFiber);
|
||||
}
|
||||
|
||||
void GpFiber_Win32::Destroy()
|
||||
{
|
||||
this->~GpFiber_Win32();
|
||||
free(this);
|
||||
}
|
||||
|
||||
GpFiber_Win32::~GpFiber_Win32()
|
||||
{
|
||||
DeleteFiber(m_fiber);
|
||||
{
|
||||
DeleteFiber(m_fiber);
|
||||
}
|
||||
|
||||
|
||||
IGpFiber *GpFiber_Win32::Create(LPVOID fiber)
|
||||
{
|
||||
void *storage = malloc(sizeof(GpFiber_Win32));
|
||||
if (!storage)
|
||||
return nullptr;
|
||||
|
||||
return new (storage) GpFiber_Win32(fiber);
|
||||
return new (storage) GpFiber_Win32(fiber);
|
||||
}
|
||||
|
@@ -5,14 +5,22 @@
|
||||
class GpFiber_Win32 final : public IGpFiber
|
||||
{
|
||||
public:
|
||||
void YieldTo() override;
|
||||
void YieldTo(IGpFiber *toFiber) override;
|
||||
void YieldToTerminal(IGpFiber *toFiber) override;
|
||||
void Destroy() override;
|
||||
|
||||
static IGpFiber *Create(LPVOID fiber);
|
||||
|
||||
LPVOID GetFiber() const;
|
||||
|
||||
private:
|
||||
explicit GpFiber_Win32(LPVOID fiber);
|
||||
~GpFiber_Win32();
|
||||
|
||||
LPVOID m_fiber;
|
||||
};
|
||||
|
||||
inline LPVOID GpFiber_Win32::GetFiber() const
|
||||
{
|
||||
return m_fiber;
|
||||
}
|
||||
|
@@ -1,144 +1,149 @@
|
||||
#include "GpFileStream_Win32.h"
|
||||
|
||||
GpFileStream_Win32::GpFileStream_Win32(HANDLE handle, bool readable, bool writeable, bool seekable)
|
||||
: m_handle(handle)
|
||||
, m_readable(readable)
|
||||
, m_writeable(writeable)
|
||||
, m_seekable(seekable)
|
||||
{
|
||||
}
|
||||
|
||||
size_t GpFileStream_Win32::Read(void *bytesOut, size_t size)
|
||||
{
|
||||
if (!m_readable)
|
||||
return 0;
|
||||
|
||||
size_t totalRead = 0;
|
||||
while (size)
|
||||
{
|
||||
const DWORD chunkSizeToRead = (size > MAXDWORD) ? MAXDWORD : size;
|
||||
DWORD numRead = 0;
|
||||
|
||||
BOOL readSucceeded = ReadFile(m_handle, bytesOut, chunkSizeToRead, &numRead, nullptr);
|
||||
if (!readSucceeded)
|
||||
return totalRead;
|
||||
|
||||
totalRead += static_cast<size_t>(numRead);
|
||||
size -= static_cast<size_t>(numRead);
|
||||
bytesOut = static_cast<void*>(static_cast<uint8_t*>(bytesOut) + numRead);
|
||||
|
||||
if (numRead != chunkSizeToRead)
|
||||
return totalRead;
|
||||
}
|
||||
|
||||
return totalRead;
|
||||
}
|
||||
|
||||
size_t GpFileStream_Win32::Write(const void *bytes, size_t size)
|
||||
{
|
||||
if (!m_writeable)
|
||||
return 0;
|
||||
|
||||
size_t totalWritten = 0;
|
||||
while (size)
|
||||
{
|
||||
const DWORD chunkSizeToWrite = (size > MAXDWORD) ? MAXDWORD : size;
|
||||
DWORD numWritten = 0;
|
||||
|
||||
BOOL writeSucceeded = WriteFile(m_handle, bytes, chunkSizeToWrite, &numWritten, nullptr);
|
||||
#include "GpFileStream_Win32.h"
|
||||
|
||||
GpFileStream_Win32::GpFileStream_Win32(HANDLE handle, bool readable, bool writeable, bool seekable)
|
||||
: m_handle(handle)
|
||||
, m_readable(readable)
|
||||
, m_writeable(writeable)
|
||||
, m_seekable(seekable)
|
||||
{
|
||||
}
|
||||
|
||||
size_t GpFileStream_Win32::Read(void *bytesOut, size_t size)
|
||||
{
|
||||
if (!m_readable)
|
||||
return 0;
|
||||
|
||||
size_t totalRead = 0;
|
||||
while (size)
|
||||
{
|
||||
const DWORD chunkSizeToRead = (size > MAXDWORD) ? MAXDWORD : size;
|
||||
DWORD numRead = 0;
|
||||
|
||||
BOOL readSucceeded = ReadFile(m_handle, bytesOut, chunkSizeToRead, &numRead, nullptr);
|
||||
if (!readSucceeded)
|
||||
return totalRead;
|
||||
|
||||
totalRead += static_cast<size_t>(numRead);
|
||||
size -= static_cast<size_t>(numRead);
|
||||
bytesOut = static_cast<void*>(static_cast<uint8_t*>(bytesOut) + numRead);
|
||||
|
||||
if (numRead != chunkSizeToRead)
|
||||
return totalRead;
|
||||
}
|
||||
|
||||
return totalRead;
|
||||
}
|
||||
|
||||
size_t GpFileStream_Win32::Write(const void *bytes, size_t size)
|
||||
{
|
||||
if (!m_writeable)
|
||||
return 0;
|
||||
|
||||
size_t totalWritten = 0;
|
||||
while (size)
|
||||
{
|
||||
const DWORD chunkSizeToWrite = (size > MAXDWORD) ? MAXDWORD : size;
|
||||
DWORD numWritten = 0;
|
||||
|
||||
BOOL writeSucceeded = WriteFile(m_handle, bytes, chunkSizeToWrite, &numWritten, nullptr);
|
||||
if (!writeSucceeded)
|
||||
{
|
||||
DWORD lastError = GetLastError();
|
||||
DWORD lastError = GetLastError();
|
||||
return totalWritten;
|
||||
}
|
||||
|
||||
totalWritten += static_cast<size_t>(numWritten);
|
||||
size -= static_cast<size_t>(numWritten);
|
||||
bytes = static_cast<const void*>(static_cast<const uint8_t*>(bytes) + numWritten);
|
||||
|
||||
if (numWritten != chunkSizeToWrite)
|
||||
return totalWritten;
|
||||
}
|
||||
|
||||
return totalWritten;
|
||||
}
|
||||
|
||||
bool GpFileStream_Win32::IsSeekable() const
|
||||
{
|
||||
return m_seekable;
|
||||
}
|
||||
|
||||
bool GpFileStream_Win32::IsReadOnly() const
|
||||
{
|
||||
return !m_writeable;
|
||||
}
|
||||
|
||||
bool GpFileStream_Win32::IsWriteOnly() const
|
||||
{
|
||||
return !m_readable;
|
||||
}
|
||||
|
||||
bool GpFileStream_Win32::SeekStart(PortabilityLayer::UFilePos_t loc)
|
||||
{
|
||||
LARGE_INTEGER li;
|
||||
li.QuadPart = static_cast<LONGLONG>(loc);
|
||||
return SetFilePointerEx(m_handle, li, nullptr, FILE_BEGIN) != 0;
|
||||
}
|
||||
|
||||
bool GpFileStream_Win32::SeekCurrent(PortabilityLayer::FilePos_t loc)
|
||||
{
|
||||
LARGE_INTEGER li;
|
||||
li.QuadPart = static_cast<LONGLONG>(loc);
|
||||
return SetFilePointerEx(m_handle, li, nullptr, FILE_CURRENT) != 0;
|
||||
}
|
||||
|
||||
bool GpFileStream_Win32::SeekEnd(PortabilityLayer::UFilePos_t loc)
|
||||
{
|
||||
LARGE_INTEGER li;
|
||||
li.QuadPart = -static_cast<LONGLONG>(loc);
|
||||
return SetFilePointerEx(m_handle, li, nullptr, FILE_END) != 0;
|
||||
}
|
||||
|
||||
bool GpFileStream_Win32::Truncate(PortabilityLayer::UFilePos_t loc)
|
||||
{
|
||||
if (!m_writeable)
|
||||
return false;
|
||||
|
||||
PortabilityLayer::UFilePos_t oldPos = Tell();
|
||||
if (!SeekStart(loc))
|
||||
return false;
|
||||
|
||||
if (!SetEndOfFile(m_handle))
|
||||
return false;
|
||||
|
||||
if (!SeekStart(oldPos))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
PortabilityLayer::UFilePos_t GpFileStream_Win32::Size() const
|
||||
{
|
||||
LARGE_INTEGER fsize;
|
||||
if (!GetFileSizeEx(m_handle, &fsize))
|
||||
return 0;
|
||||
|
||||
return static_cast<PortabilityLayer::UFilePos_t>(fsize.QuadPart);
|
||||
}
|
||||
|
||||
PortabilityLayer::UFilePos_t GpFileStream_Win32::Tell() const
|
||||
{
|
||||
LARGE_INTEGER zero;
|
||||
zero.QuadPart = 0;
|
||||
|
||||
LARGE_INTEGER fpos;
|
||||
if (!SetFilePointerEx(m_handle, zero, &fpos, FILE_CURRENT))
|
||||
return 0;
|
||||
|
||||
return static_cast<PortabilityLayer::UFilePos_t>(fpos.QuadPart);
|
||||
}
|
||||
|
||||
void GpFileStream_Win32::Close()
|
||||
{
|
||||
CloseHandle(m_handle);
|
||||
}
|
||||
}
|
||||
|
||||
totalWritten += static_cast<size_t>(numWritten);
|
||||
size -= static_cast<size_t>(numWritten);
|
||||
bytes = static_cast<const void*>(static_cast<const uint8_t*>(bytes) + numWritten);
|
||||
|
||||
if (numWritten != chunkSizeToWrite)
|
||||
return totalWritten;
|
||||
}
|
||||
|
||||
return totalWritten;
|
||||
}
|
||||
|
||||
bool GpFileStream_Win32::IsSeekable() const
|
||||
{
|
||||
return m_seekable;
|
||||
}
|
||||
|
||||
bool GpFileStream_Win32::IsReadOnly() const
|
||||
{
|
||||
return !m_writeable;
|
||||
}
|
||||
|
||||
bool GpFileStream_Win32::IsWriteOnly() const
|
||||
{
|
||||
return !m_readable;
|
||||
}
|
||||
|
||||
bool GpFileStream_Win32::SeekStart(GpUFilePos_t loc)
|
||||
{
|
||||
LARGE_INTEGER li;
|
||||
li.QuadPart = static_cast<LONGLONG>(loc);
|
||||
return SetFilePointerEx(m_handle, li, nullptr, FILE_BEGIN) != 0;
|
||||
}
|
||||
|
||||
bool GpFileStream_Win32::SeekCurrent(GpFilePos_t loc)
|
||||
{
|
||||
LARGE_INTEGER li;
|
||||
li.QuadPart = static_cast<LONGLONG>(loc);
|
||||
return SetFilePointerEx(m_handle, li, nullptr, FILE_CURRENT) != 0;
|
||||
}
|
||||
|
||||
bool GpFileStream_Win32::SeekEnd(GpUFilePos_t loc)
|
||||
{
|
||||
LARGE_INTEGER li;
|
||||
li.QuadPart = -static_cast<LONGLONG>(loc);
|
||||
return SetFilePointerEx(m_handle, li, nullptr, FILE_END) != 0;
|
||||
}
|
||||
|
||||
bool GpFileStream_Win32::Truncate(GpUFilePos_t loc)
|
||||
{
|
||||
if (!m_writeable)
|
||||
return false;
|
||||
|
||||
GpUFilePos_t oldPos = Tell();
|
||||
if (!SeekStart(loc))
|
||||
return false;
|
||||
|
||||
if (!SetEndOfFile(m_handle))
|
||||
return false;
|
||||
|
||||
if (!SeekStart(oldPos))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
GpUFilePos_t GpFileStream_Win32::Size() const
|
||||
{
|
||||
LARGE_INTEGER fsize;
|
||||
if (!GetFileSizeEx(m_handle, &fsize))
|
||||
return 0;
|
||||
|
||||
return static_cast<GpUFilePos_t>(fsize.QuadPart);
|
||||
}
|
||||
|
||||
GpUFilePos_t GpFileStream_Win32::Tell() const
|
||||
{
|
||||
LARGE_INTEGER zero;
|
||||
zero.QuadPart = 0;
|
||||
|
||||
LARGE_INTEGER fpos;
|
||||
if (!SetFilePointerEx(m_handle, zero, &fpos, FILE_CURRENT))
|
||||
return 0;
|
||||
|
||||
return static_cast<GpUFilePos_t>(fpos.QuadPart);
|
||||
}
|
||||
|
||||
void GpFileStream_Win32::Close()
|
||||
{
|
||||
CloseHandle(m_handle);
|
||||
}
|
||||
|
||||
void GpFileStream_Win32::Flush()
|
||||
{
|
||||
FlushFileBuffers(m_handle);
|
||||
}
|
||||
|
@@ -1,30 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include "GpCoreDefs.h"
|
||||
#include "GpWindows.h"
|
||||
#include "IOStream.h"
|
||||
|
||||
class GpFileStream_Win32 final : public PortabilityLayer::IOStream
|
||||
{
|
||||
public:
|
||||
explicit GpFileStream_Win32(HANDLE handle, bool readable, bool writeable, bool seekable);
|
||||
|
||||
size_t Read(void *bytesOut, size_t size) override;
|
||||
size_t Write(const void *bytes, size_t size) override;
|
||||
bool IsSeekable() const override;
|
||||
bool IsReadOnly() const override;
|
||||
bool IsWriteOnly() const override;
|
||||
bool SeekStart(PortabilityLayer::UFilePos_t loc) override;
|
||||
bool SeekCurrent(PortabilityLayer::FilePos_t loc) override;
|
||||
bool SeekEnd(PortabilityLayer::UFilePos_t loc) override;
|
||||
bool Truncate(PortabilityLayer::UFilePos_t loc) override;
|
||||
PortabilityLayer::UFilePos_t Size() const override;
|
||||
PortabilityLayer::UFilePos_t Tell() const override;
|
||||
void Close() override;
|
||||
|
||||
private:
|
||||
HANDLE m_handle;
|
||||
bool m_readable;
|
||||
bool m_writeable;
|
||||
bool m_seekable;
|
||||
};
|
||||
#pragma once
|
||||
|
||||
#include "GpCoreDefs.h"
|
||||
#include "GpWindows.h"
|
||||
#include "GpIOStream.h"
|
||||
|
||||
class GpFileStream_Win32 final : public GpIOStream
|
||||
{
|
||||
public:
|
||||
explicit GpFileStream_Win32(HANDLE handle, bool readable, bool writeable, bool seekable);
|
||||
|
||||
size_t Read(void *bytesOut, size_t size) override;
|
||||
size_t Write(const void *bytes, size_t size) override;
|
||||
bool IsSeekable() const override;
|
||||
bool IsReadOnly() const override;
|
||||
bool IsWriteOnly() const override;
|
||||
bool SeekStart(GpUFilePos_t loc) override;
|
||||
bool SeekCurrent(GpFilePos_t loc) override;
|
||||
bool SeekEnd(GpUFilePos_t loc) override;
|
||||
bool Truncate(GpUFilePos_t loc) override;
|
||||
GpUFilePos_t Size() const override;
|
||||
GpUFilePos_t Tell() const override;
|
||||
void Close() override;
|
||||
void Flush() override;
|
||||
|
||||
private:
|
||||
HANDLE m_handle;
|
||||
bool m_readable;
|
||||
bool m_writeable;
|
||||
bool m_seekable;
|
||||
};
|
||||
|
@@ -3,8 +3,7 @@
|
||||
#include "GpApplicationName.h"
|
||||
#include "GpFileStream_Win32.h"
|
||||
#include "GpWindows.h"
|
||||
#include "GpMemoryBuffer.h"
|
||||
#include "HostDirectoryCursor.h"
|
||||
#include "IGpDirectoryCursor.h"
|
||||
|
||||
#include <string>
|
||||
#include <Shlwapi.h>
|
||||
@@ -15,7 +14,7 @@
|
||||
|
||||
extern GpWindowsGlobals g_gpWindowsGlobals;
|
||||
|
||||
class GpDirectoryCursor_Win32 final : public PortabilityLayer::HostDirectoryCursor
|
||||
class GpDirectoryCursor_Win32 final : public IGpDirectoryCursor
|
||||
{
|
||||
public:
|
||||
static GpDirectoryCursor_Win32 *Create(const HANDLE &handle, const WIN32_FIND_DATAW &findData);
|
||||
@@ -122,16 +121,22 @@ GpFileSystem_Win32::GpFileSystem_Win32()
|
||||
m_userHousesDir = m_prefsDir + L"\\Houses";
|
||||
m_userSavesDir = m_prefsDir + L"\\SavedGames";
|
||||
m_scoresDir = m_prefsDir + L"\\Scores";
|
||||
m_logsDir = m_prefsDir + L"\\Logs";
|
||||
m_fontCacheDir = m_prefsDir + L"\\FontCache";
|
||||
|
||||
CreateDirectoryW(m_prefsDir.c_str(), nullptr);
|
||||
CreateDirectoryW(m_scoresDir.c_str(), nullptr);
|
||||
CreateDirectoryW(m_userHousesDir.c_str(), nullptr);
|
||||
CreateDirectoryW(m_userSavesDir.c_str(), nullptr);
|
||||
CreateDirectoryW(m_logsDir.c_str(), nullptr);
|
||||
CreateDirectoryW(m_fontCacheDir.c_str(), nullptr);
|
||||
|
||||
m_prefsDir.append(L"\\");
|
||||
m_scoresDir.append(L"\\");
|
||||
m_userHousesDir.append(L"\\");
|
||||
m_userSavesDir.append(L"\\");
|
||||
m_logsDir.append(L"\\");
|
||||
m_fontCacheDir.append(L"\\");
|
||||
}
|
||||
|
||||
DWORD modulePathSize = GetModuleFileNameW(nullptr, m_executablePath, MAX_PATH);
|
||||
@@ -180,7 +185,7 @@ bool GpFileSystem_Win32::FileExists(PortabilityLayer::VirtualDirectory_t virtual
|
||||
{
|
||||
wchar_t winPath[MAX_PATH + 1];
|
||||
|
||||
if (!ResolvePath(virtualDirectory, path, winPath))
|
||||
if (!ResolvePath(virtualDirectory, &path, 1, winPath))
|
||||
return false;
|
||||
|
||||
return PathFileExistsW(winPath) != 0;
|
||||
@@ -190,7 +195,7 @@ bool GpFileSystem_Win32::FileLocked(PortabilityLayer::VirtualDirectory_t virtual
|
||||
{
|
||||
wchar_t winPath[MAX_PATH + 1];
|
||||
|
||||
if (!ResolvePath(virtualDirectory, path, winPath))
|
||||
if (!ResolvePath(virtualDirectory, &path, 1, winPath))
|
||||
{
|
||||
*exists = false;
|
||||
return false;
|
||||
@@ -207,11 +212,11 @@ bool GpFileSystem_Win32::FileLocked(PortabilityLayer::VirtualDirectory_t virtual
|
||||
return (attribs & FILE_ATTRIBUTE_READONLY) != 0;
|
||||
}
|
||||
|
||||
PortabilityLayer::IOStream *GpFileSystem_Win32::OpenFile(PortabilityLayer::VirtualDirectory_t virtualDirectory, const char *path, bool writeAccess, GpFileCreationDisposition_t createDisposition)
|
||||
GpIOStream *GpFileSystem_Win32::OpenFileNested(PortabilityLayer::VirtualDirectory_t virtualDirectory, char const* const* paths, size_t numPaths, bool writeAccess, GpFileCreationDisposition_t createDisposition)
|
||||
{
|
||||
wchar_t winPath[MAX_PATH + 1];
|
||||
|
||||
if (!ResolvePath(virtualDirectory, path, winPath))
|
||||
if (!ResolvePath(virtualDirectory, paths, numPaths, winPath))
|
||||
return false;
|
||||
|
||||
const DWORD desiredAccess = writeAccess ? (GENERIC_WRITE | GENERIC_READ) : GENERIC_READ;
|
||||
@@ -249,7 +254,7 @@ bool GpFileSystem_Win32::DeleteFile(PortabilityLayer::VirtualDirectory_t virtual
|
||||
{
|
||||
wchar_t winPath[MAX_PATH + 1];
|
||||
|
||||
if (!ResolvePath(virtualDirectory, path, winPath))
|
||||
if (!ResolvePath(virtualDirectory, &path, 1, winPath))
|
||||
return false;
|
||||
|
||||
if (DeleteFileW(winPath))
|
||||
@@ -267,11 +272,22 @@ bool GpFileSystem_Win32::DeleteFile(PortabilityLayer::VirtualDirectory_t virtual
|
||||
return false;
|
||||
}
|
||||
|
||||
PortabilityLayer::HostDirectoryCursor *GpFileSystem_Win32::ScanDirectory(PortabilityLayer::VirtualDirectory_t virtualDirectory)
|
||||
IGpDirectoryCursor *GpFileSystem_Win32::ScanDirectoryNested(PortabilityLayer::VirtualDirectory_t virtualDirectory, char const* const* paths, size_t numPaths)
|
||||
{
|
||||
wchar_t winPath[MAX_PATH + 2];
|
||||
|
||||
if (!ResolvePath(virtualDirectory, "*", winPath))
|
||||
const char **expandedPaths = static_cast<const char**>(malloc(sizeof(const char*) * (numPaths + 1)));
|
||||
if (!expandedPaths)
|
||||
return nullptr;
|
||||
|
||||
for (size_t i = 0; i < numPaths; i++)
|
||||
expandedPaths[i] = paths[i];
|
||||
expandedPaths[numPaths] = "*";
|
||||
|
||||
const bool isPathResolved = ResolvePath(virtualDirectory, expandedPaths, numPaths + 1, winPath);
|
||||
free(expandedPaths);
|
||||
|
||||
if (!isPathResolved)
|
||||
return nullptr;
|
||||
|
||||
WIN32_FIND_DATAW findData;
|
||||
@@ -283,172 +299,38 @@ PortabilityLayer::HostDirectoryCursor *GpFileSystem_Win32::ScanDirectory(Portabi
|
||||
return GpDirectoryCursor_Win32::Create(ff, findData);
|
||||
}
|
||||
|
||||
bool GpFileSystem_Win32::PromptSaveFile(PortabilityLayer::VirtualDirectory_t virtualDirectory, char *path, size_t &outPathLength, size_t pathCapacity, const char *initialFileName)
|
||||
bool GpFileSystem_Win32::ValidateFilePathUnicodeChar(uint32_t c) const
|
||||
{
|
||||
wchar_t baseFN[MAX_PATH + 5];
|
||||
wchar_t baseDir[MAX_PATH + 5];
|
||||
if (c >= '0' && c <= '9')
|
||||
return true;
|
||||
|
||||
const size_t existingPathLen = strlen(initialFileName);
|
||||
if (existingPathLen >= MAX_PATH)
|
||||
return false;
|
||||
if (c == '_' || c == '\'')
|
||||
return true;
|
||||
|
||||
for (size_t i = 0; i < existingPathLen; i++)
|
||||
baseFN[i] = static_cast<wchar_t>(initialFileName[i]);
|
||||
baseFN[existingPathLen] = 0;
|
||||
if (c == ' ')
|
||||
return true;
|
||||
|
||||
if (!ResolvePath(virtualDirectory, "", baseDir))
|
||||
return false;
|
||||
if (c >= 'a' && c <= 'z')
|
||||
return true;
|
||||
|
||||
OPENFILENAMEW ofn;
|
||||
memset(&ofn, 0, sizeof(ofn));
|
||||
if (c >= 'A' && c <= 'Z')
|
||||
return true;
|
||||
|
||||
ofn.lStructSize = sizeof(ofn);
|
||||
ofn.lpstrFilter = GP_APPLICATION_NAME_W L" File (*.gpf)\0*.gpf\0";
|
||||
ofn.lpstrFile = baseFN;
|
||||
ofn.lpstrDefExt = L"gpf";
|
||||
ofn.nMaxFile = MAX_PATH;
|
||||
ofn.lpstrInitialDir = baseDir;
|
||||
ofn.Flags = OFN_EXPLORER | OFN_NOCHANGEDIR | OFN_OVERWRITEPROMPT;
|
||||
ofn.hwndOwner = g_gpWindowsGlobals.m_hwnd;
|
||||
|
||||
if (!GetSaveFileNameW(&ofn))
|
||||
return false;
|
||||
|
||||
if (ofn.Flags & OFN_EXTENSIONDIFFERENT)
|
||||
{
|
||||
MessageBeep(MB_ICONERROR);
|
||||
MessageBoxW(nullptr, L"Save file failed: Saved files must have the '.gpf' extension", L"Invalid file path", MB_OK);
|
||||
return false;
|
||||
}
|
||||
|
||||
const wchar_t *fn = ofn.lpstrFile + ofn.nFileOffset;
|
||||
size_t fnLengthWithoutExt = wcslen(fn);
|
||||
if (ofn.nFileExtension - 1 > ofn.nFileOffset) // Off by 1 because extension doesn't include .
|
||||
fnLengthWithoutExt = ofn.nFileExtension - ofn.nFileOffset - 1;
|
||||
|
||||
if (fnLengthWithoutExt >= pathCapacity)
|
||||
{
|
||||
wchar_t msg[256];
|
||||
wsprintfW(msg, L"Save file failed: File name is too long. Limit is %i characters.", static_cast<int>(pathCapacity));
|
||||
MessageBeep(MB_ICONERROR);
|
||||
MessageBoxW(nullptr, msg, L"Invalid file path", MB_OK);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ofn.nFileOffset != wcslen(baseDir) || memcmp(ofn.lpstrFile, baseDir, ofn.nFileOffset * sizeof(wchar_t)))
|
||||
{
|
||||
wchar_t msg[256 + MAX_PATH];
|
||||
wsprintfW(msg, L"Save file failed: File can't be saved here, it must be saved in %s", baseDir);
|
||||
MessageBeep(MB_ICONERROR);
|
||||
MessageBoxW(nullptr, msg, L"Invalid file path", MB_OK);
|
||||
return false;
|
||||
}
|
||||
|
||||
const wchar_t *unsupportedCharMsg = L"File name contains unsupported characters.";
|
||||
|
||||
for (size_t i = 0; i < fnLengthWithoutExt; i++)
|
||||
{
|
||||
if (fn[i] < static_cast<wchar_t>(0) || fn[i] >= static_cast<wchar_t>(128))
|
||||
{
|
||||
MessageBeep(MB_ICONERROR);
|
||||
MessageBoxW(nullptr, unsupportedCharMsg, L"Invalid file path", MB_OK);
|
||||
return false;
|
||||
}
|
||||
|
||||
path[i] = static_cast<char>(fn[i]);
|
||||
}
|
||||
|
||||
if (!ValidateFilePath(path, fnLengthWithoutExt))
|
||||
{
|
||||
MessageBeep(MB_ICONERROR);
|
||||
MessageBoxW(nullptr, unsupportedCharMsg, L"Invalid file path", MB_OK);
|
||||
return false;
|
||||
}
|
||||
|
||||
outPathLength = fnLengthWithoutExt;
|
||||
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GpFileSystem_Win32::PromptOpenFile(PortabilityLayer::VirtualDirectory_t virtualDirectory, char *path, size_t &outPathLength, size_t pathCapacity)
|
||||
bool GpFileSystem_Win32::IsVirtualDirectoryLooseResources(PortabilityLayer::VirtualDirectory_t virtualDir) const
|
||||
{
|
||||
wchar_t baseFN[MAX_PATH + 5];
|
||||
wchar_t baseDir[MAX_PATH + 5];
|
||||
return false;
|
||||
}
|
||||
|
||||
baseFN[0] = 0;
|
||||
void GpFileSystem_Win32::SetMainThreadRelay(IGpThreadRelay *relay)
|
||||
{
|
||||
(void)relay;
|
||||
}
|
||||
|
||||
if (!ResolvePath(virtualDirectory, "", baseDir))
|
||||
return false;
|
||||
|
||||
OPENFILENAMEW ofn;
|
||||
memset(&ofn, 0, sizeof(ofn));
|
||||
|
||||
ofn.lStructSize = sizeof(ofn);
|
||||
ofn.lpstrFilter = GP_APPLICATION_NAME_W L" File (*.gpf)\0*.gpf\0";
|
||||
ofn.lpstrFile = baseFN;
|
||||
ofn.lpstrDefExt = L"gpf";
|
||||
ofn.nMaxFile = MAX_PATH;
|
||||
ofn.lpstrInitialDir = baseDir;
|
||||
ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST;
|
||||
ofn.hwndOwner = g_gpWindowsGlobals.m_hwnd;
|
||||
|
||||
if (!GetOpenFileNameW(&ofn))
|
||||
return false;
|
||||
|
||||
if (ofn.Flags & OFN_EXTENSIONDIFFERENT)
|
||||
{
|
||||
MessageBeep(MB_ICONERROR);
|
||||
MessageBoxW(nullptr, L"Open file failed: Files must have the '.gpf' extension", L"Invalid file path", MB_OK);
|
||||
return false;
|
||||
}
|
||||
|
||||
const wchar_t *fn = ofn.lpstrFile + ofn.nFileOffset;
|
||||
size_t fnLengthWithoutExt = wcslen(fn);
|
||||
if (ofn.nFileExtension - 1 > ofn.nFileOffset) // Off by 1 because extension doesn't include .
|
||||
fnLengthWithoutExt = ofn.nFileExtension - ofn.nFileOffset - 1;
|
||||
|
||||
if (fnLengthWithoutExt >= pathCapacity)
|
||||
{
|
||||
wchar_t msg[256];
|
||||
wsprintfW(msg, L"Open file failed: File name is too long. Limit is %i characters.", static_cast<int>(pathCapacity));
|
||||
MessageBeep(MB_ICONERROR);
|
||||
MessageBoxW(nullptr, msg, L"Invalid file path", MB_OK);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ofn.nFileOffset != wcslen(baseDir) || memcmp(ofn.lpstrFile, baseDir, ofn.nFileOffset * sizeof(wchar_t)))
|
||||
{
|
||||
wchar_t msg[256 + MAX_PATH];
|
||||
wsprintfW(msg, L"Open file failed: File can't be opened from here, it must be in %s", baseDir);
|
||||
MessageBeep(MB_ICONERROR);
|
||||
MessageBoxW(nullptr, msg, L"Invalid file path", MB_OK);
|
||||
return false;
|
||||
}
|
||||
|
||||
const wchar_t *unsupportedCharMsg = L"File name contains unsupported characters.";
|
||||
|
||||
for (size_t i = 0; i < fnLengthWithoutExt; i++)
|
||||
{
|
||||
if (fn[i] < static_cast<wchar_t>(0) || fn[i] >= static_cast<wchar_t>(128))
|
||||
{
|
||||
MessageBeep(MB_ICONERROR);
|
||||
MessageBoxW(nullptr, unsupportedCharMsg, L"Invalid file path", MB_OK);
|
||||
return false;
|
||||
}
|
||||
|
||||
path[i] = static_cast<char>(fn[i]);
|
||||
}
|
||||
|
||||
if (!ValidateFilePath(path, fnLengthWithoutExt))
|
||||
{
|
||||
MessageBeep(MB_ICONERROR);
|
||||
MessageBoxW(nullptr, unsupportedCharMsg, L"Invalid file path", MB_OK);
|
||||
return false;
|
||||
}
|
||||
|
||||
outPathLength = fnLengthWithoutExt;
|
||||
|
||||
return true;
|
||||
void GpFileSystem_Win32::SetDelayCallback(GpFileSystem_Win32::DelayCallback_t delayCallback)
|
||||
{
|
||||
}
|
||||
|
||||
bool GpFileSystem_Win32::ValidateFilePath(const char *str, size_t length) const
|
||||
@@ -474,6 +356,71 @@ bool GpFileSystem_Win32::ValidateFilePath(const char *str, size_t length) const
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *bannedNames[] =
|
||||
{
|
||||
"CON",
|
||||
"PRN",
|
||||
"AUX",
|
||||
"NUL",
|
||||
"COM1",
|
||||
"COM2",
|
||||
"COM3",
|
||||
"COM4",
|
||||
"COM5",
|
||||
"COM6",
|
||||
"COM7",
|
||||
"COM8",
|
||||
"COM9",
|
||||
"LPT1",
|
||||
"LPT2",
|
||||
"LPT3",
|
||||
"LPT4",
|
||||
"LPT5",
|
||||
"LPT6",
|
||||
"LPT7",
|
||||
"LPT8",
|
||||
"LPT9"
|
||||
};
|
||||
|
||||
size_t nameLengthWithoutExt = length;
|
||||
for (size_t i = 0; i < length; i++)
|
||||
{
|
||||
if (str[i] == '.')
|
||||
{
|
||||
nameLengthWithoutExt = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const size_t numBannedNames = sizeof(bannedNames) / sizeof(bannedNames[0]);
|
||||
|
||||
for (size_t i = 0; i < numBannedNames; i++)
|
||||
{
|
||||
const char *bannedName = bannedNames[i];
|
||||
const size_t banLength = strlen(bannedName);
|
||||
|
||||
if (banLength == nameLengthWithoutExt)
|
||||
{
|
||||
bool isBanned = true;
|
||||
|
||||
for (size_t j = 0; j < banLength; j++)
|
||||
{
|
||||
char checkCH = str[j];
|
||||
if (checkCH >= 'a' && checkCH <= 'z')
|
||||
checkCH += ('A' - 'a');
|
||||
|
||||
if (bannedName[j] != checkCH)
|
||||
{
|
||||
isBanned = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isBanned)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -487,7 +434,7 @@ GpFileSystem_Win32 *GpFileSystem_Win32::GetInstance()
|
||||
return &ms_instance;
|
||||
}
|
||||
|
||||
bool GpFileSystem_Win32::ResolvePath(PortabilityLayer::VirtualDirectory_t virtualDirectory, const char *path, wchar_t *outPath)
|
||||
bool GpFileSystem_Win32::ResolvePath(PortabilityLayer::VirtualDirectory_t virtualDirectory, char const* const* paths, size_t numPaths, wchar_t *outPath)
|
||||
{
|
||||
const wchar_t *baseDir = nullptr;
|
||||
|
||||
@@ -514,6 +461,12 @@ bool GpFileSystem_Win32::ResolvePath(PortabilityLayer::VirtualDirectory_t virtua
|
||||
case PortabilityLayer::VirtualDirectories::kHighScores:
|
||||
baseDir = m_scoresDir.c_str();
|
||||
break;
|
||||
case PortabilityLayer::VirtualDirectories::kLogs:
|
||||
baseDir = m_logsDir.c_str();
|
||||
break;
|
||||
case PortabilityLayer::VirtualDirectories::kFontCache:
|
||||
baseDir = m_fontCacheDir.c_str();
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -522,23 +475,39 @@ bool GpFileSystem_Win32::ResolvePath(PortabilityLayer::VirtualDirectory_t virtua
|
||||
return false;
|
||||
|
||||
const size_t baseDirLen = wcslen(baseDir);
|
||||
const size_t pathLen = strlen(path);
|
||||
|
||||
if (baseDirLen >= MAX_PATH || MAX_PATH - baseDirLen < pathLen)
|
||||
return false;
|
||||
|
||||
memcpy(outPath, baseDir, sizeof(wchar_t) * baseDirLen);
|
||||
for (size_t i = 0; i < pathLen; i++)
|
||||
outPath[baseDirLen] = static_cast<wchar_t>(0);
|
||||
|
||||
for (size_t i = 0; i < numPaths; i++)
|
||||
{
|
||||
char c = path[i];
|
||||
if (c == '/')
|
||||
c = '\\';
|
||||
size_t outDirLen = wcslen(outPath);
|
||||
|
||||
outPath[baseDirLen + i] = static_cast<wchar_t>(c);
|
||||
if (i != 0)
|
||||
{
|
||||
if (baseDirLen >= MAX_PATH || MAX_PATH - baseDirLen < 1)
|
||||
return false;
|
||||
|
||||
outPath[outDirLen++] = '\\';
|
||||
}
|
||||
|
||||
const char *path = paths[i];
|
||||
const size_t pathLen = strlen(path);
|
||||
|
||||
if (baseDirLen >= MAX_PATH || MAX_PATH - baseDirLen < pathLen)
|
||||
return false;
|
||||
|
||||
for (size_t j = 0; j < pathLen; j++)
|
||||
{
|
||||
char c = path[j];
|
||||
if (c == '/')
|
||||
c = '\\';
|
||||
|
||||
outPath[outDirLen + j] = static_cast<wchar_t>(c);
|
||||
}
|
||||
|
||||
outPath[outDirLen + pathLen] = static_cast<wchar_t>(0);
|
||||
}
|
||||
|
||||
outPath[baseDirLen + pathLen] = static_cast<wchar_t>(0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@@ -1,42 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include "HostFileSystem.h"
|
||||
#include "IGpFileSystem.h"
|
||||
|
||||
#include "GpCoreDefs.h"
|
||||
#include "GpWindows.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
class GpFileSystem_Win32 final : public PortabilityLayer::HostFileSystem
|
||||
class GpFileSystem_Win32 final : public IGpFileSystem
|
||||
{
|
||||
public:
|
||||
GpFileSystem_Win32();
|
||||
|
||||
bool FileExists(PortabilityLayer::VirtualDirectory_t virtualDirectory, const char *path) override;
|
||||
bool FileLocked(PortabilityLayer::VirtualDirectory_t virtualDirectory, const char *path, bool *exists) override;
|
||||
PortabilityLayer::IOStream *OpenFile(PortabilityLayer::VirtualDirectory_t virtualDirectory, const char *path, bool writeAccess, GpFileCreationDisposition_t createDisposition) override;
|
||||
GpIOStream *OpenFileNested(PortabilityLayer::VirtualDirectory_t virtualDirectory, char const* const* paths, size_t numPaths, bool writeAccess, GpFileCreationDisposition_t createDisposition) override;
|
||||
bool DeleteFile(PortabilityLayer::VirtualDirectory_t virtualDirectory, const char *path, bool &existed) override;
|
||||
PortabilityLayer::HostDirectoryCursor *ScanDirectory(PortabilityLayer::VirtualDirectory_t virtualDirectory) override;
|
||||
|
||||
bool PromptSaveFile(PortabilityLayer::VirtualDirectory_t dirID, char *path, size_t &outPathLength, size_t pathCapacity, const char *initialFileName) override;
|
||||
bool PromptOpenFile(PortabilityLayer::VirtualDirectory_t dirID, char *path, size_t &outPathLength, size_t pathCapacity) override;
|
||||
IGpDirectoryCursor *ScanDirectoryNested(PortabilityLayer::VirtualDirectory_t virtualDirectory, char const* const* paths, size_t numPaths) override;
|
||||
|
||||
bool ValidateFilePath(const char *path, size_t sz) const override;
|
||||
bool ValidateFilePathUnicodeChar(uint32_t ch) const override;
|
||||
|
||||
bool IsVirtualDirectoryLooseResources(PortabilityLayer::VirtualDirectory_t virtualDir) const override;
|
||||
|
||||
void SetMainThreadRelay(IGpThreadRelay *relay) override;
|
||||
void SetDelayCallback(DelayCallback_t delayCallback) override;
|
||||
|
||||
const wchar_t *GetBasePath() const;
|
||||
|
||||
static GpFileSystem_Win32 *GetInstance();
|
||||
|
||||
private:
|
||||
bool ResolvePath(PortabilityLayer::VirtualDirectory_t virtualDirectory, const char *path, wchar_t *outPath);
|
||||
bool ResolvePath(PortabilityLayer::VirtualDirectory_t virtualDirectory, char const* const* paths, size_t numPaths, wchar_t *outPath);
|
||||
|
||||
std::wstring m_prefsDir;
|
||||
std::wstring m_scoresDir;
|
||||
std::wstring m_packagedDir;
|
||||
std::wstring m_housesDir;
|
||||
std::wstring m_logsDir;
|
||||
std::wstring m_userHousesDir;
|
||||
std::wstring m_userSavesDir;
|
||||
std::wstring m_resourcesDir;
|
||||
std::wstring m_fontCacheDir;
|
||||
wchar_t m_executablePath[MAX_PATH];
|
||||
|
||||
static GpFileSystem_Win32 ms_instance;
|
||||
|
@@ -1,9 +0,0 @@
|
||||
#include "GpFontHandlerFactory.h"
|
||||
#include "GpFontHandler_FreeType2.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
PortabilityLayer::HostFontHandler *GpFontHandlerFactory::Create()
|
||||
{
|
||||
return GpFontHandler_FreeType2::Create();
|
||||
}
|
@@ -1,12 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
namespace PortabilityLayer
|
||||
{
|
||||
class HostFontHandler;
|
||||
}
|
||||
|
||||
class GpFontHandlerFactory final
|
||||
{
|
||||
public:
|
||||
static PortabilityLayer::HostFontHandler *Create();
|
||||
};
|
113
Aerofoil/GpLogDriver_Win32.cpp
Normal file
113
Aerofoil/GpLogDriver_Win32.cpp
Normal file
@@ -0,0 +1,113 @@
|
||||
#include "GpLogDriver_Win32.h"
|
||||
#include "GpFileSystem_Win32.h"
|
||||
|
||||
#include "GpApplicationName.h"
|
||||
#include "GpIOStream.h"
|
||||
|
||||
GpLogDriver_Win32::GpLogDriver_Win32()
|
||||
: m_stream(nullptr)
|
||||
, m_isInitialized(false)
|
||||
{
|
||||
}
|
||||
|
||||
void GpLogDriver_Win32::Init()
|
||||
{
|
||||
ms_instance.InitInternal();
|
||||
}
|
||||
|
||||
void GpLogDriver_Win32::VPrintf(Category category, const char *fmt, va_list args)
|
||||
{
|
||||
size_t fmtSize = 0;
|
||||
bool hasFormatting = false;
|
||||
for (const char *fmtCheck = fmt; *fmtCheck; fmtCheck++)
|
||||
{
|
||||
if (*fmtCheck == '%')
|
||||
hasFormatting = true;
|
||||
|
||||
fmtSize++;
|
||||
}
|
||||
|
||||
SYSTEMTIME sysTime;
|
||||
GetSystemTime(&sysTime);
|
||||
|
||||
char timestampBuffer[64];
|
||||
sprintf(timestampBuffer, "[%02d:%02d:%02d:%03d] ", sysTime.wHour, sysTime.wMinute, sysTime.wSecond, sysTime.wMilliseconds);
|
||||
|
||||
m_stream->Write(timestampBuffer, strlen(timestampBuffer));
|
||||
|
||||
const char *debugTag = "";
|
||||
|
||||
switch (category)
|
||||
{
|
||||
case Category_Warning:
|
||||
debugTag = "[WARNING] ";
|
||||
break;
|
||||
case Category_Error:
|
||||
debugTag = "[ERROR] ";
|
||||
break;
|
||||
};
|
||||
|
||||
if (debugTag[0])
|
||||
m_stream->Write(debugTag, strlen(debugTag));
|
||||
|
||||
if (!hasFormatting)
|
||||
m_stream->Write(fmt, fmtSize);
|
||||
else
|
||||
{
|
||||
int formattedSize = vsnprintf(nullptr, 0, fmt, args);
|
||||
if (formattedSize <= 0)
|
||||
return;
|
||||
|
||||
char *charBuff = static_cast<char*>(malloc(formattedSize + 1));
|
||||
if (!charBuff)
|
||||
return;
|
||||
|
||||
vsnprintf(charBuff, formattedSize + 1, fmt, args);
|
||||
|
||||
m_stream->Write(charBuff, formattedSize);
|
||||
free(charBuff);
|
||||
}
|
||||
|
||||
m_stream->Write("\n", 1);
|
||||
|
||||
m_stream->Flush();
|
||||
}
|
||||
|
||||
void GpLogDriver_Win32::Shutdown()
|
||||
{
|
||||
if (m_stream)
|
||||
m_stream->Close();
|
||||
}
|
||||
|
||||
GpLogDriver_Win32 *GpLogDriver_Win32::GetInstance()
|
||||
{
|
||||
if (ms_instance.m_isInitialized)
|
||||
return &ms_instance;
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void GpLogDriver_Win32::InitInternal()
|
||||
{
|
||||
SYSTEMTIME utcTime;
|
||||
GetSystemTime(&utcTime);
|
||||
|
||||
char logFileName[256];
|
||||
|
||||
sprintf(logFileName, GP_APPLICATION_NAME "-%04d-%02d-%02d_%02d-%02d_%02d.txt", utcTime.wYear, utcTime.wMonth, utcTime.wDay, utcTime.wHour, utcTime.wMinute, utcTime.wSecond);
|
||||
|
||||
m_stream = GpFileSystem_Win32::GetInstance()->OpenFile(PortabilityLayer::VirtualDirectories::kLogs, logFileName, true, GpFileCreationDispositions::kCreateOrOverwrite);
|
||||
if (m_stream)
|
||||
{
|
||||
this->Printf(IGpLogDriver::Category_Information, GP_APPLICATION_NAME " build " __TIMESTAMP__);
|
||||
#if !GP_DEBUG_CONFIG
|
||||
this->Printf(IGpLogDriver::Category_Information, "Configuration: Release");
|
||||
#else
|
||||
this->Printf(IGpLogDriver::Category_Information, "Configuration: Debug");
|
||||
#endif
|
||||
|
||||
m_isInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
GpLogDriver_Win32 GpLogDriver_Win32::ms_instance;
|
26
Aerofoil/GpLogDriver_Win32.h
Normal file
26
Aerofoil/GpLogDriver_Win32.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include "IGpLogDriver.h"
|
||||
|
||||
class GpIOStream;
|
||||
|
||||
class GpLogDriver_Win32 : public IGpLogDriver
|
||||
{
|
||||
public:
|
||||
GpLogDriver_Win32();
|
||||
|
||||
static void Init();
|
||||
|
||||
void VPrintf(Category category, const char *fmt, va_list args) override;
|
||||
void Shutdown() override;
|
||||
|
||||
static GpLogDriver_Win32 *GetInstance();
|
||||
|
||||
private:
|
||||
void InitInternal();
|
||||
|
||||
GpIOStream *m_stream;
|
||||
bool m_isInitialized;
|
||||
|
||||
static GpLogDriver_Win32 ms_instance;
|
||||
};
|
@@ -1,20 +1,25 @@
|
||||
#include "GpMain.h"
|
||||
#include "GpAudioDriverFactory.h"
|
||||
#include "GpCursor_Win32.h"
|
||||
#include "GpBWCursor_Win32.h"
|
||||
#include "GpColorCursor_Win32.h"
|
||||
#include "GpDisplayDriverFactory.h"
|
||||
#include "GpGlobalConfig.h"
|
||||
#include "GpFiber_Win32.h"
|
||||
#include "GpFileSystem_Win32.h"
|
||||
#include "GpLogDriver_Win32.h"
|
||||
#include "GpFontHandlerFactory.h"
|
||||
#include "GpInputDriverFactory.h"
|
||||
#include "GpAppInterface.h"
|
||||
#include "GpSystemServices_Win32.h"
|
||||
#include "GpVOSEvent.h"
|
||||
#include "IGpFileSystem.h"
|
||||
#include "IGpVOSEventQueue.h"
|
||||
|
||||
#include "HostFileSystem.h"
|
||||
|
||||
#include "GpWindows.h"
|
||||
|
||||
#include "resource.h"
|
||||
|
||||
#include <shellapi.h>
|
||||
#include <stdio.h>
|
||||
#include <windowsx.h>
|
||||
|
||||
@@ -23,6 +28,7 @@ GpWindowsGlobals g_gpWindowsGlobals;
|
||||
extern "C" __declspec(dllimport) IGpAudioDriver *GpDriver_CreateAudioDriver_XAudio2(const GpAudioDriverProperties &properties);
|
||||
extern "C" __declspec(dllimport) IGpDisplayDriver *GpDriver_CreateDisplayDriver_D3D11(const GpDisplayDriverProperties &properties);
|
||||
extern "C" __declspec(dllimport) IGpInputDriver *GpDriver_CreateInputDriver_XInput(const GpInputDriverProperties &properties);
|
||||
extern "C" __declspec(dllimport) IGpFontHandler *GpDriver_CreateFontHandler_FreeType2(const GpFontHandlerProperties &properties);
|
||||
|
||||
static void PostMouseEvent(IGpVOSEventQueue *eventQueue, GpMouseEventType_t eventType, GpMouseButton_t button, int32_t x, int32_t y, float pixelScaleX, float pixelScaleY)
|
||||
{
|
||||
@@ -392,23 +398,49 @@ static void TranslateWindowsMessage(const MSG *msg, IGpVOSEventQueue *eventQueue
|
||||
|
||||
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
|
||||
{
|
||||
GpAppInterface_Get()->PL_HostFileSystem_SetInstance(GpFileSystem_Win32::GetInstance());
|
||||
GpAppInterface_Get()->PL_HostSystemServices_SetInstance(GpSystemServices_Win32::GetInstance());
|
||||
(void)lpCmdLine;
|
||||
|
||||
LPWSTR cmdLine = GetCommandLineW();
|
||||
|
||||
int nArgs;
|
||||
LPWSTR *cmdLineArgs = CommandLineToArgvW(cmdLine, &nArgs);
|
||||
|
||||
for (int i = 1; i < nArgs; i++)
|
||||
{
|
||||
if (!wcscmp(cmdLineArgs[i], L"-diagnostics"))
|
||||
GpLogDriver_Win32::Init();
|
||||
}
|
||||
|
||||
IGpLogDriver *logger = GpLogDriver_Win32::GetInstance();
|
||||
|
||||
GpDriverCollection *drivers = GpAppInterface_Get()->PL_GetDriverCollection();
|
||||
|
||||
drivers->SetDriver<GpDriverIDs::kFileSystem>(GpFileSystem_Win32::GetInstance());
|
||||
drivers->SetDriver<GpDriverIDs::kSystemServices>(GpSystemServices_Win32::GetInstance());
|
||||
drivers->SetDriver<GpDriverIDs::kLog>(GpLogDriver_Win32::GetInstance());
|
||||
|
||||
g_gpWindowsGlobals.m_hInstance = hInstance;
|
||||
g_gpWindowsGlobals.m_hPrevInstance = hPrevInstance;
|
||||
g_gpWindowsGlobals.m_cmdLine = lpCmdLine;
|
||||
g_gpWindowsGlobals.m_cmdLine = cmdLine;
|
||||
g_gpWindowsGlobals.m_cmdLineArgc = nArgs;
|
||||
g_gpWindowsGlobals.m_cmdLineArgv = cmdLineArgs;
|
||||
g_gpWindowsGlobals.m_nCmdShow = nCmdShow;
|
||||
g_gpWindowsGlobals.m_baseDir = GpFileSystem_Win32::GetInstance()->GetBasePath();
|
||||
g_gpWindowsGlobals.m_hwnd = nullptr;
|
||||
g_gpWindowsGlobals.m_hIcon = LoadIconW(hInstance, MAKEINTRESOURCEW(IDI_ICON1));
|
||||
g_gpWindowsGlobals.m_hIconSm = LoadIconW(hInstance, MAKEINTRESOURCEW(IDI_ICON2));
|
||||
|
||||
g_gpWindowsGlobals.m_createFiberFunc = GpFiber_Win32::Create;
|
||||
g_gpWindowsGlobals.m_loadCursorFunc = GpCursor_Win32::Load;
|
||||
g_gpWindowsGlobals.m_createBWCursorFunc = GpBWCursor_Win32::Create;
|
||||
g_gpWindowsGlobals.m_createColorCursorFunc = GpColorCursor_Win32::Create;
|
||||
g_gpWindowsGlobals.m_translateWindowsMessageFunc = TranslateWindowsMessage;
|
||||
|
||||
g_gpGlobalConfig.m_displayDriverType = EGpDisplayDriverType_D3D11;
|
||||
|
||||
g_gpGlobalConfig.m_audioDriverType = EGpAudioDriverType_XAudio2;
|
||||
|
||||
g_gpGlobalConfig.m_fontHandlerType = EGpFontHandlerType_FreeType2;
|
||||
|
||||
EGpInputDriverType inputDrivers[] =
|
||||
{
|
||||
EGpInputDriverType_XInput
|
||||
@@ -418,10 +450,23 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
|
||||
g_gpGlobalConfig.m_numInputDrivers = sizeof(inputDrivers) / sizeof(inputDrivers[0]);
|
||||
|
||||
g_gpGlobalConfig.m_osGlobals = &g_gpWindowsGlobals;
|
||||
g_gpGlobalConfig.m_logger = logger;
|
||||
g_gpGlobalConfig.m_systemServices = GpSystemServices_Win32::GetInstance();
|
||||
|
||||
GpDisplayDriverFactory::RegisterDisplayDriverFactory(EGpDisplayDriverType_D3D11, GpDriver_CreateDisplayDriver_D3D11);
|
||||
GpAudioDriverFactory::RegisterAudioDriverFactory(EGpAudioDriverType_XAudio2, GpDriver_CreateAudioDriver_XAudio2);
|
||||
GpInputDriverFactory::RegisterInputDriverFactory(EGpInputDriverType_XInput, GpDriver_CreateInputDriver_XInput);
|
||||
GpFontHandlerFactory::RegisterFontHandlerFactory(EGpFontHandlerType_FreeType2, GpDriver_CreateFontHandler_FreeType2);
|
||||
|
||||
return GpMain::Run();
|
||||
if (logger)
|
||||
logger->Printf(IGpLogDriver::Category_Information, "Windows environment configured, starting up");
|
||||
|
||||
int returnCode = GpMain::Run();
|
||||
|
||||
if (logger)
|
||||
logger->Printf(IGpLogDriver::Category_Information, "Windows environment exited with code %i, cleaning up", returnCode);
|
||||
|
||||
LocalFree(cmdLineArgs);
|
||||
|
||||
return returnCode;
|
||||
}
|
||||
|
@@ -1,49 +0,0 @@
|
||||
#include "GpMemoryBuffer.h"
|
||||
|
||||
#include <new>
|
||||
|
||||
void *GpMemoryBuffer::Contents()
|
||||
{
|
||||
return reinterpret_cast<uint8_t*>(this) + AlignedSize();
|
||||
}
|
||||
|
||||
size_t GpMemoryBuffer::Size()
|
||||
{
|
||||
return m_size;
|
||||
}
|
||||
|
||||
void GpMemoryBuffer::Destroy()
|
||||
{
|
||||
delete[] reinterpret_cast<uint8_t*>(this);
|
||||
}
|
||||
|
||||
GpMemoryBuffer *GpMemoryBuffer::Create(size_t sz)
|
||||
{
|
||||
const size_t allowedSize = SIZE_MAX - AlignedSize();
|
||||
if (sz > allowedSize)
|
||||
return nullptr;
|
||||
|
||||
const size_t bufferSize = GpMemoryBuffer::AlignedSize() + sz;
|
||||
|
||||
uint8_t *buffer = new uint8_t[bufferSize];
|
||||
new (buffer) GpMemoryBuffer(sz);
|
||||
|
||||
return reinterpret_cast<GpMemoryBuffer*>(buffer);
|
||||
}
|
||||
|
||||
GpMemoryBuffer::GpMemoryBuffer(size_t sz)
|
||||
: m_size(sz)
|
||||
{
|
||||
}
|
||||
|
||||
GpMemoryBuffer::~GpMemoryBuffer()
|
||||
{
|
||||
}
|
||||
|
||||
size_t GpMemoryBuffer::AlignedSize()
|
||||
{
|
||||
const size_t paddedSize = (sizeof(GpMemoryBuffer) + GP_SYSTEM_MEMORY_ALIGNMENT - 1);
|
||||
const size_t sz = paddedSize - paddedSize % GP_SYSTEM_MEMORY_ALIGNMENT;
|
||||
|
||||
return sz;
|
||||
}
|
@@ -1,21 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "HostMemoryBuffer.h"
|
||||
|
||||
class GpMemoryBuffer final : public PortabilityLayer::HostMemoryBuffer
|
||||
{
|
||||
public:
|
||||
void *Contents() override;
|
||||
size_t Size() override;
|
||||
void Destroy() override;
|
||||
|
||||
static GpMemoryBuffer *Create(size_t sz);
|
||||
|
||||
private:
|
||||
explicit GpMemoryBuffer(size_t sz);
|
||||
~GpMemoryBuffer();
|
||||
|
||||
static size_t AlignedSize();
|
||||
|
||||
size_t m_size;
|
||||
};
|
@@ -1,10 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "HostMutex.h"
|
||||
#include "IGpMutex.h"
|
||||
|
||||
#include "GpWindows.h"
|
||||
|
||||
class GpMutex_Win32 final : public PortabilityLayer::HostMutex
|
||||
class GpMutex_Win32 final : public IGpMutex
|
||||
{
|
||||
public:
|
||||
void Destroy() override;
|
||||
|
@@ -9,7 +9,28 @@
|
||||
#undef CreateMutex
|
||||
#endif
|
||||
|
||||
struct GpSystemServices_Win32_ThreadStartParams
|
||||
{
|
||||
GpSystemServices_Win32::ThreadFunc_t m_threadFunc;
|
||||
void *m_threadContext;
|
||||
IGpThreadEvent *m_threadStartEvent;
|
||||
};
|
||||
|
||||
static DWORD WINAPI StaticStartThread(LPVOID lpThreadParameter)
|
||||
{
|
||||
const GpSystemServices_Win32_ThreadStartParams *threadParams = static_cast<const GpSystemServices_Win32_ThreadStartParams*>(lpThreadParameter);
|
||||
|
||||
GpSystemServices_Win32::ThreadFunc_t threadFunc = threadParams->m_threadFunc;
|
||||
void *threadContext = threadParams->m_threadContext;
|
||||
IGpThreadEvent *threadStartEvent = threadParams->m_threadStartEvent;
|
||||
|
||||
threadStartEvent->Signal();
|
||||
|
||||
return threadFunc(threadContext);
|
||||
}
|
||||
|
||||
GpSystemServices_Win32::GpSystemServices_Win32()
|
||||
: m_isTouchscreenSimulation(false)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -51,16 +72,45 @@ void GpSystemServices_Win32::GetLocalDateTime(unsigned int &year, unsigned int &
|
||||
second = localTime.wSecond;
|
||||
}
|
||||
|
||||
PortabilityLayer::HostMutex *GpSystemServices_Win32::CreateMutex()
|
||||
IGpMutex *GpSystemServices_Win32::CreateMutex()
|
||||
{
|
||||
return GpMutex_Win32::Create();
|
||||
}
|
||||
|
||||
PortabilityLayer::HostThreadEvent *GpSystemServices_Win32::CreateThreadEvent(bool autoReset, bool startSignaled)
|
||||
IGpMutex *GpSystemServices_Win32::CreateRecursiveMutex()
|
||||
{
|
||||
return GpMutex_Win32::Create();
|
||||
}
|
||||
|
||||
IGpThreadEvent *GpSystemServices_Win32::CreateThreadEvent(bool autoReset, bool startSignaled)
|
||||
{
|
||||
return GpThreadEvent_Win32::Create(autoReset, startSignaled);
|
||||
}
|
||||
|
||||
void *GpSystemServices_Win32::CreateThread(ThreadFunc_t threadFunc, void *context)
|
||||
{
|
||||
IGpThreadEvent *evt = CreateThreadEvent(true, false);
|
||||
if (!evt)
|
||||
return nullptr;
|
||||
|
||||
GpSystemServices_Win32_ThreadStartParams startParams;
|
||||
startParams.m_threadContext = context;
|
||||
startParams.m_threadFunc = threadFunc;
|
||||
startParams.m_threadStartEvent = evt;
|
||||
|
||||
HANDLE threadHdl = ::CreateThread(nullptr, 0, StaticStartThread, &startParams, 0, nullptr);
|
||||
if (threadHdl == nullptr)
|
||||
{
|
||||
evt->Destroy();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
evt->Wait();
|
||||
evt->Destroy();
|
||||
|
||||
return threadHdl;
|
||||
}
|
||||
|
||||
uint64_t GpSystemServices_Win32::GetFreeMemoryCosmetic() const
|
||||
{
|
||||
MEMORYSTATUSEX memStatus;
|
||||
@@ -79,6 +129,53 @@ void GpSystemServices_Win32::Beep() const
|
||||
MessageBeep(MB_OK);
|
||||
}
|
||||
|
||||
bool GpSystemServices_Win32::IsTouchscreen() const
|
||||
{
|
||||
return m_isTouchscreenSimulation;
|
||||
}
|
||||
|
||||
bool GpSystemServices_Win32::IsUsingMouseAsTouch() const
|
||||
{
|
||||
return m_isTouchscreenSimulation;
|
||||
}
|
||||
|
||||
bool GpSystemServices_Win32::IsTextInputObstructive() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GpSystemServices_Win32::IsFullscreenPreferred() const
|
||||
{
|
||||
return !m_isTouchscreenSimulation;
|
||||
}
|
||||
|
||||
unsigned int GpSystemServices_Win32::GetCPUCount() const
|
||||
{
|
||||
SYSTEM_INFO sysInfo;
|
||||
GetSystemInfo(&sysInfo);
|
||||
return sysInfo.dwNumberOfProcessors;
|
||||
}
|
||||
|
||||
void GpSystemServices_Win32::SetTextInputEnabled(bool isEnabled)
|
||||
{
|
||||
(void)isEnabled;
|
||||
}
|
||||
|
||||
bool GpSystemServices_Win32::IsTextInputEnabled() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GpSystemServices_Win32::AreFontResourcesSeekable() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void GpSystemServices_Win32::SetTouchscreenSimulation(bool isTouchscreenSimulation)
|
||||
{
|
||||
m_isTouchscreenSimulation = isTouchscreenSimulation;
|
||||
}
|
||||
|
||||
GpSystemServices_Win32 *GpSystemServices_Win32::GetInstance()
|
||||
{
|
||||
|
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "HostSystemServices.h"
|
||||
#include "IGpSystemServices.h"
|
||||
#include "GpCoreDefs.h"
|
||||
#include "GpWindows.h"
|
||||
|
||||
@@ -9,23 +9,43 @@
|
||||
#undef CreateMutex
|
||||
#endif
|
||||
|
||||
#pragma push_macro("CreateThread")
|
||||
#ifdef CreateThread
|
||||
#undef CreateThread
|
||||
#endif
|
||||
|
||||
class GpSystemServices_Win32 final : public PortabilityLayer::HostSystemServices
|
||||
|
||||
class GpSystemServices_Win32 final : public IGpSystemServices
|
||||
{
|
||||
public:
|
||||
GpSystemServices_Win32();
|
||||
|
||||
int64_t GetTime() const override;
|
||||
void GetLocalDateTime(unsigned int &year, unsigned int &month, unsigned int &day, unsigned int &hour, unsigned int &minute, unsigned int &second) const override;
|
||||
PortabilityLayer::HostMutex *CreateMutex() override;
|
||||
PortabilityLayer::HostThreadEvent *CreateThreadEvent(bool autoReset, bool startSignaled) override;
|
||||
IGpMutex *CreateMutex() override;
|
||||
IGpMutex *CreateRecursiveMutex() override;
|
||||
void *CreateThread(ThreadFunc_t threadFunc, void *context) override;
|
||||
IGpThreadEvent *CreateThreadEvent(bool autoReset, bool startSignaled) override;
|
||||
uint64_t GetFreeMemoryCosmetic() const override;
|
||||
void Beep() const override;
|
||||
bool IsTouchscreen() const override;
|
||||
bool IsUsingMouseAsTouch() const override;
|
||||
bool IsTextInputObstructive() const override;
|
||||
bool IsFullscreenPreferred() const override;
|
||||
unsigned int GetCPUCount() const override;
|
||||
void SetTextInputEnabled(bool isEnabled) override;
|
||||
bool IsTextInputEnabled() const override;
|
||||
bool AreFontResourcesSeekable() const override;
|
||||
|
||||
void SetTouchscreenSimulation(bool isTouchscreenSimulation);
|
||||
|
||||
static GpSystemServices_Win32 *GetInstance();
|
||||
|
||||
private:
|
||||
bool m_isTouchscreenSimulation;
|
||||
|
||||
static GpSystemServices_Win32 ms_instance;
|
||||
};
|
||||
|
||||
#pragma pop_macro("CreateMutex")
|
||||
#pragma pop_macro("CreateThread")
|
||||
|
@@ -8,9 +8,9 @@ void GpThreadEvent_Win32::Wait()
|
||||
WaitForSingleObject(m_event, INFINITE);
|
||||
}
|
||||
|
||||
void GpThreadEvent_Win32::WaitTimed(uint32_t msec)
|
||||
bool GpThreadEvent_Win32::WaitTimed(uint32_t msec)
|
||||
{
|
||||
WaitForSingleObject(m_event, static_cast<DWORD>(msec));
|
||||
return WaitForSingleObject(m_event, static_cast<DWORD>(msec)) == WAIT_OBJECT_0;
|
||||
}
|
||||
|
||||
void GpThreadEvent_Win32::Signal()
|
||||
|
@@ -1,14 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include "HostThreadEvent.h"
|
||||
#include "IGpThreadEvent.h"
|
||||
|
||||
#include "GpWindows.h"
|
||||
|
||||
class GpThreadEvent_Win32 final : public PortabilityLayer::HostThreadEvent
|
||||
class GpThreadEvent_Win32 final : public IGpThreadEvent
|
||||
{
|
||||
public:
|
||||
void Wait() override;
|
||||
void WaitTimed(uint32_t msec) override;
|
||||
bool WaitTimed(uint32_t msec) override;
|
||||
void Signal() override;
|
||||
void Destroy() override;
|
||||
|
||||
|
5
AerofoilAndroid/.gitignore
vendored
Normal file
5
AerofoilAndroid/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
.idea
|
||||
.gradle
|
||||
gradle
|
||||
local.properties
|
||||
build
|
5
AerofoilAndroid/GPSPrivacyPolicy.txt
Normal file
5
AerofoilAndroid/GPSPrivacyPolicy.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
Aerofoil - Google Play Store Distribution - Privacy Policy
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
The Aerofoil app does not collect or transmit any personal information,
|
||||
sensitive information, or any other form of user data.
|
4
AerofoilAndroid/app/.gitignore
vendored
Normal file
4
AerofoilAndroid/app/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
.cxx
|
||||
.externalNativeBuild
|
||||
build
|
||||
release
|
77
AerofoilAndroid/app/build.gradle
Normal file
77
AerofoilAndroid/app/build.gradle
Normal file
@@ -0,0 +1,77 @@
|
||||
def buildAsLibrary = project.hasProperty('BUILD_AS_LIBRARY');
|
||||
def buildAsApplication = !buildAsLibrary
|
||||
if (buildAsApplication) {
|
||||
apply plugin: 'com.android.application'
|
||||
}
|
||||
else {
|
||||
apply plugin: 'com.android.library'
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 29
|
||||
defaultConfig {
|
||||
if (buildAsApplication) {
|
||||
applicationId "org.thecodedeposit.aerofoil"
|
||||
}
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 29
|
||||
versionCode 7
|
||||
versionName "1.0.11"
|
||||
externalNativeBuild {
|
||||
ndkBuild {
|
||||
arguments "APP_PLATFORM=android-16"
|
||||
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
|
||||
}
|
||||
// cmake {
|
||||
// arguments "-DANDROID_APP_PLATFORM=android-16", "-DANDROID_STL=c++_static"
|
||||
// // abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
|
||||
// abiFilters 'arm64-v8a'
|
||||
// }
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
if (!project.hasProperty('EXCLUDE_NATIVE_LIBS')) {
|
||||
sourceSets.main {
|
||||
jniLibs.srcDir 'libs'
|
||||
}
|
||||
externalNativeBuild {
|
||||
ndkBuild {
|
||||
path 'jni/Android.mk'
|
||||
}
|
||||
// cmake {
|
||||
// path 'jni/CMakeLists.txt'
|
||||
// }
|
||||
}
|
||||
|
||||
}
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
}
|
||||
|
||||
if (buildAsLibrary) {
|
||||
libraryVariants.all { variant ->
|
||||
variant.outputs.each { output ->
|
||||
def outputFile = output.outputFile
|
||||
if (outputFile != null && outputFile.name.endsWith(".aar")) {
|
||||
def fileName = "org.thecodedeposit.aerofoil.aar";
|
||||
output.outputFile = new File(outputFile.parent, fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
aaptOptions {
|
||||
noCompress 'gpa'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||
}
|
||||
|
||||
android.buildTypes.release.ndk.debugSymbolLevel = 'FULL'
|
13
AerofoilAndroid/app/jni/.gitignore
vendored
Normal file
13
AerofoilAndroid/app/jni/.gitignore
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
AerofoilSDL
|
||||
Common
|
||||
FreeType
|
||||
GpApp
|
||||
GpCommon
|
||||
GpFontHandler_FreeType2
|
||||
GpShell
|
||||
MacRomanConversion
|
||||
PortabilityLayer
|
||||
rapidjson
|
||||
SDL2
|
||||
stb
|
||||
zlib
|
1
AerofoilAndroid/app/jni/Android.mk
Normal file
1
AerofoilAndroid/app/jni/Android.mk
Normal file
@@ -0,0 +1 @@
|
||||
include $(call all-subdir-makefiles)
|
10
AerofoilAndroid/app/jni/Application.mk
Normal file
10
AerofoilAndroid/app/jni/Application.mk
Normal file
@@ -0,0 +1,10 @@
|
||||
|
||||
# Uncomment this if you're using STL in your project
|
||||
# You can find more information here:
|
||||
# https://developer.android.com/ndk/guides/cpp-support
|
||||
APP_STL := c++_shared
|
||||
|
||||
APP_ABI := armeabi-v7a arm64-v8a x86 x86_64
|
||||
|
||||
# Min runtime API level
|
||||
APP_PLATFORM=android-16
|
20
AerofoilAndroid/app/jni/CMakeLists.txt
Normal file
20
AerofoilAndroid/app/jni/CMakeLists.txt
Normal file
@@ -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)
|
||||
|
30
AerofoilAndroid/app/jni/main/Android.mk
Normal file
30
AerofoilAndroid/app/jni/main/Android.mk
Normal file
@@ -0,0 +1,30 @@
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_MODULE := main
|
||||
|
||||
SDL_PATH := ../SDL
|
||||
|
||||
LOCAL_C_INCLUDES := $(LOCAL_PATH)/$(SDL_PATH)/include \
|
||||
$(LOCAL_PATH)/../GpShell \
|
||||
$(LOCAL_PATH)/../GpCommon \
|
||||
$(LOCAL_PATH)/../AerofoilSDL \
|
||||
$(LOCAL_PATH)/../Common \
|
||||
$(LOCAL_PATH)/../PortabilityLayer
|
||||
|
||||
LOCAL_CFLAGS := -DGP_DEBUG_CONFIG=0
|
||||
|
||||
# Add your application source files here...
|
||||
LOCAL_SRC_FILES := \
|
||||
GpMain_SDL_Android.cpp \
|
||||
GpSystemServices_Android.cpp \
|
||||
GpFileSystem_Android.cpp
|
||||
|
||||
LOCAL_SHARED_LIBRARIES := SDL2
|
||||
|
||||
LOCAL_STATIC_LIBRARIES := GpShell GpFontHandler_FreeType2 AerofoilSDL GpApp
|
||||
|
||||
LOCAL_LDLIBS := -lGLESv1_CM -lGLESv2 -llog
|
||||
|
||||
include $(BUILD_SHARED_LIBRARY)
|
13
AerofoilAndroid/app/jni/main/CMakeLists.txt
Normal file
13
AerofoilAndroid/app/jni/main/CMakeLists.txt
Normal file
@@ -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)
|
||||
|
||||
|
5
AerofoilAndroid/app/jni/main/GpAndroid.h
Normal file
5
AerofoilAndroid/app/jni/main/GpAndroid.h
Normal file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
struct GpAndroidGlobals
|
||||
{
|
||||
};
|
944
AerofoilAndroid/app/jni/main/GpFileSystem_Android.cpp
Normal file
944
AerofoilAndroid/app/jni/main/GpFileSystem_Android.cpp
Normal file
@@ -0,0 +1,944 @@
|
||||
#define _LARGEFILE64_SOURCE
|
||||
#include "GpFileSystem_Android.h"
|
||||
#include "GpIOStream.h"
|
||||
#include "IGpDirectoryCursor.h"
|
||||
#include "IGpSystemServices.h"
|
||||
#include "IGpMutex.h"
|
||||
#include "IGpThreadRelay.h"
|
||||
#include "VirtualDirectory.h"
|
||||
|
||||
#include "PLDrivers.h"
|
||||
|
||||
#include "SDL.h"
|
||||
#include "SDL_rwops.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <dirent.h>
|
||||
#include <jni.h>
|
||||
#include "UTF8.h"
|
||||
|
||||
JNIEXPORT void JNICALL nativePostSourceExportRequest(JNIEnv *env, jclass cls, jboolean cancelled, jint fd, jobject pfd);
|
||||
|
||||
static JNINativeMethod GpFileSystemAPI_tab[] =
|
||||
{
|
||||
{ "nativePostSourceExportRequest", "(ZILjava/lang/Object;)V", reinterpret_cast<void*>(nativePostSourceExportRequest) },
|
||||
};
|
||||
|
||||
class GpFileStream_PFD final : public GpIOStream
|
||||
{
|
||||
public:
|
||||
GpFileStream_PFD(GpFileSystem_Android *fs, int fd, jobject pfd, bool readOnly, bool writeOnly);
|
||||
~GpFileStream_PFD();
|
||||
|
||||
size_t Read(void *bytesOut, size_t size) override;
|
||||
size_t Write(const void *bytes, size_t size) override;
|
||||
bool IsSeekable() const override;
|
||||
bool IsReadOnly() const override;
|
||||
bool IsWriteOnly() const override;
|
||||
bool SeekStart(GpUFilePos_t loc) override;
|
||||
bool SeekCurrent(GpFilePos_t loc) override;
|
||||
bool SeekEnd(GpUFilePos_t loc) override;
|
||||
bool Truncate(GpUFilePos_t loc) override;
|
||||
GpUFilePos_t Size() const override;
|
||||
GpUFilePos_t Tell() const override;
|
||||
void Close() override;
|
||||
void Flush() override;
|
||||
|
||||
private:
|
||||
GpFileSystem_Android *m_fs;
|
||||
int m_fd;
|
||||
jobject m_pfd;
|
||||
bool m_readOnly;
|
||||
bool m_writeOnly;
|
||||
};
|
||||
|
||||
class GpFileStream_SDLRWops final : public GpIOStream
|
||||
{
|
||||
public:
|
||||
GpFileStream_SDLRWops(SDL_RWops *f, bool readOnly, bool writeOnly);
|
||||
~GpFileStream_SDLRWops();
|
||||
|
||||
size_t Read(void *bytesOut, size_t size) override;
|
||||
size_t Write(const void *bytes, size_t size) override;
|
||||
bool IsSeekable() const override;
|
||||
bool IsReadOnly() const override;
|
||||
bool IsWriteOnly() const override;
|
||||
bool SeekStart(GpUFilePos_t loc) override;
|
||||
bool SeekCurrent(GpFilePos_t loc) override;
|
||||
bool SeekEnd(GpUFilePos_t loc) override;
|
||||
bool Truncate(GpUFilePos_t loc) override;
|
||||
GpUFilePos_t Size() const override;
|
||||
GpUFilePos_t Tell() const override;
|
||||
void Close() override;
|
||||
void Flush() override;
|
||||
|
||||
private:
|
||||
SDL_RWops *m_rw;
|
||||
bool m_isReadOnly;
|
||||
bool m_isWriteOnly;
|
||||
};
|
||||
|
||||
class GpFileStream_Android_File final : public GpIOStream
|
||||
{
|
||||
public:
|
||||
GpFileStream_Android_File(FILE *f, int fd, bool readOnly, bool writeOnly);
|
||||
~GpFileStream_Android_File();
|
||||
|
||||
size_t Read(void *bytesOut, size_t size) override;
|
||||
size_t Write(const void *bytes, size_t size) override;
|
||||
bool IsSeekable() const override;
|
||||
bool IsReadOnly() const override;
|
||||
bool IsWriteOnly() const override;
|
||||
bool SeekStart(GpUFilePos_t loc) override;
|
||||
bool SeekCurrent(GpFilePos_t loc) override;
|
||||
bool SeekEnd(GpUFilePos_t loc) override;
|
||||
bool Truncate(GpUFilePos_t loc) override;
|
||||
GpUFilePos_t Size() const override;
|
||||
GpUFilePos_t Tell() const override;
|
||||
void Close() override;
|
||||
void Flush() override;
|
||||
|
||||
private:
|
||||
FILE *m_f;
|
||||
int m_fd;
|
||||
bool m_seekable;
|
||||
bool m_isReadOnly;
|
||||
bool m_isWriteOnly;
|
||||
};
|
||||
|
||||
GpFileStream_PFD::GpFileStream_PFD(GpFileSystem_Android *fs, int fd, jobject pfd, bool readOnly, bool writeOnly)
|
||||
: m_fs(fs)
|
||||
, m_fd(fd)
|
||||
, m_readOnly(readOnly)
|
||||
, m_writeOnly(writeOnly)
|
||||
, m_pfd(pfd)
|
||||
{
|
||||
}
|
||||
|
||||
GpFileStream_PFD::~GpFileStream_PFD()
|
||||
{
|
||||
m_fs->ClosePFD(m_pfd);
|
||||
}
|
||||
|
||||
size_t GpFileStream_PFD::Read(void *bytesOut, size_t size)
|
||||
{
|
||||
if (m_writeOnly)
|
||||
return 0;
|
||||
return read(m_fd, bytesOut, size);
|
||||
}
|
||||
|
||||
size_t GpFileStream_PFD::Write(const void *bytes, size_t size)
|
||||
{
|
||||
if (m_readOnly)
|
||||
return 0;
|
||||
return write(m_fd, bytes, size);
|
||||
}
|
||||
|
||||
bool GpFileStream_PFD::IsSeekable() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GpFileStream_PFD::IsReadOnly() const
|
||||
{
|
||||
return m_readOnly;
|
||||
}
|
||||
|
||||
bool GpFileStream_PFD::IsWriteOnly() const
|
||||
{
|
||||
return m_writeOnly;
|
||||
}
|
||||
|
||||
bool GpFileStream_PFD::SeekStart(GpUFilePos_t loc)
|
||||
{
|
||||
return lseek64(m_fd, loc, SEEK_SET) >= 0;
|
||||
}
|
||||
|
||||
bool GpFileStream_PFD::SeekCurrent(GpFilePos_t loc)
|
||||
{
|
||||
return lseek64(m_fd, loc, SEEK_CUR) >= 0;
|
||||
}
|
||||
|
||||
bool GpFileStream_PFD::SeekEnd(GpUFilePos_t loc)
|
||||
{
|
||||
return lseek64(m_fd, loc, SEEK_END) >= 0;
|
||||
}
|
||||
|
||||
bool GpFileStream_PFD::Truncate(GpUFilePos_t loc)
|
||||
{
|
||||
return ftruncate64(m_fd, static_cast<off64_t>(loc)) >= 0;
|
||||
}
|
||||
|
||||
GpUFilePos_t GpFileStream_PFD::Size() const
|
||||
{
|
||||
struct stat64 s;
|
||||
if (fstat64(m_fd, &s) < 0)
|
||||
return 0;
|
||||
|
||||
return static_cast<GpUFilePos_t>(s.st_size);
|
||||
}
|
||||
|
||||
GpUFilePos_t GpFileStream_PFD::Tell() const
|
||||
{
|
||||
return lseek64(m_fd, 0, SEEK_CUR);
|
||||
}
|
||||
|
||||
void GpFileStream_PFD::Close()
|
||||
{
|
||||
this->~GpFileStream_PFD();
|
||||
free(this);
|
||||
}
|
||||
|
||||
void GpFileStream_PFD::Flush()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
GpFileStream_SDLRWops::GpFileStream_SDLRWops(SDL_RWops *f, bool readOnly, bool writeOnly)
|
||||
: m_rw(f)
|
||||
, m_isReadOnly(readOnly)
|
||||
, m_isWriteOnly(writeOnly)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
GpFileStream_SDLRWops::~GpFileStream_SDLRWops()
|
||||
{
|
||||
m_rw->close(m_rw);
|
||||
}
|
||||
|
||||
size_t GpFileStream_SDLRWops::Read(void *bytesOut, size_t size)
|
||||
{
|
||||
return m_rw->read(m_rw, bytesOut, 1, size);
|
||||
}
|
||||
|
||||
size_t GpFileStream_SDLRWops::Write(const void *bytes, size_t size)
|
||||
{
|
||||
return m_rw->write(m_rw, bytes, 1, size);
|
||||
}
|
||||
|
||||
bool GpFileStream_SDLRWops::IsSeekable() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GpFileStream_SDLRWops::IsReadOnly() const
|
||||
{
|
||||
return m_isReadOnly;
|
||||
}
|
||||
|
||||
bool GpFileStream_SDLRWops::IsWriteOnly() const
|
||||
{
|
||||
return m_isWriteOnly;
|
||||
}
|
||||
|
||||
bool GpFileStream_SDLRWops::SeekStart(GpUFilePos_t loc)
|
||||
{
|
||||
return m_rw->seek(m_rw, static_cast<Sint64>(loc), RW_SEEK_SET) >= 0;
|
||||
}
|
||||
|
||||
bool GpFileStream_SDLRWops::SeekCurrent(GpFilePos_t loc)
|
||||
{
|
||||
return m_rw->seek(m_rw, static_cast<Sint64>(loc), RW_SEEK_CUR) >= 0;
|
||||
}
|
||||
|
||||
bool GpFileStream_SDLRWops::SeekEnd(GpUFilePos_t loc)
|
||||
{
|
||||
return m_rw->seek(m_rw, -static_cast<Sint64>(loc), RW_SEEK_END) >= 0;
|
||||
}
|
||||
|
||||
bool GpFileStream_SDLRWops::Truncate(GpUFilePos_t loc)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
GpUFilePos_t GpFileStream_SDLRWops::Size() const
|
||||
{
|
||||
return m_rw->size(m_rw);
|
||||
}
|
||||
|
||||
GpUFilePos_t GpFileStream_SDLRWops::GpFileStream_SDLRWops::Tell() const
|
||||
{
|
||||
return SDL_RWtell(m_rw);
|
||||
}
|
||||
|
||||
void GpFileStream_SDLRWops::Close()
|
||||
{
|
||||
this->~GpFileStream_SDLRWops();
|
||||
free(this);
|
||||
}
|
||||
|
||||
void GpFileStream_SDLRWops::Flush()
|
||||
{
|
||||
}
|
||||
|
||||
GpFileStream_Android_File::GpFileStream_Android_File(FILE *f, int fd, bool readOnly, bool writeOnly)
|
||||
: m_f(f)
|
||||
, m_fd(fd)
|
||||
, m_isReadOnly(readOnly)
|
||||
, m_isWriteOnly(writeOnly)
|
||||
{
|
||||
m_seekable = (fseek(m_f, 0, SEEK_CUR) == 0);
|
||||
}
|
||||
|
||||
GpFileStream_Android_File::~GpFileStream_Android_File()
|
||||
{
|
||||
fclose(m_f);
|
||||
}
|
||||
|
||||
size_t GpFileStream_Android_File::Read(void *bytesOut, size_t size)
|
||||
{
|
||||
if (m_isWriteOnly)
|
||||
return 0;
|
||||
return fread(bytesOut, 1, size, m_f);
|
||||
}
|
||||
|
||||
size_t GpFileStream_Android_File::Write(const void *bytes, size_t size)
|
||||
{
|
||||
if (m_isReadOnly)
|
||||
return 0;
|
||||
return fwrite(bytes, 1, size, m_f);
|
||||
}
|
||||
|
||||
bool GpFileStream_Android_File::IsSeekable() const
|
||||
{
|
||||
return m_seekable;
|
||||
}
|
||||
|
||||
bool GpFileStream_Android_File::IsReadOnly() const
|
||||
{
|
||||
return m_isReadOnly;
|
||||
}
|
||||
|
||||
bool GpFileStream_Android_File::IsWriteOnly() const
|
||||
{
|
||||
return m_isWriteOnly;
|
||||
}
|
||||
|
||||
bool GpFileStream_Android_File::SeekStart(GpUFilePos_t loc)
|
||||
{
|
||||
if (!m_seekable)
|
||||
return false;
|
||||
|
||||
fflush(m_f);
|
||||
return lseek64(m_fd, static_cast<off64_t>(loc), SEEK_SET) >= 0;
|
||||
}
|
||||
|
||||
bool GpFileStream_Android_File::SeekCurrent(GpFilePos_t loc)
|
||||
{
|
||||
if (!m_seekable)
|
||||
return false;
|
||||
|
||||
fflush(m_f);
|
||||
return lseek64(m_fd, static_cast<off64_t>(loc), SEEK_CUR) >= 0;
|
||||
}
|
||||
|
||||
bool GpFileStream_Android_File::SeekEnd(GpUFilePos_t loc)
|
||||
{
|
||||
if (!m_seekable)
|
||||
return false;
|
||||
|
||||
fflush(m_f);
|
||||
return lseek64(m_fd, -static_cast<off64_t>(loc), SEEK_END) >= 0;
|
||||
}
|
||||
|
||||
bool GpFileStream_Android_File::Truncate(GpUFilePos_t loc)
|
||||
{
|
||||
fflush(m_f);
|
||||
return ftruncate64(m_fd, static_cast<off64_t>(loc)) >= 0;
|
||||
}
|
||||
|
||||
GpUFilePos_t GpFileStream_Android_File::Size() const
|
||||
{
|
||||
fflush(m_f);
|
||||
|
||||
struct stat64 s;
|
||||
if (fstat64(m_fd, &s) < 0)
|
||||
return 0;
|
||||
|
||||
return static_cast<GpUFilePos_t>(s.st_size);
|
||||
}
|
||||
|
||||
GpUFilePos_t GpFileStream_Android_File::Tell() const
|
||||
{
|
||||
return static_cast<GpUFilePos_t>(ftell(m_f));
|
||||
}
|
||||
|
||||
void GpFileStream_Android_File::Close()
|
||||
{
|
||||
this->~GpFileStream_Android_File();
|
||||
free(this);
|
||||
}
|
||||
|
||||
void GpFileStream_Android_File::Flush()
|
||||
{
|
||||
fflush(m_f);
|
||||
}
|
||||
|
||||
bool GpFileSystem_Android::OpenSourceExportFD(PortabilityLayer::VirtualDirectory_t virtualDirectory, const char *const *paths, size_t numPaths, int &fd, jobject &pfd)
|
||||
{
|
||||
if (!m_sourceExportMutex)
|
||||
m_sourceExportMutex = PLDrivers::GetSystemServices()->CreateMutex();
|
||||
|
||||
m_sourceExportWaiting = true;
|
||||
m_sourceExportCancelled = false;
|
||||
|
||||
JNIEnv *jni = static_cast<JNIEnv *>(SDL_AndroidGetJNIEnv());
|
||||
|
||||
jstring fname = jni->NewStringUTF(paths[0]);
|
||||
|
||||
jni->CallVoidMethod(m_activity, this->m_selectSourceExportPathMID, fname);
|
||||
jni->DeleteLocalRef(fname);
|
||||
|
||||
for (;;)
|
||||
{
|
||||
m_sourceExportMutex->Lock();
|
||||
const bool isWaiting = m_sourceExportWaiting;
|
||||
m_sourceExportMutex->Unlock();
|
||||
|
||||
if (!isWaiting)
|
||||
break;
|
||||
|
||||
m_delayCallback(5);
|
||||
}
|
||||
|
||||
fd = m_sourceExportFD;
|
||||
pfd = m_sourceExportPFD;
|
||||
|
||||
return !m_sourceExportCancelled;
|
||||
}
|
||||
|
||||
bool GpFileSystem_Android::ResolvePath(PortabilityLayer::VirtualDirectory_t virtualDirectory, char const* const* paths, size_t numPaths, std::string &resolution, bool &isAsset)
|
||||
{
|
||||
const char *prefsAppend = nullptr;
|
||||
|
||||
isAsset = false;
|
||||
switch (virtualDirectory)
|
||||
{
|
||||
case PortabilityLayer::VirtualDirectories::kApplicationData:
|
||||
resolution = std::string("Packaged") ;
|
||||
isAsset = true;
|
||||
break;
|
||||
case PortabilityLayer::VirtualDirectories::kGameData:
|
||||
resolution = std::string("Packaged/Houses");
|
||||
isAsset = true;
|
||||
break;
|
||||
case PortabilityLayer::VirtualDirectories::kFonts:
|
||||
resolution = std::string("Resources");
|
||||
isAsset = true;
|
||||
break;
|
||||
case PortabilityLayer::VirtualDirectories::kHighScores:
|
||||
prefsAppend = "HighScores";
|
||||
break;
|
||||
case PortabilityLayer::VirtualDirectories::kUserData:
|
||||
prefsAppend = "Houses";
|
||||
break;
|
||||
case PortabilityLayer::VirtualDirectories::kUserSaves:
|
||||
prefsAppend = "SavedGames";
|
||||
break;
|
||||
case PortabilityLayer::VirtualDirectories::kPrefs:
|
||||
prefsAppend = "Prefs";
|
||||
break;
|
||||
case PortabilityLayer::VirtualDirectories::kFontCache:
|
||||
prefsAppend = "FontCache";
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
};
|
||||
|
||||
if (prefsAppend)
|
||||
{
|
||||
char *prefsDir = SDL_GetPrefPath("aerofoil", "aerofoil");
|
||||
resolution = prefsDir;
|
||||
SDL_free(prefsDir);
|
||||
resolution += prefsAppend;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < numPaths; i++)
|
||||
{
|
||||
resolution += "/";
|
||||
resolution += paths[i];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
GpFileSystem_Android::GpFileSystem_Android()
|
||||
: m_activity(nullptr)
|
||||
, m_relay(nullptr)
|
||||
, m_delayCallback(nullptr)
|
||||
, m_sourceExportMutex(nullptr)
|
||||
, m_sourceExportFD(0)
|
||||
, m_sourceExportWaiting(false)
|
||||
, m_sourceExportCancelled(false)
|
||||
, m_sourceExportPFD(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
GpFileSystem_Android::~GpFileSystem_Android()
|
||||
{
|
||||
}
|
||||
|
||||
void GpFileSystem_Android::InitJNI()
|
||||
{
|
||||
JNIEnv *jni = static_cast<JNIEnv *>(SDL_AndroidGetJNIEnv());
|
||||
|
||||
jclass fileSystemAPIClass = jni->FindClass("org/thecodedeposit/aerofoil/GpFileSystemAPI");
|
||||
int registerStatus = jni->RegisterNatives(fileSystemAPIClass, GpFileSystemAPI_tab, sizeof(GpFileSystemAPI_tab) / sizeof(GpFileSystemAPI_tab[0]));
|
||||
jni->DeleteLocalRef(fileSystemAPIClass);
|
||||
|
||||
jobject activityLR = static_cast<jobject>(SDL_AndroidGetActivity());
|
||||
jclass activityClassLR = static_cast<jclass>(jni->GetObjectClass(activityLR));
|
||||
|
||||
m_scanAssetDirectoryMID = jni->GetMethodID(activityClassLR, "scanAssetDirectory", "(Ljava/lang/String;)[Ljava/lang/String;");
|
||||
m_selectSourceExportPathMID = jni->GetMethodID(activityClassLR, "selectSourceExportPath", "(Ljava/lang/String;)V");
|
||||
m_closeSourceExportPFDMID = jni->GetMethodID(activityClassLR, "closeSourceExportPFD", "(Ljava/lang/Object;)V");
|
||||
|
||||
m_activity = jni->NewGlobalRef(activityLR);
|
||||
|
||||
jni->DeleteLocalRef(activityLR);
|
||||
jni->DeleteLocalRef(activityClassLR);
|
||||
|
||||
char *prefsDir = SDL_GetPrefPath("aerofoil", "aerofoil");
|
||||
size_t prefsDirLen = strlen(prefsDir);
|
||||
for (size_t i = 0; i < prefsDirLen; i++)
|
||||
{
|
||||
if (prefsDir[i] == '/')
|
||||
{
|
||||
prefsDir[i] = '\0';
|
||||
int created = mkdir(prefsDir, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
|
||||
prefsDir[i] = '/';
|
||||
}
|
||||
}
|
||||
const char *extensions[] = { "HighScores", "Houses", "SavedGames", "Prefs", "FontCache" };
|
||||
for (size_t i = 0; i < sizeof(extensions) / sizeof(extensions[0]); i++)
|
||||
{
|
||||
std::string prefsPath = std::string(prefsDir) + extensions[i];
|
||||
int created = mkdir(prefsPath.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
|
||||
int n = 0;
|
||||
}
|
||||
SDL_free(prefsDir);
|
||||
}
|
||||
|
||||
void GpFileSystem_Android::ShutdownJNI()
|
||||
{
|
||||
JNIEnv *jni = static_cast<JNIEnv *>(SDL_AndroidGetJNIEnv());
|
||||
|
||||
jni->DeleteGlobalRef(m_activity);
|
||||
m_activity = nullptr;
|
||||
}
|
||||
|
||||
bool GpFileSystem_Android::FileExists(PortabilityLayer::VirtualDirectory_t virtualDirectory, const char *path)
|
||||
{
|
||||
std::string resolvedPath;
|
||||
bool isAsset;
|
||||
if (!ResolvePath(virtualDirectory, &path, 1, resolvedPath, isAsset))
|
||||
return false;
|
||||
|
||||
if (isAsset)
|
||||
{
|
||||
SDL_RWops *rw = SDL_RWFromFile(resolvedPath.c_str(), "rb");
|
||||
if (!rw)
|
||||
return false;
|
||||
SDL_RWclose(rw);
|
||||
return true;
|
||||
}
|
||||
|
||||
struct stat s;
|
||||
return stat(resolvedPath.c_str(), &s) == 0;
|
||||
}
|
||||
|
||||
bool GpFileSystem_Android::FileLocked(PortabilityLayer::VirtualDirectory_t virtualDirectory, const char *path, bool *exists)
|
||||
{
|
||||
std::string resolvedPath;
|
||||
bool isAsset;
|
||||
if (!ResolvePath(virtualDirectory, &path, 1, resolvedPath, isAsset))
|
||||
{
|
||||
if (exists)
|
||||
*exists = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isAsset)
|
||||
{
|
||||
if (exists)
|
||||
*exists = this->FileExists(virtualDirectory, path);
|
||||
return true;
|
||||
}
|
||||
|
||||
int permissions = access(resolvedPath.c_str(), W_OK | F_OK);
|
||||
*exists = ((permissions & F_OK) != 0);
|
||||
return ((permissions & W_OK) != 0);
|
||||
}
|
||||
|
||||
GpIOStream *GpFileSystem_Android::OpenFileNested(PortabilityLayer::VirtualDirectory_t virtualDirectory, char const* const* subPaths, size_t numSubPaths, bool writeAccess, GpFileCreationDisposition_t createDisposition)
|
||||
{
|
||||
const char *mode = nullptr;
|
||||
bool canWrite = false;
|
||||
bool needResetPosition = false;
|
||||
|
||||
switch (createDisposition)
|
||||
{
|
||||
case GpFileCreationDispositions::kCreateOrOverwrite:
|
||||
mode = "w+b";
|
||||
break;
|
||||
case GpFileCreationDispositions::kCreateNew:
|
||||
mode = "x+b";
|
||||
break;
|
||||
case GpFileCreationDispositions::kCreateOrOpen:
|
||||
mode = "a+b";
|
||||
needResetPosition = true;
|
||||
break;
|
||||
case GpFileCreationDispositions::kOpenExisting:
|
||||
mode = writeAccess ? "r+b" : "rb";
|
||||
break;
|
||||
case GpFileCreationDispositions::kOverwriteExisting:
|
||||
mode = "r+b";
|
||||
break;
|
||||
default:
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
if (virtualDirectory == PortabilityLayer::VirtualDirectories::kSourceExport)
|
||||
{
|
||||
void *objStorage = malloc(sizeof(GpFileStream_PFD));
|
||||
if (!objStorage)
|
||||
return nullptr;
|
||||
|
||||
int fd = 0;
|
||||
jobject pfd = nullptr;
|
||||
const bool resolved = OpenSourceExportFD(virtualDirectory, subPaths, numSubPaths, fd, pfd);
|
||||
if (!resolved)
|
||||
{
|
||||
free(objStorage);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return new (objStorage) GpFileStream_PFD(this, fd, pfd, false, true);
|
||||
}
|
||||
|
||||
std::string resolvedPath;
|
||||
bool isAsset;
|
||||
if (!ResolvePath(virtualDirectory, subPaths, numSubPaths, resolvedPath, isAsset))
|
||||
return nullptr;
|
||||
|
||||
if (isAsset)
|
||||
{
|
||||
if (createDisposition == GpFileCreationDispositions::kOverwriteExisting || writeAccess)
|
||||
return nullptr;
|
||||
|
||||
void *objStorage = malloc(sizeof(GpFileStream_SDLRWops));
|
||||
if (!objStorage)
|
||||
return nullptr;
|
||||
|
||||
SDL_RWops *rw = SDL_RWFromFile(resolvedPath.c_str(), mode);
|
||||
if (!rw)
|
||||
{
|
||||
free(objStorage);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return new (objStorage) GpFileStream_SDLRWops(rw, true, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
void *objStorage = malloc(sizeof(GpFileStream_Android_File));
|
||||
if (!objStorage)
|
||||
return nullptr;
|
||||
|
||||
FILE *f = fopen(resolvedPath.c_str(), mode);
|
||||
if (!f)
|
||||
{
|
||||
free(objStorage);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (needResetPosition)
|
||||
fseek(f, 0, SEEK_SET);
|
||||
|
||||
int fd = fileno(f);
|
||||
|
||||
if (createDisposition == GpFileCreationDispositions::kOverwriteExisting)
|
||||
{
|
||||
if (ftruncate64(fd, 0) < 0)
|
||||
{
|
||||
free(objStorage);
|
||||
fclose(f);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return new (objStorage) GpFileStream_Android_File(f, fd, !writeAccess, false);
|
||||
}
|
||||
}
|
||||
|
||||
bool GpFileSystem_Android::DeleteFile(PortabilityLayer::VirtualDirectory_t virtualDirectory, const char *path, bool &existed)
|
||||
{
|
||||
std::string resolvedPath;
|
||||
bool isAsset;
|
||||
if (!ResolvePath(virtualDirectory, &path, 1, resolvedPath, isAsset))
|
||||
{
|
||||
existed = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isAsset)
|
||||
return false;
|
||||
|
||||
if (unlink(resolvedPath.c_str()) < 0)
|
||||
{
|
||||
existed = (errno != ENOENT);
|
||||
return false;
|
||||
}
|
||||
existed = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
IGpDirectoryCursor *GpFileSystem_Android::ScanDirectoryNested(PortabilityLayer::VirtualDirectory_t virtualDirectory, const char *const *paths, size_t numPaths)
|
||||
{
|
||||
ScanDirectoryNestedContext ctx;
|
||||
ctx.m_this = this;
|
||||
ctx.m_returnValue = nullptr;
|
||||
ctx.m_virtualDirectory = virtualDirectory;
|
||||
ctx.m_paths = paths;
|
||||
ctx.m_numPaths = numPaths;
|
||||
m_relay->Invoke(ScanDirectoryNestedThunk, &ctx);
|
||||
|
||||
return ctx.m_returnValue;
|
||||
}
|
||||
|
||||
void GpFileSystem_Android::ScanDirectoryNestedThunk(void *context)
|
||||
{
|
||||
ScanDirectoryNestedContext *ctx = static_cast<ScanDirectoryNestedContext*>(context);
|
||||
ctx->m_returnValue = ctx->m_this->ScanDirectoryNestedInternal(ctx->m_virtualDirectory, ctx->m_paths, ctx->m_numPaths);
|
||||
}
|
||||
|
||||
IGpDirectoryCursor *GpFileSystem_Android::ScanDirectoryNestedInternal(PortabilityLayer::VirtualDirectory_t virtualDirectory, const char *const *paths, size_t numPaths)
|
||||
{
|
||||
if (virtualDirectory == PortabilityLayer::VirtualDirectories::kGameData || virtualDirectory == PortabilityLayer::VirtualDirectories::kApplicationData)
|
||||
return ScanAssetDirectory(virtualDirectory, paths, numPaths);
|
||||
|
||||
return ScanStorageDirectory(virtualDirectory, paths, numPaths);
|
||||
}
|
||||
|
||||
bool GpFileSystem_Android::ValidateFilePath(const char *path, size_t length) const
|
||||
{
|
||||
for (size_t i = 0; i < length; i++)
|
||||
{
|
||||
const char c = path[i];
|
||||
if (c >= '0' && c <= '9')
|
||||
continue;
|
||||
|
||||
if (c == '_' || c == '.' || c == '\'')
|
||||
continue;
|
||||
|
||||
if (c == ' ' && i != 0 && i != length - 1)
|
||||
continue;
|
||||
|
||||
if (c >= 'a' && c <= 'z')
|
||||
continue;
|
||||
|
||||
if (c >= 'A' && c <= 'Z')
|
||||
continue;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GpFileSystem_Android::ValidateFilePathUnicodeChar(uint32_t c) const
|
||||
{
|
||||
if (c >= '0' && c <= '9')
|
||||
return true;
|
||||
|
||||
if (c == '_' || c == '\'')
|
||||
return true;
|
||||
|
||||
if (c == ' ')
|
||||
return true;
|
||||
|
||||
if (c >= 'a' && c <= 'z')
|
||||
return true;
|
||||
|
||||
if (c >= 'A' && c <= 'Z')
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GpFileSystem_Android::IsVirtualDirectoryLooseResources(PortabilityLayer::VirtualDirectory_t virtualDir) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void GpFileSystem_Android::SetMainThreadRelay(IGpThreadRelay *relay)
|
||||
{
|
||||
m_relay = relay;
|
||||
}
|
||||
|
||||
void GpFileSystem_Android::SetDelayCallback(DelayCallback_t delayCallback)
|
||||
{
|
||||
m_delayCallback = delayCallback;
|
||||
}
|
||||
|
||||
void GpFileSystem_Android::PostSourceExportRequest(bool cancelled, int fd, jobject pfd)
|
||||
{
|
||||
JNIEnv *jni = static_cast<JNIEnv *>(SDL_AndroidGetJNIEnv());
|
||||
jobject globalRef = jni->NewGlobalRef(pfd);
|
||||
|
||||
m_sourceExportMutex->Lock();
|
||||
m_sourceExportWaiting = false;
|
||||
m_sourceExportCancelled = cancelled;
|
||||
m_sourceExportFD = fd;
|
||||
m_sourceExportPFD = globalRef;
|
||||
m_sourceExportMutex->Unlock();
|
||||
}
|
||||
|
||||
void GpFileSystem_Android::ClosePFD(jobject pfd)
|
||||
{
|
||||
JNIEnv *jni = static_cast<JNIEnv *>(SDL_AndroidGetJNIEnv());
|
||||
jni->CallVoidMethod(m_activity, m_closeSourceExportPFDMID, pfd);
|
||||
jni->DeleteGlobalRef(pfd);
|
||||
}
|
||||
|
||||
GpFileSystem_Android *GpFileSystem_Android::GetInstance()
|
||||
{
|
||||
return &ms_instance;
|
||||
}
|
||||
|
||||
class GpDirectoryCursor_StringList final : public IGpDirectoryCursor
|
||||
{
|
||||
public:
|
||||
explicit GpDirectoryCursor_StringList(std::vector<std::string> &paths);
|
||||
~GpDirectoryCursor_StringList();
|
||||
|
||||
bool GetNext(const char *&outFileName) override;
|
||||
void Destroy() override;
|
||||
|
||||
private:
|
||||
std::vector<std::string> m_paths;
|
||||
size_t m_index;
|
||||
};
|
||||
|
||||
GpDirectoryCursor_StringList::GpDirectoryCursor_StringList(std::vector<std::string> &paths)
|
||||
: m_index(0)
|
||||
{
|
||||
std::swap(paths, m_paths);
|
||||
}
|
||||
|
||||
GpDirectoryCursor_StringList::~GpDirectoryCursor_StringList()
|
||||
{
|
||||
}
|
||||
|
||||
bool GpDirectoryCursor_StringList::GetNext(const char *&outFileName)
|
||||
{
|
||||
if (m_index == m_paths.size())
|
||||
return false;
|
||||
outFileName = m_paths[m_index].c_str();
|
||||
m_index++;
|
||||
return true;
|
||||
}
|
||||
|
||||
void GpDirectoryCursor_StringList::Destroy()
|
||||
{
|
||||
delete this;
|
||||
}
|
||||
|
||||
class GpDirectoryCursor_POSIX final : public IGpDirectoryCursor
|
||||
{
|
||||
public:
|
||||
explicit GpDirectoryCursor_POSIX(DIR *dir);
|
||||
~GpDirectoryCursor_POSIX();
|
||||
|
||||
bool GetNext(const char *&outFileName) override;
|
||||
void Destroy() override;
|
||||
|
||||
private:
|
||||
DIR *m_dir;
|
||||
};
|
||||
|
||||
GpDirectoryCursor_POSIX::GpDirectoryCursor_POSIX(DIR *dir)
|
||||
: m_dir(dir)
|
||||
{
|
||||
}
|
||||
|
||||
GpDirectoryCursor_POSIX::~GpDirectoryCursor_POSIX()
|
||||
{
|
||||
closedir(m_dir);
|
||||
}
|
||||
|
||||
bool GpDirectoryCursor_POSIX::GetNext(const char *&outFileName)
|
||||
{
|
||||
struct dirent *dir = readdir(m_dir);
|
||||
if (!dir)
|
||||
return false;
|
||||
|
||||
outFileName = dir->d_name;
|
||||
return true;
|
||||
}
|
||||
|
||||
void GpDirectoryCursor_POSIX::Destroy()
|
||||
{
|
||||
delete this;
|
||||
}
|
||||
|
||||
IGpDirectoryCursor *GpFileSystem_Android::ScanAssetDirectory(PortabilityLayer::VirtualDirectory_t virtualDirectory, char const* const* paths, size_t numPaths)
|
||||
{
|
||||
|
||||
std::string resolvedPath;
|
||||
std::vector<std::string> subPaths;
|
||||
bool isAsset = true;
|
||||
if (!ResolvePath(virtualDirectory, paths, numPaths, resolvedPath, isAsset))
|
||||
return nullptr;
|
||||
|
||||
JNIEnv *jni = static_cast<JNIEnv *>(SDL_AndroidGetJNIEnv());
|
||||
|
||||
jstring directory = jni->NewStringUTF(resolvedPath.c_str());
|
||||
|
||||
jobjectArray resultArray = static_cast<jobjectArray>(jni->CallObjectMethod(m_activity, m_scanAssetDirectoryMID, directory));
|
||||
jni->DeleteLocalRef(directory);
|
||||
|
||||
size_t arrayLength = jni->GetArrayLength(resultArray);
|
||||
subPaths.reserve(arrayLength);
|
||||
for (size_t i = 0; i < arrayLength; i++)
|
||||
{
|
||||
jstring pathJStr = static_cast<jstring>(jni->GetObjectArrayElement(resultArray, i));
|
||||
const char *pathStrChars = jni->GetStringUTFChars(pathJStr, nullptr);
|
||||
|
||||
subPaths.push_back(std::string(pathStrChars, static_cast<size_t>(jni->GetStringUTFLength(pathJStr))));
|
||||
|
||||
jni->ReleaseStringUTFChars(pathJStr, pathStrChars);
|
||||
jni->DeleteLocalRef(pathJStr);
|
||||
}
|
||||
|
||||
jni->DeleteLocalRef(resultArray);
|
||||
|
||||
return new GpDirectoryCursor_StringList(subPaths);
|
||||
}
|
||||
|
||||
IGpDirectoryCursor *GpFileSystem_Android::ScanStorageDirectory(PortabilityLayer::VirtualDirectory_t virtualDirectory, char const* const* paths, size_t numPaths)
|
||||
{
|
||||
std::string resolvedPath;
|
||||
std::vector<std::string> subPaths;
|
||||
bool isAsset = true;
|
||||
if (!ResolvePath(virtualDirectory, paths, numPaths, resolvedPath, isAsset))
|
||||
return nullptr;
|
||||
|
||||
DIR *d = opendir(resolvedPath.c_str());
|
||||
if (!d)
|
||||
return nullptr;
|
||||
|
||||
return new GpDirectoryCursor_POSIX(d);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL nativePostSourceExportRequest(JNIEnv *env, jclass cls, jboolean cancelled, jint fd, jobject pfd)
|
||||
{
|
||||
GpFileSystem_Android::GetInstance()->PostSourceExportRequest(cancelled != JNI_FALSE, fd, pfd);
|
||||
}
|
||||
|
||||
GpFileSystem_Android GpFileSystem_Android::ms_instance;
|
75
AerofoilAndroid/app/jni/main/GpFileSystem_Android.h
Normal file
75
AerofoilAndroid/app/jni/main/GpFileSystem_Android.h
Normal file
@@ -0,0 +1,75 @@
|
||||
#pragma once
|
||||
|
||||
#include "IGpFileSystem.h"
|
||||
|
||||
#include "GpCoreDefs.h"
|
||||
|
||||
#include <jni.h>
|
||||
#include <string>
|
||||
|
||||
struct IGpMutex;
|
||||
|
||||
class GpFileSystem_Android final : public IGpFileSystem
|
||||
{
|
||||
public:
|
||||
GpFileSystem_Android();
|
||||
~GpFileSystem_Android();
|
||||
|
||||
void InitJNI();
|
||||
void ShutdownJNI();
|
||||
|
||||
bool FileExists(PortabilityLayer::VirtualDirectory_t virtualDirectory, const char *path) override;
|
||||
bool FileLocked(PortabilityLayer::VirtualDirectory_t virtualDirectory, const char *path, bool *exists) override;
|
||||
GpIOStream *OpenFileNested(PortabilityLayer::VirtualDirectory_t virtualDirectory, char const* const* subPaths, size_t numSubPaths, bool writeAccess, GpFileCreationDisposition_t createDisposition) override;
|
||||
bool DeleteFile(PortabilityLayer::VirtualDirectory_t virtualDirectory, const char *path, bool &existed) override;
|
||||
IGpDirectoryCursor *ScanDirectoryNested(PortabilityLayer::VirtualDirectory_t virtualDirectory, char const* const* paths, size_t numPaths) override;
|
||||
|
||||
bool ValidateFilePath(const char *path, size_t pathLen) const override;
|
||||
bool ValidateFilePathUnicodeChar(uint32_t ch) const override;
|
||||
|
||||
bool IsVirtualDirectoryLooseResources(PortabilityLayer::VirtualDirectory_t virtualDir) const override;
|
||||
|
||||
void SetMainThreadRelay(IGpThreadRelay *relay) override;
|
||||
void SetDelayCallback(DelayCallback_t delayCallback) override;
|
||||
|
||||
void PostSourceExportRequest(bool cancelled, int fd, jobject pfd);
|
||||
void ClosePFD(jobject pfd);
|
||||
|
||||
static GpFileSystem_Android *GetInstance();
|
||||
|
||||
private:
|
||||
struct ScanDirectoryNestedContext
|
||||
{
|
||||
GpFileSystem_Android *m_this;
|
||||
|
||||
IGpDirectoryCursor *m_returnValue;
|
||||
PortabilityLayer::VirtualDirectory_t m_virtualDirectory;
|
||||
char const *const *m_paths;
|
||||
size_t m_numPaths;
|
||||
};
|
||||
|
||||
static void ScanDirectoryNestedThunk(void *context);
|
||||
IGpDirectoryCursor *ScanDirectoryNestedInternal(PortabilityLayer::VirtualDirectory_t virtualDirectory, char const* const* paths, size_t numPaths);
|
||||
|
||||
IGpDirectoryCursor *ScanAssetDirectory(PortabilityLayer::VirtualDirectory_t virtualDirectory, char const* const* paths, size_t numPaths);
|
||||
IGpDirectoryCursor *ScanStorageDirectory(PortabilityLayer::VirtualDirectory_t virtualDirectory, char const* const* paths, size_t numPaths);
|
||||
|
||||
bool OpenSourceExportFD(PortabilityLayer::VirtualDirectory_t virtualDirectory, char const* const* paths, size_t numPaths, int &fd, jobject &pfd);
|
||||
bool ResolvePath(PortabilityLayer::VirtualDirectory_t virtualDirectory, char const* const* paths, size_t numPaths, std::string &resolution, bool &isAsset);
|
||||
|
||||
IGpThreadRelay *m_relay;
|
||||
DelayCallback_t m_delayCallback;
|
||||
|
||||
jobject m_activity;
|
||||
jmethodID m_scanAssetDirectoryMID;
|
||||
jmethodID m_selectSourceExportPathMID;
|
||||
jmethodID m_closeSourceExportPFDMID;
|
||||
|
||||
IGpMutex *m_sourceExportMutex;
|
||||
int m_sourceExportFD;
|
||||
bool m_sourceExportWaiting;
|
||||
bool m_sourceExportCancelled;
|
||||
jobject m_sourceExportPFD;
|
||||
|
||||
static GpFileSystem_Android ms_instance;
|
||||
};
|
118
AerofoilAndroid/app/jni/main/GpMain_SDL_Android.cpp
Normal file
118
AerofoilAndroid/app/jni/main/GpMain_SDL_Android.cpp
Normal file
@@ -0,0 +1,118 @@
|
||||
#include "SDL.h"
|
||||
|
||||
#include "GpMain.h"
|
||||
#include "GpAudioDriverFactory.h"
|
||||
#include "GpDisplayDriverFactory.h"
|
||||
#include "GpGlobalConfig.h"
|
||||
#include "GpFiber_SDL.h"
|
||||
#include "GpFileSystem_Android.h"
|
||||
#include "GpFontHandlerFactory.h"
|
||||
#include "GpInputDriverFactory.h"
|
||||
#include "GpAppInterface.h"
|
||||
#include "GpSystemServices_Android.h"
|
||||
#include "GpVOSEvent.h"
|
||||
#include "IGpVOSEventQueue.h"
|
||||
#include "IGpLogDriver.h"
|
||||
|
||||
#include "IGpFileSystem.h"
|
||||
#include "IGpThreadEvent.h"
|
||||
|
||||
#include "GpAndroid.h"
|
||||
|
||||
#include <exception>
|
||||
|
||||
GpAndroidGlobals g_gpAndroidGlobals;
|
||||
|
||||
extern "C" IGpFontHandler *GpDriver_CreateFontHandler_FreeType2(const GpFontHandlerProperties &properties);
|
||||
|
||||
IGpDisplayDriver *GpDriver_CreateDisplayDriver_SDL_GL2(const GpDisplayDriverProperties &properties);
|
||||
IGpAudioDriver *GpDriver_CreateAudioDriver_SDL(const GpAudioDriverProperties &properties);
|
||||
|
||||
class GpLogDriver_Android final : public IGpLogDriver
|
||||
{
|
||||
public:
|
||||
void VPrintf(Category category, const char *fmt, va_list args) override;
|
||||
void Shutdown() override;
|
||||
|
||||
static GpLogDriver_Android *GetInstance();
|
||||
|
||||
private:
|
||||
static GpLogDriver_Android ms_instance;
|
||||
};
|
||||
|
||||
void GpLogDriver_Android::VPrintf(IGpLogDriver::Category category, const char *fmt, va_list args)
|
||||
{
|
||||
switch (category)
|
||||
{
|
||||
case IGpLogDriver::Category_Error:
|
||||
SDL_LogMessageV(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_ERROR, fmt, args);
|
||||
break;
|
||||
case IGpLogDriver::Category_Warning:
|
||||
SDL_LogMessageV(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_WARN, fmt, args);
|
||||
break;
|
||||
case IGpLogDriver::Category_Information:
|
||||
SDL_LogMessageV(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO, fmt, args);
|
||||
break;
|
||||
default:
|
||||
SDL_LogMessageV(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_VERBOSE, fmt, args);
|
||||
break;
|
||||
};
|
||||
}
|
||||
|
||||
void GpLogDriver_Android::Shutdown()
|
||||
{
|
||||
}
|
||||
|
||||
GpLogDriver_Android *GpLogDriver_Android::GetInstance()
|
||||
{
|
||||
return &ms_instance;
|
||||
}
|
||||
|
||||
GpLogDriver_Android GpLogDriver_Android::ms_instance;
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
SDL_LogSetAllPriority(SDL_LOG_PRIORITY_INFO);
|
||||
|
||||
if (SDL_Init(SDL_INIT_VIDEO) < 0)
|
||||
return -1;
|
||||
|
||||
SDL_SetHint(SDL_HINT_ORIENTATIONS, "LandscapeLeft LandscapeRight");
|
||||
|
||||
GpFileSystem_Android::GetInstance()->InitJNI();
|
||||
|
||||
GpDriverCollection *drivers = GpAppInterface_Get()->PL_GetDriverCollection();
|
||||
drivers->SetDriver<GpDriverIDs::kFileSystem>(GpFileSystem_Android::GetInstance());
|
||||
drivers->SetDriver<GpDriverIDs::kSystemServices>(GpSystemServices_Android::GetInstance());
|
||||
drivers->SetDriver<GpDriverIDs::kLog>(GpLogDriver_Android::GetInstance());
|
||||
|
||||
g_gpGlobalConfig.m_displayDriverType = EGpDisplayDriverType_SDL_GL2;
|
||||
|
||||
g_gpGlobalConfig.m_audioDriverType = EGpAudioDriverType_SDL2;
|
||||
|
||||
g_gpGlobalConfig.m_fontHandlerType = EGpFontHandlerType_FreeType2;
|
||||
|
||||
g_gpGlobalConfig.m_inputDriverTypes = nullptr;
|
||||
g_gpGlobalConfig.m_numInputDrivers = 0;
|
||||
|
||||
g_gpGlobalConfig.m_osGlobals = &g_gpAndroidGlobals;
|
||||
g_gpGlobalConfig.m_logger = GpLogDriver_Android::GetInstance();
|
||||
g_gpGlobalConfig.m_systemServices = GpSystemServices_Android::GetInstance();
|
||||
|
||||
GpDisplayDriverFactory::RegisterDisplayDriverFactory(EGpDisplayDriverType_SDL_GL2, GpDriver_CreateDisplayDriver_SDL_GL2);
|
||||
GpAudioDriverFactory::RegisterAudioDriverFactory(EGpAudioDriverType_SDL2, GpDriver_CreateAudioDriver_SDL);
|
||||
GpFontHandlerFactory::RegisterFontHandlerFactory(EGpFontHandlerType_FreeType2, GpDriver_CreateFontHandler_FreeType2);
|
||||
|
||||
int returnCode = GpMain::Run();
|
||||
|
||||
GpFileSystem_Android::GetInstance()->ShutdownJNI();
|
||||
|
||||
SDL_Quit();
|
||||
|
||||
// This doesn't even actually exit, but it does stop this stupid brain-damaged OS from
|
||||
// just calling "main" again with no cleanup...
|
||||
// That'll have to do until proper cleanup code is added to everything.
|
||||
exit(returnCode);
|
||||
|
||||
return returnCode;
|
||||
}
|
298
AerofoilAndroid/app/jni/main/GpSystemServices_Android.cpp
Normal file
298
AerofoilAndroid/app/jni/main/GpSystemServices_Android.cpp
Normal file
@@ -0,0 +1,298 @@
|
||||
#include "GpSystemServices_Android.h"
|
||||
#include "IGpMutex.h"
|
||||
#include "IGpThreadEvent.h"
|
||||
#include "SDL.h"
|
||||
|
||||
#include <time.h>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <unistd.h>
|
||||
|
||||
struct GpSystemServices_Android_ThreadStartParams
|
||||
{
|
||||
GpSystemServices_Android::ThreadFunc_t m_threadFunc;
|
||||
void *m_threadContext;
|
||||
IGpThreadEvent *m_threadStartEvent;
|
||||
};
|
||||
|
||||
static int SDLCALL StaticStartThread(void *lpThreadParameter)
|
||||
{
|
||||
const GpSystemServices_Android_ThreadStartParams *threadParams = static_cast<const GpSystemServices_Android_ThreadStartParams*>(lpThreadParameter);
|
||||
|
||||
GpSystemServices_Android::ThreadFunc_t threadFunc = threadParams->m_threadFunc;
|
||||
void *threadContext = threadParams->m_threadContext;
|
||||
IGpThreadEvent *threadStartEvent = threadParams->m_threadStartEvent;
|
||||
|
||||
threadStartEvent->Signal();
|
||||
|
||||
return threadFunc(threadContext);
|
||||
}
|
||||
|
||||
template<class TMutex>
|
||||
class GpMutex_Cpp11 final : public IGpMutex
|
||||
{
|
||||
public:
|
||||
GpMutex_Cpp11();
|
||||
~GpMutex_Cpp11();
|
||||
|
||||
void Destroy() override;
|
||||
|
||||
void Lock() override;
|
||||
void Unlock() override;
|
||||
|
||||
private:
|
||||
TMutex m_mutex;
|
||||
};
|
||||
|
||||
template<class TMutex>
|
||||
GpMutex_Cpp11<TMutex>::GpMutex_Cpp11()
|
||||
{
|
||||
}
|
||||
|
||||
template<class TMutex>
|
||||
GpMutex_Cpp11<TMutex>::~GpMutex_Cpp11()
|
||||
{
|
||||
}
|
||||
|
||||
template<class TMutex>
|
||||
void GpMutex_Cpp11<TMutex>::Destroy()
|
||||
{
|
||||
this->~GpMutex_Cpp11();
|
||||
free(this);
|
||||
}
|
||||
|
||||
template<class TMutex>
|
||||
void GpMutex_Cpp11<TMutex>::Lock()
|
||||
{
|
||||
m_mutex.lock();
|
||||
}
|
||||
|
||||
template<class TMutex>
|
||||
void GpMutex_Cpp11<TMutex>::Unlock()
|
||||
{
|
||||
m_mutex.unlock();
|
||||
}
|
||||
|
||||
typedef GpMutex_Cpp11<std::mutex> GpMutex_Cpp11_Vanilla;
|
||||
typedef GpMutex_Cpp11<std::recursive_mutex> GpMutex_Cpp11_Recursive;
|
||||
|
||||
|
||||
class GpThreadEvent_Cpp11 final : public IGpThreadEvent
|
||||
{
|
||||
public:
|
||||
GpThreadEvent_Cpp11(bool autoReset, bool startSignaled);
|
||||
~GpThreadEvent_Cpp11();
|
||||
|
||||
void Wait() override;
|
||||
bool WaitTimed(uint32_t msec) override;
|
||||
void Signal() override;
|
||||
void Destroy() override;
|
||||
|
||||
private:
|
||||
std::mutex m_mutex;
|
||||
std::condition_variable m_cvar;
|
||||
bool m_flag;
|
||||
bool m_autoReset;
|
||||
};
|
||||
|
||||
GpThreadEvent_Cpp11::GpThreadEvent_Cpp11(bool autoReset, bool startSignaled)
|
||||
: m_flag(startSignaled)
|
||||
, m_autoReset(autoReset)
|
||||
{
|
||||
}
|
||||
|
||||
GpThreadEvent_Cpp11::~GpThreadEvent_Cpp11()
|
||||
{
|
||||
}
|
||||
|
||||
void GpThreadEvent_Cpp11::Wait()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
if (m_autoReset)
|
||||
{
|
||||
m_cvar.wait(lock,[&]()->bool{
|
||||
if (m_flag)
|
||||
{
|
||||
m_flag = false;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
});
|
||||
}
|
||||
else
|
||||
m_cvar.wait(lock,[&]()->bool{ return m_flag; });
|
||||
}
|
||||
|
||||
bool GpThreadEvent_Cpp11::WaitTimed(uint32_t msec)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
if (m_autoReset)
|
||||
{
|
||||
if (!m_cvar.wait_for(lock, std::chrono::milliseconds(msec), [&]()->bool{
|
||||
if (m_flag)
|
||||
{
|
||||
m_flag = false;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!m_cvar.wait_for(lock, std::chrono::milliseconds(msec), [&]()->bool{ return m_flag; }))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void GpThreadEvent_Cpp11::Signal()
|
||||
{
|
||||
m_mutex.lock();
|
||||
m_flag = true;
|
||||
m_mutex.unlock();
|
||||
if (m_autoReset)
|
||||
m_cvar.notify_one();
|
||||
else
|
||||
m_cvar.notify_all();
|
||||
}
|
||||
|
||||
void GpThreadEvent_Cpp11::Destroy()
|
||||
{
|
||||
this->~GpThreadEvent_Cpp11();
|
||||
free(this);
|
||||
}
|
||||
|
||||
GpSystemServices_Android::GpSystemServices_Android()
|
||||
: m_textInputEnabled(false)
|
||||
{
|
||||
}
|
||||
|
||||
int64_t GpSystemServices_Android::GetTime() const
|
||||
{
|
||||
time_t t = time(nullptr);
|
||||
return static_cast<int64_t>(t) - 2082844800;
|
||||
}
|
||||
|
||||
void GpSystemServices_Android::GetLocalDateTime(unsigned int &year, unsigned int &month, unsigned int &day, unsigned int &hour, unsigned int &minute, unsigned int &second) const
|
||||
{
|
||||
time_t t = time(nullptr);
|
||||
tm *tmObject = localtime(&t);
|
||||
year = static_cast<unsigned int>(tmObject->tm_year);
|
||||
month = static_cast<unsigned int>(tmObject->tm_mon + 1);
|
||||
hour = static_cast<unsigned int>(tmObject->tm_hour);
|
||||
minute = static_cast<unsigned int>(tmObject->tm_min);
|
||||
second = static_cast<unsigned int>(tmObject->tm_sec);
|
||||
}
|
||||
|
||||
IGpMutex *GpSystemServices_Android::CreateMutex()
|
||||
{
|
||||
GpMutex_Cpp11_Vanilla *mutex = static_cast<GpMutex_Cpp11_Vanilla*>(malloc(sizeof(GpMutex_Cpp11_Vanilla)));
|
||||
if (!mutex)
|
||||
return nullptr;
|
||||
|
||||
return new (mutex) GpMutex_Cpp11_Vanilla();
|
||||
}
|
||||
|
||||
|
||||
void *GpSystemServices_Android::CreateThread(ThreadFunc_t threadFunc, void *context)
|
||||
{
|
||||
IGpThreadEvent *evt = CreateThreadEvent(true, false);
|
||||
if (!evt)
|
||||
return nullptr;
|
||||
|
||||
GpSystemServices_Android_ThreadStartParams startParams;
|
||||
startParams.m_threadContext = context;
|
||||
startParams.m_threadFunc = threadFunc;
|
||||
startParams.m_threadStartEvent = evt;
|
||||
|
||||
SDL_Thread *thread = SDL_CreateThread(StaticStartThread, "WorkerThread", &startParams);
|
||||
if (thread == nullptr)
|
||||
{
|
||||
evt->Destroy();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
evt->Wait();
|
||||
evt->Destroy();
|
||||
|
||||
return thread;
|
||||
}
|
||||
|
||||
IGpMutex *GpSystemServices_Android::CreateRecursiveMutex()
|
||||
{
|
||||
GpMutex_Cpp11_Recursive *mutex = static_cast<GpMutex_Cpp11_Recursive*>(malloc(sizeof(GpMutex_Cpp11_Recursive)));
|
||||
if (!mutex)
|
||||
return nullptr;
|
||||
|
||||
return new (mutex) GpMutex_Cpp11_Recursive();
|
||||
}
|
||||
|
||||
IGpThreadEvent *GpSystemServices_Android::CreateThreadEvent(bool autoReset, bool startSignaled)
|
||||
{
|
||||
GpThreadEvent_Cpp11 *evt = static_cast<GpThreadEvent_Cpp11*>(malloc(sizeof(GpThreadEvent_Cpp11)));
|
||||
if (!evt)
|
||||
return nullptr;
|
||||
|
||||
return new (evt) GpThreadEvent_Cpp11(autoReset, startSignaled);
|
||||
}
|
||||
|
||||
uint64_t GpSystemServices_Android::GetFreeMemoryCosmetic() const
|
||||
{
|
||||
long pages = sysconf(_SC_AVPHYS_PAGES);
|
||||
long pageSize = sysconf(_SC_PAGE_SIZE);
|
||||
return pages * pageSize;
|
||||
}
|
||||
|
||||
void GpSystemServices_Android::Beep() const
|
||||
{
|
||||
}
|
||||
|
||||
bool GpSystemServices_Android::IsTouchscreen() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GpSystemServices_Android::IsUsingMouseAsTouch() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GpSystemServices_Android::IsTextInputObstructive() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GpSystemServices_Android::IsFullscreenPreferred() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
unsigned int GpSystemServices_Android::GetCPUCount() const
|
||||
{
|
||||
return SDL_GetCPUCount();
|
||||
}
|
||||
|
||||
void GpSystemServices_Android::SetTextInputEnabled(bool isEnabled)
|
||||
{
|
||||
m_textInputEnabled = isEnabled;
|
||||
}
|
||||
|
||||
bool GpSystemServices_Android::IsTextInputEnabled() const
|
||||
{
|
||||
return m_textInputEnabled;
|
||||
}
|
||||
|
||||
bool GpSystemServices_Android::AreFontResourcesSeekable() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
GpSystemServices_Android *GpSystemServices_Android::GetInstance()
|
||||
{
|
||||
return &ms_instance;
|
||||
}
|
||||
|
||||
GpSystemServices_Android GpSystemServices_Android::ms_instance;
|
36
AerofoilAndroid/app/jni/main/GpSystemServices_Android.h
Normal file
36
AerofoilAndroid/app/jni/main/GpSystemServices_Android.h
Normal file
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include "IGpSystemServices.h"
|
||||
#include "GpCoreDefs.h"
|
||||
|
||||
class GpSystemServices_Android final : public IGpSystemServices
|
||||
{
|
||||
public:
|
||||
GpSystemServices_Android();
|
||||
|
||||
int64_t GetTime() const override;
|
||||
void GetLocalDateTime(unsigned int &year, unsigned int &month, unsigned int &day, unsigned int &hour, unsigned int &minute, unsigned int &second) const override;
|
||||
IGpMutex *CreateMutex() override;
|
||||
IGpMutex *CreateRecursiveMutex() override;
|
||||
void *CreateThread(ThreadFunc_t threadFunc, void *context) override;
|
||||
IGpThreadEvent *CreateThreadEvent(bool autoReset, bool startSignaled) override;
|
||||
uint64_t GetFreeMemoryCosmetic() const override;
|
||||
void Beep() const override;
|
||||
bool IsTouchscreen() const override;
|
||||
bool IsUsingMouseAsTouch() const override;
|
||||
bool IsTextInputObstructive() const override;
|
||||
bool IsFullscreenPreferred() const override;
|
||||
unsigned int GetCPUCount() const override;
|
||||
void SetTextInputEnabled(bool isEnabled) override;
|
||||
bool IsTextInputEnabled() const override;
|
||||
bool AreFontResourcesSeekable() const override;
|
||||
|
||||
void FlushTextInputEnabled();
|
||||
|
||||
static GpSystemServices_Android *GetInstance();
|
||||
|
||||
private:
|
||||
static GpSystemServices_Android ms_instance;
|
||||
|
||||
bool m_textInputEnabled;
|
||||
};
|
17
AerofoilAndroid/app/proguard-rules.pro
vendored
Normal file
17
AerofoilAndroid/app/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in [sdk]/tools/proguard/proguard-android.txt
|
||||
# You can edit the include path and order by changing the proguardFiles
|
||||
# directive in build.gradle.
|
||||
#
|
||||
# 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 *;
|
||||
#}
|
94
AerofoilAndroid/app/src/main/AndroidManifest.xml
Normal file
94
AerofoilAndroid/app/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,94 @@
|
||||
<?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.thecodedeposit.aerofoil"
|
||||
android:versionCode="1"
|
||||
android:versionName="1.0"
|
||||
android:installLocation="auto">
|
||||
|
||||
<!-- OpenGL ES 2.0 -->
|
||||
<uses-feature android:glEsVersion="0x00020000" android:required="true" />
|
||||
|
||||
<!-- 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="GpActivity"
|
||||
android:label="@string/app_name"
|
||||
android:alwaysRetainTaskState="true"
|
||||
android:launchMode="singleInstance"
|
||||
android:configChanges="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>
|
3
AerofoilAndroid/app/src/main/assets/.gitignore
vendored
Normal file
3
AerofoilAndroid/app/src/main/assets/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
Packaged
|
||||
Resources
|
||||
SourceCode.pkg
|
@@ -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,304 @@
|
||||
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) {
|
||||
result = mDevice.getSerialNumber();
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
84
AerofoilAndroid/app/src/main/java/org/libsdl/app/SDL.java
Normal file
84
AerofoilAndroid/app/src/main/java/org/libsdl/app/SDL.java
Normal file
@@ -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;
|
||||
}
|
2326
AerofoilAndroid/app/src/main/java/org/libsdl/app/SDLActivity.java
Normal file
2326
AerofoilAndroid/app/src/main/java/org/libsdl/app/SDLActivity.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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,105 @@
|
||||
package org.thecodedeposit.aerofoil;
|
||||
|
||||
import org.libsdl.app.SDLActivity;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.AssetManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.provider.MediaStore;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public class GpActivity extends SDLActivity
|
||||
{
|
||||
private static final int SOURCE_EXPORT_REQUEST_ID = 20;
|
||||
|
||||
private AssetManager assetManager;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
assetManager = getAssets();
|
||||
}
|
||||
|
||||
public String[] scanAssetDirectory(String directory)
|
||||
{
|
||||
try
|
||||
{
|
||||
return this.assetManager.list(directory);
|
||||
}
|
||||
catch (java.io.IOException ex)
|
||||
{
|
||||
return new String[0];
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent intent)
|
||||
{
|
||||
if (requestCode == SOURCE_EXPORT_REQUEST_ID)
|
||||
{
|
||||
if (resultCode == RESULT_OK)
|
||||
{
|
||||
Uri uri = intent.getData();
|
||||
Context context = getContext();
|
||||
ContentResolver contentResolver = context.getContentResolver();
|
||||
try
|
||||
{
|
||||
ParcelFileDescriptor fd = contentResolver.openFileDescriptor(uri, "w");
|
||||
GpFileSystemAPI.nativePostSourceExportRequest(false, fd.getFd(), fd);
|
||||
}
|
||||
catch (FileNotFoundException e)
|
||||
{
|
||||
GpFileSystemAPI.nativePostSourceExportRequest(true, 0, null);
|
||||
return;
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
GpFileSystemAPI.nativePostSourceExportRequest(true, 0, null);
|
||||
return;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
GpFileSystemAPI.nativePostSourceExportRequest(true, 0, null);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
GpFileSystemAPI.nativePostSourceExportRequest(true, 0, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
super.onActivityResult(requestCode, resultCode, intent);
|
||||
}
|
||||
}
|
||||
|
||||
public void selectSourceExportPath(String fname)
|
||||
{
|
||||
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT)
|
||||
.setType("application/zip")
|
||||
.addCategory(Intent.CATEGORY_OPENABLE)
|
||||
.putExtra(Intent.EXTRA_TITLE, fname);
|
||||
startActivityForResult(intent, SOURCE_EXPORT_REQUEST_ID);
|
||||
}
|
||||
|
||||
public void closeSourceExportPFD(Object obj)
|
||||
{
|
||||
try
|
||||
{
|
||||
((ParcelFileDescriptor) obj).close();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,6 @@
|
||||
package org.thecodedeposit.aerofoil;
|
||||
|
||||
public class GpFileSystemAPI
|
||||
{
|
||||
public static native void nativePostSourceExportRequest(boolean cancelled, int fd, Object pfd);
|
||||
}
|
@@ -0,0 +1,5 @@
|
||||
package org.thecodedeposit.aerofoil;
|
||||
|
||||
public class GpSystemServices
|
||||
{
|
||||
}
|
BIN
AerofoilAndroid/app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
BIN
AerofoilAndroid/app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.4 KiB |
BIN
AerofoilAndroid/app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
BIN
AerofoilAndroid/app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.9 KiB |
BIN
AerofoilAndroid/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
BIN
AerofoilAndroid/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 525 B |
BIN
AerofoilAndroid/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
BIN
AerofoilAndroid/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 746 B |
BIN
AerofoilAndroid/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
BIN
AerofoilAndroid/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 966 B |
6
AerofoilAndroid/app/src/main/res/values/colors.xml
Normal file
6
AerofoilAndroid/app/src/main/res/values/colors.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="colorPrimary">#3F51B5</color>
|
||||
<color name="colorPrimaryDark">#303F9F</color>
|
||||
<color name="colorAccent">#FF4081</color>
|
||||
</resources>
|
3
AerofoilAndroid/app/src/main/res/values/strings.xml
Normal file
3
AerofoilAndroid/app/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">Aerofoil</string>
|
||||
</resources>
|
8
AerofoilAndroid/app/src/main/res/values/styles.xml
Normal file
8
AerofoilAndroid/app/src/main/res/values/styles.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<resources>
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
|
||||
</resources>
|
25
AerofoilAndroid/build.gradle
Normal file
25
AerofoilAndroid/build.gradle
Normal file
@@ -0,0 +1,25 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
google()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:4.1.1'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
jcenter()
|
||||
google()
|
||||
}
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
12
AerofoilAndroid/copy_packaged_resources.bat
Normal file
12
AerofoilAndroid/copy_packaged_resources.bat
Normal file
@@ -0,0 +1,12 @@
|
||||
cd app
|
||||
cd src
|
||||
cd main
|
||||
cd assets
|
||||
rmdir /S /Q Packaged
|
||||
mkdir Packaged
|
||||
cd Packaged
|
||||
rmdir /S /Q Houses
|
||||
mkdir Houses
|
||||
copy ..\..\..\..\..\..\Packaged\*.gpa .\
|
||||
copy ..\..\..\..\..\..\Packaged\Houses\* Houses\
|
||||
cd ..
|
9
AerofoilAndroid/copy_source_package.bat
Normal file
9
AerofoilAndroid/copy_source_package.bat
Normal file
@@ -0,0 +1,9 @@
|
||||
cd ..
|
||||
copy /Y DefaultTimestamp.timestamp AerofoilAndroid\app\src\main\assets\Packaged\DefaultTimestamp.timestamp
|
||||
del AerofoilAndroid\app\src\main\assets\Packaged\SourceCode.zip
|
||||
del AerofoilAndroid\app\src\main\assets\Packaged\SourceCode.pkg
|
||||
git archive -0 --format zip -o AerofoilAndroid\app\src\main\assets\Packaged\SourceCode.zip HEAD
|
||||
tools\7z.exe d AerofoilAndroid\app\src\main\assets\Packaged\SourceCode.zip GliderProData\
|
||||
cd AerofoilAndroid\app\src\main\assets\Packaged
|
||||
rename SourceCode.zip SourceCode.pkg
|
||||
pause
|
17
AerofoilAndroid/gradle.properties
Normal file
17
AerofoilAndroid/gradle.properties
Normal file
@@ -0,0 +1,17 @@
|
||||
# Project-wide Gradle settings.
|
||||
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
org.gradle.jvmargs=-Xmx1536m
|
||||
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
BIN
AerofoilAndroid/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
AerofoilAndroid/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
6
AerofoilAndroid/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
6
AerofoilAndroid/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
#Sat Oct 17 02:06:49 EDT 2020
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip
|
160
AerofoilAndroid/gradlew
vendored
Normal file
160
AerofoilAndroid/gradlew
vendored
Normal file
@@ -0,0 +1,160 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn ( ) {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die ( ) {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
esac
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
||||
function splitJvmOpts() {
|
||||
JVM_OPTS=("$@")
|
||||
}
|
||||
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
||||
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
||||
|
||||
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
90
AerofoilAndroid/gradlew.bat
vendored
Normal file
90
AerofoilAndroid/gradlew.bat
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windowz variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
if "%@eval[2+2]" == "4" goto 4NT_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
goto execute
|
||||
|
||||
:4NT_args
|
||||
@rem Get arguments from the 4NT Shell from JP Software
|
||||
set CMD_LINE_ARGS=%$
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
21
AerofoilAndroid/make_symlinks.bat
Normal file
21
AerofoilAndroid/make_symlinks.bat
Normal file
@@ -0,0 +1,21 @@
|
||||
@setlocal enableextensions
|
||||
@cd /d "%~dp0"
|
||||
|
||||
call remove_symlinks.bat
|
||||
|
||||
|
||||
mklink /D app\jni\AerofoilSDL ..\..\..\AerofoilSDL
|
||||
mklink /D app\jni\Common ..\..\..\Common
|
||||
mklink /D app\jni\SDL2 ..\..\..\SDL2-2.0.12
|
||||
mklink /D app\jni\GpApp ..\..\..\GpApp
|
||||
mklink /D app\jni\GpShell ..\..\..\GpShell
|
||||
mklink /D app\jni\GpCommon ..\..\..\GpCommon
|
||||
mklink /D app\jni\GpFontHandler_FreeType2 ..\..\..\GpFontHandler_FreeType2
|
||||
mklink /D app\jni\PortabilityLayer ..\..\..\PortabilityLayer
|
||||
mklink /D app\jni\FreeType ..\..\..\FreeType
|
||||
mklink /D app\jni\rapidjson ..\..\..\rapidjson
|
||||
mklink /D app\jni\MacRomanConversion ..\..\..\MacRomanConversion
|
||||
mklink /D app\jni\stb ..\..\..\stb
|
||||
mklink /D app\src\main\assets\Resources ..\..\..\..\..\Resources
|
||||
|
||||
pause
|
17
AerofoilAndroid/remove_symlinks.bat
Normal file
17
AerofoilAndroid/remove_symlinks.bat
Normal file
@@ -0,0 +1,17 @@
|
||||
@setlocal enableextensions
|
||||
@cd /d "%~dp0"
|
||||
|
||||
rmdir app\jni\AerofoilSDL
|
||||
rmdir app\jni\Common
|
||||
rmdir app\jni\SDL2
|
||||
rmdir app\jni\GpShell
|
||||
rmdir app\jni\GpCommon
|
||||
rmdir app\jni\GpApp
|
||||
rmdir app\jni\GpFontHandler_FreeType2
|
||||
rmdir app\jni\PortabilityLayer
|
||||
rmdir app\jni\FreeType
|
||||
rmdir app\jni\zlib
|
||||
rmdir app\jni\rapidjson
|
||||
rmdir app\jni\MacRomanConversion
|
||||
rmdir app\jni\stb
|
||||
rmdir app\src\main\assets\Resources
|
1
AerofoilAndroid/settings.gradle
Normal file
1
AerofoilAndroid/settings.gradle
Normal file
@@ -0,0 +1 @@
|
||||
include ':app'
|
16
AerofoilSDL/AerofoilSDL.props
Normal file
16
AerofoilSDL/AerofoilSDL.props
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ImportGroup Label="PropertySheets" />
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup>
|
||||
<IncludePath>$(SolutionDir)SDL2-2.0.12\include;$(IncludePath)</IncludePath>
|
||||
<LibraryPath>$(SolutionDir)SDL2-2.0.12\lib\x64;$(LibraryPath)</LibraryPath>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup>
|
||||
<Link>
|
||||
<AdditionalDependencies>SDL2.lib;SDL2main.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup />
|
||||
</Project>
|
129
AerofoilSDL/AerofoilSDL.vcxproj
Normal file
129
AerofoilSDL/AerofoilSDL.vcxproj
Normal file
@@ -0,0 +1,129 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>15.0</VCProjectVersion>
|
||||
<ProjectGuid>{33542FF0-0473-4802-BC79-3B8261790F65}</ProjectGuid>
|
||||
<RootNamespace>AerofoilSDL</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0.17763.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<CharacterSet>MultiByte</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>MultiByte</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
<Import Project="AerofoilSDL.props" />
|
||||
<Import Project="..\Common.props" />
|
||||
<Import Project="..\GpShell.props" />
|
||||
<Import Project="..\GpMainApp.props" />
|
||||
<Import Project="..\PortabilityLayer.props" />
|
||||
<Import Project="..\GpCommon.props" />
|
||||
<Import Project="..\Debug.props" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
<Import Project="AerofoilSDL.props" />
|
||||
<Import Project="..\Common.props" />
|
||||
<Import Project="..\GpShell.props" />
|
||||
<Import Project="..\Release.props" />
|
||||
<Import Project="..\GpMainApp.props" />
|
||||
<Import Project="..\PortabilityLayer.props" />
|
||||
<Import Project="..\GpCommon.props" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup />
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<AdditionalDependencies>shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\Aerofoil\GpColorCursor_Win32.cpp" />
|
||||
<ClCompile Include="..\Aerofoil\GpFileStream_Win32.cpp" />
|
||||
<ClCompile Include="..\Aerofoil\GpFileSystem_Win32.cpp" />
|
||||
<ClCompile Include="..\Aerofoil\GpLogDriver_Win32.cpp" />
|
||||
<ClCompile Include="..\Aerofoil\GpMutex_Win32.cpp" />
|
||||
<ClCompile Include="..\Aerofoil\GpSystemServices_Win32.cpp" />
|
||||
<ClCompile Include="..\Aerofoil\GpThreadEvent_Win32.cpp" />
|
||||
<ClCompile Include="GpAudioDriver_SDL2.cpp" />
|
||||
<ClCompile Include="GpDisplayDriver_SDL_GL2.cpp" />
|
||||
<ClCompile Include="GpFiberStarter_SDL.cpp" />
|
||||
<ClCompile Include="GpFiber_SDL.cpp" />
|
||||
<ClCompile Include="GpMain_SDL_Win32.cpp" />
|
||||
<ClCompile Include="ShaderCode\CopyQuadP.cpp" />
|
||||
<ClCompile Include="ShaderCode\DrawQuadPaletteP.cpp" />
|
||||
<ClCompile Include="ShaderCode\DrawQuadV.cpp" />
|
||||
<ClCompile Include="ShaderCode\ScaleQuadP.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\GpApp\GpApp.vcxproj">
|
||||
<Project>{6233c3f2-5781-488e-b190-4fa8836f5a77}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\GpAudioDriver_XAudio2\GpAudioDriver_XAudio2.vcxproj">
|
||||
<Project>{e3bdc783-8646-433e-adf0-8b6390d36669}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\GpFontHandler_FreeType2\GpFontHandler_FreeType2.vcxproj">
|
||||
<Project>{4b564030-8985-4975-91e1-e1b2c16ae2a1}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\GpInputDriver_XInput\GpInputDriver_XInput.vcxproj">
|
||||
<Project>{17b96f07-ef92-47cd-95a5-8e6ee38ab564}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\GpShell\GpShell.vcxproj">
|
||||
<Project>{10cf9b5f-61d0-4b5b-89f4-810b58fc053d}</Project>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="GpFiber_SDL.h" />
|
||||
<ClInclude Include="ShaderCode\DrawQuadPixelConstants.h" />
|
||||
<ClInclude Include="ShaderCode\Functions.h" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
81
AerofoilSDL/AerofoilSDL.vcxproj.filters
Normal file
81
AerofoilSDL/AerofoilSDL.vcxproj.filters
Normal file
@@ -0,0 +1,81 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hh;hpp;hxx;hm;inl;inc;ipp;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Source Files\ShaderCode">
|
||||
<UniqueIdentifier>{85279826-1cd2-4894-a780-3f74af9c1260}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="GpMain_SDL_Win32.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\Aerofoil\GpFileSystem_Win32.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\Aerofoil\GpSystemServices_Win32.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\Aerofoil\GpColorCursor_Win32.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\Aerofoil\GpMutex_Win32.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\Aerofoil\GpThreadEvent_Win32.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\Aerofoil\GpFileStream_Win32.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ShaderCode\DrawQuadV.cpp">
|
||||
<Filter>Source Files\ShaderCode</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ShaderCode\DrawQuadPaletteP.cpp">
|
||||
<Filter>Source Files\ShaderCode</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ShaderCode\ScaleQuadP.cpp">
|
||||
<Filter>Source Files\ShaderCode</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="GpFiber_SDL.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="GpFiberStarter_SDL.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="GpDisplayDriver_SDL_GL2.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="GpAudioDriver_SDL2.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\Aerofoil\GpLogDriver_Win32.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ShaderCode\CopyQuadP.cpp">
|
||||
<Filter>Source Files\ShaderCode</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="ShaderCode\Functions.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ShaderCode\DrawQuadPixelConstants.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="GpFiber_SDL.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
</Project>
|
29
AerofoilSDL/Android.mk
Normal file
29
AerofoilSDL/Android.mk
Normal file
@@ -0,0 +1,29 @@
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_MODULE := AerofoilSDL
|
||||
|
||||
SDL_PATH := ../SDL2
|
||||
|
||||
LOCAL_C_INCLUDES := \
|
||||
$(LOCAL_PATH)/../GpCommon \
|
||||
$(LOCAL_PATH)/../GpShell \
|
||||
$(LOCAL_PATH)/../Common \
|
||||
$(LOCAL_PATH)/../PortabilityLayer \
|
||||
$(LOCAL_PATH)/$(SDL_PATH)/include
|
||||
|
||||
LOCAL_CFLAGS := -DGP_DEBUG_CONFIG=0
|
||||
|
||||
# Add your application source files here...
|
||||
LOCAL_SRC_FILES := \
|
||||
GpAudioDriver_SDL2.cpp \
|
||||
GpDisplayDriver_SDL_GL2.cpp \
|
||||
GpFiber_SDL.cpp \
|
||||
GpFiberStarter_SDL.cpp \
|
||||
ShaderCode/CopyQuadP.cpp \
|
||||
ShaderCode/DrawQuadPaletteP.cpp \
|
||||
ShaderCode/DrawQuadV.cpp \
|
||||
ShaderCode/ScaleQuadP.cpp
|
||||
|
||||
include $(BUILD_STATIC_LIBRARY)
|
566
AerofoilSDL/GpAudioDriver_SDL2.cpp
Normal file
566
AerofoilSDL/GpAudioDriver_SDL2.cpp
Normal file
@@ -0,0 +1,566 @@
|
||||
#include "IGpAudioDriver.h"
|
||||
#include "IGpAudioChannel.h"
|
||||
#include "IGpAudioChannelCallbacks.h"
|
||||
#include "IGpMutex.h"
|
||||
#include "IGpPrefsHandler.h"
|
||||
#include "IGpSystemServices.h"
|
||||
#include "GpAudioDriverProperties.h"
|
||||
#include "CoreDefs.h"
|
||||
|
||||
|
||||
#include "SDL_audio.h"
|
||||
#include "GpRingBuffer.h"
|
||||
|
||||
#include "SDL_atomic.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <new>
|
||||
#include <stdio.h>
|
||||
|
||||
class GpAudioDriver_SDL2;
|
||||
|
||||
static void *AlignedAlloc(size_t size, size_t alignment)
|
||||
{
|
||||
void *storage = malloc(size + alignment);
|
||||
if (!storage)
|
||||
return nullptr;
|
||||
|
||||
uintptr_t alignedPtr = reinterpret_cast<uintptr_t>(storage);
|
||||
size_t padding = alignment - static_cast<size_t>(alignedPtr % alignment);
|
||||
|
||||
uint8_t *storageLoc = static_cast<uint8_t*>(storage);
|
||||
uint8_t *objectLoc = storageLoc + padding;
|
||||
uint8_t *paddingSizeLoc = storageLoc + padding - 1;
|
||||
|
||||
*reinterpret_cast<uint8_t*>(paddingSizeLoc) = static_cast<uint8_t>(padding);
|
||||
|
||||
return objectLoc;
|
||||
}
|
||||
|
||||
static void AlignedFree(void *ptr)
|
||||
{
|
||||
size_t padding = static_cast<uint8_t*>(ptr)[-1];
|
||||
void *storageLoc = static_cast<uint8_t*>(ptr) - padding;
|
||||
|
||||
free(storageLoc);
|
||||
}
|
||||
|
||||
struct GpAudioChannelBufferChain_SDL2 final
|
||||
{
|
||||
GpAudioChannelBufferChain_SDL2();
|
||||
|
||||
static GpAudioChannelBufferChain_SDL2 *Alloc();
|
||||
void Release();
|
||||
|
||||
static const size_t kMaxCapacity = 65536;
|
||||
|
||||
size_t m_consumed;
|
||||
size_t m_used;
|
||||
uint8_t m_data[kMaxCapacity];
|
||||
GpAudioChannelBufferChain_SDL2 *m_next;
|
||||
bool m_hasTrigger;
|
||||
};
|
||||
|
||||
class GP_ALIGNED(GP_SYSTEM_MEMORY_ALIGNMENT) GpAudioChannel_SDL2 final : public IGpAudioChannel
|
||||
{
|
||||
public:
|
||||
enum ChannelState
|
||||
{
|
||||
ChannelState_Idle,
|
||||
ChannelState_Playing,
|
||||
ChannelState_Stopped,
|
||||
};
|
||||
|
||||
friend class GpAudioDriver_SDL2;
|
||||
|
||||
GpAudioChannel_SDL2();
|
||||
~GpAudioChannel_SDL2();
|
||||
|
||||
void AddRef();
|
||||
void Release();
|
||||
|
||||
void SetAudioChannelContext(IGpAudioChannelCallbacks *callbacks) override;
|
||||
void PostBuffer(const void *buffer, size_t bufferSize) override;
|
||||
void Stop() override;
|
||||
void Destroy() override;
|
||||
|
||||
void Consume(uint8_t *output, size_t sz);
|
||||
|
||||
static GpAudioChannel_SDL2 *Alloc(GpAudioDriver_SDL2 *driver);
|
||||
|
||||
private:
|
||||
bool Init(GpAudioDriver_SDL2 *driver);
|
||||
|
||||
IGpAudioChannelCallbacks *m_callbacks;
|
||||
IGpMutex *m_mutex;
|
||||
GpAudioDriver_SDL2 *m_owner;
|
||||
|
||||
SDL_atomic_t m_refCount;
|
||||
|
||||
GpAudioChannelBufferChain_SDL2 *m_firstPendingBuffer;
|
||||
GpAudioChannelBufferChain_SDL2 *m_lastPendingBuffer;
|
||||
|
||||
ChannelState m_channelState;
|
||||
};
|
||||
|
||||
class GP_ALIGNED(GP_SYSTEM_MEMORY_ALIGNMENT) GpAudioDriver_SDL2 final : public IGpAudioDriver, public IGpPrefsHandler
|
||||
{
|
||||
public:
|
||||
friend class GpAudioChannel_SDL2;
|
||||
|
||||
explicit GpAudioDriver_SDL2(const GpAudioDriverProperties &properties);
|
||||
~GpAudioDriver_SDL2();
|
||||
|
||||
IGpAudioChannel *CreateChannel() override;
|
||||
void SetMasterVolume(uint32_t vol, uint32_t maxVolume) override;
|
||||
void Shutdown() override;
|
||||
IGpPrefsHandler *GetPrefsHandler() const override;
|
||||
|
||||
void ApplyPrefs(const void *identifier, size_t identifierSize, const void *contents, size_t contentsSize, uint32_t version) override;
|
||||
bool SavePrefs(void *context, WritePrefsFunc_t writeFunc) override;
|
||||
|
||||
bool Init();
|
||||
|
||||
private:
|
||||
void DetachAudioChannel(GpAudioChannel_SDL2 *channel);
|
||||
|
||||
static void SDLCALL StaticMixAudio(void *userdata, Uint8 *stream, int len);
|
||||
|
||||
void MixAudio(void *stream, size_t len);
|
||||
void RefillMixChunk(GpAudioChannel_SDL2 *const*channels, size_t numChannels);
|
||||
|
||||
GpAudioDriverProperties m_properties;
|
||||
IGpMutex *m_mutex;
|
||||
IGpMutex *m_mixState;
|
||||
|
||||
static const size_t kMaxChannels = 16;
|
||||
static const size_t kMixChunkSize = 256;
|
||||
|
||||
GpAudioChannel_SDL2 *m_channels[kMaxChannels];
|
||||
size_t m_numChannels;
|
||||
|
||||
bool m_sdlAudioRunning;
|
||||
|
||||
GP_ALIGNED(GP_SYSTEM_MEMORY_ALIGNMENT) int16_t m_mixChunk[kMixChunkSize];
|
||||
size_t m_mixChunkReadOffset;
|
||||
};
|
||||
|
||||
GpAudioChannelBufferChain_SDL2::GpAudioChannelBufferChain_SDL2()
|
||||
: m_used(0)
|
||||
, m_consumed(0)
|
||||
, m_next(nullptr)
|
||||
, m_hasTrigger(false)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
GpAudioChannelBufferChain_SDL2 *GpAudioChannelBufferChain_SDL2::Alloc()
|
||||
{
|
||||
void *storage = AlignedAlloc(sizeof(GpAudioChannelBufferChain_SDL2), GP_SYSTEM_MEMORY_ALIGNMENT);
|
||||
return new (storage) GpAudioChannelBufferChain_SDL2();
|
||||
}
|
||||
|
||||
void GpAudioChannelBufferChain_SDL2::Release()
|
||||
{
|
||||
this->~GpAudioChannelBufferChain_SDL2();
|
||||
AlignedFree(this);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
// GpAudioChannel
|
||||
|
||||
GpAudioChannel_SDL2::GpAudioChannel_SDL2()
|
||||
: m_callbacks(nullptr)
|
||||
, m_mutex(nullptr)
|
||||
, m_owner(nullptr)
|
||||
, m_firstPendingBuffer(nullptr)
|
||||
, m_lastPendingBuffer(nullptr)
|
||||
{
|
||||
SDL_AtomicSet(&m_refCount, 1);
|
||||
}
|
||||
|
||||
GpAudioChannel_SDL2::~GpAudioChannel_SDL2()
|
||||
{
|
||||
Stop();
|
||||
|
||||
if (m_mutex)
|
||||
m_mutex->Destroy();
|
||||
|
||||
while (m_firstPendingBuffer)
|
||||
{
|
||||
GpAudioChannelBufferChain_SDL2 *buffer = m_firstPendingBuffer;
|
||||
m_firstPendingBuffer = buffer->m_next;
|
||||
buffer->Release();
|
||||
}
|
||||
}
|
||||
|
||||
void GpAudioChannel_SDL2::AddRef()
|
||||
{
|
||||
SDL_AtomicIncRef(&m_refCount);
|
||||
}
|
||||
|
||||
void GpAudioChannel_SDL2::Release()
|
||||
{
|
||||
if (SDL_AtomicDecRef(&m_refCount))
|
||||
{
|
||||
this->~GpAudioChannel_SDL2();
|
||||
AlignedFree(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void GpAudioChannel_SDL2::SetAudioChannelContext(IGpAudioChannelCallbacks *callbacks)
|
||||
{
|
||||
m_callbacks = callbacks;
|
||||
}
|
||||
|
||||
void GpAudioChannel_SDL2::PostBuffer(const void *buffer, size_t bufferSize)
|
||||
{
|
||||
m_mutex->Lock();
|
||||
|
||||
while (bufferSize > 0)
|
||||
{
|
||||
GpAudioChannelBufferChain_SDL2 *newBuffer = GpAudioChannelBufferChain_SDL2::Alloc();
|
||||
if (newBuffer == nullptr)
|
||||
break;
|
||||
|
||||
if (m_lastPendingBuffer == nullptr)
|
||||
m_firstPendingBuffer = newBuffer;
|
||||
else
|
||||
m_lastPendingBuffer->m_next = newBuffer;
|
||||
m_lastPendingBuffer = newBuffer;
|
||||
|
||||
size_t bufferable = newBuffer->kMaxCapacity;
|
||||
if (bufferSize < bufferable)
|
||||
bufferable = bufferSize;
|
||||
|
||||
memcpy(newBuffer->m_data, buffer, bufferable);
|
||||
|
||||
buffer = static_cast<const uint8_t*>(buffer) + bufferable;
|
||||
bufferSize -= bufferable;
|
||||
m_lastPendingBuffer->m_used = bufferable;
|
||||
m_lastPendingBuffer->m_hasTrigger = (bufferSize == 0);
|
||||
}
|
||||
|
||||
m_mutex->Unlock();
|
||||
}
|
||||
|
||||
void GpAudioChannel_SDL2::Stop()
|
||||
{
|
||||
m_mutex->Lock();
|
||||
|
||||
GpAudioChannelBufferChain_SDL2 *buffer = m_firstPendingBuffer;
|
||||
m_firstPendingBuffer = nullptr;
|
||||
m_lastPendingBuffer = nullptr;
|
||||
|
||||
while (buffer)
|
||||
{
|
||||
if (buffer->m_hasTrigger && m_callbacks)
|
||||
m_callbacks->NotifyBufferFinished();
|
||||
|
||||
GpAudioChannelBufferChain_SDL2 *nextBuffer = buffer->m_next;
|
||||
buffer->Release();
|
||||
|
||||
buffer = nextBuffer;
|
||||
}
|
||||
|
||||
m_mutex->Unlock();
|
||||
}
|
||||
|
||||
void GpAudioChannel_SDL2::Destroy()
|
||||
{
|
||||
if (m_owner)
|
||||
m_owner->DetachAudioChannel(this);
|
||||
|
||||
this->Release();
|
||||
}
|
||||
|
||||
bool GpAudioChannel_SDL2::Init(GpAudioDriver_SDL2 *driver)
|
||||
{
|
||||
m_owner = driver;
|
||||
m_mutex = driver->m_properties.m_systemServices->CreateRecursiveMutex();
|
||||
if (!m_mutex)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void GpAudioChannel_SDL2::Consume(uint8_t *output, size_t sz)
|
||||
{
|
||||
m_mutex->Lock();
|
||||
|
||||
while (m_firstPendingBuffer != nullptr)
|
||||
{
|
||||
GpAudioChannelBufferChain_SDL2 *buffer = m_firstPendingBuffer;
|
||||
const size_t available = (buffer->m_used - buffer->m_consumed);
|
||||
if (available <= sz)
|
||||
{
|
||||
memcpy(output, buffer->m_data + buffer->m_consumed, available);
|
||||
sz -= available;
|
||||
output += available;
|
||||
|
||||
m_firstPendingBuffer = buffer->m_next;
|
||||
if (m_firstPendingBuffer == nullptr)
|
||||
m_lastPendingBuffer = nullptr;
|
||||
|
||||
if (buffer->m_hasTrigger && m_callbacks)
|
||||
m_callbacks->NotifyBufferFinished();
|
||||
|
||||
buffer->Release();
|
||||
|
||||
if (sz == 0)
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
memcpy(output, buffer->m_data + buffer->m_consumed, sz);
|
||||
buffer->m_consumed += sz;
|
||||
buffer += sz;
|
||||
sz = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
m_mutex->Unlock();
|
||||
|
||||
memset(output, 0x80, sz);
|
||||
}
|
||||
|
||||
GpAudioChannel_SDL2 *GpAudioChannel_SDL2::Alloc(GpAudioDriver_SDL2 *driver)
|
||||
{
|
||||
void *storage = AlignedAlloc(sizeof(GpAudioChannel_SDL2), GP_SYSTEM_MEMORY_ALIGNMENT);
|
||||
if (!storage)
|
||||
return nullptr;
|
||||
|
||||
GpAudioChannel_SDL2 *channel = new (storage) GpAudioChannel_SDL2();
|
||||
if (!channel->Init(driver))
|
||||
{
|
||||
channel->Destroy();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return channel;
|
||||
}
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
// GpAudioDriver_SDL2
|
||||
|
||||
GpAudioDriver_SDL2::GpAudioDriver_SDL2(const GpAudioDriverProperties &properties)
|
||||
: m_properties(properties)
|
||||
, m_mutex(nullptr)
|
||||
, m_numChannels(0)
|
||||
, m_sdlAudioRunning(false)
|
||||
, m_mixChunkReadOffset(kMixChunkSize)
|
||||
|
||||
{
|
||||
for (size_t i = 0; i < kMaxChannels; i++)
|
||||
m_channels[i] = nullptr;
|
||||
|
||||
for (size_t i = 0; i < kMixChunkSize; i++)
|
||||
m_mixChunk[i] = 0;
|
||||
}
|
||||
|
||||
GpAudioDriver_SDL2::~GpAudioDriver_SDL2()
|
||||
{
|
||||
if (m_sdlAudioRunning)
|
||||
SDL_CloseAudio();
|
||||
|
||||
if (m_mutex)
|
||||
m_mutex->Destroy();
|
||||
}
|
||||
|
||||
IGpAudioChannel *GpAudioDriver_SDL2::CreateChannel()
|
||||
{
|
||||
GpAudioChannel_SDL2 *newChannel = GpAudioChannel_SDL2::Alloc(this);
|
||||
if (!newChannel)
|
||||
return nullptr;
|
||||
|
||||
m_mutex->Lock();
|
||||
if (m_numChannels == kMaxChannels)
|
||||
{
|
||||
newChannel->Destroy();
|
||||
m_mutex->Unlock();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
m_channels[m_numChannels] = newChannel;
|
||||
m_numChannels++;
|
||||
|
||||
m_mutex->Unlock();
|
||||
|
||||
return newChannel;
|
||||
}
|
||||
|
||||
void GpAudioDriver_SDL2::SetMasterVolume(uint32_t vol, uint32_t maxVolume)
|
||||
{
|
||||
}
|
||||
|
||||
void GpAudioDriver_SDL2::Shutdown()
|
||||
{
|
||||
this->~GpAudioDriver_SDL2();
|
||||
AlignedFree(this);
|
||||
}
|
||||
|
||||
IGpPrefsHandler *GpAudioDriver_SDL2::GetPrefsHandler() const
|
||||
{
|
||||
return const_cast<GpAudioDriver_SDL2*>(this);
|
||||
}
|
||||
|
||||
void GpAudioDriver_SDL2::ApplyPrefs(const void *identifier, size_t identifierSize, const void *contents, size_t contentsSize, uint32_t version)
|
||||
{
|
||||
}
|
||||
|
||||
bool GpAudioDriver_SDL2::SavePrefs(void *context, WritePrefsFunc_t writeFunc)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GpAudioDriver_SDL2::Init()
|
||||
{
|
||||
m_mutex = m_properties.m_systemServices->CreateMutex();
|
||||
if (!m_mutex)
|
||||
return false;
|
||||
|
||||
SDL_AudioSpec requestedSpec;
|
||||
memset(&requestedSpec, 0, sizeof(requestedSpec));
|
||||
|
||||
requestedSpec.callback = GpAudioDriver_SDL2::StaticMixAudio;
|
||||
requestedSpec.channels = 1;
|
||||
requestedSpec.format = AUDIO_S16;
|
||||
requestedSpec.freq = m_properties.m_sampleRate;
|
||||
requestedSpec.samples = 512;
|
||||
requestedSpec.userdata = this;
|
||||
|
||||
if (SDL_OpenAudio(&requestedSpec, nullptr))
|
||||
{
|
||||
requestedSpec.freq = 22050;
|
||||
if (SDL_OpenAudio(&requestedSpec, nullptr))
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_PauseAudio(0);
|
||||
|
||||
m_sdlAudioRunning = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void GpAudioDriver_SDL2::DetachAudioChannel(GpAudioChannel_SDL2 *channel)
|
||||
{
|
||||
m_mutex->Lock();
|
||||
const size_t numChannels = m_numChannels;
|
||||
for (size_t i = 0; i < numChannels; i++)
|
||||
{
|
||||
if (m_channels[i] == channel)
|
||||
{
|
||||
m_numChannels = numChannels - 1;
|
||||
m_channels[i] = m_channels[numChannels - 1];
|
||||
m_channels[numChannels - 1] = nullptr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
m_mutex->Unlock();
|
||||
}
|
||||
|
||||
void GpAudioDriver_SDL2::StaticMixAudio(void *userdata, Uint8 *stream, int len)
|
||||
{
|
||||
static_cast<GpAudioDriver_SDL2*>(userdata)->MixAudio(stream, static_cast<size_t>(len));
|
||||
}
|
||||
|
||||
void GpAudioDriver_SDL2::MixAudio(void *stream, size_t len)
|
||||
{
|
||||
GpAudioChannel_SDL2 *mixingChannels[kMaxChannels];
|
||||
size_t numChannels = 0;
|
||||
|
||||
m_mutex->Lock();
|
||||
numChannels = m_numChannels;
|
||||
for (size_t i = 0; i < numChannels; i++)
|
||||
{
|
||||
GpAudioChannel_SDL2 *channel = m_channels[i];
|
||||
channel->AddRef();
|
||||
|
||||
mixingChannels[i] = channel;
|
||||
}
|
||||
m_mutex->Unlock();
|
||||
|
||||
size_t samplesRemaining = len / sizeof(int16_t);
|
||||
|
||||
for (;;)
|
||||
{
|
||||
size_t availableInMixChunk = kMixChunkSize - m_mixChunkReadOffset;
|
||||
|
||||
if (availableInMixChunk > samplesRemaining)
|
||||
{
|
||||
m_mixChunkReadOffset += samplesRemaining;
|
||||
memcpy(stream, m_mixChunk + m_mixChunkReadOffset, samplesRemaining * sizeof(int16_t));
|
||||
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
memcpy(stream, m_mixChunk + m_mixChunkReadOffset, availableInMixChunk * sizeof(int16_t));
|
||||
|
||||
stream = static_cast<int16_t*>(stream) + availableInMixChunk;
|
||||
samplesRemaining -= availableInMixChunk;
|
||||
|
||||
m_mixChunkReadOffset = 0;
|
||||
RefillMixChunk(mixingChannels, numChannels);
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < numChannels; i++)
|
||||
mixingChannels[i]->Release();
|
||||
}
|
||||
|
||||
void GpAudioDriver_SDL2::RefillMixChunk(GpAudioChannel_SDL2 *const*channels, size_t numChannels)
|
||||
{
|
||||
uint8_t audioMixBufferUnaligned[kMixChunkSize + GP_SYSTEM_MEMORY_ALIGNMENT];
|
||||
uint8_t *audioMixBuffer = audioMixBufferUnaligned;
|
||||
|
||||
{
|
||||
uintptr_t bufferPtr = reinterpret_cast<uintptr_t>(audioMixBuffer);
|
||||
size_t alignPadding = GP_SYSTEM_MEMORY_ALIGNMENT - (bufferPtr % GP_SYSTEM_MEMORY_ALIGNMENT);
|
||||
audioMixBuffer += alignPadding;
|
||||
}
|
||||
|
||||
bool noAudio = true;
|
||||
|
||||
for (size_t i = 0; i < numChannels; i++)
|
||||
{
|
||||
channels[i]->Consume(audioMixBuffer, kMixChunkSize);
|
||||
|
||||
if (i == 0)
|
||||
{
|
||||
noAudio = false;
|
||||
for (size_t j = 0; j < kMixChunkSize; j++)
|
||||
m_mixChunk[j] = (audioMixBuffer[j] - 0x80) * 25;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (size_t j = 0; j < kMixChunkSize; j++)
|
||||
m_mixChunk[j] += (audioMixBuffer[j] - 0x80) * 25;
|
||||
}
|
||||
}
|
||||
|
||||
if (noAudio)
|
||||
memset(m_mixChunk, 0, kMixChunkSize * sizeof(m_mixChunk[0]));
|
||||
}
|
||||
|
||||
|
||||
IGpAudioDriver *GpDriver_CreateAudioDriver_SDL(const GpAudioDriverProperties &properties)
|
||||
{
|
||||
void *storage = AlignedAlloc(sizeof(GpAudioDriver_SDL2), GP_SYSTEM_MEMORY_ALIGNMENT);
|
||||
if (!storage)
|
||||
return nullptr;
|
||||
|
||||
GpAudioDriver_SDL2 *driver = new (storage) GpAudioDriver_SDL2(properties);
|
||||
if (!driver->Init())
|
||||
{
|
||||
driver->Shutdown();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return driver;
|
||||
}
|
3189
AerofoilSDL/GpDisplayDriver_SDL_GL2.cpp
Normal file
3189
AerofoilSDL/GpDisplayDriver_SDL_GL2.cpp
Normal file
File diff suppressed because it is too large
Load Diff
70
AerofoilSDL/GpFiberStarter_SDL.cpp
Normal file
70
AerofoilSDL/GpFiberStarter_SDL.cpp
Normal file
@@ -0,0 +1,70 @@
|
||||
#include "GpFiberStarter.h"
|
||||
#include "GpFiber_SDL.h"
|
||||
|
||||
#include "IGpSystemServices.h"
|
||||
#include "IGpThreadEvent.h"
|
||||
|
||||
#include "SDL_thread.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
namespace GpFiberStarter_SDL
|
||||
{
|
||||
struct FiberStartState
|
||||
{
|
||||
GpFiberStarter::ThreadFunc_t m_threadFunc;
|
||||
IGpThreadEvent *m_creatingReturnEvent;
|
||||
IGpThreadEvent *m_creatingWakeEvent;
|
||||
void *m_context;
|
||||
};
|
||||
|
||||
static int SDLCALL FiberStartRoutine(void *lpThreadParameter)
|
||||
{
|
||||
const FiberStartState *tss = static_cast<const FiberStartState*>(lpThreadParameter);
|
||||
|
||||
GpFiberStarter::ThreadFunc_t threadFunc = tss->m_threadFunc;
|
||||
IGpThreadEvent *creatingReturnEvent = tss->m_creatingReturnEvent;
|
||||
IGpThreadEvent *wakeEvent = tss->m_creatingWakeEvent;
|
||||
void *context = tss->m_context;
|
||||
creatingReturnEvent->Signal();
|
||||
|
||||
wakeEvent->Wait();
|
||||
|
||||
threadFunc(context);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
IGpFiber *GpFiberStarter::StartFiber(IGpSystemServices *systemServices, ThreadFunc_t threadFunc, void *context, IGpFiber *creatingFiber)
|
||||
{
|
||||
IGpThreadEvent *returnEvent = systemServices->CreateThreadEvent(true, false);
|
||||
if (!returnEvent)
|
||||
return nullptr;
|
||||
|
||||
IGpThreadEvent *wakeEvent = systemServices->CreateThreadEvent(true, false);
|
||||
if (!wakeEvent)
|
||||
{
|
||||
returnEvent->Destroy();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
GpFiberStarter_SDL::FiberStartState startState;
|
||||
startState.m_context = context;
|
||||
startState.m_creatingReturnEvent = returnEvent;
|
||||
startState.m_creatingWakeEvent = wakeEvent;
|
||||
startState.m_threadFunc = threadFunc;
|
||||
|
||||
SDL_Thread *thread = SDL_CreateThread(GpFiberStarter_SDL::FiberStartRoutine, "Fiber", &startState);
|
||||
if (!thread)
|
||||
{
|
||||
returnEvent->Destroy();
|
||||
wakeEvent->Destroy();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
returnEvent->Wait();
|
||||
returnEvent->Destroy();
|
||||
|
||||
return new GpFiber_SDL(thread, wakeEvent);
|
||||
}
|
29
AerofoilSDL/GpFiber_SDL.cpp
Normal file
29
AerofoilSDL/GpFiber_SDL.cpp
Normal file
@@ -0,0 +1,29 @@
|
||||
#include "GpFiber_SDL.h"
|
||||
#include "IGpThreadEvent.h"
|
||||
|
||||
GpFiber_SDL::GpFiber_SDL(SDL_Thread *thread, IGpThreadEvent *threadEvent)
|
||||
: m_event(threadEvent)
|
||||
, m_thread(thread)
|
||||
{
|
||||
}
|
||||
|
||||
GpFiber_SDL::~GpFiber_SDL()
|
||||
{
|
||||
m_event->Destroy();
|
||||
}
|
||||
|
||||
void GpFiber_SDL::YieldTo(IGpFiber *toFiber)
|
||||
{
|
||||
static_cast<GpFiber_SDL*>(toFiber)->m_event->Signal();
|
||||
m_event->Wait();
|
||||
}
|
||||
|
||||
void GpFiber_SDL::YieldToTerminal(IGpFiber *toFiber)
|
||||
{
|
||||
static_cast<GpFiber_SDL*>(toFiber)->m_event->Signal();
|
||||
}
|
||||
|
||||
void GpFiber_SDL::Destroy()
|
||||
{
|
||||
delete this;
|
||||
}
|
24
AerofoilSDL/GpFiber_SDL.h
Normal file
24
AerofoilSDL/GpFiber_SDL.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "IGpFiber.h"
|
||||
#include "SDL_thread.h"
|
||||
|
||||
struct IGpThreadEvent;
|
||||
|
||||
class GpFiber_SDL final : public IGpFiber
|
||||
{
|
||||
public:
|
||||
explicit GpFiber_SDL(SDL_Thread *thread, IGpThreadEvent *threadEvent);
|
||||
~GpFiber_SDL();
|
||||
|
||||
void YieldTo(IGpFiber *fromFiber) override;
|
||||
void YieldToTerminal(IGpFiber *fromFiber) override;
|
||||
void Destroy() override;
|
||||
|
||||
private:
|
||||
static int SDLCALL InternalThreadFunction(void *data);
|
||||
|
||||
bool m_isDestroying;
|
||||
IGpThreadEvent *m_event;
|
||||
SDL_Thread *m_thread;
|
||||
};
|
108
AerofoilSDL/GpMain_SDL_Win32.cpp
Normal file
108
AerofoilSDL/GpMain_SDL_Win32.cpp
Normal file
@@ -0,0 +1,108 @@
|
||||
#include "SDL.h"
|
||||
|
||||
#include "GpMain.h"
|
||||
#include "GpAudioDriverFactory.h"
|
||||
#include "GpDisplayDriverFactory.h"
|
||||
#include "GpGlobalConfig.h"
|
||||
#include "GpFiber_Win32.h"
|
||||
#include "GpFiber_SDL.h"
|
||||
#include "GpFileSystem_Win32.h"
|
||||
#include "GpLogDriver_Win32.h"
|
||||
#include "GpFontHandlerFactory.h"
|
||||
#include "GpInputDriverFactory.h"
|
||||
#include "GpAppInterface.h"
|
||||
#include "GpSystemServices_Win32.h"
|
||||
#include "GpVOSEvent.h"
|
||||
|
||||
#include "IGpFileSystem.h"
|
||||
#include "IGpThreadEvent.h"
|
||||
#include "IGpVOSEventQueue.h"
|
||||
|
||||
#include "GpWindows.h"
|
||||
|
||||
#include "resource.h"
|
||||
|
||||
#include <shellapi.h>
|
||||
#include <stdio.h>
|
||||
#include <windowsx.h>
|
||||
|
||||
|
||||
GpWindowsGlobals g_gpWindowsGlobals;
|
||||
|
||||
extern "C" __declspec(dllimport) IGpInputDriver *GpDriver_CreateInputDriver_XInput(const GpInputDriverProperties &properties);
|
||||
extern "C" __declspec(dllimport) IGpFontHandler *GpDriver_CreateFontHandler_FreeType2(const GpFontHandlerProperties &properties);
|
||||
|
||||
IGpDisplayDriver *GpDriver_CreateDisplayDriver_SDL_GL2(const GpDisplayDriverProperties &properties);
|
||||
IGpAudioDriver *GpDriver_CreateAudioDriver_SDL(const GpAudioDriverProperties &properties);
|
||||
|
||||
|
||||
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
|
||||
{
|
||||
if (SDL_Init(SDL_INIT_VIDEO) < 0)
|
||||
return -1;
|
||||
|
||||
LPWSTR cmdLine = GetCommandLineW();
|
||||
|
||||
int nArgs;
|
||||
LPWSTR *cmdLineArgs = CommandLineToArgvW(cmdLine, &nArgs);
|
||||
|
||||
for (int i = 1; i < nArgs; i++)
|
||||
{
|
||||
if (!wcscmp(cmdLineArgs[i], L"-diagnostics"))
|
||||
GpLogDriver_Win32::Init();
|
||||
|
||||
if (!wcscmp(cmdLineArgs[i], L"-touchscreensimulation"))
|
||||
GpSystemServices_Win32::GetInstance()->SetTouchscreenSimulation(true);
|
||||
}
|
||||
|
||||
IGpLogDriver *logger = GpLogDriver_Win32::GetInstance();
|
||||
GpDriverCollection *drivers = GpAppInterface_Get()->PL_GetDriverCollection();
|
||||
|
||||
drivers->SetDriver<GpDriverIDs::kFileSystem>(GpFileSystem_Win32::GetInstance());
|
||||
drivers->SetDriver<GpDriverIDs::kSystemServices>(GpSystemServices_Win32::GetInstance());
|
||||
drivers->SetDriver<GpDriverIDs::kLog>(GpLogDriver_Win32::GetInstance());
|
||||
|
||||
g_gpWindowsGlobals.m_hInstance = hInstance;
|
||||
g_gpWindowsGlobals.m_hPrevInstance = hPrevInstance;
|
||||
g_gpWindowsGlobals.m_cmdLine = cmdLine;
|
||||
g_gpWindowsGlobals.m_cmdLineArgc = nArgs;
|
||||
g_gpWindowsGlobals.m_cmdLineArgv = cmdLineArgs;
|
||||
g_gpWindowsGlobals.m_nCmdShow = nCmdShow;
|
||||
g_gpWindowsGlobals.m_baseDir = GpFileSystem_Win32::GetInstance()->GetBasePath();
|
||||
g_gpWindowsGlobals.m_hwnd = nullptr;
|
||||
|
||||
g_gpGlobalConfig.m_displayDriverType = EGpDisplayDriverType_SDL_GL2;
|
||||
|
||||
g_gpGlobalConfig.m_audioDriverType = EGpAudioDriverType_SDL2;
|
||||
|
||||
g_gpGlobalConfig.m_fontHandlerType = EGpFontHandlerType_FreeType2;
|
||||
|
||||
EGpInputDriverType inputDrivers[] =
|
||||
{
|
||||
EGpInputDriverType_XInput
|
||||
};
|
||||
|
||||
g_gpGlobalConfig.m_inputDriverTypes = inputDrivers;
|
||||
g_gpGlobalConfig.m_numInputDrivers = sizeof(inputDrivers) / sizeof(inputDrivers[0]);
|
||||
|
||||
g_gpGlobalConfig.m_osGlobals = &g_gpWindowsGlobals;
|
||||
g_gpGlobalConfig.m_logger = logger;
|
||||
g_gpGlobalConfig.m_systemServices = GpSystemServices_Win32::GetInstance();
|
||||
|
||||
GpDisplayDriverFactory::RegisterDisplayDriverFactory(EGpDisplayDriverType_SDL_GL2, GpDriver_CreateDisplayDriver_SDL_GL2);
|
||||
GpAudioDriverFactory::RegisterAudioDriverFactory(EGpAudioDriverType_SDL2, GpDriver_CreateAudioDriver_SDL);
|
||||
GpInputDriverFactory::RegisterInputDriverFactory(EGpInputDriverType_XInput, GpDriver_CreateInputDriver_XInput);
|
||||
GpFontHandlerFactory::RegisterFontHandlerFactory(EGpFontHandlerType_FreeType2, GpDriver_CreateFontHandler_FreeType2);
|
||||
|
||||
if (logger)
|
||||
logger->Printf(IGpLogDriver::Category_Information, "SDL environment configured, starting up");
|
||||
|
||||
int returnCode = GpMain::Run();
|
||||
|
||||
if (logger)
|
||||
logger->Printf(IGpLogDriver::Category_Information, "SDL environment exited with code %i, cleaning up", returnCode);
|
||||
|
||||
LocalFree(cmdLineArgs);
|
||||
|
||||
return returnCode;
|
||||
}
|
16
AerofoilSDL/ShaderCode/CopyQuadP.cpp
Normal file
16
AerofoilSDL/ShaderCode/CopyQuadP.cpp
Normal file
@@ -0,0 +1,16 @@
|
||||
#include "Functions.h"
|
||||
|
||||
#define GP_GL_SHADER_CODE_COPYQUADP_GLSL "uniform sampler2D surfaceTexture;\n"\
|
||||
"varying vec4 texCoord;\n"\
|
||||
"\n"\
|
||||
"uniform vec4 dxdy_dimensions;\n"\
|
||||
"\n"\
|
||||
"void main()\n"\
|
||||
"{\n"\
|
||||
" gl_FragColor = vec4(LinearToSRGB(texture2D(surfaceTexture, texCoord.xy).rgb), 1.0);\n"\
|
||||
"}\n"
|
||||
|
||||
namespace GpBinarizedShaders
|
||||
{
|
||||
const char *g_copyQuadP_GL2 = GP_GL_SHADER_CODE_MEDIUM_PRECISION_PREFIX GP_GL_SHADER_CODE_FUNCTIONS_H GP_GL_SHADER_CODE_COPYQUADP_GLSL;
|
||||
}
|
36
AerofoilSDL/ShaderCode/DrawQuadPaletteP.cpp
Normal file
36
AerofoilSDL/ShaderCode/DrawQuadPaletteP.cpp
Normal file
@@ -0,0 +1,36 @@
|
||||
#include "Functions.h"
|
||||
#include "DrawQuadPixelConstants.h"
|
||||
|
||||
#define GP_GL_SHADER_CODE_DRAWQUADPALETTEP_GLSL \
|
||||
"\n"\
|
||||
"varying vec4 texCoord;\n"\
|
||||
"uniform sampler2D surfaceTexture;\n"\
|
||||
"uniform sampler2D paletteTexture;\n"\
|
||||
"\n"\
|
||||
"vec3 SamplePixel(vec2 tc)\n"\
|
||||
"{\n"\
|
||||
" float surfaceColor = texture2D(surfaceTexture, tc).r;\n"\
|
||||
" return texture2D(paletteTexture, vec2(surfaceColor * (255.0 / 256.0) + (0.5 / 256.0), 0.5)).rgb;\n"\
|
||||
"}\n"\
|
||||
"\n"\
|
||||
"void main()\n"\
|
||||
"{\n"\
|
||||
" vec4 resultColor = vec4(SamplePixel(texCoord.xy), 1.0);\n"\
|
||||
" resultColor *= constants_Modulation;\n"\
|
||||
"#ifdef ENABLE_FLICKER\n"\
|
||||
" resultColor = ApplyFlicker(constants_FlickerAxis, texCoord.zw, constants_FlickerStartThreshold, constants_FlickerEndThreshold, resultColor * constants_Modulation);\n"\
|
||||
" if (resultColor.a < 1.0)\n"\
|
||||
" discard;\n"\
|
||||
"#endif\n"\
|
||||
" resultColor = ApplyDesaturation(constants_Desaturation, resultColor);\n"\
|
||||
"\n"\
|
||||
" gl_FragColor = vec4(ApplyColorSpaceTransform(resultColor.rgb), resultColor.a);\n"\
|
||||
"}\n"
|
||||
|
||||
namespace GpBinarizedShaders
|
||||
{
|
||||
const char *g_drawQuadPalettePF_GL2 = "#define ENABLE_FLICKER\n" GP_GL_SHADER_CODE_MEDIUM_PRECISION_PREFIX GP_GL_SHADER_CODE_DRAWQUADPIXELCONSTANTS_H GP_GL_SHADER_CODE_FUNCTIONS_H GP_GL_SHADER_CODE_DRAWQUADPALETTEP_GLSL;
|
||||
const char *g_drawQuadPalettePNF_GL2 = GP_GL_SHADER_CODE_MEDIUM_PRECISION_PREFIX GP_GL_SHADER_CODE_DRAWQUADPIXELCONSTANTS_H GP_GL_SHADER_CODE_FUNCTIONS_H GP_GL_SHADER_CODE_DRAWQUADPALETTEP_GLSL;
|
||||
const char *g_drawQuadPaletteICCPF_GL2 = "#define USE_ICC_PROFILE\n" "#define ENABLE_FLICKER\n" GP_GL_SHADER_CODE_MEDIUM_PRECISION_PREFIX GP_GL_SHADER_CODE_DRAWQUADPIXELCONSTANTS_H GP_GL_SHADER_CODE_FUNCTIONS_H GP_GL_SHADER_CODE_DRAWQUADPALETTEP_GLSL;
|
||||
const char *g_drawQuadPaletteICCPNF_GL2 = "#define USE_ICC_PROFILE\n" GP_GL_SHADER_CODE_MEDIUM_PRECISION_PREFIX GP_GL_SHADER_CODE_DRAWQUADPIXELCONSTANTS_H GP_GL_SHADER_CODE_FUNCTIONS_H GP_GL_SHADER_CODE_DRAWQUADPALETTEP_GLSL;
|
||||
}
|
5
AerofoilSDL/ShaderCode/DrawQuadPixelConstants.h
Normal file
5
AerofoilSDL/ShaderCode/DrawQuadPixelConstants.h
Normal file
@@ -0,0 +1,5 @@
|
||||
#define GP_GL_SHADER_CODE_DRAWQUADPIXELCONSTANTS_H "uniform vec4 constants_Modulation;\n"\
|
||||
"uniform vec2 constants_FlickerAxis;\n"\
|
||||
"uniform float constants_FlickerStartThreshold;\n"\
|
||||
"uniform float constants_FlickerEndThreshold;\n"\
|
||||
"uniform float constants_Desaturation;\n"
|
17
AerofoilSDL/ShaderCode/DrawQuadV.cpp
Normal file
17
AerofoilSDL/ShaderCode/DrawQuadV.cpp
Normal file
@@ -0,0 +1,17 @@
|
||||
#define GP_GL_SHADER_CODE_DRAWQUADV_GLSL "uniform vec4 ndcOriginAndDimensions;\n"\
|
||||
"uniform vec4 surfaceDimensions_TextureRegion;\n"\
|
||||
"attribute vec2 posUV;\n"\
|
||||
"varying vec4 texCoord;\n"\
|
||||
"\n"\
|
||||
"void main()\n"\
|
||||
"{\n"\
|
||||
" vec2 ndcPos = ndcOriginAndDimensions.xy + posUV.xy * ndcOriginAndDimensions.zw;\n"\
|
||||
"\n"\
|
||||
" gl_Position = vec4(ndcPos.x, ndcPos.y, 0.0, 1.0);\n"\
|
||||
" texCoord = vec4(posUV.xyxy * surfaceDimensions_TextureRegion.zwxy);\n"\
|
||||
"}\n"
|
||||
|
||||
namespace GpBinarizedShaders
|
||||
{
|
||||
const char *g_drawQuadV_GL2 = GP_GL_SHADER_CODE_DRAWQUADV_GLSL;
|
||||
}
|
105
AerofoilSDL/ShaderCode/Functions.h
Normal file
105
AerofoilSDL/ShaderCode/Functions.h
Normal file
@@ -0,0 +1,105 @@
|
||||
#define GP_GL_SHADER_CODE_MEDIUM_PRECISION_PREFIX "precision mediump float;\n"\
|
||||
|
||||
#define GP_GL_SHADER_CODE_HIGH_PRECISION_PREFIX "precision highp float;\n"\
|
||||
|
||||
|
||||
#define GP_GL_SHADER_CODE_FUNCTIONS_H "vec3 pow3(vec3 v, float ex)\n"\
|
||||
"{\n"\
|
||||
" return vec3(pow(v.x, ex), pow(v.y, ex), pow(v.z, ex));\n"\
|
||||
"}\n"\
|
||||
"\n"\
|
||||
"float SRGBToLinear(float v)\n"\
|
||||
"{\n"\
|
||||
" if (v <= 0.04045)\n"\
|
||||
" return v * (1.0 / 12.92);\n"\
|
||||
" else\n"\
|
||||
" return pow(((v + 0.055) * (1.0 / 1.055)), 2.4);\n"\
|
||||
"}\n"\
|
||||
"\n"\
|
||||
"vec2 SRGBToLinear(vec2 v)\n"\
|
||||
"{\n"\
|
||||
" return vec2(SRGBToLinear(v.x), SRGBToLinear(v.y));\n"\
|
||||
"}\n"\
|
||||
"\n"\
|
||||
"vec3 SRGBToLinear(vec3 v)\n"\
|
||||
"{\n"\
|
||||
" return vec3(SRGBToLinear(v.x), SRGBToLinear(v.y), SRGBToLinear(v.z));\n"\
|
||||
"}\n"\
|
||||
"\n"\
|
||||
"float LinearToSRGB(float v)\n"\
|
||||
"{\n"\
|
||||
" if (v <= 0.0031308)\n"\
|
||||
" return 12.92 * v;\n"\
|
||||
" else\n"\
|
||||
" return 1.055 * pow(v, 1.0 / 2.4) - 0.055;\n"\
|
||||
"}\n"\
|
||||
"\n"\
|
||||
"vec2 LinearToSRGB(vec2 v)\n"\
|
||||
"{\n"\
|
||||
" return vec2(LinearToSRGB(v.x), LinearToSRGB(v.y));\n"\
|
||||
"}\n"\
|
||||
"\n"\
|
||||
"vec3 LinearToSRGB(vec3 v)\n"\
|
||||
"{\n"\
|
||||
" return vec3(LinearToSRGB(v.x), LinearToSRGB(v.y), LinearToSRGB(v.z));\n"\
|
||||
"}\n"\
|
||||
"\n"\
|
||||
"vec4 ApplyFlicker(vec2 flickerAxis, vec2 coordinate, float startThreshold, float endThreshold, vec4 color)\n"\
|
||||
"{\n"\
|
||||
" float flickerTotal = dot(flickerAxis, coordinate);\n"\
|
||||
" if (flickerTotal < startThreshold)\n"\
|
||||
" return vec4(0, 0, 0, 0);\n"\
|
||||
" else if (flickerTotal >= endThreshold)\n"\
|
||||
" return vec4(color.rgb, 1);\n"\
|
||||
"\n"\
|
||||
" return vec4(1, 1, 1, 1);\n"\
|
||||
"}\n"\
|
||||
"\n"\
|
||||
"vec4 ApplyDesaturation(float desaturation, vec4 color)\n"\
|
||||
"{\n"\
|
||||
" // This is intentionally done in gamma space\n"\
|
||||
" if (desaturation == 0.0)\n"\
|
||||
" return color;\n"\
|
||||
"\n"\
|
||||
" float grayLevel = dot(color.rgb, vec3(3.0, 6.0, 1.0) / 10.0);\n"\
|
||||
"\n"\
|
||||
" color.rgb = color.rgb * (1.0 - desaturation) + vec3(grayLevel, grayLevel, grayLevel) * desaturation;\n"\
|
||||
" return color;\n"\
|
||||
"}\n"\
|
||||
"\n"\
|
||||
"float saturate(float v)\n"\
|
||||
"{\n"\
|
||||
" return min(1.0, max(0.0, v));\n"\
|
||||
"}\n"\
|
||||
"\n"\
|
||||
"vec2 saturate(vec2 v)\n"\
|
||||
"{\n"\
|
||||
" return vec2(saturate(v.x), saturate(v.y));\n"\
|
||||
"}\n"\
|
||||
"\n"\
|
||||
"vec3 saturate(vec3 v)\n"\
|
||||
"{\n"\
|
||||
" return vec3(saturate(v.x), saturate(v.y), saturate(v.z));\n"\
|
||||
"}\n"\
|
||||
"\n"\
|
||||
"vec3 AppleRGBToSRGBLinear(vec3 color)\n"\
|
||||
"{\n"\
|
||||
" color = pow3(saturate(color), 1.8);\n"\
|
||||
"\n"\
|
||||
" vec3 result;\n"\
|
||||
" result = color.r * vec3(1.06870538834699, 0.024110476735, 0.00173499822713);\n"\
|
||||
" result += color.g * vec3(-0.07859532843279, 0.96007030899244, 0.02974755969275);\n"\
|
||||
" result += color.b * vec3(0.00988984558395, 0.01581936633364, 0.96851741859153);\n"\
|
||||
"\n"\
|
||||
" return result;\n"\
|
||||
"}\n"\
|
||||
"\n"\
|
||||
"vec3 ApplyColorSpaceTransform(vec3 color)\n"\
|
||||
"{\n"\
|
||||
"#ifdef USE_ICC_PROFILE\n"\
|
||||
" return saturate(AppleRGBToSRGBLinear(color));\n"\
|
||||
"#else\n"\
|
||||
" return SRGBToLinear(color);\n"\
|
||||
"#endif\n"\
|
||||
"}\n"\
|
||||
"\n"
|
25
AerofoilSDL/ShaderCode/ScaleQuadP.cpp
Normal file
25
AerofoilSDL/ShaderCode/ScaleQuadP.cpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#include "Functions.h"
|
||||
|
||||
#define GP_GL_SHADER_CODE_SCALEQUADP_GLSL "uniform sampler2D surfaceTexture;\n"\
|
||||
"varying vec4 texCoord;\n"\
|
||||
"\n"\
|
||||
"uniform vec4 dxdy_dimensions;\n"\
|
||||
"\n"\
|
||||
"void main()\n"\
|
||||
"{\n"\
|
||||
" vec2 tc = texCoord.xy;\n"\
|
||||
"\n"\
|
||||
" gl_FragColor = vec4(texture2D(surfaceTexture, tc).rgb, 1.0);\n"\
|
||||
"}\n"
|
||||
|
||||
namespace GpBinarizedShaders
|
||||
{
|
||||
const char *g_scaleQuadP_GL2 = GP_GL_SHADER_CODE_MEDIUM_PRECISION_PREFIX GP_GL_SHADER_CODE_FUNCTIONS_H GP_GL_SHADER_CODE_SCALEQUADP_GLSL;
|
||||
|
||||
extern const char *g_drawQuadRGBP_GL2;
|
||||
extern const char *g_drawQuad15BitP_GL2;
|
||||
|
||||
extern const char *g_drawQuadRGBICCP_GL2;
|
||||
extern const char *g_drawQuad15BitICCP_GL2;
|
||||
|
||||
}
|
@@ -65,10 +65,42 @@
|
||||
"id" : 0,
|
||||
"enabled" : false
|
||||
},
|
||||
{
|
||||
"name" : "32-bit color (Requires restart)",
|
||||
"itemType" : "CheckBox",
|
||||
"pos" : [ 8, 85 ],
|
||||
"size" : [ 256, 18 ],
|
||||
"id" : 0,
|
||||
"enabled" : true
|
||||
},
|
||||
{
|
||||
"name" : "Scale to fit window",
|
||||
"itemType" : "CheckBox",
|
||||
"pos" : [ 8, 105 ],
|
||||
"size" : [ 256, 18 ],
|
||||
"id" : 0,
|
||||
"enabled" : true
|
||||
},
|
||||
{
|
||||
"name" : "Simulate Apple RGB monitor",
|
||||
"itemType" : "CheckBox",
|
||||
"pos" : [ 8, 125 ],
|
||||
"size" : [ 256, 18 ],
|
||||
"id" : 0,
|
||||
"enabled" : true
|
||||
},
|
||||
{
|
||||
"name" : "Beautiful opening color fade",
|
||||
"itemType" : "CheckBox",
|
||||
"pos" : [ 8, 142 ],
|
||||
"pos" : [ 8, 152 ],
|
||||
"size" : [ 256, 18 ],
|
||||
"id" : 0,
|
||||
"enabled" : true
|
||||
},
|
||||
{
|
||||
"name" : "Full screen mode",
|
||||
"itemType" : "CheckBox",
|
||||
"pos" : [ 8, 172 ],
|
||||
"size" : [ 256, 18 ],
|
||||
"id" : 0,
|
||||
"enabled" : true
|
||||
@@ -76,7 +108,7 @@
|
||||
{
|
||||
"name" : "",
|
||||
"itemType" : "UserItem",
|
||||
"pos" : [ 8, 137 ],
|
||||
"pos" : [ 8, 147 ],
|
||||
"size" : [ 317, 1 ],
|
||||
"id" : 0,
|
||||
"enabled" : false
|
||||
|
45
ApplicationResourcePatches/DITL/1041.json
Normal file
45
ApplicationResourcePatches/DITL/1041.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"items" :
|
||||
[
|
||||
{
|
||||
"name" : "Save First",
|
||||
"itemType" : "Button",
|
||||
"pos" : [ 174, 48 ],
|
||||
"size" : [ 80, 20 ],
|
||||
"id" : 0,
|
||||
"enabled" : true
|
||||
},
|
||||
{
|
||||
"name" : "Don't Save",
|
||||
"itemType" : "Button",
|
||||
"pos" : [ 82, 48 ],
|
||||
"size" : [ 80, 20 ],
|
||||
"id" : 0,
|
||||
"enabled" : true
|
||||
},
|
||||
{
|
||||
"name" : "Cancel",
|
||||
"itemType" : "Button",
|
||||
"pos" : [ 20, 48 ],
|
||||
"size" : [ 50, 20 ],
|
||||
"id" : 0,
|
||||
"enabled" : true
|
||||
},
|
||||
{
|
||||
"name" : "Do you want to save the state of the game before quitting?",
|
||||
"itemType" : "Label",
|
||||
"pos" : [ 8, 8 ],
|
||||
"size" : [ 201, 32 ],
|
||||
"id" : 0,
|
||||
"enabled" : false
|
||||
},
|
||||
{
|
||||
"name" : "",
|
||||
"itemType" : "Icon",
|
||||
"pos" : [ 222, 8 ],
|
||||
"size" : [ 32, 32 ],
|
||||
"id" : 1072,
|
||||
"enabled" : false
|
||||
}
|
||||
]
|
||||
}
|
21
ApplicationResourcePatches/DITL/2000.json
Normal file
21
ApplicationResourcePatches/DITL/2000.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"items" :
|
||||
[
|
||||
{
|
||||
"name" : "Okay",
|
||||
"itemType" : "Button",
|
||||
"pos" : [ 376, 240 ],
|
||||
"size" : [ 58, 20 ],
|
||||
"id" : 1,
|
||||
"enabled" : true
|
||||
},
|
||||
{
|
||||
"name" : "Third Party/Licensing Info...",
|
||||
"itemType" : "Button",
|
||||
"pos" : [ 176, 240 ],
|
||||
"size" : [ 190, 20 ],
|
||||
"id" : 1,
|
||||
"enabled" : true
|
||||
}
|
||||
]
|
||||
}
|
53
ApplicationResourcePatches/DITL/2001.json
Normal file
53
ApplicationResourcePatches/DITL/2001.json
Normal file
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"items" :
|
||||
[
|
||||
{
|
||||
"name" : "Okay",
|
||||
"itemType" : "Button",
|
||||
"pos" : [ 376, 240 ],
|
||||
"size" : [ 58, 20 ],
|
||||
"id" : 1,
|
||||
"enabled" : false
|
||||
},
|
||||
{
|
||||
"name" : "Cancel",
|
||||
"itemType" : "Button",
|
||||
"pos" : [ 302, 240 ],
|
||||
"size" : [ 58, 20 ],
|
||||
"id" : 2,
|
||||
"enabled" : true
|
||||
},
|
||||
{
|
||||
"name" : "",
|
||||
"itemType" : "CustomControl",
|
||||
"pos" : [ 17, 33 ],
|
||||
"size" : [ 401, 190 ],
|
||||
"id" : 3,
|
||||
"enabled" : true
|
||||
},
|
||||
{
|
||||
"name" : "",
|
||||
"itemType" : "CustomControl",
|
||||
"pos" : [ 418, 32 ],
|
||||
"size" : [ 16, 192 ],
|
||||
"id" : 4,
|
||||
"enabled" : true
|
||||
},
|
||||
{
|
||||
"name" : "Delete",
|
||||
"itemType" : "Button",
|
||||
"pos" : [ 228, 240 ],
|
||||
"size" : [ 58, 20 ],
|
||||
"id" : 5,
|
||||
"enabled" : false
|
||||
},
|
||||
{
|
||||
"name" : "^0",
|
||||
"itemType" : "Label",
|
||||
"pos" : [ 16, 16 ],
|
||||
"size" : [ 418, 16 ],
|
||||
"id" : 10,
|
||||
"enabled" : true
|
||||
}
|
||||
]
|
||||
}
|
53
ApplicationResourcePatches/DITL/2002.json
Normal file
53
ApplicationResourcePatches/DITL/2002.json
Normal file
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"items" :
|
||||
[
|
||||
{
|
||||
"name" : "Okay",
|
||||
"itemType" : "Button",
|
||||
"pos" : [ 376, 240 ],
|
||||
"size" : [ 58, 20 ],
|
||||
"id" : 1,
|
||||
"enabled" : true
|
||||
},
|
||||
{
|
||||
"name" : "Cancel",
|
||||
"itemType" : "Button",
|
||||
"pos" : [ 302, 240 ],
|
||||
"size" : [ 58, 20 ],
|
||||
"id" : 2,
|
||||
"enabled" : true
|
||||
},
|
||||
{
|
||||
"name" : "",
|
||||
"itemType" : "CustomControl",
|
||||
"pos" : [ 17, 33 ],
|
||||
"size" : [ 401, 186 ],
|
||||
"id" : 2,
|
||||
"enabled" : true
|
||||
},
|
||||
{
|
||||
"name" : "",
|
||||
"itemType" : "CustomControl",
|
||||
"pos" : [ 418, 32 ],
|
||||
"size" : [ 16, 188 ],
|
||||
"id" : 3,
|
||||
"enabled" : true
|
||||
},
|
||||
{
|
||||
"name" : "",
|
||||
"itemType" : "EditBox",
|
||||
"pos" : [ 16, 240 ],
|
||||
"size" : [ 270, 16 ],
|
||||
"id" : 4,
|
||||
"enabled" : true
|
||||
},
|
||||
{
|
||||
"name" : "^0",
|
||||
"itemType" : "Label",
|
||||
"pos" : [ 16, 16 ],
|
||||
"size" : [ 418, 16 ],
|
||||
"id" : 10,
|
||||
"enabled" : true
|
||||
}
|
||||
]
|
||||
}
|
29
ApplicationResourcePatches/DITL/2003.json
Normal file
29
ApplicationResourcePatches/DITL/2003.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"items" :
|
||||
[
|
||||
{
|
||||
"name" : "No",
|
||||
"itemType" : "Button",
|
||||
"pos" : [ 253, 99 ],
|
||||
"size" : [ 58, 20 ],
|
||||
"id" : 1,
|
||||
"enabled" : true
|
||||
},
|
||||
{
|
||||
"name" : "Yes",
|
||||
"itemType" : "Button",
|
||||
"pos" : [ 184, 99 ],
|
||||
"size" : [ 58, 20 ],
|
||||
"id" : 2,
|
||||
"enabled" : true
|
||||
},
|
||||
{
|
||||
"name" : "^0 already exists.\rDo you want to replace it?",
|
||||
"itemType" : "Label",
|
||||
"pos" : [ 16, 16 ],
|
||||
"size" : [ 295, 74 ],
|
||||
"id" : 0,
|
||||
"enabled" : false
|
||||
}
|
||||
]
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user