Compare commits

..

No commits in common. "master" and "2024.02.29" have entirely different histories.

2361 changed files with 158539 additions and 138847 deletions

6
.gitattributes vendored
View file

@ -1,3 +1,7 @@
# Exclude vendored libraries from GitHub language detection
lib/* linguist-vendored
src/lua/* linguist-vendored
src/luasocket/* linguist-vendored
src/SDL_image/* linguist-vendored
src/SDL_mixer/* linguist-vendored
src/SDL-mobile/* linguist-vendored

View file

@ -1,2 +0,0 @@
# Contributing to Principia
Contributions are welcome! Please see [Contributing to the Game](https://principia-web.se/wiki/Contributing_to_the_Game) on the Principia Wiki for more information.

28
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View file

@ -0,0 +1,28 @@
---
name: Bug report
about: If you are reporting a bug, please fill out this form with the necessary details.
title: ''
labels: Bug
assignees: ''
---
##### Principia version
<!--
Describe the version you are running here. Are you on a 1.5.2 Beta build downloaded
from principia-web or have you built from source? If you're reporting something from
1.5.1 make sure it still exists in the open source version.
-->
##### OS / Hardware
<!-- General information about your hardware and operating system -->
- Platform:
- Operating system:
<!-- For graphical issues only, feel free to omit if not graphical -->
- GPU model:
##### Summary
<!-- Describe your problem here -->
##### Steps to reproduce
<!-- Explain the steps one could take to reproduce it. If reporting a Lua issue, please give a minimal code example to reproduce it. -->

View file

@ -1,40 +0,0 @@
name: Bug report
description: Reporting bugs, crashes and other related issues with the game
labels: ["Bug"]
body:
- type: markdown
attributes:
value: Thank you for helping to make the game better by reporting issues that you discover. Make sure to describe the necessary details about the bug such that it can be reproduced by others. Additionally please refrain from using LLMs ("AI") to write or "improve" your report, as they typically just make it more verbose without adding any useful information.
- type: input
attributes:
label: Principia version
description: Describe the version you are running here. Are you on a stable release or on a nightly build, or have you built from source? To determine where a non-release build was built off of you can press the version number in the bottom right corner of the main menu.
placeholder: "Example: 20XX.XX.XX"
validations:
required: true
- type: input
attributes:
label: Platform
description: A lot of times a issue can be specific to a particular platform. Please write the platform you're on, or list the platforms you can reproduce it on.
placeholder: "Example: Linux"
validations:
required: true
- type: input
attributes:
label: System information
description: Give some information about your device that may be relevant for your issue. For example it would be helpful to know your GPU vendor for graphical issues.
placeholder: "Example: AMD Radeon RX 560 with Mesa drivers"
validations:
required: false
- type: textarea
attributes:
label: Summary
description: Describe the problem here.
validations:
required: true
- type: textarea
attributes:
label: Steps to reproduce
description: Explain the steps one could take to reproduce it. If applicable, providing Lua code snippets or attaching a test level would also be helpful.
validations:
required: true

View file

@ -1,5 +0,0 @@
blank_issues_enabled: true
contact_links:
- name: Questions
url: https://principia-web.se/forum/
about: For asking more general questions about Principia, please see the Principia forums.

11
.github/SECURITY.md vendored
View file

@ -1,11 +0,0 @@
# Security Policy
## Supported Versions
We only support the latest stable version for security issues.
## Reporting a Vulnerability
If you have found a bug that could be exploited by a malicious actor by playing a level or in any other way where an end-user could be victimised, then please responsibly disclose it privately by email to:
- ROllerozxa, project maintainer \<rollerozxa@voxelmanip.se\>
We will evaluate possible means of mitigating it on the community site and/or swiftly release a new version of Principia to fix the issue, depending on the severity. If it is deemed to not be a security-critical issue then we'll ask you to file a public bug report issue.

View file

@ -1,46 +0,0 @@
name: android
on:
push:
paths:
- 'src/**'
- 'lib/**'
- 'android/**'
- 'data/**'
- 'packaging/**'
- 'CMakeLists.txt'
- '.github/workflows/android.yml'
pull_request:
paths:
- 'src/**'
- 'lib/**'
- 'android/**'
- 'data/**'
- 'packaging/**'
- 'CMakeLists.txt'
- '.github/workflows/android.yml'
jobs:
android:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v6
- name: Set up JDK 17
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: '17'
- name: Build with gradle
run: |
cd android
./gradlew assemblerelease
- name: Save apk artifact
uses: actions/upload-artifact@v7
with:
name: principia-release-unsigned.apk
path: android/principia/build/outputs/apk/release/principia-release-unsigned.apk
#archive: false
if-no-files-found: error

125
.github/workflows/build.yml vendored Normal file
View file

@ -0,0 +1,125 @@
name: build
on:
push:
paths:
- 'src/**'
- 'build-*/**'
- 'data-*/**'
- 'packaging/**'
- 'CMakeLists.txt'
- '.github/workflows/**.yml'
pull_request:
paths:
- 'src/**'
- 'build-*/**'
- 'data-*/**'
- 'packaging/**'
- 'CMakeLists.txt'
- '.github/workflows/**.yml'
jobs:
linux:
runs-on: ubuntu-latest
container:
image: debian:bullseye
env: { LANG: "C.UTF-8" }
steps:
- uses: actions/checkout@v4
- name: Install deps
run: |
apt-get update
apt-get install -y g++ libgtk-3-dev libgl-dev libglew-dev libcurl4-openssl-dev libpng-dev libjpeg-dev libfreetype6-dev libsdl2-dev cmake ninja-build desktop-file-utils ca-certificates wget file --no-install-recommends
- name: Build
run: |
mkdir build
cd build
../packaging/build-appimage.sh
- name: Upload output as artifact
uses: actions/upload-artifact@v4
with:
name: Principia-x86_64.AppImage
path: build/Principia-x86_64.AppImage
windows:
runs-on: windows-latest
defaults:
run:
shell: msys2 {0}
steps:
- uses: actions/checkout@v4
- uses: msys2/setup-msys2@v2
with:
update: true
release: false
msystem: UCRT64
pacboy: >-
gcc:p
cmake:p
ninja:p
glew:p
zlib:p
gtk3:p
libpng:p
libjpeg-turbo:p
SDL2:p
nsis:p
7zip:p
# custom built packages with less dependencies than MSYS' counterparts
- name: Install external packages
run: |
wget https://grejer.voxelmanip.se/msys-pkgs/mingw-w64-ucrt-x86_64-{curl-winssl-8.6.0-2,freetype-2.13.2-1}-any.pkg.tar.zst
pacman -U --noconfirm *.pkg.tar.zst
- name: Build
run: |
mkdir build
cd build
cmake .. -G Ninja
ninja -j4
- name: Bundle together installer and portable
run: |
cd build
../packaging/windows_release.sh
../packaging/windows_portable.sh
- name: Upload output as artifact
uses: actions/upload-artifact@v4
with:
name: principia-setup.exe
path: build/principia-setup.exe
- name: Upload output as artifact
uses: actions/upload-artifact@v4
with:
name: principia-portable.7z
path: build/principia-portable.7z
android:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v4
- name: Install deps
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends openjdk-11-jdk-headless
- name: Build with gradle
run: |
cd build-android
./gradlew assemblerelease
- name: Save apk artifact
uses: actions/upload-artifact@v4
with:
name: principia-release-unsigned.apk
path: build-android/principia/build/outputs/apk/release/principia-release-unsigned.apk

View file

@ -1,34 +0,0 @@
name: build_utils
on:
push:
paths:
- 'src/**'
- 'utils/**'
pull_request:
paths:
- 'src/**'
- 'utils/**'
jobs:
build_utils:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v6
- name: Install deps
run: |
sudo apt-get update
sudo apt-get install -y libjansson-dev
- name: Build
run: |
cd utils
make
- name: Upload output as artifact
uses: actions/upload-artifact@v7
with:
name: principia-utils
path: utils/bin/
if-no-files-found: error

View file

@ -1,59 +0,0 @@
name: linux
on:
push:
paths:
- 'src/**'
- 'lib/**'
- 'data/**'
- 'packaging/**'
- 'CMakeLists.txt'
- '.github/workflows/linux.yml'
pull_request:
paths:
- 'src/**'
- 'lib/**'
- 'data/**'
- 'packaging/**'
- 'CMakeLists.txt'
- '.github/workflows/linux.yml'
jobs:
linux:
runs-on: ubuntu-latest
container:
image: debian:bullseye
env: { LANG: "C.UTF-8" }
steps:
- uses: actions/checkout@v6
- name: Install deps
run: |
apt-get update
apt-get install -y \
clang-16 libgtk-3-dev libgl-dev libcurl4-openssl-dev libpng-dev libjpeg-dev libfreetype6-dev cmake ninja-build desktop-file-utils ca-certificates wget file \
libasound2-dev libpulse-dev libjack-dev libsndio-dev libx11-dev libxext-dev libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev libxtst-dev libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev libgles2-mesa-dev libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev libpipewire-0.3-dev libwayland-dev liburing-dev \
--no-install-recommends
- name: Build
run: |
mkdir build; cd build
../packaging/build-appimage.sh
env:
CC: clang-16
CXX: clang++-16
- name: Upload output as artifact
uses: actions/upload-artifact@v7
with:
name: Principia-x86_64.AppImage
path: build/Principia-x86_64.AppImage
#archive: false
if-no-files-found: error
- name: Upload debug symbols as artifact
uses: actions/upload-artifact@v7
with:
name: linux-appimage-dbgsym
path: build/principia.debug
if-no-files-found: error

View file

@ -1,43 +0,0 @@
name: linux_ss
on:
push:
paths:
- 'src/**'
- 'lib/**'
- 'data/**'
- 'CMakeLists.txt'
- '.github/workflows/linux_ss.yml'
pull_request:
paths:
- 'src/**'
- 'lib/**'
- 'data/**'
- 'CMakeLists.txt'
- '.github/workflows/linux_ss.yml'
jobs:
linux_ss:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v6
- name: Install deps
run: |
sudo apt-get update
sudo apt-get install -y g++ libgl-dev libpng-dev libjpeg-dev libfreetype6-dev cmake ninja-build \
libx11-dev libxext-dev libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev libxtst-dev libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev libegl1-mesa-dev \
--no-install-recommends
- name: Build
run: |
mkdir build; cd build
cmake .. -DSCREENSHOT_BUILD=ON -G Ninja -DUSE_VENDORED_SDL3=ON
ninja -j4
- name: Upload output as artifact
uses: actions/upload-artifact@v7
with:
name: screenshotter_artifact
path: build/principia
if-no-files-found: error

View file

@ -1,55 +0,0 @@
name: macos
on:
push:
paths:
- 'src/**'
- 'lib/**'
- 'data/**'
- 'packaging/**'
- 'CMakeLists.txt'
- '.github/workflows/macos.yml'
pull_request:
paths:
- 'src/**'
- 'lib/**'
- 'data/**'
- 'packaging/**'
- 'CMakeLists.txt'
- '.github/workflows/macos.yml'
jobs:
macos:
runs-on: macos-latest
steps:
- uses: actions/checkout@v6
- name: Install deps
run: |
export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1
export HOMEBREW_NO_INSTALL_CLEANUP=1
brew update --auto-update
brew install cmake ninja libpng libjpeg-turbo freetype sdl3 gtk+3
- name: Compile
run: |
mkdir build; cd build
cmake .. -G Ninja \
-DCMAKE_OSX_DEPLOYMENT_TARGET=10.14 \
-DCMAKE_FIND_FRAMEWORK=LAST \
-DCMAKE_INSTALL_PREFIX=../build/macos/ \
-DCMAKE_EXE_LINKER_FLAGS="-L/opt/homebrew/lib/"
ninja
- name: Package
run: |
cd build
ninja package
- name: Upload output as artifact
uses: actions/upload-artifact@v7
with:
name: principia-macos
path: build/*.zip
#archive: false
if-no-files-found: error

View file

@ -1,47 +0,0 @@
name: web
on:
push:
paths:
- 'src/**'
- 'lib/**'
- 'data/**'
- 'packaging/**'
- 'CMakeLists.txt'
- '.github/workflows/web.yml'
pull_request:
paths:
- 'src/**'
- 'lib/**'
- 'data/**'
- 'packaging/**'
- 'CMakeLists.txt'
- '.github/workflows/web.yml'
jobs:
web:
runs-on: ubuntu-latest
container:
image: emscripten/emsdk:latest
env: { LANG: "C.UTF-8" }
steps:
- uses: actions/checkout@v6
- name: Install deps
run: |
sudo apt-get update
sudo apt-get install -y ninja-build
- name: Build
run: |
mkdir build; cd build
emcmake cmake .. -G Ninja -DCMAKE_INSTALL_PREFIX= -DUSE_VENDORED_SDL3=ON
ninja
DESTDIR=../web ninja install
- name: Save apk artifact
uses: actions/upload-artifact@v7
with:
name: principia-wasm
path: web/
if-no-files-found: error

View file

@ -1,78 +0,0 @@
name: windows
on:
push:
paths:
- 'src/**'
- 'lib/**'
- 'data/**'
- 'packaging/**'
- 'CMakeLists.txt'
- '.github/workflows/windows.yml'
pull_request:
paths:
- 'src/**'
- 'lib/**'
- 'data/**'
- 'packaging/**'
- 'CMakeLists.txt'
- '.github/workflows/windows.yml'
jobs:
windows:
runs-on: windows-latest
defaults:
run:
shell: msys2 {0}
steps:
- uses: actions/checkout@v6
- uses: msys2/setup-msys2@v2
with:
update: true
release: false
msystem: CLANG64
pacboy: >-
git:
gcc:p
cmake:p
ninja:p
zlib:p
gtk3:p
libpng:p
libjpeg-turbo:p
sdl3:p
nsis:p
7zip:p
- name: Install external packages
run: |
./packaging/windows-install-pkgs.sh
- name: Build
run: |
mkdir build; cd build
cmake .. -G Ninja
ninja -j4
- name: Bundle together installer and portable
run: |
cd build
../packaging/windows_release.sh
../packaging/windows_portable.sh
- name: Upload output as artifact
uses: actions/upload-artifact@v7
with:
name: principia-setup.exe
path: build/principia-setup.exe
#archive: false
if-no-files-found: error
- name: Upload output as artifact
uses: actions/upload-artifact@v7
with:
name: principia-portable.7z
path: build/principia-portable.7z
#archive: false
if-no-files-found: error

7
.gitignore vendored
View file

@ -6,13 +6,8 @@ build_*/
# Misc. development files
.vscode/
*.patch
local_config.h
opengl32.dll
.cache/
compile_commands.json
# Doxygen documentation
doxy/
doxygen-awesome.css
# Model junk files
*.blend1

3
.gitmodules vendored
View file

@ -1,3 +0,0 @@
[submodule "android/principia/deps-src"]
path = android/principia/deps-src
url = https://github.com/principia-game/android-deps

View file

@ -1,17 +1,18 @@
Original Authors of Principia
=============================
===========================
**Emil Romanus**<br>
Emil Romanus
Original creator, programming, game design, 3D modeling, graphics
**Rasmus Karlsson**<br>
Rasmus Karlsson
Game programming, server development, and more
**Mikael Romanus**<br>
Mikael Romanus
3D Modeling, code twiddling
Contributors
------------
-------------
**Anders Schanche**<br>
Anders Schanche
3D Models (Tesla Gun, Rocket Launcher, and more), testing

