mirror of
https://github.com/Bithack/principia.git
synced 2026-06-24 02:04:08 +00:00
Compare commits
2 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f6af58b195 | ||
|
|
ec3c8e59b8 |
2354 changed files with 158540 additions and 138845 deletions
6
.gitattributes
vendored
6
.gitattributes
vendored
|
|
@ -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
|
||||
|
|
|
|||
2
.github/CONTRIBUTING.md
vendored
2
.github/CONTRIBUTING.md
vendored
|
|
@ -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
28
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal 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. -->
|
||||
40
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
40
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
|
|
@ -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
|
||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
5
.github/ISSUE_TEMPLATE/config.yml
vendored
|
|
@ -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
11
.github/SECURITY.md
vendored
|
|
@ -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.
|
||||
46
.github/workflows/android.yml
vendored
46
.github/workflows/android.yml
vendored
|
|
@ -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
125
.github/workflows/build.yml
vendored
Normal 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
|
||||
34
.github/workflows/build_utils.yml
vendored
34
.github/workflows/build_utils.yml
vendored
|
|
@ -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
|
||||
59
.github/workflows/linux.yml
vendored
59
.github/workflows/linux.yml
vendored
|
|
@ -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
|
||||
43
.github/workflows/linux_ss.yml
vendored
43
.github/workflows/linux_ss.yml
vendored
|
|
@ -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
|
||||
55
.github/workflows/macos.yml
vendored
55
.github/workflows/macos.yml
vendored
|
|
@ -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
|
||||
47
.github/workflows/web.yml
vendored
47
.github/workflows/web.yml
vendored
|
|
@ -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
|
||||
78
.github/workflows/windows.yml
vendored
78
.github/workflows/windows.yml
vendored
|
|
@ -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
7
.gitignore
vendored
|
|
@ -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
3
.gitmodules
vendored
|
|
@ -1,3 +0,0 @@
|
|||
[submodule "android/principia/deps-src"]
|
||||
path = android/principia/deps-src
|
||||
url = https://github.com/principia-game/android-deps
|
||||
13
AUTHORS.md
13
AUTHORS.md
|
|
@ -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
|
||||
|
||||
|
|
|
|||
415
CMakeLists.txt
415
CMakeLists.txt
|
|
@ -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()
|
||||
|
|
|
|||
37
Doxyfile
37
Doxyfile
|
|
@ -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
153
README.md
|
|
@ -1,9 +1,9 @@
|
|||
# Principia Open Source Project
|
||||

|
||||
|
||||
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)
|
||||
|
|
|
|||
52
THANKS.md
52
THANKS.md
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
BIN
android/gradle/wrapper/gradle-wrapper.jar
vendored
BIN
android/gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
251
android/gradlew
vendored
251
android/gradlew
vendored
|
|
@ -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
|
||||
|
|
@ -1 +0,0 @@
|
|||
../../../../data
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
|
@ -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);
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -1,8 +1,7 @@
|
|||
.gradle/
|
||||
.idea/
|
||||
deps
|
||||
deps/
|
||||
principia/build/
|
||||
*.apk
|
||||
*.apk.idsig
|
||||
local.properties
|
||||
.cxx/
|
||||
19
build-android/build.gradle
Normal file
19
build-android/build.gradle
Normal 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
|
||||
}
|
||||
|
|
@ -5,4 +5,3 @@ android.nonTransitiveRClass=true
|
|||
org.gradle.daemon=true
|
||||
org.gradle.parallel=true
|
||||
org.gradle.parallel.threads=8
|
||||
android.nonFinalResIds=false
|
||||
BIN
build-android/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
build-android/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
|
|
@ -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
185
build-android/gradlew
vendored
Executable 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
1
build-android/jni
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../src
|
||||
|
|
@ -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
|
||||
1
build-android/principia/src/assets/data-mobile
Symbolic link
1
build-android/principia/src/assets/data-mobile
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../../../data-mobile
|
||||
1
build-android/principia/src/assets/data-shared
Symbolic link
1
build-android/principia/src/assets/data-shared
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../../../data-shared
|
||||
|
|
@ -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"
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package com.bithack.principia;
|
||||
|
||||
public class PrincipiaActivity extends TMSActivity
|
||||
{
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.bithack.principia;
|
||||
|
||||
import org.libsdl.app.SDLActivity;
|
||||
|
||||
public class TMSActivity extends SDLActivity
|
||||
{
|
||||
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
@ -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();
|
||||
|
|
@ -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;
|
||||
|
|
@ -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 );
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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();
|
||||
|
|
@ -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) {
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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());
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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();
|
||||
|
|
@ -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");
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
@ -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;
|
||||
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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() {
|
||||
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)) {
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -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);
|
||||
|
||||
|
|
@ -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(){
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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 />
|
||||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue