mirror of
https://github.com/kichikuou/system3-sdl2.git
synced 2026-10-03 04:18:04 +03:00
Add SDL3 support alongside SDL2
Centralize the differences between SDL2 and SDL3 behind compatibility interfaces and select the appropriate implementations with the SYSTEM3_SDL_VERSION CMake option. The default is SDL2 except on Android, which supports only SDL3.
This commit is contained in:
@@ -4,6 +4,7 @@ on: [push, pull_request]
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
name: Android SDL3
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
@@ -26,5 +27,6 @@ jobs:
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: system3-android-sdl3
|
||||
path: android/app/build/outputs/apk/release/app-release.apk
|
||||
archive: false
|
||||
|
||||
@@ -8,6 +8,10 @@ env:
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
sdl-version: [2, 3]
|
||||
name: Emscripten SDL${{ matrix.sdl-version }}
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
@@ -32,9 +36,11 @@ jobs:
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
mkdir -p out/wasm
|
||||
cd out/wasm
|
||||
emcmake cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_COMPILE_WARNING_AS_ERROR=YES ../../
|
||||
mkdir -p out/wasm-sdl${{ matrix.sdl-version }}
|
||||
cd out/wasm-sdl${{ matrix.sdl-version }}
|
||||
emcmake cmake -DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_COMPILE_WARNING_AS_ERROR=YES \
|
||||
-DSYSTEM3_SDL_VERSION=${{ matrix.sdl-version }} ../..
|
||||
make -j4
|
||||
mkdir system3
|
||||
mv system3.* system3/
|
||||
@@ -42,5 +48,5 @@ jobs:
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: system3-wasm
|
||||
path: out/wasm/system3
|
||||
name: system3-wasm-sdl${{ matrix.sdl-version }}
|
||||
path: out/wasm-sdl${{ matrix.sdl-version }}/system3
|
||||
|
||||
@@ -3,12 +3,13 @@ on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
strategy:
|
||||
matrix:
|
||||
build-type: ["Debug", "Release"]
|
||||
sdl-version: [2, 3]
|
||||
|
||||
name: Linux ${{ matrix.build-type }}
|
||||
name: Linux SDL${{ matrix.sdl-version }} ${{ matrix.build-type }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
@@ -17,11 +18,18 @@ jobs:
|
||||
- name: Install Deps
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install libsdl2-dev libsdl2-ttf-dev librtmidi-dev nlohmann-json3-dev ninja-build
|
||||
sudo apt install librtmidi-dev nlohmann-json3-dev ninja-build
|
||||
if [ "${{ matrix.sdl-version }}" = 2 ]; then
|
||||
sudo apt install libsdl2-dev libsdl2-ttf-dev
|
||||
else
|
||||
sudo apt install libsdl3-dev libsdl3-ttf-dev
|
||||
fi
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=${{ matrix.build-type }}
|
||||
cmake -S . -B build -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=${{ matrix.build-type }} \
|
||||
-DSYSTEM3_SDL_VERSION=${{ matrix.sdl-version }}
|
||||
cmake --build build
|
||||
|
||||
- name: Test
|
||||
|
||||
@@ -5,6 +5,7 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
container: devkitpro/devkita64:latest
|
||||
name: Nintendo Switch SDL2
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
@@ -15,7 +16,8 @@ jobs:
|
||||
run: |
|
||||
mkdir -p out/switch
|
||||
cd out/switch
|
||||
/opt/devkitpro/portlibs/switch/bin/aarch64-none-elf-cmake -DCMAKE_BUILD_TYPE=Release ../../
|
||||
/opt/devkitpro/portlibs/switch/bin/aarch64-none-elf-cmake \
|
||||
-DCMAKE_BUILD_TYPE=Release -DSYSTEM3_SDL_VERSION=2 ../../
|
||||
make -j4
|
||||
mkdir system3
|
||||
mv system3.* system3/
|
||||
|
||||
@@ -6,13 +6,20 @@ jobs:
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
matrix:
|
||||
arch: ["Win32", "x64"]
|
||||
include:
|
||||
- arch: "Win32"
|
||||
sdl-version: 2
|
||||
package: "system3-sdl2-msvc-32bit"
|
||||
- arch: "x64"
|
||||
sdl-version: 2
|
||||
package: "system3-sdl2-msvc-64bit"
|
||||
name: MSVS ${{ matrix.arch }}
|
||||
- arch: "Win32"
|
||||
sdl-version: 3
|
||||
package: "system3-sdl3-msvc-32bit"
|
||||
- arch: "x64"
|
||||
sdl-version: 3
|
||||
package: "system3-sdl3-msvc-64bit"
|
||||
name: MSVS SDL${{ matrix.sdl-version }} ${{ matrix.arch }}
|
||||
steps:
|
||||
|
||||
- name: Checkout
|
||||
@@ -24,7 +31,7 @@ jobs:
|
||||
run: |
|
||||
mkdir out
|
||||
cd out
|
||||
cmake -A ${{ matrix.arch }} -DCMAKE_COMPILE_WARNING_AS_ERROR=YES ../
|
||||
cmake -A ${{ matrix.arch }} -DCMAKE_COMPILE_WARNING_AS_ERROR=YES -DSYSTEM3_SDL_VERSION=${{ matrix.sdl-version }} ../
|
||||
cmake --build . --config Release
|
||||
cmake --install . --prefix artifacts
|
||||
cp ../COPYING.txt artifacts/
|
||||
|
||||
@@ -10,14 +10,23 @@ jobs:
|
||||
include:
|
||||
- sys: mingw32
|
||||
package: "system3-sdl2-32bit"
|
||||
sdl-version: 2
|
||||
sdl-deps: "SDL2:p SDL2_ttf:p"
|
||||
deps: ""
|
||||
- sys: ucrt64
|
||||
package: "system3-sdl2-64bit"
|
||||
sdl-version: 2
|
||||
sdl-deps: "SDL2:p SDL2_ttf:p"
|
||||
deps: "nlohmann-json:p"
|
||||
- sys: ucrt64
|
||||
package: "system3-sdl3-64bit"
|
||||
sdl-version: 3
|
||||
sdl-deps: "sdl3:p sdl3-ttf:p"
|
||||
deps: "nlohmann-json:p"
|
||||
defaults:
|
||||
run:
|
||||
shell: msys2 {0}
|
||||
name: MSYS2 ${{ matrix.sys }}
|
||||
name: MSYS2 SDL${{ matrix.sdl-version }} ${{ matrix.sys }}
|
||||
steps:
|
||||
|
||||
- name: Set up MSYS2
|
||||
@@ -25,8 +34,7 @@ jobs:
|
||||
with:
|
||||
msystem: ${{ matrix.sys }}
|
||||
pacboy: >-
|
||||
SDL2:p
|
||||
SDL2_ttf:p
|
||||
${{ matrix.sdl-deps }}
|
||||
rtmidi:p
|
||||
${{ matrix.deps }}
|
||||
|
||||
@@ -48,7 +56,9 @@ jobs:
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_COMPILE_WARNING_AS_ERROR=YES
|
||||
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_COMPILE_WARNING_AS_ERROR=YES \
|
||||
-DSYSTEM3_SDL_VERSION=${{ matrix.sdl-version }}
|
||||
cmake --build build
|
||||
|
||||
- name: Test
|
||||
|
||||
@@ -3,7 +3,9 @@ android/.gradle
|
||||
android/.idea/
|
||||
android/app/.cxx/
|
||||
android/app/.externalNativeBuild
|
||||
android/app/aars/
|
||||
android/app/build
|
||||
android/app/src/main/assets
|
||||
android/keystore.jks
|
||||
android/licenses/SDL.txt
|
||||
android/local.properties
|
||||
|
||||
+101
-38
@@ -9,6 +9,29 @@ project(System3 LANGUAGES CXX)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
include(CTest)
|
||||
|
||||
if (ANDROID)
|
||||
set(_SYSTEM3_DEFAULT_SDL_VERSION "3")
|
||||
else()
|
||||
set(_SYSTEM3_DEFAULT_SDL_VERSION "2")
|
||||
endif()
|
||||
set(SYSTEM3_SDL_VERSION "${_SYSTEM3_DEFAULT_SDL_VERSION}" CACHE STRING
|
||||
"SDL major version (2 or 3)")
|
||||
set_property(CACHE SYSTEM3_SDL_VERSION PROPERTY STRINGS 2 3)
|
||||
if (NOT SYSTEM3_SDL_VERSION STREQUAL "2" AND
|
||||
NOT SYSTEM3_SDL_VERSION STREQUAL "3")
|
||||
message(FATAL_ERROR "SYSTEM3_SDL_VERSION must be 2 or 3")
|
||||
endif()
|
||||
|
||||
add_library(system3_sdl INTERFACE)
|
||||
target_compile_definitions(system3_sdl INTERFACE
|
||||
SYSTEM3_SDL_VERSION=${SYSTEM3_SDL_VERSION})
|
||||
|
||||
if (SYSTEM3_SDL_VERSION STREQUAL "2")
|
||||
set(SYSTEM3_AUDIO_BACKEND src/generic/mako.cpp)
|
||||
else()
|
||||
set(SYSTEM3_AUDIO_BACKEND src/generic/mako_sdl3.cpp)
|
||||
endif()
|
||||
|
||||
# Generates a static library from pkg_check_modules() result
|
||||
function(add_static_library name pkg)
|
||||
add_library(${name} INTERFACE)
|
||||
@@ -38,14 +61,18 @@ endif()
|
||||
|
||||
|
||||
if (ANDROID)
|
||||
if (NOT SYSTEM3_SDL_VERSION STREQUAL "3")
|
||||
message(FATAL_ERROR "Android supports SDL3 only")
|
||||
endif()
|
||||
add_library(system3 SHARED)
|
||||
|
||||
target_sources(system3 PRIVATE
|
||||
src/android/nact_android.cpp
|
||||
src/generic/mako.cpp
|
||||
${SYSTEM3_AUDIO_BACKEND}
|
||||
)
|
||||
find_library(ndk_log log)
|
||||
target_link_libraries(system3 PRIVATE SDL2 SDL2_ttf ymfm ${ndk_log})
|
||||
target_link_libraries(system3_sdl INTERFACE SDL3::SDL3 SDL3_ttf::SDL3_ttf)
|
||||
target_link_libraries(system3 PRIVATE ymfm ${ndk_log})
|
||||
|
||||
elseif (EMSCRIPTEN)
|
||||
option(JSPI "Use JavaScript Promise Integration instead of Asyncify" OFF)
|
||||
@@ -56,9 +83,14 @@ elseif (EMSCRIPTEN)
|
||||
src/emscripten/nact_emscripten.cpp
|
||||
src/emscripten/mako.cpp
|
||||
)
|
||||
set(LIBS -sUSE_SDL=2 -sUSE_SDL_TTF=2)
|
||||
target_compile_options(system3 PRIVATE ${LIBS})
|
||||
target_link_options(system3 PRIVATE ${LIBS})
|
||||
set(LIBS
|
||||
-sUSE_SDL=${SYSTEM3_SDL_VERSION}
|
||||
-sUSE_SDL_TTF=${SYSTEM3_SDL_VERSION})
|
||||
if (SYSTEM3_SDL_VERSION STREQUAL "3")
|
||||
list(APPEND LIBS -Wno-experimental)
|
||||
endif()
|
||||
target_compile_options(system3_sdl INTERFACE ${LIBS})
|
||||
target_link_options(system3_sdl INTERFACE ${LIBS})
|
||||
target_link_libraries(system3 PRIVATE idbfs.js ymfm)
|
||||
|
||||
# Without optimizations, Asyncify generates very large code.
|
||||
@@ -92,20 +124,43 @@ else() # NOT (ANDROID OR EMSCRIPTEN)
|
||||
|
||||
if (MSVC)
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(sdl2
|
||||
URL https://github.com/libsdl-org/SDL/releases/download/release-2.32.10/SDL2-devel-2.32.10-VC.zip
|
||||
URL_HASH SHA1=27f5179346a0b0db80c4dd1769c7c9d62b9a91f3)
|
||||
FetchContent_MakeAvailable(sdl2)
|
||||
find_package(SDL2 REQUIRED CONFIG PATHS ${sdl2_SOURCE_DIR}/cmake NO_DEFAULT_PATH)
|
||||
get_target_property(SDL2_DLL SDL2::SDL2 IMPORTED_LOCATION)
|
||||
if (SYSTEM3_SDL_VERSION STREQUAL "2")
|
||||
FetchContent_Declare(sdl
|
||||
URL https://github.com/libsdl-org/SDL/releases/download/release-2.32.10/SDL2-devel-2.32.10-VC.zip
|
||||
URL_HASH SHA1=27f5179346a0b0db80c4dd1769c7c9d62b9a91f3)
|
||||
FetchContent_MakeAvailable(sdl)
|
||||
find_package(SDL2 REQUIRED CONFIG PATHS ${sdl_SOURCE_DIR}/cmake NO_DEFAULT_PATH)
|
||||
set(SDL_TARGET SDL2::SDL2)
|
||||
set(SDL_MAIN_TARGET SDL2::SDL2main)
|
||||
|
||||
FetchContent_Declare(sdl2_ttf
|
||||
URL https://github.com/libsdl-org/SDL_ttf/releases/download/release-2.24.0/SDL2_ttf-devel-2.24.0-VC.zip
|
||||
URL_HASH SHA1=2d18b9a4fc2ec0eee80de2a946b088d4e6efd0ee)
|
||||
FetchContent_MakeAvailable(sdl2_ttf)
|
||||
find_package(SDL2_ttf REQUIRED CONFIG PATHS ${sdl2_ttf_SOURCE_DIR}/cmake NO_DEFAULT_PATH)
|
||||
get_target_property(SDL2TTF_DLL SDL2_ttf::SDL2_ttf IMPORTED_LOCATION)
|
||||
get_filename_component(SDL2TTF_LIBDIR ${SDL2TTF_DLL} DIRECTORY)
|
||||
FetchContent_Declare(sdl_ttf
|
||||
URL https://github.com/libsdl-org/SDL_ttf/releases/download/release-2.24.0/SDL2_ttf-devel-2.24.0-VC.zip
|
||||
URL_HASH SHA1=2d18b9a4fc2ec0eee80de2a946b088d4e6efd0ee)
|
||||
FetchContent_MakeAvailable(sdl_ttf)
|
||||
find_package(SDL2_ttf REQUIRED CONFIG PATHS ${sdl_ttf_SOURCE_DIR}/cmake NO_DEFAULT_PATH)
|
||||
set(SDL_TTF_TARGET SDL2_ttf::SDL2_ttf)
|
||||
else()
|
||||
FetchContent_Declare(sdl
|
||||
URL https://github.com/libsdl-org/SDL/releases/download/release-3.4.16/SDL3-devel-3.4.16-VC.zip
|
||||
URL_HASH SHA1=edcf1f567837e7464cf80df402661b41acd78dbf)
|
||||
FetchContent_MakeAvailable(sdl)
|
||||
find_package(SDL3 REQUIRED CONFIG PATHS ${sdl_SOURCE_DIR}/cmake NO_DEFAULT_PATH)
|
||||
set(SDL_TARGET SDL3::SDL3)
|
||||
set(SDL_MAIN_TARGET "")
|
||||
|
||||
FetchContent_Declare(sdl_ttf
|
||||
URL https://github.com/libsdl-org/SDL_ttf/releases/download/release-3.2.2/SDL3_ttf-devel-3.2.2-VC.zip
|
||||
URL_HASH SHA1=3786bc016d89ca4cf9739e4d342ca29e3e29c137)
|
||||
FetchContent_MakeAvailable(sdl_ttf)
|
||||
find_package(SDL3_ttf REQUIRED CONFIG PATHS ${sdl_ttf_SOURCE_DIR}/cmake NO_DEFAULT_PATH)
|
||||
set(SDL_TTF_TARGET SDL3_ttf::SDL3_ttf)
|
||||
endif()
|
||||
get_target_property(SDL_DLL ${SDL_TARGET} IMPORTED_LOCATION)
|
||||
get_target_property(SDL_TTF_DLL ${SDL_TTF_TARGET} IMPORTED_LOCATION)
|
||||
get_filename_component(SDL_LIBDIR ${SDL_DLL} DIRECTORY)
|
||||
get_filename_component(SDL_TTF_LIBDIR ${SDL_TTF_DLL} DIRECTORY)
|
||||
target_link_libraries(system3_sdl INTERFACE
|
||||
${SDL_MAIN_TARGET} ${SDL_TARGET} ${SDL_TTF_TARGET})
|
||||
|
||||
FetchContent_Declare(rtmidi
|
||||
URL https://github.com/thestk/rtmidi/archive/refs/tags/6.0.0.zip
|
||||
@@ -127,36 +182,40 @@ else() # NOT (ANDROID OR EMSCRIPTEN)
|
||||
|
||||
target_compile_definitions(system3 PRIVATE _CRT_NONSTDC_NO_DEPRECATE _CRT_SECURE_NO_WARNINGS)
|
||||
target_compile_options(system3 PRIVATE /utf-8 /wd4244)
|
||||
target_link_libraries(system3 PRIVATE SDL2::SDL2main SDL2::SDL2 SDL2_ttf::SDL2_ttf)
|
||||
add_custom_command(TARGET system3 POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
"$<TARGET_FILE:SDL2::SDL2>" "$<TARGET_FILE_DIR:system3>"
|
||||
"$<TARGET_FILE:${SDL_TARGET}>" "$<TARGET_FILE_DIR:system3>"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
"$<TARGET_FILE:SDL2_ttf::SDL2_ttf>" "$<TARGET_FILE_DIR:system3>"
|
||||
"$<TARGET_FILE:${SDL_TTF_TARGET}>" "$<TARGET_FILE_DIR:system3>"
|
||||
COMMENT "Copying SDL runtime DLLs"
|
||||
VERBATIM)
|
||||
set_target_properties(system3 PROPERTIES
|
||||
WIN32_EXECUTABLE TRUE
|
||||
VS_DEBUGGER_ENVIRONMENT "PATH=${SDL2_LIBDIR}$<SEMICOLON>${SDL2TTF_LIBDIR}$<SEMICOLON>$ENV{PATH}")
|
||||
VS_DEBUGGER_ENVIRONMENT "PATH=${SDL_LIBDIR}$<SEMICOLON>${SDL_TTF_LIBDIR}$<SEMICOLON>$ENV{PATH}")
|
||||
set_directory_properties(PROPERTIES VS_STARTUP_PROJECT system3)
|
||||
|
||||
install(FILES ${SDL2_DLL} ${SDL2TTF_DLL} DESTINATION .)
|
||||
install(FILES ${SDL_DLL} ${SDL_TTF_DLL} DESTINATION .)
|
||||
install(TARGETS system3 RUNTIME DESTINATION .)
|
||||
else()
|
||||
include(FindPkgConfig)
|
||||
pkg_check_modules(SDL2 REQUIRED IMPORTED_TARGET sdl2)
|
||||
pkg_check_modules(SDL2TTF REQUIRED IMPORTED_TARGET SDL2_ttf)
|
||||
if (MINGW)
|
||||
add_static_library(sdl2 SDL2)
|
||||
add_static_library(sdl2_ttf SDL2TTF)
|
||||
# Workaround for linking error
|
||||
set_property(TARGET sdl2_ttf PROPERTY INTERFACE_LINK_LIBRARIES
|
||||
$<LINK_GROUP:RESCAN,${SDL2TTF_STATIC_LIBRARIES}>)
|
||||
if (SYSTEM3_SDL_VERSION STREQUAL "2")
|
||||
pkg_check_modules(SDL REQUIRED IMPORTED_TARGET sdl2)
|
||||
pkg_check_modules(SDLTTF REQUIRED IMPORTED_TARGET SDL2_ttf)
|
||||
else()
|
||||
add_library(sdl2 ALIAS PkgConfig::SDL2)
|
||||
add_library(sdl2_ttf ALIAS PkgConfig::SDL2TTF)
|
||||
pkg_check_modules(SDL REQUIRED IMPORTED_TARGET sdl3)
|
||||
pkg_check_modules(SDLTTF REQUIRED IMPORTED_TARGET sdl3-ttf)
|
||||
endif()
|
||||
target_link_libraries(system3 PRIVATE sdl2 sdl2_ttf)
|
||||
if (MINGW)
|
||||
add_static_library(sdl SDL)
|
||||
add_static_library(sdl_ttf SDLTTF)
|
||||
# Workaround for linking error
|
||||
set_property(TARGET sdl_ttf PROPERTY INTERFACE_LINK_LIBRARIES
|
||||
$<LINK_GROUP:RESCAN,${SDLTTF_STATIC_LIBRARIES}>)
|
||||
else()
|
||||
add_library(sdl ALIAS PkgConfig::SDL)
|
||||
add_library(sdl_ttf ALIAS PkgConfig::SDLTTF)
|
||||
endif()
|
||||
target_link_libraries(system3_sdl INTERFACE sdl sdl_ttf)
|
||||
|
||||
find_package(nlohmann_json 3.2.0)
|
||||
if (nlohmann_json_FOUND)
|
||||
@@ -180,8 +239,8 @@ else() # NOT (ANDROID OR EMSCRIPTEN)
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL "Windows")
|
||||
target_sources(system3 PRIVATE
|
||||
src/win/nact_win.cpp
|
||||
src/generic/mako.cpp
|
||||
src/win/resource.rc
|
||||
${SYSTEM3_AUDIO_BACKEND}
|
||||
)
|
||||
target_compile_definitions(system3 PRIVATE NOMINMAX)
|
||||
target_link_libraries(system3 PRIVATE winmm)
|
||||
@@ -191,7 +250,7 @@ else() # NOT (ANDROID OR EMSCRIPTEN)
|
||||
else()
|
||||
target_sources(system3 PRIVATE
|
||||
src/generic/nact_generic.cpp
|
||||
src/generic/mako.cpp
|
||||
${SYSTEM3_AUDIO_BACKEND}
|
||||
)
|
||||
set(RESOURCE_PATH ${CMAKE_INSTALL_PREFIX}/share/system3/)
|
||||
install(TARGETS system3 RUNTIME DESTINATION bin)
|
||||
@@ -243,6 +302,9 @@ if (ENABLE_DEBUGGER)
|
||||
endif()
|
||||
|
||||
if(NINTENDO_SWITCH)
|
||||
if (SYSTEM3_SDL_VERSION STREQUAL "3")
|
||||
message(FATAL_ERROR "Nintendo Switch currently supports SDL2 only")
|
||||
endif()
|
||||
set(RESOURCE_PATH romfs:/)
|
||||
|
||||
nx_generate_nacp(system3.nacp
|
||||
@@ -259,16 +321,17 @@ if(NINTENDO_SWITCH)
|
||||
endif()
|
||||
|
||||
if (NOT EMSCRIPTEN)
|
||||
target_sources(system3 PRIVATE src/sys/mako_music.cpp)
|
||||
target_sources(system3 PRIVATE src/sys/music_decoder.cpp)
|
||||
target_include_directories(system3 PRIVATE deps/dr_libs deps/stb)
|
||||
# dr_mp3 and stb_vorbis are amalgamated into this translation unit; silence
|
||||
# their warnings so they are not promoted to errors by WARNING_AS_ERROR.
|
||||
set_source_files_properties(src/sys/mako_music.cpp PROPERTIES
|
||||
set_source_files_properties(src/sys/music_decoder.cpp PROPERTIES
|
||||
COMPILE_OPTIONS "$<IF:$<CXX_COMPILER_ID:MSVC>,/w,-w>")
|
||||
endif()
|
||||
|
||||
target_compile_definitions(system3 PRIVATE RESOURCE_PATH="${RESOURCE_PATH}")
|
||||
target_include_directories(system3 PRIVATE src src/sys)
|
||||
target_link_libraries(system3 PRIVATE system3_sdl)
|
||||
|
||||
if (BUILD_TESTING AND NOT ANDROID AND NOT EMSCRIPTEN AND NOT NINTENDO_SWITCH)
|
||||
add_subdirectory(test)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# System3 for SDL2
|
||||
# System3 for SDL2/SDL3
|
||||
|
||||
This is an SDL2 port of
|
||||
This is an SDL port of
|
||||
[System3 for Win32](http://takeda-toshiya.my.coocan.jp/alice/) by Takeda
|
||||
Toshiya. It supports multiple platforms, including Android and Emscripten.
|
||||
|
||||
@@ -124,6 +124,9 @@ the game ID. You need to specify the `game` option in `system3.ini`.
|
||||
|
||||
## Building from Source
|
||||
|
||||
Desktop and Emscripten builds use SDL2 by default. Pass CMake option
|
||||
`-DSYSTEM3_SDL_VERSION=3` to select SDL3.
|
||||
|
||||
### Linux (Debian, Ubuntu)
|
||||
|
||||
```bash
|
||||
@@ -131,11 +134,13 @@ $ git submodule update --init
|
||||
$ sudo apt install g++ cmake libsdl2-dev libsdl2-ttf-dev librtmidi-dev nlohmann-json3-dev
|
||||
$ mkdir -p out/debug
|
||||
$ cd out/debug
|
||||
$ cmake -DCMAKE_BUILD_TYPE=Debug ../../
|
||||
$ cmake -DCMAKE_BUILD_TYPE=Debug -DSYSTEM3_SDL_VERSION=2 ../../
|
||||
$ make
|
||||
$ sudo make install
|
||||
```
|
||||
|
||||
For SDL3, install `libsdl3-dev libsdl3-ttf-dev` and use `-DSYSTEM3_SDL_VERSION=3`.
|
||||
|
||||
### MacOS
|
||||
|
||||
```bash
|
||||
@@ -143,11 +148,13 @@ $ git submodule update --init
|
||||
$ brew install cmake pkg-config sdl2 sdl2_ttf rtmidi nlohmann-json
|
||||
$ mkdir -p out/debug
|
||||
$ cd out/debug
|
||||
$ cmake -DCMAKE_BUILD_TYPE=Debug ../../
|
||||
$ cmake -DCMAKE_BUILD_TYPE=Debug -DSYSTEM3_SDL_VERSION=2 ../../
|
||||
$ make
|
||||
$ sudo make install
|
||||
```
|
||||
|
||||
For SDL3, install `sdl3 sdl3_ttf` and use `-DSYSTEM3_SDL_VERSION=3`.
|
||||
|
||||
### Windows (MSYS2)
|
||||
|
||||
```bash
|
||||
@@ -155,27 +162,36 @@ $ git submodule update --init
|
||||
$ pacman -S make mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-cmake mingw-w64-ucrt-x86_64-SDL2 mingw-w64-ucrt-x86_64-SDL2_ttf mingw-w64-ucrt-x86_64-rtmidi mingw-w64-ucrt-x86_64-nlohmann-json
|
||||
$ mkdir -p out/debug
|
||||
$ cd out/debug
|
||||
$ cmake -G"MSYS Makefiles" -DCMAKE_BUILD_TYPE=Debug ../../
|
||||
$ cmake -G"MSYS Makefiles" -DCMAKE_BUILD_TYPE=Debug -DSYSTEM3_SDL_VERSION=2 ../../
|
||||
$ make
|
||||
```
|
||||
|
||||
For SDL3, install `mingw-w64-ucrt-x86_64-sdl3 mingw-w64-ucrt-x86_64-sdl3-ttf`
|
||||
and use `-DSYSTEM3_SDL_VERSION=3`.
|
||||
|
||||
### Windows (Microsoft Visual Studio)
|
||||
|
||||
- Install Visual Studio 2026 with the **Desktop development with C++**
|
||||
workload.
|
||||
- On the Visual Studio start screen, select **Clone a repository** and clone
|
||||
this repository.
|
||||
- Wait until the Output window reports `CMake generation finished.`
|
||||
- Wait until the Output window reports `CMake generation finished.` The
|
||||
automatically generated default configuration builds with SDL2.
|
||||
- To build with SDL3, select **Manage Configurations** from the configuration
|
||||
dropdown, create a separate Configure Preset with the CMake cache variable
|
||||
`SYSTEM3_SDL_VERSION` set to `3`, and select that preset. Visual Studio then
|
||||
configures the SDL3 build automatically.
|
||||
- Select **Build > Build All** to build the project.
|
||||
|
||||
The executable and its runtime DLLs are generated under
|
||||
`out/build/x64-Debug`:
|
||||
The executable and its runtime DLLs are generated under the active preset's
|
||||
build directory (`out/build/x64-Debug` for the default configuration):
|
||||
|
||||
- `system3.exe`
|
||||
- `SDL2.dll`
|
||||
- `SDL2_ttf.dll`
|
||||
- `SDL2.dll` or `SDL3.dll`
|
||||
- `SDL2_ttf.dll` or `SDL3_ttf.dll`
|
||||
|
||||
To run a game, copy all three files to the game folder and run `system3.exe`.
|
||||
To run a game, copy `system3.exe` and the two matching SDL DLLs to the game
|
||||
folder, then run `system3.exe`.
|
||||
|
||||
### Emscripten
|
||||
|
||||
@@ -183,10 +199,12 @@ To run a game, copy all three files to the game folder and run `system3.exe`.
|
||||
$ git submodule update --init
|
||||
$ mkdir -p out/wasm
|
||||
$ cd out/wasm
|
||||
$ emcmake cmake -DCMAKE_BUILD_TYPE=Release ../../
|
||||
$ emcmake cmake -DCMAKE_BUILD_TYPE=Release -DSYSTEM3_SDL_VERSION=2 ../../
|
||||
$ make
|
||||
```
|
||||
|
||||
For SDL3, use `-DSYSTEM3_SDL_VERSION=3`.
|
||||
|
||||
### Android
|
||||
|
||||
See [android/README.md](android/README.md).
|
||||
|
||||
@@ -8,10 +8,45 @@ else {
|
||||
}
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
// Use SDL3's official prebuilt Android Archive (AAR). The AAR provides SDL3
|
||||
// to the native CMake build through Prefab. If it is not available locally,
|
||||
// download the official Android package and extract the AAR and license.
|
||||
def sdlAarUrl = 'https://github.com/libsdl-org/SDL/releases/download/release-3.4.16/SDL3-devel-3.4.16-android.zip'
|
||||
def sdlAarName = 'SDL3-3.4.16.aar'
|
||||
def sdlLicenseName = 'SDL.txt'
|
||||
def aarsDir = file('aars')
|
||||
def licensesDir = file('../licenses')
|
||||
aarsDir.mkdirs()
|
||||
licensesDir.mkdirs()
|
||||
def aarFile = new File(aarsDir, sdlAarName)
|
||||
def licenseFile = new File(licensesDir, sdlLicenseName)
|
||||
if (!aarFile.exists() || !licenseFile.exists()) {
|
||||
logger.lifecycle("Downloading ${sdlAarName} ...")
|
||||
def tmpZip = File.createTempFile('sdl-aar', '.zip')
|
||||
new URL(sdlAarUrl).withInputStream { input ->
|
||||
tmpZip.withOutputStream { it << input }
|
||||
}
|
||||
copy {
|
||||
from zipTree(tmpZip)
|
||||
include sdlAarName
|
||||
into aarsDir
|
||||
}
|
||||
copy {
|
||||
from zipTree(tmpZip)
|
||||
include 'LICENSE.txt'
|
||||
into licensesDir
|
||||
rename 'LICENSE.txt', sdlLicenseName
|
||||
}
|
||||
tmpZip.delete()
|
||||
}
|
||||
|
||||
android {
|
||||
if (buildAsApplication) {
|
||||
namespace "io.github.kichikuou.system3"
|
||||
}
|
||||
buildFeatures {
|
||||
prefab true
|
||||
}
|
||||
compileSdkVersion 34
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
@@ -20,7 +55,7 @@ android {
|
||||
versionName "1.8.0" + gitRevision()
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments "-DANDROID_APP_PLATFORM=android-19", "-DANDROID_STL=c++_static"
|
||||
arguments "-DANDROID_STL=c++_static", "-DSYSTEM3_SDL_VERSION=3"
|
||||
// abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
|
||||
abiFilters 'armeabi-v7a', 'arm64-v8a'
|
||||
}
|
||||
@@ -84,6 +119,7 @@ android {
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||
implementation files(aarFile)
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
include $(call all-subdir-makefiles)
|
||||
@@ -1,10 +0,0 @@
|
||||
|
||||
# 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
|
||||
@@ -3,35 +3,31 @@ cmake_minimum_required(VERSION 3.14)
|
||||
project(GAME)
|
||||
set(PROJECT_ROOT_DIR ../../..)
|
||||
|
||||
# Compilation of SDL and companion libraries
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
SDL
|
||||
URL https://github.com/libsdl-org/SDL/releases/download/release-2.32.10/SDL2-2.32.10.tar.gz
|
||||
URL_HASH SHA1=ce98fa93e31836a751feca374ab28a0770b63c16
|
||||
)
|
||||
FetchContent_Declare(
|
||||
SDL_ttf
|
||||
URL https://github.com/libsdl-org/SDL_ttf/releases/download/release-2.22.0/SDL2_ttf-2.22.0.tar.gz
|
||||
URL_HASH SHA1=da5e86b601ad299a697878fab1af6f3be47b529d
|
||||
)
|
||||
set(SDL2TTF_SAMPLES OFF CACHE BOOL "Build the SDL2_ttf sample program(s)" FORCE)
|
||||
set(SDL2TTF_INSTALL OFF CACHE BOOL "Enable SDL2_ttf install target" FORCE)
|
||||
set(SDL2TTF_VENDORED ON CACHE BOOL "Use vendored third-party libraries" FORCE)
|
||||
find_package(SDL3 REQUIRED CONFIG)
|
||||
|
||||
FetchContent_MakeAvailable(SDL)
|
||||
include(FetchContent)
|
||||
set(SDLTTF_VENDORED ON CACHE BOOL "Use vendored third-party libraries" FORCE)
|
||||
set(SDLTTF_HARFBUZZ OFF CACHE BOOL "Use harfbuzz to improve text shaping" FORCE)
|
||||
set(SDLTTF_PLUTOSVG OFF CACHE BOOL "Enable plutosvg for color SVG fonts" FORCE)
|
||||
set(SDLTTF_SAMPLES OFF CACHE BOOL "Build the SDL3_ttf sample program(s)" FORCE)
|
||||
set(SDLTTF_INSTALL OFF CACHE BOOL "Enable SDL3_ttf install target" FORCE)
|
||||
FetchContent_Declare(SDL_ttf
|
||||
GIT_REPOSITORY https://github.com/libsdl-org/SDL_ttf.git
|
||||
GIT_TAG release-3.2.2
|
||||
GIT_SHALLOW TRUE
|
||||
GIT_SUBMODULES external/freetype)
|
||||
FetchContent_MakeAvailable(SDL_ttf)
|
||||
|
||||
# The main CMakeLists.txt of system3
|
||||
add_subdirectory(${PROJECT_ROOT_DIR} src)
|
||||
|
||||
# Copy asset files
|
||||
# Copy license files into the assets directory so they can be shown in-app.
|
||||
set(ASSETS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../src/main/assets)
|
||||
set(LICENSES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../licenses)
|
||||
file(MAKE_DIRECTORY ${ASSETS_DIR}/licenses)
|
||||
file(COPY_FILE ${PROJECT_ROOT_DIR}/COPYING.txt ${ASSETS_DIR}/licenses/system3)
|
||||
file(COPY_FILE ${PROJECT_ROOT_DIR}/resources/fonts/MTLc3m.ttf.license ${ASSETS_DIR}/licenses/MTLc3m)
|
||||
file(COPY_FILE ${PROJECT_ROOT_DIR}/deps/ymfm/LICENSE ${ASSETS_DIR}/licenses/ymfm)
|
||||
file(COPY_FILE ${sdl_SOURCE_DIR}/LICENSE.txt ${ASSETS_DIR}/licenses/SDL)
|
||||
file(COPY_FILE ${LICENSES_DIR}/SDL.txt ${ASSETS_DIR}/licenses/SDL)
|
||||
file(COPY_FILE ${sdl_ttf_SOURCE_DIR}/LICENSE.txt ${ASSETS_DIR}/licenses/SDL_ttf)
|
||||
file(COPY_FILE ${sdl_ttf_SOURCE_DIR}/external/freetype/docs/GPLv2.TXT ${ASSETS_DIR}/licenses/freetype)
|
||||
file(COPY_FILE ${sdl_ttf_SOURCE_DIR}/external/harfbuzz/COPYING ${ASSETS_DIR}/licenses/harfbuzz)
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_MODULE := main
|
||||
|
||||
SDL_PATH := ../SDL
|
||||
|
||||
LOCAL_C_INCLUDES := $(LOCAL_PATH)/$(SDL_PATH)/include
|
||||
|
||||
# Add your application source files here...
|
||||
LOCAL_SRC_FILES := YourSourceHere.c
|
||||
|
||||
LOCAL_SHARED_LIBRARIES := SDL2
|
||||
|
||||
LOCAL_LDLIBS := -lGLESv1_CM -lGLESv2 -lOpenSLES -llog -landroid
|
||||
|
||||
include $(BUILD_SHARED_LIBRARY)
|
||||
@@ -1,13 +0,0 @@
|
||||
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)
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ class GameActivity : SDLActivity() {
|
||||
}
|
||||
|
||||
override fun getLibraries(): Array<String> {
|
||||
return arrayOf("SDL2", "system3")
|
||||
return arrayOf("SDL3", "system3")
|
||||
}
|
||||
|
||||
override fun getArguments(): Array<String> {
|
||||
|
||||
@@ -13,7 +13,6 @@ class LicensesMenuActivity : Activity() {
|
||||
Entry("SDL", "SDL", "https://www.libsdl.org/"),
|
||||
Entry("SDL_ttf", "SDL_ttf", "https://github.com/libsdl-org/SDL_ttf"),
|
||||
Entry("FreeType", "freetype", "https://freetype.org/"),
|
||||
Entry("HarfBuzz", "harfbuzz", "https://harfbuzz.github.io/"),
|
||||
Entry("ymfm", "ymfm", "https://github.com/aaronsgiles/ymfm"),
|
||||
Entry("MotoyaLCedar W3 mono", "MTLc3m", "https://github.com/aosp-mirror/platform_frameworks_base/tree/lollipop-release/data/fonts"),
|
||||
)
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
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();
|
||||
}
|
||||
@@ -1,650 +0,0 @@
|
||||
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 /* Android 6.0 (M) */) {
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,698 +0,0 @@
|
||||
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.os.Build;
|
||||
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;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
if (mUsbManager == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
// 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
|
||||
0x1949, // Lab126, Inc.
|
||||
0x1bad, // Harmonix
|
||||
0x20d6, // PowerA
|
||||
0x24c6, // PowerA
|
||||
0x2c22, // Qanba
|
||||
0x2dc8, // 8BitDo
|
||||
0x9886, // ASTRO Gaming
|
||||
};
|
||||
|
||||
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 = {
|
||||
0x03f0, // HP
|
||||
0x044f, // Thrustmaster
|
||||
0x045e, // Microsoft
|
||||
0x0738, // Mad Catz
|
||||
0x0b05, // ASUS
|
||||
0x0e6f, // PDP
|
||||
0x0f0d, // Hori
|
||||
0x10f5, // Turtle Beach
|
||||
0x1532, // Razer Wildcat
|
||||
0x20d6, // PowerA
|
||||
0x24c6, // PowerA
|
||||
0x2dc8, // 8BitDo
|
||||
0x2e24, // Hyperkin
|
||||
0x3537, // GameSir
|
||||
};
|
||||
|
||||
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) {
|
||||
int interface_mask = 0;
|
||||
for (int interface_index = 0; interface_index < usbDevice.getInterfaceCount(); interface_index++) {
|
||||
UsbInterface usbInterface = usbDevice.getInterface(interface_index);
|
||||
if (isHIDDeviceInterface(usbDevice, usbInterface)) {
|
||||
// Check to see if we've already added this interface
|
||||
// This happens with the Xbox Series X controller which has a duplicate interface 0, which is inactive
|
||||
int interface_id = usbInterface.getId();
|
||||
if ((interface_mask & (1 << interface_id)) != 0) {
|
||||
continue;
|
||||
}
|
||||
interface_mask |= (1 << interface_id);
|
||||
|
||||
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 (Build.VERSION.SDK_INT >= 31 /* Android 12 */ &&
|
||||
mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH_CONNECT, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) {
|
||||
Log.d(TAG, "Couldn't initialize Bluetooth, missing android.permission.BLUETOOTH_CONNECT");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT <= 30 /* Android 11.0 (R) */ &&
|
||||
mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) {
|
||||
Log.d(TAG, "Couldn't initialize Bluetooth, missing android.permission.BLUETOOTH");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE) || (Build.VERSION.SDK_INT < 18 /* Android 4.3 (JELLY_BEAN_MR2) */)) {
|
||||
Log.d(TAG, "Couldn't initialize Bluetooth, this version of Android does not support Bluetooth LE");
|
||||
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 initialize(boolean usb, boolean bluetooth) {
|
||||
Log.v(TAG, "initialize(" + usb + ", " + bluetooth + ")");
|
||||
|
||||
if (usb) {
|
||||
initializeUSB();
|
||||
}
|
||||
if (bluetooth) {
|
||||
initializeBluetooth();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
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 {
|
||||
final int FLAG_MUTABLE = 0x02000000; // PendingIntent.FLAG_MUTABLE, but don't require SDK 31
|
||||
int flags;
|
||||
if (Build.VERSION.SDK_INT >= 31 /* Android 12.0 (S) */) {
|
||||
flags = FLAG_MUTABLE;
|
||||
} else {
|
||||
flags = 0;
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= 33 /* Android 14.0 (U) */) {
|
||||
Intent intent = new Intent(HIDDeviceManager.ACTION_USB_PERMISSION);
|
||||
intent.setPackage(mContext.getPackageName());
|
||||
mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, intent, flags));
|
||||
} else {
|
||||
mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, new Intent(HIDDeviceManager.ACTION_USB_PERMISSION), flags));
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
@@ -1,309 +0,0 @@
|
||||
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 /* Android 5.0 (LOLLIPOP) */) {
|
||||
try {
|
||||
result = mDevice.getSerialNumber();
|
||||
}
|
||||
catch (SecurityException exception) {
|
||||
//Log.w(TAG, "App permissions mean we cannot get serial number for device " + getDeviceName() + " message: " + exception.getMessage());
|
||||
}
|
||||
}
|
||||
if (result == null) {
|
||||
result = "";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVersion() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getManufacturerName() {
|
||||
String result = null;
|
||||
if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) {
|
||||
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 /* Android 5.0 (LOLLIPOP) */) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
package org.libsdl.app;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import java.lang.Class;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
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) {
|
||||
SDLAudioManager.setContext(context);
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
public static Context getContext() {
|
||||
return mContext;
|
||||
}
|
||||
|
||||
public static void loadLibrary(String libraryName) throws UnsatisfiedLinkError, SecurityException, NullPointerException {
|
||||
loadLibrary(libraryName, mContext);
|
||||
}
|
||||
|
||||
public static void loadLibrary(String libraryName, Context context) 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 = context.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker");
|
||||
Class<?> relinkListenerClass = context.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker$LoadListener");
|
||||
Class<?> contextClass = context.getClassLoader().loadClass("android.content.Context");
|
||||
Class<?> stringClass = context.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, context, 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;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,514 +0,0 @@
|
||||
package org.libsdl.app;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.AudioDeviceCallback;
|
||||
import android.media.AudioDeviceInfo;
|
||||
import android.media.AudioFormat;
|
||||
import android.media.AudioManager;
|
||||
import android.media.AudioRecord;
|
||||
import android.media.AudioTrack;
|
||||
import android.media.MediaRecorder;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class SDLAudioManager {
|
||||
protected static final String TAG = "SDLAudio";
|
||||
|
||||
protected static AudioTrack mAudioTrack;
|
||||
protected static AudioRecord mAudioRecord;
|
||||
protected static Context mContext;
|
||||
|
||||
private static final int[] NO_DEVICES = {};
|
||||
|
||||
private static AudioDeviceCallback mAudioDeviceCallback;
|
||||
|
||||
public static void initialize() {
|
||||
mAudioTrack = null;
|
||||
mAudioRecord = null;
|
||||
mAudioDeviceCallback = null;
|
||||
|
||||
if(Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */)
|
||||
{
|
||||
mAudioDeviceCallback = new AudioDeviceCallback() {
|
||||
@Override
|
||||
public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
|
||||
Arrays.stream(addedDevices).forEach(deviceInfo -> addAudioDevice(deviceInfo.isSink(), deviceInfo.getId()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
|
||||
Arrays.stream(removedDevices).forEach(deviceInfo -> removeAudioDevice(deviceInfo.isSink(), deviceInfo.getId()));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static void setContext(Context context) {
|
||||
mContext = context;
|
||||
if (context != null) {
|
||||
registerAudioDeviceCallback();
|
||||
}
|
||||
}
|
||||
|
||||
public static void release(Context context) {
|
||||
unregisterAudioDeviceCallback(context);
|
||||
}
|
||||
|
||||
// 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 deviceId) {
|
||||
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 /* Android 5.0 (LOLLIPOP) */) {
|
||||
if (desiredChannels > 2) {
|
||||
desiredChannels = 2;
|
||||
}
|
||||
}
|
||||
|
||||
/* AudioTrack has sample rate limitation of 48000 (fixed in 5.0.2) */
|
||||
if (Build.VERSION.SDK_INT < 22 /* Android 5.1 (LOLLIPOP_MR1) */) {
|
||||
if (sampleRate < 8000) {
|
||||
sampleRate = 8000;
|
||||
} else if (sampleRate > 48000) {
|
||||
sampleRate = 48000;
|
||||
}
|
||||
}
|
||||
|
||||
if (audioFormat == AudioFormat.ENCODING_PCM_FLOAT) {
|
||||
int minSDKVersion = (isCapture ? 23 /* Android 6.0 (M) */ : 21 /* Android 5.0 (LOLLIPOP) */);
|
||||
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 /* Android 6.0 (M) */) {
|
||||
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;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */ && deviceId != 0) {
|
||||
mAudioRecord.setPreferredDevice(getOutputAudioDeviceInfo(deviceId));
|
||||
}
|
||||
|
||||
mAudioRecord.startRecording();
|
||||
}
|
||||
|
||||
results[0] = mAudioRecord.getSampleRate();
|
||||
results[1] = mAudioRecord.getAudioFormat();
|
||||
results[2] = mAudioRecord.getChannelCount();
|
||||
|
||||
} 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;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */ && deviceId != 0) {
|
||||
mAudioTrack.setPreferredDevice(getInputAudioDeviceInfo(deviceId));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
private static AudioDeviceInfo getInputAudioDeviceInfo(int deviceId) {
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
|
||||
return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS))
|
||||
.filter(deviceInfo -> deviceInfo.getId() == deviceId)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static AudioDeviceInfo getOutputAudioDeviceInfo(int deviceId) {
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
|
||||
return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS))
|
||||
.filter(deviceInfo -> deviceInfo.getId() == deviceId)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void registerAudioDeviceCallback() {
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
|
||||
audioManager.registerAudioDeviceCallback(mAudioDeviceCallback, null);
|
||||
}
|
||||
}
|
||||
|
||||
private static void unregisterAudioDeviceCallback(Context context) {
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
||||
audioManager.unregisterAudioDeviceCallback(mAudioDeviceCallback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static int[] getAudioOutputDevices() {
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
|
||||
return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)).mapToInt(AudioDeviceInfo::getId).toArray();
|
||||
} else {
|
||||
return NO_DEVICES;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static int[] getAudioInputDevices() {
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
|
||||
return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).mapToInt(AudioDeviceInfo::getId).toArray();
|
||||
} else {
|
||||
return NO_DEVICES;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static int[] audioOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames, int deviceId) {
|
||||
return open(false, sampleRate, audioFormat, desiredChannels, desiredFrames, deviceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT < 21 /* Android 5.0 (LOLLIPOP) */) {
|
||||
Log.e(TAG, "Attempted to make an incompatible audio call with uninitialized audio! (floating-point output is supported since Android 5.0 Lollipop)");
|
||||
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, int deviceId) {
|
||||
return open(true, sampleRate, audioFormat, desiredChannels, desiredFrames, deviceId);
|
||||
}
|
||||
|
||||
/** This method is called by SDL using JNI. */
|
||||
public static int captureReadFloatBuffer(float[] buffer, boolean blocking) {
|
||||
if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {
|
||||
return 0;
|
||||
} 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 captureReadShortBuffer(short[] buffer, boolean blocking) {
|
||||
if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {
|
||||
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 /* Android 6.0 (M) */) {
|
||||
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();
|
||||
|
||||
public static native void removeAudioDevice(boolean isCapture, int deviceId);
|
||||
|
||||
public static native void addAudioDevice(boolean isCapture, int deviceId);
|
||||
|
||||
}
|
||||
@@ -1,856 +0,0 @@
|
||||
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.Build;
|
||||
import android.os.VibrationEffect;
|
||||
import android.os.Vibrator;
|
||||
import android.util.Log;
|
||||
import android.view.InputDevice;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
|
||||
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 axis_mask, 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 /* Android 4.4 (KITKAT) */) {
|
||||
mJoystickHandler = new SDLJoystickHandler_API19();
|
||||
} else {
|
||||
mJoystickHandler = new SDLJoystickHandler_API16();
|
||||
}
|
||||
}
|
||||
|
||||
if (mHapticHandler == null) {
|
||||
if (Build.VERSION.SDK_INT >= 26 /* Android 8.0 (O) */) {
|
||||
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;
|
||||
}
|
||||
|
||||
// Make sure the AXIS_Z is sorted between AXIS_RY and AXIS_RZ.
|
||||
// This is because the usual pairing are:
|
||||
// - AXIS_X + AXIS_Y (left stick).
|
||||
// - AXIS_RX, AXIS_RY (sometimes the right stick, sometimes triggers).
|
||||
// - AXIS_Z, AXIS_RZ (sometimes the right stick, sometimes triggers).
|
||||
// This sorts the axes in the above order, which tends to be correct
|
||||
// for Xbox-ish game pads that have the right stick on RX/RY and the
|
||||
// triggers on Z/RZ.
|
||||
//
|
||||
// Gamepads that don't have AXIS_Z/AXIS_RZ but use
|
||||
// AXIS_LTRIGGER/AXIS_RTRIGGER are unaffected by this.
|
||||
//
|
||||
// References:
|
||||
// - https://developer.android.com/develop/ui/views/touch-and-input/game-controllers/controller-input
|
||||
// - https://www.kernel.org/doc/html/latest/input/gamepad.html
|
||||
if (arg0Axis == MotionEvent.AXIS_Z) {
|
||||
arg0Axis = MotionEvent.AXIS_RZ - 1;
|
||||
} else if (arg0Axis > MotionEvent.AXIS_Z && arg0Axis < MotionEvent.AXIS_RZ) {
|
||||
--arg0Axis;
|
||||
}
|
||||
if (arg1Axis == MotionEvent.AXIS_Z) {
|
||||
arg1Axis = MotionEvent.AXIS_RZ - 1;
|
||||
} else if (arg1Axis > MotionEvent.AXIS_Z && arg1Axis < MotionEvent.AXIS_RZ) {
|
||||
--arg1Axis;
|
||||
}
|
||||
|
||||
return arg0Axis - arg1Axis;
|
||||
}
|
||||
}
|
||||
|
||||
private final ArrayList<SDLJoystick> mJoysticks;
|
||||
|
||||
public SDLJoystickHandler_API16() {
|
||||
|
||||
mJoysticks = new ArrayList<SDLJoystick>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pollInputDevices() {
|
||||
int[] deviceIds = InputDevice.getDeviceIds();
|
||||
|
||||
for (int device_id : deviceIds) {
|
||||
if (SDLControllerManager.isDeviceSDLJoystick(device_id)) {
|
||||
SDLJoystick joystick = getJoystick(device_id);
|
||||
if (joystick == null) {
|
||||
InputDevice joystickDevice = InputDevice.getDevice(device_id);
|
||||
joystick = new SDLJoystick();
|
||||
joystick.device_id = device_id;
|
||||
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(), getAxisMask(joystick.axes), joystick.hats.size()/2, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Check removed devices */
|
||||
ArrayList<Integer> removedDevices = null;
|
||||
for (SDLJoystick joystick : mJoysticks) {
|
||||
int device_id = joystick.device_id;
|
||||
int i;
|
||||
for (i = 0; i < deviceIds.length; i++) {
|
||||
if (device_id == deviceIds[i]) break;
|
||||
}
|
||||
if (i == deviceIds.length) {
|
||||
if (removedDevices == null) {
|
||||
removedDevices = new ArrayList<Integer>();
|
||||
}
|
||||
removedDevices.add(device_id);
|
||||
}
|
||||
}
|
||||
|
||||
if (removedDevices != null) {
|
||||
for (int device_id : removedDevices) {
|
||||
SDLControllerManager.nativeRemoveJoystick(device_id);
|
||||
for (int i = 0; i < mJoysticks.size(); i++) {
|
||||
if (mJoysticks.get(i).device_id == device_id) {
|
||||
mJoysticks.remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected SDLJoystick getJoystick(int device_id) {
|
||||
for (SDLJoystick joystick : mJoysticks) {
|
||||
if (joystick.device_id == device_id) {
|
||||
return joystick;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleMotionEvent(MotionEvent event) {
|
||||
int actionPointerIndex = event.getActionIndex();
|
||||
int action = event.getActionMasked();
|
||||
if (action == 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() / 2; i++) {
|
||||
int hatX = Math.round(event.getAxisValue(joystick.hats.get(2 * i).getAxis(), actionPointerIndex));
|
||||
int hatY = Math.round(event.getAxisValue(joystick.hats.get(2 * i + 1).getAxis(), actionPointerIndex));
|
||||
SDLControllerManager.onNativeHat(joystick.device_id, i, hatX, hatY);
|
||||
}
|
||||
}
|
||||
}
|
||||
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 getAxisMask(List<InputDevice.MotionRange> ranges) {
|
||||
return -1;
|
||||
}
|
||||
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 getAxisMask(List<InputDevice.MotionRange> ranges) {
|
||||
// For compatibility, keep computing the axis mask like before,
|
||||
// only really distinguishing 2, 4 and 6 axes.
|
||||
int axis_mask = 0;
|
||||
if (ranges.size() >= 2) {
|
||||
// ((1 << SDL_GAMEPAD_AXIS_LEFTX) | (1 << SDL_GAMEPAD_AXIS_LEFTY))
|
||||
axis_mask |= 0x0003;
|
||||
}
|
||||
if (ranges.size() >= 4) {
|
||||
// ((1 << SDL_GAMEPAD_AXIS_RIGHTX) | (1 << SDL_GAMEPAD_AXIS_RIGHTY))
|
||||
axis_mask |= 0x000c;
|
||||
}
|
||||
if (ranges.size() >= 6) {
|
||||
// ((1 << SDL_GAMEPAD_AXIS_LEFT_TRIGGER) | (1 << SDL_GAMEPAD_AXIS_RIGHT_TRIGGER))
|
||||
axis_mask |= 0x0030;
|
||||
}
|
||||
// Also add an indicator bit for whether the sorting order has changed.
|
||||
// This serves to disable outdated gamecontrollerdb.txt mappings.
|
||||
boolean have_z = false;
|
||||
boolean have_past_z_before_rz = false;
|
||||
for (InputDevice.MotionRange range : ranges) {
|
||||
int axis = range.getAxis();
|
||||
if (axis == MotionEvent.AXIS_Z) {
|
||||
have_z = true;
|
||||
} else if (axis > MotionEvent.AXIS_Z && axis < MotionEvent.AXIS_RZ) {
|
||||
have_past_z_before_rz = true;
|
||||
}
|
||||
}
|
||||
if (have_z && have_past_z_before_rz) {
|
||||
// If both these exist, the compare() function changed sorting order.
|
||||
// Set a bit to indicate this fact.
|
||||
axis_mask |= 0x8000;
|
||||
}
|
||||
return axis_mask;
|
||||
}
|
||||
|
||||
@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_MENU,
|
||||
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 << 6), // MENU -> START
|
||||
(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 {
|
||||
|
||||
static class SDLHaptic {
|
||||
public int device_id;
|
||||
public String name;
|
||||
public Vibrator vib;
|
||||
}
|
||||
|
||||
private final 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 != null) {
|
||||
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 = null;
|
||||
for (SDLHaptic haptic : mHaptics) {
|
||||
int device_id = haptic.device_id;
|
||||
int i;
|
||||
for (i = 0; i < deviceIds.length; i++) {
|
||||
if (device_id == deviceIds[i]) break;
|
||||
}
|
||||
|
||||
if (device_id != deviceId_VIBRATOR_SERVICE || !hasVibratorService) {
|
||||
if (i == deviceIds.length) {
|
||||
if (removedDevices == null) {
|
||||
removedDevices = new ArrayList<Integer>();
|
||||
}
|
||||
removedDevices.add(device_id);
|
||||
}
|
||||
} // else: don't remove the vibrator if it is still present
|
||||
}
|
||||
|
||||
if (removedDevices != null) {
|
||||
for (int device_id : removedDevices) {
|
||||
SDLControllerManager.nativeRemoveHaptic(device_id);
|
||||
for (int i = 0; i < mHaptics.size(); i++) {
|
||||
if (mHaptics.get(i).device_id == device_id) {
|
||||
mHaptics.remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected SDLHaptic getHaptic(int device_id) {
|
||||
for (SDLHaptic haptic : mHaptics) {
|
||||
if (haptic.device_id == device_id) {
|
||||
return haptic;
|
||||
}
|
||||
}
|
||||
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:
|
||||
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:
|
||||
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 /* Android 8.1 (O_MR1) */);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean inRelativeMode() {
|
||||
return mRelativeModeEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setRelativeMouseEnabled(boolean enabled) {
|
||||
if (!SDLActivity.isDeXMode() || Build.VERSION.SDK_INT >= 27 /* Android 8.1 (O_MR1) */) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,405 +0,0 @@
|
||||
package org.libsdl.app;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.hardware.Sensor;
|
||||
import android.hardware.SensorEvent;
|
||||
import android.hardware.SensorEventListener;
|
||||
import android.hardware.SensorManager;
|
||||
import android.os.Build;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.Display;
|
||||
import android.view.InputDevice;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.Surface;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.SurfaceView;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
|
||||
|
||||
/**
|
||||
SDLSurface. This is what we draw on, so we need to know when it's created
|
||||
in order to do anything useful.
|
||||
|
||||
Because of this, that's where we set up the SDL thread
|
||||
*/
|
||||
public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
|
||||
View.OnKeyListener, View.OnTouchListener, SensorEventListener {
|
||||
|
||||
// Sensors
|
||||
protected SensorManager mSensorManager;
|
||||
protected Display mDisplay;
|
||||
|
||||
// Keep track of the surface size to normalize touch events
|
||||
protected float mWidth, mHeight;
|
||||
|
||||
// Is SurfaceView ready for rendering
|
||||
public boolean mIsSurfaceReady;
|
||||
|
||||
// Startup
|
||||
public SDLSurface(Context context) {
|
||||
super(context);
|
||||
getHolder().addCallback(this);
|
||||
|
||||
setFocusable(true);
|
||||
setFocusableInTouchMode(true);
|
||||
requestFocus();
|
||||
setOnKeyListener(this);
|
||||
setOnTouchListener(this);
|
||||
|
||||
mDisplay = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
|
||||
mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
|
||||
|
||||
setOnGenericMotionListener(SDLActivity.getMotionListener());
|
||||
|
||||
// Some arbitrary defaults to avoid a potential division by zero
|
||||
mWidth = 1.0f;
|
||||
mHeight = 1.0f;
|
||||
|
||||
mIsSurfaceReady = false;
|
||||
}
|
||||
|
||||
public void handlePause() {
|
||||
enableSensor(Sensor.TYPE_ACCELEROMETER, false);
|
||||
}
|
||||
|
||||
public void handleResume() {
|
||||
setFocusable(true);
|
||||
setFocusableInTouchMode(true);
|
||||
requestFocus();
|
||||
setOnKeyListener(this);
|
||||
setOnTouchListener(this);
|
||||
enableSensor(Sensor.TYPE_ACCELEROMETER, true);
|
||||
}
|
||||
|
||||
public Surface getNativeSurface() {
|
||||
return getHolder().getSurface();
|
||||
}
|
||||
|
||||
// Called when we have a valid drawing surface
|
||||
@Override
|
||||
public void surfaceCreated(SurfaceHolder holder) {
|
||||
Log.v("SDL", "surfaceCreated()");
|
||||
SDLActivity.onNativeSurfaceCreated();
|
||||
}
|
||||
|
||||
// Called when we lose the surface
|
||||
@Override
|
||||
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||
Log.v("SDL", "surfaceDestroyed()");
|
||||
|
||||
// Transition to pause, if needed
|
||||
SDLActivity.mNextNativeState = SDLActivity.NativeState.PAUSED;
|
||||
SDLActivity.handleNativeState();
|
||||
|
||||
mIsSurfaceReady = false;
|
||||
SDLActivity.onNativeSurfaceDestroyed();
|
||||
}
|
||||
|
||||
// Called when the surface is resized
|
||||
@Override
|
||||
public void surfaceChanged(SurfaceHolder holder,
|
||||
int format, int width, int height) {
|
||||
Log.v("SDL", "surfaceChanged()");
|
||||
|
||||
if (SDLActivity.mSingleton == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
int nDeviceWidth = width;
|
||||
int nDeviceHeight = height;
|
||||
try
|
||||
{
|
||||
if (Build.VERSION.SDK_INT >= 17 /* Android 4.2 (JELLY_BEAN_MR1) */) {
|
||||
DisplayMetrics realMetrics = new DisplayMetrics();
|
||||
mDisplay.getRealMetrics( realMetrics );
|
||||
nDeviceWidth = realMetrics.widthPixels;
|
||||
nDeviceHeight = realMetrics.heightPixels;
|
||||
}
|
||||
} catch(Exception ignored) {
|
||||
}
|
||||
|
||||
synchronized(SDLActivity.getContext()) {
|
||||
// In case we're waiting on a size change after going fullscreen, send a notification.
|
||||
SDLActivity.getContext().notifyAll();
|
||||
}
|
||||
|
||||
Log.v("SDL", "Window size: " + width + "x" + height);
|
||||
Log.v("SDL", "Device size: " + nDeviceWidth + "x" + nDeviceHeight);
|
||||
SDLActivity.nativeSetScreenResolution(width, height, nDeviceWidth, nDeviceHeight, mDisplay.getRefreshRate());
|
||||
SDLActivity.onNativeResize();
|
||||
|
||||
// Prevent a screen distortion glitch,
|
||||
// for instance when the device is in Landscape and a Portrait App is resumed.
|
||||
boolean skip = false;
|
||||
int requestedOrientation = SDLActivity.mSingleton.getRequestedOrientation();
|
||||
|
||||
if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT) {
|
||||
if (mWidth > mHeight) {
|
||||
skip = true;
|
||||
}
|
||||
} else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE) {
|
||||
if (mWidth < mHeight) {
|
||||
skip = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Special Patch for Square Resolution: Black Berry Passport
|
||||
if (skip) {
|
||||
double min = Math.min(mWidth, mHeight);
|
||||
double max = Math.max(mWidth, mHeight);
|
||||
|
||||
if (max / min < 1.20) {
|
||||
Log.v("SDL", "Don't skip on such aspect-ratio. Could be a square resolution.");
|
||||
skip = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Don't skip in MultiWindow.
|
||||
if (skip) {
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
if (SDLActivity.mSingleton.isInMultiWindowMode()) {
|
||||
Log.v("SDL", "Don't skip in Multi-Window");
|
||||
skip = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (skip) {
|
||||
Log.v("SDL", "Skip .. Surface is not ready.");
|
||||
mIsSurfaceReady = false;
|
||||
return;
|
||||
}
|
||||
|
||||
/* If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here */
|
||||
SDLActivity.onNativeSurfaceChanged();
|
||||
|
||||
/* Surface is ready */
|
||||
mIsSurfaceReady = true;
|
||||
|
||||
SDLActivity.mNextNativeState = SDLActivity.NativeState.RESUMED;
|
||||
SDLActivity.handleNativeState();
|
||||
}
|
||||
|
||||
// Key events
|
||||
@Override
|
||||
public boolean onKey(View v, int keyCode, KeyEvent event) {
|
||||
return SDLActivity.handleKeyEvent(v, keyCode, event, null);
|
||||
}
|
||||
|
||||
// Touch events
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
/* Ref: http://developer.android.com/training/gestures/multi.html */
|
||||
int touchDevId = event.getDeviceId();
|
||||
final int pointerCount = event.getPointerCount();
|
||||
int action = event.getActionMasked();
|
||||
int pointerFingerId;
|
||||
int i = -1;
|
||||
float x,y,p;
|
||||
|
||||
/*
|
||||
* Prevent id to be -1, since it's used in SDL internal for synthetic events
|
||||
* Appears when using Android emulator, eg:
|
||||
* adb shell input mouse tap 100 100
|
||||
* adb shell input touchscreen tap 100 100
|
||||
*/
|
||||
if (touchDevId < 0) {
|
||||
touchDevId -= 1;
|
||||
}
|
||||
|
||||
// 12290 = Samsung DeX mode desktop mouse
|
||||
// 12290 = 0x3002 = 0x2002 | 0x1002 = SOURCE_MOUSE | SOURCE_TOUCHSCREEN
|
||||
// 0x2 = SOURCE_CLASS_POINTER
|
||||
if (event.getSource() == InputDevice.SOURCE_MOUSE || event.getSource() == (InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_TOUCHSCREEN)) {
|
||||
int mouseButton = 1;
|
||||
try {
|
||||
Object object = event.getClass().getMethod("getButtonState").invoke(event);
|
||||
if (object != null) {
|
||||
mouseButton = (Integer) object;
|
||||
}
|
||||
} catch(Exception ignored) {
|
||||
}
|
||||
|
||||
// We need to check if we're in relative mouse mode and get the axis offset rather than the x/y values
|
||||
// if we are. We'll leverage our existing mouse motion listener
|
||||
SDLGenericMotionListener_API12 motionListener = SDLActivity.getMotionListener();
|
||||
x = motionListener.getEventX(event);
|
||||
y = motionListener.getEventY(event);
|
||||
|
||||
SDLActivity.onNativeMouse(mouseButton, action, x, y, motionListener.inRelativeMode());
|
||||
} else {
|
||||
switch(action) {
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
for (i = 0; i < pointerCount; i++) {
|
||||
pointerFingerId = event.getPointerId(i);
|
||||
x = event.getX(i) / mWidth;
|
||||
y = event.getY(i) / mHeight;
|
||||
p = event.getPressure(i);
|
||||
if (p > 1.0f) {
|
||||
// may be larger than 1.0f on some devices
|
||||
// see the documentation of getPressure(i)
|
||||
p = 1.0f;
|
||||
}
|
||||
SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
|
||||
}
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
// Primary pointer up/down, the index is always zero
|
||||
i = 0;
|
||||
/* fallthrough */
|
||||
case MotionEvent.ACTION_POINTER_UP:
|
||||
case MotionEvent.ACTION_POINTER_DOWN:
|
||||
// Non primary pointer up/down
|
||||
if (i == -1) {
|
||||
i = event.getActionIndex();
|
||||
}
|
||||
|
||||
pointerFingerId = event.getPointerId(i);
|
||||
x = event.getX(i) / mWidth;
|
||||
y = event.getY(i) / mHeight;
|
||||
p = event.getPressure(i);
|
||||
if (p > 1.0f) {
|
||||
// may be larger than 1.0f on some devices
|
||||
// see the documentation of getPressure(i)
|
||||
p = 1.0f;
|
||||
}
|
||||
SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
for (i = 0; i < pointerCount; i++) {
|
||||
pointerFingerId = event.getPointerId(i);
|
||||
x = event.getX(i) / mWidth;
|
||||
y = event.getY(i) / mHeight;
|
||||
p = event.getPressure(i);
|
||||
if (p > 1.0f) {
|
||||
// may be larger than 1.0f on some devices
|
||||
// see the documentation of getPressure(i)
|
||||
p = 1.0f;
|
||||
}
|
||||
SDLActivity.onNativeTouch(touchDevId, pointerFingerId, MotionEvent.ACTION_UP, x, y, p);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Sensor events
|
||||
public void enableSensor(int sensortype, boolean enabled) {
|
||||
// TODO: This uses getDefaultSensor - what if we have >1 accels?
|
||||
if (enabled) {
|
||||
mSensorManager.registerListener(this,
|
||||
mSensorManager.getDefaultSensor(sensortype),
|
||||
SensorManager.SENSOR_DELAY_GAME, null);
|
||||
} else {
|
||||
mSensorManager.unregisterListener(this,
|
||||
mSensorManager.getDefaultSensor(sensortype));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAccuracyChanged(Sensor sensor, int accuracy) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSensorChanged(SensorEvent event) {
|
||||
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
|
||||
|
||||
// Since we may have an orientation set, we won't receive onConfigurationChanged events.
|
||||
// We thus should check here.
|
||||
int newOrientation;
|
||||
|
||||
float x, y;
|
||||
switch (mDisplay.getRotation()) {
|
||||
case Surface.ROTATION_90:
|
||||
x = -event.values[1];
|
||||
y = event.values[0];
|
||||
newOrientation = SDLActivity.SDL_ORIENTATION_LANDSCAPE;
|
||||
break;
|
||||
case Surface.ROTATION_270:
|
||||
x = event.values[1];
|
||||
y = -event.values[0];
|
||||
newOrientation = SDLActivity.SDL_ORIENTATION_LANDSCAPE_FLIPPED;
|
||||
break;
|
||||
case Surface.ROTATION_180:
|
||||
x = -event.values[0];
|
||||
y = -event.values[1];
|
||||
newOrientation = SDLActivity.SDL_ORIENTATION_PORTRAIT_FLIPPED;
|
||||
break;
|
||||
case Surface.ROTATION_0:
|
||||
default:
|
||||
x = event.values[0];
|
||||
y = event.values[1];
|
||||
newOrientation = SDLActivity.SDL_ORIENTATION_PORTRAIT;
|
||||
break;
|
||||
}
|
||||
|
||||
if (newOrientation != SDLActivity.mCurrentOrientation) {
|
||||
SDLActivity.mCurrentOrientation = newOrientation;
|
||||
SDLActivity.onNativeOrientationChanged(newOrientation);
|
||||
}
|
||||
|
||||
SDLActivity.onNativeAccel(-x / SensorManager.GRAVITY_EARTH,
|
||||
y / SensorManager.GRAVITY_EARTH,
|
||||
event.values[2] / SensorManager.GRAVITY_EARTH);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Captured pointer events for API 26.
|
||||
public boolean onCapturedPointerEvent(MotionEvent event)
|
||||
{
|
||||
int action = event.getActionMasked();
|
||||
|
||||
float x, y;
|
||||
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:
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
x = event.getX(0);
|
||||
y = event.getY(0);
|
||||
SDLActivity.onNativeMouse(0, action, x, y, true);
|
||||
return true;
|
||||
|
||||
case MotionEvent.ACTION_BUTTON_PRESS:
|
||||
case MotionEvent.ACTION_BUTTON_RELEASE:
|
||||
|
||||
// Change our action value to what SDL's code expects.
|
||||
if (action == MotionEvent.ACTION_BUTTON_PRESS) {
|
||||
action = MotionEvent.ACTION_DOWN;
|
||||
} else { /* MotionEvent.ACTION_BUTTON_RELEASE */
|
||||
action = MotionEvent.ACTION_UP;
|
||||
}
|
||||
|
||||
x = event.getX(0);
|
||||
y = event.getY(0);
|
||||
int button = event.getButtonState();
|
||||
|
||||
SDLActivity.onNativeMouse(button, action, x, y, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,8 @@
|
||||
class JNILocalFrame {
|
||||
public:
|
||||
JNILocalFrame(int n) :
|
||||
env_((JNIEnv*)SDL_AndroidGetJNIEnv()),
|
||||
context_((jobject)SDL_AndroidGetActivity())
|
||||
env_((JNIEnv*)SDL_GetAndroidJNIEnv()),
|
||||
context_((jobject)SDL_GetAndroidActivity())
|
||||
{
|
||||
if (env_->PushLocalFrame(n) < 0) {
|
||||
ERROR("Failed to allocate JVM local references");
|
||||
|
||||
@@ -41,9 +41,4 @@ enum CustomEvent {
|
||||
DISABLE_CD_MENU,
|
||||
};
|
||||
|
||||
// resource.cpp
|
||||
struct SDL_RWops;
|
||||
SDL_RWops* open_resource(const char* name, const char* type);
|
||||
SDL_RWops* open_file(const char* name);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include "debugger/frontend.h"
|
||||
#include <algorithm>
|
||||
#include <queue>
|
||||
#include <SDL.h>
|
||||
#include "sdl_compat.h"
|
||||
#include "nlohmann/json.hpp"
|
||||
#include "common.h"
|
||||
#include "encoding.h"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
#include <memory>
|
||||
#include <SDL.h>
|
||||
#include "sdl_compat.h"
|
||||
#include <emscripten.h>
|
||||
#include "mako.h"
|
||||
#include "fm/mako_ymfm.h"
|
||||
@@ -118,7 +118,7 @@ void MAKO::play_pcm(int page, int loops)
|
||||
std::vector<uint8_t> buffer = amse.load(page);
|
||||
if (!buffer.empty()) {
|
||||
// AMSE形式 (乙女戦記)
|
||||
uint32_t amse_size = SDL_SwapLE32(*reinterpret_cast<uint32_t*>(&buffer[8]));
|
||||
uint32_t amse_size = sdl::Swap32LE(*reinterpret_cast<uint32_t*>(&buffer[8]));
|
||||
int samples = (amse_size - 12) * 2;
|
||||
int total = samples + 0x24;
|
||||
|
||||
|
||||
+85
-5
@@ -8,11 +8,11 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <limits.h>
|
||||
#include <SDL.h>
|
||||
#include "sdl_compat.h"
|
||||
|
||||
#include "mako.h"
|
||||
#include "mako_midi.h"
|
||||
#include "mako_music.h"
|
||||
#include "music_decoder.h"
|
||||
#include "fm/mako_ymfm.h"
|
||||
#include "config.h"
|
||||
#include "dri.h"
|
||||
@@ -20,6 +20,86 @@
|
||||
|
||||
namespace {
|
||||
|
||||
// Plays a single BGM file (MP3, OGG, or WAV, chosen by file extension),
|
||||
// decoding on demand and mixing into the audio callback's output buffer.
|
||||
class Music {
|
||||
public:
|
||||
// loops: number of times to play; 0 means loop forever.
|
||||
Music(const std::string& path, int loops, const SDL_AudioSpec& device_spec)
|
||||
: decoder(create_music_decoder(path)), loops_(loops)
|
||||
{
|
||||
if (!decoder)
|
||||
return;
|
||||
const SDL_AudioSpec& src_spec = decoder->spec();
|
||||
|
||||
stream = SDL_NewAudioStream(src_spec.format, src_spec.channels, src_spec.freq,
|
||||
device_spec.format, device_spec.channels, device_spec.freq);
|
||||
if (!stream) {
|
||||
WARNING("SDL_NewAudioStream failed: %s", SDL_GetError());
|
||||
return;
|
||||
}
|
||||
playing = true;
|
||||
}
|
||||
|
||||
~Music()
|
||||
{
|
||||
if (stream)
|
||||
SDL_FreeAudioStream(stream);
|
||||
}
|
||||
|
||||
bool is_open() const { return stream != nullptr; }
|
||||
bool is_playing() const { return playing; }
|
||||
|
||||
// Called from the audio callback.
|
||||
void mix(Uint8* out, int len)
|
||||
{
|
||||
if (!stream || !playing)
|
||||
return;
|
||||
|
||||
while (SDL_AudioStreamAvailable(stream) < len && !input_finished)
|
||||
decode();
|
||||
|
||||
Uint8* tmp = SDL_stack_alloc(Uint8, len);
|
||||
int got = SDL_AudioStreamGet(stream, tmp, len);
|
||||
if (got > 0)
|
||||
SDL_MixAudioFormat(out, tmp, AUDIO_S16SYS, got, SDL_MIX_MAXVOLUME);
|
||||
SDL_stack_free(tmp);
|
||||
if (input_finished && SDL_AudioStreamAvailable(stream) <= 0)
|
||||
playing = false;
|
||||
}
|
||||
|
||||
private:
|
||||
// Decodes and queues one chunk, handling EOF and decoder errors.
|
||||
void decode()
|
||||
{
|
||||
constexpr int CHUNK_FRAMES = 1024;
|
||||
|
||||
DecodedChunk chunk = decoder->decode(CHUNK_FRAMES);
|
||||
|
||||
if (chunk.frames == 0) {
|
||||
if (loops_ && --loops_ == 0) {
|
||||
SDL_AudioStreamFlush(stream);
|
||||
input_finished = true;
|
||||
} else {
|
||||
decoder->seek_start();
|
||||
}
|
||||
return;
|
||||
}
|
||||
const SDL_AudioSpec& spec = decoder->spec();
|
||||
int bytes = chunk.frames * SDL_AUDIO_BITSIZE(spec.format) / 8 * spec.channels;
|
||||
if (SDL_AudioStreamPut(stream, chunk.data, bytes) < 0) {
|
||||
WARNING("SDL_AudioStreamPut failed: %s", SDL_GetError());
|
||||
input_finished = true;
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<MusicDecoder> decoder;
|
||||
SDL_AudioStream* stream = nullptr;
|
||||
int loops_; // number of times to play, 0 for infinite loop
|
||||
bool playing = false;
|
||||
bool input_finished = false;
|
||||
};
|
||||
|
||||
const int SAMPLE_RATE = 44100;
|
||||
|
||||
#ifdef _WIN32
|
||||
@@ -36,7 +116,7 @@ const int8_t DPSALL_tracks[] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
SDL_AudioDeviceID g_device;
|
||||
SDL_AudioSpec g_device_spec;
|
||||
|
||||
std::unique_ptr<MakoMusic> music;
|
||||
std::unique_ptr<Music> music;
|
||||
std::unique_ptr<MakoYmfm> fm;
|
||||
std::unique_ptr<MAKOMidi> midi;
|
||||
|
||||
@@ -182,7 +262,7 @@ void MAKO::play_music(int page)
|
||||
return;
|
||||
file = abspath;
|
||||
#endif
|
||||
auto m = std::make_unique<MakoMusic>(file, next_loop, g_device_spec);
|
||||
auto m = std::make_unique<Music>(file, next_loop, g_device_spec);
|
||||
if (!m->is_open())
|
||||
return;
|
||||
SDL_LockAudioDevice(g_device);
|
||||
@@ -208,7 +288,7 @@ void MAKO::stop_music()
|
||||
{
|
||||
if (music || fm) {
|
||||
SDL_LockAudioDevice(g_device);
|
||||
std::unique_ptr<MakoMusic> old_music = std::move(music);
|
||||
std::unique_ptr<Music> old_music = std::move(music);
|
||||
std::unique_ptr<MakoYmfm> old_fm = std::move(fm);
|
||||
SDL_UnlockAudioDevice(g_device);
|
||||
// old_music/old_fm are destroyed here, outside the lock.
|
||||
|
||||
@@ -0,0 +1,462 @@
|
||||
/*
|
||||
ALICE SOFT SYSTEM 3 for Win32
|
||||
|
||||
[ MAKO ]
|
||||
*/
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <limits.h>
|
||||
#include "sdl_compat.h"
|
||||
|
||||
#include "mako.h"
|
||||
#include "mako_midi.h"
|
||||
#include "music_decoder.h"
|
||||
#include "fm/mako_ymfm.h"
|
||||
#include "config.h"
|
||||
#include "dri.h"
|
||||
#include "game_id.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// Plays a single BGM file (MP3, OGG, or WAV, chosen by file extension),
|
||||
// decoding on demand into an audio stream bound to the playback device.
|
||||
class Music {
|
||||
public:
|
||||
// loops: number of times to play; 0 means loop forever.
|
||||
Music(SDL_AudioDeviceID device, const std::string& path, int loops)
|
||||
: decoder(create_music_decoder(path)), loops_(loops)
|
||||
{
|
||||
if (!decoder)
|
||||
return;
|
||||
const SDL_AudioSpec& src_spec = decoder->spec();
|
||||
SDL_AudioSpec device_spec;
|
||||
SDL_GetAudioDeviceFormat(device, &device_spec, nullptr);
|
||||
stream = SDL_CreateAudioStream(&src_spec, &device_spec);
|
||||
if (!stream) {
|
||||
WARNING("SDL_CreateAudioStream failed: %s", SDL_GetError());
|
||||
return;
|
||||
}
|
||||
playing = true;
|
||||
SDL_SetAudioStreamGetCallback(stream, [](void* self, SDL_AudioStream*, int additional_amount, int) {
|
||||
static_cast<Music*>(self)->audio_callback(additional_amount);
|
||||
}, this);
|
||||
SDL_BindAudioStream(device, stream);
|
||||
}
|
||||
|
||||
~Music()
|
||||
{
|
||||
if (stream)
|
||||
SDL_DestroyAudioStream(stream);
|
||||
}
|
||||
|
||||
bool is_open() const { return stream != nullptr; }
|
||||
|
||||
bool is_playing() const
|
||||
{
|
||||
SDL_LockAudioStream(stream);
|
||||
bool result = playing;
|
||||
SDL_UnlockAudioStream(stream);
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
void audio_callback(int additional_amount)
|
||||
{
|
||||
if (!playing)
|
||||
return;
|
||||
|
||||
while (SDL_GetAudioStreamAvailable(stream) < additional_amount && !input_finished)
|
||||
decode();
|
||||
if (input_finished && SDL_GetAudioStreamAvailable(stream) <= 0)
|
||||
playing = false;
|
||||
}
|
||||
|
||||
// Decodes and queues one chunk, handling EOF and decoder errors.
|
||||
void decode()
|
||||
{
|
||||
constexpr int CHUNK_FRAMES = 1024;
|
||||
|
||||
DecodedChunk chunk = decoder->decode(CHUNK_FRAMES);
|
||||
|
||||
if (chunk.frames == 0) {
|
||||
if (loops_ && --loops_ == 0) {
|
||||
SDL_FlushAudioStream(stream);
|
||||
input_finished = true;
|
||||
} else {
|
||||
decoder->seek_start();
|
||||
}
|
||||
return;
|
||||
}
|
||||
const SDL_AudioSpec& spec = decoder->spec();
|
||||
int bytes = chunk.frames * SDL_AUDIO_BITSIZE(spec.format) / 8 * spec.channels;
|
||||
if (!SDL_PutAudioStreamData(stream, chunk.data, bytes)) {
|
||||
WARNING("SDL_PutAudioStreamData failed: %s", SDL_GetError());
|
||||
input_finished = true;
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<MusicDecoder> decoder;
|
||||
SDL_AudioStream* stream = nullptr;
|
||||
int loops_; // number of times to play, 0 for infinite loop
|
||||
bool playing = false;
|
||||
bool input_finished = false;
|
||||
};
|
||||
|
||||
#ifdef _WIN32
|
||||
// Per-game mapping from music numbers to CD tracks. This is necessary to
|
||||
// forcibly change the sound device with a menu command.
|
||||
//
|
||||
// When the game uses "Z 100+x,y" command, the xth element of the array should
|
||||
// be y. The array must be terminated with -1.
|
||||
const int8_t RANCE41_tracks[] = {2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,1,-1};
|
||||
const int8_t RANCE42_tracks[] = {2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,1,-1};
|
||||
const int8_t DPSALL_tracks[] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,5,1,2,3,-1};
|
||||
#endif
|
||||
|
||||
class FmStream {
|
||||
public:
|
||||
FmStream(SDL_AudioDeviceID device, std::vector<uint8_t> data)
|
||||
: FmStream(device, std::move(data), device_format(device)) {}
|
||||
~FmStream() { SDL_DestroyAudioStream(stream); } // unbinds and stops fill()
|
||||
|
||||
// FM music keeps playing until every channel has looped.
|
||||
bool is_playing() { int mark, loop; get_mark(&mark, &loop); return !loop; }
|
||||
|
||||
void get_mark(int* mark, int* loop) {
|
||||
SDL_LockAudioStream(stream);
|
||||
ymfm.get_mark(mark, loop);
|
||||
SDL_UnlockAudioStream(stream);
|
||||
}
|
||||
|
||||
private:
|
||||
FmStream(SDL_AudioDeviceID device, std::vector<uint8_t> data, const SDL_AudioSpec& device_spec)
|
||||
: ymfm(device_spec.freq, std::move(data))
|
||||
{
|
||||
SDL_AudioSpec src_spec = { SDL_AUDIO_S16, 2, device_spec.freq };
|
||||
stream = SDL_CreateAudioStream(&src_spec, &device_spec);
|
||||
SDL_SetAudioStreamGetCallback(stream, [](void* self, SDL_AudioStream*, int additional_amount, int) {
|
||||
static_cast<FmStream*>(self)->fill(additional_amount);
|
||||
}, this);
|
||||
SDL_BindAudioStream(device, stream);
|
||||
}
|
||||
|
||||
static SDL_AudioSpec device_format(SDL_AudioDeviceID device) {
|
||||
SDL_AudioSpec spec;
|
||||
SDL_GetAudioDeviceFormat(device, &spec, nullptr);
|
||||
return spec;
|
||||
}
|
||||
|
||||
void fill(int additional_amount) {
|
||||
const int CHUNK = 4096; // bytes; 1024 stereo S16 frames
|
||||
int16_t buffer[CHUNK / 2];
|
||||
while (additional_amount > 0) {
|
||||
int len = additional_amount < CHUNK ? additional_amount : CHUNK;
|
||||
ymfm.Process(buffer, len / 4);
|
||||
SDL_PutAudioStreamData(stream, buffer, len);
|
||||
additional_amount -= len;
|
||||
}
|
||||
}
|
||||
|
||||
MakoYmfm ymfm;
|
||||
SDL_AudioStream* stream;
|
||||
};
|
||||
|
||||
// The audio device. Each sound source (music, fm, pcm) creates its own
|
||||
// SDL_AudioStream and binds it to this device.
|
||||
SDL_AudioDeviceID g_device;
|
||||
|
||||
std::unique_ptr<Music> music;
|
||||
std::unique_ptr<FmStream> fm;
|
||||
std::unique_ptr<MAKOMidi> midi;
|
||||
|
||||
// PCM playback. pcm_loops is the number of remaining plays, or -1 for an
|
||||
// infinite loop. pcm_stream's get-callback re-feeds pcm_src.
|
||||
SDL_AudioStream* pcm_stream;
|
||||
std::vector<uint8_t> pcm_src;
|
||||
int pcm_loops;
|
||||
bool pcm_input_finished;
|
||||
|
||||
void SDLCALL pcm_audio_callback(void*, SDL_AudioStream* stream, int additional_amount, int /*total_amount*/)
|
||||
{
|
||||
while (SDL_GetAudioStreamAvailable(stream) < additional_amount && !pcm_input_finished) {
|
||||
if (pcm_loops == 0) {
|
||||
SDL_FlushAudioStream(stream);
|
||||
pcm_input_finished = true;
|
||||
break;
|
||||
}
|
||||
SDL_PutAudioStreamData(stream, pcm_src.data(), static_cast<int>(pcm_src.size()));
|
||||
if (pcm_loops > 0)
|
||||
pcm_loops--;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
MAKO::MAKO(const Config& config, const GameId& game_id) :
|
||||
use_fm(config.use_fm),
|
||||
current_music(0),
|
||||
next_loop(0),
|
||||
game_id(game_id)
|
||||
{
|
||||
if (!config.playlist.empty())
|
||||
load_playlist(config.playlist.c_str());
|
||||
|
||||
#ifdef _WIN32
|
||||
if (!is_cd_available()) {
|
||||
SDL_Event event = {};
|
||||
event.user.type = sdl_custom_event_type;
|
||||
event.user.code = DISABLE_CD_MENU;
|
||||
SDL_PushEvent(&event);
|
||||
}
|
||||
#endif
|
||||
|
||||
amus.open("AMUS.DAT");
|
||||
awav.open("AWAV.DAT");
|
||||
amse.open("AMSE.DAT");
|
||||
mda.open("AMUS.MDA");
|
||||
for (int i = 1; i <= 99; i++)
|
||||
cd_track[i] = 0;
|
||||
|
||||
SDL_InitSubSystem(SDL_INIT_AUDIO);
|
||||
g_device = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, nullptr);
|
||||
if (!g_device)
|
||||
WARNING("Cannot open audio device: %s", SDL_GetError());
|
||||
|
||||
midi = std::make_unique<MAKOMidi>(config.midi_device);
|
||||
if (!midi->is_available())
|
||||
use_fm = true;
|
||||
}
|
||||
|
||||
MAKO::~MAKO()
|
||||
{
|
||||
stop_music();
|
||||
stop_pcm();
|
||||
midi.reset();
|
||||
|
||||
if (g_device)
|
||||
SDL_CloseAudioDevice(g_device);
|
||||
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
||||
}
|
||||
|
||||
bool MAKO::load_playlist(const char* path)
|
||||
{
|
||||
FILE* fp = fopen(path, "r");
|
||||
if (!fp) {
|
||||
WARNING("Cannot open %s", path);
|
||||
return false;
|
||||
}
|
||||
char buf[256];
|
||||
while (fgets(buf, sizeof(buf) - 1, fp)) {
|
||||
for (char *p = buf; *p; p++) {
|
||||
if (*p == '\\')
|
||||
*p = '/';
|
||||
else if (*p == '\r' || *p == '\n')
|
||||
*p = '\0';
|
||||
}
|
||||
playlist.push_back(buf[0] ? strdup(buf) : NULL);
|
||||
}
|
||||
fclose(fp);
|
||||
return true;
|
||||
}
|
||||
|
||||
void MAKO::play_music(int page)
|
||||
{
|
||||
if (current_music == page)
|
||||
return;
|
||||
|
||||
stop_music();
|
||||
|
||||
size_t track = page < 100 ? cd_track[page] : 0;
|
||||
if (track && is_cd_available()) {
|
||||
if (track >= playlist.size() || !playlist[track])
|
||||
return;
|
||||
const char* file = playlist[track];
|
||||
#ifdef __ANDROID__
|
||||
// dr_mp3/stb_vorbis open the file via fopen, which requires an absolute
|
||||
// path on Android.
|
||||
char abspath[PATH_MAX];
|
||||
if (!realpath(file, abspath))
|
||||
return;
|
||||
file = abspath;
|
||||
#endif
|
||||
music = std::make_unique<Music>(g_device, file, next_loop);
|
||||
if (!music->is_open()) {
|
||||
music.reset();
|
||||
return;
|
||||
}
|
||||
} else if (use_fm) {
|
||||
std::vector<uint8_t> data = amus.load(page);
|
||||
if (data.empty())
|
||||
return;
|
||||
fm = std::make_unique<FmStream>(g_device, std::move(data));
|
||||
} else if (midi->is_available()) {
|
||||
if (!midi->play(game_id, amus, mda, page, next_loop))
|
||||
return;
|
||||
}
|
||||
current_music = page;
|
||||
next_loop = 0;
|
||||
}
|
||||
|
||||
void MAKO::stop_music()
|
||||
{
|
||||
music.reset();
|
||||
fm.reset();
|
||||
if (midi->is_available())
|
||||
midi->stop();
|
||||
current_music = 0;
|
||||
}
|
||||
|
||||
bool MAKO::check_music()
|
||||
{
|
||||
if (fm)
|
||||
return fm->is_playing();
|
||||
if (music)
|
||||
return music->is_playing();
|
||||
return midi->is_playing();
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
void MAKO::select_sound(BGMDevice dev)
|
||||
{
|
||||
int page = current_music;
|
||||
int old_dev = (1 <= page && page <= 99 && cd_track[page]) ? BGM_CD :
|
||||
use_fm ? BGM_FM : BGM_MIDI;
|
||||
|
||||
switch (dev) {
|
||||
case BGM_FM:
|
||||
case BGM_MIDI:
|
||||
for (int i = 1; i <= 99; i++)
|
||||
cd_track[i] = 0;
|
||||
if (midi->is_available())
|
||||
use_fm = dev == BGM_FM;
|
||||
else
|
||||
dev = BGM_FM;
|
||||
break;
|
||||
|
||||
case BGM_CD:
|
||||
const int8_t* tracks;
|
||||
|
||||
switch (game_id.game) {
|
||||
case GameId::RANCE41:
|
||||
tracks = RANCE41_tracks;
|
||||
break;
|
||||
case GameId::RANCE42:
|
||||
tracks = RANCE42_tracks;
|
||||
break;
|
||||
case GameId::DPS_ALL:
|
||||
tracks = DPSALL_tracks;
|
||||
break;
|
||||
|
||||
// For the following games, the default mapping (cd_track[i] = i) works.
|
||||
case GameId::AYUMI_CD:
|
||||
case GameId::FUNNYBEE_CD:
|
||||
case GameId::ONLYYOU:
|
||||
default:
|
||||
tracks = nullptr;
|
||||
}
|
||||
|
||||
if (tracks) {
|
||||
for (int i = 0; tracks[i] >= 0; i++)
|
||||
cd_track[i + 1] = tracks[i];
|
||||
} else {
|
||||
for (int i = 1; i <= 99; i++)
|
||||
cd_track[i] = i;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (dev != old_dev && page) {
|
||||
stop_music();
|
||||
play_music(page);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void MAKO::get_mark(int* mark, int* loop)
|
||||
{
|
||||
if (fm) {
|
||||
fm->get_mark(mark, loop);
|
||||
return;
|
||||
}
|
||||
midi->get_mark(mark, loop);
|
||||
}
|
||||
|
||||
void MAKO::play_pcm(int page, int loops)
|
||||
{
|
||||
stop_pcm();
|
||||
|
||||
SDL_AudioSpec device_spec;
|
||||
SDL_GetAudioDeviceFormat(g_device, &device_spec, nullptr);
|
||||
|
||||
SDL_AudioStream* stream = nullptr;
|
||||
std::vector<uint8_t> src;
|
||||
|
||||
// WAV形式 (Only You)
|
||||
std::vector<uint8_t> data = awav.load(page);
|
||||
if (!data.empty()) {
|
||||
SDL_AudioSpec spec;
|
||||
Uint8* wav;
|
||||
Uint32 wavlen;
|
||||
if (!SDL_LoadWAV_IO(SDL_IOFromConstMem(data.data(), static_cast<int>(data.size())), 1, &spec, &wav, &wavlen)) {
|
||||
WARNING("SDL_LoadWAV_IO failed: %s", SDL_GetError());
|
||||
return;
|
||||
}
|
||||
src.assign(wav, wav + wavlen);
|
||||
SDL_free(wav);
|
||||
stream = SDL_CreateAudioStream(&spec, &device_spec);
|
||||
} else {
|
||||
// AMSE形式 (乙女戦記)
|
||||
data = amse.load(page);
|
||||
if (data.empty())
|
||||
return;
|
||||
uint32_t amse_size = SDL_Swap32LE(*reinterpret_cast<uint32_t*>(&data[8]));
|
||||
// 4-bit PCM -> 8-bit PCM, mono, 8000Hz
|
||||
for (uint32_t i = 12; i < amse_size; i++) {
|
||||
src.push_back(data[i] & 0xf0);
|
||||
src.push_back((data[i] & 0x0f) << 4);
|
||||
}
|
||||
SDL_AudioSpec src_spec = { SDL_AUDIO_U8, 1, 8000 };
|
||||
stream = SDL_CreateAudioStream(&src_spec, &device_spec);
|
||||
}
|
||||
if (!stream) {
|
||||
WARNING("SDL_CreateAudioStream failed: %s", SDL_GetError());
|
||||
return;
|
||||
}
|
||||
|
||||
pcm_src = std::move(src);
|
||||
pcm_loops = loops ? loops : -1;
|
||||
pcm_input_finished = false;
|
||||
pcm_stream = stream;
|
||||
SDL_SetAudioStreamGetCallback(pcm_stream, pcm_audio_callback, nullptr);
|
||||
SDL_BindAudioStream(g_device, pcm_stream);
|
||||
}
|
||||
|
||||
void MAKO::stop_pcm()
|
||||
{
|
||||
// Destroy the stream first (unbinds and stops its get-callback), then it is
|
||||
// safe to drop the source buffer the callback was reading.
|
||||
if (pcm_stream) {
|
||||
SDL_DestroyAudioStream(pcm_stream);
|
||||
pcm_stream = nullptr;
|
||||
}
|
||||
pcm_src.clear();
|
||||
pcm_input_finished = false;
|
||||
}
|
||||
|
||||
bool MAKO::check_pcm()
|
||||
{
|
||||
if (!pcm_stream)
|
||||
return false;
|
||||
SDL_LockAudioStream(pcm_stream);
|
||||
bool playing = pcm_stream &&
|
||||
(!pcm_input_finished || SDL_GetAudioStreamAvailable(pcm_stream) > 0);
|
||||
SDL_UnlockAudioStream(pcm_stream);
|
||||
return playing;
|
||||
}
|
||||
|
||||
bool MAKO::is_cd_available() const
|
||||
{
|
||||
return !playlist.empty();
|
||||
}
|
||||
+13
-11
@@ -1,12 +1,14 @@
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#undef ERROR
|
||||
#endif
|
||||
#include <SDL.h>
|
||||
#include "common.h"
|
||||
#include "sdl_compat.h"
|
||||
|
||||
SDL_RWops* open_resource(const char* name, const char* type) {
|
||||
sdl::IOStream* open_resource(const char* name, const char* type) {
|
||||
#ifdef _WIN32
|
||||
// On Windows, read from resource.
|
||||
HINSTANCE hInst = GetModuleHandle(NULL);
|
||||
@@ -16,25 +18,25 @@ SDL_RWops* open_resource(const char* name, const char* type) {
|
||||
WARNING("Cannot load resource %s (type: %s)", name, type);
|
||||
return NULL;
|
||||
}
|
||||
return SDL_RWFromConstMem(LockResource(hGlobal), SizeofResource(hInst, hRes));
|
||||
return sdl::IOFromConstMem(LockResource(hGlobal), SizeofResource(hInst, hRes));
|
||||
#else
|
||||
// On Android, read from APK assets.
|
||||
// On other platforms, read from a file under RESOURCE_PATH.
|
||||
char path[PATH_MAX];
|
||||
snprintf(path, PATH_MAX, "%s%s/%s", RESOURCE_PATH, type, name);
|
||||
return SDL_RWFromFile(path, "rb");
|
||||
return sdl::IOFromFile(path, "rb");
|
||||
#endif
|
||||
}
|
||||
|
||||
SDL_RWops* open_file(const char* name) {
|
||||
sdl::IOStream* open_file(const char* name) {
|
||||
#ifdef __ANDROID__
|
||||
// We cannot use SDL_RWFromFile() because it does not resolve relative
|
||||
// paths using the current directory on Android.
|
||||
FILE *fp = fopen(name, "rb");
|
||||
if (!fp)
|
||||
// SDL_IOFromFile() treats relative paths as APK assets on Android, so use
|
||||
// an absolute path to open files relative to the current game directory.
|
||||
char path[PATH_MAX];
|
||||
if (!realpath(name, path))
|
||||
return NULL;
|
||||
return SDL_RWFromFP(fp, SDL_TRUE);
|
||||
return sdl::IOFromFile(path, "rb");
|
||||
#else
|
||||
return SDL_RWFromFile(name, "rb");
|
||||
return sdl::IOFromFile(name, "rb");
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -0,0 +1,518 @@
|
||||
#ifndef SYSTEM3_SDL_COMPAT_H_
|
||||
#define SYSTEM3_SDL_COMPAT_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// SDL's public headers have different include paths in SDL2 and SDL3. Keep
|
||||
// that choice in one place so the rest of the engine does not depend on the
|
||||
// installed major version's directory layout.
|
||||
#ifndef SYSTEM3_SDL_VERSION
|
||||
#define SYSTEM3_SDL_VERSION 2
|
||||
#endif
|
||||
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
#include <SDL.h>
|
||||
#elif SYSTEM3_SDL_VERSION == 3
|
||||
#include <SDL3/SDL.h>
|
||||
#else
|
||||
#error "Unsupported SYSTEM3_SDL_VERSION"
|
||||
#endif
|
||||
|
||||
static_assert(SDL_MAJOR_VERSION == SYSTEM3_SDL_VERSION,
|
||||
"The selected SDL headers do not match SYSTEM3_SDL_VERSION");
|
||||
|
||||
// windows.h defines CreateWindow as a macro.
|
||||
#ifdef CreateWindow
|
||||
#undef CreateWindow
|
||||
#endif
|
||||
|
||||
namespace sdl {
|
||||
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
using Gamepad = SDL_GameController;
|
||||
using PixelFormat = SDL_PixelFormatEnum;
|
||||
using Mutex = SDL_mutex;
|
||||
using AtomicInt = SDL_atomic_t;
|
||||
using IOStream = SDL_RWops;
|
||||
using GamepadButton = SDL_GameControllerButton;
|
||||
using GamepadAxis = SDL_GameControllerAxis;
|
||||
|
||||
inline constexpr Uint32 INIT_GAMEPAD = SDL_INIT_GAMECONTROLLER;
|
||||
inline constexpr Uint32 EVENT_QUIT = SDL_QUIT;
|
||||
inline constexpr Uint32 EVENT_KEY_UP = SDL_KEYUP;
|
||||
inline constexpr Uint32 EVENT_MOUSE_MOTION = SDL_MOUSEMOTION;
|
||||
inline constexpr Uint32 EVENT_MOUSE_WHEEL = SDL_MOUSEWHEEL;
|
||||
inline constexpr Uint32 EVENT_FINGER_DOWN = SDL_FINGERDOWN;
|
||||
inline constexpr Uint32 EVENT_FINGER_UP = SDL_FINGERUP;
|
||||
inline constexpr Uint32 EVENT_FINGER_MOTION = SDL_FINGERMOTION;
|
||||
inline constexpr SDL_AudioFormat AudioS16 = AUDIO_S16SYS;
|
||||
inline constexpr GamepadButton GAMEPAD_BUTTON_DPAD_UP = SDL_CONTROLLER_BUTTON_DPAD_UP;
|
||||
inline constexpr GamepadButton GAMEPAD_BUTTON_DPAD_DOWN = SDL_CONTROLLER_BUTTON_DPAD_DOWN;
|
||||
inline constexpr GamepadButton GAMEPAD_BUTTON_DPAD_LEFT = SDL_CONTROLLER_BUTTON_DPAD_LEFT;
|
||||
inline constexpr GamepadButton GAMEPAD_BUTTON_DPAD_RIGHT = SDL_CONTROLLER_BUTTON_DPAD_RIGHT;
|
||||
inline constexpr GamepadButton GAMEPAD_BUTTON_SOUTH = SDL_CONTROLLER_BUTTON_A;
|
||||
inline constexpr GamepadButton GAMEPAD_BUTTON_EAST = SDL_CONTROLLER_BUTTON_B;
|
||||
inline constexpr GamepadButton GAMEPAD_BUTTON_WEST = SDL_CONTROLLER_BUTTON_X;
|
||||
inline constexpr GamepadButton GAMEPAD_BUTTON_NORTH = SDL_CONTROLLER_BUTTON_Y;
|
||||
inline constexpr GamepadAxis GAMEPAD_AXIS_LEFTX = SDL_CONTROLLER_AXIS_LEFTX;
|
||||
inline constexpr GamepadAxis GAMEPAD_AXIS_LEFTY = SDL_CONTROLLER_AXIS_LEFTY;
|
||||
#else
|
||||
using Gamepad = SDL_Gamepad;
|
||||
using PixelFormat = SDL_PixelFormat;
|
||||
using Mutex = SDL_Mutex;
|
||||
using AtomicInt = SDL_AtomicInt;
|
||||
using IOStream = SDL_IOStream;
|
||||
using GamepadButton = SDL_GamepadButton;
|
||||
using GamepadAxis = SDL_GamepadAxis;
|
||||
|
||||
inline constexpr Uint32 INIT_GAMEPAD = SDL_INIT_GAMEPAD;
|
||||
inline constexpr Uint32 EVENT_QUIT = SDL_EVENT_QUIT;
|
||||
inline constexpr Uint32 EVENT_KEY_UP = SDL_EVENT_KEY_UP;
|
||||
inline constexpr Uint32 EVENT_MOUSE_MOTION = SDL_EVENT_MOUSE_MOTION;
|
||||
inline constexpr Uint32 EVENT_MOUSE_WHEEL = SDL_EVENT_MOUSE_WHEEL;
|
||||
inline constexpr Uint32 EVENT_FINGER_DOWN = SDL_EVENT_FINGER_DOWN;
|
||||
inline constexpr Uint32 EVENT_FINGER_UP = SDL_EVENT_FINGER_UP;
|
||||
inline constexpr Uint32 EVENT_FINGER_MOTION = SDL_EVENT_FINGER_MOTION;
|
||||
inline constexpr SDL_AudioFormat AudioS16 = SDL_AUDIO_S16;
|
||||
inline constexpr GamepadButton GAMEPAD_BUTTON_DPAD_UP = SDL_GAMEPAD_BUTTON_DPAD_UP;
|
||||
inline constexpr GamepadButton GAMEPAD_BUTTON_DPAD_DOWN = SDL_GAMEPAD_BUTTON_DPAD_DOWN;
|
||||
inline constexpr GamepadButton GAMEPAD_BUTTON_DPAD_LEFT = SDL_GAMEPAD_BUTTON_DPAD_LEFT;
|
||||
inline constexpr GamepadButton GAMEPAD_BUTTON_DPAD_RIGHT = SDL_GAMEPAD_BUTTON_DPAD_RIGHT;
|
||||
inline constexpr GamepadButton GAMEPAD_BUTTON_SOUTH = SDL_GAMEPAD_BUTTON_SOUTH;
|
||||
inline constexpr GamepadButton GAMEPAD_BUTTON_EAST = SDL_GAMEPAD_BUTTON_EAST;
|
||||
inline constexpr GamepadButton GAMEPAD_BUTTON_WEST = SDL_GAMEPAD_BUTTON_WEST;
|
||||
inline constexpr GamepadButton GAMEPAD_BUTTON_NORTH = SDL_GAMEPAD_BUTTON_NORTH;
|
||||
inline constexpr GamepadAxis GAMEPAD_AXIS_LEFTX = SDL_GAMEPAD_AXIS_LEFTX;
|
||||
inline constexpr GamepadAxis GAMEPAD_AXIS_LEFTY = SDL_GAMEPAD_AXIS_LEFTY;
|
||||
#endif
|
||||
|
||||
inline IOStream* IOFromFile(const char* path, const char* mode)
|
||||
{
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
return SDL_RWFromFile(path, mode);
|
||||
#else
|
||||
return SDL_IOFromFile(path, mode);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline IOStream* IOFromConstMem(const void* data, size_t size)
|
||||
{
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
return SDL_RWFromConstMem(data, static_cast<int>(size));
|
||||
#else
|
||||
return SDL_IOFromConstMem(data, size);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline size_t ReadIO(IOStream* stream, void* destination, size_t bytes)
|
||||
{
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
return SDL_RWread(stream, destination, 1, bytes);
|
||||
#else
|
||||
return SDL_ReadIO(stream, destination, bytes);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline int64_t SeekIO(IOStream* stream, int64_t offset, int whence)
|
||||
{
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
return SDL_RWseek(stream, offset, whence);
|
||||
#else
|
||||
return SDL_SeekIO(stream, offset, static_cast<SDL_IOWhence>(whence));
|
||||
#endif
|
||||
}
|
||||
|
||||
inline bool CloseIO(IOStream* stream)
|
||||
{
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
return SDL_RWclose(stream) == 0;
|
||||
#else
|
||||
return SDL_CloseIO(stream);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline int SetAtomicInt(AtomicInt* value, int desired)
|
||||
{
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
return SDL_AtomicSet(value, desired);
|
||||
#else
|
||||
return SDL_SetAtomicInt(value, desired);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline int GetAtomicInt(AtomicInt* value)
|
||||
{
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
return SDL_AtomicGet(value);
|
||||
#else
|
||||
return SDL_GetAtomicInt(value);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline bool CompareAndSwapAtomicInt(AtomicInt* value, int expected, int desired)
|
||||
{
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
return SDL_AtomicCAS(value, expected, desired) == SDL_TRUE;
|
||||
#else
|
||||
return SDL_CompareAndSwapAtomicInt(value, expected, desired);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline bool SetCurrentThreadPriority(SDL_ThreadPriority priority)
|
||||
{
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
return SDL_SetThreadPriority(priority) == 0;
|
||||
#else
|
||||
return SDL_SetCurrentThreadPriority(priority);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline int GetNumTouchFingers(SDL_TouchID touch_id)
|
||||
{
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
return SDL_GetNumTouchFingers(touch_id);
|
||||
#else
|
||||
int count = 0;
|
||||
SDL_Finger** fingers = SDL_GetTouchFingers(touch_id, &count);
|
||||
SDL_free(fingers);
|
||||
return count;
|
||||
#endif
|
||||
}
|
||||
|
||||
inline SDL_TouchID GetTouchID(const SDL_TouchFingerEvent& event)
|
||||
{
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
return event.touchId;
|
||||
#else
|
||||
return event.touchID;
|
||||
#endif
|
||||
}
|
||||
|
||||
inline SDL_Scancode GetKeyScancode(const SDL_KeyboardEvent& event)
|
||||
{
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
return event.keysym.scancode;
|
||||
#else
|
||||
return event.scancode;
|
||||
#endif
|
||||
}
|
||||
|
||||
inline bool RenderCoordinatesFromWindow(SDL_Renderer* renderer, float window_x,
|
||||
float window_y, float* x, float* y)
|
||||
{
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
*x = window_x;
|
||||
*y = window_y;
|
||||
return true;
|
||||
#else
|
||||
return SDL_RenderCoordinatesFromWindow(renderer, window_x, window_y, x, y);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline bool GetGamepadButton(Gamepad* gamepad, GamepadButton button)
|
||||
{
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
return SDL_GameControllerGetButton(gamepad, button) != 0;
|
||||
#else
|
||||
return SDL_GetGamepadButton(gamepad, button);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline Sint16 GetGamepadAxis(Gamepad* gamepad, GamepadAxis axis)
|
||||
{
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
return SDL_GameControllerGetAxis(gamepad, axis);
|
||||
#else
|
||||
return SDL_GetGamepadAxis(gamepad, axis);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline bool ShowMessageBox(const SDL_MessageBoxData* data, int* button_id)
|
||||
{
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
return SDL_ShowMessageBox(data, button_id) == 0;
|
||||
#else
|
||||
return SDL_ShowMessageBox(data, button_id);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void HideCursor()
|
||||
{
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
SDL_ShowCursor(SDL_DISABLE);
|
||||
#else
|
||||
SDL_HideCursor();
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void ShowCursor()
|
||||
{
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
SDL_ShowCursor(SDL_ENABLE);
|
||||
#else
|
||||
SDL_ShowCursor();
|
||||
#endif
|
||||
}
|
||||
|
||||
inline bool SaveBMP(SDL_Surface* surface, const char* path)
|
||||
{
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
return SDL_SaveBMP(surface, path) == 0;
|
||||
#else
|
||||
return SDL_SaveBMP(surface, path);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void FreeWAV(Uint8* data)
|
||||
{
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
SDL_FreeWAV(data);
|
||||
#else
|
||||
SDL_free(data);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline Uint32 Swap32LE(Uint32 value)
|
||||
{
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
return SDL_SwapLE32(value);
|
||||
#else
|
||||
return SDL_Swap32LE(value);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline SDL_Surface* CreateSurface(int width, int height, PixelFormat format)
|
||||
{
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
return SDL_CreateRGBSurfaceWithFormat(0, width, height,
|
||||
SDL_BITSPERPIXEL(format), format);
|
||||
#else
|
||||
SDL_Surface* surface = SDL_CreateSurface(width, height, format);
|
||||
if (surface && format == SDL_PIXELFORMAT_INDEX8)
|
||||
SDL_CreateSurfacePalette(surface);
|
||||
return surface;
|
||||
#endif
|
||||
}
|
||||
|
||||
inline SDL_Surface* CreateSurfaceWithMasks(int width, int height, int depth,
|
||||
Uint32 red, Uint32 green, Uint32 blue, Uint32 alpha)
|
||||
{
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
return SDL_CreateRGBSurface(0, width, height, depth, red, green, blue, alpha);
|
||||
#else
|
||||
return SDL_CreateSurface(width, height,
|
||||
SDL_GetPixelFormatForMasks(depth, red, green, blue, alpha));
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void DestroySurface(SDL_Surface* surface)
|
||||
{
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
SDL_FreeSurface(surface);
|
||||
#else
|
||||
SDL_DestroySurface(surface);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline SDL_Palette* GetSurfacePalette(SDL_Surface* surface)
|
||||
{
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
return surface->format->palette;
|
||||
#else
|
||||
return SDL_GetSurfacePalette(surface);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline SDL_Window* CreateWindow(const char* title, int width, int height,
|
||||
SDL_WindowFlags flags)
|
||||
{
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
return SDL_CreateWindow(title, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
|
||||
width, height, flags);
|
||||
#else
|
||||
// SDL3 uses physical pixels for window coordinates on platforms such as
|
||||
// Windows. Scale the requested content size so that it has the same
|
||||
// apparent size on a high-DPI display. On platforms whose window
|
||||
// coordinates are already logical (macOS and Wayland), the content scale is
|
||||
// 1 and HIGH_PIXEL_DENSITY provides the correspondingly larger backbuffer.
|
||||
float scale = SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay());
|
||||
if (scale <= 0.0f)
|
||||
scale = 1.0f;
|
||||
width = static_cast<int>(width * scale + 0.5f);
|
||||
height = static_cast<int>(height * scale + 0.5f);
|
||||
return SDL_CreateWindow(title, width, height,
|
||||
flags | SDL_WINDOW_HIGH_PIXEL_DENSITY);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void SetWindowContentSize(SDL_Window* window, int width, int height)
|
||||
{
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
SDL_SetWindowSize(window, width, height);
|
||||
#else
|
||||
// Convert content units to window coordinates. Dividing the window's
|
||||
// display scale by its pixel density gives the platform content scale.
|
||||
float pixel_density = SDL_GetWindowPixelDensity(window);
|
||||
float scale = SDL_GetWindowDisplayScale(window);
|
||||
if (pixel_density > 0.0f && scale > 0.0f)
|
||||
scale /= pixel_density;
|
||||
else
|
||||
scale = 1.0f;
|
||||
SDL_SetWindowSize(window,
|
||||
static_cast<int>(width * scale + 0.5f),
|
||||
static_cast<int>(height * scale + 0.5f));
|
||||
#endif
|
||||
}
|
||||
|
||||
inline SDL_Renderer* CreateRenderer(SDL_Window* window)
|
||||
{
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
return SDL_CreateRenderer(window, -1, 0);
|
||||
#else
|
||||
return SDL_CreateRenderer(window, nullptr);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline bool SetRenderLogicalPresentation(SDL_Renderer* renderer, int width, int height)
|
||||
{
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
return SDL_RenderSetLogicalSize(renderer, width, height) == 0;
|
||||
#else
|
||||
return SDL_SetRenderLogicalPresentation(renderer, width, height,
|
||||
SDL_LOGICAL_PRESENTATION_LETTERBOX);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline SDL_Palette* CreatePalette(int colors)
|
||||
{
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
return SDL_AllocPalette(colors);
|
||||
#else
|
||||
return SDL_CreatePalette(colors);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void DestroyPalette(SDL_Palette* palette)
|
||||
{
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
SDL_FreePalette(palette);
|
||||
#else
|
||||
SDL_DestroyPalette(palette);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void DestroyCursor(SDL_Cursor* cursor)
|
||||
{
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
SDL_FreeCursor(cursor);
|
||||
#else
|
||||
SDL_DestroyCursor(cursor);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline bool GetRectIntersection(const SDL_Rect* a, const SDL_Rect* b, SDL_Rect* result)
|
||||
{
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
return SDL_IntersectRect(a, b, result) == SDL_TRUE;
|
||||
#else
|
||||
return SDL_GetRectIntersection(a, b, result);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void GetRectUnion(const SDL_Rect* a, const SDL_Rect* b, SDL_Rect* result)
|
||||
{
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
SDL_UnionRect(a, b, result);
|
||||
#else
|
||||
SDL_GetRectUnion(a, b, result);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline bool RenderTexture(SDL_Renderer* renderer, SDL_Texture* texture,
|
||||
const SDL_Rect* source, const SDL_Rect* destination)
|
||||
{
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
return SDL_RenderCopy(renderer, texture, source, destination) == 0;
|
||||
#else
|
||||
SDL_FRect src;
|
||||
SDL_FRect dst;
|
||||
const SDL_FRect* src_ptr = nullptr;
|
||||
const SDL_FRect* dst_ptr = nullptr;
|
||||
if (source) {
|
||||
src = { static_cast<float>(source->x), static_cast<float>(source->y),
|
||||
static_cast<float>(source->w), static_cast<float>(source->h) };
|
||||
src_ptr = &src;
|
||||
}
|
||||
if (destination) {
|
||||
dst = { static_cast<float>(destination->x), static_cast<float>(destination->y),
|
||||
static_cast<float>(destination->w), static_cast<float>(destination->h) };
|
||||
dst_ptr = &dst;
|
||||
}
|
||||
return SDL_RenderTexture(renderer, texture, src_ptr, dst_ptr);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline bool SetSurfaceColorKey(SDL_Surface* surface, bool enabled, Uint32 key)
|
||||
{
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
return SDL_SetColorKey(surface, enabled ? SDL_TRUE : SDL_FALSE, key) == 0;
|
||||
#else
|
||||
return SDL_SetSurfaceColorKey(surface, enabled, key);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline bool SurfaceHasColorKey(SDL_Surface* surface)
|
||||
{
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
return SDL_HasColorKey(surface) == SDL_TRUE;
|
||||
#else
|
||||
return SDL_SurfaceHasColorKey(surface);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline bool GetSurfaceColorKey(SDL_Surface* surface, Uint32* key)
|
||||
{
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
return SDL_GetColorKey(surface, key) == 0;
|
||||
#else
|
||||
return SDL_GetSurfaceColorKey(surface, key);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline bool StretchSurface(SDL_Surface* source, const SDL_Rect* source_rect,
|
||||
SDL_Surface* destination, SDL_Rect* destination_rect)
|
||||
{
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
return SDL_SoftStretch(source, source_rect, destination, destination_rect) == 0;
|
||||
#else
|
||||
return SDL_StretchSurface(source, source_rect, destination, destination_rect,
|
||||
SDL_SCALEMODE_NEAREST);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline bool FillSurfaceRect(SDL_Surface* surface, const SDL_Rect* rect, Uint32 color)
|
||||
{
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
return SDL_FillRect(surface, rect, color) == 0;
|
||||
#else
|
||||
return SDL_FillSurfaceRect(surface, rect, color);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline bool FillSurfaceRects(SDL_Surface* surface, const SDL_Rect* rects,
|
||||
int count, Uint32 color)
|
||||
{
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
return SDL_FillRects(surface, rects, count, color) == 0;
|
||||
#else
|
||||
return SDL_FillSurfaceRects(surface, rects, count, color);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace sdl
|
||||
|
||||
sdl::IOStream* open_resource(const char* name, const char* type);
|
||||
sdl::IOStream* open_file(const char* name);
|
||||
|
||||
#endif // SYSTEM3_SDL_COMPAT_H_
|
||||
+12
-3
@@ -1,3 +1,6 @@
|
||||
#if SYSTEM3_SDL_VERSION == 3
|
||||
#include <SDL3/SDL_main.h>
|
||||
#endif
|
||||
#include <string>
|
||||
#include "common.h"
|
||||
#include "config.h"
|
||||
@@ -35,7 +38,9 @@ SDL_Window* create_window(const Config& config, const GameId& game_id)
|
||||
|
||||
SDL_Init(SDL_INIT_VIDEO);
|
||||
SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0");
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
|
||||
#endif
|
||||
#ifdef __EMSCRIPTEN__
|
||||
// Stop SDL from calling emscripten_sleep() in functions that are called
|
||||
// indirectly, which does not work with ASYNCIFY_IGNORE_INDIRECT=1. For
|
||||
@@ -48,7 +53,11 @@ SDL_Window* create_window(const Config& config, const GameId& game_id)
|
||||
SDL_SetHint(SDL_HINT_ORIENTATIONS, "LandscapeLeft LandscapeRight");
|
||||
Uint32 flags = SDL_WINDOW_FULLSCREEN;
|
||||
#else
|
||||
Uint32 flags = config.fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : SDL_WINDOW_RESIZABLE;
|
||||
SDL_WindowFlags flags = config.fullscreen ? SDL_WINDOW_FULLSCREEN : SDL_WINDOW_RESIZABLE;
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
if (config.fullscreen)
|
||||
flags = SDL_WINDOW_FULLSCREEN_DESKTOP;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
@@ -57,7 +66,7 @@ SDL_Window* create_window(const Config& config, const GameId& game_id)
|
||||
#else
|
||||
const char *window_title = title.c_str();
|
||||
#endif
|
||||
return SDL_CreateWindow(window_title, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 640, 400, flags);
|
||||
return sdl::CreateWindow(window_title, 640, 400, flags);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -77,7 +86,7 @@ int main(int argc, char *argv[])
|
||||
GameId game_id(config);
|
||||
|
||||
g_window = create_window(config, game_id);
|
||||
g_renderer = SDL_CreateRenderer(g_window, -1, 0);
|
||||
g_renderer = sdl::CreateRenderer(g_window);
|
||||
sdl_custom_event_type = SDL_RegisterEvents(1);
|
||||
|
||||
// system3 初期化
|
||||
|
||||
+29
-29
@@ -20,7 +20,7 @@ const uint32 SCANLINE_ALPHA = 0x38; // 0-255
|
||||
|
||||
SDL_Texture* create_scanline_texture(SDL_Renderer* renderer, int width, int height)
|
||||
{
|
||||
SDL_Surface* sf = SDL_CreateRGBSurfaceWithFormat(0, width, height, 32, SDL_PIXELFORMAT_ARGB8888);
|
||||
SDL_Surface* sf = sdl::CreateSurface(width, height, SDL_PIXELFORMAT_ARGB8888);
|
||||
for (int y = 0; y < height; y++) {
|
||||
uint32* p = reinterpret_cast<uint32*>(surface_line(sf, y));
|
||||
uint32 v = y % 2 ? (SCANLINE_ALPHA << 24) : 0;
|
||||
@@ -30,7 +30,7 @@ SDL_Texture* create_scanline_texture(SDL_Renderer* renderer, int width, int heig
|
||||
}
|
||||
SDL_Texture* tx = SDL_CreateTextureFromSurface(renderer, sf);
|
||||
SDL_SetTextureBlendMode(tx, SDL_BLENDMODE_BLEND);
|
||||
SDL_FreeSurface(sf);
|
||||
sdl::DestroySurface(sf);
|
||||
return tx;
|
||||
}
|
||||
|
||||
@@ -48,19 +48,19 @@ AGS::AGS(const Config& config, const GameId& game_id) : game_id(game_id)
|
||||
window_height = screen_height = 400;
|
||||
}
|
||||
|
||||
SDL_SetWindowSize(g_window, window_width, window_height);
|
||||
SDL_RenderSetLogicalSize(g_renderer, window_width, window_height);
|
||||
sdl::SetWindowContentSize(g_window, window_width, window_height);
|
||||
sdl::SetRenderLogicalPresentation(g_renderer, window_width, window_height);
|
||||
sdlTexture = SDL_CreateTexture(g_renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, screen_width, screen_height);
|
||||
scanline_texture = NULL;
|
||||
|
||||
// DIBSection 8bpp
|
||||
for(int i = 0; i < NR_SCREENS; i++) {
|
||||
hBmpScreen[i] = SDL_CreateRGBSurfaceWithFormat(0, 640, 480, 8, SDL_PIXELFORMAT_INDEX8);
|
||||
hBmpScreen[i] = sdl::CreateSurface(640, 480, SDL_PIXELFORMAT_INDEX8);
|
||||
vram[i] = reinterpret_cast<uint8_t(*)[640]>(hBmpScreen[i]->pixels);
|
||||
}
|
||||
|
||||
// All surfaces share the same palette.
|
||||
screen_palette = hBmpScreen[SCREEN_FRONT]->format->palette;
|
||||
screen_palette = sdl::GetSurfacePalette(hBmpScreen[SCREEN_FRONT]);
|
||||
SDL_SetSurfacePalette(hBmpScreen[SCREEN_BACK], screen_palette);
|
||||
|
||||
if (!config.censor_list.empty())
|
||||
@@ -77,17 +77,17 @@ AGS::AGS(const Config& config, const GameId& game_id) : game_id(game_id)
|
||||
if (!rw_font)
|
||||
sys_error("Cannot open default font");
|
||||
}
|
||||
hFont16 = TTF_OpenFontRW(rw_font, 0, 16);
|
||||
SDL_RWseek(rw_font, 0, SEEK_SET);
|
||||
hFont24 = TTF_OpenFontRW(rw_font, 0, 24);
|
||||
SDL_RWseek(rw_font, 0, SEEK_SET);
|
||||
hFont32 = TTF_OpenFontRW(rw_font, 0, 32);
|
||||
SDL_RWseek(rw_font, 0, SEEK_SET);
|
||||
hFont48 = TTF_OpenFontRW(rw_font, 0, 48);
|
||||
SDL_RWseek(rw_font, 0, SEEK_SET);
|
||||
hFont64 = TTF_OpenFontRW(rw_font, 0, 64);
|
||||
hFont16 = ttf::OpenFontIO(rw_font, 16);
|
||||
sdl::SeekIO(rw_font, 0, SEEK_SET);
|
||||
hFont24 = ttf::OpenFontIO(rw_font, 24);
|
||||
sdl::SeekIO(rw_font, 0, SEEK_SET);
|
||||
hFont32 = ttf::OpenFontIO(rw_font, 32);
|
||||
sdl::SeekIO(rw_font, 0, SEEK_SET);
|
||||
hFont48 = ttf::OpenFontIO(rw_font, 48);
|
||||
sdl::SeekIO(rw_font, 0, SEEK_SET);
|
||||
hFont64 = ttf::OpenFontIO(rw_font, 64);
|
||||
if (!hFont16 || !hFont24 || !hFont32 || !hFont48 || !hFont64) {
|
||||
sys_error("TTF_OpenFontRW failed: %s", TTF_GetError());
|
||||
sys_error("Cannot open font: %s", SDL_GetError());
|
||||
}
|
||||
if (config.no_antialias)
|
||||
ags_setAntialiasedStringMode(0);
|
||||
@@ -120,7 +120,7 @@ AGS::AGS(const Config& config, const GameId& game_id) : game_id(game_id)
|
||||
acg.open("ACG.DAT");
|
||||
|
||||
// パレット
|
||||
program_palette = SDL_AllocPalette(256);
|
||||
program_palette = sdl::CreatePalette(256);
|
||||
program_palette->colors[0x00] = {0x00, 0x00, 0x00, 0xff};
|
||||
program_palette->colors[0x01] = {0x00, 0x00, 0xaa, 0xff};
|
||||
program_palette->colors[0x02] = {0xaa, 0x00, 0x00, 0xff};
|
||||
@@ -199,7 +199,7 @@ AGS::~AGS()
|
||||
// カーソル開放
|
||||
for(int i = 0; i < 10; i++) {
|
||||
if(hCursor[i]) {
|
||||
SDL_FreeCursor(hCursor[i]);
|
||||
sdl::DestroyCursor(hCursor[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,13 +210,13 @@ AGS::~AGS()
|
||||
TTF_CloseFont(hFont32);
|
||||
TTF_CloseFont(hFont48);
|
||||
TTF_CloseFont(hFont64);
|
||||
SDL_RWclose(rw_font);
|
||||
sdl::CloseIO(rw_font);
|
||||
}
|
||||
|
||||
SDL_FreePalette(program_palette);
|
||||
sdl::DestroyPalette(program_palette);
|
||||
|
||||
for(int i = 0; i < NR_SCREENS; i++) {
|
||||
SDL_FreeSurface(hBmpScreen[i]);
|
||||
sdl::DestroySurface(hBmpScreen[i]);
|
||||
}
|
||||
|
||||
SDL_DestroyTexture(sdlTexture);
|
||||
@@ -264,8 +264,8 @@ void AGS::invalidate_screen(int sx, int sy, int width, int height)
|
||||
{
|
||||
SDL_Rect rect = {sx, sy, width, height};
|
||||
SDL_Rect screen_rect = {0, 0, screen_width, screen_height};
|
||||
SDL_IntersectRect(&rect, &screen_rect, &rect);
|
||||
SDL_UnionRect(&dirty_rect, &rect, &dirty_rect);
|
||||
sdl::GetRectIntersection(&rect, &screen_rect, &rect);
|
||||
sdl::GetRectUnion(&dirty_rect, &rect, &dirty_rect);
|
||||
}
|
||||
|
||||
void AGS::update_screen()
|
||||
@@ -288,7 +288,7 @@ void AGS::update_screen()
|
||||
dest.y = -scroll;
|
||||
src.h = dest.h = screen_height + scroll;
|
||||
}
|
||||
SDL_RenderCopy(g_renderer, sdlTexture, &src, &dest);
|
||||
sdl::RenderTexture(g_renderer, sdlTexture, &src, &dest);
|
||||
|
||||
if (fade_level) {
|
||||
SDL_SetRenderDrawBlendMode(g_renderer, SDL_BLENDMODE_BLEND);
|
||||
@@ -298,7 +298,7 @@ void AGS::update_screen()
|
||||
SDL_SetRenderDrawBlendMode(g_renderer, SDL_BLENDMODE_NONE);
|
||||
}
|
||||
if (scanline_texture)
|
||||
SDL_RenderCopy(g_renderer, scanline_texture, NULL, NULL);
|
||||
sdl::RenderTexture(g_renderer, scanline_texture, NULL, NULL);
|
||||
SDL_RenderPresent(g_renderer);
|
||||
}
|
||||
|
||||
@@ -314,25 +314,25 @@ void AGS::set_scanline_mode(bool enable)
|
||||
|
||||
bool AGS::save_screenshot(const char* path)
|
||||
{
|
||||
SDL_Surface* sf = SDL_CreateRGBSurface(0, screen_width, screen_height, 32, 0, 0, 0, 0);
|
||||
SDL_Surface* sf = sdl::CreateSurfaceWithMasks(screen_width, screen_height, 32, 0, 0, 0, 0);
|
||||
SDL_BlitSurface(hBmpScreen[SCREEN_FRONT], NULL, sf, NULL);
|
||||
|
||||
if (scanline_texture) {
|
||||
SDL_Renderer* renderer = SDL_CreateSoftwareRenderer(sf);
|
||||
SDL_Texture *tx = create_scanline_texture(renderer, screen_width, screen_height);
|
||||
SDL_RenderCopy(renderer, tx, NULL, NULL);
|
||||
sdl::RenderTexture(renderer, tx, NULL, NULL);
|
||||
SDL_DestroyTexture(tx);
|
||||
SDL_DestroyRenderer(renderer);
|
||||
}
|
||||
|
||||
bool ok = SDL_SaveBMP(sf, path) == 0;
|
||||
bool ok = sdl::SaveBMP(sf, path);
|
||||
if (!ok) {
|
||||
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "system3",
|
||||
SDL_GetError(), g_window);
|
||||
SDL_ClearError();
|
||||
}
|
||||
|
||||
SDL_FreeSurface(sf);
|
||||
sdl::DestroySurface(sf);
|
||||
return ok;
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -16,7 +16,7 @@
|
||||
#include "game_id.h"
|
||||
#include "cg.h"
|
||||
#include "dri.h"
|
||||
#include <SDL_ttf.h>
|
||||
#include "ttf_compat.h"
|
||||
|
||||
struct Config;
|
||||
|
||||
@@ -137,7 +137,7 @@ private:
|
||||
int fade_color = 0; // 0: black, 255: white
|
||||
|
||||
// font
|
||||
SDL_RWops* rw_font;
|
||||
sdl::IOStream* rw_font;
|
||||
TTF_Font* hFont16;
|
||||
TTF_Font* hFont24;
|
||||
TTF_Font* hFont32;
|
||||
|
||||
@@ -72,7 +72,7 @@ void AGS::load_cursor(int page, uint8_t flags)
|
||||
}
|
||||
}
|
||||
if(hCursor[i]) {
|
||||
SDL_FreeCursor(hCursor[i]);
|
||||
sdl::DestroyCursor(hCursor[i]);
|
||||
}
|
||||
// TODO: fix amask/xmask values
|
||||
hCursor[i] = SDL_CreateCursor(amask, xmask, 32, 32, 2, 2);
|
||||
@@ -90,6 +90,12 @@ void AGS::select_cursor()
|
||||
|
||||
void AGS::translate_mouse_coords(int* x, int* y)
|
||||
{
|
||||
#if SYSTEM3_SDL_VERSION == 3
|
||||
float window_x, window_y;
|
||||
SDL_RenderCoordinatesToWindow(g_renderer, *x, *y, &window_x, &window_y);
|
||||
*x = window_x;
|
||||
*y = window_y;
|
||||
#else
|
||||
// scale mouse x and y
|
||||
float scalex, scaley;
|
||||
SDL_RenderGetScale(g_renderer, &scalex, &scaley);
|
||||
@@ -113,4 +119,5 @@ void AGS::translate_mouse_coords(int* x, int* y)
|
||||
// offset x and y by window borders
|
||||
*x += border_left;
|
||||
*y += border_top;
|
||||
#endif
|
||||
}
|
||||
|
||||
+16
-16
@@ -19,14 +19,14 @@ namespace {
|
||||
const int MOSAIC_SIZE = 16;
|
||||
|
||||
void mosaic(SDL_Surface* sf) {
|
||||
SDL_Surface *tmp = SDL_CreateRGBSurfaceWithFormat(
|
||||
0, (sf->w + MOSAIC_SIZE - 1) / MOSAIC_SIZE, (sf->h + MOSAIC_SIZE - 1) / MOSAIC_SIZE, 8, SDL_PIXELFORMAT_INDEX8);
|
||||
if (sf->format->palette)
|
||||
SDL_SetSurfacePalette(tmp, sf->format->palette);
|
||||
SDL_Surface *tmp = sdl::CreateSurface(
|
||||
(sf->w + MOSAIC_SIZE - 1) / MOSAIC_SIZE, (sf->h + MOSAIC_SIZE - 1) / MOSAIC_SIZE, SDL_PIXELFORMAT_INDEX8);
|
||||
if (sdl::GetSurfacePalette(sf))
|
||||
SDL_SetSurfacePalette(tmp, sdl::GetSurfacePalette(sf));
|
||||
// NOTE: SDL_BlitScaled() does not support 8-bit surfaces.
|
||||
SDL_SoftStretch(sf, NULL, tmp, NULL);
|
||||
SDL_SoftStretch(tmp, NULL, sf, NULL);
|
||||
SDL_FreeSurface(tmp);
|
||||
sdl::StretchSurface(sf, NULL, tmp, NULL);
|
||||
sdl::StretchSurface(tmp, NULL, sf, NULL);
|
||||
sdl::DestroySurface(tmp);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -160,10 +160,10 @@ void AGS::copy_screen(ScreenId src, ScreenId dest, int sx, int sy, int ex, int e
|
||||
|
||||
SDL_Surface* src_surface = hBmpScreen[src];
|
||||
if (transparent_color >= 0)
|
||||
SDL_SetColorKey(src_surface, SDL_TRUE, transparent_color);
|
||||
sdl::SetSurfaceColorKey(src_surface, true, transparent_color);
|
||||
SDL_BlitSurface(src_surface, &srcrect, hBmpScreen[dest], &destrect);
|
||||
if (transparent_color >= 0)
|
||||
SDL_SetColorKey(src_surface, SDL_FALSE, 0);
|
||||
sdl::SetSurfaceColorKey(src_surface, false, 0);
|
||||
|
||||
if (dest == SCREEN_FRONT)
|
||||
invalidate_screen(dx, dy, width, height);
|
||||
@@ -248,7 +248,7 @@ void AGS::draw_mesh(int sx, int sy, int width, int height)
|
||||
void AGS::box_fill(ScreenId dest, int sx, int sy, int ex, int ey, uint8 color)
|
||||
{
|
||||
SDL_Rect rect = {sx, sy, ex - sx + 1, ey - sy + 1};
|
||||
SDL_FillRect(hBmpScreen[dest], &rect, color);
|
||||
sdl::FillSurfaceRect(hBmpScreen[dest], &rect, color);
|
||||
if(dest == SCREEN_FRONT) {
|
||||
invalidate_screen(sx, sy, ex - sx + 1, ey - sy + 1);
|
||||
}
|
||||
@@ -261,10 +261,10 @@ void AGS::box_line(ScreenId dest, int sx, int sy, int ex, int ey, uint8 color)
|
||||
SDL_Rect left = {sx, sy, 1, ey - sy + 1};
|
||||
SDL_Rect right = {ex, sy, 1, ey - sy + 1};
|
||||
|
||||
SDL_FillRect(hBmpScreen[dest], &top, color);
|
||||
SDL_FillRect(hBmpScreen[dest], &bottom, color);
|
||||
SDL_FillRect(hBmpScreen[dest], &left, color);
|
||||
SDL_FillRect(hBmpScreen[dest], &right, color);
|
||||
sdl::FillSurfaceRect(hBmpScreen[dest], &top, color);
|
||||
sdl::FillSurfaceRect(hBmpScreen[dest], &bottom, color);
|
||||
sdl::FillSurfaceRect(hBmpScreen[dest], &left, color);
|
||||
sdl::FillSurfaceRect(hBmpScreen[dest], &right, color);
|
||||
if(dest == SCREEN_FRONT) {
|
||||
invalidate_screen(sx, sy, ex - sx + 1, ey - sy + 1);
|
||||
}
|
||||
@@ -273,7 +273,7 @@ void AGS::box_line(ScreenId dest, int sx, int sy, int ex, int ey, uint8 color)
|
||||
void AGS::draw_window(int sx, int sy, int ex, int ey, bool frame, uint8 frame_color, uint8 back_color)
|
||||
{
|
||||
SDL_Rect rect = {sx, sy, ex - sx + 1, ey - sy + 1};
|
||||
SDL_FillRect(hBmpScreen[SCREEN_FRONT], &rect, back_color);
|
||||
sdl::FillSurfaceRect(hBmpScreen[SCREEN_FRONT], &rect, back_color);
|
||||
|
||||
if (frame) {
|
||||
SDL_Rect rects[] = {
|
||||
@@ -282,7 +282,7 @@ void AGS::draw_window(int sx, int sy, int ex, int ey, bool frame, uint8 frame_co
|
||||
{sx + 1, sy + 1, 2, ey - sy - 1},
|
||||
{ex - 2, sy + 1, 2, ey - sy - 1},
|
||||
};
|
||||
SDL_FillRects(hBmpScreen[SCREEN_FRONT], rects, 4, frame_color);
|
||||
sdl::FillSurfaceRects(hBmpScreen[SCREEN_FRONT], rects, 4, frame_color);
|
||||
box_line(SCREEN_FRONT, sx + 4, sy + 4, ex - 4, ey - 4, frame_color);
|
||||
}
|
||||
invalidate_screen(sx, sy, ex - sx + 1, ey - sy + 1);
|
||||
|
||||
+1
-2
@@ -42,7 +42,7 @@ CG AGS::load_gl3(const std::vector<uint8_t>& data, bool set_palette, int transpa
|
||||
// GL3展開
|
||||
CG cg(sx * 8, sy, width * 8, height);
|
||||
if (transparent >= 0) {
|
||||
SDL_SetColorKey(cg.surface(), SDL_TRUE, transparent | base);
|
||||
sdl::SetSurfaceColorKey(cg.surface(), true, transparent | base);
|
||||
}
|
||||
uint8 cgdata[4][80][3];
|
||||
int p = 0x36;
|
||||
@@ -128,4 +128,3 @@ CG AGS::load_gl3(const std::vector<uint8_t>& data, bool set_palette, int transpa
|
||||
|
||||
return cg;
|
||||
}
|
||||
|
||||
|
||||
+1
-2
@@ -25,7 +25,7 @@ CG AGS::load_gm3(const std::vector<uint8_t>& data, int transparent, uint8_t flag
|
||||
// GM3展開
|
||||
CG cg(sx * 8, sy * 2, width * 8, height * 2);
|
||||
if (transparent >= 0) {
|
||||
SDL_SetColorKey(cg.surface(), SDL_TRUE, transparent | base);
|
||||
sdl::SetSurfaceColorKey(cg.surface(), true, transparent | base);
|
||||
}
|
||||
uint8 cgdata[3][80][3];
|
||||
int p = 0x36;
|
||||
@@ -115,4 +115,3 @@ CG AGS::load_gm3(const std::vector<uint8_t>& data, int transparent, uint8_t flag
|
||||
|
||||
return cg;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -81,7 +81,7 @@ CG AGS::load_pms(int page, const std::vector<uint8_t>& data, bool set_palette, i
|
||||
// Extract pixel data
|
||||
CG cg(sx, sy, width, height);
|
||||
if (transparent >= 0) {
|
||||
SDL_SetColorKey(cg.surface(), SDL_TRUE, transparent);
|
||||
sdl::SetSurfaceColorKey(cg.surface(), true, transparent);
|
||||
}
|
||||
std::vector<uint8_t> buf[3];
|
||||
buf[0].resize(width);
|
||||
|
||||
@@ -30,8 +30,8 @@ int AGS::draw_text(ScreenId dest, int x, int y, std::u16string_view codes, int f
|
||||
case 48: font = hFont48; break;
|
||||
case 64: font = hFont64; break;
|
||||
}
|
||||
int ascent = TTF_FontAscent(font);
|
||||
int descent = TTF_FontDescent(font);
|
||||
int ascent = ttf::GetFontAscent(font);
|
||||
int descent = ttf::GetFontDescent(font);
|
||||
// Adjust dest_y if the font height is larger than the specified size.
|
||||
int dest_y = y - (ascent - descent - font_size) / 2;
|
||||
|
||||
@@ -49,7 +49,7 @@ int AGS::draw_text(ScreenId dest, int x, int y, std::u16string_view codes, int f
|
||||
draw_char(dest, dest_x, dest_y, code, font, color);
|
||||
|
||||
int miny, maxy, advance;
|
||||
TTF_GlyphMetrics(font, code, NULL, NULL, &miny, &maxy, &advance);
|
||||
ttf::GetGlyphMetrics(font, code, NULL, NULL, &miny, &maxy, &advance);
|
||||
// Some fonts report incorrect Ascent/Descent value so we need to fix them.
|
||||
if (miny < descent) descent = miny;
|
||||
if (maxy > ascent) ascent = maxy;
|
||||
@@ -79,7 +79,7 @@ void AGS::draw_char(ScreenId dest, int dest_x, int dest_y, uint16 code, TTF_Font
|
||||
}
|
||||
}
|
||||
|
||||
SDL_FreeSurface(fs);
|
||||
sdl::DestroySurface(fs);
|
||||
}
|
||||
|
||||
int AGS::nearest_color(int r, int g, int b) {
|
||||
@@ -128,7 +128,7 @@ void AGS::draw_char_antialias(ScreenId dest, int dest_x, int dest_y, uint16 code
|
||||
}
|
||||
}
|
||||
|
||||
SDL_FreeSurface(fs);
|
||||
sdl::DestroySurface(fs);
|
||||
}
|
||||
|
||||
void AGS::draw_gaiji(ScreenId dest, int dest_x, int dest_y, const uint8_t bitmap[32], int size, uint8 color)
|
||||
|
||||
+5
-6
@@ -12,12 +12,12 @@ namespace {
|
||||
|
||||
void trim(CG& cg, int w, int h)
|
||||
{
|
||||
SDL_Surface *sf = SDL_CreateRGBSurfaceWithFormat(0, w, h, 8, SDL_PIXELFORMAT_INDEX8);
|
||||
SDL_Surface *sf = sdl::CreateSurface(w, h, SDL_PIXELFORMAT_INDEX8);
|
||||
SDL_SetSurfacePalette(sf, cg.palette());
|
||||
if (SDL_HasColorKey(cg.surface())) {
|
||||
if (sdl::SurfaceHasColorKey(cg.surface())) {
|
||||
Uint32 key;
|
||||
SDL_GetColorKey(cg.surface(), &key);
|
||||
SDL_SetColorKey(sf, SDL_TRUE, key);
|
||||
sdl::GetSurfaceColorKey(cg.surface(), &key);
|
||||
sdl::SetSurfaceColorKey(sf, true, key);
|
||||
}
|
||||
SDL_Rect srcrect = { 0, 0, w, h };
|
||||
SDL_BlitSurface(cg.surface(), &srcrect, sf, NULL);
|
||||
@@ -74,7 +74,7 @@ CG AGS::load_vsp(const std::vector<uint8_t>& data, bool set_palette, int transpa
|
||||
// Gakuen Senki uses exact sx values rather than 8x.
|
||||
CG cg(game_id.is(GameId::GAKUEN_SENKI) ? sx : sx * 8, sy, width * 8, height);
|
||||
if (transparent >= 0) {
|
||||
SDL_SetColorKey(cg.surface(), SDL_TRUE, transparent | base);
|
||||
sdl::SetSurfaceColorKey(cg.surface(), true, transparent | base);
|
||||
}
|
||||
uint8 cgdata[4][2][480], mask = 0;
|
||||
int p = 0x3a;
|
||||
@@ -166,4 +166,3 @@ CG AGS::load_vsp(const std::vector<uint8_t>& data, bool set_palette, int transpa
|
||||
}
|
||||
return cg;
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ CG AGS::load_vsp2l(const std::vector<uint8_t>& data, int transparent, uint8_t fl
|
||||
// VSP2L展開
|
||||
CG cg(sx * 8, sy * 2, width * 8, height * 2);
|
||||
if (transparent >= 0) {
|
||||
SDL_SetColorKey(cg.surface(), SDL_TRUE, transparent | base);
|
||||
sdl::SetSurfaceColorKey(cg.surface(), true, transparent | base);
|
||||
}
|
||||
uint8 cgdata[3][2][200], mask = 0;
|
||||
int p = 0x1a;
|
||||
|
||||
+4
-4
@@ -8,7 +8,7 @@
|
||||
#define _CG_H_
|
||||
|
||||
#include <memory>
|
||||
#include <SDL.h>
|
||||
#include "sdl_compat.h"
|
||||
#include "common.h"
|
||||
|
||||
inline uint8_t* surface_line(SDL_Surface* surface, int y)
|
||||
@@ -18,7 +18,7 @@ inline uint8_t* surface_line(SDL_Surface* surface, int y)
|
||||
|
||||
struct SurfaceDeleter {
|
||||
void operator()(SDL_Surface* s) const noexcept {
|
||||
if (s) SDL_FreeSurface(s);
|
||||
if (s) sdl::DestroySurface(s);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -29,14 +29,14 @@ struct CG {
|
||||
|
||||
CG() = default;
|
||||
CG(int x, int y, int width, int height)
|
||||
: surface_(SDL_CreateRGBSurfaceWithFormat(0, width, height, 8, SDL_PIXELFORMAT_INDEX8)),
|
||||
: surface_(sdl::CreateSurface(width, height, SDL_PIXELFORMAT_INDEX8)),
|
||||
x(x), y(y) {}
|
||||
explicit operator bool() const noexcept { return static_cast<bool>(surface_); }
|
||||
|
||||
SDL_Surface* surface() const { return surface_.get(); }
|
||||
int width() const { return surface_->w; }
|
||||
int height() const { return surface_->h; }
|
||||
SDL_Palette* palette() const { return surface_->format->palette; }
|
||||
SDL_Palette* palette() const { return sdl::GetSurfacePalette(surface_.get()); }
|
||||
};
|
||||
|
||||
#endif // _CG_H_
|
||||
|
||||
+16
-16
@@ -7,9 +7,9 @@
|
||||
#include "dri.h"
|
||||
#include <memory>
|
||||
#include <string.h>
|
||||
#include <SDL.h>
|
||||
#include "game_id.h"
|
||||
#include "fileio.h"
|
||||
#include "sdl_compat.h"
|
||||
|
||||
void Dri::open(const char* file_name)
|
||||
{
|
||||
@@ -185,37 +185,37 @@ std::vector<uint8> Dri::load_mda(const GameId& game_id, int page)
|
||||
return {};
|
||||
}
|
||||
|
||||
SDL_RWops* rw = open_resource(name, "mda");
|
||||
sdl::IOStream* rw = open_resource(name, "mda");
|
||||
if (!rw)
|
||||
return {};
|
||||
uint8 buf[4];
|
||||
|
||||
// ページの位置を取得
|
||||
SDL_RWread(rw, buf, 4, 1);
|
||||
sdl::ReadIO(rw, buf, 4);
|
||||
int link_sector = buf[0] | (buf[1] << 8);
|
||||
int data_sector = buf[2] | (buf[3] << 8);
|
||||
|
||||
if(page > (data_sector - link_sector) * 128 - 1) {
|
||||
// ページ番号不正
|
||||
SDL_RWclose(rw);
|
||||
sdl::CloseIO(rw);
|
||||
return {};
|
||||
}
|
||||
|
||||
SDL_RWseek(rw, (link_sector - 1) * 256 + (page - 1) * 2, RW_SEEK_SET);
|
||||
SDL_RWread(rw, buf, 2, 1);
|
||||
sdl::SeekIO(rw, (link_sector - 1) * 256 + (page - 1) * 2, SEEK_SET);
|
||||
sdl::ReadIO(rw, buf, 2);
|
||||
|
||||
int disk_index = buf[0];
|
||||
int link_index = buf[1];
|
||||
|
||||
if(disk_index == 0 || disk_index == 0x1a) {
|
||||
// 欠番
|
||||
SDL_RWclose(rw);
|
||||
sdl::CloseIO(rw);
|
||||
return {};
|
||||
}
|
||||
|
||||
// AMUS.MDA以外にリンクされている場合はリソースを開き直す
|
||||
if(disk_index == 2) {
|
||||
SDL_RWclose(rw);
|
||||
sdl::CloseIO(rw);
|
||||
switch (game_id.game) {
|
||||
case GameId::DPS_SG_FAHREN:
|
||||
name = "BMUS_FAH.MDA";
|
||||
@@ -264,7 +264,7 @@ std::vector<uint8> Dri::load_mda(const GameId& game_id, int page)
|
||||
return {};
|
||||
}
|
||||
} else if(disk_index == 3) {
|
||||
SDL_RWclose(rw);
|
||||
sdl::CloseIO(rw);
|
||||
switch (game_id.game) {
|
||||
case GameId::TOUSHIN_HINT:
|
||||
name = "CMUS_T1.MDA";
|
||||
@@ -281,27 +281,27 @@ std::vector<uint8> Dri::load_mda(const GameId& game_id, int page)
|
||||
}
|
||||
} else if(disk_index != 1) {
|
||||
// AMUS.MDA以外にリンクされている場合は失敗
|
||||
SDL_RWclose(rw);
|
||||
sdl::CloseIO(rw);
|
||||
return {};
|
||||
}
|
||||
|
||||
// データ取得
|
||||
SDL_RWseek(rw, link_index * 2, RW_SEEK_SET);
|
||||
SDL_RWread(rw, buf, 4, 1);
|
||||
sdl::SeekIO(rw, link_index * 2, SEEK_SET);
|
||||
sdl::ReadIO(rw, buf, 4);
|
||||
int start_sector = buf[0] | (buf[1] << 8);
|
||||
int end_sector = buf[2] | (buf[3] << 8);
|
||||
|
||||
int size = (end_sector - start_sector) * 256;
|
||||
if (size == 0) {
|
||||
// サイズ不正
|
||||
SDL_RWclose(rw);
|
||||
sdl::CloseIO(rw);
|
||||
return {};
|
||||
}
|
||||
std::vector<uint8_t> buffer(size);
|
||||
SDL_RWseek(rw, (start_sector - 1) * 256, RW_SEEK_SET);
|
||||
SDL_RWread(rw, buffer.data(), size, 1);
|
||||
sdl::SeekIO(rw, (start_sector - 1) * 256, SEEK_SET);
|
||||
sdl::ReadIO(rw, buffer.data(), size);
|
||||
|
||||
SDL_RWclose(rw);
|
||||
sdl::CloseIO(rw);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
#include <string.h>
|
||||
#include <SDL.h>
|
||||
#include "sdl_compat.h"
|
||||
#include "game_id.h"
|
||||
#include "fileio.h"
|
||||
#include "config.h"
|
||||
|
||||
+18
-18
@@ -6,7 +6,7 @@
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <SDL.h>
|
||||
#include "sdl_compat.h"
|
||||
#include <RtMidi.h>
|
||||
|
||||
#include "mako_midi.h"
|
||||
@@ -98,7 +98,7 @@ public:
|
||||
bool load_mml(const std::vector<uint8_t>& data);
|
||||
void load_mda(const std::vector<uint8_t>& data);
|
||||
void start_midi();
|
||||
bool play_midi(SDL_atomic_t* current_loop, SDL_atomic_t* current_mark);
|
||||
bool play_midi(sdl::AtomicInt* current_loop, sdl::AtomicInt* current_mark);
|
||||
int seq() const { return seq_; }
|
||||
|
||||
private:
|
||||
@@ -231,7 +231,7 @@ void Playback::start_midi()
|
||||
play_time = 0;
|
||||
}
|
||||
|
||||
bool Playback::play_midi(SDL_atomic_t* current_loop, SDL_atomic_t* current_mark)
|
||||
bool Playback::play_midi(sdl::AtomicInt* current_loop, sdl::AtomicInt* current_mark)
|
||||
{
|
||||
// 経過時間の取得
|
||||
Uint32 current_time = SDL_GetTicks();
|
||||
@@ -272,7 +272,7 @@ bool Playback::play_midi(SDL_atomic_t* current_loop, SDL_atomic_t* current_mark)
|
||||
!play[6].loop_flag && !play[7].loop_flag && !play[8].loop_flag) {
|
||||
// 全チャンネルが再生停止 (ループしない曲)
|
||||
stop_midi();
|
||||
SDL_AtomicSet(current_loop, 1);
|
||||
sdl::SetAtomicInt(current_loop, 1);
|
||||
return false;
|
||||
}
|
||||
play[i].wait_time = 1;
|
||||
@@ -284,7 +284,7 @@ bool Playback::play_midi(SDL_atomic_t* current_loop, SDL_atomic_t* current_mark)
|
||||
loop = play[j].loop_cnt;
|
||||
}
|
||||
}
|
||||
SDL_AtomicSet(current_loop, loop);
|
||||
sdl::SetAtomicInt(current_loop, loop);
|
||||
if(loop_ && loop >= loop_) {
|
||||
// 指定回数だけ再生完了
|
||||
stop_midi();
|
||||
@@ -311,7 +311,7 @@ bool Playback::play_midi(SDL_atomic_t* current_loop, SDL_atomic_t* current_mark)
|
||||
}
|
||||
play[i].note = 128;
|
||||
} else if(d0 == 0xe0) {
|
||||
SDL_AtomicSet(current_mark, mml[i].next());
|
||||
sdl::SetAtomicInt(current_mark, mml[i].next());
|
||||
} else if(d0 == 0xe1) {
|
||||
d1 = mml[i].next();
|
||||
play[i].velocity += (d1 > 127) ? (d1 - 256) : d1;
|
||||
@@ -638,9 +638,9 @@ MAKOMidi::MAKOMidi(int device)
|
||||
thread = SDL_CreateThread(thread_main, "MAKOMidi", this);
|
||||
queue_mutex = SDL_CreateMutex();
|
||||
}
|
||||
SDL_AtomicSet(¤t_seq, 0);
|
||||
SDL_AtomicSet(¤t_loop, 0);
|
||||
SDL_AtomicSet(¤t_mark, 0);
|
||||
sdl::SetAtomicInt(¤t_seq, 0);
|
||||
sdl::SetAtomicInt(¤t_loop, 0);
|
||||
sdl::SetAtomicInt(¤t_mark, 0);
|
||||
}
|
||||
|
||||
MAKOMidi::~MAKOMidi()
|
||||
@@ -666,7 +666,7 @@ bool MAKOMidi::play(const GameId& game_id, Dri& amus, Dri& mda, int page, int lo
|
||||
auto playback = Playback::create(game_id, amus, mda, page, loop, seq);
|
||||
if (!playback)
|
||||
return false;
|
||||
SDL_AtomicSet(¤t_seq, seq);
|
||||
sdl::SetAtomicInt(¤t_seq, seq);
|
||||
SDL_LockMutex(queue_mutex);
|
||||
queue.push(std::make_unique<MAKOMidi::Command>(Command::PLAY, std::move(playback)));
|
||||
SDL_UnlockMutex(queue_mutex);
|
||||
@@ -675,7 +675,7 @@ bool MAKOMidi::play(const GameId& game_id, Dri& amus, Dri& mda, int page, int lo
|
||||
|
||||
void MAKOMidi::stop()
|
||||
{
|
||||
SDL_AtomicSet(¤t_seq, 0);
|
||||
sdl::SetAtomicInt(¤t_seq, 0);
|
||||
SDL_LockMutex(queue_mutex);
|
||||
queue.push(std::make_unique<MAKOMidi::Command>(Command::STOP));
|
||||
SDL_UnlockMutex(queue_mutex);
|
||||
@@ -683,13 +683,13 @@ void MAKOMidi::stop()
|
||||
|
||||
bool MAKOMidi::is_playing()
|
||||
{
|
||||
return SDL_AtomicGet(¤t_seq) != 0;
|
||||
return sdl::GetAtomicInt(¤t_seq) != 0;
|
||||
}
|
||||
|
||||
void MAKOMidi::get_mark(int* mark, int* loop)
|
||||
{
|
||||
*mark = SDL_AtomicGet(¤t_mark);
|
||||
*loop = SDL_AtomicGet(¤t_loop);
|
||||
*mark = sdl::GetAtomicInt(¤t_mark);
|
||||
*loop = sdl::GetAtomicInt(¤t_loop);
|
||||
}
|
||||
|
||||
void MAKOMidi::thread_loop()
|
||||
@@ -708,8 +708,8 @@ void MAKOMidi::thread_loop()
|
||||
stop_midi();
|
||||
current = std::move(cmd->playback);
|
||||
current->start_midi();
|
||||
SDL_AtomicSet(¤t_loop, 0);
|
||||
SDL_AtomicSet(¤t_mark, 0);
|
||||
sdl::SetAtomicInt(¤t_loop, 0);
|
||||
sdl::SetAtomicInt(¤t_mark, 0);
|
||||
SDL_Delay(100); // ?
|
||||
break;
|
||||
case Command::STOP:
|
||||
@@ -726,7 +726,7 @@ void MAKOMidi::thread_loop()
|
||||
SDL_UnlockMutex(queue_mutex);
|
||||
if (current) {
|
||||
if (!current->play_midi(¤t_loop, ¤t_mark)) {
|
||||
SDL_AtomicCAS(¤t_seq, current->seq(), 0);
|
||||
sdl::CompareAndSwapAtomicInt(¤t_seq, current->seq(), 0);
|
||||
current.reset();
|
||||
}
|
||||
}
|
||||
@@ -737,7 +737,7 @@ void MAKOMidi::thread_loop()
|
||||
// static
|
||||
int MAKOMidi::thread_main(void* data)
|
||||
{
|
||||
SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH);
|
||||
sdl::SetCurrentThreadPriority(SDL_THREAD_PRIORITY_HIGH);
|
||||
|
||||
MAKOMidi* mm = reinterpret_cast<MAKOMidi*>(data);
|
||||
mm->thread_loop();
|
||||
|
||||
+5
-5
@@ -3,7 +3,7 @@
|
||||
|
||||
#include <memory>
|
||||
#include <queue>
|
||||
#include <SDL.h>
|
||||
#include "sdl_compat.h"
|
||||
#include "common.h"
|
||||
|
||||
struct GameId;
|
||||
@@ -24,11 +24,11 @@ public:
|
||||
private:
|
||||
struct Command;
|
||||
std::queue<std::unique_ptr<Command>> queue;
|
||||
SDL_mutex* queue_mutex = nullptr;
|
||||
sdl::Mutex* queue_mutex = nullptr;
|
||||
SDL_Thread* thread = nullptr;
|
||||
SDL_atomic_t current_seq;
|
||||
SDL_atomic_t current_loop;
|
||||
SDL_atomic_t current_mark;
|
||||
sdl::AtomicInt current_seq;
|
||||
sdl::AtomicInt current_loop;
|
||||
sdl::AtomicInt current_mark;
|
||||
int next_seq = 0;
|
||||
|
||||
void thread_loop();
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
#ifndef MAKO_MUSIC_H_
|
||||
#define MAKO_MUSIC_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <SDL.h>
|
||||
|
||||
class MakoMusicDecoder;
|
||||
|
||||
// Plays a single BGM file (MP3, OGG, or WAV, chosen by file extension),
|
||||
// decoding on demand and mixing into the audio callback's output buffer.
|
||||
class MakoMusic {
|
||||
public:
|
||||
// loops: number of times to play; 0 means loop forever.
|
||||
MakoMusic(const std::string& path, int loops, const SDL_AudioSpec& device_spec);
|
||||
~MakoMusic();
|
||||
|
||||
bool is_open() const { return stream != nullptr; }
|
||||
bool is_playing() const { return playing; }
|
||||
|
||||
// Called from the audio callback.
|
||||
void mix(Uint8* out, int len);
|
||||
|
||||
private:
|
||||
// Decodes and queues one chunk, handling EOF and decoder errors.
|
||||
void decode();
|
||||
|
||||
std::unique_ptr<MakoMusicDecoder> decoder;
|
||||
SDL_AudioStream* stream = nullptr;
|
||||
int loops_; // number of times to play, 0 for infinite loop
|
||||
bool playing = false;
|
||||
bool input_finished = false;
|
||||
};
|
||||
|
||||
#endif // MAKO_MUSIC_H_
|
||||
@@ -1,31 +1,16 @@
|
||||
#include "mako_music.h"
|
||||
#include "music_decoder.h"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cctype>
|
||||
#include <memory>
|
||||
#include "common.h"
|
||||
|
||||
#define DR_MP3_IMPLEMENTATION
|
||||
#include "dr_mp3.h"
|
||||
#include "stb_vorbis.h"
|
||||
|
||||
struct DecodedChunk {
|
||||
const Uint8* data;
|
||||
int frames;
|
||||
};
|
||||
|
||||
class MakoMusicDecoder {
|
||||
public:
|
||||
virtual ~MakoMusicDecoder() = default;
|
||||
virtual bool is_open() const = 0;
|
||||
virtual const SDL_AudioSpec& spec() const = 0;
|
||||
virtual DecodedChunk decode(int max_frames) = 0;
|
||||
virtual void seek_start() = 0;
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
class Mp3Decoder final : public MakoMusicDecoder {
|
||||
class Mp3Decoder final : public MusicDecoder {
|
||||
public:
|
||||
explicit Mp3Decoder(const std::string& path)
|
||||
{
|
||||
@@ -35,7 +20,7 @@ public:
|
||||
}
|
||||
initialized = true;
|
||||
spec_.freq = static_cast<int>(mp3.sampleRate);
|
||||
spec_.format = AUDIO_S16SYS;
|
||||
spec_.format = sdl::AudioS16;
|
||||
spec_.channels = static_cast<Uint8>(mp3.channels);
|
||||
}
|
||||
|
||||
@@ -63,7 +48,7 @@ private:
|
||||
bool initialized = false;
|
||||
};
|
||||
|
||||
class OggDecoder final : public MakoMusicDecoder {
|
||||
class OggDecoder final : public MusicDecoder {
|
||||
public:
|
||||
explicit OggDecoder(const std::string& path)
|
||||
{
|
||||
@@ -75,7 +60,7 @@ public:
|
||||
}
|
||||
stb_vorbis_info info = stb_vorbis_get_info(vorbis);
|
||||
spec_.freq = static_cast<int>(info.sample_rate);
|
||||
spec_.format = AUDIO_S16SYS;
|
||||
spec_.format = sdl::AudioS16;
|
||||
spec_.channels = static_cast<Uint8>(info.channels);
|
||||
}
|
||||
|
||||
@@ -103,7 +88,7 @@ private:
|
||||
std::array<int16_t, 1024 * 2> pcm;
|
||||
};
|
||||
|
||||
class WavDecoder final : public MakoMusicDecoder {
|
||||
class WavDecoder final : public MusicDecoder {
|
||||
public:
|
||||
explicit WavDecoder(const std::string& path)
|
||||
{
|
||||
@@ -119,7 +104,7 @@ public:
|
||||
~WavDecoder() override
|
||||
{
|
||||
if (data)
|
||||
SDL_FreeWAV(data);
|
||||
sdl::FreeWAV(data);
|
||||
}
|
||||
|
||||
bool is_open() const override { return data_size > 0 && frame_size > 0; }
|
||||
@@ -155,9 +140,11 @@ bool has_extension(const std::string& path, const char* ext)
|
||||
return tail == ext;
|
||||
}
|
||||
|
||||
std::unique_ptr<MakoMusicDecoder> create_decoder(const std::string& path)
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<MusicDecoder> create_music_decoder(const std::string& path)
|
||||
{
|
||||
std::unique_ptr<MakoMusicDecoder> decoder;
|
||||
std::unique_ptr<MusicDecoder> decoder;
|
||||
if (has_extension(path, ".ogg") || has_extension(path, ".oga"))
|
||||
decoder = std::make_unique<OggDecoder>(path);
|
||||
else if (has_extension(path, ".wav"))
|
||||
@@ -168,67 +155,3 @@ std::unique_ptr<MakoMusicDecoder> create_decoder(const std::string& path)
|
||||
return nullptr;
|
||||
return decoder;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
MakoMusic::MakoMusic(const std::string& path, int loops, const SDL_AudioSpec& device_spec)
|
||||
: decoder(create_decoder(path)), loops_(loops)
|
||||
{
|
||||
if (!decoder)
|
||||
return;
|
||||
const SDL_AudioSpec& src_spec = decoder->spec();
|
||||
|
||||
stream = SDL_NewAudioStream(src_spec.format, src_spec.channels, src_spec.freq,
|
||||
device_spec.format, device_spec.channels, device_spec.freq);
|
||||
if (!stream) {
|
||||
WARNING("SDL_NewAudioStream failed: %s", SDL_GetError());
|
||||
return;
|
||||
}
|
||||
playing = true;
|
||||
}
|
||||
|
||||
MakoMusic::~MakoMusic()
|
||||
{
|
||||
if (stream)
|
||||
SDL_FreeAudioStream(stream);
|
||||
}
|
||||
|
||||
void MakoMusic::decode()
|
||||
{
|
||||
constexpr int CHUNK_FRAMES = 1024;
|
||||
|
||||
DecodedChunk chunk = decoder->decode(CHUNK_FRAMES);
|
||||
|
||||
if (chunk.frames == 0) {
|
||||
if (loops_ && --loops_ == 0) {
|
||||
SDL_AudioStreamFlush(stream);
|
||||
input_finished = true;
|
||||
} else {
|
||||
decoder->seek_start();
|
||||
}
|
||||
return;
|
||||
}
|
||||
const SDL_AudioSpec& spec = decoder->spec();
|
||||
int bytes = chunk.frames * SDL_AUDIO_BITSIZE(spec.format) / 8 * spec.channels;
|
||||
if (SDL_AudioStreamPut(stream, chunk.data, bytes) < 0) {
|
||||
WARNING("SDL_AudioStreamPut failed: %s", SDL_GetError());
|
||||
input_finished = true;
|
||||
}
|
||||
}
|
||||
|
||||
void MakoMusic::mix(Uint8* out, int len)
|
||||
{
|
||||
if (!stream || !playing)
|
||||
return;
|
||||
|
||||
while (SDL_AudioStreamAvailable(stream) < len && !input_finished)
|
||||
decode();
|
||||
|
||||
Uint8* tmp = SDL_stack_alloc(Uint8, len);
|
||||
int got = SDL_AudioStreamGet(stream, tmp, len);
|
||||
if (got > 0)
|
||||
SDL_MixAudioFormat(out, tmp, AUDIO_S16SYS, got, SDL_MIX_MAXVOLUME);
|
||||
SDL_stack_free(tmp);
|
||||
if (input_finished && SDL_AudioStreamAvailable(stream) <= 0)
|
||||
playing = false;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#ifndef MAKO_MUSIC_DECODER_H_
|
||||
#define MAKO_MUSIC_DECODER_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include "sdl_compat.h"
|
||||
|
||||
struct DecodedChunk {
|
||||
const Uint8* data;
|
||||
int frames;
|
||||
};
|
||||
|
||||
class MusicDecoder {
|
||||
public:
|
||||
virtual ~MusicDecoder() = default;
|
||||
virtual bool is_open() const = 0;
|
||||
virtual const SDL_AudioSpec& spec() const = 0;
|
||||
virtual DecodedChunk decode(int max_frames) = 0;
|
||||
virtual void seek_start() = 0;
|
||||
};
|
||||
|
||||
std::unique_ptr<MusicDecoder> create_music_decoder(const std::string& path);
|
||||
|
||||
#endif // MAKO_MUSIC_DECODER_H_
|
||||
+15
-1
@@ -79,7 +79,8 @@ NACT::NACT(const Config& config, const GameId& game_id)
|
||||
init_windows();
|
||||
init_text();
|
||||
|
||||
SDL_Init(SDL_INIT_GAMECONTROLLER);
|
||||
SDL_Init(sdl::INIT_GAMEPAD);
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
for (int i = 0; i < SDL_NumJoysticks(); ++i) {
|
||||
if (SDL_IsGameController(i)) {
|
||||
sdl_gamecontroller = SDL_GameControllerOpen(i);
|
||||
@@ -90,6 +91,19 @@ NACT::NACT(const Config& config, const GameId& game_id)
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
int count = 0;
|
||||
SDL_JoystickID* ids = SDL_GetJoysticks(&count);
|
||||
for (int i = 0; i < count; ++i) {
|
||||
if (SDL_IsGamepad(ids[i])) {
|
||||
sdl_gamecontroller = SDL_OpenGamepad(ids[i]);
|
||||
if (sdl_gamecontroller)
|
||||
break;
|
||||
WARNING("Could not open gamepad %i: %s\n", i, SDL_GetError());
|
||||
}
|
||||
}
|
||||
SDL_free(ids);
|
||||
#endif
|
||||
}
|
||||
|
||||
NACT::~NACT()
|
||||
|
||||
+11
-2
@@ -14,7 +14,7 @@
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
#include <stdio.h>
|
||||
#include <SDL.h>
|
||||
#include "sdl_compat.h"
|
||||
#include "common.h"
|
||||
#include "config.h"
|
||||
#include "cg.h"
|
||||
@@ -22,6 +22,11 @@
|
||||
#include "game_id.h"
|
||||
#include "scenario.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
struct tagMSG;
|
||||
typedef tagMSG MSG;
|
||||
#endif
|
||||
|
||||
#define RND var[ 0]
|
||||
|
||||
#define MAX_VERB 128
|
||||
@@ -58,6 +63,10 @@ public:
|
||||
|
||||
void set_skip_menu_state(bool enabled, bool checked);
|
||||
|
||||
#ifdef _WIN32
|
||||
void handle_windows_event(MSG* msg);
|
||||
#endif
|
||||
|
||||
virtual uint16 cali() = 0;
|
||||
|
||||
Scenario sco;
|
||||
@@ -270,7 +279,7 @@ protected:
|
||||
// input
|
||||
bool mouse_move_enabled = true;
|
||||
bool wait_keydown = true; // ウェイト時のキー受付
|
||||
SDL_GameController *sdl_gamecontroller = NULL;
|
||||
sdl::Gamepad *sdl_gamecontroller = NULL;
|
||||
|
||||
uint8 get_key(bool notify_texthook = true);
|
||||
void wait_key_release(uint8_t mask = 0xff);
|
||||
|
||||
@@ -568,7 +568,7 @@ private:
|
||||
base = CELL_EMPTY;
|
||||
if (base == CELL_EMPTY) {
|
||||
SDL_Rect rect = { px, py, TILE_SIZE, TILE_SIZE };
|
||||
SDL_FillRect(composed_map.surface(), &rect, 0);
|
||||
sdl::FillSurfaceRect(composed_map.surface(), &rect, 0);
|
||||
} else {
|
||||
draw_map_tile(base, px, py, false);
|
||||
}
|
||||
@@ -666,8 +666,7 @@ private:
|
||||
sheet_x, sheet_y, width, height, cg.width(), cg.height());
|
||||
return std::nullopt;
|
||||
}
|
||||
SDL_SetColorKey(cg.surface(), transparent ? SDL_TRUE : SDL_FALSE,
|
||||
TRANSPARENT_COLOR);
|
||||
sdl::SetSurfaceColorKey(cg.surface(), transparent, TRANSPARENT_COLOR);
|
||||
return SDL_Rect{ sheet_x, sheet_y, width, height };
|
||||
}
|
||||
|
||||
@@ -1472,7 +1471,7 @@ private:
|
||||
get_cursor(&seen_x, &seen_y);
|
||||
// Hides the system pointer and draws the cursor at (cx, cy) instead.
|
||||
auto show_cursor = [&] {
|
||||
SDL_ShowCursor(SDL_DISABLE);
|
||||
sdl::HideCursor();
|
||||
draw_mouse_cursor(shape, cx, cy);
|
||||
cursor_drawn = true;
|
||||
};
|
||||
@@ -1481,7 +1480,7 @@ private:
|
||||
auto hide_cursor = [&] {
|
||||
if (!cursor_drawn)
|
||||
return;
|
||||
SDL_ShowCursor(SDL_ENABLE);
|
||||
sdl::ShowCursor();
|
||||
ags->copy_screen(SCREEN_BACK, SCREEN_FRONT, cx, cy, cx + MOUSE_CURSOR_SIZE - 1,
|
||||
cy + MOUSE_CURSOR_SIZE - 1, cx, cy);
|
||||
cursor_drawn = false;
|
||||
|
||||
+26
-22
@@ -8,7 +8,6 @@
|
||||
#include <windows.h>
|
||||
#undef ERROR
|
||||
#endif
|
||||
#include <SDL_syswm.h>
|
||||
#include "nact.h"
|
||||
#include "ags.h"
|
||||
#include "texthook.h"
|
||||
@@ -21,6 +20,7 @@ enum TouchState {
|
||||
};
|
||||
|
||||
extern SDL_Window* g_window;
|
||||
extern SDL_Renderer* g_renderer;
|
||||
static int mousex, mousey, wheel;
|
||||
static TouchState touch_state = TOUCH_NONE;
|
||||
|
||||
@@ -30,33 +30,37 @@ void NACT::handle_event(SDL_Event e)
|
||||
return;
|
||||
|
||||
switch (e.type) {
|
||||
case SDL_QUIT:
|
||||
case sdl::EVENT_QUIT:
|
||||
show_quit_dialog();
|
||||
break;
|
||||
|
||||
#ifdef __ANDROID__
|
||||
case SDL_KEYUP:
|
||||
if (e.key.keysym.scancode == SDL_SCANCODE_AC_BACK) {
|
||||
case sdl::EVENT_KEY_UP:
|
||||
if (sdl::GetKeyScancode(e.key) == SDL_SCANCODE_AC_BACK) {
|
||||
show_quit_dialog();
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
|
||||
case SDL_MOUSEMOTION:
|
||||
mousex = e.motion.x * ags->screen_width / ags->window_width;
|
||||
mousey = e.motion.y * ags->screen_height / ags->window_height;
|
||||
case sdl::EVENT_MOUSE_MOTION: {
|
||||
float render_x, render_y;
|
||||
sdl::RenderCoordinatesFromWindow(g_renderer, e.motion.x, e.motion.y,
|
||||
&render_x, &render_y);
|
||||
mousex = render_x * ags->screen_width / ags->window_width;
|
||||
mousey = render_y * ags->screen_height / ags->window_height;
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_MOUSEWHEEL:
|
||||
case sdl::EVENT_MOUSE_WHEEL:
|
||||
wheel += e.wheel.y * (e.wheel.direction == SDL_MOUSEWHEEL_FLIPPED ? -1 : 1);
|
||||
break;
|
||||
|
||||
case SDL_FINGERDOWN:
|
||||
case SDL_FINGERUP:
|
||||
case SDL_FINGERMOTION:
|
||||
case sdl::EVENT_FINGER_DOWN:
|
||||
case sdl::EVENT_FINGER_UP:
|
||||
case sdl::EVENT_FINGER_MOTION:
|
||||
mousex = e.tfinger.x * ags->screen_width;
|
||||
mousey = e.tfinger.y * ags->screen_height;
|
||||
switch (SDL_GetNumTouchFingers(e.tfinger.touchId)) {
|
||||
switch (sdl::GetNumTouchFingers(sdl::GetTouchID(e.tfinger))) {
|
||||
case 0:
|
||||
touch_state = TOUCH_NONE;
|
||||
break;
|
||||
@@ -116,7 +120,7 @@ uint8 NACT::get_key(bool notify_texthook)
|
||||
pump_events();
|
||||
|
||||
// キーボード&マウス
|
||||
const Uint8* key = SDL_GetKeyboardState(NULL);
|
||||
const auto* key = SDL_GetKeyboardState(NULL);
|
||||
Uint32 mouse = SDL_GetMouseState(NULL, NULL);
|
||||
|
||||
if(key[SDL_SCANCODE_UP ] || key[SDL_SCANCODE_KP_8 ] ) val |= 0x01;
|
||||
@@ -131,14 +135,14 @@ uint8 NACT::get_key(bool notify_texthook)
|
||||
// マウス移動で方向入力はサポートしない
|
||||
|
||||
if(sdl_gamecontroller) {
|
||||
if(SDL_GameControllerGetButton(sdl_gamecontroller, SDL_CONTROLLER_BUTTON_DPAD_UP) || SDL_GameControllerGetAxis(sdl_gamecontroller, SDL_CONTROLLER_AXIS_LEFTY) <= -8000) val |= 0x01;
|
||||
if(SDL_GameControllerGetButton(sdl_gamecontroller, SDL_CONTROLLER_BUTTON_DPAD_DOWN) || SDL_GameControllerGetAxis(sdl_gamecontroller, SDL_CONTROLLER_AXIS_LEFTY) >= 8000) val |= 0x02;
|
||||
if(SDL_GameControllerGetButton(sdl_gamecontroller, SDL_CONTROLLER_BUTTON_DPAD_LEFT) || SDL_GameControllerGetAxis(sdl_gamecontroller, SDL_CONTROLLER_AXIS_LEFTX) <= -8000) val |= 0x04;
|
||||
if(SDL_GameControllerGetButton(sdl_gamecontroller, SDL_CONTROLLER_BUTTON_DPAD_RIGHT) || SDL_GameControllerGetAxis(sdl_gamecontroller, SDL_CONTROLLER_AXIS_LEFTX) >= 8000) val |= 0x08;
|
||||
if(SDL_GameControllerGetButton(sdl_gamecontroller, SDL_CONTROLLER_BUTTON_A)) val |= 0x10;
|
||||
if(SDL_GameControllerGetButton(sdl_gamecontroller, SDL_CONTROLLER_BUTTON_B)) val |= 0x20;
|
||||
if(SDL_GameControllerGetButton(sdl_gamecontroller, SDL_CONTROLLER_BUTTON_X)) val |= 0x40;
|
||||
if(SDL_GameControllerGetButton(sdl_gamecontroller, SDL_CONTROLLER_BUTTON_Y)) val |= 0x80;
|
||||
if(sdl::GetGamepadButton(sdl_gamecontroller, sdl::GAMEPAD_BUTTON_DPAD_UP) || sdl::GetGamepadAxis(sdl_gamecontroller, sdl::GAMEPAD_AXIS_LEFTY) <= -8000) val |= 0x01;
|
||||
if(sdl::GetGamepadButton(sdl_gamecontroller, sdl::GAMEPAD_BUTTON_DPAD_DOWN) || sdl::GetGamepadAxis(sdl_gamecontroller, sdl::GAMEPAD_AXIS_LEFTY) >= 8000) val |= 0x02;
|
||||
if(sdl::GetGamepadButton(sdl_gamecontroller, sdl::GAMEPAD_BUTTON_DPAD_LEFT) || sdl::GetGamepadAxis(sdl_gamecontroller, sdl::GAMEPAD_AXIS_LEFTX) <= -8000) val |= 0x04;
|
||||
if(sdl::GetGamepadButton(sdl_gamecontroller, sdl::GAMEPAD_BUTTON_DPAD_RIGHT) || sdl::GetGamepadAxis(sdl_gamecontroller, sdl::GAMEPAD_AXIS_LEFTX) >= 8000) val |= 0x08;
|
||||
if(sdl::GetGamepadButton(sdl_gamecontroller, sdl::GAMEPAD_BUTTON_SOUTH)) val |= 0x10;
|
||||
if(sdl::GetGamepadButton(sdl_gamecontroller, sdl::GAMEPAD_BUTTON_EAST)) val |= 0x20;
|
||||
if(sdl::GetGamepadButton(sdl_gamecontroller, sdl::GAMEPAD_BUTTON_WEST)) val |= 0x40;
|
||||
if(sdl::GetGamepadButton(sdl_gamecontroller, sdl::GAMEPAD_BUTTON_NORTH)) val |= 0x80;
|
||||
}
|
||||
|
||||
return val;
|
||||
@@ -186,7 +190,7 @@ void NACT::show_quit_dialog()
|
||||
buttons,
|
||||
};
|
||||
int buttonid = 0;
|
||||
if (SDL_ShowMessageBox(&messagebox_data, &buttonid) < 0) {
|
||||
if (!sdl::ShowMessageBox(&messagebox_data, &buttonid)) {
|
||||
WARNING("error displaying message box");
|
||||
buttonid = 1;
|
||||
}
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <SDL.h>
|
||||
#include "sdl_compat.h"
|
||||
#include "common.h"
|
||||
#include "texthook.h"
|
||||
#ifdef __EMSCRIPTEN__
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
#ifndef SYSTEM3_TTF_COMPAT_H_
|
||||
#define SYSTEM3_TTF_COMPAT_H_
|
||||
|
||||
#include "sdl_compat.h"
|
||||
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
#include <SDL_ttf.h>
|
||||
#elif SYSTEM3_SDL_VERSION == 3
|
||||
#include <SDL3_ttf/SDL_ttf.h>
|
||||
#else
|
||||
#error "Unsupported SYSTEM3_SDL_VERSION"
|
||||
#endif
|
||||
|
||||
static_assert(SDL_TTF_MAJOR_VERSION == SYSTEM3_SDL_VERSION,
|
||||
"SDL_ttf and SDL must use the same major version");
|
||||
|
||||
namespace ttf {
|
||||
|
||||
inline TTF_Font* OpenFontIO(sdl::IOStream* stream, int point_size)
|
||||
{
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
return TTF_OpenFontRW(stream, 0, point_size);
|
||||
#else
|
||||
return TTF_OpenFontIO(stream, false, point_size);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline int GetFontAscent(TTF_Font* font)
|
||||
{
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
return TTF_FontAscent(font);
|
||||
#else
|
||||
return TTF_GetFontAscent(font);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline int GetFontDescent(TTF_Font* font)
|
||||
{
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
return TTF_FontDescent(font);
|
||||
#else
|
||||
return TTF_GetFontDescent(font);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline bool GetGlyphMetrics(TTF_Font* font, Uint32 code, int* minx, int* maxx,
|
||||
int* miny, int* maxy, int* advance)
|
||||
{
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
return TTF_GlyphMetrics(font, code, minx, maxx, miny, maxy, advance) == 0;
|
||||
#else
|
||||
return TTF_GetGlyphMetrics(font, code, minx, maxx, miny, maxy, advance);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace ttf
|
||||
|
||||
#endif // SYSTEM3_TTF_COMPAT_H_
|
||||
+47
-6
@@ -3,7 +3,9 @@
|
||||
#undef ERROR
|
||||
#include <time.h>
|
||||
#include "nact.h"
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
#include "SDL_syswm.h"
|
||||
#endif
|
||||
#include "encoding.h"
|
||||
#include "ags.h"
|
||||
#include "mako.h"
|
||||
@@ -19,12 +21,34 @@ namespace {
|
||||
bool auto_copy_enabled = false;
|
||||
|
||||
HWND get_hwnd(SDL_Window* window) {
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
SDL_SysWMinfo info;
|
||||
SDL_VERSION(&info.version);
|
||||
SDL_GetWindowWMInfo(window, &info);
|
||||
return info.info.win.window;
|
||||
#else
|
||||
return (HWND)SDL_GetPointerProperty(
|
||||
SDL_GetWindowProperties(window), SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL);
|
||||
#endif
|
||||
}
|
||||
|
||||
void set_fullscreen(bool fullscreen)
|
||||
{
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
SDL_SetWindowFullscreen(
|
||||
g_window, fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
|
||||
#else
|
||||
SDL_SetWindowFullscreen(g_window, fullscreen);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if SYSTEM3_SDL_VERSION == 3
|
||||
bool SDLCALL windows_message_hook(void* userdata, MSG* msg) {
|
||||
static_cast<NACT*>(userdata)->handle_windows_event(msg);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
void init_menu(bool mouse_move_enabled, const Config& config)
|
||||
{
|
||||
HINSTANCE hinst = (HINSTANCE)GetModuleHandle(NULL);
|
||||
@@ -146,7 +170,11 @@ void NACT::platform_initialize()
|
||||
{
|
||||
init_menu(mouse_move_enabled, config);
|
||||
init_console(config, game_id.sys_ver);
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE);
|
||||
#else
|
||||
SDL_SetWindowsMessageHook(windows_message_hook, this);
|
||||
#endif
|
||||
}
|
||||
|
||||
void NACT::platform_finalize()
|
||||
@@ -194,13 +222,27 @@ bool NACT::handle_platform_event(const SDL_Event& e)
|
||||
return false;
|
||||
}
|
||||
|
||||
#if SYSTEM3_SDL_VERSION == 2
|
||||
if (e.type != SDL_SYSWMEVENT)
|
||||
return false;
|
||||
const SDL_SysWMmsg* msg = e.syswm.msg;
|
||||
const SDL_SysWMmsg* syswm = e.syswm.msg;
|
||||
MSG msg = {};
|
||||
msg.hwnd = syswm->msg.win.hwnd;
|
||||
msg.message = syswm->msg.win.msg;
|
||||
msg.wParam = syswm->msg.win.wParam;
|
||||
msg.lParam = syswm->msg.win.lParam;
|
||||
handle_windows_event(&msg);
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
switch (msg->msg.win.msg) {
|
||||
void NACT::handle_windows_event(MSG* msg)
|
||||
{
|
||||
switch (msg->message) {
|
||||
case WM_COMMAND:
|
||||
switch (msg->msg.win.wParam) {
|
||||
switch (msg->wParam) {
|
||||
case ID_SCREENSHOT:
|
||||
save_screenshot(ags);
|
||||
break;
|
||||
@@ -211,10 +253,10 @@ bool NACT::handle_platform_event(const SDL_Event& e)
|
||||
quit(0);
|
||||
break;
|
||||
case ID_SCREEN_WINDOW:
|
||||
SDL_SetWindowFullscreen(g_window, 0);
|
||||
set_fullscreen(false);
|
||||
break;
|
||||
case ID_SCREEN_FULL:
|
||||
SDL_SetWindowFullscreen(g_window, SDL_WINDOW_FULLSCREEN_DESKTOP);
|
||||
set_fullscreen(true);
|
||||
break;
|
||||
case ID_SCANLINE:
|
||||
ags->set_scanline_mode(!ags->get_scanline_mode());
|
||||
@@ -252,5 +294,4 @@ bool NACT::handle_platform_event(const SDL_Event& e)
|
||||
}
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
+2
-1
@@ -12,7 +12,8 @@ git submodule update --init
|
||||
sudo (dkp-)pacman -S switch-dev switch-sdl2 switch-sdl2_ttf
|
||||
mkdir -p out/debug
|
||||
cd out/debug
|
||||
/opt/devkitpro/portlibs/switch/bin/aarch64-none-elf-cmake -DCMAKE_BUILD_TYPE=Debug ../../
|
||||
/opt/devkitpro/portlibs/switch/bin/aarch64-none-elf-cmake \
|
||||
-DCMAKE_BUILD_TYPE=Debug -DSYSTEM3_SDL_VERSION=2 ../../
|
||||
make
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user