View file

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.14)
cmake_minimum_required(VERSION 3.9)
project(principia)
@ -13,49 +13,14 @@ if (NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Release")
endif()
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/")
find_package(Freetype REQUIRED)
find_package(JPEG REQUIRED)
find_package(PNG REQUIRED)
find_package(SDL2 REQUIRED)
find_package(ZLIB REQUIRED)
include(DownloadLib)
add_custom_target(GenerateGitVersion
COMMAND ${CMAKE_COMMAND}
-D "GENERATE_VERSION_SOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR}"
-D "GENERATE_VERSION_BINARY_DIR=${CMAKE_CURRENT_BINARY_DIR}"
-P "${CMAKE_SOURCE_DIR}/cmake/Modules/GenerateGitVersion.cmake"
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}")
option(UNITY_BUILD "Experimental unity build" FALSE)
mark_as_advanced(UNITY_BUILD)
option(USE_VENDORED_SDL3 "Build with vendored SDL3 library" FALSE)
# Find core dependencies
# ----------------------
if(USE_VENDORED_SDL3)
include(cmake/SDL.cmake)
else()
find_package(SDL3 REQUIRED)
endif()
if(ANDROID)
cmake_minimum_required(VERSION 3.20)
include(PrincipiaAndroidLibs)
else()
set(OpenGL_GL_PREFERENCE GLVND)
find_package(OpenGL REQUIRED)
endif()
if(NOT EMSCRIPTEN)
find_package(Freetype REQUIRED)
find_package(JPEG REQUIRED)
find_package(PNG REQUIRED)
find_package(ZLIB REQUIRED)
endif()
# Determine platform and backend
# ------------------------------
set(OpenGL_GL_PREFERENCE GLVND)
find_package(OpenGL REQUIRED)
if(LINUX)
option(SCREENSHOT_BUILD "Build screenshotter build (Linux only)" FALSE)
@ -63,282 +28,181 @@ else()
set(SCREENSHOT_BUILD FALSE)
endif()
if(EMSCRIPTEN)
set(BACKEND_IMGUI TRUE)
else()
option(BACKEND_IMGUI "Enable incomplete Dear Imgui dialog backend (Experimental)" FALSE)
endif()
set(TMS_FORMFACTOR "PC")
if(WIN32)
set(TMS_BACKEND "WINDOWS")
elseif(ANDROID)
set(TMS_BACKEND "ANDROID")
set(TMS_FORMFACTOR "MOBILE")
set(TMS_BACKEND "windows")
elseif(SCREENSHOT_BUILD)
set(TMS_BACKEND "screenshot-linux")
elseif(HAIKU)
set(TMS_BACKEND "HAIKU")
elseif(APPLE)
set(TMS_BACKEND "MACOS")
elseif(EMSCRIPTEN)
set(TMS_BACKEND "EMSCRIPTEN")
set(TMS_BACKEND "haiku")
else()
set(TMS_BACKEND "LINUX")
set(TMS_BACKEND "linux")
endif()
if(LINUX)
option(USE_GLES "Use OpenGL ES on Linux (Experimental)" FALSE)
endif()
if(USE_GLES OR ANDROID OR EMSCRIPTEN)
set(SHOULD_USE_GLES TRUE)
else()
set(SHOULD_USE_GLES FALSE)
endif()
# Include dirs and main source files
# ----------------------------------
include_directories(
lib/
lib/GLAD/include/
lib/imgui/
lib/lua/
lib/SDL_image/
lib/SDL_mixer/
src/
${CMAKE_CURRENT_BINARY_DIR})
src/lua/
src/SDL_image/
src/src/
${FREETYPE_INCLUDE_DIRS}
${JPEG_INCLUDE_DIRS}
${OPENGL_INCLUDE_DIRS}
${PNG_INCLUDE_DIRS}
${SDL2_INCLUDE_DIRS}
${ZLIB_INCLUDE_DIRS})
file(GLOB SRCS CONFIGURE_DEPENDS
lib/GLAD/src/gl.c
lib/lua/*.c
lib/SDL_image/*.c
file(GLOB SRCS
src/tms/core/*.c
src/tms/math/*.c
src/tms/util/*.c
src/tms/bindings/cpp/cpp.cc
src/tms/modules/3ds.c
src/lua/*.c
src/SDL_image/*.c
src/src/*.cc
src/src/*.c
src/src/Box2D/Collision/*.cc
src/src/Box2D/Collision/Shapes/*.cc
src/src/Box2D/Common/*.cc
src/src/Box2D/Dynamics/*.cc
src/src/Box2D/Dynamics/Contacts/*.cc
src/src/Box2D/Dynamics/Joints/*.cc
src/src/Box2D/Particle/*.cc
)
if(UNITY_BUILD)
list(APPEND SRCS
src/tms/_unity_chunk.c
src/_unity_chunk.cc
src/game.cc
src/game-gearbox-edit.cc
src/game-gui.cc
src/game-panel-edit.cc
src/menu_pkg.cc
src/repair_station.cc
src/solver_ingame.cc
lib/Box2D/_unity_chunk.cc)
else()
file(GLOB MAIN_SRCS CONFIGURE_DEPENDS
src/tms/core/*.c
src/tms/math/*.c
src/tms/cpp.cc
src/tms/modules/3ds.c
src/*.cc
src/luascript/*.cc
lib/Box2D/Collision/*.cc
lib/Box2D/Collision/Shapes/*.cc
lib/Box2D/Common/*.cc
lib/Box2D/Dynamics/*.cc
lib/Box2D/Dynamics/Contacts/*.cc
lib/Box2D/Dynamics/Joints/*.cc
lib/Box2D/Particle/*.cc)
list(APPEND SRCS ${MAIN_SRCS})
endif()
if(BACKEND_IMGUI)
file(GLOB IMGUI_SRCS CONFIGURE_DEPENDS
lib/imgui/*.cpp
lib/imgui/misc/freetype/*.cpp
src/ui/*.cc)
list(APPEND SRCS ${IMGUI_SRCS})
endif()
# Optional dependencies not found on Android or in the screenshot build
if(NOT SCREENSHOT_BUILD)
if(NOT ANDROID AND NOT EMSCRIPTEN)
if(NOT BACKEND_IMGUI)
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK3 REQUIRED gtk+-3.0)
endif()
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK3 REQUIRED gtk+-3.0)
find_package(CURL REQUIRED)
find_package(GLEW REQUIRED)
if(NOT BACKEND_IMGUI)
include_directories(${GTK3_INCLUDE_DIRS})
endif()
endif()
include_directories(
${CURL_INCLUDE_DIR}
${GLEW_INCLUDE_DIRS}
${GTK3_INCLUDE_DIRS}
src/SDL_mixer/)
if(NOT EMSCRIPTEN)
find_package(CURL REQUIRED)
include_directories(${CURL_INCLUDE_DIR})
add_definitions(-DBUILD_CURL)
endif()
file(GLOB SDL_mixer_SRCS src/SDL_mixer/*.c)
set(SRCS ${SRCS} ${SDL_mixer_SRCS})
file(GLOB SDL_mixer_SRCS CONFIGURE_DEPENDS lib/SDL_mixer/*.c)
list(APPEND SRCS ${SDL_mixer_SRCS})
option(USE_LUASOCKET "Build with Luasocket support" TRUE)
else()
set(USE_LUASOCKET false)
endif()
# Luasocket
if(USE_LUASOCKET)
set(LUASOCKET_FLAGS "-DBUILD_LUASOCKET -DLUASOCKET_INET_PTON")
if(NOT UNITY_BUILD)
if(SCREENSHOT_BUILD)
set(BACKEND_SRC main_screenshotter.cc)
if(WIN32)
set(LUASOCKET_PLAT_SRC src/luasocket/wsocket.c)
else()
set(BACKEND_SRC main.cc)
list(APPEND SRCS src/tms/backend/pipe.cc)
set(LUASOCKET_PLAT_SRC
src/luasocket/usocket.c
src/luasocket/unix.c)
endif()
list(APPEND SRCS src/tms/backend/${BACKEND_SRC})
set(SRCS ${SRCS}
src/luasocket/auxiliar.c
src/luasocket/buffer.c
src/luasocket/except.c
src/luasocket/inet.c
src/luasocket/io.c
src/luasocket/luasocket.c
src/luasocket/mime.c
src/luasocket/options.c
src/luasocket/select.c
src/luasocket/tcp.c
src/luasocket/timeout.c
src/luasocket/udp.c
${LUASOCKET_PLAT_SRC})
endif()
set(SRCS ${SRCS} src/tms/backends/${TMS_BACKEND}/main.cc)
if(WIN32)
list(APPEND SRCS packaging/principia.rc)
endif()
set(WINRESOURCE_FILE "packaging/principia.rc")
set(WINMANIFEST_FILE "packaging/principia.manifest")
# Add executable (or library for Android)
# ---------------------------------------
if(ANDROID)
add_library(${PROJECT_NAME} SHARED ${SRCS})
else()
add_executable(${PROJECT_NAME} ${SRCS})
endif()
# Link libraries against executable
# ---------------------------------
if(NOT EMSCRIPTEN)
set(LIBS
Freetype::Freetype
JPEG::JPEG
PNG::PNG
ZLIB::ZLIB)
if(NOT SCREENSHOT_BUILD AND NOT EMSCRIPTEN)
list(APPEND LIBS ${CURL_LIBRARIES})
if(NOT ANDROID AND NOT BACKEND_IMGUI)
list(APPEND LIBS ${GTK3_LIBRARIES})
endif()
if(NOT CMAKE_RC_COMPILER)
set(CMAKE_RC_COMPILER "windres.exe")
endif()
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/principia.rc.o
COMMAND ${CMAKE_RC_COMPILER} -I${CMAKE_CURRENT_SOURCE_DIR} -I${CMAKE_CURRENT_BINARY_DIR}
-i${WINRESOURCE_FILE}
-o ${CMAKE_CURRENT_BINARY_DIR}/principia.rc.o
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
DEPENDS ${WINRESOURCE_FILE} ${WINMANIFEST_FILE})
set(SRCS ${SRCS} ${CMAKE_CURRENT_BINARY_DIR}/principia.rc.o)
endif()
list(APPEND LIBS SDL3::SDL3)
add_executable(${PROJECT_NAME} ${SRCS})
target_link_libraries(
${PROJECT_NAME}
${FREETYPE_LIBRARIES}
${JPEG_LIBRARIES}
${OPENGL_LIBRARIES}
${PNG_LIBRARIES}
${SDL2_LIBRARIES}
${ZLIB_LIBRARIES})
if(SHOULD_USE_GLES)
list(APPEND LIBS GLESv2)
else()
list(APPEND LIBS OpenGL::GL)
if(NOT SCREENSHOT_BUILD)
target_link_libraries(
${PROJECT_NAME}
${CURL_LIBRARIES}
${GLEW_LIBRARIES}
${GTK3_LIBRARIES})
endif()
if(ANDROID)
list(APPEND LIBS android dl log OpenSLES)
endif()
target_link_libraries(${PROJECT_NAME} ${LIBS})
add_dependencies(${PROJECT_NAME} GenerateGitVersion)
# Compiler flags
# --------------
if(SHOULD_USE_GLES)
add_definitions(-DTMS_USE_GLES)
endif()
if(UNITY_BUILD)
add_definitions(-DUNITY_BUILD)
endif()
set(COMMON_FLAGS "${LUASOCKET_FLAGS} -DTMS_BACKEND_PC -DTMS_FAST_MATH -DLUA_COMPAT_MODULE")
if(WIN32)
add_definitions(-D_WIN32_WINNT=0x0501)
target_link_libraries(${PROJECT_NAME} ws2_32.lib version.lib shlwapi.lib winmm.lib)
set(COMMON_FLAGS "${COMMON_FLAGS} -DTMS_BACKEND_WINDOWS -D_WIN32_WINNT=0x0501 -Dsrandom=srand -Drandom=rand -DUNICODE")
elseif(SCREENSHOT_BUILD)
add_definitions(-DNO_UI -DSCREENSHOT_BUILD)
# Screenshot build doesn't use GLEW
set(COMMON_FLAGS "${COMMON_FLAGS} -DGL_GLEXT_PROTOTYPES -DNO_UI -DTMS_BACKEND_LINUX_SS")
elseif(HAIKU)
set(COMMON_FLAGS "${COMMON_FLAGS} -DTMS_BACKEND_HAIKU")
else()
set(COMMON_FLAGS "${COMMON_FLAGS} -DTMS_BACKEND_LINUX")
endif()
if(BACKEND_IMGUI)
add_definitions(-DPRINCIPIA_BACKEND_IMGUI -DIMGUI_DEFINE_MATH_OPERATORS)
if(SHOULD_USE_GLES)
add_definitions(-DIMGUI_IMPL_OPENGL_ES2)
endif()
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
# Downgrade some errors to warnings when building with Clang
set(COMMON_FLAGS "${COMMON_FLAGS} -Wno-error=incompatible-function-pointer-types -Wno-error=int-conversion")
endif()
add_definitions(-DTMS_BACKEND_${TMS_FORMFACTOR} -DTMS_BACKEND_${TMS_BACKEND})
# Use a safe subset of fast math flags
set(COMMON_FLAGS "-fno-math-errno -fno-trapping-math -fno-signed-zeros")
if(EMSCRIPTEN)
set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "--preload-file ../data/")
set(LIBRARY_FLAGS "-sUSE_FREETYPE=1 -sUSE_LIBJPEG=1 -sUSE_LIBPNG=1 -sUSE_ZLIB=1 -pthread")
string(APPEND COMMON_FLAGS " ${LIBRARY_FLAGS}")
set(CMAKE_EXE_LINKER_FLAGS " ${LIBRARY_FLAGS} -pthread -sPTHREAD_POOL_SIZE=20 -sINITIAL_MEMORY=2013265920 -sALLOW_MEMORY_GROWTH=1 -sTOTAL_STACK=16Mb -sFETCH=1")
endif()
set(COMMON_FLAGS_DEBUG "${COMMON_FLAGS} -O0 -ggdb -DDEBUG=1")
set(COMMON_FLAGS_RELEASE "${COMMON_FLAGS} -DNDEBUG=1 -fomit-frame-pointer")
set(COMMON_FLAGS_DEBUG "${COMMON_FLAGS} -O0 -ggdb -ffast-math -Werror=return-type -DDEBUG=1")
set(COMMON_FLAGS_RELEASE "${COMMON_FLAGS} -DNDEBUG=1 -fomit-frame-pointer -fvisibility=hidden -fdata-sections -ffunction-sections")
set(CMAKE_C_FLAGS_RELEASE "${COMMON_FLAGS_RELEASE} -O1")
set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELEASE} -g")
set(CMAKE_C_FLAGS_DEBUG "${COMMON_FLAGS_DEBUG}")
set(CMAKE_CXX_FLAGS_RELEASE "${COMMON_FLAGS_RELEASE} -O2 -fno-rtti")
set(CMAKE_CXX_FLAGS_RELEASE "${COMMON_FLAGS_RELEASE} -O2 -fvisibility-inlines-hidden -fno-rtti")
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELEASE} -g")
set(CMAKE_CXX_FLAGS_DEBUG "${COMMON_FLAGS_DEBUG}")
if(NOT DEFINED CMAKE_EXE_LINKER_FLAGS_RELEASE)
set(CMAKE_EXE_LINKER_FLAGS_RELEASE "")
endif()
set(CMAKE_EXE_LINKER_FLAGS_RELEASE "-Wl,-O,-s,--gc-sections")
set(CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "-Wl,-O,--gc-sections")
set(CMAKE_EXE_LINKER_FLAGS_DEBUG "")
if(WIN32)
string(APPEND CMAKE_EXE_LINKER_FLAGS_RELEASE "-mwindows ")
endif()
# macOS Clang's linker doesn't like these flags
if(NOT APPLE)
string(APPEND CMAKE_EXE_LINKER_FLAGS_RELEASE "-Wl,-s ")
endif()
# Register protocol handler util on Windows
# -----------------------------------------
if(WIN32)
add_executable(register-protocol-handler
packaging/register-protocol-handler/main.c
packaging/register-protocol-handler/windows.rc)
set(CMAKE_EXE_LINKER_FLAGS_RELEASE "-mwindows")
endif()
# Installation
# ------------
if(APPLE)
set(BUNDLE_NAME ${PROJECT_NAME}.app)
set(BUNDLE_PATH "${BUNDLE_NAME}")
set(BINDIR ${BUNDLE_NAME}/Contents/MacOS)
set(SHAREDIR ${BUNDLE_NAME}/Contents/Resources)
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/data" DESTINATION "${SHAREDIR}")
install(FILES "packaging/principia.icns" DESTINATION "${SHAREDIR}")
install(FILES "packaging/Info.plist" DESTINATION "${BUNDLE_PATH}/Contents")
elseif(EMSCRIPTEN)
set(BINDIR .)
install(FILES "packaging/index.html" DESTINATION .)
install(FILES ${CMAKE_BINARY_DIR}/${PROJECT_NAME}.wasm DESTINATION .)
install(FILES ${CMAKE_BINARY_DIR}/${PROJECT_NAME}.data DESTINATION .)
else()
if(UNIX)
include(GNUInstallDirs)
set(SHAREDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}")
set(BINDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}")
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/data" DESTINATION "${SHAREDIR}/principia")
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/data-pc" DESTINATION "${SHAREDIR}/principia")
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/data-shared" DESTINATION "${SHAREDIR}/principia")
install(FILES "packaging/principia.desktop" DESTINATION "${SHAREDIR}/applications")
install(FILES "packaging/principia-url-handler.desktop" DESTINATION "${SHAREDIR}/applications")
@ -346,23 +210,6 @@ else()
install(FILES "packaging/principia.png" DESTINATION "${SHAREDIR}/icons/hicolor/128x128/apps")
install(FILES "packaging/se.principia_web.principia.metainfo.xml" DESTINATION "${SHAREDIR}/metainfo")
endif()
install(TARGETS ${PROJECT_NAME}
RUNTIME DESTINATION ${BINDIR}
LIBRARY DESTINATION ${BINDIR}
ARCHIVE DESTINATION ${BINDIR}
BUNDLE DESTINATION .
)
if(APPLE)
set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY 0)
set(CPACK_GENERATOR ZIP)
include(CPack)
install(CODE "
set(BU_CHMOD_BUNDLE_ITEMS ON)
include(BundleUtilities)
fixup_bundle(\"\${CMAKE_INSTALL_PREFIX}/${BUNDLE_PATH}\" \"\" \"\${CMAKE_INSTALL_PREFIX}/${BINDIR}\")
" COMPONENT Runtime)
install(TARGETS ${PROJECT_NAME} DESTINATION ${BINDIR})
endif()

View file

@ -1,37 +0,0 @@
# Project properties
PROJECT_NAME = Principia
PROJECT_LOGO = packaging/principia.png
# Parsing
JAVADOC_AUTOBRIEF = YES
EXTRACT_ALL = YES
EXTRACT_PRIVATE = YES
EXTRACT_STATIC = YES
SORT_MEMBERS_CTORS_1ST = YES
WARN_IF_UNDOCUMENTED = NO
BUILTIN_STL_SUPPORT = YES
# Input
RECURSIVE = YES
STRIP_FROM_PATH = src
INPUT = src/
# Dot graphs
HAVE_DOT = YES
CALL_GRAPH = YES
CALLER_GRAPH = YES
MAX_DOT_GRAPH_DEPTH = 3
DOT_MULTI_TARGETS = YES
DOT_IMAGE_FORMAT = svg
# Output
OUTPUT_DIRECTORY = doxy/ # /tmp/doxy/
GENERATE_LATEX = NO
REFERENCED_BY_RELATION = YES
REFERENCES_RELATION = YES
SEARCHENGINE = YES
DISABLE_INDEX = YES
GENERATE_TREEVIEW = YES
HTML_DYNAMIC_SECTIONS = YES
HTML_TIMESTAMP = YES
HTML_EXTRA_STYLESHEET = doxygen-awesome.css

153
README.md
View file

@ -1,9 +1,9 @@
# Principia Open Source Project
![Principia](https://raw.githubusercontent.com/Bithack/principia/master/data-src/github-image0.gif)
Principia is a sandbox physics game originally released in November 2013. It is the successor to the 2011 Android hit game "Apparatus". In August of 2022 Principia was released as open source, and is now being developed as an open source project by the Principia community.
Principia is a sandbox physics game originally released in November 2013. It is the successor to the 2011 Android hit game "Apparatus". In August of 2022 Principia was released as open source, and is now being developed as an open source project.
Principia runs on anything with a recent enough version of Windows, Linux or Android. Experimental ports to Haiku OS and macOS are also available, and work to port Principia to other platforms are very welcome.
Principia runs on anything with a recent enough version of Windows, Linux or Android. There exists code to compile for iOS, but is currently not functional. (See [#85](https://github.com/Bithack/principia/issues/85)) An incomplete port to Haiku OS is also available, and work to port Principia to other platforms are very welcome.
## Useful Links
* New community site: https://principia-web.se
@ -18,26 +18,155 @@ Principia runs on anything with a recent enough version of Windows, Linux or And
* Discord server: https://principia-web.se/discord
* Codeberg mirror: https://codeberg.org/principia/principia
* Mastodon: https://hachyderm.io/@principia
* Matrix room: https://principia-web.se/matrix
## Binary builds
Release builds builds of Principia for Windows, Android and Linux are available on the [download page](https://principia-web.se/download).
Every now and then new beta builds for 1.5.2 are made whenever things are stable enough on the `master` branch. These are available for download on the [principia-web downloads page](https://principia-web.se/download).
There are also nightly build artifacts that get automatically built by GitHub Actions CI on each commit and are available for download, see [Nightly Builds](https://principia-web.se/wiki/Nightly_Builds) on the wiki.
There are also nightly build artifacts for Windows and Android that get automatically built by GitHub CI on each commit, see [Actions](https://github.com/Bithack/principia/actions). Keep in mind these may be broken at times during development, and you are recommended to use the beta builds instead.
## Getting involved
Feel free to fork this project and send in your pull requests. This is a community project and the community decides how the project evolves.
For a brief overview on how to get started with contributing to the game, see the [Contributing to the Game](https://principia-web.se/wiki/Contributing_to_the_Game) page on the wiki.
Most of our discussion and development happens in the `#development` channel of the [Principia Discord server](https://principia-web.se/discord) (also bridged to [Matrix](https://principia-web.se/matrix)), make sure to join it if you want to discuss and participate in the development of the game.
Also be sure to follow [@principia](https://hachyderm.io/@principia) on Mastodon for more updates about the project.
Also be sure to follow [@principia](https://fosstodon.org/@principia) on Fosstodon for more updates about the project.
## Building from source
See [Compiling Principia](https://principia-web.se/wiki/Compiling_Principia) on the wiki for building from source on supported platforms.
## Building and running
Below are instructions to build Principia on Windows, Linux and Android from source. See also [this Wiki page](https://principia-web.se/wiki/Compiling_Principia) for notes on running Principia on particular platforms. (e.g. Chrome OS, Raspberry Pi or Haiku OS)
(You can also view the plaintext Markdown document [here](https://raw.githubusercontent.com/principia-game/wiki/master/pages/Compiling_Principia.md))
If you have issues building Principia, then please ask in the `#development` channel on Discord.
### Windows
The game engine behind Principia (TMS) is written in the C99 standard of C. Unfortunately, the Visual Studio C compiler does not support the C99 standard. Principia must therefore be compiled using MinGW-w64 toolchain.
The following build Windows instructions use MSYS2 which is a development environment for Windows including MinGW and other tools. Please download and install the latest version of the MSYS2 installer here: https://www.msys2.org/
After installation, a terminal opens. Run the following command to update the environment:
```bash
pacman -Syu
```
The terminal will then ask you to shut down the MSYS2 runtime to the finish the update. Proceed with doing so, and then go to the start menu and start the "MSYS2 UCRT64" environment (icon with gold background) again. Run the following command to install the necessary dependencies:
```bash
pacman -S git mingw-w64-ucrt-x86_64-{gcc,cmake,ninja,curl-winssl,gtk3,glew,libpng,libjpeg-turbo,freetype,SDL2}
```
Navigate somewhere you want to clone the Principia source code to, such as on your desktop:
```bash
cd /c/Users/$USER/Desktop/
git clone https://github.com/Bithack/principia
cd principia
```
Then generate the build files using CMake and start the compilation:
```bash
mkdir build; cd build
cmake .. -G Ninja
ninja
```
When finished there will be a `principia.exe` file in the build folder. Keep in mind that the built executable can only be run inside of the MSYS2 terminal, to make a release build see below to build the installer:
#### Windows installer
The Windows installer uses NSIS, which must be installed first before building:
```bash
pacman -S mingw-w64-ucrt-x86_64-nsis
```
For making Windows release builds you would run the `packaging/windows_release.sh` script, which will bundle necessary DLLs and other files to make the game run, and builds the installer.
### Linux
If you just want to play Principia on Linux, there are packages available for Arch-based and Debian-based distros as well as NixOS. See the [principia-web downloads page](https://principia-web.se/download) for more info.
Install dependencies.
**Debian-based distros:**
```bash
sudo apt install --no-install-recommends cmake ninja-build libgtk-3-dev libgl-dev libglew-dev libasound2-dev libcurl4-openssl-dev libpng-dev libjpeg-dev libfreetype6-dev libsdl2-dev
```
**For Arch-based distros:**
```bash
sudo pacman -S --needed cmake ninja glew gtk3 curl freetype2 libpng libjpeg sdl2
```
**For Fedora:**
```bash
sudo dnf install @development-tools cmake ninja gcc-c++ freetype-devel libcurl-devel libpng-devel libjpeg-turbo-devel gtk3-devel SDL2-devel libXxf86vm-devel glew-devel mesa-libGLU-devel alsa-lib-devel systemd-devel
```
**For Alpine:**
```bash
doas apk add build-base cmake ninja mesa-dev glew-dev gtk+3.0-dev libpng-dev jpeg-dev curl-dev freetype-dev zlib-dev sdl2-dev
```
**For NixOS**, Follow the instructions [here](./nix/README.md).
Generate the build files using CMake and start compilation:
```bash
mkdir build; cd build
cmake .. -G Ninja
ninja
```
When finished a `principia` executable will be produced, which can then be run.
While the game works fine when being run from out of the source tree, additional setup is required for the URL handler to work in order to play community levels. See [this page on the Wiki](https://principia-web.se/wiki/Principia_Protocol#linux) for more details.
#### Packaging for Linux
On Linux Principia will attempt to load data from the following directories:
1. `./` (data directories are next to the executable)
2. `../` (data directories are one directory up relative to the executable)
3. `./share/principia/` (for an installed build)
When doing `ninja install`, the data folders will be installed to `share/principia`. For packaging, you would want to pass `-DCMAKE_INSTALL_PREFIX=/usr` to CMake which when installed will put data where it can get loaded from.
### Building for Android
These instructions assume a Linux system but can likely be easily adapted to build for Android on any platform.
Download Android Studio from here: https://developer.android.com/studio
Untar the archive and run studio.sh:
```bash
tar xzf android-studio-*-linux.tar.gz
cd bin; ./studio.sh
```
Choose Custom in the Installer, click Next a bunch of times. Android Studio will download components for a while. Once finished, in the "Welcome to Android Studio" dialog, choose "Customize" in the left menu and then click "All Settings..." at the bottom center. Open Appearance -> System Settings -> Android SDK. Click the SDK Tools tab and check the following items:
- NDK (Side by side)
- Android SDK Command-line tools
Click Apply and wait for the components to download. Close Android Studio **forever**.
Open a terminal and run the build scripts:
```bash
cd build-android
export ANDROID_HOME=/home/EXAMPLE/Android/Sdk
./gradlew build
```
ANDROID_HOME should be set to the location where Android Studio installed the SDK (which you chose during setup). You might want to put that export line in your .bashrc file.
Finally, to install the game on your device:
```bash
./gradew install
```
## License
See [LICENSE.md](LICENSE.md)

View file

@ -3,32 +3,32 @@ Thanks
We would like to send a special thanks to some of the most dedicated Apparatus and Principia players who have supported development and helped the community.
- Alfajim
- Axohmega
- BobMonkeypimp
- Cheddah
- Cralant
- Ctjet
- Demon666
- Doughnuts108
- Golden
- JOELwindows7
- Nighthawk
- Ridget
- Rubicon
- Sasha from Russia
- TechZ
- Tetsu
- The_Blacksmith_
- abrackers
- incrazyboyy
- mrsimb
- mznznlt
- sjoerd1999
- wokstation
- woodnut
- yo man
- zardOz (RIP)
Alfajim
Axohmega
BobMonkeypimp
Cheddah
Cralant
Ctjet
Demon666
Doughnuts108
Golden
JOELwindows7
Nighthawk
Ridget
Rubicon
Sasha from Russia
TechZ
Tetsu
The_Blacksmith_
abrackers
incrazyboyy
mrsimb
mznznlt
sjoerd1999
wokstation
woodnut
yo man
zardOz (RIP)
And everyone else in the Principia and Apparatus communities.

View file

@ -1,18 +0,0 @@
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'de.undercouch:gradle-download-task:4.1.1'
}
}
plugins {
id 'com.android.application' version '8.8.1' apply false
}
tasks.register('clean', Delete) {
delete rootProject.buildDir
}

Binary file not shown.

251
android/gradlew vendored
View file

@ -1,251 +0,0 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

@ -1 +0,0 @@
Subproject commit f3e15935e822228400064b66677bd3e9d3d7a643

View file

@ -1 +0,0 @@
../../../../data

View file

@ -1,21 +0,0 @@
package org.libsdl.app;
import android.hardware.usb.UsbDevice;
interface HIDDevice
{
public int getId();
public int getVendorId();
public int getProductId();
public String getSerialNumber();
public int getVersion();
public String getManufacturerName();
public String getProductName();
public UsbDevice getDevice();
public boolean open();
public int writeReport(byte[] report, boolean feature);
public boolean readReport(byte[] report, boolean feature);
public void setFrozen(boolean frozen);
public void close();
public void shutdown();
}

View file

@ -1,829 +0,0 @@
package org.libsdl.app;
import android.content.Context;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothGattService;
import android.hardware.usb.UsbDevice;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.os.*;
//import com.android.internal.util.HexDump;
import java.lang.Runnable;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.UUID;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
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 mHasEnabledNotifications = false;
private boolean mHasSeenInputUpdate = false;
private boolean mFrozen = false;
private LinkedList<GattOperation> mOperations;
GattOperation mCurrentOperation = null;
private Handler mHandler;
private int mProductId = -1;
private int mReportId = 0;
private UUID mInputCharacteristic;
private static final int D0G_BLE2_PID = 0x1106;
private static final int TRITON_BLE_PID = 0x1303;
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 final UUID steamControllerService = UUID.fromString("100F6C32-1735-4313-B402-38567131E5F3");
static final UUID inputCharacteristicD0G = UUID.fromString("100F6C33-1735-4313-B402-38567131E5F3");
static final UUID inputCharacteristicTriton_0x45 = UUID.fromString("100F6C7A-1735-4313-B402-38567131E5F3");
static final UUID inputCharacteristicTriton_0x47 = UUID.fromString("100F6C7C-1735-4313-B402-38567131E5F3");
static 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 };
private HashMap<Integer, BluetoothGattCharacteristic> mOutputReportChars = new HashMap<Integer, BluetoothGattCharacteristic>();
static class GattOperation {
private enum Operation {
CHR_READ,
CHR_WRITE,
ENABLE_NOTIFICATION
}
Operation mOp;
UUID mUuid;
byte[] mValue;
BluetoothGatt mGatt;
boolean mResult = true;
int mDelayMs = 0;
private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid) {
mGatt = gatt;
mOp = operation;
mUuid = uuid;
}
private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid, int delayMs) {
mGatt = gatt;
mOp = operation;
mUuid = uuid;
mDelayMs = delayMs;
}
private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid, byte[] value) {
mGatt = gatt;
mOp = operation;
mUuid = uuid;
mValue = value;
}
private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid, byte[] value, int delayMs) {
mGatt = gatt;
mOp = operation;
mUuid = uuid;
mValue = value;
mDelayMs = delayMs;
}
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;
}
public int getDelayMs() { return mDelayMs; }
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);
}
static public GattOperation enableNotification(BluetoothGatt gatt, UUID uuid, int delayMs) {
return new GattOperation(gatt, Operation.ENABLE_NOTIFICATION, uuid, delayMs);
}
}
HIDDeviceBLESteamController(HIDDeviceManager manager, BluetoothDevice device) {
mManager = manager;
mDevice = device;
mDeviceId = mManager.getDeviceIDForIdentifier(getIdentifier());
mIsRegistered = false;
mIsChromebook = SDLActivity.isChromebook();
mOperations = new LinkedList<GattOperation>();
mHandler = new Handler(Looper.getMainLooper());
mGatt = connectGatt();
mHasEnabledNotifications = false;
mHasSeenInputUpdate = false;
// final HIDDeviceBLESteamController finalThis = this;
// mHandler.postDelayed(new Runnable() {
// @Override
// void run() {
// finalThis.checkConnectionForChromebookIssue();
// }
// }, CHROMEBOOK_CONNECTION_CHECK_INTERVAL);
}
String getIdentifier() {
return String.format("SteamController.%s", mDevice.getAddress());
}
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);
}
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(inputCharacteristicTriton_0x45)) {
Log.v(TAG, "Found Triton input characteristic 0x45");
mProductId = TRITON_BLE_PID;
mReportId = 0x45;
mInputCharacteristic = chr.getUuid();
} else if (chr.getUuid().equals(inputCharacteristicTriton_0x47)) {
Log.v(TAG, "Found Triton input characteristic 0x47");
mProductId = TRITON_BLE_PID;
mReportId = 0x47;
mInputCharacteristic = chr.getUuid();
} else if (chr.getUuid().equals(inputCharacteristicD0G)) {
Log.v(TAG, "Found D0G input characteristic");
mProductId = D0G_BLE2_PID;
mReportId = 0x03;
mInputCharacteristic = chr.getUuid();
} else {
Pattern reportPattern = Pattern.compile("100F6C([0-9A-Z]{2})", Pattern.CASE_INSENSITIVE);
Matcher matcher = reportPattern.matcher(chr.getUuid().toString());
if (matcher.find()) {
try {
int reportId = Integer.parseInt(matcher.group(1), 16);
reportId -= 0x35;
if (reportId >= 0x80) {
// This is a Triton output report characteristic that we need to care about.
Log.v(TAG, "Found Triton output report 0x" + Integer.toString(reportId, 16));
mOutputReportChars.put(reportId, chr);
}
}
catch (NumberFormatException nfe) {
Log.w(TAG, "Could not parse report characteristic " + chr.getUuid().toString() + ": " + nfe.toString());
}
}
}
}
for (BluetoothGattCharacteristic chr : service.getCharacteristics()) {
if (chr.getUuid().equals(mInputCharacteristic)) {
// 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();
}
Runnable gattOperationRunnable = 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
}
}
};
if (mCurrentOperation.getDelayMs() == 0) {
// Run in main thread
mHandler.post(gattOperationRunnable);
}
else {
// If we have a delay on this operation, wait before we post it.
mHandler.postDelayed(gattOperationRunnable, mCurrentOperation.getDelayMs());
}
}
private void queueGattOperation(GattOperation op) {
synchronized (mOperations) {
mOperations.add(op);
}
executeNextGattOperation();
}
private void enableNotification(UUID chrUuid) {
// Add a 500ms delay to notification write for Amazon Fire TV devices, as otherwise if we do this too quickly after connecting
// it will return success and then silently drop the operation on the floor.
GattOperation op = HIDDeviceBLESteamController.GattOperation.enableNotification(mGatt, chrUuid, 500);
queueGattOperation(op);
// Amazon Fire devices can also silently timeout on writeDescriptor, so
// set up a little delayed check that will attempt to write a second time.
//
// While this only seems to be needed on Amazon Fire TV devices at present, it
// doesn't hurt to have a retry on other devices as well.
//
final HIDDeviceBLESteamController finalThis = this;
final UUID finalUuid = chrUuid;
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
if (!finalThis.mHasEnabledNotifications) {
if (finalThis.mHasSeenInputUpdate) {
// Amazon Five devices may have enabled notifications on the input characteristic and not given us a callback. If we've seen
// input reports, though, somewhat by definition notifications are enabled.
Log.w(TAG, "WriteDescriptor has never returned, but we've seen input reports. Moving on with controller initialization.");
finalThis.mHasEnabledNotifications = true;
finalThis.enableValveMode();
return;
}
// Give one more try.
GattOperation retry = HIDDeviceBLESteamController.GattOperation.enableNotification(finalThis.mGatt, finalUuid, 500);
finalThis.queueGattOperation(retry);
}
}
}, 1000);
}
void writeCharacteristic(UUID uuid, byte[] value) {
GattOperation op = HIDDeviceBLESteamController.GattOperation.writeCharacteristic(mGatt, uuid, value);
queueGattOperation(op);
}
void readCharacteristic(UUID uuid) {
GattOperation op = HIDDeviceBLESteamController.GattOperation.readCharacteristic(mGatt, uuid);
queueGattOperation(op);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
////////////// BluetoothGattCallback overridden methods
//////////////////////////////////////////////////////////////////////////////////////////////////////
@Override
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.
}
@Override
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 {
if (getProductId() == TRITON_BLE_PID) {
// Android will not properly play well with Data Length Extensions without manually requesting a large MTU,
// and Triton controllers require DLE support.
//
// 517 is basically a "magic number" as far as Android's bluetooth code is concerned, so do not change
// this value. It is functionally "please enable data length extensions" on some Android builds.
mGatt.requestMtu(517);
}
probeService(this);
}
}
}
@Override
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.HIDDeviceReportResponse(getId(), characteristic.getValue());
}
finishCurrentGattOperation();
}
@Override
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, true, mReportId);
setRegistered();
}
}
finishCurrentGattOperation();
}
@Override
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(mInputCharacteristic) && !mFrozen) {
mHasSeenInputUpdate = true;
mManager.HIDDeviceInputReport(getId(), characteristic.getValue());
}
}
@Override
public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
//Log.v(TAG, "onDescriptorRead status=" + status);
}
private void enableValveMode()
{
BluetoothGattService valveService = mGatt.getService(steamControllerService);
if (valveService == null)
return;
BluetoothGattCharacteristic reportChr = valveService.getCharacteristic(reportCharacteristic);
if (reportChr != null) {
if (getProductId() == TRITON_BLE_PID) {
// For Triton we just mark things registered.
Log.v(TAG, "Registering Triton Steam Controller with ID: " + getId());
mManager.HIDDeviceConnected(getId(), getIdentifier(), getVendorId(), getProductId(), getSerialNumber(), getVersion(), getManufacturerName(), getProductName(), 0, 0, 0, 0, true, mReportId);
setRegistered();
} else {
// For the original controller, we need to manually enter Valve mode.
Log.v(TAG, "Writing report characteristic to enter valve mode");
reportChr.setValue(enterValveMode);
mGatt.writeCharacteristic(reportChr);
}
}
}
@Override
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(mInputCharacteristic)) {
mHasEnabledNotifications = true;
enableValveMode();
}
finishCurrentGattOperation();
}
@Override
public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
//Log.v(TAG, "onReliableWriteCompleted status=" + status);
}
@Override
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
//Log.v(TAG, "onReadRemoteRssi status=" + status);
}
@Override
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() {
if (mProductId > 0) {
// We've already set a product ID.
return mProductId;
}
if (mDevice.getName().startsWith("Steam Ctrl")) {
// We're a newer Triton device
mProductId = TRITON_BLE_PID;
} else {
// We're an OG Steam Controller
mProductId = D0G_BLE2_PID;
}
return mProductId;
}
@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 writeReport(byte[] report, boolean feature) {
if (!isRegistered()) {
Log.e(TAG, "Attempted writeReport before Steam Controller is registered!");
if (mIsConnected) {
probeService(this);
}
return -1;
}
if (feature) {
// 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, "writeFeatureReport " + HexDump.dumpHexString(actual_report));
writeCharacteristic(reportCharacteristic, actual_report);
return report.length;
} else {
// If we're an original-recipe Steam Controller we just write to the characteristic directly.
if (getProductId() == D0G_BLE2_PID) {
//Log.v(TAG, "writeOutputReport " + HexDump.dumpHexString(report));
writeCharacteristic(reportCharacteristic, report);
return report.length;
}
// If we're a Triton, we need to find the correct report characteristic.
if (report.length > 0) {
int reportId = report[0] & 0xFF;
BluetoothGattCharacteristic targetedReportCharacteristic = mOutputReportChars.get(reportId);
if (targetedReportCharacteristic != null) {
byte[] actual_report = Arrays.copyOfRange(report, 1, report.length - 1);
//Log.v(TAG, "writeOutputReport 0x" + Integer.toString(reportId, 16) + " " + HexDump.dumpHexString(report));
writeCharacteristic(targetedReportCharacteristic.getUuid(), actual_report);
return report.length;
} else {
Log.w(TAG, "Got report write request for unknown report type 0x" + Integer.toString(reportId, 16));
}
}
}
return -1;
}
@Override
public boolean readReport(byte[] report, boolean feature) {
if (!isRegistered()) {
Log.e(TAG, "Attempted readReport before Steam Controller is registered!");
if (mIsConnected) {
probeService(this);
}
return false;
}
if (feature) {
readCharacteristic(reportCharacteristic);
return true;
} else {
// Not implemented
return false;
}
}
@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();
}
}

View file

@ -1,698 +0,0 @@
package org.libsdl.app;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.PendingIntent;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.os.Build;
import android.util.Log;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.hardware.usb.*;
import android.os.Handler;
import android.os.Looper;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
public class HIDDeviceManager {
private static final String TAG = "hidapi";
private static final String ACTION_USB_PERMISSION = "org.libsdl.app.USB_PERMISSION";
private static HIDDeviceManager sManager;
private static int sManagerRefCount = 0;
static public HIDDeviceManager acquire(Context context) {
if (sManagerRefCount == 0) {
sManager = new HIDDeviceManager(context);
}
++sManagerRefCount;
return sManager;
}
static public void release(HIDDeviceManager manager) {
if (manager == sManager) {
--sManagerRefCount;
if (sManagerRefCount == 0) {
sManager.close();
sManager = null;
}
}
}
private Context mContext;
private HashMap<Integer, HIDDevice> mDevicesById = new HashMap<Integer, HIDDevice>();
private HashMap<BluetoothDevice, HIDDeviceBLESteamController> mBluetoothDevices = new HashMap<BluetoothDevice, HIDDeviceBLESteamController>();
private int mNextDeviceId = 0;
private SharedPreferences mSharedPreferences = null;
private boolean mIsChromebook = false;
private UsbManager mUsbManager;
private Handler mHandler;
private BluetoothManager mBluetoothManager;
private List<BluetoothDevice> mLastBluetoothDevices;
private final BroadcastReceiver mUsbBroadcast = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
handleUsbDeviceAttached(usbDevice);
} else if (action.equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) {
UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
handleUsbDeviceDetached(usbDevice);
} else if (action.equals(HIDDeviceManager.ACTION_USB_PERMISSION)) {
UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
handleUsbDevicePermission(usbDevice, intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false));
}
}
};
private final BroadcastReceiver mBluetoothBroadcast = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
// Bluetooth device was connected. If it was a Steam Controller, handle it
if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
Log.d(TAG, "Bluetooth device connected: " + device);
if (isSteamController(device)) {
connectBluetoothDevice(device);
}
}
// Bluetooth device was disconnected, remove from controller manager (if any)
if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
Log.d(TAG, "Bluetooth device disconnected: " + device);
disconnectBluetoothDevice(device);
}
}
};
private HIDDeviceManager(final Context context) {
mContext = context;
HIDDeviceRegisterCallback();
mSharedPreferences = mContext.getSharedPreferences("hidapi", Context.MODE_PRIVATE);
mIsChromebook = SDLActivity.isChromebook();
// if (shouldClear) {
// SharedPreferences.Editor spedit = mSharedPreferences.edit();
// spedit.clear();
// spedit.apply();
// }
// else
{
mNextDeviceId = mSharedPreferences.getInt("next_device_id", 0);
}
}
Context getContext() {
return mContext;
}
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.apply();
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);
if (Build.VERSION.SDK_INT >= 33) { /* Android 13.0 (TIRAMISU) */
mContext.registerReceiver(mUsbBroadcast, filter, Context.RECEIVER_EXPORTED);
} else {
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
0x3537, // GameSir
0x37d7, // Flydigi
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
0x294b, // Snakebyte
0x2dc8, // 8BitDo
0x2e24, // Hyperkin
0x2e95, // SCUF
0x3285, // Nacon
0x3537, // GameSir
0x366c, // ByoWave
};
if (usbInterface.getId() == 0 &&
usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC &&
usbInterface.getInterfaceSubclass() == XB1_IFACE_SUBCLASS &&
usbInterface.getInterfaceProtocol() == XB1_IFACE_PROTOCOL) {
int vendor_id = usbDevice.getVendorId();
for (int supportedVid : SUPPORTED_VENDORS) {
if (vendor_id == supportedVid) {
return true;
}
}
}
return false;
}
private void handleUsbDeviceAttached(UsbDevice usbDevice) {
connectHIDDeviceUSB(usbDevice);
}
private void handleUsbDeviceDetached(UsbDevice usbDevice) {
List<Integer> devices = new ArrayList<Integer>();
for (HIDDevice device : mDevicesById.values()) {
if (usbDevice.equals(device.getDevice())) {
devices.add(device.getId());
}
}
for (int id : devices) {
HIDDevice device = mDevicesById.get(id);
mDevicesById.remove(id);
device.shutdown();
HIDDeviceDisconnected(id);
}
}
private void handleUsbDevicePermission(UsbDevice usbDevice, boolean permission_granted) {
for (HIDDevice device : mDevicesById.values()) {
if (usbDevice.equals(device.getDevice())) {
boolean opened = false;
if (permission_granted) {
opened = device.open();
}
HIDDeviceOpenResult(device.getId(), opened);
}
}
}
private void connectHIDDeviceUSB(UsbDevice usbDevice) {
synchronized (this) {
int interface_mask = 0;
for (int interface_index = 0; interface_index < usbDevice.getInterfaceCount(); interface_index++) {
UsbInterface usbInterface = usbDevice.getInterface(interface_index);
if (isHIDDeviceInterface(usbDevice, usbInterface)) {
// Check to see if we've already added this interface
// This happens with the Xbox Series X controller which has a duplicate interface 0, which is inactive
int interface_id = usbInterface.getId();
if ((interface_mask & (1 << interface_id)) != 0) {
continue;
}
interface_mask |= (1 << interface_id);
HIDDeviceUSB device = new HIDDeviceUSB(this, usbDevice, interface_index);
int id = device.getId();
mDevicesById.put(id, device);
HIDDeviceConnected(id, device.getIdentifier(), device.getVendorId(), device.getProductId(), device.getSerialNumber(), device.getVersion(), device.getManufacturerName(), device.getProductName(), usbInterface.getId(), usbInterface.getInterfaceClass(), usbInterface.getInterfaceSubclass(), usbInterface.getInterfaceProtocol(), false, 0);
}
}
}
}
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)) {
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);
if (Build.VERSION.SDK_INT >= 33) { /* Android 13.0 (TIRAMISU) */
mContext.registerReceiver(mBluetoothBroadcast, filter, Context.RECEIVER_EXPORTED);
} else {
mContext.registerReceiver(mBluetoothBroadcast, filter);
}
if (mIsChromebook) {
mHandler = new Handler(Looper.getMainLooper());
mLastBluetoothDevices = new ArrayList<BluetoothDevice>();
// final HIDDeviceManager finalThis = this;
// mHandler.postDelayed(new Runnable() {
// @Override
// public void run() {
// finalThis.chromebookConnectionHandler();
// }
// }, 5000);
}
}
private void shutdownBluetooth() {
try {
mContext.unregisterReceiver(mBluetoothBroadcast);
} catch (Exception e) {
// We may not have registered, that's okay
}
}
// Chromebooks do not pass along ACTION_ACL_CONNECTED / ACTION_ACL_DISCONNECTED properly.
// This function provides a sort of dummy version of that, watching for changes in the
// connected devices and attempting to add controllers as things change.
void chromebookConnectionHandler() {
if (!mIsChromebook) {
return;
}
ArrayList<BluetoothDevice> disconnected = new ArrayList<BluetoothDevice>();
ArrayList<BluetoothDevice> connected = new ArrayList<BluetoothDevice>();
List<BluetoothDevice> currentConnected = mBluetoothManager.getConnectedDevices(BluetoothProfile.GATT);
for (BluetoothDevice bluetoothDevice : currentConnected) {
if (!mLastBluetoothDevices.contains(bluetoothDevice)) {
connected.add(bluetoothDevice);
}
}
for (BluetoothDevice bluetoothDevice : mLastBluetoothDevices) {
if (!currentConnected.contains(bluetoothDevice)) {
disconnected.add(bluetoothDevice);
}
}
mLastBluetoothDevices = currentConnected;
for (BluetoothDevice bluetoothDevice : disconnected) {
disconnectBluetoothDevice(bluetoothDevice);
}
for (BluetoothDevice bluetoothDevice : connected) {
connectBluetoothDevice(bluetoothDevice);
}
final HIDDeviceManager finalThis = this;
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
finalThis.chromebookConnectionHandler();
}
}, 10000);
}
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;
}
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);
}
}
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;
}
// Steam Controllers will always support Bluetooth Low Energy
if ((bluetoothDevice.getType() & BluetoothDevice.DEVICE_TYPE_LE) == 0) {
return false;
}
// Match on the name either the original Steam Controller or the new second-generation one advertise with.
return bluetoothDevice.getName().equals("SteamController") || bluetoothDevice.getName().startsWith("Steam Ctrl");
}
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
//////////////////////////////////////////////////////////////////////////////////////////////////////
boolean initialize(boolean usb, boolean bluetooth) {
Log.v(TAG, "initialize(" + usb + ", " + bluetooth + ")");
if (usb) {
initializeUSB();
}
if (bluetooth) {
initializeBluetooth();
}
return true;
}
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;
}
Intent intent = new Intent(HIDDeviceManager.ACTION_USB_PERMISSION);
intent.setPackage(mContext.getPackageName());
mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, intent, 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;
}
int writeReport(int deviceID, byte[] report, boolean feature) {
try {
//Log.v(TAG, "writeReport deviceID=" + deviceID + " length=" + report.length);
HIDDevice device;
device = getDevice(deviceID);
if (device == null) {
HIDDeviceDisconnected(deviceID);
return -1;
}
return device.writeReport(report, feature);
} catch (Exception e) {
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
}
return -1;
}
boolean readReport(int deviceID, byte[] report, boolean feature) {
try {
//Log.v(TAG, "readReport deviceID=" + deviceID);
HIDDevice device;
device = getDevice(deviceID);
if (device == null) {
HIDDeviceDisconnected(deviceID);
return false;
}
return device.readReport(report, feature);
} catch (Exception e) {
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
}
return false;
}
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, boolean bBluetooth, int reportID);
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 HIDDeviceReportResponse(int deviceID, byte[] report);
}

