pax_global_header00006660000000000000000000000064151507055150014516gustar00rootroot0000000000000052 comment=4079a331ad5aa06a5624e5e2b9c38d7432391137 AlisterT-openjazz-17e8e2b/000077500000000000000000000000001515070551500155225ustar00rootroot00000000000000AlisterT-openjazz-17e8e2b/.editorconfig000066400000000000000000000006761515070551500202100ustar00rootroot00000000000000# https://EditorConfig.org root = true [*] trim_trailing_whitespace = true insert_final_newline = true [{CMakeLists.txt,*.cmake,Makefile}] indent_style = tab indent_size = 4 [{*.cpp,*.h}] indent_style = tab indent_size = 4 [ext/**/{*.cpp,*.h}] indent_style = ignore indent_size = ignore [*.json,*.yml,*.rc,*.adoc] indent_style = space indent_size = 2 [*.md] indent_style = space indent_size = 4 [*.xml] indent_style = tab indent_size = 4 AlisterT-openjazz-17e8e2b/.gitattributes000066400000000000000000000004031515070551500204120ustar00rootroot00000000000000* text=auto # source files *.txt text *.cpp text *.h text CMakeLists.txt text *.cmake text *.json text # android .java text .xml text .gradle text .properties text *.bat text eol=crlf # distribution archives /.github export-ignore /builds/ci export-ignore AlisterT-openjazz-17e8e2b/.gitignore000066400000000000000000000011521515070551500175110ustar00rootroot00000000000000OpenJazz openjazz.cfg openjazz.log *.exe *.a *.o # Makefile stamp .mk-stamp-depr # CMake CMakeCache.txt CMakeFiles/ *.cmake !/builds/cmake/*.cmake ext/Makefile build.ninja CMakeUserPresets.json # CMake builds build-debug/ build-*-debug/ build-release/ build-*-release/ build-*/ # homebrew *.elf # wii *.dol # psp *.prx PARAM.SFO EBOOT.PBP # 3ds *.3dsx *.smdh *.cia # RISC OS /res/riscos/\!OpenJazz/data/* *,ff8 *,e1f # macos .DS_Store # Android builds/android/.gradle/ builds/android/app/.cxx/ # distribution archives openjazz-*.tar.* OpenJazz-*.zip # generated documentation /doc/html /res/unix/OpenJazz.6 AlisterT-openjazz-17e8e2b/CMakeLists.txt000066400000000000000000000403231515070551500202640ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.18...4.2) project(OpenJazz VERSION 20260301 LANGUAGES CXX HOMEPAGE_URL http://alister.eu/jazz/oj/) # Extra CMake Module files list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/builds/cmake) include(CMakeDependentOption) include(OJ-misc) include(Platform-Helpers) # We are in the process to migrate to C++14 set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) # OpenJazz set(OJ_SOURCES # main engine src/game/clientgame.cpp src/game/game.cpp src/game/game.h src/game/gamemode.cpp src/game/gamemode.h src/game/localgame.cpp src/game/servergame.cpp src/io/controls.cpp src/io/controls.h src/io/file.cpp src/io/file.h src/io/gfx/anim.cpp src/io/gfx/anim.h src/io/gfx/font.cpp src/io/gfx/font.h src/io/gfx/paletteeffects.cpp src/io/gfx/paletteeffects.h src/io/gfx/sprite.cpp src/io/gfx/sprite.h src/io/gfx/video.cpp src/io/gfx/video.h src/io/log.cpp src/io/log.h src/io/network.cpp src/io/network.h src/io/sound.cpp src/io/sound.h src/level/level.cpp src/level/level.h src/level/levelplayer.h src/level/movable.cpp src/level/movable.h src/logo.h src/loop.h src/main.cpp src/menu/filemenu.cpp src/menu/gamemenu.cpp src/menu/mainmenu.cpp src/menu/menu.cpp src/menu/menu.h src/menu/plasma.cpp src/menu/plasma.h src/menu/setupmenu.cpp src/OpenJazz.h src/platforms/platforms.h src/platforms/platform_interface.cpp src/platforms/platform_interface.h src/player/player.cpp src/player/player.h src/setup.cpp src/setup.h src/types.h src/util.cpp src/util.h src/version.cpp src/version.h # episode 1 src/jj1/jj1episodeutils.cpp src/jj1/jj1episodeutils.h src/jj1/bonuslevel/jj1bonuslevel.cpp src/jj1/bonuslevel/jj1bonuslevel.h src/jj1/bonuslevel/jj1bonuslevelplayer.cpp src/jj1/bonuslevel/jj1bonuslevelplayer.h src/jj1/level//jj1bird.cpp src/jj1/level//jj1bird.h src/jj1/level//jj1levelplayer.cpp src/jj1/level//jj1levelplayer.h src/jj1/level//jj1levelplayerframe.cpp src/jj1/level/event/jj1bridge.cpp src/jj1/level/event/jj1event.cpp src/jj1/level/event/jj1event.h src/jj1/level/event/jj1guardians.cpp src/jj1/level/event/jj1guardians.h src/jj1/level/event/jj1standardevent.cpp src/jj1/level/jj1bullet.cpp src/jj1/level/jj1bullet.h src/jj1/level/jj1demolevel.cpp src/jj1/level/jj1level.cpp src/jj1/level/jj1level.h src/jj1/level/jj1levelframe.cpp src/jj1/level/jj1levelload.cpp src/jj1/planet/jj1planet.cpp src/jj1/planet/jj1planet.h src/jj1/save/jj1save.cpp src/jj1/save/jj1save.h src/jj1/scene/jj1scene.cpp src/jj1/scene/jj1scene.h src/jj1/scene/jj1sceneload.cpp) if(ANDROID) add_library(OpenJazz SHARED ${OJ_SOURCES}) enable_language(C) set(BUILD_SHARED_LIBS ON) else() add_executable(OpenJazz ${OJ_SOURCES}) endif() target_include_directories(OpenJazz PUBLIC src) target_link_libraries(OpenJazz ${OJ_LIBS_HOST}) # portable mode cmake_dependent_option(PORTABLE "Do not write to external directories, etc." OFF "NOT OJ_DEFAULT_PORTABLE" ON) if(PORTABLE) set(PORTABLE_STATUS "Enabled") target_compile_definitions(OpenJazz PRIVATE PORTABLE) else() set(PORTABLE_STATUS "Disabled") endif() if((PORTABLE AND OJ_DEFAULT_PORTABLE) OR (NOT PORTABLE AND NOT OJ_DEFAULT_PORTABLE)) set(PORTABLE_STATUS "${PORTABLE_STATUS} (Default)") endif() # path to game data set(DATAPATH "" CACHE PATH "Fixed path where game data is available") if(DATAPATH) target_compile_definitions(OpenJazz PRIVATE DATAPATH="${DATAPATH}") endif() # bundled libraries add_subdirectory(ext) # external libraries if(ANDROID) target_compile_definitions(OpenJazz PRIVATE OJ_SDL2) set(SDL_STATUS "SDL2 (bundled)") elseif(EMSCRIPTEN) # emscripten ships a "port" of SDL2, no linking necessary target_compile_options(OpenJazz PRIVATE "--use-port=sdl2;-flto;-fexceptions") target_compile_definitions(OpenJazz PRIVATE OJ_SDL2) set(SDL_STATUS "SDL2 (emscripten port)") elseif(LEGACY_SDL) find_package(SDL REQUIRED) target_compile_definitions(OpenJazz PRIVATE OJ_SDL1) target_include_directories(OpenJazz PRIVATE ${SDL_INCLUDE_DIR}) target_link_libraries(OpenJazz ${SDL_LIBRARY}) set(SDL_STATUS "SDL1.2 (legacy)") else() set(SDL_VERSION "2" CACHE STRING "SDL Version to use (2 or 3") set_property(CACHE SDL_VERSION PROPERTY STRINGS 2 3) if(SDL_VERSION STREQUAL "2") find_package(SDL2 REQUIRED) target_compile_definitions(OpenJazz PRIVATE OJ_SDL2) if(WIN32 OR WII) # TODO: figure out, which other platforms need this target_link_libraries(OpenJazz SDL2::SDL2main) endif() if(PSP) target_compile_definitions(OpenJazz PRIVATE SDL_MAIN_HANDLED) endif() target_link_libraries(OpenJazz SDL2::SDL2) set(SDL_STATUS "SDL2") else() find_package(SDL3 3.4.0 REQUIRED) target_compile_definitions(OpenJazz PRIVATE OJ_SDL3) target_link_libraries(OpenJazz SDL3::SDL3) set(SDL_STATUS "SDL3") endif() endif() # network set(NETWORK_STATUS "Disabled") option(NETWORK "Enable Network support" ON) if(NETWORK) include(CheckIncludeFileCXX) check_include_file_cxx(winsock.h HAVE_WINSOCK) check_include_file_cxx(sys/socket.h HAVE_SOCKETS) if(HAVE_WINSOCK) set(NETWORK_STATUS "Enabled, Windows sockets") target_compile_definitions(OpenJazz PRIVATE USE_SOCKETS) elseif(HAVE_SOCKETS) set(NETWORK_STATUS "Enabled, sockets") target_compile_definitions(OpenJazz PRIVATE USE_SOCKETS) else() # SDL_net find_package(SDL_net) if(SDL_NET_FOUND) set(NETWORK_STATUS "Enabled, SDL_net") target_compile_definitions(OpenJazz PRIVATE USE_SDL_NET) else() set(NETWORK_STATUS "Disabled - no sockets or SDL_net found") endif() endif() if(OJ_LIBS_NET) target_link_libraries(OpenJazz ${OJ_LIBS_NET}) endif() endif() # scaling set(SCALE_STATUS "Disabled") cmake_dependent_option(SCALE "Allow scaling" ON "OJ_ALLOW_SCALE" OFF) if(SCALE) set(SCALE_STATUS "Enabled") target_compile_definitions(OpenJazz PRIVATE SCALE) add_subdirectory(ext/scale2x) endif() option(ENABLE_JJ2 "Enable experimental Episode 2 support (not recommended)" OFF) if(ENABLE_JJ2) target_sources(OpenJazz PRIVATE src/jj2/level/event/jj2event.cpp src/jj2/level/event/jj2event.h src/jj2/level/event/jj2eventframe.cpp src/jj2/level/jj2layer.cpp src/jj2/level/jj2level.cpp src/jj2/level/jj2level.h src/jj2/level/jj2levelframe.cpp src/jj2/level/jj2levelload.cpp src/jj2/level/jj2levelplayer.cpp src/jj2/level/jj2levelplayer.h src/jj2/level/jj2levelplayerframe.cpp) target_compile_definitions(OpenJazz PRIVATE ENABLE_JJ2) endif() # version string(TIMESTAMP OJ_DATE "%Y-%m-%d") include(GetGitRevisionDescription) git_get_exact_tag(GIT_TAG) # Do not include a hash, if we are building a release tag if(NOT GIT_TAG) # otherwise concatenate a version with hash git_describe(GIT_DESCRIPTION --exclude continuous) if(GIT_DESCRIPTION) string(REPLACE "-" ";" GIT_DESCRIPTION ${GIT_DESCRIPTION}) list(LENGTH GIT_DESCRIPTION GIT_DESCRIPTION_PARTS) if(GIT_DESCRIPTION_PARTS EQUAL 3) list(GET GIT_DESCRIPTION 0 GIT_TAG) list(GET GIT_DESCRIPTION 1 GIT_COMMITS) list(GET GIT_DESCRIPTION 2 GIT_HASH) set(GIT_STATUS "${GIT_COMMITS} commits since tag \"${GIT_TAG}\", ") string(PREPEND GIT_COMMITS "+") else() # no tags found, only hash list(GET GIT_DESCRIPTION 0 GIT_HASH) endif() # strip the g prefix string(SUBSTRING ${GIT_HASH} 1 -1 GIT_HASH) set(OJ_VERSION_GIT "git${GIT_COMMITS}@${GIT_HASH}") string(APPEND GIT_STATUS "object hash is ${GIT_HASH}") git_local_changes(GIT_DIRTY) if(GIT_DIRTY STREQUAL "DIRTY") string(APPEND OJ_VERSION_GIT "-dirty") string(APPEND GIT_STATUS ", with uncommitted changes") endif() endif() endif() set_property(SOURCE src/version.cpp PROPERTY COMPILE_DEFINITIONS OJ_VERSION="${PROJECT_VERSION}"; OJ_DATE="${OJ_DATE}"; $<$:OJ_VERSION_GIT="${OJ_VERSION_GIT}">) # project links set(OJ_BUGREPORT "https://github.com/AlisterT/openjazz/issues") set_property(SOURCE src/main.cpp PROPERTY COMPILE_DEFINITIONS OJ_URL="${PROJECT_HOMEPAGE_URL}"; OJ_BUGREPORT="${OJ_BUGREPORT}") # platform stuff if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") target_compile_options(OpenJazz PRIVATE "-fno-math-errno") endif() if(WIN32) # open console for debug builds set_target_properties(OpenJazz PROPERTIES WIN32_EXECUTABLE $<$>:TRUE>) # add icon target_sources(OpenJazz PRIVATE src/platforms/windows.h res/windows/OpenJazz.rc) elseif(EMSCRIPTEN) target_sources(OpenJazz PRIVATE src/platforms/emscripten.h) set_target_properties(OpenJazz PROPERTIES SUFFIX ".html") set(JS_SHELL "${CMAKE_CURRENT_SOURCE_DIR}/res/emscripten/shell.html") set_property(TARGET OpenJazz PROPERTY LINK_FLAGS "--shell-file ${JS_SHELL} -sMINIFY_HTML=0 -flto --use-port=sdl2 -sFORCE_FILESYSTEM -sEXIT_RUNTIME=1 -sASYNCIFY -sENVIRONMENT=web --closure 1 -sASSERTIONS -sNO_DISABLE_EXCEPTION_CATCHING") set_source_files_properties("src/main.cpp" PROPERTIES OBJECT_DEPENDS "${JS_SHELL}") elseif(ANDROID) target_sources(OpenJazz PRIVATE src/platforms/android.cpp src/platforms/android.h) target_link_libraries(OpenJazz -lGLESv1_CM -lGLESv2 -lOpenSLES -llog -landroid) elseif(RISCOS) target_sources(OpenJazz PRIVATE src/platforms/riscos.cpp src/platforms/riscos.h) target_link_options(OpenJazz PRIVATE "-static") elf2aif(OpenJazz) elseif(3DS) target_sources(OpenJazz PRIVATE src/platforms/3ds.cpp src/platforms/3ds.h) target_link_libraries(OpenJazz -lcitro3d) ctr_generate_smdh(OpenJazz.smdh NAME "OpenJazz" DESCRIPTION "Jack Jazzrabbit 1 game engine reimplementation" AUTHOR "AlisterT & carstene1ns" ICON ${CMAKE_CURRENT_SOURCE_DIR}/res/3ds/OpenJazz.png) ctr_create_3dsx(OpenJazz SMDH OpenJazz.smdh ${ROMFS_ARG} ${ROMFS_PATH}) elseif(SWITCH) target_sources(OpenJazz PRIVATE src/platforms/switch.cpp src/platforms/switch.h) nx_generate_nacp(OpenJazz.nacp NAME "OpenJazz" AUTHOR "AlisterT & carstene1ns" VERSION "${PROJECT_VERSION}") nx_create_nro(OpenJazz NACP OpenJazz.nacp ICON ${CMAKE_CURRENT_SOURCE_DIR}/res/switch/OpenJazz.jpg ${ROMFS_ARG} ${ROMFS_PATH}) elseif(WII) target_sources(OpenJazz PRIVATE src/platforms/wii.cpp src/platforms/wii.h) target_link_libraries(OpenJazz -laesnd -lfat -lwiikeyboard) ogc_create_dol(OpenJazz) elseif(PSP) target_sources(OpenJazz PRIVATE src/platforms/psp.cpp src/platforms/psp.h) target_link_libraries(OpenJazz -lGL -lpspdebug -lpspgu -lpspctrl -lpspge -lpspdisplay -lpsphprm -lpspvfpu -lpspaudio -lpspirkeyb -lpsppower) create_pbp_file(TARGET OpenJazz TITLE "\"Jazz Jackrabbit (OpenJazz)\"" ICON_PATH ${CMAKE_CURRENT_SOURCE_DIR}/res/psp/icon.png BUILD_PRX) elseif(PSVITA) # unfinished target_sources(OpenJazz PRIVATE src/platforms/psvita.cpp src/platforms/psvita.h) elseif(GP2X OR WIZ OR CANOO) target_sources(OpenJazz PRIVATE src/platforms/gp2x_wiz_canoo.cpp src/platforms/gp2x_wiz_canoo.h) elseif(DINGOO) target_sources(OpenJazz PRIVATE src/platforms/dingoo.h) elseif(GAMESHELL) target_sources(OpenJazz PRIVATE src/platforms/gameshell.h) elseif(HAIKU) target_sources(OpenJazz PRIVATE src/platforms/haiku.cpp src/platforms/haiku.h) elseif(APPLE) target_sources(OpenJazz PRIVATE src/platforms/apple.cpp src/platforms/apple.h) elseif(UNIX) target_sources(OpenJazz PRIVATE src/platforms/xdg.cpp src/platforms/xdg.h) target_link_libraries(OpenJazz -lm) endif() # installation if(WIN32) # put everything in a folder install(TARGETS OpenJazz RUNTIME DESTINATION dist) install(CODE [[ file(GET_RUNTIME_DEPENDENCIES EXECUTABLES $ RESOLVED_DEPENDENCIES_VAR _r_deps UNRESOLVED_DEPENDENCIES_VAR _u_deps DIRECTORIES $ ) foreach(_file ${_r_deps}) string(TOLOWER ${_file} _file_lower) if(NOT ${_file_lower} MATCHES "c:[\\/]windows[\\/]system32.*") file(INSTALL DESTINATION dist TYPE SHARED_LIBRARY FOLLOW_SYMLINK_CHAIN FILES "${_file}" ) endif() endforeach() #message("UNRESOLVED_DEPENDENCIES_VAR: ${_u_deps}") ]]) elseif(3DS) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/OpenJazz.3dsx DESTINATION OpenJazz COMPONENT 3ds) elseif(SWITCH) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/OpenJazz.nro DESTINATION OpenJazz COMPONENT switch) elseif(WII) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/OpenJazz.dol RENAME boot.dol DESTINATION OpenJazz COMPONENT wii) install(FILES res/wii/icon.png res/wii/meta.xml res/wii/READMII.txt DESTINATION OpenJazz COMPONENT wii) elseif(PSP) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/EBOOT.PBP DESTINATION OpenJazz COMPONENT psp) install(TARGETS OpenJazz RUNTIME DESTINATION . COMPONENT debug) elseif(RISCOS) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/OpenJazz,ff8 res/riscos/!Run,feb res/riscos/!Boot,feb res/riscos/!Sprites,ff9 DESTINATION !OpenJazz COMPONENT riscos) elseif(PANDORA OR CANOO OR WIZ OR GP2X OR DINGOO OR GAMESHELL) # left blank for the moment (TODO: opk?) elseif(ANDROID) # intentionally disabled elseif(UNIX) include(GNUInstallDirs) install(TARGETS OpenJazz RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) install(FILES res/unix/OpenJazz.png DESTINATION ${CMAKE_INSTALL_DATADIR}/pixmaps) install(FILES res/unix/OpenJazz.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/48x48/apps) install(FILES res/unix/OpenJazz.svg DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps) install(FILES res/unix/OpenJazz.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications) else() message(VERBOSE "No idea how to install for your platform") endif() # debug information if(3DS OR SWITCH OR WII) install(TARGETS OpenJazz RUNTIME DESTINATION . COMPONENT debug) dkp_target_generate_symbol_list(OpenJazz) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/OpenJazz.map ${CMAKE_CURRENT_BINARY_DIR}/OpenJazz.lst DESTINATION . COMPONENT debug) endif() # packaging for distribution set(CPACK_GENERATOR "ZIP") set(CPACK_SOURCE_GENERATOR ";") # disable set(CPACK_ARCHIVE_COMPONENT_INSTALL ON) # split archives set(CPACK_PACKAGE_FILE_NAME "OpenJazz-${PROJECT_VERSION}") set(CPACK_ARCHIVE_DEBUG_FILE_NAME "${CPACK_PACKAGE_FILE_NAME}-debug") set(CPACK_COMPONENT_INCLUDE_TOPLEVEL_DIRECTORY OFF) # we do this manually include(CPack) # manual page find_program(ASCIIDOCTOR_EXECUTABLE asciidoctor) set(MANUAL_STATUS "Unavailable") set(MAN_PATH "res/unix/OpenJazz.6") if(ASCIIDOCTOR_EXECUTABLE) add_custom_command(OUTPUT ${MAN_PATH} COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/res/unix COMMAND ${ASCIIDOCTOR_EXECUTABLE} -a oj_version="${PROJECT_VERSION}" -b manpage -o ${CMAKE_CURRENT_BINARY_DIR}/${MAN_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/${MAN_PATH}.adoc DEPENDS ${MAN_PATH}.adoc COMMENT "(Re-)building manual page" VERBATIM) if(UNIX) add_custom_target(man ALL DEPENDS ${MAN_PATH}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${MAN_PATH} DESTINATION ${CMAKE_INSTALL_MANDIR}/man6) set(MANUAL_STATUS "Generated") else() add_custom_target(man DEPENDS ${MAN_PATH}) set(MANUAL_STATUS "Optional") endif() else() # distribution archive? if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${MAN_PATH}) if(UNIX) install(FILES ${MAN_PATH} DESTINATION ${CMAKE_INSTALL_MANDIR}/man6) endif() set(MANUAL_STATUS "Bundled") endif() endif() unset(MAN_PATH) # doxygen source documentation find_package(Doxygen) if(DOXYGEN_FOUND) set(OJ_VERSION_DOX ${PROJECT_VERSION}) if(OJ_VERSION_GIT) string(APPEND OJ_VERSION_DOX " (${OJ_VERSION_GIT})") endif() configure_file(res/doxygen/Doxyfile.in doc/Doxyfile @ONLY) add_custom_target(doc ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/doc/Doxyfile DEPENDS ${PROJECT_NAME} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/doc COMMENT "Generating source documentation with Doxygen" VERBATIM) endif() # Print short summary message(STATUS "") message(STATUS "OpenJazz") message(STATUS "========") message(STATUS "Version: ${PROJECT_VERSION}") if(GIT_STATUS) message(STATUS "Git status: ${GIT_STATUS}") endif() message(STATUS "Target system: ${OJ_HOST}") message(STATUS "Platform abstraction: ${SDL_STATUS}") if(ROMFS) message(STATUS "RomFS: Embedding directory \"${ROMFS_PATH}\"") endif() message(STATUS "Network: ${NETWORK_STATUS}") message(STATUS "Scaling: ${SCALE_STATUS}") if(DATAPATH) message(STATUS "Additional/System Game Data Path: \"${DATAPATH}\"") endif() message(STATUS "Manual page: ${MANUAL_STATUS}") message(STATUS "Portable Engine: ${PORTABLE_STATUS}") if(DOXYGEN_FOUND) message(STATUS "Source Documentation: doxygen") endif() message(STATUS "") message(STATUS "In case something goes wrong, report bugs to ${OJ_BUGREPORT}") message(STATUS "") AlisterT-openjazz-17e8e2b/CMakePresets.json000066400000000000000000000210511515070551500207420ustar00rootroot00000000000000{ "version": 6, "configurePresets": [ { "name": "base", "displayName": "Comment: base preset, all inherit from it", "hidden": true, "generator": "Ninja", "binaryDir": "${sourceDir}/build-${presetName}", "cacheVariables": { "WANT_CCACHE": "ON" } }, { "name": "type-debug", "displayName": "Comment: build type debug preset", "hidden": true, "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug" } }, { "name": "type-release", "displayName": "Comment: build type release preset", "hidden": true, "cacheVariables": { "CMAKE_BUILD_TYPE": "RelWithDebInfo" } }, { "name": "parent", "inherits": "base", "displayName": "Comment: platform/special preset", "description": "Default build using Ninja generator", "hidden": true }, { "name": "debug", "displayName": "Native (Debug)", "inherits": [ "parent", "type-debug" ] }, { "name": "release", "displayName": "Native (Release)", "inherits": [ "parent", "type-release" ] }, { "name": "asan-parent", "inherits": "base", "displayName": "Comment: platform/special preset", "description": "ASAN build using Ninja generator", "cacheVariables": { "WANT_ASAN": "ON" }, "hidden": true }, { "name": "asan-debug", "displayName": "ASAN (Debug)", "inherits": [ "asan-parent", "type-debug" ] }, { "name": "3ds-parent", "inherits": "base", "displayName": "Comment: platform/special preset", "description": "Homebrew build using Ninja generator", "toolchainFile": "$env{DEVKITPRO}/cmake/3DS.cmake", "hidden": true }, { "name": "3ds-debug", "displayName": "Nintendo 3DS (Debug)", "inherits": [ "3ds-parent", "type-debug" ] }, { "name": "3ds-release", "displayName": "Nintendo 3DS (Release)", "inherits": [ "3ds-parent", "type-release" ] }, { "name": "switch-parent", "inherits": "base", "displayName": "Comment: platform/special preset", "description": "Homebrew build using Ninja generator", "toolchainFile": "$env{DEVKITPRO}/cmake/Switch.cmake", "hidden": true }, { "name": "switch-debug", "displayName": "Nintendo Switch (Debug)", "inherits": [ "switch-parent", "type-debug" ] }, { "name": "switch-release", "displayName": "Nintendo Switch (Release)", "inherits": [ "switch-parent", "type-release" ] }, { "name": "wii-parent", "inherits": "base", "displayName": "Comment: platform/special preset", "description": "Homebrew build using Ninja generator", "toolchainFile": "$env{DEVKITPRO}/cmake/Wii.cmake", "hidden": true }, { "name": "wii-debug", "displayName": "Nintendo Wii (Debug)", "inherits": [ "wii-parent", "type-debug" ] }, { "name": "wii-release", "displayName": "Nintendo Wii (Release)", "inherits": [ "wii-parent", "type-release" ] }, { "name": "psp-parent", "inherits": "base", "displayName": "Comment: platform/special preset", "description": "Homebrew build using Ninja generator", "toolchainFile": "$env{PSPDEV}/psp/share/pspdev.cmake", "hidden": true }, { "name": "psp-debug", "displayName": "Sony PSP (Debug)", "inherits": [ "psp-parent", "type-debug" ] }, { "name": "psp-release", "displayName": "Sony PSP (Release)", "inherits": [ "psp-parent", "type-release" ] } ], "buildPresets": [ { "name": "debug", "configurePreset": "debug" }, { "name": "release", "configurePreset": "release" }, { "name": "asan-debug", "configurePreset": "asan-debug" }, { "name": "3ds-debug", "configurePreset": "3ds-debug" }, { "name": "3ds-release", "configurePreset": "3ds-release" }, { "name": "switch-debug", "configurePreset": "switch-debug" }, { "name": "switch-release", "configurePreset": "switch-release" }, { "name": "wii-debug", "configurePreset": "wii-debug" }, { "name": "wii-release", "configurePreset": "wii-release" }, { "name": "psp-debug", "configurePreset": "psp-debug" }, { "name": "psp-release", "configurePreset": "psp-release" } ], "packagePresets": [ { "name": "debug", "configurePreset": "debug" }, { "name": "release", "configurePreset": "release" }, { "name": "asan-debug", "configurePreset": "asan-debug" }, { "name": "3ds-debug", "configurePreset": "3ds-debug" }, { "name": "3ds-release", "configurePreset": "3ds-release" }, { "name": "switch-debug", "configurePreset": "switch-debug" }, { "name": "switch-release", "configurePreset": "switch-release" }, { "name": "wii-debug", "configurePreset": "wii-debug" }, { "name": "wii-release", "configurePreset": "wii-release" }, { "name": "psp-debug", "configurePreset": "psp-debug" }, { "name": "psp-release", "configurePreset": "psp-release" } ], "workflowPresets": [ { "name": "debug", "steps": [ { "type": "configure", "name": "debug" }, { "type": "build", "name": "debug" } ] }, { "name": "release", "steps": [ { "type": "configure", "name": "release" }, { "type": "build", "name": "release" } ] }, { "name": "asan-debug", "steps": [ { "type": "configure", "name": "asan-debug" }, { "type": "build", "name": "asan-debug" } ] }, { "name": "3ds-debug", "steps": [ { "type": "configure", "name": "3ds-debug" }, { "type": "build", "name": "3ds-debug" }, { "type": "package", "name": "3ds-debug" } ] }, { "name": "3ds-release", "steps": [ { "type": "configure", "name": "3ds-release" }, { "type": "build", "name": "3ds-release" }, { "type": "package", "name": "3ds-release" } ] }, { "name": "switch-debug", "steps": [ { "type": "configure", "name": "switch-debug" }, { "type": "build", "name": "switch-debug" } ] }, { "name": "switch-release", "steps": [ { "type": "configure", "name": "switch-release" }, { "type": "build", "name": "switch-release" } ] }, { "name": "wii-debug", "steps": [ { "type": "configure", "name": "wii-debug" }, { "type": "build", "name": "wii-debug" }, { "type": "package", "name": "wii-debug" } ] }, { "name": "wii-release", "steps": [ { "type": "configure", "name": "wii-release" }, { "type": "build", "name": "wii-release" }, { "type": "package", "name": "wii-release" } ] }, { "name": "psp-debug", "steps": [ { "type": "configure", "name": "psp-debug" }, { "type": "build", "name": "psp-debug" }, { "type": "package", "name": "psp-debug" } ] }, { "name": "psp-release", "steps": [ { "type": "configure", "name": "psp-release" }, { "type": "build", "name": "psp-release" }, { "type": "package", "name": "psp-release" } ] } ] } AlisterT-openjazz-17e8e2b/COPYING000066400000000000000000000432541515070551500165650ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. AlisterT-openjazz-17e8e2b/Makefile000066400000000000000000000052221515070551500171630ustar00rootroot00000000000000# OpenJazz makefile # Sane defaults CXX ?= g++ -std=c++14 CXXFLAGS ?= -g -Wall -O2 DEFINES = -DSCALE -DPORTABLE CPPFLAGS = $(DEFINES) -Isrc -Iext/scale2x -Iext/psmplug -Iext/miniz -Iext/argparse -Iext/stb # Network support CXXFLAGS += -DUSE_SOCKETS ifeq ($(OS),Windows_NT) # Only needed under Windows. LIBS += -lws2_32 endif # SDL1.2 only CXXFLAGS += $(shell sdl-config --cflags) -DOJ_SDL1 LIBS += $(shell sdl-config --libs) LIBS += -lm # Libraries OJEXTLIBOBJ = \ ext/argparse/argparse.o \ ext/miniz/miniz.o \ ext/stb/stb_rect_pack.o \ ext/psmplug/fastmix.o \ ext/psmplug/load_psm.o \ ext/psmplug/psmplug.o \ ext/psmplug/snd_dsp.o \ ext/psmplug/snd_flt.o \ ext/psmplug/snd_fx.o \ ext/psmplug/sndfile.o \ ext/psmplug/sndmix.o \ ext/scale2x/scale2x.o \ ext/scale2x/scale3x.o \ ext/scale2x/scalebit.o # Main engine OJOBJS = \ src/game/clientgame.o \ src/game/game.o \ src/game/gamemode.o \ src/game/localgame.o \ src/game/servergame.o \ src/io/controls.o \ src/io/file.o \ src/io/log.o \ src/io/gfx/anim.o \ src/io/gfx/font.o \ src/io/gfx/paletteeffects.o \ src/io/gfx/sprite.o \ src/io/gfx/video.o \ src/io/network.o \ src/io/sound.o \ src/level/level.o \ src/level/movable.o \ src/main.o \ src/menu/filemenu.o \ src/menu/gamemenu.o \ src/menu/mainmenu.o \ src/menu/menu.o \ src/menu/plasma.o \ src/menu/setupmenu.o \ src/player/player.o \ src/platforms/platform_interface.o \ src/setup.o \ src/util.o \ src/version.o # Episode 1 OJ1OBJS = \ src/jj1/jj1episodeutils.o \ src/jj1/bonuslevel/jj1bonuslevel.o \ src/jj1/bonuslevel/jj1bonuslevelplayer.o \ src/jj1/level/event/jj1bridge.o \ src/jj1/level/event/jj1event.o \ src/jj1/level/event/jj1guardians.o \ src/jj1/level/event/jj1standardevent.o \ src/jj1/level/jj1bird.o \ src/jj1/level/jj1bullet.o \ src/jj1/level/jj1demolevel.o \ src/jj1/level/jj1level.o \ src/jj1/level/jj1levelframe.o \ src/jj1/level/jj1levelload.o \ src/jj1/level/jj1levelplayer.o \ src/jj1/level/jj1levelplayerframe.o \ src/jj1/planet/jj1planet.o \ src/jj1/save/jj1save.o \ src/jj1/scene/jj1scene.o \ src/jj1/scene/jj1sceneload.o OBJS = $(OJEXTLIBOBJ) $(OJOBJS) $(OJ1OBJS) # Stamp DEPR = .mk-stamp-depr # Build rules .PHONY: clean OpenJazz: $(DEPR) $(OBJS) @-echo [LD] $@ @$(CXX) -o OpenJazz $(LDFLAGS) $(OBJS) $(LIBS) $(DEPR): @-echo "[WARNING] This Makefile is deprecated! Please use CMake, if possible." @-echo " The old, unmaintained SDL1.2 library is used." @-echo " Report problems at https://github.com/AlisterT/openjazz/issues" @touch $(DEPR) %.o: %.cpp @-echo [CXX] $< @$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@ clean: @-echo Cleaning... @rm -f OpenJazz $(OBJS) $(DEPR) AlisterT-openjazz-17e8e2b/README.md000066400000000000000000000064571515070551500170150ustar00rootroot00000000000000 # ![OJ Logo][logo] OpenJazz ## About *OpenJazz* is a free, open-source version of the classic Jazz Jackrabbit™ games. OpenJazz can be compiled on a wide range of operating systems, including Windows, macOS, GNU/Linux and BSD flavors. Also ports are available for some homebrew platforms, consoles and handhelds. See [Platforms][platforms] for details. You can even play it [inside your web browser][web port]. To play, you will need the files from one of the original games. With the demise of DOS-based operating systems, it has become necessary to use emulators to play old DOS games. Jazz Jackrabbit™ deserves more - and would benefit greatly from new features. *Jazz Jackrabbit™* is a PC platform game. Produced by Epic Games (then Epic MegaGames), it was first released in 1994. The fast-paced, colourful gameplay proved popular, and the game won PC Format's Arcade Game of the Year award. Many people still fondly recall the shareware versions. ## History OpenJazz was started on the 23rd of August, 2005, by AJ Thomson. Academic pressures put the project on hold until late December 2005. The source code was released on the 25th, and the first version with a degree of playability was released on the 15th of January, 2006. Since then, a variety of ports have been released by other people. A lot of them have since been merged back into the main code base. More academic pressures meant there were few updates over the following few years, but in 2009 a multiplayer version was released. After that, more eye candy features have made it into the engine. These are features not directly involving the levels. However, without bonus levels, movie playback and the menu plasma effect, OpenJazz would be incomplete. In 2014, the project has been moved to GitHub to allow others to easily propose changes, report bugs and also fix them. This also lead to more ports being made. Nowadays, development has slowed down. The original author is involved with other projects and there is no clear roadmap for new features, besides striving for a "true" and faithful Jazz Jackrabbit™ adaption on all possible platforms. GitHub user [carstene1ns][carstene1ns] has cleaned and restructured parts of the code base, integrated some ports and tries to resolve bug reports. ## License OpenJazz is available under the GNU General Public License version 2 or later, see [licenses.txt][licenses] for additional information and other included software. ## [Controls](res/unix/OpenJazz.6.adoc#ingame-controls) (See manual) ## [Building][building] ## Running [![Play in Browser][web badge]][web port] Execute `OpenJazz`. Depending on the platform and compile time options, the data files are expected to be under different paths (alongside the executable works always as a general fallback). You can also specifiy a game folder as command line argument. ## Authors Original author: AJ Thomson (alister_j_t at yahoo dot com) Current Maintainer: Carsten Teibes (dev f4ke de) [Additional Authors](res/unix/OpenJazz.6.adoc#authors) (See manual) ## Homepage http://alister.eu/jazz/oj/ [logo]: res/unix/OpenJazz.png [carstene1ns]: https://github.com/carstene1ns [platforms]: doc/PLATFORMS.md [licenses]: doc/licenses.txt [building]: doc/BUILDING.md [web badge]: https://img.shields.io/badge/Play_in-Browser-blue?style=plastic [web port]: https://openjazz.github.io AlisterT-openjazz-17e8e2b/builds/000077500000000000000000000000001515070551500170045ustar00rootroot00000000000000AlisterT-openjazz-17e8e2b/builds/android/000077500000000000000000000000001515070551500204245ustar00rootroot00000000000000AlisterT-openjazz-17e8e2b/builds/android/app/000077500000000000000000000000001515070551500212045ustar00rootroot00000000000000AlisterT-openjazz-17e8e2b/builds/android/app/build.gradle000066400000000000000000000022111515070551500234570ustar00rootroot00000000000000apply plugin: 'com.android.application' android { namespace "eu.alister.openjazz" compileSdkVersion 35 defaultConfig { minSdkVersion 24 targetSdkVersion 35 versionCode project.properties["app.version_code"].toInteger() versionName project.properties["app.version_name"] externalNativeBuild { cmake { arguments "-DANDROID_APP_PLATFORM=android-24", "-DANDROID_STL=c++_static" abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86_64' } } } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } applicationVariants.all { variant -> tasks["merge${variant.name.capitalize()}Assets"] .dependsOn("externalNativeBuild${variant.name.capitalize()}") } externalNativeBuild { cmake { path '../../../CMakeLists.txt' version '3.27.9+' } } lint { abortOnError false } } dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') } AlisterT-openjazz-17e8e2b/builds/android/app/proguard-rules.pro000066400000000000000000000070341515070551500247050ustar00rootroot00000000000000# Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in [sdk]/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: -keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLInputConnection { void nativeCommitText(java.lang.String, int); void nativeGenerateScancodeForUnichar(char); } -keep,includedescriptorclasses class org.libsdl.app.SDLActivity { # for some reason these aren't compatible with allowoptimization modifier boolean supportsRelativeMouse(); void setWindowStyle(boolean); } -keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLActivity { java.lang.String nativeGetHint(java.lang.String); # Java-side doesn't use this, so it gets minified, but C-side still tries to register it boolean onNativeSoftReturnKey(); void onNativeKeyboardFocusLost(); boolean isScreenKeyboardShown(); android.util.DisplayMetrics getDisplayDPI(); java.lang.String clipboardGetText(); boolean clipboardHasText(); void clipboardSetText(java.lang.String); int createCustomCursor(int[], int, int, int, int); void destroyCustomCursor(int); android.content.Context getContext(); boolean getManifestEnvironmentVariables(); android.view.Surface getNativeSurface(); void initTouch(); boolean isAndroidTV(); boolean isChromebook(); boolean isDeXMode(); boolean isTablet(); void manualBackButton(); int messageboxShowMessageBox(int, java.lang.String, java.lang.String, int[], int[], java.lang.String[], int[]); void minimizeWindow(); int openURL(java.lang.String); void requestPermission(java.lang.String, int); int showToast(java.lang.String, int, int, int, int); boolean sendMessage(int, int); boolean setActivityTitle(java.lang.String); boolean setCustomCursor(int); void setOrientation(int, int, boolean, java.lang.String); boolean setRelativeMouseEnabled(boolean); boolean setSystemCursor(int); boolean shouldMinimizeOnFocusLoss(); boolean showTextInput(int, int, int, int); } -keep,includedescriptorclasses,allowoptimization class org.libsdl.app.HIDDeviceManager { boolean initialize(boolean, boolean); boolean openDevice(int); int sendOutputReport(int, byte[]); int sendFeatureReport(int, byte[]); boolean getFeatureReport(int, byte[]); void closeDevice(int); } -keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLAudioManager { int[] getAudioOutputDevices(); int[] getAudioInputDevices(); int[] audioOpen(int, int, int, int, int); void audioWriteFloatBuffer(float[]); void audioWriteShortBuffer(short[]); void audioWriteByteBuffer(byte[]); void audioClose(); int[] captureOpen(int, int, int, int, int); int captureReadFloatBuffer(float[], boolean); int captureReadShortBuffer(short[], boolean); int captureReadByteBuffer(byte[], boolean); void captureClose(); void audioSetThreadPriority(boolean, int); native int nativeSetupJNI(); native void removeAudioDevice(boolean, int); native void addAudioDevice(boolean, int); } -keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLControllerManager { void pollInputDevices(); void pollHapticDevices(); void hapticRun(int, float, int); void hapticStop(int); } AlisterT-openjazz-17e8e2b/builds/android/app/src/000077500000000000000000000000001515070551500217735ustar00rootroot00000000000000AlisterT-openjazz-17e8e2b/builds/android/app/src/main/000077500000000000000000000000001515070551500227175ustar00rootroot00000000000000AlisterT-openjazz-17e8e2b/builds/android/app/src/main/AndroidManifest.xml000066400000000000000000000064171515070551500265200ustar00rootroot00000000000000 AlisterT-openjazz-17e8e2b/builds/android/app/src/main/java/000077500000000000000000000000001515070551500236405ustar00rootroot00000000000000AlisterT-openjazz-17e8e2b/builds/android/app/src/main/java/eu/000077500000000000000000000000001515070551500242515ustar00rootroot00000000000000AlisterT-openjazz-17e8e2b/builds/android/app/src/main/java/eu/alister/000077500000000000000000000000001515070551500257145ustar00rootroot00000000000000AlisterT-openjazz-17e8e2b/builds/android/app/src/main/java/eu/alister/openjazz/000077500000000000000000000000001515070551500275545ustar00rootroot00000000000000AlisterT-openjazz-17e8e2b/builds/android/app/src/main/java/eu/alister/openjazz/OpenJazz.java000066400000000000000000000004031515070551500321540ustar00rootroot00000000000000package eu.alister.openjazz; import org.libsdl.app.SDLActivity; /** * Wrapper class over SDLActivity */ public class OpenJazz extends SDLActivity { @Override protected String[] getLibraries() { return new String[]{ "SDL2", "OpenJazz" }; } } AlisterT-openjazz-17e8e2b/builds/android/app/src/main/java/org/000077500000000000000000000000001515070551500244275ustar00rootroot00000000000000AlisterT-openjazz-17e8e2b/builds/android/app/src/main/java/org/libsdl/000077500000000000000000000000001515070551500257005ustar00rootroot00000000000000AlisterT-openjazz-17e8e2b/builds/android/app/src/main/java/org/libsdl/app/000077500000000000000000000000001515070551500264605ustar00rootroot00000000000000AlisterT-openjazz-17e8e2b/builds/android/app/src/main/java/org/libsdl/app/HIDDevice.java000066400000000000000000000011571515070551500310530ustar00rootroot00000000000000package 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(); } HIDDeviceBLESteamController.java000066400000000000000000000571501515070551500343610ustar00rootroot00000000000000AlisterT-openjazz-17e8e2b/builds/android/app/src/main/java/org/libsdl/apppackage 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 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(); 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(); } } AlisterT-openjazz-17e8e2b/builds/android/app/src/main/java/org/libsdl/app/HIDDeviceManager.java000066400000000000000000000657141515070551500323570ustar00rootroot00000000000000package 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 mDevicesById = new HashMap(); private HashMap mBluetoothDevices = new HashMap(); private int mNextDeviceId = 0; private SharedPreferences mSharedPreferences = null; private boolean mIsChromebook = false; private UsbManager mUsbManager; private Handler mHandler; private BluetoothManager mBluetoothManager; private List 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 devices = new ArrayList(); 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(); // 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 disconnected = new ArrayList(); ArrayList connected = new ArrayList(); List 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); } AlisterT-openjazz-17e8e2b/builds/android/app/src/main/java/org/libsdl/app/HIDDeviceUSB.java000066400000000000000000000216201515070551500314220ustar00rootroot00000000000000package 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); } } } } } } AlisterT-openjazz-17e8e2b/builds/android/app/src/main/java/org/libsdl/app/SDL.java000066400000000000000000000066431515070551500277560ustar00rootroot00000000000000package 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; } AlisterT-openjazz-17e8e2b/builds/android/app/src/main/java/org/libsdl/app/SDLActivity.java000066400000000000000000002253711515070551500314740ustar00rootroot00000000000000package org.libsdl.app; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.app.UiModeManager; import android.content.ClipboardManager; import android.content.ClipData; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; import android.hardware.Sensor; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.text.Editable; import android.text.InputType; import android.text.Selection; import android.util.DisplayMetrics; import android.util.Log; import android.util.SparseArray; import android.view.Display; import android.view.Gravity; import android.view.InputDevice; import android.view.KeyEvent; import android.view.PointerIcon; import android.view.Surface; import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; import android.view.inputmethod.BaseInputConnection; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; import android.widget.Button; import android.widget.EditText; import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; import java.util.Hashtable; import java.util.Locale; /** SDL Activity */ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityChangeListener { private static final String TAG = "SDL"; private static final int SDL_MAJOR_VERSION = 2; private static final int SDL_MINOR_VERSION = 32; private static final int SDL_MICRO_VERSION = 10; /* // Display InputType.SOURCE/CLASS of events and devices // // SDLActivity.debugSource(device.getSources(), "device[" + device.getName() + "]"); // SDLActivity.debugSource(event.getSource(), "event"); public static void debugSource(int sources, String prefix) { int s = sources; int s_copy = sources; String cls = ""; String src = ""; int tst = 0; int FLAG_TAINTED = 0x80000000; if ((s & InputDevice.SOURCE_CLASS_BUTTON) != 0) cls += " BUTTON"; if ((s & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) cls += " JOYSTICK"; if ((s & InputDevice.SOURCE_CLASS_POINTER) != 0) cls += " POINTER"; if ((s & InputDevice.SOURCE_CLASS_POSITION) != 0) cls += " POSITION"; if ((s & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) cls += " TRACKBALL"; int s2 = s_copy & ~InputDevice.SOURCE_ANY; // keep class bits s2 &= ~( InputDevice.SOURCE_CLASS_BUTTON | InputDevice.SOURCE_CLASS_JOYSTICK | InputDevice.SOURCE_CLASS_POINTER | InputDevice.SOURCE_CLASS_POSITION | InputDevice.SOURCE_CLASS_TRACKBALL); if (s2 != 0) cls += "Some_Unknown"; s2 = s_copy & InputDevice.SOURCE_ANY; // keep source only, no class; if (Build.VERSION.SDK_INT >= 23) { tst = InputDevice.SOURCE_BLUETOOTH_STYLUS; if ((s & tst) == tst) src += " BLUETOOTH_STYLUS"; s2 &= ~tst; } tst = InputDevice.SOURCE_DPAD; if ((s & tst) == tst) src += " DPAD"; s2 &= ~tst; tst = InputDevice.SOURCE_GAMEPAD; if ((s & tst) == tst) src += " GAMEPAD"; s2 &= ~tst; if (Build.VERSION.SDK_INT >= 21) { tst = InputDevice.SOURCE_HDMI; if ((s & tst) == tst) src += " HDMI"; s2 &= ~tst; } tst = InputDevice.SOURCE_JOYSTICK; if ((s & tst) == tst) src += " JOYSTICK"; s2 &= ~tst; tst = InputDevice.SOURCE_KEYBOARD; if ((s & tst) == tst) src += " KEYBOARD"; s2 &= ~tst; tst = InputDevice.SOURCE_MOUSE; if ((s & tst) == tst) src += " MOUSE"; s2 &= ~tst; if (Build.VERSION.SDK_INT >= 26) { tst = InputDevice.SOURCE_MOUSE_RELATIVE; if ((s & tst) == tst) src += " MOUSE_RELATIVE"; s2 &= ~tst; tst = InputDevice.SOURCE_ROTARY_ENCODER; if ((s & tst) == tst) src += " ROTARY_ENCODER"; s2 &= ~tst; } tst = InputDevice.SOURCE_STYLUS; if ((s & tst) == tst) src += " STYLUS"; s2 &= ~tst; tst = InputDevice.SOURCE_TOUCHPAD; if ((s & tst) == tst) src += " TOUCHPAD"; s2 &= ~tst; tst = InputDevice.SOURCE_TOUCHSCREEN; if ((s & tst) == tst) src += " TOUCHSCREEN"; s2 &= ~tst; if (Build.VERSION.SDK_INT >= 18) { tst = InputDevice.SOURCE_TOUCH_NAVIGATION; if ((s & tst) == tst) src += " TOUCH_NAVIGATION"; s2 &= ~tst; } tst = InputDevice.SOURCE_TRACKBALL; if ((s & tst) == tst) src += " TRACKBALL"; s2 &= ~tst; tst = InputDevice.SOURCE_ANY; if ((s & tst) == tst) src += " ANY"; s2 &= ~tst; if (s == FLAG_TAINTED) src += " FLAG_TAINTED"; s2 &= ~FLAG_TAINTED; if (s2 != 0) src += " Some_Unknown"; Log.v(TAG, prefix + "int=" + s_copy + " CLASS={" + cls + " } source(s):" + src); } */ public static boolean mIsResumedCalled, mHasFocus; public static final boolean mHasMultiWindow = (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */); // Cursor types // private static final int SDL_SYSTEM_CURSOR_NONE = -1; private static final int SDL_SYSTEM_CURSOR_ARROW = 0; private static final int SDL_SYSTEM_CURSOR_IBEAM = 1; private static final int SDL_SYSTEM_CURSOR_WAIT = 2; private static final int SDL_SYSTEM_CURSOR_CROSSHAIR = 3; private static final int SDL_SYSTEM_CURSOR_WAITARROW = 4; private static final int SDL_SYSTEM_CURSOR_SIZENWSE = 5; private static final int SDL_SYSTEM_CURSOR_SIZENESW = 6; private static final int SDL_SYSTEM_CURSOR_SIZEWE = 7; private static final int SDL_SYSTEM_CURSOR_SIZENS = 8; private static final int SDL_SYSTEM_CURSOR_SIZEALL = 9; private static final int SDL_SYSTEM_CURSOR_NO = 10; private static final int SDL_SYSTEM_CURSOR_HAND = 11; protected static final int SDL_ORIENTATION_UNKNOWN = 0; protected static final int SDL_ORIENTATION_LANDSCAPE = 1; protected static final int SDL_ORIENTATION_LANDSCAPE_FLIPPED = 2; protected static final int SDL_ORIENTATION_PORTRAIT = 3; protected static final int SDL_ORIENTATION_PORTRAIT_FLIPPED = 4; protected static int mCurrentOrientation; protected static Locale mCurrentLocale; // Handle the state of the native layer public enum NativeState { INIT, RESUMED, PAUSED } public static NativeState mNextNativeState; public static NativeState mCurrentNativeState; /** If shared libraries (e.g. SDL or the native application) could not be loaded. */ public static boolean mBrokenLibraries = true; // Main components protected static SDLActivity mSingleton; protected static SDLSurface mSurface; protected static DummyEdit mTextEdit; protected static boolean mScreenKeyboardShown; protected static ViewGroup mLayout; protected static SDLClipboardHandler mClipboardHandler; protected static Hashtable mCursors; protected static int mLastCursorID; protected static SDLGenericMotionListener_API12 mMotionListener; protected static HIDDeviceManager mHIDDeviceManager; // This is what SDL runs in. It invokes SDL_main(), eventually protected static Thread mSDLThread; protected static SDLGenericMotionListener_API12 getMotionListener() { if (mMotionListener == null) { if (Build.VERSION.SDK_INT >= 26 /* Android 8.0 (O) */) { mMotionListener = new SDLGenericMotionListener_API26(); } else if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) { mMotionListener = new SDLGenericMotionListener_API24(); } else { mMotionListener = new SDLGenericMotionListener_API12(); } } return mMotionListener; } /** * This method returns the name of the shared object with the application entry point * It can be overridden by derived classes. */ protected String getMainSharedObject() { String library; String[] libraries = SDLActivity.mSingleton.getLibraries(); if (libraries.length > 0) { library = "lib" + libraries[libraries.length - 1] + ".so"; } else { library = "libmain.so"; } return getContext().getApplicationInfo().nativeLibraryDir + "/" + library; } /** * This method returns the name of the application entry point * It can be overridden by derived classes. */ protected String getMainFunction() { return "SDL_main"; } /** * This method is called by SDL before loading the native shared libraries. * It can be overridden to provide names of shared libraries to be loaded. * The default implementation returns the defaults. It never returns null. * An array returned by a new implementation must at least contain "SDL2". * Also keep in mind that the order the libraries are loaded may matter. * @return names of shared libraries to be loaded (e.g. "SDL2", "main"). */ protected String[] getLibraries() { return new String[] { "SDL2", // "SDL2_image", // "SDL2_mixer", // "SDL2_net", // "SDL2_ttf", "main" }; } // Load the .so public void loadLibraries() { for (String lib : getLibraries()) { SDL.loadLibrary(lib, this); } } /** * This method is called by SDL before starting the native application thread. * It can be overridden to provide the arguments after the application name. * The default implementation returns an empty array. It never returns null. * @return arguments for the native application. */ protected String[] getArguments() { return new String[0]; } public static void initialize() { // The static nature of the singleton and Android quirkyness force us to initialize everything here // Otherwise, when exiting the app and returning to it, these variables *keep* their pre exit values mSingleton = null; mSurface = null; mTextEdit = null; mLayout = null; mClipboardHandler = null; mCursors = new Hashtable(); mLastCursorID = 0; mSDLThread = null; mIsResumedCalled = false; mHasFocus = true; mNextNativeState = NativeState.INIT; mCurrentNativeState = NativeState.INIT; } protected SDLSurface createSDLSurface(Context context) { return new SDLSurface(context); } // Setup @Override protected void onCreate(Bundle savedInstanceState) { Log.v(TAG, "Device: " + Build.DEVICE); Log.v(TAG, "Model: " + Build.MODEL); Log.v(TAG, "onCreate()"); super.onCreate(savedInstanceState); try { Thread.currentThread().setName("SDLActivity"); } catch (Exception e) { Log.v(TAG, "modify thread properties failed " + e.toString()); } // Load shared libraries String errorMsgBrokenLib = ""; try { loadLibraries(); mBrokenLibraries = false; /* success */ } catch(UnsatisfiedLinkError e) { System.err.println(e.getMessage()); mBrokenLibraries = true; errorMsgBrokenLib = e.getMessage(); } catch(Exception e) { System.err.println(e.getMessage()); mBrokenLibraries = true; errorMsgBrokenLib = e.getMessage(); } if (!mBrokenLibraries) { String expected_version = String.valueOf(SDL_MAJOR_VERSION) + "." + String.valueOf(SDL_MINOR_VERSION) + "." + String.valueOf(SDL_MICRO_VERSION); String version = nativeGetVersion(); if (!version.equals(expected_version)) { mBrokenLibraries = true; errorMsgBrokenLib = "SDL C/Java version mismatch (expected " + expected_version + ", got " + version + ")"; } } if (mBrokenLibraries) { mSingleton = this; AlertDialog.Builder dlgAlert = new AlertDialog.Builder(this); dlgAlert.setMessage("An error occurred while trying to start the application. Please try again and/or reinstall." + System.getProperty("line.separator") + System.getProperty("line.separator") + "Error: " + errorMsgBrokenLib); dlgAlert.setTitle("SDL Error"); dlgAlert.setPositiveButton("Exit", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog,int id) { // if this button is clicked, close current activity SDLActivity.mSingleton.finish(); } }); dlgAlert.setCancelable(false); dlgAlert.create().show(); return; } // Set up JNI SDL.setupJNI(); // Initialize state SDL.initialize(); // So we can call stuff from static callbacks mSingleton = this; SDL.setContext(this); mClipboardHandler = new SDLClipboardHandler(); mHIDDeviceManager = HIDDeviceManager.acquire(this); // Set up the surface mSurface = createSDLSurface(this); mLayout = new RelativeLayout(this); mLayout.addView(mSurface); // Get our current screen orientation and pass it down. mCurrentOrientation = SDLActivity.getCurrentOrientation(); // Only record current orientation SDLActivity.onNativeOrientationChanged(mCurrentOrientation); try { if (Build.VERSION.SDK_INT < 24 /* Android 7.0 (N) */) { mCurrentLocale = getContext().getResources().getConfiguration().locale; } else { mCurrentLocale = getContext().getResources().getConfiguration().getLocales().get(0); } } catch(Exception ignored) { } setContentView(mLayout); setWindowStyle(false); getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(this); // Get filename from "Open with" of another application Intent intent = getIntent(); if (intent != null && intent.getData() != null) { String filename = intent.getData().getPath(); if (filename != null) { Log.v(TAG, "Got filename: " + filename); SDLActivity.onNativeDropFile(filename); } } } protected void pauseNativeThread() { mNextNativeState = NativeState.PAUSED; mIsResumedCalled = false; if (SDLActivity.mBrokenLibraries) { return; } SDLActivity.handleNativeState(); } protected void resumeNativeThread() { mNextNativeState = NativeState.RESUMED; mIsResumedCalled = true; if (SDLActivity.mBrokenLibraries) { return; } SDLActivity.handleNativeState(); } // Events @Override protected void onPause() { Log.v(TAG, "onPause()"); super.onPause(); if (mHIDDeviceManager != null) { mHIDDeviceManager.setFrozen(true); } if (!mHasMultiWindow) { pauseNativeThread(); } } @Override protected void onResume() { Log.v(TAG, "onResume()"); super.onResume(); if (mHIDDeviceManager != null) { mHIDDeviceManager.setFrozen(false); } if (!mHasMultiWindow) { resumeNativeThread(); } } @Override protected void onStop() { Log.v(TAG, "onStop()"); super.onStop(); if (mHasMultiWindow) { pauseNativeThread(); } } @Override protected void onStart() { Log.v(TAG, "onStart()"); super.onStart(); if (mHasMultiWindow) { resumeNativeThread(); } } public static int getCurrentOrientation() { int result = SDL_ORIENTATION_UNKNOWN; Activity activity = (Activity)getContext(); if (activity == null) { return result; } Display display = activity.getWindowManager().getDefaultDisplay(); switch (display.getRotation()) { case Surface.ROTATION_0: result = SDL_ORIENTATION_PORTRAIT; break; case Surface.ROTATION_90: result = SDL_ORIENTATION_LANDSCAPE; break; case Surface.ROTATION_180: result = SDL_ORIENTATION_PORTRAIT_FLIPPED; break; case Surface.ROTATION_270: result = SDL_ORIENTATION_LANDSCAPE_FLIPPED; break; } return result; } @Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); Log.v(TAG, "onWindowFocusChanged(): " + hasFocus); if (SDLActivity.mBrokenLibraries) { return; } mHasFocus = hasFocus; if (hasFocus) { mNextNativeState = NativeState.RESUMED; SDLActivity.getMotionListener().reclaimRelativeMouseModeIfNeeded(); SDLActivity.handleNativeState(); nativeFocusChanged(true); } else { nativeFocusChanged(false); if (!mHasMultiWindow) { mNextNativeState = NativeState.PAUSED; SDLActivity.handleNativeState(); } } } @Override public void onLowMemory() { Log.v(TAG, "onLowMemory()"); super.onLowMemory(); if (SDLActivity.mBrokenLibraries) { return; } SDLActivity.nativeLowMemory(); } @Override public void onConfigurationChanged(Configuration newConfig) { Log.v(TAG, "onConfigurationChanged()"); super.onConfigurationChanged(newConfig); if (SDLActivity.mBrokenLibraries) { return; } if (mCurrentLocale == null || !mCurrentLocale.equals(newConfig.locale)) { mCurrentLocale = newConfig.locale; SDLActivity.onNativeLocaleChanged(); } } @Override protected void onDestroy() { Log.v(TAG, "onDestroy()"); if (mHIDDeviceManager != null) { HIDDeviceManager.release(mHIDDeviceManager); mHIDDeviceManager = null; } SDLAudioManager.release(this); if (SDLActivity.mBrokenLibraries) { super.onDestroy(); return; } if (SDLActivity.mSDLThread != null) { // Send Quit event to "SDLThread" thread SDLActivity.nativeSendQuit(); // Wait for "SDLThread" thread to end try { SDLActivity.mSDLThread.join(); } catch(Exception e) { Log.v(TAG, "Problem stopping SDLThread: " + e); } } SDLActivity.nativeQuit(); super.onDestroy(); } @Override public void onBackPressed() { // Check if we want to block the back button in case of mouse right click. // // If we do, the normal hardware back button will no longer work and people have to use home, // but the mouse right click will work. // boolean trapBack = SDLActivity.nativeGetHintBoolean("SDL_ANDROID_TRAP_BACK_BUTTON", false); if (trapBack) { // Exit and let the mouse handler handle this button (if appropriate) return; } // Default system back button behavior. if (!isFinishing()) { super.onBackPressed(); } } // Called by JNI from SDL. public static void manualBackButton() { mSingleton.pressBackButton(); } // Used to get us onto the activity's main thread public void pressBackButton() { runOnUiThread(new Runnable() { @Override public void run() { if (!SDLActivity.this.isFinishing()) { SDLActivity.this.superOnBackPressed(); } } }); } // Used to access the system back behavior. public void superOnBackPressed() { super.onBackPressed(); } @Override public boolean dispatchKeyEvent(KeyEvent event) { if (SDLActivity.mBrokenLibraries) { return false; } int keyCode = event.getKeyCode(); // Ignore certain special keys so they're handled by Android if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_CAMERA || keyCode == KeyEvent.KEYCODE_ZOOM_IN || /* API 11 */ keyCode == KeyEvent.KEYCODE_ZOOM_OUT /* API 11 */ ) { return false; } return super.dispatchKeyEvent(event); } /* Transition to next state */ public static void handleNativeState() { if (mNextNativeState == mCurrentNativeState) { // Already in same state, discard. return; } // Try a transition to init state if (mNextNativeState == NativeState.INIT) { mCurrentNativeState = mNextNativeState; return; } // Try a transition to paused state if (mNextNativeState == NativeState.PAUSED) { if (mSDLThread != null) { nativePause(); } if (mSurface != null) { mSurface.handlePause(); } mCurrentNativeState = mNextNativeState; return; } // Try a transition to resumed state if (mNextNativeState == NativeState.RESUMED) { if (mSurface.mIsSurfaceReady && mHasFocus && mIsResumedCalled) { if (mSDLThread == null) { // This is the entry point to the C app. // Start up the C app thread and enable sensor input for the first time // FIXME: Why aren't we enabling sensor input at start? mSDLThread = new Thread(new SDLMain(), "SDLThread"); mSurface.enableSensor(Sensor.TYPE_ACCELEROMETER, true); mSDLThread.start(); // No nativeResume(), don't signal Android_ResumeSem } else { nativeResume(); } mSurface.handleResume(); mCurrentNativeState = mNextNativeState; } } } // Messages from the SDLMain thread static final int COMMAND_CHANGE_TITLE = 1; static final int COMMAND_CHANGE_WINDOW_STYLE = 2; static final int COMMAND_TEXTEDIT_HIDE = 3; static final int COMMAND_SET_KEEP_SCREEN_ON = 5; protected static final int COMMAND_USER = 0x8000; protected static boolean mFullscreenModeActive; /** * This method is called by SDL if SDL did not handle a message itself. * This happens if a received message contains an unsupported command. * Method can be overwritten to handle Messages in a different class. * @param command the command of the message. * @param param the parameter of the message. May be null. * @return if the message was handled in overridden method. */ protected boolean onUnhandledMessage(int command, Object param) { return false; } /** * A Handler class for Messages from native SDL applications. * It uses current Activities as target (e.g. for the title). * static to prevent implicit references to enclosing object. */ protected static class SDLCommandHandler extends Handler { @Override public void handleMessage(Message msg) { Context context = SDL.getContext(); if (context == null) { Log.e(TAG, "error handling message, getContext() returned null"); return; } switch (msg.arg1) { case COMMAND_CHANGE_TITLE: if (context instanceof Activity) { ((Activity) context).setTitle((String)msg.obj); } else { Log.e(TAG, "error handling message, getContext() returned no Activity"); } break; case COMMAND_CHANGE_WINDOW_STYLE: if (Build.VERSION.SDK_INT >= 19 /* Android 4.4 (KITKAT) */) { if (context instanceof Activity) { Window window = ((Activity) context).getWindow(); if (window != null) { if ((msg.obj instanceof Integer) && ((Integer) msg.obj != 0)) { int flags = View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.INVISIBLE; window.getDecorView().setSystemUiVisibility(flags); window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); window.clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); SDLActivity.mFullscreenModeActive = true; } else { int flags = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_VISIBLE; window.getDecorView().setSystemUiVisibility(flags); window.addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); SDLActivity.mFullscreenModeActive = false; } if (Build.VERSION.SDK_INT >= 28 /* Android 9 (Pie) */) { window.getAttributes().layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; } } } else { Log.e(TAG, "error handling message, getContext() returned no Activity"); } } break; case COMMAND_TEXTEDIT_HIDE: if (mTextEdit != null) { // Note: On some devices setting view to GONE creates a flicker in landscape. // Setting the View's sizes to 0 is similar to GONE but without the flicker. // The sizes will be set to useful values when the keyboard is shown again. mTextEdit.setLayoutParams(new RelativeLayout.LayoutParams(0, 0)); InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(mTextEdit.getWindowToken(), 0); mScreenKeyboardShown = false; mSurface.requestFocus(); } break; case COMMAND_SET_KEEP_SCREEN_ON: { if (context instanceof Activity) { Window window = ((Activity) context).getWindow(); if (window != null) { if ((msg.obj instanceof Integer) && ((Integer) msg.obj != 0)) { window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } else { window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } } } break; } default: if ((context instanceof SDLActivity) && !((SDLActivity) context).onUnhandledMessage(msg.arg1, msg.obj)) { Log.e(TAG, "error handling message, command is " + msg.arg1); } } } } // Handler for the messages Handler commandHandler = new SDLCommandHandler(); // Send a message from the SDLMain thread boolean sendCommand(int command, Object data) { Message msg = commandHandler.obtainMessage(); msg.arg1 = command; msg.obj = data; boolean result = commandHandler.sendMessage(msg); if (Build.VERSION.SDK_INT >= 19 /* Android 4.4 (KITKAT) */) { if (command == COMMAND_CHANGE_WINDOW_STYLE) { // Ensure we don't return until the resize has actually happened, // or 500ms have passed. boolean bShouldWait = false; if (data instanceof Integer) { // Let's figure out if we're already laid out fullscreen or not. Display display = ((WindowManager) getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); DisplayMetrics realMetrics = new DisplayMetrics(); display.getRealMetrics(realMetrics); boolean bFullscreenLayout = ((realMetrics.widthPixels == mSurface.getWidth()) && (realMetrics.heightPixels == mSurface.getHeight())); if ((Integer) data == 1) { // If we aren't laid out fullscreen or actively in fullscreen mode already, we're going // to change size and should wait for surfaceChanged() before we return, so the size // is right back in native code. If we're already laid out fullscreen, though, we're // not going to change size even if we change decor modes, so we shouldn't wait for // surfaceChanged() -- which may not even happen -- and should return immediately. bShouldWait = !bFullscreenLayout; } else { // If we're laid out fullscreen (even if the status bar and nav bar are present), // or are actively in fullscreen, we're going to change size and should wait for // surfaceChanged before we return, so the size is right back in native code. bShouldWait = bFullscreenLayout; } } if (bShouldWait && (SDLActivity.getContext() != null)) { // We'll wait for the surfaceChanged() method, which will notify us // when called. That way, we know our current size is really the // size we need, instead of grabbing a size that's still got // the navigation and/or status bars before they're hidden. // // We'll wait for up to half a second, because some devices // take a surprisingly long time for the surface resize, but // then we'll just give up and return. // synchronized (SDLActivity.getContext()) { try { SDLActivity.getContext().wait(500); } catch (InterruptedException ie) { ie.printStackTrace(); } } } } } return result; } // C functions we call public static native String nativeGetVersion(); public static native int nativeSetupJNI(); public static native int nativeRunMain(String library, String function, Object arguments); public static native void nativeLowMemory(); public static native void nativeSendQuit(); public static native void nativeQuit(); public static native void nativePause(); public static native void nativeResume(); public static native void nativeFocusChanged(boolean hasFocus); public static native void onNativeDropFile(String filename); public static native void nativeSetScreenResolution(int surfaceWidth, int surfaceHeight, int deviceWidth, int deviceHeight, float rate); public static native void onNativeResize(); public static native void onNativeKeyDown(int keycode); public static native void onNativeKeyUp(int keycode); public static native boolean onNativeSoftReturnKey(); public static native void onNativeKeyboardFocusLost(); public static native void onNativeMouse(int button, int action, float x, float y, boolean relative); public static native void onNativeTouch(int touchDevId, int pointerFingerId, int action, float x, float y, float p); public static native void onNativeAccel(float x, float y, float z); public static native void onNativeClipboardChanged(); public static native void onNativeSurfaceCreated(); public static native void onNativeSurfaceChanged(); public static native void onNativeSurfaceDestroyed(); public static native String nativeGetHint(String name); public static native boolean nativeGetHintBoolean(String name, boolean default_value); public static native void nativeSetenv(String name, String value); public static native void onNativeOrientationChanged(int orientation); public static native void nativeAddTouch(int touchId, String name); public static native void nativePermissionResult(int requestCode, boolean result); public static native void onNativeLocaleChanged(); /** * This method is called by SDL using JNI. */ public static boolean setActivityTitle(String title) { // Called from SDLMain() thread and can't directly affect the view return mSingleton.sendCommand(COMMAND_CHANGE_TITLE, title); } /** * This method is called by SDL using JNI. */ public static void setWindowStyle(boolean fullscreen) { // Called from SDLMain() thread and can't directly affect the view mSingleton.sendCommand(COMMAND_CHANGE_WINDOW_STYLE, fullscreen ? 1 : 0); } /** * This method is called by SDL using JNI. * This is a static method for JNI convenience, it calls a non-static method * so that is can be overridden */ public static void setOrientation(int w, int h, boolean resizable, String hint) { if (mSingleton != null) { mSingleton.setOrientationBis(w, h, resizable, hint); } } /** * This can be overridden */ public void setOrientationBis(int w, int h, boolean resizable, String hint) { int orientation_landscape = -1; int orientation_portrait = -1; /* If set, hint "explicitly controls which UI orientations are allowed". */ if (hint.contains("LandscapeRight") && hint.contains("LandscapeLeft")) { orientation_landscape = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE; } else if (hint.contains("LandscapeLeft")) { orientation_landscape = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; } else if (hint.contains("LandscapeRight")) { orientation_landscape = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; } /* exact match to 'Portrait' to distinguish with PortraitUpsideDown */ boolean contains_Portrait = hint.contains("Portrait ") || hint.endsWith("Portrait"); if (contains_Portrait && hint.contains("PortraitUpsideDown")) { orientation_portrait = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT; } else if (contains_Portrait) { orientation_portrait = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; } else if (hint.contains("PortraitUpsideDown")) { orientation_portrait = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT; } boolean is_landscape_allowed = (orientation_landscape != -1); boolean is_portrait_allowed = (orientation_portrait != -1); int req; /* Requested orientation */ /* No valid hint, nothing is explicitly allowed */ if (!is_portrait_allowed && !is_landscape_allowed) { if (resizable) { /* All orientations are allowed, respecting user orientation lock setting */ req = ActivityInfo.SCREEN_ORIENTATION_FULL_USER; } else { /* Fixed window and nothing specified. Get orientation from w/h of created window */ req = (w > h ? ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE : ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT); } } else { /* At least one orientation is allowed */ if (resizable) { if (is_portrait_allowed && is_landscape_allowed) { /* hint allows both landscape and portrait, promote to full user */ req = ActivityInfo.SCREEN_ORIENTATION_FULL_USER; } else { /* Use the only one allowed "orientation" */ req = (is_landscape_allowed ? orientation_landscape : orientation_portrait); } } else { /* Fixed window and both orientations are allowed. Choose one. */ if (is_portrait_allowed && is_landscape_allowed) { req = (w > h ? orientation_landscape : orientation_portrait); } else { /* Use the only one allowed "orientation" */ req = (is_landscape_allowed ? orientation_landscape : orientation_portrait); } } } Log.v(TAG, "setOrientation() requestedOrientation=" + req + " width=" + w +" height="+ h +" resizable=" + resizable + " hint=" + hint); mSingleton.setRequestedOrientation(req); } /** * This method is called by SDL using JNI. */ public static void minimizeWindow() { if (mSingleton == null) { return; } Intent startMain = new Intent(Intent.ACTION_MAIN); startMain.addCategory(Intent.CATEGORY_HOME); startMain.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); mSingleton.startActivity(startMain); } /** * This method is called by SDL using JNI. */ public static boolean shouldMinimizeOnFocusLoss() { /* if (Build.VERSION.SDK_INT >= 24) { if (mSingleton == null) { return true; } if (mSingleton.isInMultiWindowMode()) { return false; } if (mSingleton.isInPictureInPictureMode()) { return false; } } return true; */ return false; } /** * This method is called by SDL using JNI. */ public static boolean isScreenKeyboardShown() { if (mTextEdit == null) { return false; } if (!mScreenKeyboardShown) { return false; } InputMethodManager imm = (InputMethodManager) SDL.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); return imm.isAcceptingText(); } /** * This method is called by SDL using JNI. */ public static boolean supportsRelativeMouse() { // DeX mode in Samsung Experience 9.0 and earlier doesn't support relative mice properly under // Android 7 APIs, and simply returns no data under Android 8 APIs. // // This is fixed in Samsung Experience 9.5, which corresponds to Android 8.1.0, and // thus SDK version 27. If we are in DeX mode and not API 27 or higher, as a result, // we should stick to relative mode. // if (Build.VERSION.SDK_INT < 27 /* Android 8.1 (O_MR1) */ && isDeXMode()) { return false; } return SDLActivity.getMotionListener().supportsRelativeMouse(); } /** * This method is called by SDL using JNI. */ public static boolean setRelativeMouseEnabled(boolean enabled) { if (enabled && !supportsRelativeMouse()) { return false; } return SDLActivity.getMotionListener().setRelativeMouseEnabled(enabled); } /** * This method is called by SDL using JNI. */ public static boolean sendMessage(int command, int param) { if (mSingleton == null) { return false; } return mSingleton.sendCommand(command, param); } /** * This method is called by SDL using JNI. */ public static Context getContext() { return SDL.getContext(); } /** * This method is called by SDL using JNI. */ public static boolean isAndroidTV() { UiModeManager uiModeManager = (UiModeManager) getContext().getSystemService(UI_MODE_SERVICE); if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION) { return true; } if (Build.MANUFACTURER.equals("MINIX") && Build.MODEL.equals("NEO-U1")) { return true; } if (Build.MANUFACTURER.equals("Amlogic") && Build.MODEL.equals("X96-W")) { return true; } return Build.MANUFACTURER.equals("Amlogic") && Build.MODEL.startsWith("TV"); } public static double getDiagonal() { DisplayMetrics metrics = new DisplayMetrics(); Activity activity = (Activity)getContext(); if (activity == null) { return 0.0; } activity.getWindowManager().getDefaultDisplay().getMetrics(metrics); double dWidthInches = metrics.widthPixels / (double)metrics.xdpi; double dHeightInches = metrics.heightPixels / (double)metrics.ydpi; return Math.sqrt((dWidthInches * dWidthInches) + (dHeightInches * dHeightInches)); } /** * This method is called by SDL using JNI. */ public static boolean isTablet() { // If our diagonal size is seven inches or greater, we consider ourselves a tablet. return (getDiagonal() >= 7.0); } /** * This method is called by SDL using JNI. */ public static boolean isChromebook() { if (getContext() == null) { return false; } return getContext().getPackageManager().hasSystemFeature("org.chromium.arc.device_management"); } /** * This method is called by SDL using JNI. */ public static boolean isDeXMode() { if (Build.VERSION.SDK_INT < 24 /* Android 7.0 (N) */) { return false; } try { final Configuration config = getContext().getResources().getConfiguration(); final Class configClass = config.getClass(); return configClass.getField("SEM_DESKTOP_MODE_ENABLED").getInt(configClass) == configClass.getField("semDesktopModeEnabled").getInt(config); } catch(Exception ignored) { return false; } } /** * This method is called by SDL using JNI. */ public static DisplayMetrics getDisplayDPI() { return getContext().getResources().getDisplayMetrics(); } /** * This method is called by SDL using JNI. */ public static boolean getManifestEnvironmentVariables() { try { if (getContext() == null) { return false; } ApplicationInfo applicationInfo = getContext().getPackageManager().getApplicationInfo(getContext().getPackageName(), PackageManager.GET_META_DATA); Bundle bundle = applicationInfo.metaData; if (bundle == null) { return false; } String prefix = "SDL_ENV."; final int trimLength = prefix.length(); for (String key : bundle.keySet()) { if (key.startsWith(prefix)) { String name = key.substring(trimLength); String value = bundle.get(key).toString(); nativeSetenv(name, value); } } /* environment variables set! */ return true; } catch (Exception e) { Log.v(TAG, "exception " + e.toString()); } return false; } // This method is called by SDLControllerManager's API 26 Generic Motion Handler. public static View getContentView() { return mLayout; } static class ShowTextInputTask implements Runnable { /* * This is used to regulate the pan&scan method to have some offset from * the bottom edge of the input region and the top edge of an input * method (soft keyboard) */ static final int HEIGHT_PADDING = 15; public int x, y, w, h; public ShowTextInputTask(int x, int y, int w, int h) { this.x = x; this.y = y; this.w = w; this.h = h; /* Minimum size of 1 pixel, so it takes focus. */ if (this.w <= 0) { this.w = 1; } if (this.h + HEIGHT_PADDING <= 0) { this.h = 1 - HEIGHT_PADDING; } } @Override public void run() { RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(w, h + HEIGHT_PADDING); params.leftMargin = x; params.topMargin = y; if (mTextEdit == null) { mTextEdit = new DummyEdit(SDL.getContext()); mLayout.addView(mTextEdit, params); } else { mTextEdit.setLayoutParams(params); } mTextEdit.setVisibility(View.VISIBLE); mTextEdit.requestFocus(); InputMethodManager imm = (InputMethodManager) SDL.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); imm.showSoftInput(mTextEdit, 0); mScreenKeyboardShown = true; } } /** * This method is called by SDL using JNI. */ public static boolean showTextInput(int x, int y, int w, int h) { // Transfer the task to the main thread as a Runnable return mSingleton.commandHandler.post(new ShowTextInputTask(x, y, w, h)); } public static boolean isTextInputEvent(KeyEvent event) { // Key pressed with Ctrl should be sent as SDL_KEYDOWN/SDL_KEYUP and not SDL_TEXTINPUT if (event.isCtrlPressed()) { return false; } return event.isPrintingKey() || event.getKeyCode() == KeyEvent.KEYCODE_SPACE; } public static boolean handleKeyEvent(View v, int keyCode, KeyEvent event, InputConnection ic) { int deviceId = event.getDeviceId(); int source = event.getSource(); if (source == InputDevice.SOURCE_UNKNOWN) { InputDevice device = InputDevice.getDevice(deviceId); if (device != null) { source = device.getSources(); } } // if (event.getAction() == KeyEvent.ACTION_DOWN) { // Log.v("SDL", "key down: " + keyCode + ", deviceId = " + deviceId + ", source = " + source); // } else if (event.getAction() == KeyEvent.ACTION_UP) { // Log.v("SDL", "key up: " + keyCode + ", deviceId = " + deviceId + ", source = " + source); // } // Dispatch the different events depending on where they come from // Some SOURCE_JOYSTICK, SOURCE_DPAD or SOURCE_GAMEPAD are also SOURCE_KEYBOARD // So, we try to process them as JOYSTICK/DPAD/GAMEPAD events first, if that fails we try them as KEYBOARD // // Furthermore, it's possible a game controller has SOURCE_KEYBOARD and // SOURCE_JOYSTICK, while its key events arrive from the keyboard source // So, retrieve the device itself and check all of its sources if (SDLControllerManager.isDeviceSDLJoystick(deviceId)) { // Note that we process events with specific key codes here if (event.getAction() == KeyEvent.ACTION_DOWN) { if (SDLControllerManager.onNativePadDown(deviceId, keyCode) == 0) { return true; } } else if (event.getAction() == KeyEvent.ACTION_UP) { if (SDLControllerManager.onNativePadUp(deviceId, keyCode) == 0) { return true; } } } if ((source & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) { // on some devices key events are sent for mouse BUTTON_BACK/FORWARD presses // they are ignored here because sending them as mouse input to SDL is messy if ((keyCode == KeyEvent.KEYCODE_BACK) || (keyCode == KeyEvent.KEYCODE_FORWARD)) { switch (event.getAction()) { case KeyEvent.ACTION_DOWN: case KeyEvent.ACTION_UP: // mark the event as handled or it will be handled by system // handling KEYCODE_BACK by system will call onBackPressed() return true; } } } if (event.getAction() == KeyEvent.ACTION_DOWN) { if (isTextInputEvent(event)) { if (ic != null) { ic.commitText(String.valueOf((char) event.getUnicodeChar()), 1); } else { SDLInputConnection.nativeCommitText(String.valueOf((char) event.getUnicodeChar()), 1); } } onNativeKeyDown(keyCode); return true; } else if (event.getAction() == KeyEvent.ACTION_UP) { onNativeKeyUp(keyCode); return true; } return false; } /** * This method is called by SDL using JNI. */ public static Surface getNativeSurface() { if (SDLActivity.mSurface == null) { return null; } return SDLActivity.mSurface.getNativeSurface(); } // Input /** * This method is called by SDL using JNI. */ public static void initTouch() { int[] ids = InputDevice.getDeviceIds(); for (int id : ids) { InputDevice device = InputDevice.getDevice(id); /* Allow SOURCE_TOUCHSCREEN and also Virtual InputDevices because they can send TOUCHSCREEN events */ if (device != null && ((device.getSources() & InputDevice.SOURCE_TOUCHSCREEN) == InputDevice.SOURCE_TOUCHSCREEN || device.isVirtual())) { int touchDevId = device.getId(); /* * 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; } nativeAddTouch(touchDevId, device.getName()); } } } // Messagebox /** Result of current messagebox. Also used for blocking the calling thread. */ protected final int[] messageboxSelection = new int[1]; /** * This method is called by SDL using JNI. * Shows the messagebox from UI thread and block calling thread. * buttonFlags, buttonIds and buttonTexts must have same length. * @param buttonFlags array containing flags for every button. * @param buttonIds array containing id for every button. * @param buttonTexts array containing text for every button. * @param colors null for default or array of length 5 containing colors. * @return button id or -1. */ public int messageboxShowMessageBox( final int flags, final String title, final String message, final int[] buttonFlags, final int[] buttonIds, final String[] buttonTexts, final int[] colors) { messageboxSelection[0] = -1; // sanity checks if ((buttonFlags.length != buttonIds.length) && (buttonIds.length != buttonTexts.length)) { return -1; // implementation broken } // collect arguments for Dialog final Bundle args = new Bundle(); args.putInt("flags", flags); args.putString("title", title); args.putString("message", message); args.putIntArray("buttonFlags", buttonFlags); args.putIntArray("buttonIds", buttonIds); args.putStringArray("buttonTexts", buttonTexts); args.putIntArray("colors", colors); // trigger Dialog creation on UI thread runOnUiThread(new Runnable() { @Override public void run() { messageboxCreateAndShow(args); } }); // block the calling thread synchronized (messageboxSelection) { try { messageboxSelection.wait(); } catch (InterruptedException ex) { ex.printStackTrace(); return -1; } } // return selected value return messageboxSelection[0]; } protected void messageboxCreateAndShow(Bundle args) { // TODO set values from "flags" to messagebox dialog // get colors int[] colors = args.getIntArray("colors"); int backgroundColor; int textColor; int buttonBorderColor; int buttonBackgroundColor; int buttonSelectedColor; if (colors != null) { int i = -1; backgroundColor = colors[++i]; textColor = colors[++i]; buttonBorderColor = colors[++i]; buttonBackgroundColor = colors[++i]; buttonSelectedColor = colors[++i]; } else { backgroundColor = Color.TRANSPARENT; textColor = Color.TRANSPARENT; buttonBorderColor = Color.TRANSPARENT; buttonBackgroundColor = Color.TRANSPARENT; buttonSelectedColor = Color.TRANSPARENT; } // create dialog with title and a listener to wake up calling thread final AlertDialog dialog = new AlertDialog.Builder(this).create(); dialog.setTitle(args.getString("title")); dialog.setCancelable(false); dialog.setOnDismissListener(new DialogInterface.OnDismissListener() { @Override public void onDismiss(DialogInterface unused) { synchronized (messageboxSelection) { messageboxSelection.notify(); } } }); // create text TextView message = new TextView(this); message.setGravity(Gravity.CENTER); message.setText(args.getString("message")); if (textColor != Color.TRANSPARENT) { message.setTextColor(textColor); } // create buttons int[] buttonFlags = args.getIntArray("buttonFlags"); int[] buttonIds = args.getIntArray("buttonIds"); String[] buttonTexts = args.getStringArray("buttonTexts"); final SparseArray