View file

@ -1,354 +0,0 @@
package org.libsdl.app;
import android.hardware.usb.*;
import android.os.Build;
import android.util.Log;
import java.util.Arrays;
import java.util.Locale;
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;
protected boolean mClaimed;
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;
mClaimed = false;
}
String getIdentifier() {
return String.format(Locale.ENGLISH, "%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;
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;
result = mDevice.getManufacturerName();
if (result == null) {
result = String.format("%x", getVendorId());
}
return result;
}
@Override
public String getProductName() {
String result;
result = mDevice.getProductName();
if (result == null) {
result = String.format("%x", getProductId());
}
return result;
}
@Override
public UsbDevice getDevice() {
return mDevice;
}
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;
}
mClaimed = true;
// 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. The original Steam Controller and the wireless dongle for it do NOT
// actually have -- or require -- output endpoints, so we need to accept only an input one for them or else we'll fall
// back to the Android system gamepad functionality (and lose our paddles et al).
if (mInputEndpoint == null) {
Log.w(TAG, "Missing required endpoint on USB device " + getDeviceName());
mConnection.releaseInterface(iface);
close();
return false;
}
// Start listening for input
mRunning = true;
mInputThread = new InputThread();
mInputThread.start();
return true;
}
@Override
public int writeReport(byte[] report, boolean feature) {
if (mConnection == null) {
Log.w(TAG, "writeReport() called with no device connection");
return -1;
}
if (!mClaimed) {
Log.w(TAG, "writeReport() called but some other process currently owns the USB device");
return -1;
}
if (feature) {
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, "writeFeatureReport() returned " + res + " on device " + getDeviceName());
return -1;
}
if (skipped_report_id) {
++length;
}
return length;
} else {
if (mOutputEndpoint == null)
{
Log.e(TAG, "Tried to write an output report to an interface with no output endpoint!");
return -1;
}
int res = mConnection.bulkTransfer(mOutputEndpoint, report, report.length, 1000);
if (res != report.length) {
Log.w(TAG, "writeOutputReport() returned " + res + " on device " + getDeviceName());
}
return res;
}
}
@Override
public boolean readReport(byte[] report, boolean feature) {
int res = -1;
int offset = 0;
int length = report.length;
boolean skipped_report_id = false;
byte report_number = report[0];
if (mConnection == null) {
Log.w(TAG, "readReport() called with no device connection");
return false;
}
if (!mClaimed) {
if (feature) {
return false;
}
return true;
}
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*/,
((feature ? 3/*HID feature*/ : 1/*HID Input*/) << 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.HIDDeviceReportResponse(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) {
if (mClaimed) {
UsbInterface iface = mDevice.getInterface(mInterfaceIndex);
mConnection.releaseInterface(iface);
}
mConnection.close();
mConnection = null;
mClaimed = false;
}
}
@Override
public void shutdown() {
close();
mManager = null;
}
@Override
public void setFrozen(boolean frozen) {
mFrozen = frozen;
/* If we have a valid device connection and the claim state doesn't match what we want, try to correct that. */
if (mConnection != null && mClaimed == mFrozen) {
UsbInterface iface = mDevice.getInterface(mInterfaceIndex);
if (frozen) {
mClaimed = !mConnection.releaseInterface(iface);
if (mClaimed) {
Log.e(TAG, "Tried to release claim on USB device, but failed!");
}
} else {
mClaimed = mConnection.claimInterface(iface, true);
if (!mClaimed) {
Log.e(TAG, "Tried to regain claim on USB device, but failed!");
}
}
}
}
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);
}
}
}
}
}
}

View file

@ -1,90 +0,0 @@
package org.libsdl.app;
import android.app.Activity;
import android.content.Context;
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
static public void setupJNI() {
SDLActivity.nativeSetupJNI();
SDLAudioManager.nativeSetupJNI();
SDLControllerManager.nativeSetupJNI();
}
// This function should be called each time the activity is started
static public void initialize() {
setContext(null);
SDLActivity.initialize();
SDLAudioManager.initialize();
SDLControllerManager.initialize();
}
// This function stores the current activity (SDL or not)
static public void setContext(Activity context) {
SDLAudioManager.setContext(context);
mContext = context;
}
static public Activity getContext() {
return mContext;
}
static void loadLibrary(String libraryName) throws UnsatisfiedLinkError, SecurityException, NullPointerException {
loadLibrary(libraryName, mContext);
}
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 Activity mContext;
}

File diff suppressed because it is too large Load diff

View file

@ -1,126 +0,0 @@
package org.libsdl.app;
import android.content.Context;
import android.media.AudioDeviceCallback;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.os.Build;
import android.util.Log;
import java.util.Arrays;
import java.util.ArrayList;
class SDLAudioManager {
protected static final String TAG = "SDLAudio";
protected static Context mContext;
private static AudioDeviceCallback mAudioDeviceCallback;
static void initialize() {
mAudioDeviceCallback = null;
if(Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */)
{
mAudioDeviceCallback = new AudioDeviceCallback() {
@Override
public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
for (AudioDeviceInfo deviceInfo : addedDevices) {
nativeAddAudioDevice(deviceInfo.isSink(), deviceInfo.getProductName().toString(), deviceInfo.getId());
}
}
@Override
public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
for (AudioDeviceInfo deviceInfo : removedDevices) {
nativeRemoveAudioDevice(deviceInfo.isSink(), deviceInfo.getId());
}
}
};
}
}
static void setContext(Context context) {
mContext = context;
}
static void release(Context context) {
// no-op atm
}
// Audio
private static AudioDeviceInfo getInputAudioDeviceInfo(int deviceId) {
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
for (AudioDeviceInfo deviceInfo : audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)) {
if (deviceInfo.getId() == deviceId) {
return deviceInfo;
}
}
}
return null;
}
private static AudioDeviceInfo getPlaybackAudioDeviceInfo(int deviceId) {
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
for (AudioDeviceInfo deviceInfo : audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)) {
if (deviceInfo.getId() == deviceId) {
return deviceInfo;
}
}
}
return null;
}
static void registerAudioDeviceCallback() {
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
// get an initial list now, before hotplug callbacks fire.
for (AudioDeviceInfo dev : audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)) {
if (dev.getType() == AudioDeviceInfo.TYPE_TELEPHONY) {
continue; // Device cannot be opened
}
nativeAddAudioDevice(dev.isSink(), dev.getProductName().toString(), dev.getId());
}
for (AudioDeviceInfo dev : audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)) {
nativeAddAudioDevice(dev.isSink(), dev.getProductName().toString(), dev.getId());
}
audioManager.registerAudioDeviceCallback(mAudioDeviceCallback, null);
}
}
static void unregisterAudioDeviceCallback() {
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
audioManager.unregisterAudioDeviceCallback(mAudioDeviceCallback);
}
}
/** This method is called by SDL using JNI. */
static void audioSetThreadPriority(boolean recording, int device_id) {
try {
/* Set thread name */
if (recording) {
Thread.currentThread().setName("SDLAudioC" + device_id);
} else {
Thread.currentThread().setName("SDLAudioP" + device_id);
}
/* Set thread priority */
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_AUDIO);
} catch (Exception e) {
Log.v(TAG, "modify thread properties failed " + e.toString());
}
}
static native void nativeSetupJNI();
static native void nativeRemoveAudioDevice(boolean recording, int deviceId);
static native void nativeAddAudioDevice(boolean recording, String name, int deviceId);
}

View file

@ -1,66 +0,0 @@
package org.libsdl.app;
import android.content.*;
import android.text.InputType;
import android.view.*;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
/* This is a fake invisible editor view that receives the input and defines the
* pan&scan region
*/
public class SDLDummyEdit extends View implements View.OnKeyListener
{
InputConnection ic;
int input_type;
SDLDummyEdit(Context context) {
super(context);
setFocusableInTouchMode(true);
setFocusable(true);
setOnKeyListener(this);
}
void setInputType(int input_type) {
this.input_type = input_type;
}
@Override
public boolean onCheckIsTextEditor() {
return true;
}
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
return SDLActivity.handleKeyEvent(v, keyCode, event, ic);
}
//
@Override
public boolean onKeyPreIme (int keyCode, KeyEvent event) {
// As seen on StackOverflow: http://stackoverflow.com/questions/7634346/keyboard-hide-event
// FIXME: Discussion at http://bugzilla.libsdl.org/show_bug.cgi?id=1639
// FIXME: This is not a 100% effective solution to the problem of detecting if the keyboard is showing or not
// FIXME: A more effective solution would be to assume our Layout to be RelativeLayout or LinearLayout
// FIXME: And determine the keyboard presence doing this: http://stackoverflow.com/questions/2150078/how-to-check-visibility-of-software-keyboard-in-android
// FIXME: An even more effective way would be if Android provided this out of the box, but where would the fun be in that :)
if (event.getAction()==KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
if (SDLActivity.mTextEdit != null && SDLActivity.mTextEdit.getVisibility() == View.VISIBLE) {
SDLActivity.onNativeKeyboardFocusLost();
}
}
return super.onKeyPreIme(keyCode, event);
}
@Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
ic = new SDLInputConnection(this, true);
outAttrs.inputType = input_type;
outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI |
EditorInfo.IME_FLAG_NO_FULLSCREEN /* API 11 */;
return ic;
}
}

View file

@ -1,136 +0,0 @@
package org.libsdl.app;
import android.content.*;
import android.os.Build;
import android.text.Editable;
import android.view.*;
import android.view.inputmethod.BaseInputConnection;
import android.widget.EditText;
class SDLInputConnection extends BaseInputConnection
{
protected EditText mEditText;
protected String mCommittedText = "";
SDLInputConnection(View targetView, boolean fullEditor) {
super(targetView, fullEditor);
mEditText = new EditText(SDL.getContext());
}
@Override
public Editable getEditable() {
return mEditText.getEditableText();
}
@Override
public boolean sendKeyEvent(KeyEvent event) {
/*
* This used to handle the keycodes from soft keyboard (and IME-translated input from hardkeyboard)
* However, as of Ice Cream Sandwich and later, almost all soft keyboard doesn't generate key presses
* and so we need to generate them ourselves in commitText. To avoid duplicates on the handful of keys
* that still do, we empty this out.
*/
/*
* Return DOES still generate a key event, however. So rather than using it as the 'click a button' key
* as we do with physical keyboards, let's just use it to hide the keyboard.
*/
if (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) {
if (SDLActivity.onNativeSoftReturnKey()) {
return true;
}
}
return super.sendKeyEvent(event);
}
@Override
public boolean commitText(CharSequence text, int newCursorPosition) {
if (!super.commitText(text, newCursorPosition)) {
return false;
}
updateText();
return true;
}
@Override
public boolean setComposingText(CharSequence text, int newCursorPosition) {
if (!super.setComposingText(text, newCursorPosition)) {
return false;
}
updateText();
return true;
}
@Override
public boolean deleteSurroundingText(int beforeLength, int afterLength) {
// Workaround to capture backspace key. Ref: http://stackoverflow.com/questions>/14560344/android-backspace-in-webview-baseinputconnection
// and https://bugzilla.libsdl.org/show_bug.cgi?id=2265
if (beforeLength > 0 && afterLength == 0) {
// backspace(s)
while (beforeLength-- > 0) {
nativeGenerateScancodeForUnichar('\b');
}
return true;
}
if (!super.deleteSurroundingText(beforeLength, afterLength)) {
return false;
}
updateText();
return true;
}
protected void updateText() {
final Editable content = getEditable();
if (content == null) {
return;
}
String text = content.toString();
int compareLength = Math.min(text.length(), mCommittedText.length());
int matchLength, offset;
/* Backspace over characters that are no longer in the string */
for (matchLength = 0; matchLength < compareLength; ) {
int codePoint = mCommittedText.codePointAt(matchLength);
if (codePoint != text.codePointAt(matchLength)) {
break;
}
matchLength += Character.charCount(codePoint);
}
/* FIXME: This doesn't handle graphemes, like '🌬️' */
for (offset = matchLength; offset < mCommittedText.length(); ) {
int codePoint = mCommittedText.codePointAt(offset);
nativeGenerateScancodeForUnichar('\b');
offset += Character.charCount(codePoint);
}
if (matchLength < text.length()) {
String pendingText = text.subSequence(matchLength, text.length()).toString();
if (!SDLActivity.dispatchingKeyEvent()) {
for (offset = 0; offset < pendingText.length(); ) {
int codePoint = pendingText.codePointAt(offset);
if (codePoint == '\n') {
if (SDLActivity.onNativeSoftReturnKey()) {
return;
}
}
/* Higher code points don't generate simulated scancodes */
if (codePoint > 0 && codePoint < 128) {
nativeGenerateScancodeForUnichar((char)codePoint);
}
offset += Character.charCount(codePoint);
}
}
SDLInputConnection.nativeCommitText(pendingText, 0);
}
mCommittedText = text;
}
public static native void nativeCommitText(String text, int newCursorPosition);
public static native void nativeGenerateScancodeForUnichar(char c);
}

View file

@ -1,32 +0,0 @@
package org.libsdl.app;
import android.hardware.Sensor;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
// This class coordinates synchronized access to sensor manager registration
//
// This prevents a java.util.ConcurrentModificationException exception on
// Android 16, specifically on the Samsung Tab S9 Ultra.
class SDLSensorManager
{
static private SDLSensorManager mManager = new SDLSensorManager();
public static void registerListener(SensorManager manager, SensorEventListener listener, Sensor sensor, int samplingPeriodUs) {
mManager.RegisterListener(manager, listener, sensor, samplingPeriodUs);
}
public static void unregisterListener(SensorManager manager, SensorEventListener listener, Sensor sensor) {
mManager.UnregisterListener(manager, listener, sensor);
}
private synchronized void RegisterListener(SensorManager manager, SensorEventListener listener, Sensor sensor, int samplingPeriodUs) {
manager.registerListener(listener, sensor, samplingPeriodUs, null);
}
private synchronized void UnregisterListener(SensorManager manager, SensorEventListener listener, Sensor sensor) {
manager.unregisterListener(listener, sensor);
}
}

View file

@ -1,464 +0,0 @@
package org.libsdl.app;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.graphics.Insets;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Build;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.PointerIcon;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.ScaleGestureDetector;
/**
SDLSurface. This is what we draw on, so we need to know when it's created
in order to do anything useful.
Because of this, that's where we set up the SDL thread
*/
public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
View.OnApplyWindowInsetsListener, View.OnKeyListener, View.OnTouchListener,
SensorEventListener, ScaleGestureDetector.OnScaleGestureListener {
// Sensors
protected SensorManager mSensorManager;
protected Display mDisplay;
// Keep track of the surface size to normalize touch events
protected float mWidth, mHeight;
// Is SurfaceView ready for rendering
protected boolean mIsSurfaceReady;
// Is on-screen keyboard visible
protected boolean mKeyboardVisible;
// Pinch events
private final ScaleGestureDetector scaleGestureDetector;
// Startup
protected SDLSurface(Context context) {
super(context);
getHolder().addCallback(this);
scaleGestureDetector = new ScaleGestureDetector(context, this);
setFocusable(true);
setFocusableInTouchMode(true);
requestFocus();
setOnApplyWindowInsetsListener(this);
setOnKeyListener(this);
setOnTouchListener(this);
mDisplay = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
setOnGenericMotionListener(SDLActivity.getMotionListener());
// Some arbitrary defaults to avoid a potential division by zero
mWidth = 1.0f;
mHeight = 1.0f;
mIsSurfaceReady = false;
}
protected void handlePause() {
enableSensor(Sensor.TYPE_ACCELEROMETER, false);
}
protected void handleResume() {
setFocusable(true);
setFocusableInTouchMode(true);
requestFocus();
setOnApplyWindowInsetsListener(this);
setOnKeyListener(this);
setOnTouchListener(this);
enableSensor(Sensor.TYPE_ACCELEROMETER, true);
}
protected Surface getNativeSurface() {
return getHolder().getSurface();
}
// Called when we have a valid drawing surface
@Override
public void surfaceCreated(SurfaceHolder holder) {
Log.v("SDL", "surfaceCreated()");
SDLActivity.onNativeSurfaceCreated();
}
// Called when we lose the surface
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.v("SDL", "surfaceDestroyed()");
// Transition to pause, if needed
SDLActivity.mNextNativeState = SDLActivity.NativeState.PAUSED;
SDLActivity.handleNativeState();
mIsSurfaceReady = false;
SDLActivity.onNativeSurfaceDestroyed();
}
// Called when the surface is resized
@Override
public void surfaceChanged(SurfaceHolder holder,
int format, int width, int height) {
Log.v("SDL", "surfaceChanged()");
if (SDLActivity.mSingleton == null) {
return;
}
mWidth = width;
mHeight = height;
int nDeviceWidth = width;
int nDeviceHeight = height;
float density = 1.0f;
try
{
DisplayMetrics realMetrics = new DisplayMetrics();
mDisplay.getRealMetrics( realMetrics );
nDeviceWidth = realMetrics.widthPixels;
nDeviceHeight = realMetrics.heightPixels;
// Use densityDpi instead of density to more closely match what the UI scale is
density = (float)realMetrics.densityDpi / 160.0f;
} catch(Exception ignored) {
}
synchronized(SDLActivity.getContext()) {
// In case we're waiting on a size change after going fullscreen, send a notification.
SDLActivity.getContext().notifyAll();
}
Log.v("SDL", "Window size: " + width + "x" + height);
Log.v("SDL", "Device size: " + nDeviceWidth + "x" + nDeviceHeight);
SDLActivity.nativeSetScreenResolution(width, height, nDeviceWidth, nDeviceHeight, density, mDisplay.getRefreshRate());
SDLActivity.onNativeResize();
// Prevent a screen distortion glitch,
// for instance when the device is in Landscape and a Portrait App is resumed.
boolean skip = false;
int requestedOrientation = SDLActivity.mSingleton.getRequestedOrientation();
if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT) {
if (mWidth > mHeight) {
skip = true;
}
} else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE) {
if (mWidth < mHeight) {
skip = true;
}
}
// Special Patch for Square Resolution: Black Berry Passport
if (skip) {
double min = Math.min(mWidth, mHeight);
double max = Math.max(mWidth, mHeight);
if (max / min < 1.20) {
Log.v("SDL", "Don't skip on such aspect-ratio. Could be a square resolution.");
skip = false;
}
}
// Don't skip if we might be multi-window or have popup dialogs
if (skip) {
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
skip = false;
}
}
if (skip) {
Log.v("SDL", "Skip .. Surface is not ready.");
mIsSurfaceReady = false;
return;
}
/* If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here */
SDLActivity.onNativeSurfaceChanged();
/* Surface is ready */
mIsSurfaceReady = true;
SDLActivity.mNextNativeState = SDLActivity.NativeState.RESUMED;
SDLActivity.handleNativeState();
}
// Window inset
@Override
public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
if (Build.VERSION.SDK_INT >= 30 /* Android 11 (R) */) {
Insets combined = insets.getInsets(WindowInsets.Type.systemBars() |
WindowInsets.Type.systemGestures() |
WindowInsets.Type.mandatorySystemGestures() |
WindowInsets.Type.tappableElement() |
WindowInsets.Type.displayCutout());
SDLActivity.onNativeInsetsChanged(combined.left, combined.right, combined.top, combined.bottom);
if (insets.isVisible(WindowInsets.Type.ime())) {
if (!mKeyboardVisible) {
mKeyboardVisible = true;
SDLActivity.onNativeScreenKeyboardShown();
}
} else {
if (mKeyboardVisible) {
mKeyboardVisible = false;
SDLActivity.onNativeScreenKeyboardHidden();
}
}
}
// Pass these to any child views in case they need them
return insets;
}
// Key events
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
return SDLActivity.handleKeyEvent(v, keyCode, event, null);
}
private float getNormalizedX(float x)
{
if (mWidth <= 1) {
return 0.5f;
} else {
return (x / (mWidth - 1));
}
}
private float getNormalizedY(float y)
{
if (mHeight <= 1) {
return 0.5f;
} else {
return (y / (mHeight - 1));
}
}
// Touch events
@Override
public boolean onTouch(View v, MotionEvent event) {
/* Ref: http://developer.android.com/training/gestures/multi.html */
int touchDevId = event.getDeviceId();
final int pointerCount = event.getPointerCount();
int action = event.getActionMasked();
int pointerId;
int i = 0;
float x,y,p;
if (action == MotionEvent.ACTION_POINTER_UP || action == MotionEvent.ACTION_POINTER_DOWN)
i = event.getActionIndex();
do {
int toolType = event.getToolType(i);
if (toolType == MotionEvent.TOOL_TYPE_MOUSE) {
int buttonState = event.getButtonState();
boolean relative = false;
// We need to check if we're in relative mouse mode and get the axis offset rather than the x/y values
// if we are. We'll leverage our existing mouse motion listener
SDLGenericMotionListener_API14 motionListener = SDLActivity.getMotionListener();
x = motionListener.getEventX(event, i);
y = motionListener.getEventY(event, i);
relative = motionListener.inRelativeMode();
SDLActivity.onNativeMouse(buttonState, action, x, y, relative);
} else if (toolType == MotionEvent.TOOL_TYPE_STYLUS || toolType == MotionEvent.TOOL_TYPE_ERASER) {
pointerId = event.getPointerId(i);
x = event.getX(i);
y = event.getY(i);
p = event.getPressure(i);
if (p > 1.0f) {
// may be larger than 1.0f on some devices
// see the documentation of getPressure(i)
p = 1.0f;
}
// BUTTON_STYLUS_PRIMARY is 2^5, so shift by 4, and apply SDL_PEN_INPUT_DOWN/SDL_PEN_INPUT_ERASER_TIP
int buttonState = (event.getButtonState() >> 4) | (1 << (toolType == MotionEvent.TOOL_TYPE_STYLUS ? 0 : 30));
if ((event.getButtonState() & MotionEvent.BUTTON_TERTIARY) != 0) {
buttonState |= 0x08;
}
SDLActivity.onNativePen(pointerId, SDLActivity.getMotionListener().getPenDeviceType(event.getDevice()), buttonState, action, x, y, p);
} else { // MotionEvent.TOOL_TYPE_FINGER or MotionEvent.TOOL_TYPE_UNKNOWN
pointerId = event.getPointerId(i);
x = getNormalizedX(event.getX(i));
y = getNormalizedY(event.getY(i));
p = event.getPressure(i);
if (p > 1.0f) {
// may be larger than 1.0f on some devices
// see the documentation of getPressure(i)
p = 1.0f;
}
SDLActivity.onNativeTouch(touchDevId, pointerId, action, x, y, p);
}
// Non-primary up/down
if (action == MotionEvent.ACTION_POINTER_UP || action == MotionEvent.ACTION_POINTER_DOWN)
break;
} while (++i < pointerCount);
scaleGestureDetector.onTouchEvent(event);
return true;
}
// Sensor events
protected void enableSensor(int sensortype, boolean enabled) {
// TODO: This uses getDefaultSensor - what if we have >1 accels?
if (enabled) {
SDLSensorManager.registerListener(mSensorManager, this,
mSensorManager.getDefaultSensor(sensortype),
SensorManager.SENSOR_DELAY_GAME);
} else {
SDLSensorManager.unregisterListener(mSensorManager, this,
mSensorManager.getDefaultSensor(sensortype));
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// TODO
}
@Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
// Since we may have an orientation set, we won't receive onConfigurationChanged events.
// We thus should check here.
int newRotation;
float x, y;
switch (mDisplay.getRotation()) {
case Surface.ROTATION_0:
default:
x = event.values[0];
y = event.values[1];
newRotation = 0;
break;
case Surface.ROTATION_90:
x = -event.values[1];
y = event.values[0];
newRotation = 90;
break;
case Surface.ROTATION_180:
x = -event.values[0];
y = -event.values[1];
newRotation = 180;
break;
case Surface.ROTATION_270:
x = event.values[1];
y = -event.values[0];
newRotation = 270;
break;
}
if (newRotation != SDLActivity.mCurrentRotation) {
SDLActivity.mCurrentRotation = newRotation;
SDLActivity.onNativeRotationChanged(newRotation);
}
SDLActivity.onNativeAccel(-x / SensorManager.GRAVITY_EARTH,
y / SensorManager.GRAVITY_EARTH,
event.values[2] / SensorManager.GRAVITY_EARTH);
}
}
// Prevent android internal NullPointerException (https://github.com/libsdl-org/SDL/issues/13306)
@Override
public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
try {
return super.onResolvePointerIcon(event, pointerIndex);
} catch (NullPointerException e) {
return null;
}
}
// Captured pointer events for API 26.
@Override
public boolean onCapturedPointerEvent(MotionEvent event)
{
int action = event.getActionMasked();
int pointerCount = event.getPointerCount();
for (int i = 0; i < pointerCount; i++) {
float x, y;
switch (action) {
case MotionEvent.ACTION_SCROLL:
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, i);
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, i);
SDLActivity.onNativeMouse(0, action, x, y, false);
return true;
case MotionEvent.ACTION_HOVER_MOVE:
case MotionEvent.ACTION_MOVE:
x = event.getX(i);
y = event.getY(i);
SDLActivity.onNativeMouse(0, action, x, y, true);
return true;
case MotionEvent.ACTION_BUTTON_PRESS:
case MotionEvent.ACTION_BUTTON_RELEASE:
// Change our action value to what SDL's code expects.
if (action == MotionEvent.ACTION_BUTTON_PRESS) {
action = MotionEvent.ACTION_DOWN;
} else { /* MotionEvent.ACTION_BUTTON_RELEASE */
action = MotionEvent.ACTION_UP;
}
x = event.getX(i);
y = event.getY(i);
int button = event.getButtonState();
SDLActivity.onNativeMouse(button, action, x, y, true);
return true;
}
}
return false;
}
@Override
public boolean onScale(ScaleGestureDetector detector) {
float scale = detector.getScaleFactor();
SDLActivity.onNativePinchUpdate(scale);
return true;
}
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
SDLActivity.onNativePinchStart();
return true;
}
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
SDLActivity.onNativePinchEnd();
}
}

View file

@ -1,32 +0,0 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<style name="PrincipiaTheme" parent="@style/Theme.AppCompat">
<item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowFullscreen">true</item>
</style>
<style name="ScrollViewBody" parent="@android:style/Widget.ScrollView">
<item name="android:padding">8dp</item>
</style>
<style name="TinyDialog" parent="@style/Theme.AppCompat.Dialog">
<!-- Fill the screen -->
<item name="android:layout_width">fill_parent</item>
<item name="android:layout_height">fill_parent</item>
<!-- No backgrounds, titles or window float -->
<item name="android:windowBackground">@null</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowIsFloating">false</item>
</style>
<style name="DialogNoAnimation">
<item name="android:windowEnterAnimation">@anim/enter</item>
<item name="android:windowExitAnimation">@anim/exit</item>
</style>
<style name="CodeText">
<item name="android:typeface">monospace</item>
</style>
<style name="ButtonBar" parent="@android:style/ButtonBar">
</style>
</resources>

View file

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="false">
<trust-anchors>
<certificates src="system" />
<certificates src="user" />
</trust-anchors>
</base-config>
</network-security-config>

View file

@ -1,8 +1,7 @@
.gradle/
.idea/
deps
deps/
principia/build/
*.apk
*.apk.idsig
local.properties
.cxx/

View file

@ -0,0 +1,19 @@
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'de.undercouch:gradle-download-task:4.1.1'
}
}
plugins {
id 'com.android.application' version '7.3.1' apply false
id 'com.android.library' version '7.3.1' apply false
}
task clean(type: Delete) {
delete rootProject.buildDir
}

View file

@ -5,4 +5,3 @@ android.nonTransitiveRClass=true
org.gradle.daemon=true
org.gradle.parallel=true
org.gradle.parallel.threads=8
android.nonFinalResIds=false

Binary file not shown.

View file

@ -1,7 +1,6 @@
#Sun Aug 07 11:51:41 CEST 2022
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME

185
build-android/gradlew vendored Executable file
View file

@ -0,0 +1,185 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

1
build-android/jni Symbolic link
View file

@ -0,0 +1 @@
../src

View file

@ -4,33 +4,33 @@ plugins {
}
android {
compileSdk 35
ndkVersion "27.2.12479018"
namespace 'com.bithack.principia'
compileSdk 33
ndkVersion "23.2.8568313"
defaultConfig {
applicationId "com.bithack.principia"
minSdk 21
targetSdk 35
versionCode 41
versionName "2026.06.19"
minSdk 19
targetSdk 33
versionCode 35
versionName "2024.02.29"
externalNativeBuild {
cmake.arguments "-DUSE_VENDORED_SDL3=1"
ndkBuild {
arguments '-j' + Runtime.getRuntime().availableProcessors()
}
}
ndk {
abiFilters "arm64-v8a", "armeabi-v7a", "x86_64"
//abiFilters "arm64-v8a" // debugging on phone
//abiFilters "x86_64" // debugging on emulator
// "x86", "x86-64"
}
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
externalNativeBuild {
cmake {
path file("../../CMakeLists.txt")
ndkBuild {
path = file("../jni/Android.mk")
}
}
@ -56,20 +56,19 @@ dependencies {
}
// get precompiled deps
tasks.register('downloadDeps', Download) {
src 'https://github.com/principia-game/android-deps/releases/download/latest/deps.zip'
dest new File(buildDir, 'deps.zip')
overwrite false
task downloadDeps(type: Download) {
src 'https://github.com/principia-game/principia-android-deps/releases/download/latest/deps.zip'
dest new File(buildDir, 'deps.zip')
overwrite false
}
tasks.register('getDeps', Copy) {
dependsOn downloadDeps
def deps = new File(buildDir.parent, '../deps')
if (!deps.exists()) {
deps.mkdir()
from zipTree(downloadDeps.dest)
into deps
}
task getDeps(dependsOn: downloadDeps, type: Copy) {
def deps = new File(buildDir.parent, '../deps')
if (!deps.exists()) {
deps.mkdir()
from zipTree(downloadDeps.dest)
into deps
}
}
preBuild.dependsOn getDeps

View file

@ -0,0 +1 @@
../../../../data-mobile

View file

@ -0,0 +1 @@
../../../../data-shared

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:installLocation="auto">
package="com.bithack.principia" android:installLocation="auto">
<supports-screens android:resizeable="true" android:smallScreens="false" android:normalScreens="true"
android:largeScreens="true" android:xlargeScreens="true" android:anyDensity="true" />
@ -17,8 +17,7 @@
android:hasFragileUserData="true"
android:label="@string/app_name"
android:icon="@drawable/icon"
android:theme="@style/PrincipiaTheme"
android:networkSecurityConfig="@xml/network_security_config">
android:theme="@style/PrincipiaTheme">
<activity android:screenOrientation="landscape" android:name="com.bithack.principia.PrincipiaActivity"
android:exported="true"
android:configChanges="keyboard|keyboardHidden|orientation"

View file

@ -0,0 +1,6 @@
package com.bithack.principia;
public class PrincipiaActivity extends TMSActivity
{
}

View file

@ -0,0 +1,8 @@
package com.bithack.principia;
import org.libsdl.app.SDLActivity;
public class TMSActivity extends SDLActivity
{
}

View file

@ -1,7 +1,9 @@
package com.bithack.principia.shared;
import org.libsdl.app.PrincipiaBackend;
import org.libsdl.app.SDLActivity;
import com.bithack.principia.PrincipiaActivity;
import com.bithack.principia.PrincipiaBackend;
import com.bithack.principia.R;
import android.app.AlertDialog;
import android.app.Dialog;
@ -26,7 +28,7 @@ public class AnimalDialog {
s_animal = (Spinner)view.findViewById(R.id.s_animal);
String[] consumables = PrincipiaBackend.getAnimals().split(",.,");
ArrayAdapter<String> spinnerArrayAdapter = new ArrayAdapter<String>(PrincipiaActivity.mSingleton, android.R.layout.select_dialog_item, consumables);
ArrayAdapter<String> spinnerArrayAdapter = new ArrayAdapter<String>(SDLActivity.mSingleton, android.R.layout.select_dialog_item, consumables);
s_animal.setAdapter(spinnerArrayAdapter);
_dialog = new AlertDialog.Builder(PrincipiaActivity.mSingleton)

View file

@ -1,6 +1,7 @@
package com.bithack.principia.shared;
import com.bithack.principia.PrincipiaBackend;
import org.libsdl.app.PrincipiaBackend;
import org.libsdl.app.SDLActivity;
import com.bithack.principia.PrincipiaActivity;
@ -19,12 +20,12 @@ public class AutosaveDialog
.setMessage("Autosave file detected. Open or remove?")
.setPositiveButton("Open", new OnClickListener(){
public void onClick(DialogInterface dialog, int which){
PrincipiaBackend.addActionAsInt(PrincipiaActivity.ACTION_OPEN_AUTOSAVE, 0);
PrincipiaBackend.addActionAsInt(SDLActivity.ACTION_OPEN_AUTOSAVE, 0);
}}
)
.setNegativeButton("Remove", new OnClickListener(){
public void onClick(DialogInterface dialog, int which){
PrincipiaBackend.addActionAsInt(PrincipiaActivity.ACTION_REMOVE_AUTOSAVE, 0);
PrincipiaBackend.addActionAsInt(SDLActivity.ACTION_REMOVE_AUTOSAVE, 0);
}}
)
.create();

View file

@ -1,12 +1,14 @@
package com.bithack.principia.shared;
import com.bithack.principia.PrincipiaBackend;
import org.libsdl.app.PrincipiaBackend;
import com.bithack.principia.PrincipiaActivity;
import com.bithack.principia.R;
import android.os.Bundle;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;

View file

@ -12,6 +12,7 @@ import android.text.Spanned;
import android.text.style.ForegroundColorSpan;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.EditText;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
@ -203,7 +204,9 @@ public class CodeEditor extends androidx.appcompat.widget.AppCompatEditText
private Editable highlight( Editable e )
{
last_edit = SystemClock.uptimeMillis();
if (true) {
//return e;
}
Log.v("Principia", "highlight begin");
try
{
@ -260,7 +263,7 @@ public class CodeEditor extends androidx.appcompat.widget.AppCompatEditText
{
// remove foreground color spans
{
ForegroundColorSpan[] spans = e.getSpans(
ForegroundColorSpan spans[] = e.getSpans(
0,
e.length(),
ForegroundColorSpan.class );

View file

@ -3,7 +3,7 @@ package com.bithack.principia.shared;
import com.bithack.principia.PrincipiaActivity;
import com.bithack.principia.R;
import com.bithack.principia.PrincipiaBackend;
import org.libsdl.app.PrincipiaBackend;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;

View file

@ -1,6 +1,6 @@
package com.bithack.principia.shared;
import com.bithack.principia.PrincipiaBackend;
import org.libsdl.app.PrincipiaBackend;
import com.bithack.principia.PrincipiaActivity;
import com.bithack.principia.R;

View file

@ -1,12 +1,18 @@
package com.bithack.principia.shared;
import com.bithack.principia.PrincipiaBackend;
import org.libsdl.app.PrincipiaBackend;
import org.libsdl.app.SDLActivity;
import com.bithack.principia.PrincipiaActivity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
public class CommunityDialog
{
@ -18,13 +24,13 @@ public class CommunityDialog
.setMessage("Do you want to return to the community site or to the main menu?")
.setPositiveButton("Community", new OnClickListener(){
public void onClick(DialogInterface dialog, int which){
PrincipiaActivity.wv.loadUrl(PrincipiaBackend.getCurrentCommunityUrl());
PrincipiaActivity.wv_dialog.show();
SDLActivity.wv.loadUrl(PrincipiaBackend.getCurrentCommunityUrl());
SDLActivity.wv_dialog.show();
}}
)
.setNegativeButton("Main menu", new OnClickListener(){
public void onClick(DialogInterface dialog, int which){
PrincipiaBackend.addActionAsInt(PrincipiaActivity.ACTION_GOTO_MAINMENU, 0);
PrincipiaBackend.addActionAsInt(SDLActivity.ACTION_GOTO_MAINMENU, 0);
}}
)
.create();

View file

@ -2,7 +2,8 @@ package com.bithack.principia.shared;
import com.bithack.principia.PrincipiaActivity;
import com.bithack.principia.R;
import com.bithack.principia.PrincipiaBackend;
import org.libsdl.app.SDLActivity;
import org.libsdl.app.PrincipiaBackend;
import android.app.AlertDialog;
import android.content.DialogInterface;
@ -43,7 +44,7 @@ public class ConfirmDialog
{
final CheckBox cb;
AlertDialog dialog = new AlertDialog.Builder(PrincipiaActivity.getContext()).create();
AlertDialog dialog = new AlertDialog.Builder(SDLActivity.getContext()).create();
if (dna_sandbox_back) {
View view = LayoutInflater.from(PrincipiaActivity.mSingleton).inflate(R.layout.confirm_sandbox, null);
dialog.setView(view);
@ -54,8 +55,8 @@ public class ConfirmDialog
cb = null;
}
dialog.setCancelable(true);
dialog.setOnShowListener(PrincipiaActivity.mSingleton);
dialog.setOnDismissListener(PrincipiaActivity.mSingleton);
dialog.setOnShowListener(SDLActivity.mSingleton);
dialog.setOnDismissListener(SDLActivity.mSingleton);
dialog.setButton(DialogInterface.BUTTON_POSITIVE, button1, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int buttonId) {
if (mListener != null) {

View file

@ -1,6 +1,8 @@
package com.bithack.principia.shared;
import com.bithack.principia.PrincipiaBackend;
import org.libsdl.app.PrincipiaBackend;
import org.libsdl.app.SDLActivity;
import com.bithack.principia.PrincipiaActivity;
import com.bithack.principia.R;
import android.app.AlertDialog;
@ -26,7 +28,7 @@ public class ConsumableDialog {
s_consumable = (Spinner)view.findViewById(R.id.s_consumable);
String[] consumables = PrincipiaBackend.getConsumables().split(",");
ArrayAdapter<String> spinnerArrayAdapter = new ArrayAdapter<String>(PrincipiaActivity.mSingleton, android.R.layout.select_dialog_item, consumables);
ArrayAdapter<String> spinnerArrayAdapter = new ArrayAdapter<String>(SDLActivity.mSingleton, android.R.layout.select_dialog_item, consumables);
s_consumable.setAdapter(spinnerArrayAdapter);
_dialog = new AlertDialog.Builder(PrincipiaActivity.mSingleton)

View file

@ -1,6 +1,8 @@
package com.bithack.principia.shared;
import com.bithack.principia.PrincipiaBackend;
import org.libsdl.app.PrincipiaBackend;
import org.libsdl.app.SDLActivity;
import com.bithack.principia.PrincipiaActivity;
import com.bithack.principia.R;
import android.app.AlertDialog;
@ -26,7 +28,7 @@ public class DecorationDialog {
s_deco = (Spinner)view.findViewById(R.id.s_deco);
String[] consumables = PrincipiaBackend.getDecorations().split(",.,");
ArrayAdapter<String> spinnerArrayAdapter = new ArrayAdapter<String>(PrincipiaActivity.mSingleton, android.R.layout.select_dialog_item, consumables);
ArrayAdapter<String> spinnerArrayAdapter = new ArrayAdapter<String>(SDLActivity.mSingleton, android.R.layout.select_dialog_item, consumables);
s_deco.setAdapter(spinnerArrayAdapter);
_dialog = new AlertDialog.Builder(PrincipiaActivity.mSingleton)

View file

@ -4,7 +4,8 @@ import java.util.ArrayList;
import java.util.List;
import com.bithack.principia.PrincipiaActivity;
import com.bithack.principia.R;
import com.bithack.principia.PrincipiaBackend;
import org.libsdl.app.PrincipiaBackend;
import org.libsdl.app.SDLActivity;
import android.app.AlertDialog;
import android.app.Dialog;
@ -63,7 +64,7 @@ public class DigitalDisplayDialog {
ll_dd = (LinearLayout)view.findViewById(R.id.ll_dd);
ll_wrap = (LinearLayout)view.findViewById(R.id.display_ll_wrap);
np_initial_position = new com.bithack.principia.shared.NumberPicker(PrincipiaActivity.getContext());
np_initial_position = new com.bithack.principia.shared.NumberPicker(SDLActivity.getContext());
np_initial_position.setRange(MIN_INITIAL_POS, 40);
ll_dd.addView((View)np_initial_position);
@ -132,7 +133,7 @@ public class DigitalDisplayDialog {
new_str.setCharAt(y, (isChecked?'1':'0'));
symbols.set(cur_symbol, new_str.toString());
} catch (StringIndexOutOfBoundsException e) {
Log.e("Principia", "An unknown error occurred: " + e.getMessage());
Log.e("Principia", "An unknown error occured: " + e.getMessage());
}
}
});
@ -180,7 +181,7 @@ public class DigitalDisplayDialog {
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (num_symbols == 40) {
PrincipiaActivity.message("Maximum number of symbols reached.", 0);
SDLActivity.message("Maximum number of symbols reached.", 0);
return false;
}
@ -199,7 +200,7 @@ public class DigitalDisplayDialog {
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (num_symbols == 40) {
PrincipiaActivity.message("Maximum number of symbols reached.", 0);
SDLActivity.message("Maximum number of symbols reached.", 0);
return false;
}
@ -311,7 +312,7 @@ public class DigitalDisplayDialog {
}
}
symbols.add(sb.toString());
Log.v("Principia", "Got a cool symbol: "+ sb);
Log.v("Principia", "Got a cool symbol: "+sb.toString());
}
cur_symbol = initial_position - 1;

View file

@ -4,7 +4,7 @@ import java.util.Locale;
import com.bithack.principia.PrincipiaActivity;
import com.bithack.principia.R;
import com.bithack.principia.PrincipiaBackend;
import org.libsdl.app.PrincipiaBackend;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;

View file

@ -1,6 +1,6 @@
package com.bithack.principia.shared;
import com.bithack.principia.PrincipiaBackend;
import org.libsdl.app.PrincipiaBackend;
import com.bithack.principia.PrincipiaActivity;
import com.bithack.principia.R;

View file

@ -1,6 +1,6 @@
package com.bithack.principia.shared;
import com.bithack.principia.PrincipiaBackend;
import org.libsdl.app.PrincipiaBackend;
import com.bithack.principia.PrincipiaActivity;
import com.bithack.principia.R;

View file

@ -5,7 +5,7 @@ import java.util.ArrayList;
import com.bithack.principia.PrincipiaActivity;
import com.bithack.principia.R;
import com.bithack.principia.PrincipiaBackend;
import org.libsdl.app.PrincipiaBackend;
import android.app.Dialog;
import android.content.DialogInterface;
@ -218,7 +218,8 @@ public class FactoryDialog {
for (int x=0; x<resource_list.length; ++x) {
try {
np_resources.get(x).setValue(Integer.parseInt(resource_list[x]));
} catch (NumberFormatException ignored) {
} catch (NumberFormatException e) {
continue;
}
}
@ -249,7 +250,7 @@ public class FactoryDialog {
sb.append(";");
}
sb.append(r.id);
sb.append(Integer.toString(r.id));
first = false;
}
@ -266,7 +267,7 @@ public class FactoryDialog {
}
}
Log.v("Principia", "ZZZ: '" + sb + "'");
Log.v("Principia", "ZZZ: '" + sb.toString() + "'");
PrincipiaBackend.setPropertyString(0, sb.toString());
}

View file

@ -1,6 +1,6 @@
package com.bithack.principia.shared;
import com.bithack.principia.PrincipiaBackend;
import org.libsdl.app.PrincipiaBackend;
import com.bithack.principia.PrincipiaActivity;
import com.bithack.principia.R;

View file

@ -1,6 +1,6 @@
package com.bithack.principia.shared;
import com.bithack.principia.PrincipiaBackend;
import org.libsdl.app.PrincipiaBackend;
import com.bithack.principia.PrincipiaActivity;
import com.bithack.principia.R;

View file

@ -1,6 +1,6 @@
package com.bithack.principia.shared;
import com.bithack.principia.PrincipiaBackend;
import org.libsdl.app.PrincipiaBackend;
import com.bithack.principia.PrincipiaActivity;
import com.bithack.principia.R;

View file

@ -8,20 +8,23 @@ import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.text.Html;
public class InfoDialog
public class HelpDialog
{
Dialog _dialog;
public static String description;
public static String description ="";
public static String title = "";
public InfoDialog()
public HelpDialog()
{
AlertDialog.Builder bld = new AlertDialog.Builder(PrincipiaActivity.mSingleton);
bld.setTitle("Level description");
bld.setMessage(description);
bld.setTitle(title);
bld.setMessage(Html.fromHtml((description.replaceAll("\n", "<br />"))));
bld.setNeutralButton("Close", new OnClickListener(){
public void onClick(DialogInterface dialog, int which) { }
public void onClick(DialogInterface dialog, int which)
{
}
});
this._dialog = bld.create();

View file

@ -1,6 +1,7 @@
package com.bithack.principia.shared;
import com.bithack.principia.PrincipiaBackend;
import org.libsdl.app.PrincipiaBackend;
import org.libsdl.app.SDLActivity;
import com.bithack.principia.PrincipiaActivity;
@ -22,7 +23,7 @@ public class ImportDialog
private final String[] level_names;
public static ListView lv;
public static ArrayAdapter<Level> list_adapter = new ArrayAdapter<Level>(PrincipiaActivity.mSingleton,
public static ArrayAdapter<Level> list_adapter = new ArrayAdapter<Level>(SDLActivity.mSingleton,
android.R.layout.select_dialog_item);
public ImportDialog(final boolean is_multiemitter)
@ -31,7 +32,7 @@ public class ImportDialog
AlertDialog.Builder bld = new AlertDialog.Builder(PrincipiaActivity.mSingleton);
String level_list = PrincipiaBackend.getLevels(PrincipiaActivity.LEVEL_PARTIAL);
String level_list = PrincipiaBackend.getLevels(SDLActivity.LEVEL_PARTIAL);
String[] levels = level_list.split("\n");
level_names = new String[levels.length];
@ -81,7 +82,7 @@ public class ImportDialog
this._dialog.setOnShowListener(new OnShowListener() {
@Override
public void onShow(DialogInterface dialog) {
PrincipiaActivity.on_show(dialog);
SDLActivity.on_show(dialog);
ListView lv = _dialog.getListView();
ImportDialog.lv = lv;
if (lv != null) {
@ -103,7 +104,7 @@ public class ImportDialog
}
});
lv.setAdapter(ImportDialog.list_adapter);
PrincipiaActivity.mSingleton.registerForContextMenu(lv);
SDLActivity.mSingleton.registerForContextMenu(lv);
} else {
Log.v("Principia", "listview = null");
}

View file

@ -2,7 +2,7 @@ package com.bithack.principia.shared;
import com.bithack.principia.PrincipiaActivity;
import com.bithack.principia.R;
import com.bithack.principia.PrincipiaBackend;
import org.libsdl.app.PrincipiaBackend;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;

View file

@ -2,7 +2,7 @@ package com.bithack.principia.shared;
import java.util.ArrayList;
import com.bithack.principia.PrincipiaBackend;
import org.libsdl.app.PrincipiaBackend;
import com.bithack.principia.PrincipiaActivity;
import com.bithack.principia.R;
@ -15,6 +15,9 @@ import android.view.View;
import android.widget.Spinner;
import android.widget.ArrayAdapter;
import java.util.HashMap;
import java.util.Map;
public class KeyListenerDialog {
static Dialog _dialog;

View file

@ -4,7 +4,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import com.bithack.principia.PrincipiaBackend;
import org.libsdl.app.PrincipiaBackend;
import com.bithack.principia.PrincipiaActivity;
import com.bithack.principia.R;

View file

@ -2,7 +2,8 @@ package com.bithack.principia.shared;
import com.bithack.principia.PrincipiaActivity;
import com.bithack.principia.R;
import com.bithack.principia.PrincipiaBackend;
import org.libsdl.app.PrincipiaBackend;
import org.libsdl.app.SDLActivity;
import android.app.AlertDialog;
import android.app.Dialog;
@ -42,7 +43,7 @@ public class LoginDialog
_dialog.setOnShowListener(new OnShowListener() {
@Override
public void onShow(DialogInterface dialog) {
PrincipiaActivity.on_show(dialog);
SDLActivity.on_show(dialog);
Button b = _dialog.getButton(AlertDialog.BUTTON_POSITIVE);
@ -54,7 +55,7 @@ public class LoginDialog
String password = et_password.getText().toString().trim();
if (username.length() <= 0 || password.length() <= 0) {
PrincipiaActivity.message("You must enter a valid username and password.", 0);
SDLActivity.message("You must enter a valid username and password.", 0);
return;
}
@ -73,7 +74,7 @@ public class LoginDialog
btn_register_account.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
PrincipiaActivity.open_dialog(PrincipiaActivity.DIALOG_REGISTER);
SDLActivity.open_dialog(SDLActivity.DIALOG_REGISTER);
_dialog.dismiss();
}
});

View file

@ -2,7 +2,7 @@ package com.bithack.principia.shared;
import java.util.ArrayList;
import com.bithack.principia.PrincipiaBackend;
import org.libsdl.app.PrincipiaBackend;
import android.util.Log;

View file

@ -1,5 +1,15 @@
package com.bithack.principia.shared;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import org.libsdl.app.PrincipiaBackend;
import com.bithack.principia.PrincipiaActivity;
import com.bithack.principia.R;
import com.bithack.principia.shared.ConfirmDialog.OnOptionSelectedListener;
import android.app.Dialog;
import android.content.DialogInterface;
import android.graphics.Color;
@ -7,28 +17,33 @@ import android.graphics.PorterDuff.Mode;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnFocusChangeListener;
import android.view.ViewGroup.LayoutParams;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.ScrollView;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.Spinner;
import android.widget.TableRow;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
import android.widget.ToggleButton;
import com.bithack.principia.PrincipiaActivity;
import com.bithack.principia.R;
import com.bithack.principia.PrincipiaBackend;
import java.util.ArrayList;
public class MultiSelectDialog implements OnSeekBarChangeListener, OnCheckedChangeListener {
public static final int NUM_TABS = 3;
static Dialog _dialog;
static View view;
@ -58,7 +73,7 @@ public class MultiSelectDialog implements OnSeekBarChangeListener, OnCheckedChan
this.view_id = view_id;
this.title = title;
}
}
};
static ArrayList<Tab> tabs = new ArrayList<Tab>();
@ -138,8 +153,8 @@ public class MultiSelectDialog implements OnSeekBarChangeListener, OnCheckedChan
ll_tabs.addView(tb);
}
btn_apply = view.findViewById(R.id.btn_apply);
btn_close = view.findViewById(R.id.btn_close);
btn_apply = (Button)view.findViewById(R.id.btn_apply);
btn_close = (Button)view.findViewById(R.id.btn_close);
btn_apply.setOnClickListener(new View.OnClickListener() {
@Override
@ -275,13 +290,13 @@ public class MultiSelectDialog implements OnSeekBarChangeListener, OnCheckedChan
break;
case PLASTIC_DENSITY:
PrincipiaBackend.addActionAsInt(PrincipiaActivity.ACTION_MULTI_PLASTIC_DENSITY, sb_plastic_density.getProgress()/10);
PrincipiaBackend.addActionAsInt(PrincipiaActivity.ACTION_MULTI_PLASTIC_DENSITY, sb_plastic_density.getProgress());
message = "Changed the density of all plastic entities.";
break;
case CONNECTION_RENDER_TYPE:
{
int t; // CONN_RENDER_DEFAULT
int t = 0; // CONN_RENDER_DEFAULT
switch (render_type.getCheckedRadioButtonId()) {
case R.id.rb_small:
t = 1; // CONN_RENDER_SMALL

View file

@ -2,6 +2,8 @@ package com.bithack.principia.shared;
import java.util.List;
import com.bithack.principia.R;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
@ -9,8 +11,7 @@ import android.content.DialogInterface.OnCancelListener;
import android.content.DialogInterface.OnMultiChoiceClickListener;
import android.util.AttributeSet;
import android.widget.ArrayAdapter;
import com.bithack.principia.R;
import android.widget.Spinner;
public class MultiSpinner extends androidx.appcompat.widget.AppCompatSpinner implements OnMultiChoiceClickListener, OnCancelListener {
@ -32,7 +33,11 @@ public class MultiSpinner extends androidx.appcompat.widget.AppCompatSpinner imp
@Override
public void onClick(DialogInterface dialog, int which, boolean isChecked) {
selected[which] = isChecked;
if (isChecked) {
selected[which] = true;
} else {
selected[which] = false;
}
}
@Override
@ -44,7 +49,7 @@ public class MultiSpinner extends androidx.appcompat.widget.AppCompatSpinner imp
public boolean performClick() {
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
builder.setMultiChoiceItems(
items.toArray(new CharSequence[0]), selected, this);
items.toArray(new CharSequence[items.size()]), selected, this);
builder.setPositiveButton(R.string.ok,
new DialogInterface.OnClickListener() {

View file

@ -1,6 +1,6 @@
package com.bithack.principia.shared;
import com.bithack.principia.PrincipiaBackend;
import org.libsdl.app.PrincipiaBackend;
import com.bithack.principia.PrincipiaActivity;
@ -20,15 +20,15 @@ public class NewLevelDialog
AlertDialog.Builder bld = new AlertDialog.Builder(PrincipiaActivity.mSingleton);
bld.setTitle("Create new level");
bld.setMessage("Please select the level type:\nAdventure - Control a robot\nCustom - Create whatever you want here!");
bld.setMessage("Please select the level type:\nPuzzle\nAdventure - Control a robot\nCustom - Create whatever you want here!");
/*bld.setPositiveButton("Puzzle", new OnClickListener(){
bld.setPositiveButton("Puzzle", new OnClickListener(){
public void onClick(DialogInterface dialog, int which)
{
PrincipiaBackend.triggerCreateLevel(0);
Toast.makeText(PrincipiaActivity.mSingleton, "New Puzzle level created!", Toast.LENGTH_SHORT).show();
}
});*/
});
bld.setNeutralButton("Adventure", new OnClickListener(){
public void onClick(DialogInterface dialog, int which)

View file

@ -386,7 +386,7 @@ public class NumberPicker extends LinearLayout implements OnClickListener,
String result = String.valueOf(dest.subSequence(0, dstart))
+ filtered
+ dest.subSequence(dend, dest.length());
String str = result.toLowerCase(Locale.US);
String str = String.valueOf(result).toLowerCase(Locale.US);
for (String val : mDisplayedValues) {
val = val.toLowerCase(Locale.US);
if (val.startsWith(str)) {

View file

@ -24,6 +24,7 @@ import android.content.Context;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.widget.ImageButton;
import com.bithack.principia.R;

View file

@ -1,6 +1,8 @@
package com.bithack.principia.shared;
import com.bithack.principia.PrincipiaBackend;
import org.libsdl.app.PrincipiaBackend;
import org.libsdl.app.SDLActivity;
import com.bithack.principia.PrincipiaActivity;
import android.app.AlertDialog;
@ -23,11 +25,11 @@ public class OpenDialog
public OpenDialog(final boolean is_state)
{
PrincipiaActivity.open_adapter.clear();
SDLActivity.open_adapter.clear();
AlertDialog.Builder bld = new AlertDialog.Builder(PrincipiaActivity.mSingleton);
String level_list = PrincipiaBackend.getLevels(is_state ? PrincipiaActivity.LEVEL_LOCAL_STATE : PrincipiaActivity.LEVEL_LOCAL);
String level_list = PrincipiaBackend.getLevels(is_state ? SDLActivity.LEVEL_LOCAL_STATE : SDLActivity.LEVEL_LOCAL);
Log.v("Principia", "Level list: " + level_list);
String[] levels = level_list.split("\n");
@ -58,7 +60,7 @@ public class OpenDialog
Log.v("Principia", "Adding "+name);
PrincipiaActivity.open_adapter.add(l);
SDLActivity.open_adapter.add(l);
level_names[x] = name;
}
@ -80,7 +82,7 @@ public class OpenDialog
@Override
public void onShow(DialogInterface dialog)
{
PrincipiaActivity.on_show(dialog);
SDLActivity.on_show(dialog);
ListView lv = _dialog.getListView();
OpenDialog.lv = lv;
if (lv != null) {
@ -90,7 +92,7 @@ public class OpenDialog
int position, long id) {
Level level = (Level)parent.getAdapter().getItem(position);
if (is_state) {
PrincipiaBackend.openState(level.get_level_type(), level.get_id(), level.get_save_id(), PrincipiaActivity.is_cool); /* XXX */
PrincipiaBackend.openState(level.get_level_type(), level.get_id(), level.get_save_id(), SDLActivity.is_cool); /* XXX */
} else {
PrincipiaBackend.addActionAsInt(PrincipiaActivity.ACTION_OPEN, level.get_id());
}
@ -98,8 +100,8 @@ public class OpenDialog
}
});
lv.setAdapter(PrincipiaActivity.open_adapter);
PrincipiaActivity.mSingleton.registerForContextMenu(lv);
lv.setAdapter(SDLActivity.open_adapter);
SDLActivity.mSingleton.registerForContextMenu(lv);
}
}
});

View file

@ -2,7 +2,8 @@ package com.bithack.principia.shared;
import com.bithack.principia.PrincipiaActivity;
import com.bithack.principia.R;
import com.bithack.principia.PrincipiaBackend;
import org.libsdl.app.PrincipiaBackend;
import org.libsdl.app.SDLActivity;
import android.app.AlertDialog;
import android.app.Dialog;
@ -26,7 +27,7 @@ public class PkgLevelDialog {
view = LayoutInflater.from(PrincipiaActivity.mSingleton).inflate(R.layout.pkg_level_id, null);
np_level_id = new com.bithack.principia.shared.NumberPicker(PrincipiaActivity.getContext());
np_level_id = new com.bithack.principia.shared.NumberPicker(SDLActivity.getContext());
np_level_id.setRange(0, 255);
np_level_id.setValue(1);

View file

@ -3,14 +3,19 @@ package com.bithack.principia.shared;
import java.util.ArrayList;
import java.util.List;
import org.libsdl.app.PrincipiaBackend;
import org.libsdl.app.SDLActivity;
import com.bithack.principia.PrincipiaActivity;
import com.bithack.principia.PrincipiaBackend;
import com.bithack.principia.R;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.os.Bundle;
import android.util.Log;
import android.view.ViewGroup.LayoutParams;
public class PlayDialog
{
@ -20,17 +25,17 @@ public class PlayDialog
*/
static final String[] shared_items = new String[] {
PrincipiaActivity.mSingleton.getString(R.string.open_save),
PrincipiaActivity.mSingleton.getString(R.string.back_to_sandbox),
PrincipiaActivity.mSingleton.getString(R.string.back_to_main_menu),
PrincipiaActivity.mSingleton.getString(R.string.cancel)
SDLActivity.mSingleton.getString(R.string.open_save),
SDLActivity.mSingleton.getString(R.string.back_to_sandbox),
SDLActivity.mSingleton.getString(R.string.back_to_main_menu),
SDLActivity.mSingleton.getString(R.string.cancel)
};
static final String save_state = PrincipiaActivity.mSingleton.getString(R.string.save_state);
static final String save_state = SDLActivity.mSingleton.getString(R.string.save_state);
static final String[] community_items = new String[] {
PrincipiaActivity.mSingleton.getString(R.string.restart_level),
PrincipiaActivity.mSingleton.getString(R.string.back_to_community)
SDLActivity.mSingleton.getString(R.string.restart_level),
SDLActivity.mSingleton.getString(R.string.back_to_community)
};
public static Dialog create_dialog()
@ -40,28 +45,28 @@ public class PlayDialog
if (source > 100) {
source -= 100;
}
AlertDialog.Builder bld = new AlertDialog.Builder(PrincipiaActivity.mSingleton);
AlertDialog.Builder bld = new AlertDialog.Builder(SDLActivity.mSingleton);
List<CharSequence> items = new ArrayList<CharSequence>();
if (PrincipiaBackend.getLevelFlag(33)) {
items.add(save_state);
}
items.add(PrincipiaActivity.mSingleton.getString(R.string.open_save));
items.add(SDLActivity.mSingleton.getString(R.string.open_save));
if (PrincipiaBackend.isAdventure()) {
items.add(PrincipiaActivity.mSingleton.getString(R.string.selfdestruct));
items.add(SDLActivity.mSingleton.getString(R.string.selfdestruct));
}
if (source == 1) {
items.add(PrincipiaActivity.mSingleton.getString(R.string.restart_level));
items.add(PrincipiaActivity.mSingleton.getString(R.string.back_to_community));
items.add(SDLActivity.mSingleton.getString(R.string.restart_level));
items.add(SDLActivity.mSingleton.getString(R.string.back_to_community));
} else if (source == 0) {
items.add(PrincipiaActivity.mSingleton.getString(R.string.back_to_sandbox));
items.add(SDLActivity.mSingleton.getString(R.string.back_to_sandbox));
}
items.add(PrincipiaActivity.mSingleton.getString(R.string.back_to_main_menu));
items.add(PrincipiaActivity.mSingleton.getString(R.string.cancel));
items.add(SDLActivity.mSingleton.getString(R.string.back_to_main_menu));
items.add(SDLActivity.mSingleton.getString(R.string.cancel));
final CharSequence[] real_items = items.toArray(new CharSequence[items.size()]);
@ -70,26 +75,26 @@ public class PlayDialog
String cool = real_items[which].toString();
if (cool.equalsIgnoreCase("open save")) {
PrincipiaActivity.mSingleton.runOnUiThread(new Runnable(){
SDLActivity.mSingleton.runOnUiThread(new Runnable(){
public void run() {
try { PrincipiaActivity.mSingleton.removeDialog(PrincipiaActivity.DIALOG_OPEN); } catch(Exception e) {};
try { PrincipiaActivity.mSingleton.removeDialog(PrincipiaActivity.DIALOG_OPEN_STATE); } catch(Exception e) {};
try { SDLActivity.mSingleton.removeDialog(SDLActivity.DIALOG_OPEN); } catch(Exception e) {};
try { SDLActivity.mSingleton.removeDialog(SDLActivity.DIALOG_OPEN_STATE); } catch(Exception e) {};
}
});
PrincipiaActivity.mSingleton.showDialog(PrincipiaActivity.DIALOG_OPEN_STATE);
SDLActivity.mSingleton.showDialog(SDLActivity.DIALOG_OPEN_STATE);
} else if (cool.equalsIgnoreCase("back to sandbox")) {
PrincipiaBackend.addActionAsInt(PrincipiaActivity.ACTION_BACK, 0);
PrincipiaBackend.addActionAsInt(SDLActivity.ACTION_BACK, 0);
} else if (cool.equalsIgnoreCase("back to community")) {
PrincipiaActivity.wv.loadUrl(PrincipiaBackend.getCurrentCommunityUrl());
PrincipiaActivity.wv_dialog.show();
SDLActivity.wv.loadUrl(PrincipiaBackend.getCurrentCommunityUrl());
SDLActivity.wv_dialog.show();
} else if (cool.equalsIgnoreCase("save state")) {
PrincipiaBackend.addActionAsInt(PrincipiaActivity.ACTION_SAVE_STATE, 0);
PrincipiaBackend.addActionAsInt(SDLActivity.ACTION_SAVE_STATE, 0);
} else if (cool.equalsIgnoreCase("back to main menu")) {
PrincipiaBackend.addActionAsInt(PrincipiaActivity.ACTION_GOTO_MAINMENU, 0);
PrincipiaBackend.addActionAsInt(SDLActivity.ACTION_GOTO_MAINMENU, 0);
} else if (cool.equalsIgnoreCase("restart level")) {
PrincipiaBackend.addActionAsInt(PrincipiaActivity.ACTION_RESTART_LEVEL, 0);
PrincipiaBackend.addActionAsInt(SDLActivity.ACTION_RESTART_LEVEL, 0);
} else if (cool.equalsIgnoreCase("self-destruct")) {
PrincipiaBackend.addActionAsInt(PrincipiaActivity.ACTION_SELF_DESTRUCT, 0);
PrincipiaBackend.addActionAsInt(SDLActivity.ACTION_SELF_DESTRUCT, 0);
} else {
Log.e("PRINCIPIA", "UNKNOWN THING: " + cool);
}
@ -109,7 +114,7 @@ public class PlayDialog
.setNeutralButton("Back", new OnClickListener(){
public void onClick(DialogInterface dialog, int which)
{
PrincipiaBackend.addActionAsInt(PrincipiaActivity.ACTION_BACK, 0);
PrincipiaBackend.addActionAsInt(SDLActivity.ACTION_BACK, 0);
}
})
.setNegativeButton("Cancel", new OnClickListener(){

View file

@ -4,7 +4,7 @@ import java.util.Locale;
import com.bithack.principia.PrincipiaActivity;
import com.bithack.principia.R;
import com.bithack.principia.PrincipiaBackend;
import org.libsdl.app.PrincipiaBackend;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;

View file

@ -6,7 +6,7 @@ import java.util.List;
import com.bithack.principia.PrincipiaActivity;
import com.bithack.principia.R;
import com.bithack.principia.PrincipiaBackend;
import org.libsdl.app.PrincipiaBackend;
import android.app.AlertDialog;
import android.app.Dialog;

View file

@ -2,7 +2,8 @@ package com.bithack.principia.shared;
import com.bithack.principia.PrincipiaActivity;
import com.bithack.principia.R;
import com.bithack.principia.PrincipiaBackend;
import org.libsdl.app.PrincipiaBackend;
import org.libsdl.app.SDLActivity;
import android.app.AlertDialog;
import android.app.Dialog;
@ -40,7 +41,7 @@ public class PromptSettingsDialog
_dialog.setOnShowListener(new OnShowListener() {
@Override
public void onShow(DialogInterface dialog) {
PrincipiaActivity.on_show(dialog);
SDLActivity.on_show(dialog);
Button b = _dialog.getButton(AlertDialog.BUTTON_POSITIVE);
@ -58,12 +59,12 @@ public class PromptSettingsDialog
int message_len = message.length();
if (message_len <= 0) {
PrincipiaActivity.message("You must enter a message for the prompt.", 0);
SDLActivity.message("You must enter a message for the prompt.", 0);
return;
}
if (b1_len <= 0 && b2_len <= 0 && b3_len <= 0) {
PrincipiaActivity.message("You must use at least one button.", 0);
SDLActivity.message("You must use at least one button.", 0);
return;
}

View file

@ -2,7 +2,8 @@ package com.bithack.principia.shared;
import com.bithack.principia.PrincipiaActivity;
import com.bithack.principia.R;
import com.bithack.principia.PrincipiaBackend;
import org.libsdl.app.PrincipiaBackend;
import org.libsdl.app.SDLActivity;
import android.app.AlertDialog;
import android.app.Dialog;
@ -23,6 +24,7 @@ public class PublishDialog
static EditText et_name;
static EditText et_descr;
static CheckBox cb_allow_derivatives;
static CheckBox cb_locked;
public static Dialog get_dialog()
@ -40,7 +42,7 @@ public class PublishDialog
_dialog.setOnShowListener(new OnShowListener() {
@Override
public void onShow(DialogInterface dialog) {
PrincipiaActivity.on_show(dialog);
SDLActivity.on_show(dialog);
Button b = _dialog.getButton(AlertDialog.BUTTON_POSITIVE);
@ -52,15 +54,16 @@ public class PublishDialog
String descr = et_descr.getText().toString().trim();
if (name.length() <= 0) {
PrincipiaActivity.message("You must enter a name for your level!", 0);
SDLActivity.message("You must enter a name for your level!", 0);
return;
}
PrincipiaBackend.setLevelName(name);
PrincipiaBackend.setLevelDescription(descr);
PrincipiaBackend.setLevelAllowDerivatives(cb_allow_derivatives.isChecked());
PrincipiaBackend.setLevelLocked(cb_locked.isChecked());
PrincipiaBackend.addActionAsInt(PrincipiaActivity.ACTION_PUBLISH, 0);
PrincipiaBackend.addActionAsInt(SDLActivity.ACTION_PUBLISH, 0);
_dialog.dismiss();
}
@ -70,6 +73,7 @@ public class PublishDialog
et_name = (EditText)view.findViewById(R.id.publish_name);
et_descr = (EditText)view.findViewById(R.id.publish_descr);
cb_allow_derivatives = (CheckBox)view.findViewById(R.id.publish_allow_derivatives);
cb_locked = (CheckBox)view.findViewById(R.id.publish_locked);
}

View file

@ -2,7 +2,7 @@ package com.bithack.principia.shared;
import com.bithack.principia.PrincipiaActivity;
import com.bithack.principia.R;
import com.bithack.principia.PrincipiaBackend;
import org.libsdl.app.PrincipiaBackend;
import android.app.AlertDialog;
import android.app.Dialog;

View file

@ -0,0 +1,52 @@
package com.bithack.principia.shared;
import org.libsdl.app.PrincipiaBackend;
import org.libsdl.app.SDLActivity;
import com.bithack.principia.PrincipiaActivity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
public class PuzzlePlayDialog
{
static Dialog _dialog = null;
public PuzzlePlayDialog()
{
AlertDialog.Builder bld = new AlertDialog.Builder(PrincipiaActivity.mSingleton);
bld.setTitle("Play method");
bld.setMessage("Do you want to test play the level, or just simulate it?");
bld.setPositiveButton("Test play", new OnClickListener(){
public void onClick(DialogInterface dialog, int which)
{
PrincipiaBackend.addActionAsInt(SDLActivity.ACTION_PUZZLEPLAY, 0);
SDLActivity.message("Test-playing level!", 0);
}
});
bld.setNeutralButton("Simulate", new OnClickListener(){
public void onClick(DialogInterface dialog, int which)
{
PrincipiaBackend.addActionAsInt(SDLActivity.ACTION_PUZZLEPLAY, 1);
SDLActivity.message("Simulating level!", 0);
}
});
bld.setNegativeButton("Cancel", new OnClickListener(){
public void onClick(DialogInterface dialog, int which)
{
}
});
_dialog = bld.create();
}
public Dialog get_dialog()
{
return _dialog;
}
}

View file

@ -1,6 +1,6 @@
package com.bithack.principia.shared;
import com.bithack.principia.PrincipiaBackend;
import org.libsdl.app.PrincipiaBackend;
import com.bithack.principia.PrincipiaActivity;
import com.bithack.principia.R;

View file

@ -16,6 +16,7 @@ import android.os.Bundle;
import android.os.Parcelable;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import android.widget.ImageView;
/**
* Widget that lets users select a minimum and maximum value on a given numerical range. The range value types can be one of Long, Double, Integer, Float, Short, Byte or BigDecimal.<br />

View file

@ -2,13 +2,15 @@ package com.bithack.principia.shared;
import com.bithack.principia.PrincipiaActivity;
import com.bithack.principia.R;
import com.bithack.principia.PrincipiaBackend;
import org.libsdl.app.PrincipiaBackend;
import org.libsdl.app.SDLActivity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.DialogInterface.OnShowListener;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
@ -43,7 +45,7 @@ public class RegisterDialog
_dialog.setOnShowListener(new OnShowListener() {
@Override
public void onShow(DialogInterface dialog) {
PrincipiaActivity.on_show(dialog);
SDLActivity.on_show(dialog);
Button b = _dialog.getButton(AlertDialog.BUTTON_POSITIVE);
@ -57,22 +59,22 @@ public class RegisterDialog
final String email = et_email.getText().toString().trim();
if (password.length() < 6 || password.length() > 100) {
PrincipiaActivity.message("Your password must be at least 3 and at most 100 characters.", 0);
SDLActivity.message("Your password must be at least 3 and at most 100 characters.", 0);
return;
}
if (!password.equals(password_confirm)) {
PrincipiaActivity.message("The two passwords you entered don't match.", 0);
SDLActivity.message("The two passwords you entered don't match.", 0);
return;
}
if (username.length() < 3 || username.length() > 20) {
PrincipiaActivity.message("Your username must be at least 3 and at most 20 characters.", 0);
SDLActivity.message("Your username must be at least 3 and at most 20 characters.", 0);
return;
}
if (!android.util.Patterns.EMAIL_ADDRESS.matcher(email).matches()) {
PrincipiaActivity.message("You must enter a valid email address.", 0);
SDLActivity.message("You must enter a valid email address.", 0);
return;
}

Some files were not shown because too many files have changed in this diff Show more