Compare commits

..

51 commits

Author SHA1 Message Date
ROllerozxa
462369618e Turn TMS_USE_GLES compile-time option into a runtime use_gles setting
This of course requires support from the SDL library Principia is linked to, but should still make it more accessible to switch between the two on Linux
2026-06-23 23:38:23 +02:00
ROllerozxa
5865ce72ac Remove unused GUI sprite textures cache code
Never has been used and it doesn't seem like it really needs to be cached either
2026-06-23 00:26:40 +02:00
ROllerozxa
ac9f350134 Make user data storage persistent on Emscripten 2026-06-23 00:23:33 +02:00
ROllerozxa
e36bf6a9bc [ci skip] Add a CONTRIBUTING.md that just links to the wiki page 2026-06-22 20:20:51 +02:00
ROllerozxa
1b5dd42b4e [ci skip] Tell issue reporters not to use LLMs to write or "improve" the report 2026-06-22 20:14:12 +02:00
ROllerozxa
ff2e8252de occured -> occurred 2026-06-22 19:31:19 +02:00
ROllerozxa
ec68f7ca47 Some TMS header cleanups
Remove mostly redundant backend.h header, move print.h into core
2026-06-22 19:30:37 +02:00
ROllerozxa
b0cded7e5f Dehardcode some touchscreen-specific codepaths and put them behind touch_controls setting 2026-06-22 00:06:36 +02:00
ROllerozxa
21942f4c0e Fix the "Browse community levels" button glitching when resizing 2026-06-21 23:28:52 +02:00
ROllerozxa
b19e8106dc Fix Android CI 2026-06-21 21:59:53 +02:00
ROllerozxa
e7ddb1a19b Cleanup and hopefully improve the package level select menu
Unfortunately it doesn't entirely adapt to window resizing, but it's slightly better now... ;)
2026-06-21 21:56:36 +02:00
ROllerozxa
40928c047e Remove Luasocket library
This was never really used by anyone and considering it hasn't been updated ever since it was checked into the source tree in 2014 it's a bit of a security issue. It has been hidden behind a level flag for some time, no levels on the community site have the level flag set, and new levels with the flag are blocked from being uploaded.
2026-06-21 17:06:37 +02:00
ROllerozxa
7f87a48538 Add program for portable Windows builds to register a per-user protocol handler
Replaces the old play_community_level.bat Batch script that was bundled with portable
2026-06-21 16:13:53 +02:00
ROllerozxa
713331fe0e Installer: Properly scale and center logo based on display scaling 2026-06-21 00:23:12 +02:00
ROllerozxa
90fd1d87b9 Fix Windows CI 2026-06-20 21:35:41 +02:00
ROllerozxa
c209ca04ce Redesign Windows installer 2026-06-20 21:16:45 +02:00
ROllerozxa
068524c0a1 Call SDL_SetAppMetadata with metadata instead of just setting SDL_HINT_APP_NAME 2026-06-20 21:15:52 +02:00
ROllerozxa
0f0ee6a9aa Fix main menu background not adapting to window resize
Replace hardcoded shader global with uniform
2026-06-20 21:14:07 +02:00
ROllerozxa
99e16dc5a3 Merge branch 'backport-2026-06-19' 2026-06-19 20:00:25 +02:00
ROllerozxa
dc4d71e3e7 Make network header comparisons case insensitive 2026-06-19 19:00:48 +02:00
ROllerozxa
5b209b5e62 Uncomment sound thing in GTK dialogs
Was commented out during the SDL3 migration while sound was disabled
2026-06-19 17:27:25 +02:00
ROllerozxa
9422867597 Refactor Android Java code
Move everything Principia-specific from SDLActivity into PrincipiaActivity and update any references in the dialog code
2026-06-19 17:03:15 +02:00
ROllerozxa
6fb9160601 Rework main entrypoint to use SDL's callbacks instead
Means no special workaround needed on Emscripten to manage the main loop, among other things...
2026-06-19 16:35:33 +02:00
ROllerozxa
1e4dfe80e1 Replace custom UTF-8 step function with SDL_StepUTF8 2026-06-19 16:20:55 +02:00
ROllerozxa
55b1506b0d Fix black level background when doing unity build 2026-06-19 16:12:02 +02:00
ROllerozxa
303b9d2a2f Fix building utils 2026-06-19 00:31:59 +02:00
ROllerozxa
d623a95857 Use filename extensions for determining image format rather than magic 2026-06-19 00:13:04 +02:00
ROllerozxa
22c192b6d2 Remove unused main menu contest stuff
This was never used and didn't really work all that great when I tried implementing it in the featured list creator. Just clean up all references of it.
2026-06-18 22:09:35 +02:00
ROllerozxa
e9d944afa8 Turn some stray C source files in the main src directory into C++ 2026-06-18 21:58:57 +02:00
ROllerozxa
494de12927 Fix zoom and Apparatus grid salute gestures on Android 2026-06-18 20:10:52 +02:00
ROllerozxa
1add6eff33 Hopefully fix Linux CI 2026-06-18 19:30:43 +02:00
ROllerozxa
e1c948f166 Clean up Win32 API usage, explicitly use -W functions rather than defining UNICODE
Also fixes linkage issues with SDL's main wrapper
2026-06-18 19:30:18 +02:00
ROllerozxa
1968f347d2 Try to fix failing CI builds 2026-06-18 00:48:41 +02:00
ROllerozxa
d631ca3d55 Fix SDL3 Android build
Pinch to zoom no longer works which seems to be due to SDL finger IDs changing. There is a new pinch event specifically for this which should be used instead.
2026-06-18 00:28:38 +02:00
ROllerozxa
8dccfb17a4 Use SDL_SetWindowFillDocument on Emscripten to fill browser window 2026-06-18 00:05:25 +02:00
ROllerozxa
c07486f02f Fix SDL3 Windows build 2026-06-18 00:04:19 +02:00
ROllerozxa
73f0dd1195 Fix up CI builds to build with SDL3
Adds a new CMake option to build with a vendored copy of the latest version of SDL3, which gets downloaded during configuration.
2026-06-18 00:04:03 +02:00
ROllerozxa
e00585b9fe Make audio work again on SDL3
Currently using the legacy `sdl2-api-on-sdl3` branch of SDL_mixer, which isn't recommended to be used but works for now as the real SDL3_mixer has a completely different API.
2026-06-17 21:19:16 +02:00
ROllerozxa
15600f1682 Update SDL_image to latest in upstream
There were two indexed images with transparency that became messed up with the new SDL_image. I'm not sure why but I resaved them as non-indexed in GIMP, they grew a bit but now look fine.

Also replace savepng with SDL_image's IMG_SavePNG.
2026-06-17 20:10:28 +02:00
ROllerozxa
641b449286 Another round of SDL3 migration 2026-06-17 19:53:41 +02:00
ROllerozxa
74c7cdb517 Incomplete SDL3 port
WIP, Some things have been stubbed out for now.
2026-06-17 14:53:45 +02:00
ROllerozxa
f75299ee8d Add fallback for Windows cache dir if LOCALAPPDATA is undefined 2026-06-10 00:02:15 +02:00
ROllerozxa
0922b84a69 Fix building Dear Imgui backend on Windows 2026-06-09 23:45:23 +02:00
ROllerozxa
b55735c2a0 Fix unity build on Windows 2026-06-09 23:43:23 +02:00
ROllerozxa
4c395f09af Build TMS as a unity build too
The C portion of TMS is its own unity build chunk while the portions of TMS in C++ are lumped into the main chunk
2026-06-07 21:13:52 +02:00
ROllerozxa
1505be8191 Use #pragma once in TMS sources 2026-06-07 20:55:02 +02:00
ROllerozxa
a2b7870842 Clean up TMS includes in Principia code
Always only `#include <tms/cpp.hh>` in C++ source files to make sure everything gets included with the correct declarations (i.e. `extern "C"` for TMS C stuff)
2026-06-07 20:50:17 +02:00
ROllerozxa
9ffb133da5 Add experimental unity build support behind UNITY_BUILD compile option
It will build most of the main C++ codebase in one chunk, as well as build the entirety of Box2D in its own chunk. This should significantly improve build times for full builds.
2026-06-07 19:20:21 +02:00
ROllerozxa
76bd4d9c70 Undefine preprocessor defines that conflict across source files 2026-06-07 16:34:45 +02:00
ROllerozxa
df00758dfe Move some static global variables that cause collisions into static class members 2026-06-07 16:12:32 +02:00
ROllerozxa
e1ceb49cd2 Rename and deduplicate name colliding vert structs 2026-06-07 00:51:15 +02:00
320 changed files with 6878 additions and 11801 deletions

2
.github/CONTRIBUTING.md vendored Normal file
View file

@ -0,0 +1,2 @@
# 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.

View file

@ -4,8 +4,8 @@ labels: ["Bug"]
body:
- type: markdown
attributes:
value: Thank you for helping to make the game better by reporting issues that you discover. Please make sure to describe the necessary details about the bug such that it can be reproduced by others and hopefully be fixed.
- type: textarea
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.

View file

@ -30,10 +30,10 @@ jobs:
- 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 --no-install-recommends
wget https://github.com/principia-game/linux-deps/releases/download/latest/deps.tar.gz -O linux-deps.tar.gz
tar -xaf linux-deps.tar.gz -C /
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: |

View file

@ -18,19 +18,21 @@ on:
jobs:
linux_ss:
runs-on: ubuntu-22.04
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 libsdl2-dev cmake ninja-build --no-install-recommends
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
cmake .. -DSCREENSHOT_BUILD=ON -G Ninja -DUSE_VENDORED_SDL3=ON
ninja -j4
- name: Upload output as artifact

View file

@ -29,7 +29,7 @@ jobs:
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 sdl2 gtk+3
brew install cmake ninja libpng libjpeg-turbo freetype sdl3 gtk+3
- name: Compile
run: |

View file

@ -35,7 +35,7 @@ jobs:
- name: Build
run: |
mkdir build; cd build
emcmake cmake .. -G Ninja -DCMAKE_INSTALL_PREFIX=
emcmake cmake .. -G Ninja -DCMAKE_INSTALL_PREFIX= -DUSE_VENDORED_SDL3=ON
ninja
DESTDIR=../web ninja install

View file

@ -41,7 +41,7 @@ jobs:
gtk3:p
libpng:p
libjpeg-turbo:p
SDL2:p
sdl3:p
nsis:p
7zip:p

View file

@ -15,6 +15,8 @@ endif()
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/")
include(DownloadLib)
add_custom_target(GenerateGitVersion
COMMAND ${CMAKE_COMMAND}
-D "GENERATE_VERSION_SOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR}"
@ -22,19 +24,24 @@ add_custom_target(GenerateGitVersion
-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()
if(NOT EMSCRIPTEN)
find_package(SDL2 REQUIRED)
endif()
set(OpenGL_GL_PREFERENCE GLVND)
find_package(OpenGL REQUIRED)
endif()
@ -102,32 +109,47 @@ include_directories(
src/
${CMAKE_CURRENT_BINARY_DIR})
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/*.cc
src/luascript/*.cc
src/*.c
file(GLOB SRCS CONFIGURE_DEPENDS
lib/GLAD/src/gl.c
lib/lua/*.c
lib/SDL_image/*.c
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
)
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
file(GLOB IMGUI_SRCS CONFIGURE_DEPENDS
lib/imgui/*.cpp
lib/imgui/misc/freetype/*.cpp
src/ui/*.cc)
@ -153,39 +175,22 @@ if(NOT SCREENSHOT_BUILD)
add_definitions(-DBUILD_CURL)
endif()
file(GLOB SDL_mixer_SRCS lib/SDL_mixer/*.c)
file(GLOB SDL_mixer_SRCS CONFIGURE_DEPENDS lib/SDL_mixer/*.c)
list(APPEND SRCS ${SDL_mixer_SRCS})
endif()
# Optional luasocket functionality
if(NOT UNITY_BUILD)
if(SCREENSHOT_BUILD)
set(BACKEND_SRC main_screenshotter.cc)
else()
set(BACKEND_SRC main.cc)
list(APPEND SRCS src/tms/backend/pipe.cc)
endif()
if(NOT SCREENSHOT_BUILD AND NOT EMSCRIPTEN)
option(USE_LUASOCKET "Build with Luasocket support" TRUE)
else()
set(USE_LUASOCKET FALSE)
list(APPEND SRCS src/tms/backend/${BACKEND_SRC})
endif()
if(USE_LUASOCKET)
add_definitions(-DBUILD_LUASOCKET)
file(GLOB LUASOCKET_SRCS
lib/luasocket/*.c)
list(APPEND SRCS ${LUASOCKET_SRCS})
endif()
if(SCREENSHOT_BUILD)
set(BACKEND_SRC main_screenshotter.cc)
else()
set(BACKEND_SRC main.cc)
list(APPEND SRCS src/tms/backend/pipe.cc)
endif()
list(APPEND SRCS src/tms/backend/${BACKEND_SRC})
if(WIN32)
list(APPEND SRCS packaging/principia.rc)
endif()
@ -211,11 +216,6 @@ if(NOT EMSCRIPTEN)
PNG::PNG
ZLIB::ZLIB)
if(WIN32)
list(APPEND LIBS SDL2::SDL2main)
endif()
list(APPEND LIBS SDL2::SDL2)
if(NOT SCREENSHOT_BUILD AND NOT EMSCRIPTEN)
list(APPEND LIBS ${CURL_LIBRARIES})
@ -225,6 +225,8 @@ if(NOT EMSCRIPTEN)
endif()
endif()
list(APPEND LIBS SDL3::SDL3)
if(SHOULD_USE_GLES)
list(APPEND LIBS GLESv2)
else()
@ -233,8 +235,6 @@ endif()
if(ANDROID)
list(APPEND LIBS android dl log OpenSLES)
elseif(WIN32)
list(APPEND LIBS ws2_32.lib version.lib shlwapi.lib winmm.lib)
endif()
target_link_libraries(${PROJECT_NAME} ${LIBS})
@ -248,8 +248,12 @@ if(SHOULD_USE_GLES)
add_definitions(-DTMS_USE_GLES)
endif()
if(UNITY_BUILD)
add_definitions(-DUNITY_BUILD)
endif()
if(WIN32)
add_definitions(-D_WIN32_WINNT=0x0501 -DUNICODE)
add_definitions(-D_WIN32_WINNT=0x0501)
elseif(SCREENSHOT_BUILD)
add_definitions(-DNO_UI -DSCREENSHOT_BUILD)
endif()
@ -269,7 +273,7 @@ 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 -sUSE_SDL=2 -pthread")
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()
@ -298,6 +302,15 @@ 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)
endif()
# Installation
# ------------

View file

@ -16,9 +16,7 @@ android {
versionName "2026.06.19"
externalNativeBuild {
ndkBuild {
arguments '-j' + Runtime.getRuntime().availableProcessors()
}
cmake.arguments "-DUSE_VENDORED_SDL3=1"
}
ndk {

View file

@ -1,6 +1,933 @@
package com.bithack.principia;
public class PrincipiaActivity extends TMSActivity
{
import com.bithack.principia.shared.AutosaveDialog;
import com.bithack.principia.shared.CamTargeterDialog;
import com.bithack.principia.shared.ColorChooserDialog;
import com.bithack.principia.shared.CommandPadDialog;
import com.bithack.principia.shared.CommunityDialog;
import com.bithack.principia.shared.ConfirmDialog;
import com.bithack.principia.shared.ConfirmDialog.OnOptionSelectedListener;
import com.bithack.principia.shared.ConsumableDialog;
import com.bithack.principia.shared.DigitalDisplayDialog;
import com.bithack.principia.shared.EventListenerDialog;
import com.bithack.principia.shared.ExportDialog;
import com.bithack.principia.shared.FactoryDialog;
import com.bithack.principia.shared.FrequencyDialog;
import com.bithack.principia.shared.FrequencyRangeDialog;
import com.bithack.principia.shared.FxEmitterDialog;
import com.bithack.principia.shared.InfoDialog;
import com.bithack.principia.shared.ImportDialog;
import com.bithack.principia.shared.JumperDialog;
import com.bithack.principia.shared.Level;
import com.bithack.principia.shared.LevelDialog;
import com.bithack.principia.shared.LoginDialog;
import com.bithack.principia.shared.NewLevelDialog;
import com.bithack.principia.shared.OpenDialog;
import com.bithack.principia.shared.PkgLevelDialog;
import com.bithack.principia.shared.PlayDialog;
import com.bithack.principia.shared.PromptDialog;
import com.bithack.principia.shared.PromptSettingsDialog;
import com.bithack.principia.shared.PublishDialog;
import com.bithack.principia.shared.PublishedDialog;
import com.bithack.principia.shared.QuickaddDialog;
import com.bithack.principia.shared.RegisterDialog;
import com.bithack.principia.shared.ResourceDialog;
import com.bithack.principia.shared.RobotDialog;
import com.bithack.principia.shared.RubberDialog;
import com.bithack.principia.shared.SandboxTipsDialog;
import com.bithack.principia.shared.SaveAsDialog;
import com.bithack.principia.shared.ScriptDialog;
import com.bithack.principia.shared.SequencerDialog;
import com.bithack.principia.shared.SettingsDialog;
import com.bithack.principia.shared.Sfx2Dialog;
import com.bithack.principia.shared.SfxDialog;
import com.bithack.principia.shared.ShapeExtruderDialog;
import com.bithack.principia.shared.PolygonDialog;
import com.bithack.principia.shared.KeyListenerDialog;
import com.bithack.principia.shared.EmitterDialog;
import com.bithack.principia.shared.DecorationDialog;
import com.bithack.principia.shared.AnimalDialog;
import com.bithack.principia.shared.StickyDialog;
import com.bithack.principia.shared.SynthesizerDialog;
import com.bithack.principia.shared.TimerDialog;
import com.bithack.principia.shared.ToolDialog;
import com.bithack.principia.shared.TouchFieldDialog;
import com.bithack.principia.shared.VariableDialog;
import com.bithack.principia.shared.SoundManDialog;
import com.bithack.principia.shared.MultiSelectDialog;
import com.bithack.principia.shared.VendorDialog;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.ActivityNotFoundException;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.Locale;
import android.annotation.SuppressLint;
import android.app.*;
import android.content.*;
import android.content.DialogInterface.OnKeyListener;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.view.*;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.MenuItem.OnMenuItemClickListener;
import android.view.View.OnClickListener;
import android.view.ViewGroup.LayoutParams;
import android.webkit.CookieManager;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
import android.widget.Toast;
import android.net.Uri;
import android.os.*;
import android.graphics.*;
import android.media.*;
import android.hardware.*;
import android.widget.ArrayAdapter;
import org.libsdl.app.SDLActivity;
import java.util.List;
public class PrincipiaActivity extends SDLActivity implements View.OnSystemUiVisibilityChangeListener, DialogInterface.OnDismissListener, DialogInterface.OnShowListener, OnSeekBarChangeListener {
private static final String TAG = "PrincipiaActivity";
public static Dialog wv_dialog;
public static WebView wv;
public static CookieManager wv_cm;
public static int num_dialogs = 0;
static Toast last_toast = null;
private static SettingsDialog settings_dialog;
public static final int LEVEL_LOCAL = 0;
public static final int LEVEL_DB = 1;
public static final int LEVEL_MAIN = 2;
public static final int LEVEL_SYS = 3;
public static final int LEVEL_PARTIAL = 4;
public static final int LEVEL_LOCAL_STATE = 100;
public static final int LEVEL_DB_STATE = 101;
public static final int LEVEL_MAIN_STATE = 102;
public static final int ACTION_OPEN = 1;
public static final int ACTION_RELOAD_GRAPHICS = 2;
public static final int ACTION_RELOAD_LEVEL = 3;
public static final int ACTION_SAVE = 4;
public static final int ACTION_NEW_LEVEL = 5;
public static final int ACTION_STICKY = 6;
public static final int ACTION_LOGIN = 7;
public static final int ACTION_SAVE_COPY = 8;
public static final int ACTION_CONSTRUCT_ENTITY = 9;
public static final int ACTION_OPEN_PLAY = 10;
public static final int ACTION_PUBLISH = 11;
public static final int ACTION_PLAY_PKG = 12;
public static final int ACTION_WARP = 13;
public static final int ACTION_PUBLISH_PKG = 14;
public static final int ACTION_PING = 15;
public static final int ACTION_UPGRADE_LEVEL = 16;
public static final int ACTION_DERIVE = 17;
public static final int ACTION_SET_STICKY_TEXT = 18;
public static final int ACTION_IMPORT_OBJECT = 19;
public static final int ACTION_EXPORT_OBJECT = 20;
public static final int ACTION_MULTIEMITTER_SET = 21;
public static final int ACTION_PUZZLEPLAY = 22;
public static final int ACTION_EDIT = 23;
public static final int ACTION_AUTOFIT_LEVEL_BORDERS = 24;
public static final int ACTION_RESTART_LEVEL = 25;
public static final int ACTION_BACK = 26;
public static final int ACTION_RESELECT = 27;
public static final int ACTION_HIGHLIGHT_SELECTED = 28;
public static final int ACTION_OPEN_AUTOSAVE = 31;
public static final int ACTION_REMOVE_AUTOSAVE = 32;
public static final int ACTION_GOTO_MAINMENU = 33;
public static final int ACTION_DELETE_LEVEL = 34;
public static final int ACTION_DELETE_PARTIAL = 35;
public static final int ACTION_SET_LEVEL_TYPE = 36;
public static final int ACTION_RELOAD_DISPLAY = 37;
public static final int ACTION_ENTITY_MODIFIED = 38;
public static final int ACTION_SET_MODE = 39;
public static final int ACTION_MAIN_MENU_PKG = 40;
public static final int ACTION_WORLD_PAUSE = 41;
public static final int ACTION_CONSTRUCT_ITEM = 45;
public static final int ACTION_SUBMIT_SCORE = 46;
public static final int ACTION_MULTI_DELETE = 47;
public static final int ACTION_OPEN_STATE = 48;
public static final int ACTION_AUTOSAVE = 49;
public static final int ACTION_MULTI_JOINT_STRENGTH = 50;
public static final int ACTION_MULTI_PLASTIC_COLOR = 51;
public static final int ACTION_MULTI_PLASTIC_DENSITY = 52;
public static final int ACTION_MULTI_CHANGE_CONNECTION_RENDER_TYPE = 53;
public static final int ACTION_MULTI_UNLOCK_ALL = 54;
public static final int ACTION_MULTI_DISCONNECT_ALL = 55;
public static final int ACTION_SAVE_STATE = 65;
public static final int ACTION_SELF_DESTRUCT = 71;
public static final int DIALOG_SANDBOX_MENU = 1;
public static final int DIALOG_QUICKADD = 100;
public static final int DIALOG_BEAM_COLOR = 101;
public static final int DIALOG_SAVE = 102;
public static final int DIALOG_OPEN = 103;
public static final int DIALOG_NEW_LEVEL = 104;
public static final int DIALOG_SET_FREQUENCY = 105;
public static final int DIALOG_PIXEL_COLOR = 106;
public static final int DIALOG_CONFIRM_QUIT = 107;
public static final int DIALOG_SET_COMMAND = 108;
public static final int DIALOG_STICKY = 109;
public static final int DIALOG_FXEMITTER = 110;
public static final int DIALOG_CAMTARGETER = 111;
public static final int DIALOG_SET_FREQ_RANGE = 112;
public static final int DIALOG_OPEN_OBJECT = 113;
public static final int DIALOG_EXPORT = 114;
public static final int DIALOG_SET_PKG_LEVEL = 115;
public static final int DIALOG_ROBOT = 116;
public static final int DIALOG_MULTIEMITTER = 117;
public static final int DIALOG_PUZZLE_PLAY = 118;
public static final int DIALOG_TIMER = 119;
public static final int DIALOG_EVENTLISTENER = 120;
public static final int DIALOG_SETTINGS = 121;
public static final int DIALOG_SAVE_COPY = 122;
public static final int DIALOG_LEVEL_PROPERTIES = 123;
public static final int DIALOG_LEVEL_INFO = 124;
public static final int DIALOG_DIGITAL_DISPLAY = 125;
public static final int DIALOG_PLAY_MENU = 126;
public static final int DIALOG_OPEN_AUTOSAVE = 127;
public static final int DIALOG_COMMUNITY = 128;
public static final int DIALOG_PROMPT_SETTINGS = 129;
public static final int DIALOG_PROMPT = 130;
public static final int DIALOG_SFX_EMITTER = 131;
public static final int DIALOG_VARIABLE = 132;
public static final int DIALOG_SYNTHESIZER = 133;
public static final int DIALOG_SEQUENCER = 134;
public static final int DIALOG_SHAPEEXTRUDER = 135;
public static final int DIALOG_JUMPER = 136;
public static final int DIALOG_PUBLISHED = 138;
public static final int DIALOG_TOUCHFIELD = 140;
public static final int DIALOG_ESCRIPT = 141;
public static final int DIALOG_ITEM = 142;
public static final int DIALOG_SANDBOX_MODE = 143;
public static final int DIALOG_RUBBER = 144;
public static final int DIALOG_SOUNDMAN = 148;
public static final int DIALOG_FACTORY = 149;
public static final int DIALOG_SET_FACTION = 150;
public static final int DIALOG_RESOURCE = 151;
public static final int DIALOG_VENDOR = 152;
public static final int DIALOG_ANIMAL = 153;
public static final int DIALOG_POLYGON = 154;
public static final int DIALOG_KEY_LISTENER = 155;
public static final int DIALOG_OPEN_STATE = 156;
public static final int DIALOG_POLYGON_COLOR = 157;
public static final int DIALOG_MULTI_CONFIG = 158;
public static final int DIALOG_EMITTER = 159;
public static final int DIALOG_TREASURE_CHEST = 160;
public static final int DIALOG_DECORATION = 161;
public static final int DIALOG_SFX_EMITTER_2 = 162; // New SFX Emitter dialog (1.5.1+)
public static final int DIALOG_PUBLISH = 300;
public static final int DIALOG_LOGIN = 301;
public static final int DIALOG_SANDBOX_TIPS = 302;
public static final int DIALOG_REGISTER = 303;
public static final int CLOSE_ALL_DIALOGS = 200;
public static final int CLOSE_ABSOLUTELY_ALL_DIALOGS = 201;
public static final int CLOSE_REGISTER_DIALOG = 202;
public static final int DISABLE_REGISTER_LOADER = 203;
public static final int PROMPT_RESPONSE_NONE = 0;
public static final int PROMPT_RESPONSE_A = 1;
public static final int PROMPT_RESPONSE_B = 2;
public static final int PROMPT_RESPONSE_C = 3;
@Override
protected String[] getLibraries() {
return new String[] {
"principia"
};
}
public static PrincipiaActivity mSingleton;
@Override
protected void onCreate(Bundle savedInstanceState) {
mSingleton = this;
super.onCreate(savedInstanceState);
this.init_webview();
this.handle_intent(this.getIntent());
open_adapter = new ArrayAdapter<Level>(SDLActivity.mSingleton,
android.R.layout.select_dialog_item);
QuickaddDialog.object_adapter = new ArrayAdapter<String>(this, android.R.layout.simple_dropdown_item_1line);
}
public static void message(final String s, final int longd)
{
Log.v("tes message", s);
SDLActivity.mSingleton.runOnUiThread(new Runnable(){
public void run() {
if (last_toast != null) {
last_toast.setText(s);
} else {
last_toast = Toast.makeText(SDLActivity.mSingleton, s, longd==1?Toast.LENGTH_LONG:Toast.LENGTH_SHORT);
}
last_toast.show();
}
});
}
public static void emit_signal(final int signal_id)
{
SDLActivity.mSingleton.runOnUiThread(new Runnable(){
public void run() {
if (signal_id == 200) { // SIGNAL_QUICKADD_REFRESH
Log.v("Principia", "Quickadd refresh.");
QuickaddDialog.object_adapter.clear();
String[] objects = PrincipiaBackend.getObjects().split(",");
Log.v("Principia", String.format("Number of objects: %d", objects.length));
for (String name : objects) {
QuickaddDialog.object_adapter.add(name);
}
}
}
});
}
public static void open_url(final String url)
{
SDLActivity.mSingleton.runOnUiThread(new Runnable(){
public void run() {
String community_host = PrincipiaBackend.getCommunityHost();
if (wv_cm != null) {
String curl_token = PrincipiaBackend.getCookies();
if (curl_token != null) {
// we got relevant cookies from curl!
wv_cm.setCookie("."+community_host, "_PRINCSECURITY="+curl_token);
}
}
wv.stopLoading();
wv.loadUrl(url);
wv_dialog.show();
}
});
}
@SuppressLint("SetJavaScriptEnabled")
public void init_webview()
{
wv_dialog = new Dialog(this, android.R.style.Theme_NoTitleBar_Fullscreen) {
@Override
protected void onCreate(Bundle saved_instance) {
super.onCreate(saved_instance);
getWindow().setLayout(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
}
};
View v = LayoutInflater.from(this).inflate(R.layout.webview, null);
LinearLayout ll = (LinearLayout)v.findViewById(R.id.wv_ll);
final ProgressBar pb = (ProgressBar)v.findViewById(R.id.wv_progress);
final TextView pb_tv = (TextView)v.findViewById(R.id.wv_progresstext);
wv = new WebView(this);
wv.getSettings().setJavaScriptEnabled(true);
int version = 0;
PackageInfo pi;
try {
pi = getPackageManager().getPackageInfo(getPackageName(), 0);
version = pi.versionCode;
} catch (NameNotFoundException e) {
version = 0;
}
wv.getSettings().setUserAgentString(String.format(Locale.US, "Principia WebView/%d (Android)", version));
wv.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
Uri uri = Uri.parse(url);
String host = uri.getHost();
if (uri.getScheme().equals("principia")) {
Log.v("Principia", "set arg "+url);
PrincipiaBackend.setarg(url);
wv_dialog.dismiss();
} else if (host.contains(PrincipiaBackend.getCommunityHost())) {
Log.v("Principia", "Load url "+url);
view.stopLoading();
view.loadUrl(url);
} else {
Log.v(TAG, "unhandled url " + url);
Log.v(TAG, "host: '" + uri.getHost()+"'");
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
try {
SDLActivity.mSingleton.startActivity(intent);
} catch (ActivityNotFoundException e) {
Log.v(TAG, "No app found to open url: " + url);
}
wv_dialog.dismiss();
}
return true;
}
@Override
public void onPageFinished(WebView view, String url)
{
Log.v("Principia", "page finished: " + url);
pb.setVisibility(View.GONE);
pb_tv.setVisibility(View.GONE);
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon)
{
Log.v("Principia", "page started: " + url);
pb.setVisibility(View.VISIBLE);
pb_tv.setVisibility(View.VISIBLE);
}
});
ll.addView(wv);
Button wv_close = (Button)v.findViewById(R.id.wv_close);
wv_close.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
wv_dialog.dismiss();
}
});
Button wv_reload = (Button)v.findViewById(R.id.wv_reload);
wv_reload.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
wv.reload();
}
});
wv_dialog.setContentView(v);
wv_dialog.setOnShowListener(this);
wv_dialog.setOnDismissListener(this);
wv_cm = CookieManager.getInstance();
wv_dialog.setOnKeyListener(new OnKeyListener() {
@Override
public boolean onKey(DialogInterface dialog, int keyCode,
KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
if (wv.canGoBack()) {
wv.goBack();
} else {
wv_dialog.dismiss();
}
return true;
}
}
return false;
}
});
}
public static boolean is_cool = false;
public static void open_dialog(final int num)
{
open_dialog(num, false);
}
public static void open_dialog(final int num, final boolean is_cool)
{
PrincipiaActivity.is_cool = is_cool;
SDLActivity.mSingleton.runOnUiThread(new Runnable(){
public void run() {
if (num == DIALOG_PROMPT) {
try {SDLActivity.mSingleton.removeDialog(num);} catch(Exception e){};
}
if (num == DIALOG_PLAY_MENU) {
try {SDLActivity.mSingleton.removeDialog(num);} catch(Exception e){};
}
try {SDLActivity.mSingleton.removeDialog(DIALOG_OPEN);} catch(Exception e){};
try {SDLActivity.mSingleton.removeDialog(DIALOG_OPEN_STATE);} catch(Exception e){};
try {SDLActivity.mSingleton.removeDialog(DIALOG_MULTIEMITTER);} catch(Exception e){};
try {SDLActivity.mSingleton.removeDialog(DIALOG_OPEN_OBJECT);} catch(Exception e){};
SDLActivity.mSingleton.showDialog(num);
}
});
}
private List<Dialog> open_dialogs = new ArrayList<Dialog>();
public static void showInfoDialog(final String description)
{
SDLActivity.mSingleton.runOnUiThread(new Runnable(){
public void run() {
try {SDLActivity.mSingleton.removeDialog(DIALOG_LEVEL_INFO);} catch(Exception e){};
InfoDialog.description = description;
SDLActivity.mSingleton.showDialog(DIALOG_LEVEL_INFO);
}
});
}
public static void confirm(final String text, final String button1, final int action1, final long action1_data, final String button2, final int action2, final long action2_data, final String button3, final int action3, final long action3_data, final boolean dna_sandbox)
{
SDLActivity.mSingleton.runOnUiThread(new Runnable(){
public void run() {
new ConfirmDialog()
.set_listener(new OnOptionSelectedListener() {
@Override
public void onOptionSelected(int option) {
if (option == ConfirmDialog.OPTION_YES) {
PrincipiaBackend.addActionAsInt(action1, action1_data);
} else if (option == ConfirmDialog.OPTION_NO) {
PrincipiaBackend.addActionAsInt(action2, action2_data);
} else if (option == ConfirmDialog.OPTION_3) {
PrincipiaBackend.addActionAsInt(action3, action3_data);
}
}
})
.run(text, button1, button2, button3, dna_sandbox);
}
});
}
public static void showSandboxTips()
{
SDLActivity.mSingleton.runOnUiThread(new Runnable(){
public void run() {
try {SDLActivity.mSingleton.removeDialog(DIALOG_SANDBOX_TIPS);} catch(Exception e){};
SDLActivity.mSingleton.showDialog(DIALOG_SANDBOX_TIPS);
}
});
}
@Override
protected Dialog onCreateDialog(int num)
{
Dialog d = null;
switch (num) {
case DIALOG_SANDBOX_MENU:
{
AlertDialog.Builder bld = new AlertDialog.Builder(this);
final CharSequence[] items;
items = new CharSequence[] {
"Level properties",
"New level",
"Save",
"Save a copy",
"Open",
"Publish online",
"Settings",
"Log in",
"Help: Getting started",
"Help: Principia Wiki",
"Browse levels online",
"Back to menu",
"Quit"
};
bld.setItems(items, new DialogInterface.OnClickListener(){
public void onClick(DialogInterface dialog, int which) {
/* TODO: Use dialog fragments */
switch (which) {
case 0: showDialog(DIALOG_LEVEL_PROPERTIES); break;
case 1: showDialog(DIALOG_NEW_LEVEL); break;
case 2: if (PrincipiaBackend.getLevelName().isEmpty()) {SaveAsDialog.refresh_name=true; SaveAsDialog.copy=false; showDialog(DIALOG_SAVE);} else PrincipiaBackend.triggerSave(false); break;
case 3: SaveAsDialog.refresh_name = true; SaveAsDialog.copy = true; showDialog(DIALOG_SAVE_COPY); break;
case 4: try {SDLActivity.mSingleton.removeDialog(DIALOG_OPEN);} catch(Exception e){}; showDialog(DIALOG_OPEN); break;
case 5: showDialog(DIALOG_PUBLISH); break;
case 6: showDialog(DIALOG_SETTINGS); break;
case 7: showDialog(DIALOG_LOGIN); break;
case 8: open_url("https://principia-web.se/wiki/Getting_Started"); break;
case 9: open_url("https://principia-web.se/wiki/Main_Page"); break;
case 10: open_url("https://principia-web.se/"); break;
case 11: PrincipiaBackend.addActionAsInt(ACTION_GOTO_MAINMENU, 0); break;
// why
case 12: android.os.Process.killProcess(android.os.Process.myPid()); break;
}
dialog.dismiss();
}
});
d = bld.create();
break;
}
case DIALOG_SETTINGS:
if (settings_dialog == null) {
d = (settings_dialog = new SettingsDialog()).get_dialog();
}
break;
case DIALOG_QUICKADD: d = QuickaddDialog.get_dialog(); break;
case DIALOG_OPEN: d = (new OpenDialog(false)).get_dialog(); break;
case DIALOG_LEVEL_PROPERTIES: d = LevelDialog.get_dialog(); break;
case DIALOG_SAVE_COPY: d = SaveAsDialog.get_dialog(); break;
case DIALOG_SAVE: d = SaveAsDialog.get_dialog(); break;
case DIALOG_LEVEL_INFO: d = (new InfoDialog()).get_dialog(); break;
case DIALOG_STICKY: d = StickyDialog.get_dialog(); break;
case DIALOG_NEW_LEVEL: d = (new NewLevelDialog()).get_dialog(); break;
case DIALOG_ROBOT: d = RobotDialog.get_dialog(); break;
case DIALOG_CAMTARGETER: d = CamTargeterDialog.get_dialog(); break;
case DIALOG_SET_COMMAND: d = CommandPadDialog.get_dialog(); break;
case DIALOG_FXEMITTER: d = FxEmitterDialog.get_dialog(); break;
case DIALOG_EVENTLISTENER: d = EventListenerDialog.get_dialog(); break;
case DIALOG_SET_PKG_LEVEL: d = PkgLevelDialog.get_dialog(); break;
case DIALOG_PIXEL_COLOR: d = ColorChooserDialog.get_dialog(); break;
case DIALOG_BEAM_COLOR: d = ColorChooserDialog.get_dialog(); break;
case DIALOG_POLYGON_COLOR: d = ColorChooserDialog.get_dialog(); break;
case DIALOG_DIGITAL_DISPLAY: d = DigitalDisplayDialog.get_dialog(); break;
case DIALOG_SET_FREQUENCY: d = FrequencyDialog.get_dialog(); break;
case DIALOG_SET_FREQ_RANGE: d = FrequencyRangeDialog.get_dialog(); break;
case DIALOG_EXPORT: d = ExportDialog.get_dialog(); break;
case DIALOG_MULTIEMITTER: d = (new ImportDialog(true)).get_dialog(); break;
case DIALOG_OPEN_OBJECT: d = (new ImportDialog(false)).get_dialog(); break;
case DIALOG_TIMER: d = TimerDialog.get_dialog(); break;
case DIALOG_PLAY_MENU: d = (PlayDialog.create_dialog()); break;
case DIALOG_OPEN_AUTOSAVE: d = (new AutosaveDialog()).get_dialog(); break;
case DIALOG_COMMUNITY: d = (new CommunityDialog()).get_dialog(); break;
case DIALOG_PROMPT_SETTINGS: d = PromptSettingsDialog.get_dialog(); break;
case DIALOG_PROMPT: d = (new PromptDialog()).get_dialog(); break;
case DIALOG_SFX_EMITTER: d = SfxDialog.get_dialog(); break;
case DIALOG_SFX_EMITTER_2: d = Sfx2Dialog.get_dialog(); break;
case DIALOG_VARIABLE: d = VariableDialog.get_dialog(); break;
case DIALOG_SYNTHESIZER: d = SynthesizerDialog.get_dialog(); break;
case DIALOG_SEQUENCER: d = SequencerDialog.get_dialog(); break;
case DIALOG_JUMPER: d = JumperDialog.get_dialog(); break;
case DIALOG_TOUCHFIELD: d = TouchFieldDialog.get_dialog(); break;
case DIALOG_ESCRIPT: d = ScriptDialog.get_dialog(); break;
case DIALOG_ITEM: d = ConsumableDialog.get_dialog(); break;
case DIALOG_SANDBOX_MODE: d = (new ToolDialog()).get_dialog(); break;
case DIALOG_RUBBER: d = RubberDialog.get_dialog(); break;
case DIALOG_SHAPEEXTRUDER: d = ShapeExtruderDialog.get_dialog(); break;
case DIALOG_POLYGON: d = PolygonDialog.get_dialog(); break;
case DIALOG_KEY_LISTENER: d = KeyListenerDialog.get_dialog(); break;
case DIALOG_EMITTER: d = EmitterDialog.get_dialog(); break;
case DIALOG_DECORATION: d = DecorationDialog.get_dialog(); break;
case DIALOG_ANIMAL: d = AnimalDialog.get_dialog(); break;
case DIALOG_SOUNDMAN: d = SoundManDialog.get_dialog(); break;
case DIALOG_MULTI_CONFIG: d = MultiSelectDialog.get_dialog(); break;
case DIALOG_VENDOR: d = VendorDialog.get_dialog(); break;
case DIALOG_FACTORY: d = FactoryDialog.get_dialog(); break;
case DIALOG_RESOURCE: d = ResourceDialog.get_dialog(); break;
case DIALOG_OPEN_STATE: d = (new OpenDialog(true)).get_dialog(); break;
case DIALOG_PUBLISH: d = PublishDialog.get_dialog(); break;
case DIALOG_PUBLISHED: d = (new PublishedDialog()).get_dialog(); break;
case DIALOG_LOGIN: d = LoginDialog.get_dialog(); break;
case DIALOG_SANDBOX_TIPS: d = (new SandboxTipsDialog()).get_dialog(); break;
case DIALOG_REGISTER: d = RegisterDialog.get_dialog(); break;
case CLOSE_ALL_DIALOGS: break; /* do nothing */
case CLOSE_ABSOLUTELY_ALL_DIALOGS:
SDLActivity.mSingleton.runOnUiThread(new Runnable(){
public void run() {
Log.v("Principia", "Closing all dialogs.");
for (Dialog open_dialog : open_dialogs) {
Log.v("Principia", "Closing a dialog["+open_dialog.toString()+"]");
//open_dialog.dismiss();
}
open_dialogs.clear();
}
});
break;
case CLOSE_REGISTER_DIALOG:
SDLActivity.mSingleton.runOnUiThread(new Runnable(){
public void run() {
RegisterDialog.get_dialog().dismiss();
}
});
break;
case DISABLE_REGISTER_LOADER:
SDLActivity.mSingleton.runOnUiThread(new Runnable(){
public void run() {
RegisterDialog.progressbar.setVisibility(View.GONE);
}
});
break;
default: Log.e("Principia", "Unhandled UI Dialog: "+num); break;
}
if (d != null) {
Log.v("Principia", "Adding dialog: "+ d);
//this.open_dialogs.add(d);
if (d.getWindow() != null) {
d.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);
}
}
return d;
}
public static ArrayAdapter<Level> open_adapter;
@Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
if (v == OpenDialog.lv) {
AdapterContextMenuInfo aInfo = (AdapterContextMenuInfo) menuInfo;
final Level level = open_adapter.getItem(aInfo.position);
menu.setHeaderTitle("Options for " + level.get_name());
menu.add(1, 1, 1, "Delete")
.setOnMenuItemClickListener(new OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
new ConfirmDialog()
.set_listener(new OnOptionSelectedListener() {
@Override
public void onOptionSelected(int option) {
if (option == ConfirmDialog.OPTION_YES) {
PrincipiaBackend.addActionAsTriple(ACTION_DELETE_LEVEL, level.get_level_type(), level.get_id(), level.get_save_id());
open_adapter.remove(level);
}
}
})
.run("Are you sure you want to delete this level?");
return false;
}
});
} else if (v == ImportDialog.lv) {
AdapterContextMenuInfo aInfo = (AdapterContextMenuInfo) menuInfo;
final Level level = ImportDialog.list_adapter.getItem(aInfo.position);
menu.setHeaderTitle("Options for " + level.get_name());
menu.add(1, 1, 1, "Delete")
.setOnMenuItemClickListener(new OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
new ConfirmDialog()
.set_listener(new OnOptionSelectedListener() {
@Override
public void onOptionSelected(int option) {
if (option == ConfirmDialog.OPTION_YES) {
PrincipiaBackend.addActionAsInt(ACTION_DELETE_PARTIAL, level.get_id());
ImportDialog.list_adapter.remove(level);
}
}
})
.run("Are you sure you want to delete this object?");
return false;
}
});
}
/* For the details option:
* Level ID
* Level name
* Date modified
*/
}
@Override
public void onPrepareDialog(int d, Dialog dialog, Bundle bundle)
{
switch (d) {
case DIALOG_SETTINGS:
if (settings_dialog != null) {
settings_dialog.load();
}
break;
case DIALOG_QUICKADD: QuickaddDialog.prepare(dialog); break;
case DIALOG_LEVEL_PROPERTIES: LevelDialog.prepare(dialog); break;
case DIALOG_SAVE: SaveAsDialog.prepare(dialog); break;
case DIALOG_SAVE_COPY: SaveAsDialog.prepare(dialog); break;
case DIALOG_ROBOT: RobotDialog.prepare(dialog); break;
case DIALOG_CAMTARGETER: CamTargeterDialog.prepare(dialog); break;
case DIALOG_SET_COMMAND: CommandPadDialog.prepare(dialog); break;
case DIALOG_FXEMITTER: FxEmitterDialog.prepare(dialog); break;
case DIALOG_EVENTLISTENER: EventListenerDialog.prepare(dialog); break;
case DIALOG_SET_PKG_LEVEL: PkgLevelDialog.prepare(dialog); break;
case DIALOG_PIXEL_COLOR: ColorChooserDialog.prepare(dialog, true); break;
case DIALOG_BEAM_COLOR: ColorChooserDialog.prepare(dialog, false); break;
case DIALOG_POLYGON_COLOR: ColorChooserDialog.prepare(dialog, false); break;
case DIALOG_DIGITAL_DISPLAY: DigitalDisplayDialog.prepare(dialog); break;
case DIALOG_SET_FREQUENCY: FrequencyDialog.prepare(dialog); break;
case DIALOG_SET_FREQ_RANGE: FrequencyRangeDialog.prepare(dialog); break;
case DIALOG_EXPORT: ExportDialog.prepare(dialog); break;
case DIALOG_STICKY: StickyDialog.prepare(dialog); break;
case DIALOG_TIMER: TimerDialog.prepare(dialog); break;
case DIALOG_PROMPT_SETTINGS: PromptSettingsDialog.prepare(dialog); break;
case DIALOG_SFX_EMITTER: SfxDialog.prepare(dialog); break;
case DIALOG_SFX_EMITTER_2: Sfx2Dialog.prepare(dialog); break;
case DIALOG_VARIABLE: VariableDialog.prepare(dialog); break;
case DIALOG_SYNTHESIZER: SynthesizerDialog.prepare(dialog); break;
case DIALOG_SEQUENCER: SequencerDialog.prepare(dialog); break;
case DIALOG_JUMPER: JumperDialog.prepare(dialog); break;
case DIALOG_TOUCHFIELD: TouchFieldDialog.prepare(dialog); break;
case DIALOG_ESCRIPT: ScriptDialog.prepare(dialog); break;
case DIALOG_ITEM: ConsumableDialog.prepare(dialog); break;
case DIALOG_RUBBER: RubberDialog.prepare(dialog); break;
case DIALOG_SHAPEEXTRUDER: ShapeExtruderDialog.prepare(dialog); break;
case DIALOG_POLYGON: PolygonDialog.prepare(dialog); break;
case DIALOG_KEY_LISTENER: KeyListenerDialog.prepare(dialog); break;
case DIALOG_EMITTER: EmitterDialog.prepare(dialog); break;
case DIALOG_DECORATION: DecorationDialog.prepare(dialog); break;
case DIALOG_ANIMAL: AnimalDialog.prepare(dialog); break;
case DIALOG_SOUNDMAN: SoundManDialog.prepare(dialog); break;
case DIALOG_MULTI_CONFIG: MultiSelectDialog.prepare(dialog); break;
case DIALOG_VENDOR: VendorDialog.prepare(dialog); break;
case DIALOG_FACTORY: FactoryDialog.prepare(dialog); break;
case DIALOG_RESOURCE: ResourceDialog.prepare(dialog); break;
case DIALOG_PUBLISH: PublishDialog.prepare(dialog); break;
case DIALOG_LOGIN: LoginDialog.prepare(dialog); break;
}
/* Dialogs that need a separate onShowListener */
switch (d) {
case DIALOG_QUICKADD:
case DIALOG_PUBLISH:
case DIALOG_LOGIN:
case DIALOG_SANDBOX_TIPS:
case DIALOG_REGISTER:
case DIALOG_PROMPT_SETTINGS:
case DIALOG_OPEN:
case DIALOG_OPEN_STATE:
case DIALOG_OPEN_OBJECT:
case DIALOG_MULTIEMITTER:
break;
default: dialog.setOnShowListener(this); break;
}
/* Dialogs that need to dismiss HARD */
switch (d) {
default: dialog.setOnDismissListener(this); break;
}
//dialog.setOnCancelListener(this);
}
public void onDismiss(DialogInterface dialog)
{
Log.v("Principia", "dialog onDismiss called");
open_dialogs.remove(dialog);
num_dialogs --;
if (num_dialogs <= 0){
num_dialogs = 0;
PrincipiaBackend.focusGL(true);
}
}
public void onShow(DialogInterface dialog) {
Log.v("Principia", "dialog onShow called");
this.open_dialogs.add((Dialog) dialog);
num_dialogs ++;
if (num_dialogs == 1) {
PrincipiaBackend.focusGL(false);
}
}
public static void on_show(DialogInterface dialog) {
Log.v("Principia", "dialog onShow called");
num_dialogs ++;
if (num_dialogs == 1) {
PrincipiaBackend.focusGL(false);
}
}
@Override
public void onProgressChanged(SeekBar sb, int progress,
boolean fromUser) {
Log.v("Principia", "Progress changed");
if (sb == SynthesizerDialog.synth_pulse_width) {
SynthesizerDialog.synth_pulse_width_tv.setText(String.format(Locale.US, "%.3f", ((float)progress) / 100.f));
} else if (sb == SynthesizerDialog.synth_bitcrushing) {
SynthesizerDialog.synth_bitcrushing_tv.setText(Integer.toString(progress));
} else if (sb == SynthesizerDialog.synth_volume_vibrato_hz) {
SynthesizerDialog.synth_volume_vibrato_hz_tv.setText(Integer.toString(progress));
} else if (sb == SynthesizerDialog.synth_volume_vibrato_extent) {
SynthesizerDialog.synth_volume_vibrato_extent_tv.setText(String.format(Locale.US, "%.3f", ((float)progress) / 100.f));
} else if (sb == SynthesizerDialog.synth_freq_vibrato_hz) {
SynthesizerDialog.synth_freq_vibrato_hz_tv.setText(Integer.toString(progress));
} else if (sb == SynthesizerDialog.synth_freq_vibrato_extent) {
SynthesizerDialog.synth_freq_vibrato_extent_tv.setText(String.format(Locale.US, "%.3f", ((float)progress) / 100.f));
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
// TODO Auto-generated method stub
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
// TODO Auto-generated method stub
}
private void handle_intent(Intent i)
{
Log.v("Principia", "intent new!");
if (i != null) {
if (i.getScheme() != null && i.getScheme().equals("principia")) {
PrincipiaBackend.setarg(i.getDataString());
}
}
}
@Override
public void onNewIntent(Intent i)
{
super.onNewIntent(i);
handle_intent(i);
}
}

View file

@ -1,4 +1,4 @@
package org.libsdl.app;
package com.bithack.principia;
import com.bithack.principia.shared.Settings;
@ -87,7 +87,7 @@ public class PrincipiaBackend
float creature_absorb_time,
float player_respawn_time
);
public static native void setLevelAllowDerivatives(boolean state);
public static native void setLevelLocked(boolean state);
public static native void setarg(String arg);

View file

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

View file

@ -1,9 +1,7 @@
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;
@ -28,7 +26,7 @@ public class AnimalDialog {
s_animal = (Spinner)view.findViewById(R.id.s_animal);
String[] consumables = PrincipiaBackend.getAnimals().split(",.,");
ArrayAdapter<String> spinnerArrayAdapter = new ArrayAdapter<String>(SDLActivity.mSingleton, android.R.layout.select_dialog_item, consumables);
ArrayAdapter<String> spinnerArrayAdapter = new ArrayAdapter<String>(PrincipiaActivity.mSingleton, android.R.layout.select_dialog_item, consumables);
s_animal.setAdapter(spinnerArrayAdapter);
_dialog = new AlertDialog.Builder(PrincipiaActivity.mSingleton)

View file

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

View file

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

View file

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

View file

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

View file

@ -1,8 +1,6 @@
package com.bithack.principia.shared;
import org.libsdl.app.PrincipiaBackend;
import org.libsdl.app.SDLActivity;
import com.bithack.principia.PrincipiaBackend;
import com.bithack.principia.PrincipiaActivity;
import android.app.AlertDialog;
@ -20,13 +18,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){
SDLActivity.wv.loadUrl(PrincipiaBackend.getCurrentCommunityUrl());
SDLActivity.wv_dialog.show();
PrincipiaActivity.wv.loadUrl(PrincipiaBackend.getCurrentCommunityUrl());
PrincipiaActivity.wv_dialog.show();
}}
)
.setNegativeButton("Main menu", new OnClickListener(){
public void onClick(DialogInterface dialog, int which){
PrincipiaBackend.addActionAsInt(SDLActivity.ACTION_GOTO_MAINMENU, 0);
PrincipiaBackend.addActionAsInt(PrincipiaActivity.ACTION_GOTO_MAINMENU, 0);
}}
)
.create();

View file

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

View file

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

View file

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

View file

@ -4,8 +4,7 @@ import java.util.ArrayList;
import java.util.List;
import com.bithack.principia.PrincipiaActivity;
import com.bithack.principia.R;
import org.libsdl.app.PrincipiaBackend;
import org.libsdl.app.SDLActivity;
import com.bithack.principia.PrincipiaBackend;
import android.app.AlertDialog;
import android.app.Dialog;
@ -64,7 +63,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(SDLActivity.getContext());
np_initial_position = new com.bithack.principia.shared.NumberPicker(PrincipiaActivity.getContext());
np_initial_position.setRange(MIN_INITIAL_POS, 40);
ll_dd.addView((View)np_initial_position);
@ -133,7 +132,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 occured: " + e.getMessage());
Log.e("Principia", "An unknown error occurred: " + e.getMessage());
}
}
});
@ -181,7 +180,7 @@ public class DigitalDisplayDialog {
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (num_symbols == 40) {
SDLActivity.message("Maximum number of symbols reached.", 0);
PrincipiaActivity.message("Maximum number of symbols reached.", 0);
return false;
}
@ -200,7 +199,7 @@ public class DigitalDisplayDialog {
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (num_symbols == 40) {
SDLActivity.message("Maximum number of symbols reached.", 0);
PrincipiaActivity.message("Maximum number of symbols reached.", 0);
return false;
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -24,7 +24,7 @@ import android.widget.ToggleButton;
import com.bithack.principia.PrincipiaActivity;
import com.bithack.principia.R;
import org.libsdl.app.PrincipiaBackend;
import com.bithack.principia.PrincipiaBackend;
import java.util.ArrayList;

View file

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

View file

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

View file

@ -2,8 +2,7 @@ package com.bithack.principia.shared;
import com.bithack.principia.PrincipiaActivity;
import com.bithack.principia.R;
import org.libsdl.app.PrincipiaBackend;
import org.libsdl.app.SDLActivity;
import com.bithack.principia.PrincipiaBackend;
import android.app.AlertDialog;
import android.app.Dialog;
@ -27,7 +26,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(SDLActivity.getContext());
np_level_id = new com.bithack.principia.shared.NumberPicker(PrincipiaActivity.getContext());
np_level_id.setRange(0, 255);
np_level_id.setValue(1);

View file

@ -3,9 +3,8 @@ 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;
@ -21,17 +20,17 @@ public class PlayDialog
*/
static final String[] shared_items = new String[] {
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)
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)
};
static final String save_state = SDLActivity.mSingleton.getString(R.string.save_state);
static final String save_state = PrincipiaActivity.mSingleton.getString(R.string.save_state);
static final String[] community_items = new String[] {
SDLActivity.mSingleton.getString(R.string.restart_level),
SDLActivity.mSingleton.getString(R.string.back_to_community)
PrincipiaActivity.mSingleton.getString(R.string.restart_level),
PrincipiaActivity.mSingleton.getString(R.string.back_to_community)
};
public static Dialog create_dialog()
@ -41,28 +40,28 @@ public class PlayDialog
if (source > 100) {
source -= 100;
}
AlertDialog.Builder bld = new AlertDialog.Builder(SDLActivity.mSingleton);
AlertDialog.Builder bld = new AlertDialog.Builder(PrincipiaActivity.mSingleton);
List<CharSequence> items = new ArrayList<CharSequence>();
if (PrincipiaBackend.getLevelFlag(33)) {
items.add(save_state);
}
items.add(SDLActivity.mSingleton.getString(R.string.open_save));
items.add(PrincipiaActivity.mSingleton.getString(R.string.open_save));
if (PrincipiaBackend.isAdventure()) {
items.add(SDLActivity.mSingleton.getString(R.string.selfdestruct));
items.add(PrincipiaActivity.mSingleton.getString(R.string.selfdestruct));
}
if (source == 1) {
items.add(SDLActivity.mSingleton.getString(R.string.restart_level));
items.add(SDLActivity.mSingleton.getString(R.string.back_to_community));
items.add(PrincipiaActivity.mSingleton.getString(R.string.restart_level));
items.add(PrincipiaActivity.mSingleton.getString(R.string.back_to_community));
} else if (source == 0) {
items.add(SDLActivity.mSingleton.getString(R.string.back_to_sandbox));
items.add(PrincipiaActivity.mSingleton.getString(R.string.back_to_sandbox));
}
items.add(SDLActivity.mSingleton.getString(R.string.back_to_main_menu));
items.add(SDLActivity.mSingleton.getString(R.string.cancel));
items.add(PrincipiaActivity.mSingleton.getString(R.string.back_to_main_menu));
items.add(PrincipiaActivity.mSingleton.getString(R.string.cancel));
final CharSequence[] real_items = items.toArray(new CharSequence[items.size()]);
@ -71,26 +70,26 @@ public class PlayDialog
String cool = real_items[which].toString();
if (cool.equalsIgnoreCase("open save")) {
SDLActivity.mSingleton.runOnUiThread(new Runnable(){
PrincipiaActivity.mSingleton.runOnUiThread(new Runnable(){
public void run() {
try { SDLActivity.mSingleton.removeDialog(SDLActivity.DIALOG_OPEN); } catch(Exception e) {};
try { SDLActivity.mSingleton.removeDialog(SDLActivity.DIALOG_OPEN_STATE); } catch(Exception e) {};
try { PrincipiaActivity.mSingleton.removeDialog(PrincipiaActivity.DIALOG_OPEN); } catch(Exception e) {};
try { PrincipiaActivity.mSingleton.removeDialog(PrincipiaActivity.DIALOG_OPEN_STATE); } catch(Exception e) {};
}
});
SDLActivity.mSingleton.showDialog(SDLActivity.DIALOG_OPEN_STATE);
PrincipiaActivity.mSingleton.showDialog(PrincipiaActivity.DIALOG_OPEN_STATE);
} else if (cool.equalsIgnoreCase("back to sandbox")) {
PrincipiaBackend.addActionAsInt(SDLActivity.ACTION_BACK, 0);
PrincipiaBackend.addActionAsInt(PrincipiaActivity.ACTION_BACK, 0);
} else if (cool.equalsIgnoreCase("back to community")) {
SDLActivity.wv.loadUrl(PrincipiaBackend.getCurrentCommunityUrl());
SDLActivity.wv_dialog.show();
PrincipiaActivity.wv.loadUrl(PrincipiaBackend.getCurrentCommunityUrl());
PrincipiaActivity.wv_dialog.show();
} else if (cool.equalsIgnoreCase("save state")) {
PrincipiaBackend.addActionAsInt(SDLActivity.ACTION_SAVE_STATE, 0);
PrincipiaBackend.addActionAsInt(PrincipiaActivity.ACTION_SAVE_STATE, 0);
} else if (cool.equalsIgnoreCase("back to main menu")) {
PrincipiaBackend.addActionAsInt(SDLActivity.ACTION_GOTO_MAINMENU, 0);
PrincipiaBackend.addActionAsInt(PrincipiaActivity.ACTION_GOTO_MAINMENU, 0);
} else if (cool.equalsIgnoreCase("restart level")) {
PrincipiaBackend.addActionAsInt(SDLActivity.ACTION_RESTART_LEVEL, 0);
PrincipiaBackend.addActionAsInt(PrincipiaActivity.ACTION_RESTART_LEVEL, 0);
} else if (cool.equalsIgnoreCase("self-destruct")) {
PrincipiaBackend.addActionAsInt(SDLActivity.ACTION_SELF_DESTRUCT, 0);
PrincipiaBackend.addActionAsInt(PrincipiaActivity.ACTION_SELF_DESTRUCT, 0);
} else {
Log.e("PRINCIPIA", "UNKNOWN THING: " + cool);
}
@ -110,7 +109,7 @@ public class PlayDialog
.setNeutralButton("Back", new OnClickListener(){
public void onClick(DialogInterface dialog, int which)
{
PrincipiaBackend.addActionAsInt(SDLActivity.ACTION_BACK, 0);
PrincipiaBackend.addActionAsInt(PrincipiaActivity.ACTION_BACK, 0);
}
})
.setNegativeButton("Cancel", new OnClickListener(){

View file

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

View file

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

View file

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

View file

@ -2,8 +2,7 @@ package com.bithack.principia.shared;
import com.bithack.principia.PrincipiaActivity;
import com.bithack.principia.R;
import org.libsdl.app.PrincipiaBackend;
import org.libsdl.app.SDLActivity;
import com.bithack.principia.PrincipiaBackend;
import android.app.AlertDialog;
import android.app.Dialog;
@ -41,7 +40,7 @@ public class PublishDialog
_dialog.setOnShowListener(new OnShowListener() {
@Override
public void onShow(DialogInterface dialog) {
SDLActivity.on_show(dialog);
PrincipiaActivity.on_show(dialog);
Button b = _dialog.getButton(AlertDialog.BUTTON_POSITIVE);
@ -53,7 +52,7 @@ public class PublishDialog
String descr = et_descr.getText().toString().trim();
if (name.length() <= 0) {
SDLActivity.message("You must enter a name for your level!", 0);
PrincipiaActivity.message("You must enter a name for your level!", 0);
return;
}
@ -61,7 +60,7 @@ public class PublishDialog
PrincipiaBackend.setLevelDescription(descr);
PrincipiaBackend.setLevelLocked(cb_locked.isChecked());
PrincipiaBackend.addActionAsInt(SDLActivity.ACTION_PUBLISH, 0);
PrincipiaBackend.addActionAsInt(PrincipiaActivity.ACTION_PUBLISH, 0);
_dialog.dismiss();
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -2,8 +2,7 @@ package com.bithack.principia.shared;
import com.bithack.principia.PrincipiaActivity;
import com.bithack.principia.R;
import org.libsdl.app.PrincipiaBackend;
import org.libsdl.app.SDLActivity;
import com.bithack.principia.PrincipiaBackend;
import android.app.AlertDialog;
import android.app.Dialog;
@ -34,13 +33,13 @@ public class SandboxTipsDialog
.setTitle("Tips & tricks")
.setPositiveButton("OK", new OnClickListener(){public void onClick(DialogInterface dialog, int which) {}})
.setNeutralButton("Next", null)
.setNegativeButton("More tips & tricks", new OnClickListener(){public void onClick(DialogInterface dialog, int which){SDLActivity.open_url("https://principia-web.se/wiki/Getting_Started");}})
.setNegativeButton("More tips & tricks", new OnClickListener(){public void onClick(DialogInterface dialog, int which){PrincipiaActivity.open_url("https://principia-web.se/wiki/Getting_Started");}})
.create();
_dialog.setOnShowListener(new OnShowListener() {
@Override
public void onShow(DialogInterface dialog) {
SDLActivity.on_show(dialog);
PrincipiaActivity.on_show(dialog);
Button b = _dialog.getButton(AlertDialog.BUTTON_NEUTRAL);

View file

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

View file

@ -5,7 +5,7 @@ import com.bithack.principia.R;
import com.bithack.principia.shared.ConfirmDialog.OnOptionSelectedListener;
import com.bithack.principia.shared.CustomLinearLayout.OnKeyboardStateChangedListener;
import org.libsdl.app.PrincipiaBackend;
import com.bithack.principia.PrincipiaBackend;
import android.app.Dialog;
import android.content.DialogInterface;

View file

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

View file

@ -2,7 +2,7 @@ package com.bithack.principia.shared;
import java.util.Locale;
import org.libsdl.app.PrincipiaBackend;
import com.bithack.principia.PrincipiaBackend;
import com.bithack.principia.PrincipiaActivity;
import com.bithack.principia.R;

View file

@ -13,8 +13,7 @@ import android.widget.Spinner;
import com.bithack.principia.PrincipiaActivity;
import com.bithack.principia.R;
import org.libsdl.app.PrincipiaBackend;
import org.libsdl.app.SDLActivity;
import com.bithack.principia.PrincipiaBackend;
public class Sfx2Dialog {
static Dialog _dialog;
@ -36,7 +35,7 @@ public class Sfx2Dialog {
s_sfx = (Spinner)view.findViewById(R.id.s_sfx);
String[] sound_effects = PrincipiaBackend.getSounds().split(",.,");
ArrayAdapter<String> spinnerArrayAdapter = new ArrayAdapter<String>(SDLActivity.mSingleton, android.R.layout.select_dialog_item, sound_effects);
ArrayAdapter<String> spinnerArrayAdapter = new ArrayAdapter<String>(PrincipiaActivity.mSingleton, android.R.layout.select_dialog_item, sound_effects);
s_sfx.setAdapter(spinnerArrayAdapter);
bld.setTitle("SFX Emitter");
@ -73,6 +72,6 @@ public class Sfx2Dialog {
PrincipiaBackend.setPropertyInt8(1, sfx_global.isChecked() ? 1 : 0);
//TODO: property_index 2 (sound chunk)
PrincipiaBackend.setPropertyInt8(3, sfx_loop.isChecked() ? 1 : 0);
SDLActivity.message("Saved properties for SFX Emitter.", 0);
PrincipiaActivity.message("Saved properties for SFX Emitter.", 0);
}
}

View file

@ -1,7 +1,6 @@
package com.bithack.principia.shared;
import org.libsdl.app.PrincipiaBackend;
import org.libsdl.app.SDLActivity;
import com.bithack.principia.PrincipiaBackend;
import com.bithack.principia.PrincipiaActivity;
import com.bithack.principia.R;
@ -33,7 +32,7 @@ public class SfxDialog {
s_sfx = (Spinner)view.findViewById(R.id.s_sfx);
String[] sound_effects = PrincipiaBackend.getSfxSounds().split(",");
ArrayAdapter<String> spinnerArrayAdapter = new ArrayAdapter<String>(SDLActivity.mSingleton, android.R.layout.select_dialog_item, sound_effects);
ArrayAdapter<String> spinnerArrayAdapter = new ArrayAdapter<String>(PrincipiaActivity.mSingleton, android.R.layout.select_dialog_item, sound_effects);
s_sfx.setAdapter(spinnerArrayAdapter);
bld.setTitle("SFX Emitter (Old)");
@ -68,6 +67,6 @@ public class SfxDialog {
{
PrincipiaBackend.setPropertyInt(0, s_sfx.getSelectedItemId());
PrincipiaBackend.setPropertyInt8(1, sfx_global.isChecked() ? 1 : 0);
SDLActivity.message("Saved properties for SFX Emitter.", 0);
PrincipiaActivity.message("Saved properties for SFX Emitter.", 0);
}
}

View file

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

View file

@ -1,7 +1,6 @@
package com.bithack.principia.shared;
import org.libsdl.app.PrincipiaBackend;
import org.libsdl.app.SDLActivity;
import com.bithack.principia.PrincipiaBackend;
import com.bithack.principia.PrincipiaActivity;
import com.bithack.principia.R;
@ -33,7 +32,7 @@ public class SoundManDialog {
s_sounds = (Spinner)view.findViewById(R.id.sm_sounds);
String[] array_data = PrincipiaBackend.getSounds().split(",.,");
ArrayAdapter<String> spinnerArrayAdapter = new ArrayAdapter<String>(SDLActivity.mSingleton, android.R.layout.select_dialog_item, array_data);
ArrayAdapter<String> spinnerArrayAdapter = new ArrayAdapter<String>(PrincipiaActivity.mSingleton, android.R.layout.select_dialog_item, array_data);
s_sounds.setAdapter(spinnerArrayAdapter);
_dialog = new AlertDialog.Builder(PrincipiaActivity.mSingleton)

View file

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

View file

@ -2,8 +2,7 @@ package com.bithack.principia.shared;
import com.bithack.principia.PrincipiaActivity;
import com.bithack.principia.R;
import org.libsdl.app.PrincipiaBackend;
import org.libsdl.app.SDLActivity;
import com.bithack.principia.PrincipiaBackend;
import android.app.Dialog;
import android.os.Bundle;
@ -106,12 +105,12 @@ public class SynthesizerDialog {
synth_freq_vibrato_hz_tv = (TextView)view.findViewById(R.id.synth_freq_vibrato_hz_tv);
synth_freq_vibrato_extent_tv = (TextView)view.findViewById(R.id.synth_freq_vibrato_extent_tv);
synth_pulse_width.setOnSeekBarChangeListener(SDLActivity.mSingleton);
synth_bitcrushing.setOnSeekBarChangeListener(SDLActivity.mSingleton);
synth_volume_vibrato_hz.setOnSeekBarChangeListener(SDLActivity.mSingleton);
synth_volume_vibrato_extent.setOnSeekBarChangeListener(SDLActivity.mSingleton);
synth_freq_vibrato_hz.setOnSeekBarChangeListener(SDLActivity.mSingleton);
synth_freq_vibrato_extent.setOnSeekBarChangeListener(SDLActivity.mSingleton);
synth_pulse_width.setOnSeekBarChangeListener(PrincipiaActivity.mSingleton);
synth_bitcrushing.setOnSeekBarChangeListener(PrincipiaActivity.mSingleton);
synth_volume_vibrato_hz.setOnSeekBarChangeListener(PrincipiaActivity.mSingleton);
synth_volume_vibrato_extent.setOnSeekBarChangeListener(PrincipiaActivity.mSingleton);
synth_freq_vibrato_hz.setOnSeekBarChangeListener(PrincipiaActivity.mSingleton);
synth_freq_vibrato_extent.setOnSeekBarChangeListener(PrincipiaActivity.mSingleton);
sp_waveform = (Spinner)view.findViewById(R.id.synth_waveform);
sp_waveform.setOnItemSelectedListener(new OnItemSelectedListener() {
@ -131,7 +130,7 @@ public class SynthesizerDialog {
});
String[] waveforms = PrincipiaBackend.getSynthWaveforms().split(",");
ArrayAdapter<String> spinner_aa = new ArrayAdapter<String>(SDLActivity.getContext(), android.R.layout.select_dialog_item, waveforms);
ArrayAdapter<String> spinner_aa = new ArrayAdapter<String>(PrincipiaActivity.getContext(), android.R.layout.select_dialog_item, waveforms);
sp_waveform.setAdapter(spinner_aa);
}

View file

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

View file

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

View file

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

View file

@ -2,8 +2,7 @@ package com.bithack.principia.shared;
import com.bithack.principia.PrincipiaActivity;
import com.bithack.principia.R;
import org.libsdl.app.PrincipiaBackend;
import org.libsdl.app.SDLActivity;
import com.bithack.principia.PrincipiaBackend;
import android.app.AlertDialog;
import android.app.Dialog;
@ -82,9 +81,9 @@ public class VariableDialog {
String s = et_name.getText().toString().trim().replaceAll("[^a-zA_Z0-9_-]", "");
if (s.length() > 0 && s.length() < 50) {
PrincipiaBackend.setPropertyString(0, s);
SDLActivity.message("Saved variable name.", 0);
PrincipiaActivity.message("Saved variable name.", 0);
} else {
SDLActivity.message("You must enter a valid variable name. a-zA-Z0-9_- allowed.", 0);
PrincipiaActivity.message("You must enter a valid variable name. a-zA-Z0-9_- allowed.", 0);
}
}
}

View file

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

View file

@ -13,9 +13,8 @@ interface HIDDevice
public String getProductName();
public UsbDevice getDevice();
public boolean open();
public int sendFeatureReport(byte[] report);
public int sendOutputReport(byte[] report);
public boolean getFeatureReport(byte[] report);
public int writeReport(byte[] report, boolean feature);
public boolean readReport(byte[] report, boolean feature);
public void setFrozen(boolean frozen);
public void close();
public void shutdown();

View file

@ -19,9 +19,13 @@ import android.os.*;
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";
@ -33,10 +37,19 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
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;
@ -44,11 +57,15 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
private static final int CHROMEBOOK_CONNECTION_CHECK_INTERVAL = 10000;
static public final UUID steamControllerService = UUID.fromString("100F6C32-1735-4313-B402-38567131E5F3");
static public final UUID inputCharacteristic = UUID.fromString("100F6C33-1735-4313-B402-38567131E5F3");
static public final UUID reportCharacteristic = UUID.fromString("100F6C34-1735-4313-B402-38567131E5F3");
static 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,
@ -61,6 +78,7 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
byte[] mValue;
BluetoothGatt mGatt;
boolean mResult = true;
int mDelayMs = 0;
private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid) {
mGatt = gatt;
@ -68,6 +86,13 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
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;
@ -75,6 +100,14 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
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;
@ -136,6 +169,8 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
return mResult;
}
public int getDelayMs() { return mDelayMs; }
private BluetoothGattCharacteristic getCharacteristic(UUID uuid) {
BluetoothGattService valveService = mGatt.getService(steamControllerService);
if (valveService == null)
@ -154,32 +189,38 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
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);
}
}
public HIDDeviceBLESteamController(HIDDeviceManager manager, BluetoothDevice device) {
HIDDeviceBLESteamController(HIDDeviceManager manager, BluetoothDevice device) {
mManager = manager;
mDevice = device;
mDeviceId = mManager.getDeviceIDForIdentifier(getIdentifier());
mIsRegistered = false;
mIsChromebook = mManager.getContext().getPackageManager().hasSystemFeature("org.chromium.arc.device_management");
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
// public void run() {
// void run() {
// finalThis.checkConnectionForChromebookIssue();
// }
// }, CHROMEBOOK_CONNECTION_CHECK_INTERVAL);
}
public String getIdentifier() {
String getIdentifier() {
return String.format("SteamController.%s", mDevice.getAddress());
}
public BluetoothGatt getGatt() {
BluetoothGatt getGatt() {
return mGatt;
}
@ -219,7 +260,7 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
return btManager.getConnectionState(mDevice, BluetoothProfile.GATT);
}
public void reconnect() {
void reconnect() {
if (getConnectionState() != BluetoothProfile.STATE_CONNECTED) {
mGatt.disconnect();
@ -314,8 +355,45 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
Log.v(TAG, "Found Valve steam controller service " + service.getUuid());
for (BluetoothGattCharacteristic chr : service.getCharacteristics()) {
if (chr.getUuid().equals(inputCharacteristic)) {
Log.v(TAG, "Found input characteristic");
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) {
@ -372,21 +450,30 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
mCurrentOperation = mOperations.removeFirst();
}
// Run in main thread
mHandler.post(new Runnable() {
@Override
public void run() {
synchronized (mOperations) {
if (mCurrentOperation == null) {
Log.e(TAG, "Current operation null in executor?");
return;
}
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
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) {
@ -397,16 +484,47 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
}
private void enableNotification(UUID chrUuid) {
GattOperation op = HIDDeviceBLESteamController.GattOperation.enableNotification(mGatt, 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);
}
public void writeCharacteristic(UUID uuid, byte[] value) {
void writeCharacteristic(UUID uuid, byte[] value) {
GattOperation op = HIDDeviceBLESteamController.GattOperation.writeCharacteristic(mGatt, uuid, value);
queueGattOperation(op);
}
public void readCharacteristic(UUID uuid) {
void readCharacteristic(UUID uuid) {
GattOperation op = HIDDeviceBLESteamController.GattOperation.readCharacteristic(mGatt, uuid);
queueGattOperation(op);
}
@ -415,6 +533,7 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
////////////// BluetoothGattCallback overridden methods
//////////////////////////////////////////////////////////////////////////////////////////////////////
@Override
public void onConnectionStateChange(BluetoothGatt g, int status, int newState) {
//Log.v(TAG, "onConnectionStateChange status=" + status + " newState=" + newState);
mIsReconnecting = false;
@ -437,6 +556,7 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
// 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) {
@ -446,23 +566,33 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
mIsConnected = false;
gatt.disconnect();
mGatt = connectGatt(false);
}
else {
} 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.HIDDeviceFeatureReport(getId(), characteristic.getValue());
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());
@ -470,7 +600,7 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
// 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);
mManager.HIDDeviceConnected(getId(), getIdentifier(), getVendorId(), getProductId(), getSerialNumber(), getVersion(), getManufacturerName(), getProductName(), 0, 0, 0, 0, true, mReportId);
setRegistered();
}
}
@ -478,44 +608,68 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
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(inputCharacteristic) && !mFrozen) {
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(inputCharacteristic)) {
boolean hasWrittenInputDescriptor = true;
BluetoothGattCharacteristic reportChr = chr.getService().getCharacteristic(reportCharacteristic);
if (reportChr != null) {
Log.v(TAG, "Writing report characteristic to enter valve mode");
reportChr.setValue(enterValveMode);
gatt.writeCharacteristic(reportChr);
}
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);
}
@ -538,9 +692,20 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
@Override
public int getProductId() {
// We don't have an easy way to query from the Bluetooth device, but we know what it is
final int D0G_BLE2_PID = 0x1106;
return D0G_BLE2_PID;
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
@ -575,50 +740,64 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
}
@Override
public int sendFeatureReport(byte[] report) {
public int writeReport(byte[] report, boolean feature) {
if (!isRegistered()) {
Log.e(TAG, "Attempted sendFeatureReport before Steam Controller is registered!");
Log.e(TAG, "Attempted writeReport before Steam Controller is registered!");
if (mIsConnected) {
probeService(this);
}
return -1;
}
// We need to skip the first byte, as that doesn't go over the air
byte[] actual_report = Arrays.copyOfRange(report, 1, report.length - 1);
//Log.v(TAG, "sendFeatureReport " + HexDump.dumpHexString(actual_report));
writeCharacteristic(reportCharacteristic, actual_report);
return report.length;
}
@Override
public int sendOutputReport(byte[] report) {
if (!isRegistered()) {
Log.e(TAG, "Attempted sendOutputReport before Steam Controller is registered!");
if (mIsConnected) {
probeService(this);
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;
}
//Log.v(TAG, "sendFeatureReport " + HexDump.dumpHexString(report));
writeCharacteristic(reportCharacteristic, report);
return report.length;
return -1;
}
@Override
public boolean getFeatureReport(byte[] report) {
public boolean readReport(byte[] report, boolean feature) {
if (!isRegistered()) {
Log.e(TAG, "Attempted getFeatureReport before Steam Controller is registered!");
Log.e(TAG, "Attempted readReport before Steam Controller is registered!");
if (mIsConnected) {
probeService(this);
}
return false;
}
//Log.v(TAG, "getFeatureReport");
readCharacteristic(reportCharacteristic);
return true;
if (feature) {
readCharacteristic(reportCharacteristic);
return true;
} else {
// Not implemented
return false;
}
}
@Override

View file

@ -32,7 +32,7 @@ public class HIDDeviceManager {
private static HIDDeviceManager sManager;
private static int sManagerRefCount = 0;
public static HIDDeviceManager acquire(Context context) {
static public HIDDeviceManager acquire(Context context) {
if (sManagerRefCount == 0) {
sManager = new HIDDeviceManager(context);
}
@ -40,7 +40,7 @@ public class HIDDeviceManager {
return sManager;
}
public static void release(HIDDeviceManager manager) {
static public void release(HIDDeviceManager manager) {
if (manager == sManager) {
--sManagerRefCount;
if (sManagerRefCount == 0) {
@ -108,12 +108,12 @@ public class HIDDeviceManager {
HIDDeviceRegisterCallback();
mSharedPreferences = mContext.getSharedPreferences("hidapi", Context.MODE_PRIVATE);
mIsChromebook = mContext.getPackageManager().hasSystemFeature("org.chromium.arc.device_management");
mIsChromebook = SDLActivity.isChromebook();
// if (shouldClear) {
// SharedPreferences.Editor spedit = mSharedPreferences.edit();
// spedit.clear();
// spedit.commit();
// spedit.apply();
// }
// else
{
@ -121,11 +121,11 @@ public class HIDDeviceManager {
}
}
public Context getContext() {
Context getContext() {
return mContext;
}
public int getDeviceIDForIdentifier(String identifier) {
int getDeviceIDForIdentifier(String identifier) {
SharedPreferences.Editor spedit = mSharedPreferences.edit();
int result = mSharedPreferences.getInt(identifier, 0);
@ -135,7 +135,7 @@ public class HIDDeviceManager {
}
spedit.putInt(identifier, result);
spedit.commit();
spedit.apply();
return result;
}
@ -193,7 +193,11 @@ public class HIDDeviceManager {
filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
filter.addAction(HIDDeviceManager.ACTION_USB_PERMISSION);
mContext.registerReceiver(mUsbBroadcast, filter);
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);
@ -252,6 +256,8 @@ public class HIDDeviceManager {
0x24c6, // PowerA
0x2c22, // Qanba
0x2dc8, // 8BitDo
0x3537, // GameSir
0x37d7, // Flydigi
0x9886, // ASTRO Gaming
};
@ -284,9 +290,13 @@ public class HIDDeviceManager {
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 &&
@ -351,7 +361,7 @@ public class HIDDeviceManager {
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());
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);
}
}
}
@ -372,7 +382,7 @@ public class HIDDeviceManager {
return;
}
if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE) || (Build.VERSION.SDK_INT < 18 /* Android 4.3 (JELLY_BEAN_MR2) */)) {
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;
}
@ -404,7 +414,11 @@ public class HIDDeviceManager {
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
mContext.registerReceiver(mBluetoothBroadcast, filter);
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());
@ -431,7 +445,7 @@ public class HIDDeviceManager {
// Chromebooks do not pass along ACTION_ACL_CONNECTED / ACTION_ACL_DISCONNECTED properly.
// This function provides a sort of dummy version of that, watching for changes in the
// connected devices and attempting to add controllers as things change.
public void chromebookConnectionHandler() {
void chromebookConnectionHandler() {
if (!mIsChromebook) {
return;
}
@ -470,7 +484,7 @@ public class HIDDeviceManager {
}, 10000);
}
public boolean connectBluetoothDevice(BluetoothDevice bluetoothDevice) {
boolean connectBluetoothDevice(BluetoothDevice bluetoothDevice) {
Log.v(TAG, "connectBluetoothDevice device=" + bluetoothDevice);
synchronized (this) {
if (mBluetoothDevices.containsKey(bluetoothDevice)) {
@ -491,7 +505,7 @@ public class HIDDeviceManager {
return true;
}
public void disconnectBluetoothDevice(BluetoothDevice bluetoothDevice) {
void disconnectBluetoothDevice(BluetoothDevice bluetoothDevice) {
synchronized (this) {
HIDDeviceBLESteamController device = mBluetoothDevices.get(bluetoothDevice);
if (device == null)
@ -505,7 +519,7 @@ public class HIDDeviceManager {
}
}
public boolean isSteamController(BluetoothDevice bluetoothDevice) {
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;
@ -516,7 +530,13 @@ public class HIDDeviceManager {
return false;
}
return bluetoothDevice.getName().equals("SteamController") && ((bluetoothDevice.getType() & BluetoothDevice.DEVICE_TYPE_LE) != 0);
// 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() {
@ -559,7 +579,7 @@ public class HIDDeviceManager {
////////// JNI interface functions
//////////////////////////////////////////////////////////////////////////////////////////////////////
public boolean initialize(boolean usb, boolean bluetooth) {
boolean initialize(boolean usb, boolean bluetooth) {
Log.v(TAG, "initialize(" + usb + ", " + bluetooth + ")");
if (usb) {
@ -571,7 +591,7 @@ public class HIDDeviceManager {
return true;
}
public boolean openDevice(int deviceID) {
boolean openDevice(int deviceID) {
Log.v(TAG, "openDevice deviceID=" + deviceID);
HIDDevice device = getDevice(deviceID);
if (device == null) {
@ -591,13 +611,10 @@ public class HIDDeviceManager {
} else {
flags = 0;
}
if (Build.VERSION.SDK_INT >= 33 /* Android 14.0 (U) */) {
Intent intent = new Intent(HIDDeviceManager.ACTION_USB_PERMISSION);
intent.setPackage(mContext.getPackageName());
mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, intent, flags));
} else {
mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, new Intent(HIDDeviceManager.ACTION_USB_PERMISSION), flags));
}
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);
@ -613,9 +630,9 @@ public class HIDDeviceManager {
return false;
}
public int sendOutputReport(int deviceID, byte[] report) {
int writeReport(int deviceID, byte[] report, boolean feature) {
try {
//Log.v(TAG, "sendOutputReport deviceID=" + deviceID + " length=" + report.length);
//Log.v(TAG, "writeReport deviceID=" + deviceID + " length=" + report.length);
HIDDevice device;
device = getDevice(deviceID);
if (device == null) {
@ -623,33 +640,16 @@ public class HIDDeviceManager {
return -1;
}
return device.sendOutputReport(report);
return device.writeReport(report, feature);
} catch (Exception e) {
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
}
return -1;
}
public int sendFeatureReport(int deviceID, byte[] report) {
boolean readReport(int deviceID, byte[] report, boolean feature) {
try {
//Log.v(TAG, "sendFeatureReport deviceID=" + deviceID + " length=" + report.length);
HIDDevice device;
device = getDevice(deviceID);
if (device == null) {
HIDDeviceDisconnected(deviceID);
return -1;
}
return device.sendFeatureReport(report);
} catch (Exception e) {
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
}
return -1;
}
public boolean getFeatureReport(int deviceID, byte[] report) {
try {
//Log.v(TAG, "getFeatureReport deviceID=" + deviceID);
//Log.v(TAG, "readReport deviceID=" + deviceID);
HIDDevice device;
device = getDevice(deviceID);
if (device == null) {
@ -657,14 +657,14 @@ public class HIDDeviceManager {
return false;
}
return device.getFeatureReport(report);
return device.readReport(report, feature);
} catch (Exception e) {
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
}
return false;
}
public void closeDevice(int deviceID) {
void closeDevice(int deviceID) {
try {
Log.v(TAG, "closeDevice deviceID=" + deviceID);
HIDDevice device;
@ -688,11 +688,11 @@ public class HIDDeviceManager {
private native void HIDDeviceRegisterCallback();
private native void HIDDeviceReleaseCallback();
native void HIDDeviceConnected(int deviceID, String identifier, int vendorId, int productId, String serial_number, int release_number, String manufacturer_string, String product_string, int interface_number, int interface_class, int interface_subclass, int interface_protocol);
native void 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 HIDDeviceFeatureReport(int deviceID, byte[] report);
native void HIDDeviceReportResponse(int deviceID, byte[] report);
}

View file

@ -4,6 +4,7 @@ import android.hardware.usb.*;
import android.os.Build;
import android.util.Log;
import java.util.Arrays;
import java.util.Locale;
class HIDDeviceUSB implements HIDDevice {
@ -20,6 +21,7 @@ class HIDDeviceUSB implements HIDDevice {
protected InputThread mInputThread;
protected boolean mRunning;
protected boolean mFrozen;
protected boolean mClaimed;
public HIDDeviceUSB(HIDDeviceManager manager, UsbDevice usbDevice, int interface_index) {
mManager = manager;
@ -28,10 +30,11 @@ class HIDDeviceUSB implements HIDDevice {
mInterface = mDevice.getInterface(mInterfaceIndex).getId();
mDeviceId = manager.getDeviceIDForIdentifier(getIdentifier());
mRunning = false;
mClaimed = false;
}
public String getIdentifier() {
return String.format("%s/%x/%x/%d", mDevice.getDeviceName(), mDevice.getVendorId(), mDevice.getProductId(), mInterfaceIndex);
String getIdentifier() {
return String.format(Locale.ENGLISH, "%s/%x/%x/%d", mDevice.getDeviceName(), mDevice.getVendorId(), mDevice.getProductId(), mInterfaceIndex);
}
@Override
@ -52,13 +55,11 @@ class HIDDeviceUSB implements HIDDevice {
@Override
public String getSerialNumber() {
String result = null;
if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) {
try {
result = mDevice.getSerialNumber();
}
catch (SecurityException exception) {
//Log.w(TAG, "App permissions mean we cannot get serial number for device " + getDeviceName() + " message: " + exception.getMessage());
}
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 = "";
@ -73,10 +74,8 @@ class HIDDeviceUSB implements HIDDevice {
@Override
public String getManufacturerName() {
String result = null;
if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) {
result = mDevice.getManufacturerName();
}
String result;
result = mDevice.getManufacturerName();
if (result == null) {
result = String.format("%x", getVendorId());
}
@ -85,10 +84,8 @@ class HIDDeviceUSB implements HIDDevice {
@Override
public String getProductName() {
String result = null;
if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) {
result = mDevice.getProductName();
}
String result;
result = mDevice.getProductName();
if (result == null) {
result = String.format("%x", getProductId());
}
@ -100,7 +97,7 @@ class HIDDeviceUSB implements HIDDevice {
return mDevice;
}
public String getDeviceName() {
String getDeviceName() {
return getManufacturerName() + " " + getProductName() + "(0x" + String.format("%x", getVendorId()) + "/0x" + String.format("%x", getProductId()) + ")";
}
@ -119,6 +116,7 @@ class HIDDeviceUSB implements HIDDevice {
close();
return false;
}
mClaimed = true;
// Find the endpoints
for (int j = 0; j < iface.getEndpointCount(); j++) {
@ -137,9 +135,12 @@ class HIDDeviceUSB implements HIDDevice {
}
}
// Make sure the required endpoints were present
if (mInputEndpoint == null || mOutputEndpoint == null) {
// 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;
}
@ -153,55 +154,80 @@ class HIDDeviceUSB implements HIDDevice {
}
@Override
public int sendFeatureReport(byte[] report) {
int res = -1;
int offset = 0;
int length = report.length;
boolean skipped_report_id = false;
byte report_number = report[0];
if (report_number == 0x0) {
++offset;
--length;
skipped_report_id = true;
}
res = mConnection.controlTransfer(
UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_OUT,
0x09/*HID set_report*/,
(3/*HID feature*/ << 8) | report_number,
mInterface,
report, offset, length,
1000/*timeout millis*/);
if (res < 0) {
Log.w(TAG, "sendFeatureReport() returned " + res + " on device " + getDeviceName());
public int writeReport(byte[] report, boolean feature) {
if (mConnection == null) {
Log.w(TAG, "writeReport() called with no device connection");
return -1;
}
if (skipped_report_id) {
++length;
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;
}
return length;
}
@Override
public int sendOutputReport(byte[] report) {
int r = mConnection.bulkTransfer(mOutputEndpoint, report, report.length, 1000);
if (r != report.length) {
Log.w(TAG, "sendOutputReport() returned " + r + " on device " + getDeviceName());
}
return r;
}
@Override
public boolean getFeatureReport(byte[] report) {
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. */
@ -213,7 +239,7 @@ class HIDDeviceUSB implements HIDDevice {
res = mConnection.controlTransfer(
UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_IN,
0x01/*HID get_report*/,
(3/*HID feature*/ << 8) | report_number,
((feature ? 3/*HID feature*/ : 1/*HID Input*/) << 8) | report_number,
mInterface,
report, offset, length,
1000/*timeout millis*/);
@ -234,7 +260,7 @@ class HIDDeviceUSB implements HIDDevice {
} else {
data = Arrays.copyOfRange(report, 0, res);
}
mManager.HIDDeviceFeatureReport(mDeviceId, data);
mManager.HIDDeviceReportResponse(mDeviceId, data);
return true;
}
@ -254,10 +280,13 @@ class HIDDeviceUSB implements HIDDevice {
mInputThread = null;
}
if (mConnection != null) {
UsbInterface iface = mDevice.getInterface(mInterfaceIndex);
mConnection.releaseInterface(iface);
if (mClaimed) {
UsbInterface iface = mDevice.getInterface(mInterfaceIndex);
mConnection.releaseInterface(iface);
}
mConnection.close();
mConnection = null;
mClaimed = false;
}
}
@ -270,6 +299,22 @@ class HIDDeviceUSB implements HIDDevice {
@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 {

View file

@ -1,8 +1,8 @@
package org.libsdl.app;
import android.app.Activity;
import android.content.Context;
import java.lang.Class;
import java.lang.reflect.Method;
/**
@ -12,14 +12,14 @@ public class SDL {
// This function should be called first and sets up the native code
// so it can call into the Java classes
public static void setupJNI() {
static public void setupJNI() {
SDLActivity.nativeSetupJNI();
SDLAudioManager.nativeSetupJNI();
SDLControllerManager.nativeSetupJNI();
}
// This function should be called each time the activity is started
public static void initialize() {
static public void initialize() {
setContext(null);
SDLActivity.initialize();
@ -28,33 +28,33 @@ public class SDL {
}
// This function stores the current activity (SDL or not)
public static void setContext(Context context) {
static public void setContext(Activity context) {
SDLAudioManager.setContext(context);
mContext = context;
}
public static Context getContext() {
static public Activity getContext() {
return mContext;
}
public static void loadLibrary(String libraryName) throws UnsatisfiedLinkError, SecurityException, NullPointerException {
static void loadLibrary(String libraryName) throws UnsatisfiedLinkError, SecurityException, NullPointerException {
loadLibrary(libraryName, mContext);
}
public static void loadLibrary(String libraryName, Context context) throws UnsatisfiedLinkError, SecurityException, NullPointerException {
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
// 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
// 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");
@ -62,7 +62,7 @@ public class SDL {
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
// 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);
@ -86,5 +86,5 @@ public class SDL {
}
}
protected static Context mContext;
protected static Activity mContext;
}

File diff suppressed because it is too large Load diff

View file

@ -3,30 +3,21 @@ package org.libsdl.app;
import android.content.Context;
import android.media.AudioDeviceCallback;
import android.media.AudioDeviceInfo;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
import android.media.MediaRecorder;
import android.os.Build;
import android.util.Log;
import java.util.Arrays;
import java.util.ArrayList;
public class SDLAudioManager {
class SDLAudioManager {
protected static final String TAG = "SDLAudio";
protected static AudioTrack mAudioTrack;
protected static AudioRecord mAudioRecord;
protected static Context mContext;
private static final int[] NO_DEVICES = {};
private static AudioDeviceCallback mAudioDeviceCallback;
public static void initialize() {
mAudioTrack = null;
mAudioRecord = null;
static void initialize() {
mAudioDeviceCallback = null;
if(Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */)
@ -34,464 +25,85 @@ public class SDLAudioManager {
mAudioDeviceCallback = new AudioDeviceCallback() {
@Override
public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
Arrays.stream(addedDevices).forEach(deviceInfo -> addAudioDevice(deviceInfo.isSink(), deviceInfo.getId()));
for (AudioDeviceInfo deviceInfo : addedDevices) {
nativeAddAudioDevice(deviceInfo.isSink(), deviceInfo.getProductName().toString(), deviceInfo.getId());
}
}
@Override
public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
Arrays.stream(removedDevices).forEach(deviceInfo -> removeAudioDevice(deviceInfo.isSink(), deviceInfo.getId()));
for (AudioDeviceInfo deviceInfo : removedDevices) {
nativeRemoveAudioDevice(deviceInfo.isSink(), deviceInfo.getId());
}
}
};
}
}
public static void setContext(Context context) {
static void setContext(Context context) {
mContext = context;
if (context != null) {
registerAudioDeviceCallback();
}
}
public static void release(Context context) {
unregisterAudioDeviceCallback(context);
static void release(Context context) {
// no-op atm
}
// Audio
protected static String getAudioFormatString(int audioFormat) {
switch (audioFormat) {
case AudioFormat.ENCODING_PCM_8BIT:
return "8-bit";
case AudioFormat.ENCODING_PCM_16BIT:
return "16-bit";
case AudioFormat.ENCODING_PCM_FLOAT:
return "float";
default:
return Integer.toString(audioFormat);
}
}
protected static int[] open(boolean isCapture, int sampleRate, int audioFormat, int desiredChannels, int desiredFrames, int deviceId) {
int channelConfig;
int sampleSize;
int frameSize;
Log.v(TAG, "Opening " + (isCapture ? "capture" : "playback") + ", requested " + desiredFrames + " frames of " + desiredChannels + " channel " + getAudioFormatString(audioFormat) + " audio at " + sampleRate + " Hz");
/* On older devices let's use known good settings */
if (Build.VERSION.SDK_INT < 21 /* Android 5.0 (LOLLIPOP) */) {
if (desiredChannels > 2) {
desiredChannels = 2;
}
}
/* AudioTrack has sample rate limitation of 48000 (fixed in 5.0.2) */
if (Build.VERSION.SDK_INT < 22 /* Android 5.1 (LOLLIPOP_MR1) */) {
if (sampleRate < 8000) {
sampleRate = 8000;
} else if (sampleRate > 48000) {
sampleRate = 48000;
}
}
if (audioFormat == AudioFormat.ENCODING_PCM_FLOAT) {
int minSDKVersion = (isCapture ? 23 /* Android 6.0 (M) */ : 21 /* Android 5.0 (LOLLIPOP) */);
if (Build.VERSION.SDK_INT < minSDKVersion) {
audioFormat = AudioFormat.ENCODING_PCM_16BIT;
}
}
switch (audioFormat)
{
case AudioFormat.ENCODING_PCM_8BIT:
sampleSize = 1;
break;
case AudioFormat.ENCODING_PCM_16BIT:
sampleSize = 2;
break;
case AudioFormat.ENCODING_PCM_FLOAT:
sampleSize = 4;
break;
default:
Log.v(TAG, "Requested format " + audioFormat + ", getting ENCODING_PCM_16BIT");
audioFormat = AudioFormat.ENCODING_PCM_16BIT;
sampleSize = 2;
break;
}
if (isCapture) {
switch (desiredChannels) {
case 1:
channelConfig = AudioFormat.CHANNEL_IN_MONO;
break;
case 2:
channelConfig = AudioFormat.CHANNEL_IN_STEREO;
break;
default:
Log.v(TAG, "Requested " + desiredChannels + " channels, getting stereo");
desiredChannels = 2;
channelConfig = AudioFormat.CHANNEL_IN_STEREO;
break;
}
} else {
switch (desiredChannels) {
case 1:
channelConfig = AudioFormat.CHANNEL_OUT_MONO;
break;
case 2:
channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
break;
case 3:
channelConfig = AudioFormat.CHANNEL_OUT_STEREO | AudioFormat.CHANNEL_OUT_FRONT_CENTER;
break;
case 4:
channelConfig = AudioFormat.CHANNEL_OUT_QUAD;
break;
case 5:
channelConfig = AudioFormat.CHANNEL_OUT_QUAD | AudioFormat.CHANNEL_OUT_FRONT_CENTER;
break;
case 6:
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1;
break;
case 7:
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1 | AudioFormat.CHANNEL_OUT_BACK_CENTER;
break;
case 8:
if (Build.VERSION.SDK_INT >= 23 /* Android 6.0 (M) */) {
channelConfig = AudioFormat.CHANNEL_OUT_7POINT1_SURROUND;
} else {
Log.v(TAG, "Requested " + desiredChannels + " channels, getting 5.1 surround");
desiredChannels = 6;
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1;
}
break;
default:
Log.v(TAG, "Requested " + desiredChannels + " channels, getting stereo");
desiredChannels = 2;
channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
break;
}
/*
Log.v(TAG, "Speaker configuration (and order of channels):");
if ((channelConfig & 0x00000004) != 0) {
Log.v(TAG, " CHANNEL_OUT_FRONT_LEFT");
}
if ((channelConfig & 0x00000008) != 0) {
Log.v(TAG, " CHANNEL_OUT_FRONT_RIGHT");
}
if ((channelConfig & 0x00000010) != 0) {
Log.v(TAG, " CHANNEL_OUT_FRONT_CENTER");
}
if ((channelConfig & 0x00000020) != 0) {
Log.v(TAG, " CHANNEL_OUT_LOW_FREQUENCY");
}
if ((channelConfig & 0x00000040) != 0) {
Log.v(TAG, " CHANNEL_OUT_BACK_LEFT");
}
if ((channelConfig & 0x00000080) != 0) {
Log.v(TAG, " CHANNEL_OUT_BACK_RIGHT");
}
if ((channelConfig & 0x00000100) != 0) {
Log.v(TAG, " CHANNEL_OUT_FRONT_LEFT_OF_CENTER");
}
if ((channelConfig & 0x00000200) != 0) {
Log.v(TAG, " CHANNEL_OUT_FRONT_RIGHT_OF_CENTER");
}
if ((channelConfig & 0x00000400) != 0) {
Log.v(TAG, " CHANNEL_OUT_BACK_CENTER");
}
if ((channelConfig & 0x00000800) != 0) {
Log.v(TAG, " CHANNEL_OUT_SIDE_LEFT");
}
if ((channelConfig & 0x00001000) != 0) {
Log.v(TAG, " CHANNEL_OUT_SIDE_RIGHT");
}
*/
}
frameSize = (sampleSize * desiredChannels);
// Let the user pick a larger buffer if they really want -- but ye
// gods they probably shouldn't, the minimums are horrifyingly high
// latency already
int minBufferSize;
if (isCapture) {
minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat);
} else {
minBufferSize = AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat);
}
desiredFrames = Math.max(desiredFrames, (minBufferSize + frameSize - 1) / frameSize);
int[] results = new int[4];
if (isCapture) {
if (mAudioRecord == null) {
mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, sampleRate,
channelConfig, audioFormat, desiredFrames * frameSize);
// see notes about AudioTrack state in audioOpen(), above. Probably also applies here.
if (mAudioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
Log.e(TAG, "Failed during initialization of AudioRecord");
mAudioRecord.release();
mAudioRecord = null;
return null;
}
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */ && deviceId != 0) {
mAudioRecord.setPreferredDevice(getOutputAudioDeviceInfo(deviceId));
}
mAudioRecord.startRecording();
}
results[0] = mAudioRecord.getSampleRate();
results[1] = mAudioRecord.getAudioFormat();
results[2] = mAudioRecord.getChannelCount();
} else {
if (mAudioTrack == null) {
mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, channelConfig, audioFormat, desiredFrames * frameSize, AudioTrack.MODE_STREAM);
// Instantiating AudioTrack can "succeed" without an exception and the track may still be invalid
// Ref: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/media/java/android/media/AudioTrack.java
// Ref: http://developer.android.com/reference/android/media/AudioTrack.html#getState()
if (mAudioTrack.getState() != AudioTrack.STATE_INITIALIZED) {
/* Try again, with safer values */
Log.e(TAG, "Failed during initialization of Audio Track");
mAudioTrack.release();
mAudioTrack = null;
return null;
}
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */ && deviceId != 0) {
mAudioTrack.setPreferredDevice(getInputAudioDeviceInfo(deviceId));
}
mAudioTrack.play();
}
results[0] = mAudioTrack.getSampleRate();
results[1] = mAudioTrack.getAudioFormat();
results[2] = mAudioTrack.getChannelCount();
}
results[3] = desiredFrames;
Log.v(TAG, "Opening " + (isCapture ? "capture" : "playback") + ", got " + results[3] + " frames of " + results[2] + " channel " + getAudioFormatString(results[1]) + " audio at " + results[0] + " Hz");
return results;
}
private static AudioDeviceInfo getInputAudioDeviceInfo(int deviceId) {
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS))
.filter(deviceInfo -> deviceInfo.getId() == deviceId)
.findFirst()
.orElse(null);
} else {
return null;
for (AudioDeviceInfo deviceInfo : audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)) {
if (deviceInfo.getId() == deviceId) {
return deviceInfo;
}
}
}
return null;
}
private static AudioDeviceInfo getOutputAudioDeviceInfo(int deviceId) {
private static AudioDeviceInfo getPlaybackAudioDeviceInfo(int deviceId) {
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS))
.filter(deviceInfo -> deviceInfo.getId() == deviceId)
.findFirst()
.orElse(null);
} else {
return null;
for (AudioDeviceInfo deviceInfo : audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)) {
if (deviceInfo.getId() == deviceId) {
return deviceInfo;
}
}
}
return null;
}
private static void registerAudioDeviceCallback() {
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);
}
}
private static void unregisterAudioDeviceCallback(Context context) {
static void unregisterAudioDeviceCallback() {
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
audioManager.unregisterAudioDeviceCallback(mAudioDeviceCallback);
}
}
/**
* This method is called by SDL using JNI.
*/
public static int[] getAudioOutputDevices() {
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)).mapToInt(AudioDeviceInfo::getId).toArray();
} else {
return NO_DEVICES;
}
}
/**
* This method is called by SDL using JNI.
*/
public static int[] getAudioInputDevices() {
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).mapToInt(AudioDeviceInfo::getId).toArray();
} else {
return NO_DEVICES;
}
}
/**
* This method is called by SDL using JNI.
*/
public static int[] audioOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames, int deviceId) {
return open(false, sampleRate, audioFormat, desiredChannels, desiredFrames, deviceId);
}
/**
* This method is called by SDL using JNI.
*/
public static void audioWriteFloatBuffer(float[] buffer) {
if (mAudioTrack == null) {
Log.e(TAG, "Attempted to make audio call with uninitialized audio!");
return;
}
if (android.os.Build.VERSION.SDK_INT < 21 /* Android 5.0 (LOLLIPOP) */) {
Log.e(TAG, "Attempted to make an incompatible audio call with uninitialized audio! (floating-point output is supported since Android 5.0 Lollipop)");
return;
}
for (int i = 0; i < buffer.length;) {
int result = mAudioTrack.write(buffer, i, buffer.length - i, AudioTrack.WRITE_BLOCKING);
if (result > 0) {
i += result;
} else if (result == 0) {
try {
Thread.sleep(1);
} catch(InterruptedException e) {
// Nom nom
}
} else {
Log.w(TAG, "SDL audio: error return from write(float)");
return;
}
}
}
/**
* This method is called by SDL using JNI.
*/
public static void audioWriteShortBuffer(short[] buffer) {
if (mAudioTrack == null) {
Log.e(TAG, "Attempted to make audio call with uninitialized audio!");
return;
}
for (int i = 0; i < buffer.length;) {
int result = mAudioTrack.write(buffer, i, buffer.length - i);
if (result > 0) {
i += result;
} else if (result == 0) {
try {
Thread.sleep(1);
} catch(InterruptedException e) {
// Nom nom
}
} else {
Log.w(TAG, "SDL audio: error return from write(short)");
return;
}
}
}
/**
* This method is called by SDL using JNI.
*/
public static void audioWriteByteBuffer(byte[] buffer) {
if (mAudioTrack == null) {
Log.e(TAG, "Attempted to make audio call with uninitialized audio!");
return;
}
for (int i = 0; i < buffer.length; ) {
int result = mAudioTrack.write(buffer, i, buffer.length - i);
if (result > 0) {
i += result;
} else if (result == 0) {
try {
Thread.sleep(1);
} catch(InterruptedException e) {
// Nom nom
}
} else {
Log.w(TAG, "SDL audio: error return from write(byte)");
return;
}
}
}
/**
* This method is called by SDL using JNI.
*/
public static int[] captureOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames, int deviceId) {
return open(true, sampleRate, audioFormat, desiredChannels, desiredFrames, deviceId);
}
/** This method is called by SDL using JNI. */
public static int captureReadFloatBuffer(float[] buffer, boolean blocking) {
if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {
return 0;
} else {
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
}
}
/** This method is called by SDL using JNI. */
public static int captureReadShortBuffer(short[] buffer, boolean blocking) {
if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {
return mAudioRecord.read(buffer, 0, buffer.length);
} else {
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
}
}
/** This method is called by SDL using JNI. */
public static int captureReadByteBuffer(byte[] buffer, boolean blocking) {
if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {
return mAudioRecord.read(buffer, 0, buffer.length);
} else {
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
}
}
/** This method is called by SDL using JNI. */
public static void audioClose() {
if (mAudioTrack != null) {
mAudioTrack.stop();
mAudioTrack.release();
mAudioTrack = null;
}
}
/** This method is called by SDL using JNI. */
public static void captureClose() {
if (mAudioRecord != null) {
mAudioRecord.stop();
mAudioRecord.release();
mAudioRecord = null;
}
}
/** This method is called by SDL using JNI. */
public static void audioSetThreadPriority(boolean iscapture, int device_id) {
static void audioSetThreadPriority(boolean recording, int device_id) {
try {
/* Set thread name */
if (iscapture) {
if (recording) {
Thread.currentThread().setName("SDLAudioC" + device_id);
} else {
Thread.currentThread().setName("SDLAudioP" + device_id);
@ -505,10 +117,10 @@ public class SDLAudioManager {
}
}
public static native int nativeSetupJNI();
static native void nativeSetupJNI();
public static native void removeAudioDevice(boolean isCapture, int deviceId);
static native void nativeRemoveAudioDevice(boolean recording, int deviceId);
public static native void addAudioDevice(boolean isCapture, int deviceId);
static native void nativeAddAudioDevice(boolean recording, String name, int deviceId);
}

View file

@ -6,9 +6,19 @@ import java.util.Comparator;
import java.util.List;
import android.content.Context;
import android.hardware.lights.Light;
import android.hardware.lights.LightsRequest;
import android.hardware.lights.LightsManager;
import android.hardware.lights.LightState;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.graphics.Color;
import android.os.Build;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.os.VibratorManager;
import android.util.Log;
import android.view.InputDevice;
import android.view.KeyEvent;
@ -19,38 +29,38 @@ import android.view.View;
public class SDLControllerManager
{
public static native int nativeSetupJNI();
static native void nativeSetupJNI();
public static native int nativeAddJoystick(int device_id, String name, String desc,
int vendor_id, int product_id,
boolean is_accelerometer, int button_mask,
int naxes, int axis_mask, int nhats, int nballs);
public static native int nativeRemoveJoystick(int device_id);
public static native int nativeAddHaptic(int device_id, String name);
public static native int nativeRemoveHaptic(int device_id);
public static native int onNativePadDown(int device_id, int keycode);
public static native int onNativePadUp(int device_id, int keycode);
public static native void onNativeJoy(int device_id, int axis,
static native void nativeAddJoystick(int device_id, String name, String desc,
int vendor_id, int product_id,
int button_mask,
int naxes, int axis_mask, int nhats, boolean can_rumble, boolean has_rgb_led,
boolean has_accelerometer, boolean has_gyroscope);
static native void nativeRemoveJoystick(int device_id);
static native void nativeAddHaptic(int device_id, String name);
static native void nativeRemoveHaptic(int device_id);
static public native boolean onNativePadDown(int device_id, int keycode, int scancode);
static public native boolean onNativePadUp(int device_id, int keycode, int scancode);
static native void onNativeJoy(int device_id, int axis,
float value);
public static native void onNativeHat(int device_id, int hat_id,
static native void onNativeHat(int device_id, int hat_id,
int x, int y);
static native void onNativeJoySensor(int device_id, int sensor_type, long sensor_timestamp, float x, float y, float z);
protected static SDLJoystickHandler mJoystickHandler;
protected static SDLHapticHandler mHapticHandler;
private static final String TAG = "SDLControllerManager";
public static void initialize() {
static void initialize() {
if (mJoystickHandler == null) {
if (Build.VERSION.SDK_INT >= 19 /* Android 4.4 (KITKAT) */) {
mJoystickHandler = new SDLJoystickHandler_API19();
} else {
mJoystickHandler = new SDLJoystickHandler_API16();
}
mJoystickHandler = new SDLJoystickHandler();
}
if (mHapticHandler == null) {
if (Build.VERSION.SDK_INT >= 26 /* Android 8.0 (O) */) {
if (Build.VERSION.SDK_INT >= 31 /* Android 12.0 (S) */) {
mHapticHandler = new SDLHapticHandler_API31();
} else if (Build.VERSION.SDK_INT >= 26 /* Android 8.0 (O) */) {
mHapticHandler = new SDLHapticHandler_API26();
} else {
mHapticHandler = new SDLHapticHandler();
@ -59,41 +69,62 @@ public class SDLControllerManager
}
// Joystick glue code, just a series of stubs that redirect to the SDLJoystickHandler instance
public static boolean handleJoystickMotionEvent(MotionEvent event) {
static public boolean handleJoystickMotionEvent(MotionEvent event) {
return mJoystickHandler.handleMotionEvent(event);
}
/**
* This method is called by SDL using JNI.
*/
public static void pollInputDevices() {
static void pollInputDevices() {
mJoystickHandler.pollInputDevices();
}
/**
* This method is called by SDL using JNI.
*/
public static void pollHapticDevices() {
static void joystickSetLED(int device_id, int red, int green, int blue) {
mJoystickHandler.setLED(device_id, red, green, blue);
}
/**
* This method is called by SDL using JNI.
*/
static void joystickSetSensorsEnabled(int device_id, boolean enabled) {
mJoystickHandler.setSensorsEnabled(device_id, enabled);
}
/**
* This method is called by SDL using JNI.
*/
static void pollHapticDevices() {
mHapticHandler.pollHapticDevices();
}
/**
* This method is called by SDL using JNI.
*/
public static void hapticRun(int device_id, float intensity, int length) {
static void hapticRun(int device_id, float intensity, int length) {
mHapticHandler.run(device_id, intensity, length);
}
/**
* This method is called by SDL using JNI.
*/
public static void hapticStop(int device_id)
static void hapticRumble(int device_id, float low_frequency_intensity, float high_frequency_intensity, int length) {
mHapticHandler.rumble(device_id, low_frequency_intensity, high_frequency_intensity, length);
}
/**
* This method is called by SDL using JNI.
*/
static void hapticStop(int device_id)
{
mHapticHandler.stop(device_id);
}
// Check if a given device is considered a possible SDL joystick
public static boolean isDeviceSDLJoystick(int deviceId) {
static public boolean isDeviceSDLJoystick(int deviceId) {
InputDevice device = InputDevice.getDevice(deviceId);
// We cannot use InputDevice.isVirtual before API 16, so let's accept
// only nonnegative device ids (VIRTUAL_KEYBOARD equals -1)
@ -123,33 +154,22 @@ public class SDLControllerManager
}
/* Actual joystick functionality available for API >= 19 devices */
class SDLJoystickHandler {
/**
* Handles given MotionEvent.
* @param event the event to be handled.
* @return if given event was processed.
*/
public boolean handleMotionEvent(MotionEvent event) {
return false;
}
/**
* Handles adding and removing of input devices.
*/
public void pollInputDevices() {
}
}
/* Actual joystick functionality available for API >= 12 devices */
class SDLJoystickHandler_API16 extends SDLJoystickHandler {
static class SDLJoystick {
public int device_id;
public String name;
public String desc;
public ArrayList<InputDevice.MotionRange> axes;
public ArrayList<InputDevice.MotionRange> hats;
int device_id;
String name;
String desc;
ArrayList<InputDevice.MotionRange> axes;
ArrayList<InputDevice.MotionRange> hats;
ArrayList<Light> lights;
LightsManager.LightsSession lightsSession;
SensorManager sensorManager;
SDLJoySensorListener sensorListener;
Sensor accelerometerSensor;
Sensor gyroscopeSensor;
}
static class RangeComparator implements Comparator<InputDevice.MotionRange> {
@Override
@ -200,13 +220,15 @@ class SDLJoystickHandler_API16 extends SDLJoystickHandler {
private final ArrayList<SDLJoystick> mJoysticks;
public SDLJoystickHandler_API16() {
SDLJoystickHandler() {
mJoysticks = new ArrayList<SDLJoystick>();
}
@Override
public void pollInputDevices() {
/**
* Handles adding and removing of input devices.
*/
synchronized void pollInputDevices() {
int[] deviceIds = InputDevice.getDeviceIds();
for (int device_id : deviceIds) {
@ -220,11 +242,13 @@ class SDLJoystickHandler_API16 extends SDLJoystickHandler {
joystick.desc = getJoystickDescriptor(joystickDevice);
joystick.axes = new ArrayList<InputDevice.MotionRange>();
joystick.hats = new ArrayList<InputDevice.MotionRange>();
java.util.Set<Integer> axisStrsSet = new java.util.HashSet<Integer>();
joystick.lights = new ArrayList<Light>();
List<InputDevice.MotionRange> ranges = joystickDevice.getMotionRanges();
Collections.sort(ranges, new RangeComparator());
for (InputDevice.MotionRange range : ranges) {
if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
if (((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) && axisStrsSet.add(range.getAxis())) {
if (range.getAxis() == MotionEvent.AXIS_HAT_X || range.getAxis() == MotionEvent.AXIS_HAT_Y) {
joystick.hats.add(range);
} else {
@ -233,10 +257,47 @@ class SDLJoystickHandler_API16 extends SDLJoystickHandler {
}
}
boolean can_rumble = false;
boolean has_rgb_led = false;
boolean has_accelerometer = false;
boolean has_gyroscope = false;
if (Build.VERSION.SDK_INT >= 31 /* Android 12.0 (S) */) {
VibratorManager vibratorManager = joystickDevice.getVibratorManager();
int[] vibrators = vibratorManager.getVibratorIds();
if (vibrators.length > 0) {
can_rumble = true;
}
LightsManager lightsManager = joystickDevice.getLightsManager();
List<Light> lights = lightsManager.getLights();
for (Light light : lights) {
if (light.hasRgbControl()) {
joystick.lights.add(light);
}
}
if (!joystick.lights.isEmpty()) {
joystick.lightsSession = lightsManager.openSession();
has_rgb_led = true;
}
SensorManager sensorManager = joystickDevice.getSensorManager();
if (sensorManager != null) {
joystick.sensorManager = sensorManager;
joystick.sensorListener = new SDLJoySensorListener(joystick.device_id);
joystick.accelerometerSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
if (joystick.accelerometerSensor != null) {
has_accelerometer = true;
}
joystick.gyroscopeSensor = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
if (joystick.gyroscopeSensor != null) {
has_gyroscope = true;
}
}
}
mJoysticks.add(joystick);
SDLControllerManager.nativeAddJoystick(joystick.device_id, joystick.name, joystick.desc,
getVendorId(joystickDevice), getProductId(joystickDevice), false,
getButtonMask(joystickDevice), joystick.axes.size(), getAxisMask(joystick.axes), joystick.hats.size()/2, 0);
getVendorId(joystickDevice), getProductId(joystickDevice),
getButtonMask(joystickDevice), joystick.axes.size(), getAxisMask(joystick.axes), joystick.hats.size()/2, can_rumble, has_rgb_led,
has_accelerometer, has_gyroscope);
}
}
}
@ -262,6 +323,16 @@ class SDLJoystickHandler_API16 extends SDLJoystickHandler {
SDLControllerManager.nativeRemoveJoystick(device_id);
for (int i = 0; i < mJoysticks.size(); i++) {
if (mJoysticks.get(i).device_id == device_id) {
if (Build.VERSION.SDK_INT >= 31 /* Android 12.0 (S) */) {
if (mJoysticks.get(i).lightsSession != null) {
try {
mJoysticks.get(i).lightsSession.close();
} catch (Exception e) {
// Session may already be unregistered when device disconnects
}
mJoysticks.get(i).lightsSession = null;
}
}
mJoysticks.remove(i);
break;
}
@ -270,7 +341,7 @@ class SDLJoystickHandler_API16 extends SDLJoystickHandler {
}
}
protected SDLJoystick getJoystick(int device_id) {
synchronized protected SDLJoystick getJoystick(int device_id) {
for (SDLJoystick joystick : mJoysticks) {
if (joystick.device_id == device_id) {
return joystick;
@ -279,8 +350,12 @@ class SDLJoystickHandler_API16 extends SDLJoystickHandler {
return null;
}
@Override
public boolean handleMotionEvent(MotionEvent event) {
/**
* Handles given MotionEvent.
* @param event the event to be handled.
* @return if given event was processed.
*/
boolean handleMotionEvent(MotionEvent event) {
int actionPointerIndex = event.getActionIndex();
int action = event.getActionMasked();
if (action == MotionEvent.ACTION_MOVE) {
@ -302,7 +377,7 @@ class SDLJoystickHandler_API16 extends SDLJoystickHandler {
return true;
}
public String getJoystickDescriptor(InputDevice joystickDevice) {
String getJoystickDescriptor(InputDevice joystickDevice) {
String desc = joystickDevice.getDescriptor();
if (desc != null && !desc.isEmpty()) {
@ -311,34 +386,16 @@ class SDLJoystickHandler_API16 extends SDLJoystickHandler {
return joystickDevice.getName();
}
public int getProductId(InputDevice joystickDevice) {
return 0;
}
public int getVendorId(InputDevice joystickDevice) {
return 0;
}
public int getAxisMask(List<InputDevice.MotionRange> ranges) {
return -1;
}
public int getButtonMask(InputDevice joystickDevice) {
return -1;
}
}
class SDLJoystickHandler_API19 extends SDLJoystickHandler_API16 {
@Override
public int getProductId(InputDevice joystickDevice) {
int getProductId(InputDevice joystickDevice) {
return joystickDevice.getProductId();
}
@Override
public int getVendorId(InputDevice joystickDevice) {
int getVendorId(InputDevice joystickDevice) {
return joystickDevice.getVendorId();
}
@Override
public int getAxisMask(List<InputDevice.MotionRange> ranges) {
int getAxisMask(List<InputDevice.MotionRange> ranges) {
// For compatibility, keep computing the axis mask like before,
// only really distinguishing 2, 4 and 6 axes.
int axis_mask = 0;
@ -374,8 +431,7 @@ class SDLJoystickHandler_API19 extends SDLJoystickHandler_API16 {
return axis_mask;
}
@Override
public int getButtonMask(InputDevice joystickDevice) {
int getButtonMask(InputDevice joystickDevice) {
int button_mask = 0;
int[] keys = new int[] {
KeyEvent.KEYCODE_BUTTON_A,
@ -468,14 +524,125 @@ class SDLJoystickHandler_API19 extends SDLJoystickHandler_API16 {
}
return button_mask;
}
void setLED(int device_id, int red, int green, int blue) {
if (Build.VERSION.SDK_INT < 31 /* Android 12.0 (S) */) {
return;
}
SDLJoystick joystick = getJoystick(device_id);
if (joystick == null || joystick.lights.isEmpty()) {
return;
}
LightsRequest.Builder lightsRequest = new LightsRequest.Builder();
LightState lightState = new LightState.Builder().setColor(Color.rgb(red, green, blue)).build();
for (Light light : joystick.lights) {
if (light.hasRgbControl()) {
lightsRequest.addLight(light, lightState);
}
}
joystick.lightsSession.requestLights(lightsRequest.build());
}
void setSensorsEnabled(int device_id, boolean enabled) {
if (Build.VERSION.SDK_INT < 31 /* Android 12.0 (S) */) {
return;
}
SDLJoystick joystick = getJoystick(device_id);
if (joystick == null || joystick.sensorManager == null) {
return;
}
if (enabled) {
if (joystick.accelerometerSensor != null) {
SDLSensorManager.registerListener(joystick.sensorManager, joystick.sensorListener, joystick.accelerometerSensor, SensorManager.SENSOR_DELAY_GAME);
}
if (joystick.gyroscopeSensor != null) {
SDLSensorManager.registerListener(joystick.sensorManager, joystick.sensorListener, joystick.gyroscopeSensor, SensorManager.SENSOR_DELAY_GAME);
}
} else {
if (joystick.accelerometerSensor != null) {
SDLSensorManager.unregisterListener(joystick.sensorManager, joystick.sensorListener, joystick.accelerometerSensor);
}
if (joystick.gyroscopeSensor != null) {
SDLSensorManager.unregisterListener(joystick.sensorManager, joystick.sensorListener, joystick.gyroscopeSensor);
}
}
}
}
class SDLHapticHandler_API31 extends SDLHapticHandler {
@Override
void run(int device_id, float intensity, int length) {
SDLHaptic haptic = getHaptic(device_id);
if (haptic != null) {
vibrate(haptic.vib, intensity, length);
}
}
@Override
void rumble(int device_id, float low_frequency_intensity, float high_frequency_intensity, int length) {
InputDevice device = InputDevice.getDevice(device_id);
if (device == null) {
return;
}
if (Build.VERSION.SDK_INT < 31 /* Android 12.0 (S) */) {
/* Silence 'lint' warning */
return;
}
VibratorManager manager = device.getVibratorManager();
int[] vibrators = manager.getVibratorIds();
if (vibrators.length >= 2) {
vibrate(manager.getVibrator(vibrators[0]), low_frequency_intensity, length);
vibrate(manager.getVibrator(vibrators[1]), high_frequency_intensity, length);
} else if (vibrators.length == 1) {
float intensity = (low_frequency_intensity * 0.6f) + (high_frequency_intensity * 0.4f);
vibrate(manager.getVibrator(vibrators[0]), intensity, length);
}
}
private void vibrate(Vibrator vibrator, float intensity, int length) {
if (Build.VERSION.SDK_INT < 31 /* Android 12.0 (S) */) {
/* Silence 'lint' warning */
return;
}
if (intensity == 0.0f) {
vibrator.cancel();
return;
}
int value = Math.round(intensity * 255);
if (value > 255) {
value = 255;
}
if (value < 1) {
vibrator.cancel();
return;
}
try {
vibrator.vibrate(VibrationEffect.createOneShot(length, value));
}
catch (Exception e) {
// Fall back to the generic method, which uses DEFAULT_AMPLITUDE, but works even if
// something went horribly wrong with the Android 8.0 APIs.
vibrator.vibrate(length);
}
}
}
class SDLHapticHandler_API26 extends SDLHapticHandler {
@Override
public void run(int device_id, float intensity, int length) {
void run(int device_id, float intensity, int length) {
if (Build.VERSION.SDK_INT < 26 /* Android 8.0 (O) */) {
/* Silence 'lint' warning */
return;
}
SDLHaptic haptic = getHaptic(device_id);
if (haptic != null) {
Log.d("SDL", "Rtest: Vibe with intensity " + intensity + " for " + length);
if (intensity == 0.0f) {
stop(device_id);
return;
@ -505,60 +672,40 @@ class SDLHapticHandler_API26 extends SDLHapticHandler {
class SDLHapticHandler {
static class SDLHaptic {
public int device_id;
public String name;
public Vibrator vib;
int device_id;
String name;
Vibrator vib;
}
private final ArrayList<SDLHaptic> mHaptics;
public SDLHapticHandler() {
SDLHapticHandler() {
mHaptics = new ArrayList<SDLHaptic>();
}
public void run(int device_id, float intensity, int length) {
void run(int device_id, float intensity, int length) {
SDLHaptic haptic = getHaptic(device_id);
if (haptic != null) {
haptic.vib.vibrate(length);
}
}
public void stop(int device_id) {
void rumble(int device_id, float low_frequency_intensity, float high_frequency_intensity, int length) {
// Not supported in older APIs
}
void stop(int device_id) {
SDLHaptic haptic = getHaptic(device_id);
if (haptic != null) {
haptic.vib.cancel();
}
}
public void pollHapticDevices() {
synchronized void pollHapticDevices() {
final int deviceId_VIBRATOR_SERVICE = 999999;
boolean hasVibratorService = false;
int[] deviceIds = InputDevice.getDeviceIds();
// It helps processing the device ids in reverse order
// For example, in the case of the XBox 360 wireless dongle,
// so the first controller seen by SDL matches what the receiver
// considers to be the first controller
for (int i = deviceIds.length - 1; i > -1; i--) {
SDLHaptic haptic = getHaptic(deviceIds[i]);
if (haptic == null) {
InputDevice device = InputDevice.getDevice(deviceIds[i]);
Vibrator vib = device.getVibrator();
if (vib != null) {
if (vib.hasVibrator()) {
haptic = new SDLHaptic();
haptic.device_id = deviceIds[i];
haptic.name = device.getName();
haptic.vib = vib;
mHaptics.add(haptic);
SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name);
}
}
}
}
/* Check VIBRATOR_SERVICE */
Vibrator vib = (Vibrator) SDL.getContext().getSystemService(Context.VIBRATOR_SERVICE);
if (vib != null) {
@ -581,18 +728,11 @@ class SDLHapticHandler {
ArrayList<Integer> removedDevices = null;
for (SDLHaptic haptic : mHaptics) {
int device_id = haptic.device_id;
int i;
for (i = 0; i < deviceIds.length; i++) {
if (device_id == deviceIds[i]) break;
}
if (device_id != deviceId_VIBRATOR_SERVICE || !hasVibratorService) {
if (i == deviceIds.length) {
if (removedDevices == null) {
removedDevices = new ArrayList<Integer>();
}
removedDevices.add(device_id);
if (removedDevices == null) {
removedDevices = new ArrayList<Integer>();
}
removedDevices.add(device_id);
} // else: don't remove the vibrator if it is still present
}
@ -609,7 +749,7 @@ class SDLHapticHandler {
}
}
protected SDLHaptic getHaptic(int device_id) {
synchronized protected SDLHaptic getHaptic(int device_id) {
for (SDLHaptic haptic : mHaptics) {
if (haptic.device_id == device_id) {
return haptic;
@ -619,129 +759,154 @@ class SDLHapticHandler {
}
}
class SDLGenericMotionListener_API12 implements View.OnGenericMotionListener {
class SDLGenericMotionListener_API14 implements View.OnGenericMotionListener {
protected static final int SDL_PEN_DEVICE_TYPE_UNKNOWN = 0;
protected static final int SDL_PEN_DEVICE_TYPE_DIRECT = 1;
protected static final int SDL_PEN_DEVICE_TYPE_INDIRECT = 2;
// Generic Motion (mouse hover, joystick...) events go here
@Override
public boolean onGenericMotion(View v, MotionEvent event) {
if (event.getSource() == InputDevice.SOURCE_JOYSTICK)
return SDLControllerManager.handleJoystickMotionEvent(event);
float x, y;
int action;
int action = event.getActionMasked();
int pointerCount = event.getPointerCount();
boolean consumed = false;
switch ( event.getSource() ) {
case InputDevice.SOURCE_JOYSTICK:
return SDLControllerManager.handleJoystickMotionEvent(event);
for (int i = 0; i < pointerCount; i++) {
int toolType = event.getToolType(i);
case InputDevice.SOURCE_MOUSE:
action = event.getActionMasked();
if (toolType == MotionEvent.TOOL_TYPE_MOUSE) {
switch (action) {
case MotionEvent.ACTION_SCROLL:
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, i);
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, i);
SDLActivity.onNativeMouse(0, action, x, y, false);
return true;
consumed = true;
break;
case MotionEvent.ACTION_HOVER_MOVE:
x = event.getX(0);
y = event.getY(0);
x = getEventX(event, i);
y = getEventY(event, i);
SDLActivity.onNativeMouse(0, action, x, y, false);
return true;
SDLActivity.onNativeMouse(0, action, x, y, checkRelativeEvent(event));
consumed = true;
break;
default:
break;
}
break;
} else if (toolType == MotionEvent.TOOL_TYPE_STYLUS || toolType == MotionEvent.TOOL_TYPE_ERASER) {
switch (action) {
case MotionEvent.ACTION_HOVER_ENTER:
case MotionEvent.ACTION_HOVER_MOVE:
case MotionEvent.ACTION_HOVER_EXIT:
x = event.getX(i);
y = event.getY(i);
float 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;
}
default:
break;
// BUTTON_STYLUS_PRIMARY is 2^5, so shift by 4, and apply SDL_PEN_INPUT_DOWN/SDL_PEN_INPUT_ERASER_TIP
int buttons = (event.getButtonState() >> 4) | (1 << (toolType == MotionEvent.TOOL_TYPE_STYLUS ? 0 : 30));
if ((event.getButtonState() & MotionEvent.BUTTON_TERTIARY) != 0) {
buttons |= 0x08;
}
SDLActivity.onNativePen(event.getPointerId(i), getPenDeviceType(event.getDevice()), buttons, action, x, y, p);
consumed = true;
break;
}
}
}
// Event was not managed
return consumed;
}
boolean supportsRelativeMouse() {
return false;
}
public boolean supportsRelativeMouse() {
boolean inRelativeMode() {
return false;
}
public boolean inRelativeMode() {
boolean setRelativeMouseEnabled(boolean enabled) {
return false;
}
public boolean setRelativeMouseEnabled(boolean enabled) {
return false;
}
public void reclaimRelativeMouseModeIfNeeded()
{
void reclaimRelativeMouseModeIfNeeded() {
}
public float getEventX(MotionEvent event) {
return event.getX(0);
boolean checkRelativeEvent(MotionEvent event) {
return inRelativeMode();
}
public float getEventY(MotionEvent event) {
return event.getY(0);
float getEventX(MotionEvent event, int pointerIndex) {
return event.getX(pointerIndex);
}
float getEventY(MotionEvent event, int pointerIndex) {
return event.getY(pointerIndex);
}
int getPenDeviceType(InputDevice penDevice) {
return SDL_PEN_DEVICE_TYPE_UNKNOWN;
}
}
class SDLGenericMotionListener_API24 extends SDLGenericMotionListener_API12 {
class SDLGenericMotionListener_API24 extends SDLGenericMotionListener_API14 {
// Generic Motion (mouse hover, joystick...) events go here
private boolean mRelativeModeEnabled;
@Override
public boolean onGenericMotion(View v, MotionEvent event) {
// Handle relative mouse mode
if (mRelativeModeEnabled) {
if (event.getSource() == InputDevice.SOURCE_MOUSE) {
int action = event.getActionMasked();
if (action == MotionEvent.ACTION_HOVER_MOVE) {
float x = event.getAxisValue(MotionEvent.AXIS_RELATIVE_X);
float y = event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y);
SDLActivity.onNativeMouse(0, action, x, y, true);
return true;
}
}
}
// Event was not managed, call SDLGenericMotionListener_API12 method
return super.onGenericMotion(v, event);
}
@Override
public boolean supportsRelativeMouse() {
boolean supportsRelativeMouse() {
return true;
}
@Override
public boolean inRelativeMode() {
boolean inRelativeMode() {
return mRelativeModeEnabled;
}
@Override
public boolean setRelativeMouseEnabled(boolean enabled) {
boolean setRelativeMouseEnabled(boolean enabled) {
mRelativeModeEnabled = enabled;
return true;
}
@Override
public float getEventX(MotionEvent event) {
if (mRelativeModeEnabled) {
return event.getAxisValue(MotionEvent.AXIS_RELATIVE_X);
float getEventX(MotionEvent event, int pointerIndex) {
if (Build.VERSION.SDK_INT < 24 /* Android 7.0 (N) */) {
/* Silence 'lint' warning */
return 0;
}
if (mRelativeModeEnabled && event.getToolType(pointerIndex) == MotionEvent.TOOL_TYPE_MOUSE) {
return event.getAxisValue(MotionEvent.AXIS_RELATIVE_X, pointerIndex);
} else {
return event.getX(0);
return event.getX(pointerIndex);
}
}
@Override
public float getEventY(MotionEvent event) {
if (mRelativeModeEnabled) {
return event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y);
float getEventY(MotionEvent event, int pointerIndex) {
if (Build.VERSION.SDK_INT < 24 /* Android 7.0 (N) */) {
/* Silence 'lint' warning */
return 0;
}
if (mRelativeModeEnabled && event.getToolType(pointerIndex) == MotionEvent.TOOL_TYPE_MOUSE) {
return event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y, pointerIndex);
} else {
return event.getY(0);
return event.getY(pointerIndex);
}
}
}
@ -751,76 +916,23 @@ class SDLGenericMotionListener_API26 extends SDLGenericMotionListener_API24 {
private boolean mRelativeModeEnabled;
@Override
public boolean onGenericMotion(View v, MotionEvent event) {
float x, y;
int action;
switch ( event.getSource() ) {
case InputDevice.SOURCE_JOYSTICK:
return SDLControllerManager.handleJoystickMotionEvent(event);
case InputDevice.SOURCE_MOUSE:
// DeX desktop mouse cursor is a separate non-standard input type.
case InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_TOUCHSCREEN:
action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_SCROLL:
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
SDLActivity.onNativeMouse(0, action, x, y, false);
return true;
case MotionEvent.ACTION_HOVER_MOVE:
x = event.getX(0);
y = event.getY(0);
SDLActivity.onNativeMouse(0, action, x, y, false);
return true;
default:
break;
}
break;
case InputDevice.SOURCE_MOUSE_RELATIVE:
action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_SCROLL:
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
SDLActivity.onNativeMouse(0, action, x, y, false);
return true;
case MotionEvent.ACTION_HOVER_MOVE:
x = event.getX(0);
y = event.getY(0);
SDLActivity.onNativeMouse(0, action, x, y, true);
return true;
default:
break;
}
break;
default:
break;
}
// Event was not managed
return false;
}
@Override
public boolean supportsRelativeMouse() {
boolean supportsRelativeMouse() {
return (!SDLActivity.isDeXMode() || Build.VERSION.SDK_INT >= 27 /* Android 8.1 (O_MR1) */);
}
@Override
public boolean inRelativeMode() {
boolean inRelativeMode() {
return mRelativeModeEnabled;
}
@Override
public boolean setRelativeMouseEnabled(boolean enabled) {
boolean setRelativeMouseEnabled(boolean enabled) {
if (Build.VERSION.SDK_INT < 26 /* Android 8.0 (O) */) {
/* Silence 'lint' warning */
return false;
}
if (!SDLActivity.isDeXMode() || Build.VERSION.SDK_INT >= 27 /* Android 8.1 (O_MR1) */) {
if (enabled) {
SDLActivity.getContentView().requestPointerCapture();
@ -835,22 +947,64 @@ class SDLGenericMotionListener_API26 extends SDLGenericMotionListener_API24 {
}
@Override
public void reclaimRelativeMouseModeIfNeeded()
{
void reclaimRelativeMouseModeIfNeeded() {
if (Build.VERSION.SDK_INT < 26 /* Android 8.0 (O) */) {
/* Silence 'lint' warning */
return;
}
if (mRelativeModeEnabled && !SDLActivity.isDeXMode()) {
SDLActivity.getContentView().requestPointerCapture();
}
}
@Override
public float getEventX(MotionEvent event) {
// Relative mouse in capture mode will only have relative for X/Y
return event.getX(0);
boolean checkRelativeEvent(MotionEvent event) {
if (Build.VERSION.SDK_INT < 26 /* Android 8.0 (O) */) {
/* Silence 'lint' warning */
return false;
}
return event.getSource() == InputDevice.SOURCE_MOUSE_RELATIVE;
}
@Override
public float getEventY(MotionEvent event) {
float getEventX(MotionEvent event, int pointerIndex) {
// Relative mouse in capture mode will only have relative for X/Y
return event.getY(0);
return event.getX(pointerIndex);
}
@Override
float getEventY(MotionEvent event, int pointerIndex) {
// Relative mouse in capture mode will only have relative for X/Y
return event.getY(pointerIndex);
}
}
class SDLGenericMotionListener_API29 extends SDLGenericMotionListener_API26 {
@Override
int getPenDeviceType(InputDevice penDevice)
{
if (penDevice == null) {
return SDL_PEN_DEVICE_TYPE_UNKNOWN;
}
return penDevice.isExternal() ? SDL_PEN_DEVICE_TYPE_INDIRECT : SDL_PEN_DEVICE_TYPE_DIRECT;
}
}
class SDLJoySensorListener implements SensorEventListener {
int device_id;
public SDLJoySensorListener(int device_id) {
this.device_id = device_id;
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {}
@Override
public void onSensorChanged(SensorEvent event) {
SDLControllerManager.onNativeJoySensor(device_id, event.sensor.getType(), event.timestamp, event.values[0], event.values[1], event.values[2]);
}
}

View file

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

View file

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

View file

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

View file

@ -3,6 +3,7 @@ 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;
@ -14,12 +15,15 @@ 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
@ -28,7 +32,8 @@ import android.view.WindowManager;
Because of this, that's where we set up the SDL thread
*/
public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
View.OnKeyListener, View.OnTouchListener, SensorEventListener {
View.OnApplyWindowInsetsListener, View.OnKeyListener, View.OnTouchListener,
SensorEventListener, ScaleGestureDetector.OnScaleGestureListener {
// Sensors
protected SensorManager mSensorManager;
@ -38,16 +43,25 @@ public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
protected float mWidth, mHeight;
// Is SurfaceView ready for rendering
public boolean mIsSurfaceReady;
protected boolean mIsSurfaceReady;
// Is on-screen keyboard visible
protected boolean mKeyboardVisible;
// Pinch events
private final ScaleGestureDetector scaleGestureDetector;
// Startup
public SDLSurface(Context context) {
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);
@ -63,20 +77,21 @@ public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
mIsSurfaceReady = false;
}
public void handlePause() {
protected void handlePause() {
enableSensor(Sensor.TYPE_ACCELEROMETER, false);
}
public void handleResume() {
protected void handleResume() {
setFocusable(true);
setFocusableInTouchMode(true);
requestFocus();
setOnApplyWindowInsetsListener(this);
setOnKeyListener(this);
setOnTouchListener(this);
enableSensor(Sensor.TYPE_ACCELEROMETER, true);
}
public Surface getNativeSurface() {
protected Surface getNativeSurface() {
return getHolder().getSurface();
}
@ -114,14 +129,15 @@ public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
mHeight = height;
int nDeviceWidth = width;
int nDeviceHeight = height;
float density = 1.0f;
try
{
if (Build.VERSION.SDK_INT >= 17 /* Android 4.2 (JELLY_BEAN_MR1) */) {
DisplayMetrics realMetrics = new DisplayMetrics();
mDisplay.getRealMetrics( realMetrics );
nDeviceWidth = realMetrics.widthPixels;
nDeviceHeight = realMetrics.heightPixels;
}
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) {
}
@ -132,7 +148,7 @@ public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
Log.v("SDL", "Window size: " + width + "x" + height);
Log.v("SDL", "Device size: " + nDeviceWidth + "x" + nDeviceHeight);
SDLActivity.nativeSetScreenResolution(width, height, nDeviceWidth, nDeviceHeight, mDisplay.getRefreshRate());
SDLActivity.nativeSetScreenResolution(width, height, nDeviceWidth, nDeviceHeight, density, mDisplay.getRefreshRate());
SDLActivity.onNativeResize();
// Prevent a screen distortion glitch,
@ -161,13 +177,10 @@ public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
}
}
// Don't skip in MultiWindow.
// 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) */) {
if (SDLActivity.mSingleton.isInMultiWindowMode()) {
Log.v("SDL", "Don't skip in Multi-Window");
skip = false;
}
skip = false;
}
}
@ -187,12 +200,59 @@ public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
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) {
@ -200,113 +260,79 @@ public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
int touchDevId = event.getDeviceId();
final int pointerCount = event.getPointerCount();
int action = event.getActionMasked();
int pointerFingerId;
int i = -1;
int pointerId;
int i = 0;
float x,y,p;
/*
* Prevent id to be -1, since it's used in SDL internal for synthetic events
* Appears when using Android emulator, eg:
* adb shell input mouse tap 100 100
* adb shell input touchscreen tap 100 100
*/
if (touchDevId < 0) {
touchDevId -= 1;
}
if (action == MotionEvent.ACTION_POINTER_UP || action == MotionEvent.ACTION_POINTER_DOWN)
i = event.getActionIndex();
// 12290 = Samsung DeX mode desktop mouse
// 12290 = 0x3002 = 0x2002 | 0x1002 = SOURCE_MOUSE | SOURCE_TOUCHSCREEN
// 0x2 = SOURCE_CLASS_POINTER
if (event.getSource() == InputDevice.SOURCE_MOUSE || event.getSource() == (InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_TOUCHSCREEN)) {
int mouseButton = 1;
try {
Object object = event.getClass().getMethod("getButtonState").invoke(event);
if (object != null) {
mouseButton = (Integer) object;
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;
}
} catch(Exception ignored) {
// 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);
}
// We need to check if we're in relative mouse mode and get the axis offset rather than the x/y values
// if we are. We'll leverage our existing mouse motion listener
SDLGenericMotionListener_API12 motionListener = SDLActivity.getMotionListener();
x = motionListener.getEventX(event);
y = motionListener.getEventY(event);
// Non-primary up/down
if (action == MotionEvent.ACTION_POINTER_UP || action == MotionEvent.ACTION_POINTER_DOWN)
break;
} while (++i < pointerCount);
SDLActivity.onNativeMouse(mouseButton, action, x, y, motionListener.inRelativeMode());
} else {
switch(action) {
case MotionEvent.ACTION_MOVE:
for (i = 0; i < pointerCount; i++) {
pointerFingerId = event.getPointerId(i);
x = event.getX(i) / mWidth;
y = event.getY(i) / mHeight;
p = event.getPressure(i);
if (p > 1.0f) {
// may be larger than 1.0f on some devices
// see the documentation of getPressure(i)
p = 1.0f;
}
SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_DOWN:
// Primary pointer up/down, the index is always zero
i = 0;
/* fallthrough */
case MotionEvent.ACTION_POINTER_UP:
case MotionEvent.ACTION_POINTER_DOWN:
// Non primary pointer up/down
if (i == -1) {
i = event.getActionIndex();
}
pointerFingerId = event.getPointerId(i);
x = event.getX(i) / mWidth;
y = event.getY(i) / mHeight;
p = event.getPressure(i);
if (p > 1.0f) {
// may be larger than 1.0f on some devices
// see the documentation of getPressure(i)
p = 1.0f;
}
SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
break;
case MotionEvent.ACTION_CANCEL:
for (i = 0; i < pointerCount; i++) {
pointerFingerId = event.getPointerId(i);
x = event.getX(i) / mWidth;
y = event.getY(i) / mHeight;
p = event.getPressure(i);
if (p > 1.0f) {
// may be larger than 1.0f on some devices
// see the documentation of getPressure(i)
p = 1.0f;
}
SDLActivity.onNativeTouch(touchDevId, pointerFingerId, MotionEvent.ACTION_UP, x, y, p);
}
break;
default:
break;
}
}
scaleGestureDetector.onTouchEvent(event);
return true;
}
}
// Sensor events
public void enableSensor(int sensortype, boolean enabled) {
protected void enableSensor(int sensortype, boolean enabled) {
// TODO: This uses getDefaultSensor - what if we have >1 accels?
if (enabled) {
mSensorManager.registerListener(this,
SDLSensorManager.registerListener(mSensorManager, this,
mSensorManager.getDefaultSensor(sensortype),
SensorManager.SENSOR_DELAY_GAME, null);
SensorManager.SENSOR_DELAY_GAME);
} else {
mSensorManager.unregisterListener(this,
SDLSensorManager.unregisterListener(mSensorManager, this,
mSensorManager.getDefaultSensor(sensortype));
}
}
@ -322,36 +348,36 @@ public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
// Since we may have an orientation set, we won't receive onConfigurationChanged events.
// We thus should check here.
int newOrientation;
int newRotation;
float x, y;
switch (mDisplay.getRotation()) {
case Surface.ROTATION_90:
x = -event.values[1];
y = event.values[0];
newOrientation = SDLActivity.SDL_ORIENTATION_LANDSCAPE;
break;
case Surface.ROTATION_270:
x = event.values[1];
y = -event.values[0];
newOrientation = SDLActivity.SDL_ORIENTATION_LANDSCAPE_FLIPPED;
break;
case Surface.ROTATION_180:
x = -event.values[0];
y = -event.values[1];
newOrientation = SDLActivity.SDL_ORIENTATION_PORTRAIT_FLIPPED;
break;
case Surface.ROTATION_0:
default:
x = event.values[0];
y = event.values[1];
newOrientation = SDLActivity.SDL_ORIENTATION_PORTRAIT;
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 (newOrientation != SDLActivity.mCurrentOrientation) {
SDLActivity.mCurrentOrientation = newOrientation;
SDLActivity.onNativeOrientationChanged(newOrientation);
if (newRotation != SDLActivity.mCurrentRotation) {
SDLActivity.mCurrentRotation = newRotation;
SDLActivity.onNativeRotationChanged(newRotation);
}
SDLActivity.onNativeAccel(-x / SensorManager.GRAVITY_EARTH,
@ -362,44 +388,77 @@ public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
}
}
// 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();
float x, y;
switch (action) {
case MotionEvent.ACTION_SCROLL:
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
SDLActivity.onNativeMouse(0, action, x, y, false);
return true;
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(0);
y = event.getY(0);
SDLActivity.onNativeMouse(0, action, x, y, true);
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:
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;
}
// Change our action value to what SDL's code expects.
if (action == MotionEvent.ACTION_BUTTON_PRESS) {
action = MotionEvent.ACTION_DOWN;
} else { /* MotionEvent.ACTION_BUTTON_RELEASE */
action = MotionEvent.ACTION_UP;
}
x = event.getX(0);
y = event.getY(0);
int button = event.getButtonState();
x = event.getX(i);
y = event.getY(i);
int button = event.getButtonState();
SDLActivity.onNativeMouse(button, action, x, y, true);
return true;
SDLActivity.onNativeMouse(button, action, x, y, true);
return true;
}
}
return false;
}
@Override
public boolean onScale(ScaleGestureDetector detector) {
float scale = detector.getScaleFactor();
SDLActivity.onNativePinchUpdate(scale);
return true;
}
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
SDLActivity.onNativePinchStart();
return true;
}
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
SDLActivity.onNativePinchEnd();
}
}

View file

@ -0,0 +1,51 @@
function(download_dep_tarball depname version url sha256)
set(lib_path "${CMAKE_SOURCE_DIR}/lib")
get_filename_component(filename "${url}" NAME)
set(download_path "${lib_path}/${depname}-${filename}")
set(dep_path "${lib_path}/${depname}")
set(ver_file "${dep_path}/.download_version")
if(EXISTS "${ver_file}")
file(READ "${ver_file}" current_version)
if("${current_version}" STREQUAL "${version}")
# up to date
return()
endif()
# outdated, redownload
file(REMOVE_RECURSE "${dep_path}")
endif()
file(MAKE_DIRECTORY "${dep_path}")
message(STATUS "Downloading ${depname} ${version}")
file(DOWNLOAD "${url}" "${download_path}" STATUS status)
list(GET status 0 status_code)
if(NOT status_code EQUAL 0)
message(FATAL_ERROR "Failed to download ${depname}, status: ${status}")
endif()
# Verify SHA256 checksum if provided
if(NOT sha256 STREQUAL "SKIP")
file(SHA256 "${download_path}" downloaded_sha256)
if(NOT "${downloaded_sha256}" STREQUAL "${sha256}")
message(FATAL_ERROR "SHA256 checksum mismatch for ${depname}!\n Expected: ${sha256}\n Got: ${downloaded_sha256}")
endif()
endif()
message(STATUS "Extracting...")
execute_process(
COMMAND tar -xzf "${download_path}" --strip-components=1
WORKING_DIRECTORY "${dep_path}"
RESULT_VARIABLE extract_result)
if(NOT extract_result EQUAL 0)
message(FATAL_ERROR "Failed to extract ${depname}")
endif()
file(WRITE "${ver_file}" "${version}")
endfunction()

View file

@ -9,9 +9,3 @@ set(JPEG_INCLUDE_DIR ${DEPS}/libjpeg/include)
set(JPEG_LIBRARY ${DEPS}/libjpeg/libjpeg.a)
set(PNG_PNG_INCLUDE_DIR ${DEPS}/libpng/include) #what
set(PNG_LIBRARY ${DEPS}/libpng/libpng.a)
add_library(SDL2::SDL2 STATIC IMPORTED)
set_target_properties(SDL2::SDL2 PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES ${DEPS}/SDL2/include/SDL2
IMPORTED_LOCATION ${DEPS}/SDL2/libSDL2.a)

33
cmake/SDL.cmake Normal file
View file

@ -0,0 +1,33 @@
set(DEP_SDL_VER "3.4.10")
download_dep_tarball(
"SDL"
"${DEP_SDL_VER}"
"https://github.com/libsdl-org/SDL/releases/download/release-${DEP_SDL_VER}/SDL3-${DEP_SDL_VER}.tar.gz"
"12b34280415ec8418c864408b93d008a20a6530687ee613d60bfbd20411f2785"
)
set(SDL_SHARED OFF CACHE BOOL "" FORCE)
set(SDL_STATIC ON CACHE BOOL "" FORCE)
set(DISABLED_FEATURES CAMERA GPU HAPTIC POWER RENDER SENSOR TESTS VULKAN)
foreach(feature ${DISABLED_FEATURES})
set(SDL_${feature} OFF CACHE BOOL "" FORCE)
endforeach()
if(ANDROID OR HAIKU)
enable_language(CXX)
endif()
if(HAIKU)
add_definitions(-fPIC)
endif()
if(EMSCRIPTEN)
set(SDL_PTHREADS ON CACHE BOOL "" FORCE)
set(SDL_EMSCRIPTEN_PERSISTENT_PATH "/storage" CACHE STRING "" FORCE)
endif()
add_definitions(-DSDL_LEAN_AND_MEAN=1)
add_subdirectory(lib/SDL EXCLUDE_FROM_ALL)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 336 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 397 B

After

Width:  |  Height:  |  Size: 618 B

Before After
Before After

2
lib/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
*.tar.gz
SDL

View file

@ -26,7 +26,7 @@
#include <Box2D/Collision/Shapes/b2Shape.h>
#include <algorithm>
#include <tms/backend/print.h>
#include <tms/core/print.h>
#include "creature.hh"
static const uint32 xTruncBits = 12;
@ -165,7 +165,7 @@ int32 b2ParticleSystem::CreateParticle(const b2ParticleDef& def)
tms_debugf("reached maximum particles");
return b2_invalidParticleIndex;
}
if (m_count >= m_internalAllocatedCapacity)
{
int32 capacity = m_count ? 2 * m_count : b2_minParticleBufferCapacity;

55
lib/Box2D/_unity_chunk.cc Normal file
View file

@ -0,0 +1,55 @@
#ifdef UNITY_BUILD
#include "Collision/b2BroadPhase.cc"
#include "Collision/b2CollideCircle.cc"
#include "Collision/b2CollideEdge.cc"
#include "Collision/b2CollidePolygon.cc"
#include "Collision/b2Collision.cc"
#include "Collision/b2Distance.cc"
#include "Collision/b2DynamicTree.cc"
#include "Collision/b2TimeOfImpact.cc"
#include "Collision/Shapes/b2ChainShape.cc"
#include "Collision/Shapes/b2CircleShape.cc"
#include "Collision/Shapes/b2EdgeShape.cc"
#include "Collision/Shapes/b2PolygonShape.cc"
#include "Common/b2BlockAllocator.cc"
#include "Common/b2Draw.cc"
#include "Common/b2Math.cc"
#include "Common/b2Settings.cc"
#include "Common/b2StackAllocator.cc"
#include "Common/b2Timer.cc"
#include "Dynamics/b2Body.cc"
#include "Dynamics/b2ContactManager.cc"
#include "Dynamics/b2Fixture.cc"
#include "Dynamics/b2Island.cc"
#include "Dynamics/b2WorldCallbacks.cc"
#include "Dynamics/b2World.cc"
#include "Dynamics/Contacts/b2ChainAndCircleContact.cc"
#include "Dynamics/Contacts/b2ChainAndPolygonContact.cc"
#include "Dynamics/Contacts/b2CircleContact.cc"
#include "Dynamics/Contacts/b2Contact.cc"
#include "Dynamics/Contacts/b2ContactSolver.cc"
#include "Dynamics/Contacts/b2EdgeAndCircleContact.cc"
#include "Dynamics/Contacts/b2EdgeAndPolygonContact.cc"
#include "Dynamics/Contacts/b2PolygonAndCircleContact.cc"
#include "Dynamics/Contacts/b2PolygonContact.cc"
#include "Dynamics/Joints/b2DistanceJoint.cc"
#include "Dynamics/Joints/b2FrictionJoint.cc"
#include "Dynamics/Joints/b2GearJoint.cc"
#include "Dynamics/Joints/b2Joint.cc"
#include "Dynamics/Joints/b2MotorJoint.cc"
#include "Dynamics/Joints/b2MouseJoint.cc"
#include "Dynamics/Joints/b2PivotJoint.cc"
#include "Dynamics/Joints/b2PrismaticJoint.cc"
#include "Dynamics/Joints/b2PulleyJoint.cc"
#include "Dynamics/Joints/b2RevoluteJoint.cc"
#include "Dynamics/Joints/b2RopeJoint.cc"
#include "Dynamics/Joints/b2WeldJoint.cc"
#include "Dynamics/Joints/b2WheelJoint.cc"
#include "Particle/b2Particle.cc"
#include "Particle/b2ParticleGroup.cc"
#include "Particle/b2ParticleSystem.cc"
#include "Particle/b2VoronoiDiagram.cc"
#include "Rope/b2Rope.cc"
#endif

49
lib/SDL3_polyfills.h vendored
View file

@ -1,49 +0,0 @@
#pragma once
// Put polyfills for stuff we want to use from SDL3. Remove this when we're on SDL3.
#include <SDL.h>
#include <jni.h>
const char *SDL_GetAndroidCachePath(void)
{
static char *s_AndroidCachePath = NULL;
if (!s_AndroidCachePath) {
JNIEnv* env = (JNIEnv*)SDL_AndroidGetJNIEnv();
jobject activity = (jobject)SDL_AndroidGetActivity();
if (!env || !activity) {
SDL_Log("Failed to get JNI environment or Activity.");
return NULL;
}
// Get the getCacheDir() method
jclass activityClass = (*env)->GetObjectClass(env, activity);
jmethodID getCacheDirMethod = (*env)->GetMethodID(env, activityClass, "getCacheDir", "()Ljava/io/File;");
if (!getCacheDirMethod) {
SDL_Log("Failed to get getCacheDir method.");
return NULL;
}
jobject cacheDirFile = (*env)->CallObjectMethod(env, activity, getCacheDirMethod);
jclass fileClass = (*env)->GetObjectClass(env, cacheDirFile);
jmethodID getAbsolutePathMethod = (*env)->GetMethodID(env, fileClass, "getAbsolutePath", "()Ljava/lang/String;");
if (!getAbsolutePathMethod) {
SDL_Log("Failed to get getAbsolutePath method.");
return NULL;
}
jstring absolutePath = (jstring)(*env)->CallObjectMethod(env, cacheDirFile, getAbsolutePathMethod);
const char* cachePath = (*env)->GetStringUTFChars(env, absolutePath, NULL);
char* result = SDL_strdup(cachePath);
(*env)->ReleaseStringUTFChars(env, absolutePath, cachePath);
s_AndroidCachePath = result;
}
return s_AndroidCachePath;
}

View file

@ -1,6 +1,6 @@
/*
SDL_image: An example image loading library for use with SDL
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@ -19,166 +19,64 @@
3. This notice may not be removed or altered from any source distribution.
*/
/* A simple library to load images of various formats as SDL surfaces */
#include <SDL_image.h>
#include "SDL_image.h"
#if defined(SDL_BUILD_MAJOR_VERSION) && defined(SDL_COMPILE_TIME_ASSERT)
SDL_COMPILE_TIME_ASSERT(SDL_BUILD_MAJOR_VERSION,
SDL_IMAGE_MAJOR_VERSION == SDL_BUILD_MAJOR_VERSION);
SDL_COMPILE_TIME_ASSERT(SDL_BUILD_MINOR_VERSION,
SDL_IMAGE_MINOR_VERSION == SDL_BUILD_MINOR_VERSION);
SDL_COMPILE_TIME_ASSERT(SDL_BUILD_MICRO_VERSION,
SDL_IMAGE_PATCHLEVEL == SDL_BUILD_MICRO_VERSION);
#endif
#if defined(SDL_COMPILE_TIME_ASSERT)
SDL_COMPILE_TIME_ASSERT(SDL_IMAGE_MAJOR_VERSION_min, SDL_IMAGE_MAJOR_VERSION >= 0);
/* Limited only by the need to fit in SDL_version */
SDL_COMPILE_TIME_ASSERT(SDL_IMAGE_MAJOR_VERSION_max, SDL_IMAGE_MAJOR_VERSION <= 255);
SDL_COMPILE_TIME_ASSERT(SDL_IMAGE_MINOR_VERSION_min, SDL_IMAGE_MINOR_VERSION >= 0);
/* Limited only by the need to fit in SDL_version */
SDL_COMPILE_TIME_ASSERT(SDL_IMAGE_MINOR_VERSION_max, SDL_IMAGE_MINOR_VERSION <= 255);
SDL_COMPILE_TIME_ASSERT(SDL_IMAGE_PATCHLEVEL_min, SDL_IMAGE_PATCHLEVEL >= 0);
/* Limited by its encoding in SDL_VERSIONNUM and in the ABI versions */
SDL_COMPILE_TIME_ASSERT(SDL_IMAGE_PATCHLEVEL_max, SDL_IMAGE_PATCHLEVEL <= 99);
#endif
/* Table of image detection and loading functions */
static struct {
const char *type;
int (SDLCALL *is)(SDL_RWops *src);
SDL_Surface *(SDLCALL *load)(SDL_RWops *src);
} supported[] = {
{ "JPG", IMG_isJPG, IMG_LoadJPG_RW },
{ "PNG", IMG_isPNG, IMG_LoadPNG_RW },
};
const SDL_version *IMG_Linked_Version(void)
/* Load an image from an SDL datasource, optionally specifying the type */
SDL_Surface *IMG_LoadTyped_IO(SDL_IOStream *src, bool closeio, const char *type)
{
static SDL_version linked_version;
SDL_IMAGE_VERSION(&linked_version);
return(&linked_version);
}
SDL_Surface *image;
extern int IMG_InitJPG(void);
extern void IMG_QuitJPG(void);
extern int IMG_InitPNG(void);
extern void IMG_QuitPNG(void);
static int initialized = 0;
int IMG_Init(int flags)
{
int result = 0;
if (flags & IMG_INIT_JPG) {
if ((initialized & IMG_INIT_JPG) || IMG_InitJPG() == 0) {
result |= IMG_INIT_JPG;
}
/* Make sure there is something to do.. */
if (!src) {
SDL_InvalidParamError("src");
return NULL;
}
if (flags & IMG_INIT_PNG) {
if ((initialized & IMG_INIT_PNG) || IMG_InitPNG() == 0) {
result |= IMG_INIT_PNG;
/* See whether or not this data source can handle seeking */
if (SDL_SeekIO(src, 0, SDL_IO_SEEK_CUR) < 0) {
SDL_SetError("Can't seek in this data source");
if (closeio) {
SDL_CloseIO(src);
}
return NULL;
}
if (type) {
if (SDL_strcasecmp(type, "JPEG") == 0 || SDL_strcasecmp(type, "JPG") == 0) {
image = IMG_LoadJPG_IO(src);
if (closeio) {
SDL_CloseIO(src);
}
return image;
}
if (SDL_strcasecmp(type, "PNG") == 0) {
image = IMG_LoadPNG_IO(src);
if (closeio) {
SDL_CloseIO(src);
}
return image;
}
}
initialized |= result;
return initialized;
}
void IMG_Quit()
{
if (initialized & IMG_INIT_JPG) {
IMG_QuitJPG();
if (closeio) {
SDL_CloseIO(src);
}
if (initialized & IMG_INIT_PNG) {
IMG_QuitPNG();
}
initialized = 0;
SDL_SetError("Unsupported image format");
return NULL;
}
/* Load an image from a file */
SDL_Surface *IMG_Load(const char *file)
{
SDL_RWops *src = SDL_RWFromFile(file, "rb");
SDL_IOStream *src = SDL_IOFromFile(file, "rb");
if (!src) {
/* The error message has been set in SDL_IOFromFile */
return NULL;
}
const char *ext = SDL_strrchr(file, '.');
if (ext) {
ext++;
}
if (!src) {
/* The error message has been set in SDL_RWFromFile */
return NULL;
}
return IMG_LoadTyped_RW(src, 1, ext);
}
/* Load an image from an SDL datasource (for compatibility) */
SDL_Surface *IMG_Load_RW(SDL_RWops *src, int freesrc)
{
return IMG_LoadTyped_RW(src, freesrc, NULL);
}
/* Portable case-insensitive string compare function */
static int IMG_string_equals(const char *str1, const char *str2)
{
while ( *str1 && *str2 ) {
if ( SDL_toupper((unsigned char)*str1) !=
SDL_toupper((unsigned char)*str2) )
break;
++str1;
++str2;
}
return (!*str1 && !*str2);
}
/* Load an image from an SDL datasource, optionally specifying the type */
SDL_Surface *IMG_LoadTyped_RW(SDL_RWops *src, int freesrc, const char *type)
{
int i;
SDL_Surface *image;
/* Make sure there is something to do.. */
if ( src == NULL ) {
IMG_SetError("Passed a NULL data source");
return(NULL);
}
/* See whether or not this data source can handle seeking */
if ( SDL_RWseek(src, 0, RW_SEEK_CUR) < 0 ) {
IMG_SetError("Can't seek in this data source");
if (freesrc)
SDL_RWclose(src);
return(NULL);
}
/* Detect the type of image being loaded */
for ( i=0; i < SDL_arraysize(supported); ++i ) {
if (supported[i].is) {
if (!supported[i].is(src))
continue;
} else {
/* magicless format */
if (!type || !IMG_string_equals(type, supported[i].type))
continue;
}
#ifdef DEBUG_IMGLIB
fprintf(stderr, "IMGLIB: Loading image as %s\n",
supported[i].type);
#endif
image = supported[i].load(src);
if (freesrc)
SDL_RWclose(src);
return image;
}
if ( freesrc ) {
SDL_RWclose(src);
}
IMG_SetError("Unsupported image format");
return NULL;
return IMG_LoadTyped_IO(src, true, ext);
}

View file

@ -1,6 +1,6 @@
/*
SDL_image: An example image loading library for use with SDL
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@ -21,168 +21,18 @@
/* This is a JPEG image file loading framework */
#include "SDL_image.h"
#include <SDL_image.h>
#include <stdio.h>
#include <setjmp.h>
#include <jpeglib.h>
#ifdef JPEG_TRUE /* MinGW version of jpeg-8.x renamed TRUE to JPEG_TRUE etc. */
typedef JPEG_boolean boolean;
#define TRUE JPEG_TRUE
#define FALSE JPEG_FALSE
#endif
/* Define this for fast loading and not as good image quality */
/*#define FAST_JPEG*/
/* Define this for quicker (but less perfect) JPEG identification */
#define FAST_IS_JPEG
static struct {
int loaded;
void *handle;
void (*jpeg_calc_output_dimensions) (j_decompress_ptr cinfo);
void (*jpeg_CreateDecompress) (j_decompress_ptr cinfo, int version, size_t structsize);
void (*jpeg_destroy_decompress) (j_decompress_ptr cinfo);
boolean (*jpeg_finish_decompress) (j_decompress_ptr cinfo);
int (*jpeg_read_header) (j_decompress_ptr cinfo, boolean require_image);
JDIMENSION (*jpeg_read_scanlines) (j_decompress_ptr cinfo, JSAMPARRAY scanlines, JDIMENSION max_lines);
boolean (*jpeg_resync_to_restart) (j_decompress_ptr cinfo, int desired);
boolean (*jpeg_start_decompress) (j_decompress_ptr cinfo);
void (*jpeg_CreateCompress) (j_compress_ptr cinfo, int version, size_t structsize);
void (*jpeg_start_compress) (j_compress_ptr cinfo, boolean write_all_tables);
void (*jpeg_set_quality) (j_compress_ptr cinfo, int quality, boolean force_baseline);
void (*jpeg_set_defaults) (j_compress_ptr cinfo);
JDIMENSION (*jpeg_write_scanlines) (j_compress_ptr cinfo, JSAMPARRAY scanlines, JDIMENSION num_lines);
void (*jpeg_finish_compress) (j_compress_ptr cinfo);
void (*jpeg_destroy_compress) (j_compress_ptr cinfo);
struct jpeg_error_mgr * (*jpeg_std_error) (struct jpeg_error_mgr * err);
} lib;
#ifdef LOAD_JPG_DYNAMIC
#define FUNCTION_LOADER(FUNC, SIG) \
lib.FUNC = (SIG) SDL_LoadFunction(lib.handle, #FUNC); \
if (lib.FUNC == NULL) { SDL_UnloadObject(lib.handle); return -1; }
#else
#define FUNCTION_LOADER(FUNC, SIG) \
lib.FUNC = FUNC;
#endif
int IMG_InitJPG()
{
if ( lib.loaded == 0 ) {
#ifdef LOAD_JPG_DYNAMIC
lib.handle = SDL_LoadObject(LOAD_JPG_DYNAMIC);
if ( lib.handle == NULL ) {
return -1;
}
#endif
FUNCTION_LOADER(jpeg_calc_output_dimensions, void (*) (j_decompress_ptr cinfo))
FUNCTION_LOADER(jpeg_CreateDecompress, void (*) (j_decompress_ptr cinfo, int version, size_t structsize))
FUNCTION_LOADER(jpeg_destroy_decompress, void (*) (j_decompress_ptr cinfo))
FUNCTION_LOADER(jpeg_finish_decompress, boolean (*) (j_decompress_ptr cinfo))
FUNCTION_LOADER(jpeg_read_header, int (*) (j_decompress_ptr cinfo, boolean require_image))
FUNCTION_LOADER(jpeg_read_scanlines, JDIMENSION (*) (j_decompress_ptr cinfo, JSAMPARRAY scanlines, JDIMENSION max_lines))
FUNCTION_LOADER(jpeg_resync_to_restart, boolean (*) (j_decompress_ptr cinfo, int desired))
FUNCTION_LOADER(jpeg_start_decompress, boolean (*) (j_decompress_ptr cinfo))
FUNCTION_LOADER(jpeg_CreateCompress, void (*) (j_compress_ptr cinfo, int version, size_t structsize))
FUNCTION_LOADER(jpeg_start_compress, void (*) (j_compress_ptr cinfo, boolean write_all_tables))
FUNCTION_LOADER(jpeg_set_quality, void (*) (j_compress_ptr cinfo, int quality, boolean force_baseline))
FUNCTION_LOADER(jpeg_set_defaults, void (*) (j_compress_ptr cinfo))
FUNCTION_LOADER(jpeg_write_scanlines, JDIMENSION (*) (j_compress_ptr cinfo, JSAMPARRAY scanlines, JDIMENSION num_lines))
FUNCTION_LOADER(jpeg_finish_compress, void (*) (j_compress_ptr cinfo))
FUNCTION_LOADER(jpeg_destroy_compress, void (*) (j_compress_ptr cinfo))
FUNCTION_LOADER(jpeg_std_error, struct jpeg_error_mgr * (*) (struct jpeg_error_mgr * err))
}
++lib.loaded;
return 0;
}
void IMG_QuitJPG()
{
if ( lib.loaded == 0 ) {
return;
}
if ( lib.loaded == 1 ) {
#ifdef LOAD_JPG_DYNAMIC
SDL_UnloadObject(lib.handle);
#endif
}
--lib.loaded;
}
/* See if an image is contained in a data source */
int IMG_isJPG(SDL_RWops *src)
{
Sint64 start;
int is_JPG;
int in_scan;
Uint8 magic[4];
/* This detection code is by Steaphan Greene <stea@cs.binghamton.edu> */
/* Blame me, not Sam, if this doesn't work right. */
/* And don't forget to report the problem to the the sdl list too! */
if ( !src )
return 0;
start = SDL_RWtell(src);
is_JPG = 0;
in_scan = 0;
if ( SDL_RWread(src, magic, 2, 1) ) {
if ( (magic[0] == 0xFF) && (magic[1] == 0xD8) ) {
is_JPG = 1;
while (is_JPG == 1) {
if(SDL_RWread(src, magic, 1, 2) != 2) {
is_JPG = 0;
} else if( (magic[0] != 0xFF) && (in_scan == 0) ) {
is_JPG = 0;
} else if( (magic[0] != 0xFF) || (magic[1] == 0xFF) ) {
/* Extra padding in JPEG (legal) */
/* or this is data and we are scanning */
SDL_RWseek(src, -1, RW_SEEK_CUR);
} else if(magic[1] == 0xD9) {
/* Got to end of good JPEG */
break;
} else if( (in_scan == 1) && (magic[1] == 0x00) ) {
/* This is an encoded 0xFF within the data */
} else if( (magic[1] >= 0xD0) && (magic[1] < 0xD9) ) {
/* These have nothing else */
} else if(SDL_RWread(src, magic+2, 1, 2) != 2) {
is_JPG = 0;
} else {
/* Yes, it's big-endian */
Sint64 innerStart;
Uint32 size;
Sint64 end;
innerStart = SDL_RWtell(src);
size = (magic[2] << 8) + magic[3];
end = SDL_RWseek(src, size-2, RW_SEEK_CUR);
if ( end != innerStart + size - 2 ) is_JPG = 0;
if ( magic[1] == 0xDA ) {
/* Now comes the actual JPEG meat */
#ifdef FAST_IS_JPEG
/* Ok, I'm convinced. It is a JPEG. */
break;
#else
/* I'm not convinced. Prove it! */
in_scan = 1;
#endif
}
}
}
}
}
SDL_RWseek(src, start, RW_SEEK_SET);
return(is_JPG);
}
#define INPUT_BUFFER_SIZE 4096
typedef struct {
struct jpeg_source_mgr pub;
SDL_RWops *ctx;
SDL_IOStream *ctx;
Uint8 buffer[INPUT_BUFFER_SIZE];
} my_source_mgr;
@ -193,7 +43,6 @@ typedef struct {
static void init_source (j_decompress_ptr cinfo)
{
/* We don't actually need to do anything */
(void)cinfo;
return;
}
@ -203,10 +52,10 @@ static void init_source (j_decompress_ptr cinfo)
static boolean fill_input_buffer (j_decompress_ptr cinfo)
{
my_source_mgr * src = (my_source_mgr *) cinfo->src;
int nbytes;
size_t nbytes;
nbytes = (int)SDL_RWread(src->ctx, src->buffer, 1, INPUT_BUFFER_SIZE);
if (nbytes <= 0) {
nbytes = SDL_ReadIO(src->ctx, src->buffer, INPUT_BUFFER_SIZE);
if (nbytes == 0) {
/* Insert a fake EOI marker */
src->buffer[0] = (Uint8) 0xFF;
src->buffer[1] = (Uint8) JPEG_EOI;
@ -258,7 +107,6 @@ static void skip_input_data (j_decompress_ptr cinfo, long num_bytes)
static void term_source (j_decompress_ptr cinfo)
{
/* We don't actually need to do anything */
(void)cinfo;
return;
}
@ -267,7 +115,7 @@ static void term_source (j_decompress_ptr cinfo)
* The caller must have already opened the stream, and is responsible
* for closing it after finishing decompression.
*/
static void jpeg_SDL_RW_src (j_decompress_ptr cinfo, SDL_RWops *ctx)
static void jpeg_SDL_IO_src (j_decompress_ptr cinfo, SDL_IOStream *ctx)
{
my_source_mgr *src;
@ -289,7 +137,7 @@ static void jpeg_SDL_RW_src (j_decompress_ptr cinfo, SDL_RWops *ctx)
src->pub.init_source = init_source;
src->pub.fill_input_buffer = fill_input_buffer;
src->pub.skip_input_data = skip_input_data;
src->pub.resync_to_restart = lib.jpeg_resync_to_restart; /* use default method */
src->pub.resync_to_restart = jpeg_resync_to_restart; /* use default method */
src->pub.term_source = term_source;
src->ctx = ctx;
src->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */
@ -310,106 +158,107 @@ static void my_error_exit(j_common_ptr cinfo)
static void output_no_message(j_common_ptr cinfo)
{
/* do nothing */
(void)cinfo;
}
/* Load a JPEG type image from an SDL datasource */
SDL_Surface *IMG_LoadJPG_RW(SDL_RWops *src)
{
Sint64 start;
struct loadjpeg_vars {
const char *error;
SDL_Surface *surface;
struct jpeg_decompress_struct cinfo;
JSAMPROW rowptr[1];
SDL_Surface *volatile surface = NULL;
struct my_error_mgr jerr;
};
if ( !src ) {
/* The error message has been set in SDL_RWFromFile */
return NULL;
}
start = SDL_RWtell(src);
if ( (IMG_Init(IMG_INIT_JPG) & IMG_INIT_JPG) == 0 ) {
return NULL;
}
/* Load a JPEG type image from an SDL datasource */
static bool LIBJPEG_LoadJPG_IO(SDL_IOStream *src, struct loadjpeg_vars *vars)
{
JSAMPROW rowptr[1];
/* Create a decompression structure and load the JPEG header */
cinfo.err = lib.jpeg_std_error(&jerr.errmgr);
jerr.errmgr.error_exit = my_error_exit;
jerr.errmgr.output_message = output_no_message;
#ifdef _MSC_VER
#pragma warning(disable:4611) /* warning C4611: interaction between '_setjmp' and C++ object destruction is non-portable */
#endif
if(setjmp(jerr.escape)) {
vars->cinfo.err = jpeg_std_error(&vars->jerr.errmgr);
vars->jerr.errmgr.error_exit = my_error_exit;
vars->jerr.errmgr.output_message = output_no_message;
if (setjmp(vars->jerr.escape)) {
/* If we get here, libjpeg found an error */
lib.jpeg_destroy_decompress(&cinfo);
if ( surface != NULL ) {
SDL_FreeSurface(surface);
}
SDL_RWseek(src, start, RW_SEEK_SET);
IMG_SetError("JPEG loading error");
return NULL;
jpeg_destroy_decompress(&vars->cinfo);
vars->error = "JPEG loading error";
return false;
}
lib.jpeg_create_decompress(&cinfo);
jpeg_SDL_RW_src(&cinfo, src);
lib.jpeg_read_header(&cinfo, TRUE);
jpeg_create_decompress(&vars->cinfo);
jpeg_SDL_IO_src(&vars->cinfo, src);
jpeg_read_header(&vars->cinfo, TRUE);
if(cinfo.num_components == 4) {
if (vars->cinfo.num_components == 4) {
/* Set 32-bit Raw output */
cinfo.out_color_space = JCS_CMYK;
cinfo.quantize_colors = FALSE;
lib.jpeg_calc_output_dimensions(&cinfo);
vars->cinfo.out_color_space = JCS_CMYK;
vars->cinfo.quantize_colors = FALSE;
jpeg_calc_output_dimensions(&vars->cinfo);
/* Allocate an output surface to hold the image */
surface = SDL_CreateRGBSurface(SDL_SWSURFACE,
cinfo.output_width, cinfo.output_height, 32,
#if SDL_BYTEORDER == SDL_LIL_ENDIAN
0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000);
#else
0x0000FF00, 0x00FF0000, 0xFF000000, 0x000000FF);
#endif
//surface = SDL_CreateRGBSurfaceWithFormat(0, cinfo.output_width, cinfo.output_height, 0, SDL_PIXELFORMAT_BGRA32);
vars->surface = SDL_CreateSurface(vars->cinfo.output_width, vars->cinfo.output_height, SDL_PIXELFORMAT_RGBA32);
} else {
/* Set 24-bit RGB output */
cinfo.out_color_space = JCS_RGB;
cinfo.quantize_colors = FALSE;
#ifdef FAST_JPEG
cinfo.scale_num = 1;
cinfo.scale_denom = 1;
cinfo.dct_method = JDCT_FASTEST;
cinfo.do_fancy_upsampling = FALSE;
#endif
lib.jpeg_calc_output_dimensions(&cinfo);
vars->cinfo.out_color_space = JCS_RGB;
vars->cinfo.quantize_colors = FALSE;
jpeg_calc_output_dimensions(&vars->cinfo);
/* Allocate an output surface to hold the image */
surface = SDL_CreateRGBSurface(SDL_SWSURFACE,
cinfo.output_width, cinfo.output_height, 24,
#if SDL_BYTEORDER == SDL_LIL_ENDIAN
0x0000FF, 0x00FF00, 0xFF0000,
#else
0xFF0000, 0x00FF00, 0x0000FF,
#endif
0);
//surface = SDL_CreateRGBSurfaceWithFormat(0, cinfo.output_width, cinfo.output_height, 0, SDL_PIXELFORMAT_RGB24);
vars->surface = SDL_CreateSurface(vars->cinfo.output_width, vars->cinfo.output_height, SDL_PIXELFORMAT_RGB24);
}
if ( surface == NULL ) {
lib.jpeg_destroy_decompress(&cinfo);
SDL_RWseek(src, start, RW_SEEK_SET);
IMG_SetError("Out of memory");
return NULL;
if (!vars->surface) {
jpeg_destroy_decompress(&vars->cinfo);
return false;
}
/* Decompress the image */
lib.jpeg_start_decompress(&cinfo);
while ( cinfo.output_scanline < cinfo.output_height ) {
rowptr[0] = (JSAMPROW)(Uint8 *)surface->pixels +
cinfo.output_scanline * surface->pitch;
lib.jpeg_read_scanlines(&cinfo, rowptr, (JDIMENSION) 1);
jpeg_start_decompress(&vars->cinfo);
while (vars->cinfo.output_scanline < vars->cinfo.output_height) {
rowptr[0] = (JSAMPROW)(Uint8 *)vars->surface->pixels +
vars->cinfo.output_scanline * vars->surface->pitch;
jpeg_read_scanlines(&vars->cinfo, rowptr, (JDIMENSION) 1);
}
lib.jpeg_finish_decompress(&cinfo);
lib.jpeg_destroy_decompress(&cinfo);
jpeg_finish_decompress(&vars->cinfo);
jpeg_destroy_decompress(&vars->cinfo);
return(surface);
if (vars->cinfo.num_components == 4) {
// The CMYK image is essentially RGBA composed over black
SDL_Surface *output = SDL_CreateSurface(vars->cinfo.output_width, vars->cinfo.output_height, SDL_PIXELFORMAT_RGB24);
if (!output) {
return false;
}
SDL_BlitSurface(vars->surface, NULL, output, NULL);
SDL_DestroySurface(vars->surface);
vars->surface = output;
}
return true;
}
SDL_Surface *IMG_LoadJPG_IO(SDL_IOStream *src)
{
Sint64 start;
struct loadjpeg_vars vars;
if (!src) {
/* The error message has been set in SDL_IOFromFile */
return NULL;
}
start = SDL_TellIO(src);
SDL_zero(vars);
if (LIBJPEG_LoadJPG_IO(src, &vars)) {
return vars.surface;
}
/* this may clobber a set error if seek fails: don't care. */
SDL_SeekIO(src, start, SDL_IO_SEEK_SET);
if (vars.surface) {
SDL_DestroySurface(vars.surface);
}
if (vars.error) {
SDL_SetError("%s", vars.error);
}
return NULL;
}

View file

@ -1,6 +1,6 @@
/*
SDL_image: An example image loading library for use with SDL
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@ -21,403 +21,398 @@
/* This is a PNG image file loading framework */
#include "SDL_image.h"
#define WANT_LIBPNG
/* This code was originally written by Philippe Lavoie (2 November 1998) */
#include "SDL_endian.h"
#ifdef macintosh
#define MACOS
#endif
#include <SDL_image.h>
#include <png.h>
/* Check for the older version of libpng */
#if (PNG_LIBPNG_VER_MAJOR == 1)
#if (PNG_LIBPNG_VER_MINOR < 5)
#define LIBPNG_VERSION_12
typedef png_bytep png_const_bytep;
typedef png_color *png_const_colorp;
typedef png_color_16 *png_const_color_16p;
#endif
#if (PNG_LIBPNG_VER_MINOR < 4)
typedef png_structp png_const_structp;
typedef png_infop png_const_infop;
#endif
#if (PNG_LIBPNG_VER_MINOR < 6)
typedef png_structp png_structrp;
typedef png_infop png_inforp;
typedef png_const_structp png_const_structrp;
typedef png_const_infop png_const_inforp;
/* noconst15: version < 1.6 doesn't have const, >= 1.6 adds it */
/* noconst16: version < 1.6 does have const, >= 1.6 removes it */
typedef png_structp png_noconst15_structrp;
typedef png_inforp png_noconst15_inforp;
typedef png_const_inforp png_noconst16_inforp;
#else
typedef png_const_structp png_noconst15_structrp;
typedef png_const_inforp png_noconst15_inforp;
typedef png_inforp png_noconst16_inforp;
#endif
#else
typedef png_const_structp png_noconst15_structrp;
typedef png_const_inforp png_noconst15_inforp;
typedef png_inforp png_noconst16_inforp;
#endif
static struct {
int loaded;
void *handle;
png_infop (*png_create_info_struct) (png_noconst15_structrp png_ptr);
png_structp (*png_create_read_struct) (png_const_charp user_png_ver, png_voidp error_ptr, png_error_ptr error_fn, png_error_ptr warn_fn);
void (*png_destroy_read_struct) (png_structpp png_ptr_ptr, png_infopp info_ptr_ptr, png_infopp end_info_ptr_ptr);
png_uint_32 (*png_get_IHDR) (png_noconst15_structrp png_ptr, png_noconst15_inforp info_ptr, png_uint_32 *width, png_uint_32 *height, int *bit_depth, int *color_type, int *interlace_method, int *compression_method, int *filter_method);
png_voidp (*png_get_io_ptr) (png_noconst15_structrp png_ptr);
png_byte (*png_get_channels) (png_const_structrp png_ptr, png_const_inforp info_ptr);
png_uint_32 (*png_get_PLTE) (png_const_structrp png_ptr, png_noconst16_inforp info_ptr, png_colorp *palette, int *num_palette);
png_uint_32 (*png_get_tRNS) (png_const_structrp png_ptr, png_inforp info_ptr, png_bytep *trans, int *num_trans, png_color_16p *trans_values);
png_uint_32 (*png_get_valid) (png_const_structrp png_ptr, png_const_inforp info_ptr, png_uint_32 flag);
void (*png_read_image) (png_structrp png_ptr, png_bytepp image);
void (*png_read_info) (png_structrp png_ptr, png_inforp info_ptr);
void (*png_read_update_info) (png_structrp png_ptr, png_inforp info_ptr);
void (*png_set_expand) (png_structrp png_ptr);
void (*png_set_gray_to_rgb) (png_structrp png_ptr);
void (*png_set_packing) (png_structrp png_ptr);
void (*png_set_read_fn) (png_structrp png_ptr, png_voidp io_ptr, png_rw_ptr read_data_fn);
void (*png_set_strip_16) (png_structrp png_ptr);
int (*png_set_interlace_handling) (png_structrp png_ptr);
int (*png_sig_cmp) (png_const_bytep sig, png_size_t start, png_size_t num_to_check);
#ifdef PNG_SETJMP_SUPPORTED
#ifndef LIBPNG_VERSION_12
jmp_buf* (*png_set_longjmp_fn) (png_structrp, png_longjmp_ptr, size_t);
#endif
#endif
} lib;
#ifdef LOAD_PNG_DYNAMIC
#define FUNCTION_LOADER(FUNC, SIG) \
lib.FUNC = (SIG) SDL_LoadFunction(lib.handle, #FUNC); \
if (lib.FUNC == NULL) { SDL_UnloadObject(lib.handle); return -1; }
#else
#define FUNCTION_LOADER(FUNC, SIG) \
lib.FUNC = FUNC;
#endif
int IMG_InitPNG()
static void png_read_data(png_structp png_ptr, png_bytep area, png_size_t size)
{
if ( lib.loaded == 0 ) {
#ifdef LOAD_PNG_DYNAMIC
lib.handle = SDL_LoadObject(LOAD_PNG_DYNAMIC);
if ( lib.handle == NULL ) {
return -1;
}
#endif
FUNCTION_LOADER(png_create_info_struct, png_infop (*) (png_noconst15_structrp png_ptr))
FUNCTION_LOADER(png_create_read_struct, png_structp (*) (png_const_charp user_png_ver, png_voidp error_ptr, png_error_ptr error_fn, png_error_ptr warn_fn))
FUNCTION_LOADER(png_destroy_read_struct, void (*) (png_structpp png_ptr_ptr, png_infopp info_ptr_ptr, png_infopp end_info_ptr_ptr))
FUNCTION_LOADER(png_get_IHDR, png_uint_32 (*) (png_noconst15_structrp png_ptr, png_noconst15_inforp info_ptr, png_uint_32 *width, png_uint_32 *height, int *bit_depth, int *color_type, int *interlace_method, int *compression_method, int *filter_method))
FUNCTION_LOADER(png_get_io_ptr, png_voidp (*) (png_noconst15_structrp png_ptr))
FUNCTION_LOADER(png_get_channels, png_byte (*) (png_const_structrp png_ptr, png_const_inforp info_ptr))
FUNCTION_LOADER(png_get_PLTE, png_uint_32 (*) (png_const_structrp png_ptr, png_noconst16_inforp info_ptr, png_colorp *palette, int *num_palette))
FUNCTION_LOADER(png_get_tRNS, png_uint_32 (*) (png_const_structrp png_ptr, png_inforp info_ptr, png_bytep *trans, int *num_trans, png_color_16p *trans_values))
FUNCTION_LOADER(png_get_valid, png_uint_32 (*) (png_const_structrp png_ptr, png_const_inforp info_ptr, png_uint_32 flag))
FUNCTION_LOADER(png_read_image, void (*) (png_structrp png_ptr, png_bytepp image))
FUNCTION_LOADER(png_read_info, void (*) (png_structrp png_ptr, png_inforp info_ptr))
FUNCTION_LOADER(png_read_update_info, void (*) (png_structrp png_ptr, png_inforp info_ptr))
FUNCTION_LOADER(png_set_expand, void (*) (png_structrp png_ptr))
FUNCTION_LOADER(png_set_gray_to_rgb, void (*) (png_structrp png_ptr))
FUNCTION_LOADER(png_set_packing, void (*) (png_structrp png_ptr))
FUNCTION_LOADER(png_set_read_fn, void (*) (png_structrp png_ptr, png_voidp io_ptr, png_rw_ptr read_data_fn))
FUNCTION_LOADER(png_set_strip_16, void (*) (png_structrp png_ptr))
FUNCTION_LOADER(png_set_interlace_handling, int (*) (png_structrp png_ptr))
FUNCTION_LOADER(png_sig_cmp, int (*) (png_const_bytep sig, png_size_t start, png_size_t num_to_check))
#ifdef PNG_SETJMP_SUPPORTED
#ifndef LIBPNG_VERSION_12
FUNCTION_LOADER(png_set_longjmp_fn, jmp_buf* (*) (png_structrp, png_longjmp_ptr, size_t))
#endif
#endif
SDL_IOStream *src = (SDL_IOStream *)png_get_io_ptr(png_ptr);
if (SDL_ReadIO(src, area, size) != size) {
png_error(png_ptr, "Failed to read all expected data from SDL_IOStream for PNG image.");
}
++lib.loaded;
return 0;
}
void IMG_QuitPNG()
{
if ( lib.loaded == 0 ) {
return;
}
if ( lib.loaded == 1 ) {
#ifdef LOAD_PNG_DYNAMIC
SDL_UnloadObject(lib.handle);
#endif
}
--lib.loaded;
}
/* See if an image is contained in a data source */
int IMG_isPNG(SDL_RWops *src)
static void png_write_data(png_structp png_ptr, png_bytep src, png_size_t size)
{
Sint64 start;
int is_PNG;
Uint8 magic[4];
SDL_IOStream *dst = (SDL_IOStream *)png_get_io_ptr(png_ptr);
if (SDL_WriteIO(dst, src, size) != size) {
png_error(png_ptr, "Failed to write all expected data to SDL_IOStream for PNG image.");
}
}
if ( !src ) {
return 0;
static void png_flush_data(png_structp png_ptr)
{
png_write_flush(png_ptr);
}
struct png_load_vars
{
const char *error;
SDL_Surface *surface;
png_structp png_ptr;
png_infop info_ptr;
png_bytep *row_pointers;
png_colorp color_ptr;
SDL_Surface *source_surface_for_save;
png_uint_32 width, height;
int bit_depth, color_type, interlace_type;
Uint32 format;
unsigned char header[8];
png_colorp png_palette;
int num_palette;
png_bytep trans;
int num_trans;
png_color_16p trans_values;
};
static bool LIBPNG_LoadPNG_IO_Internal(SDL_IOStream *src, struct png_load_vars *vars)
{
if (SDL_ReadIO(src, vars->header, sizeof(vars->header)) != sizeof(vars->header)) {
vars->error = "Failed to read PNG header from SDL_IOStream";
return false;
}
if (png_sig_cmp(vars->header, 0, 8)) {
vars->error = "Not a valid PNG file signature";
return false;
}
start = SDL_RWtell(src);
is_PNG = 0;
if ( SDL_RWread(src, magic, 1, sizeof(magic)) == sizeof(magic) ) {
if ( magic[0] == 0x89 &&
magic[1] == 'P' &&
magic[2] == 'N' &&
magic[3] == 'G' ) {
is_PNG = 1;
vars->png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (vars->png_ptr == NULL) {
vars->error = "Couldn't allocate memory for PNG read struct";
return false;
}
vars->info_ptr = png_create_info_struct(vars->png_ptr);
if (vars->info_ptr == NULL) {
vars->error = "Couldn't create image information for PNG file";
return false;
}
if (setjmp(*png_set_longjmp_fn(vars->png_ptr, longjmp, sizeof(jmp_buf)))) {
vars->error = "Error during PNG read operation";
return false;
}
png_set_read_fn(vars->png_ptr, src, png_read_data);
png_set_sig_bytes(vars->png_ptr, 8);
png_read_info(vars->png_ptr, vars->info_ptr);
png_get_IHDR(vars->png_ptr, vars->info_ptr, &vars->width, &vars->height, &vars->bit_depth,
&vars->color_type, &vars->interlace_type, NULL, NULL);
// Only convert non-palette formats to RGB/RGBA
// TODO: Convert this to a colour key - the specs say that there's
// only one transparent colour for non-palette images
if (vars->color_type != PNG_COLOR_TYPE_PALETTE) {
if (png_get_valid(vars->png_ptr, vars->info_ptr, PNG_INFO_tRNS)) {
png_set_tRNS_to_alpha(vars->png_ptr);
vars->color_type |= PNG_COLOR_MASK_ALPHA;
}
}
SDL_RWseek(src, start, RW_SEEK_SET);
return(is_PNG);
/* SDL doesn't currently support this format, so we convert to RGB for now. */
if (vars->color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
png_set_gray_to_rgb(vars->png_ptr);
}
if (vars->color_type == PNG_COLOR_TYPE_PALETTE ||
vars->color_type == PNG_COLOR_TYPE_GRAY) {
if (vars->bit_depth == 1) {
vars->format = SDL_PIXELFORMAT_INDEX1MSB;
} else if (vars->bit_depth == 2) {
vars->format = SDL_PIXELFORMAT_INDEX2MSB;
} else if (vars->bit_depth == 4) {
vars->format = SDL_PIXELFORMAT_INDEX4MSB;
} else /* if (vars->bit_depth == 8) */ {
vars->format = SDL_PIXELFORMAT_INDEX8;
}
} else if (vars->bit_depth == 16) {
if (vars->color_type & PNG_COLOR_MASK_ALPHA) {
vars->format = SDL_PIXELFORMAT_RGBA64;
} else {
vars->format = SDL_PIXELFORMAT_RGB48;
}
} else {
if (vars->color_type & PNG_COLOR_MASK_ALPHA) {
vars->format = SDL_PIXELFORMAT_RGBA32;
} else {
vars->format = SDL_PIXELFORMAT_RGB24;
}
}
png_read_update_info(vars->png_ptr, vars->info_ptr);
png_get_IHDR(vars->png_ptr, vars->info_ptr, &vars->width, &vars->height, &vars->bit_depth,
&vars->color_type, &vars->interlace_type, NULL, NULL);
vars->surface = SDL_CreateSurface(vars->width, vars->height, vars->format);
if (vars->surface == NULL) {
vars->error = SDL_GetError();
return false;
}
// For palette images, set up the palette
if (vars->color_type == PNG_COLOR_TYPE_PALETTE) {
if (png_get_PLTE(vars->png_ptr, vars->info_ptr, &vars->png_palette, &vars->num_palette)) {
SDL_Palette *palette = SDL_CreateSurfacePalette(vars->surface);
if (!palette) {
vars->error = "Failed to create palette for PNG image";
return false;
}
for (int i = 0; i < palette->ncolors; i++) {
palette->colors[i].r = vars->png_palette[i].red;
palette->colors[i].g = vars->png_palette[i].green;
palette->colors[i].b = vars->png_palette[i].blue;
palette->colors[i].a = 255;
}
if (png_get_tRNS(vars->png_ptr, vars->info_ptr, &vars->trans, &vars->num_trans, &vars->trans_values)) {
for (int i = 0; i < vars->num_trans && i < palette->ncolors; i++) {
palette->colors[i].a = vars->trans[i];
}
SDL_SetSurfaceBlendMode(vars->surface, SDL_BLENDMODE_BLEND);
}
}
} else if (vars->color_type == PNG_COLOR_TYPE_GRAY) {
SDL_Palette *palette = SDL_CreateSurfacePalette(vars->surface);
if (!palette) {
vars->error = "Failed to create palette for PNG image";
return false;
}
for (int i = 0; i < palette->ncolors; i++) {
palette->colors[i].r = (i * 255) / palette->ncolors;
palette->colors[i].g = (i * 255) / palette->ncolors;
palette->colors[i].b = (i * 255) / palette->ncolors;
palette->colors[i].a = 255;
}
}
vars->row_pointers = (png_bytep *)SDL_malloc(sizeof(png_bytep) * vars->height);
if (!vars->row_pointers) {
vars->error = "Out of memory allocating row pointers";
return false;
}
for (png_uint_32 y = 0; y < vars->height; y++) {
vars->row_pointers[y] = (png_bytep)((Uint8 *)vars->surface->pixels + y * (size_t)vars->surface->pitch);
}
png_read_image(vars->png_ptr, vars->row_pointers);
#if SDL_BYTEORDER != SDL_BIG_ENDIAN
if (vars->format == SDL_PIXELFORMAT_RGBA64) {
Uint16 *pixels = (Uint16 *)vars->surface->pixels;
int num_pixels = vars->width * vars->height * 4;
for (int i = 0; i < num_pixels; i++) {
pixels[i] = SDL_Swap16(pixels[i]);
}
}
#endif
return true;
}
/* Load a PNG type image from an SDL datasource */
static void png_read_data(png_structp ctx, png_bytep area, png_size_t size)
SDL_Surface *IMG_LoadPNG_IO(SDL_IOStream *src)
{
SDL_RWops *src;
Sint64 start_pos;
bool success = false;
src = (SDL_RWops *)lib.png_get_io_ptr(ctx);
SDL_RWread(src, area, size, 1);
if (!src) {
SDL_SetError("SDL_IOStream is NULL");
return NULL;
}
start_pos = SDL_TellIO(src);
struct png_load_vars vars;
SDL_zero(vars);
success = LIBPNG_LoadPNG_IO_Internal(src, &vars);
if (vars.png_ptr) {
png_destroy_read_struct(&vars.png_ptr,
vars.info_ptr ? &vars.info_ptr : (png_infopp)NULL,
(png_infopp)NULL);
}
if (vars.row_pointers) {
SDL_free(vars.row_pointers);
}
if (success) {
return vars.surface;
} else {
SDL_SeekIO(src, start_pos, SDL_IO_SEEK_SET);
if (vars.surface) {
SDL_DestroySurface(vars.surface);
}
if (vars.error) {
SDL_SetError("%s", vars.error);
}
return NULL;
}
}
SDL_Surface *IMG_LoadPNG_RW(SDL_RWops *src)
// Save PNGs
struct png_save_vars
{
Sint64 start;
const char *error;
SDL_Surface *volatile surface;
SDL_Surface *surface;
png_structp png_ptr;
png_infop info_ptr;
png_uint_32 width, height;
int bit_depth, color_type, interlace_type, num_channels;
Uint32 Rmask;
Uint32 Gmask;
Uint32 Bmask;
Uint32 Amask;
png_bytep *row_pointers;
png_colorp color_ptr;
SDL_Surface *source_surface_for_save;
Uint8 transparent_table[256];
SDL_Palette *palette;
png_bytep *volatile row_pointers;
int row, i;
int ckey;
png_color_16 *transv;
int png_color_type;
int bit_depth; // default to 8
};
if ( !src ) {
/* The error message has been set in SDL_RWFromFile */
return NULL;
static bool LIBPNG_SavePNG_IO_Internal(struct png_save_vars *vars, SDL_Surface *surface, SDL_IOStream *dst)
{
vars->source_surface_for_save = surface;
vars->png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (vars->png_ptr == NULL) {
vars->error = "Couldn't allocate memory for PNG write struct";
return false;
}
start = SDL_RWtell(src);
if ( (IMG_Init(IMG_INIT_PNG) & IMG_INIT_PNG) == 0 ) {
return NULL;
vars->info_ptr = png_create_info_struct(vars->png_ptr);
if (vars->info_ptr == NULL) {
vars->error = "Couldn't create image information for PNG file";
return false;
}
/* Initialize the data we will clean up when we're done */
error = NULL;
png_ptr = NULL; info_ptr = NULL; row_pointers = NULL; surface = NULL;
/* Create the PNG loading context structure */
png_ptr = lib.png_create_read_struct(PNG_LIBPNG_VER_STRING,
NULL,NULL,NULL);
if (png_ptr == NULL){
error = "Couldn't allocate memory for PNG file or incompatible PNG dll";
goto done;
if (setjmp(*png_set_longjmp_fn(vars->png_ptr, longjmp, sizeof(jmp_buf)))) {
vars->error = "Error during PNG write operation";
return false;
}
/* Allocate/initialize the memory for image information. REQUIRED. */
info_ptr = lib.png_create_info_struct(png_ptr);
if (info_ptr == NULL) {
error = "Couldn't create image information for PNG file";
goto done;
}
png_set_write_fn(vars->png_ptr, dst, png_write_data, png_flush_data);
/* Set error handling if you are using setjmp/longjmp method (this is
* the normal method of doing things with libpng). REQUIRED unless you
* set up your own error handlers in png_create_read_struct() earlier.
*/
vars->palette = SDL_GetSurfacePalette(surface);
if (vars->palette) {
const int ncolors = vars->palette->ncolors;
int i;
int last_transparent = -1;
#ifdef PNG_SETJMP_SUPPORTED
#ifdef _MSC_VER
#pragma warning(disable:4611) /* warning C4611: interaction between '_setjmp' and C++ object destruction is non-portable */
#endif
#ifndef LIBPNG_VERSION_12
if ( setjmp(*lib.png_set_longjmp_fn(png_ptr, longjmp, sizeof (jmp_buf))) )
#else
if ( setjmp(png_ptr->jmpbuf) )
#endif
{
error = "Error reading the PNG file.";
goto done;
}
#endif
/* Set up the input control */
lib.png_set_read_fn(png_ptr, src, png_read_data);
/* Read PNG header info */
lib.png_read_info(png_ptr, info_ptr);
lib.png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth,
&color_type, &interlace_type, NULL, NULL);
/* tell libpng to strip 16 bit/color files down to 8 bits/color */
lib.png_set_strip_16(png_ptr);
/* tell libpng to de-interlace (if the image is interlaced) */
lib.png_set_interlace_handling(png_ptr);
/* Extract multiple pixels with bit depths of 1, 2, and 4 from a single
* byte into separate bytes (useful for paletted and grayscale images).
*/
lib.png_set_packing(png_ptr);
/* scale greyscale values to the range 0..255 */
if (color_type == PNG_COLOR_TYPE_GRAY)
lib.png_set_expand(png_ptr);
/* For images with a single "transparent colour", set colour key;
if more than one index has transparency, or if partially transparent
entries exist, use full alpha channel */
ckey = -1;
if (lib.png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
int num_trans;
Uint8 *trans;
lib.png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, &transv);
if (color_type == PNG_COLOR_TYPE_PALETTE) {
/* Check if all tRNS entries are opaque except one */
int j, t = -1;
for (j = 0; j < num_trans; j++) {
if (trans[j] == 0) {
if (t >= 0) {
break;
}
t = j;
} else if (trans[j] != 255) {
break;
}
vars->color_ptr = (png_colorp)SDL_malloc(sizeof(png_color) * ncolors);
if (vars->color_ptr == NULL) {
vars->error = "Couldn't allocate palette for PNG file";
return false;
}
for (i = 0; i < ncolors; i++) {
vars->color_ptr[i].red = vars->palette->colors[i].r;
vars->color_ptr[i].green = vars->palette->colors[i].g;
vars->color_ptr[i].blue = vars->palette->colors[i].b;
if (vars->palette->colors[i].a != 255) {
last_transparent = i;
}
if (j == num_trans) {
/* exactly one transparent index */
ckey = t;
} else {
/* more than one transparent index, or translucency */
lib.png_set_expand(png_ptr);
}
png_set_PLTE(vars->png_ptr, vars->info_ptr, vars->color_ptr, ncolors);
vars->png_color_type = PNG_COLOR_TYPE_PALETTE;
if (last_transparent >= 0) {
for (i = 0; i <= last_transparent; ++i) {
vars->transparent_table[i] = vars->palette->colors[i].a;
}
} else {
ckey = 0; /* actual value will be set later */
png_set_tRNS(vars->png_ptr, vars->info_ptr, vars->transparent_table, last_transparent + 1, NULL);
}
} else if (surface->format == SDL_PIXELFORMAT_RGB24) {
vars->png_color_type = PNG_COLOR_TYPE_RGB;
} else if (!SDL_ISPIXELFORMAT_ALPHA(surface->format)) {
vars->png_color_type = PNG_COLOR_TYPE_RGB;
vars->source_surface_for_save = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_RGB24);
if (!vars->source_surface_for_save) {
vars->error = SDL_GetError();
return false;
}
} else {
vars->png_color_type = PNG_COLOR_TYPE_RGBA;
vars->source_surface_for_save = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_RGBA32);
if (!vars->source_surface_for_save) {
vars->error = SDL_GetError();
return false;
}
}
if ( color_type == PNG_COLOR_TYPE_GRAY_ALPHA )
lib.png_set_gray_to_rgb(png_ptr);
png_set_IHDR(vars->png_ptr, vars->info_ptr, vars->source_surface_for_save->w, vars->source_surface_for_save->h,
vars->bit_depth, vars->png_color_type, PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
lib.png_read_update_info(png_ptr, info_ptr);
png_write_info(vars->png_ptr, vars->info_ptr);
lib.png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth,
&color_type, &interlace_type, NULL, NULL);
/* Allocate the SDL surface to hold the image */
Rmask = Gmask = Bmask = Amask = 0 ;
num_channels = lib.png_get_channels(png_ptr, info_ptr);
if ( num_channels >= 3 ) {
#if SDL_BYTEORDER == SDL_LIL_ENDIAN
Rmask = 0x000000FF;
Gmask = 0x0000FF00;
Bmask = 0x00FF0000;
Amask = (num_channels == 4) ? 0xFF000000 : 0;
#else
int s = (num_channels == 4) ? 0 : 8;
Rmask = 0xFF000000 >> s;
Gmask = 0x00FF0000 >> s;
Bmask = 0x0000FF00 >> s;
Amask = 0x000000FF >> s;
#endif
vars->row_pointers = (png_bytep *)SDL_malloc(sizeof(png_bytep) * vars->source_surface_for_save->h);
if (!vars->row_pointers) {
vars->error = "Out of memory allocating row pointers";
return false;
}
for (int row = 0; row < (int)vars->source_surface_for_save->h; row++) {
vars->row_pointers[row] = (png_bytep)((Uint8 *)vars->source_surface_for_save->pixels + row * (size_t)vars->source_surface_for_save->pitch);
}
surface = SDL_CreateRGBSurface(SDL_SWSURFACE, width, height,
bit_depth*num_channels, Rmask,Gmask,Bmask,Amask);
png_write_image(vars->png_ptr, vars->row_pointers);
png_write_end(vars->png_ptr, vars->info_ptr);
if ( surface == NULL ) {
error = SDL_GetError();
goto done;
}
if (ckey != -1) {
if (color_type != PNG_COLOR_TYPE_PALETTE) {
/* FIXME: Should these be truncated or shifted down? */
ckey = SDL_MapRGB(surface->format,
(Uint8)transv->red,
(Uint8)transv->green,
(Uint8)transv->blue);
}
SDL_SetColorKey(surface, SDL_TRUE, ckey);
}
/* Create the array of pointers to image data */
row_pointers = (png_bytep*) SDL_malloc(sizeof(png_bytep)*height);
if (!row_pointers) {
error = "Out of memory";
goto done;
}
for (row = 0; row < (int)height; row++) {
row_pointers[row] = (png_bytep)
(Uint8 *)surface->pixels + row*surface->pitch;
}
/* Read the entire image in one go */
lib.png_read_image(png_ptr, row_pointers);
/* and we're done! (png_read_end() can be omitted if no processing of
* post-IDAT text/time/etc. is desired)
* In some cases it can't read PNG's created by some popular programs (ACDSEE),
* we do not want to process comments, so we omit png_read_end
lib.png_read_end(png_ptr, info_ptr);
*/
/* Load the palette, if any */
palette = surface->format->palette;
if ( palette ) {
int png_num_palette;
png_colorp png_palette;
lib.png_get_PLTE(png_ptr, info_ptr, &png_palette, &png_num_palette);
if (color_type == PNG_COLOR_TYPE_GRAY) {
palette->ncolors = 256;
for (i = 0; i < 256; i++) {
palette->colors[i].r = (Uint8)i;
palette->colors[i].g = (Uint8)i;
palette->colors[i].b = (Uint8)i;
}
} else if (png_num_palette > 0 ) {
palette->ncolors = png_num_palette;
for ( i=0; i<png_num_palette; ++i ) {
palette->colors[i].b = png_palette[i].blue;
palette->colors[i].g = png_palette[i].green;
palette->colors[i].r = png_palette[i].red;
}
}
}
done: /* Clean up and return */
if ( png_ptr ) {
lib.png_destroy_read_struct(&png_ptr,
info_ptr ? &info_ptr : (png_infopp)0,
(png_infopp)0);
}
if ( row_pointers ) {
SDL_free(row_pointers);
}
if ( error ) {
SDL_RWseek(src, start, RW_SEEK_SET);
if ( surface ) {
SDL_FreeSurface(surface);
surface = NULL;
}
IMG_SetError("%s", error);
}
return(surface);
return true;
}
bool IMG_SavePNG_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio)
{
if (!surface) {
return SDL_InvalidParamError("surface");
}
if (SDL_ISPIXELFORMAT_INDEXED(surface->format) && !SDL_GetSurfacePalette(surface)) {
return SDL_SetError("Indexed surfaces must have a palette");
}
if (!surface || !dst) {
SDL_SetError("Surface or SDL_IOStream is NULL");
return false;
}
struct png_save_vars vars;
bool result = false;
SDL_zero(vars);
vars.bit_depth = 8;
result = LIBPNG_SavePNG_IO_Internal(&vars, surface, dst);
if (vars.png_ptr) {
png_destroy_write_struct(&vars.png_ptr, &vars.info_ptr);
}
if (vars.color_ptr) {
SDL_free(vars.color_ptr);
}
if (vars.row_pointers) {
SDL_free(vars.row_pointers);
}
if (vars.source_surface_for_save && vars.source_surface_for_save != surface) {
SDL_DestroySurface(vars.source_surface_for_save);
}
if (!result && vars.error) {
SDL_SetError("%s", vars.error);
}
if (closeio) {
result &= SDL_CloseIO(dst);
}
return result;
}
bool IMG_SavePNG(SDL_Surface *surface, const char *file)
{
SDL_IOStream *dst = SDL_IOFromFile(file, "wb");
if (dst) {
return IMG_SavePNG_IO(surface, dst, true);
} else {
return false;
}
}

View file

@ -1,4 +1,4 @@
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages

View file

@ -1,5 +1,5 @@
This is a cut down version of SDL2_image for use with Principia.
This is a cut down version of SDL3_image for use with Principia.
It only allows loading PNG and JPEG images using libpng and libjpeg respectively.
This version of SDL_image is based on commit fc0263b58036429bb5ff99f3b41acf919c44a46f in upstream.
This version of SDL_image is based on commit 0e2eaa923ddea285dfa35c4bf0c0092d3799e2ee in upstream.

View file

@ -1,6 +1,6 @@
/*
SDL_image: An example image loading library for use with SDL
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@ -19,147 +19,51 @@
3. This notice may not be removed or altered from any source distribution.
*/
/**
* \file SDL_image.h
*
* Header file for SDL_image library
*
* A simple library to load images of various formats as SDL surfaces
*/
#ifndef SDL_IMAGE_H_
#define SDL_IMAGE_H_
#include "SDL.h"
#include "SDL_version.h"
#include "begin_code.h"
#include <SDL3/SDL.h>
#include <SDL3/SDL_begin_code.h>
/* Set up for C function definitions, even when using C++ */
#ifdef __cplusplus
extern "C" {
#endif
/**
* Printable format: "%d.%d.%d", MAJOR, MINOR, PATCHLEVEL
*/
#define SDL_IMAGE_MAJOR_VERSION 2
#define SDL_IMAGE_MINOR_VERSION 9
#define SDL_IMAGE_PATCHLEVEL 0
/**
* This macro can be used to fill a version structure with the compile-time
* version of the SDL_image library.
*/
#define SDL_IMAGE_VERSION(X) \
{ \
(X)->major = SDL_IMAGE_MAJOR_VERSION; \
(X)->minor = SDL_IMAGE_MINOR_VERSION; \
(X)->patch = SDL_IMAGE_PATCHLEVEL; \
}
#if SDL_IMAGE_MAJOR_VERSION < 3 && SDL_MAJOR_VERSION < 3
/**
* This is the version number macro for the current SDL_image version.
*
* In versions higher than 2.9.0, the minor version overflows into
* the thousands digit: for example, 2.23.0 is encoded as 4300.
* This macro will not be available in SDL 3.x or SDL_image 3.x.
*
* Deprecated, use SDL_IMAGE_VERSION_ATLEAST or SDL_IMAGE_VERSION instead.
*/
#define SDL_IMAGE_COMPILEDVERSION \
SDL_VERSIONNUM(SDL_IMAGE_MAJOR_VERSION, SDL_IMAGE_MINOR_VERSION, SDL_IMAGE_PATCHLEVEL)
#endif /* SDL_IMAGE_MAJOR_VERSION < 3 && SDL_MAJOR_VERSION < 3 */
/**
* This macro will evaluate to true if compiled with SDL_image at least X.Y.Z.
*/
#define SDL_IMAGE_VERSION_ATLEAST(X, Y, Z) \
((SDL_IMAGE_MAJOR_VERSION >= X) && \
(SDL_IMAGE_MAJOR_VERSION > X || SDL_IMAGE_MINOR_VERSION >= Y) && \
(SDL_IMAGE_MAJOR_VERSION > X || SDL_IMAGE_MINOR_VERSION > Y || SDL_IMAGE_PATCHLEVEL >= Z))
/**
* This function gets the version of the dynamically linked SDL_image library.
*
* it should NOT be used to fill a version structure, instead you should use
* the SDL_IMAGE_VERSION() macro.
*
* \returns SDL_image version
*/
extern DECLSPEC const SDL_version * SDLCALL IMG_Linked_Version(void);
/**
* Initialization flags
*/
typedef enum
{
IMG_INIT_JPG = 0x00000001,
IMG_INIT_PNG = 0x00000002
} IMG_InitFlags;
/**
* Initialize SDL_image.
*/
extern DECLSPEC int SDLCALL IMG_Init(int flags);
/**
* Deinitialize SDL_image.
*/
extern DECLSPEC void SDLCALL IMG_Quit(void);
/**
* Load an image from an SDL data source into a software surface.
*/
extern DECLSPEC SDL_Surface * SDLCALL IMG_LoadTyped_RW(SDL_RWops *src, int freesrc, const char *type);
/**
* Load an image from a filesystem path into a software surface.
*/
extern DECLSPEC SDL_Surface * SDLCALL IMG_Load(const char *file);
extern SDL_DECLSPEC SDL_Surface * SDLCALL IMG_Load(const char *file);
/**
* Load an image from an SDL data source into a software surface.
*/
extern DECLSPEC SDL_Surface * SDLCALL IMG_Load_RW(SDL_RWops *src, int freesrc);
/**
* Detect JPG image data on a readable/seekable SDL_RWops.
*/
extern DECLSPEC int SDLCALL IMG_isJPG(SDL_RWops *src);
/**
* Detect PNG image data on a readable/seekable SDL_RWops.
*/
extern DECLSPEC int SDLCALL IMG_isPNG(SDL_RWops *src);
extern SDL_DECLSPEC SDL_Surface * SDLCALL IMG_LoadTyped_IO(SDL_IOStream *src, bool closeio, const char *type);
/**
* Load a JPG image directly.
*/
extern DECLSPEC SDL_Surface * SDLCALL IMG_LoadJPG_RW(SDL_RWops *src);
extern SDL_DECLSPEC SDL_Surface * SDLCALL IMG_LoadJPG_IO(SDL_IOStream *src);
/**
* Load a PNG image directly.
*/
extern DECLSPEC SDL_Surface * SDLCALL IMG_LoadPNG_RW(SDL_RWops *src);
extern SDL_DECLSPEC SDL_Surface * SDLCALL IMG_LoadPNG_IO(SDL_IOStream *src);
/**
* Report SDL_image errors
*
* \sa IMG_GetError
* Save an SDL_Surface into a PNG image file.
*/
#define IMG_SetError SDL_SetError
extern SDL_DECLSPEC bool SDLCALL IMG_SavePNG(SDL_Surface *surface, const char *file);
/**
* Get last SDL_image error
*
* \sa IMG_SetError
* Save an SDL_Surface into PNG image data, via an SDL_IOStream.
*/
#define IMG_GetError SDL_GetError
extern SDL_DECLSPEC bool SDLCALL IMG_SavePNG_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio);
/* Ends C function definitions when using C++ */
#ifdef __cplusplus
}
#endif
#include "close_code.h"
#include <SDL3/SDL_close_code.h>
#endif /* SDL_IMAGE_H_ */

View file

@ -1,5 +1,5 @@
This is a cut down version of SDL2_mixer for use with Principia.
This is a cut down version of SDL3_mixer for use with Principia.
It only allows playing WAV sound files.
This version of SDL_mixer is based on commit 485be4be5fbd51e390c651b2d0fe0cc8479f0e62 in upstream.
Currently we're on the legacy `sdl2-api-on-sdl3` branch of SDL_mixer, which is SDL2_mixer adapted to work with SDL3 but with the mixer API largely unchanged. This branch is not recommended to be used, but is the easiest option for now before Principia's sound code is rewritten to use the new SDL3_mixer ("SDL_remixer") API.

View file

@ -22,11 +22,8 @@
#ifndef SDL_MIXER_H_
#define SDL_MIXER_H_
#include "SDL_stdinc.h"
#include "SDL_rwops.h"
#include "SDL_audio.h"
#include "SDL_version.h"
#include "begin_code.h"
#include <SDL3/SDL.h>
#include <SDL3/SDL_begin_code.h>
/* Set up for C function definitions, even when using C++ */
#ifdef __cplusplus
@ -34,11 +31,11 @@ extern "C" {
#endif
/**
* Printable format: "%d.%d.%d", MAJOR, MINOR, PATCHLEVEL
* Printable format: "%d.%d.%d", MAJOR, MINOR, MICRO
*/
#define SDL_MIXER_MAJOR_VERSION 2
#define SDL_MIXER_MINOR_VERSION 7
#define SDL_MIXER_PATCHLEVEL 2
#define SDL_MIXER_MAJOR_VERSION 3
#define SDL_MIXER_MINOR_VERSION 0
#define SDL_MIXER_MICRO_VERSION 0
/**
* The default mixer has 8 simultaneous mixing channels
@ -49,9 +46,9 @@ extern "C" {
/* Good default values for a PC soundcard */
#define MIX_DEFAULT_FREQUENCY 44100
#define MIX_DEFAULT_FORMAT AUDIO_S16SYS
#define MIX_DEFAULT_FORMAT SDL_AUDIO_S16
#define MIX_DEFAULT_CHANNELS 2
#define MIX_MAX_VOLUME SDL_MIX_MAXVOLUME /* Volume of a chunk */
#define MIX_MAX_VOLUME 128 /* Volume of a chunk */
/**
* The internal format for an audio chunk
@ -75,74 +72,73 @@ typedef enum {
/**
* Open the default audio device for playback.
*/
extern DECLSPEC int SDLCALL Mix_OpenAudio(int frequency, Uint16 format, int channels, int chunksize);
/**
* Open a specific audio device for playback.
*/
extern DECLSPEC int SDLCALL Mix_OpenAudioDevice(int frequency, Uint16 format, int channels, int chunksize, const char* device, int allowed_changes);
extern SDL_DECLSPEC bool SDLCALL Mix_OpenAudio(SDL_AudioDeviceID devid, const SDL_AudioSpec *spec);
/**
* Suspend or resume the whole audio output.
*/
extern DECLSPEC void SDLCALL Mix_PauseAudio(int pause_on);
extern SDL_DECLSPEC void SDLCALL Mix_PauseAudio(int pause_on);
/**
* Find out what the actual audio device parameters are.
*/
extern DECLSPEC int SDLCALL Mix_QuerySpec(int *frequency, Uint16 *format, int *channels);
extern SDL_DECLSPEC bool SDLCALL Mix_QuerySpec(int *frequency, SDL_AudioFormat *format, int *channels);
/**
* Dynamically change the number of channels managed by the mixer.
*/
extern DECLSPEC int SDLCALL Mix_AllocateChannels(int numchans);
extern SDL_DECLSPEC int SDLCALL Mix_AllocateChannels(int numchans);
/**
* Load a supported audio format into a chunk.
*/
extern DECLSPEC Mix_Chunk * SDLCALL Mix_LoadWAV_RW(SDL_RWops *src, int freesrc);
extern SDL_DECLSPEC Mix_Chunk * SDLCALL Mix_LoadWAV_RW(SDL_IOStream *src, int freesrc);
/**
* Load a supported audio format into a chunk.
*/
extern DECLSPEC Mix_Chunk * SDLCALL Mix_LoadWAV(const char *file);
extern SDL_DECLSPEC Mix_Chunk * SDLCALL Mix_LoadWAV(const char *file);
/**
* Load a raw audio data from memory as quickly as possible.
*/
extern DECLSPEC Mix_Chunk * SDLCALL Mix_QuickLoad_RAW(Uint8 *mem, Uint32 len);
extern SDL_DECLSPEC Mix_Chunk * SDLCALL Mix_QuickLoad_RAW(Uint8 *mem, Uint32 len);
/**
* Free an audio chunk.
*/
extern DECLSPEC void SDLCALL Mix_FreeChunk(Mix_Chunk *chunk);
extern SDL_DECLSPEC void SDLCALL Mix_FreeChunk(Mix_Chunk *chunk);
/**
* Get a list of chunk decoders that this build of SDL_mixer provides.
*/
extern DECLSPEC int SDLCALL Mix_GetNumChunkDecoders(void);
extern SDL_DECLSPEC int SDLCALL Mix_GetNumChunkDecoders(void);
/**
* Get a chunk decoder's name.
*/
extern DECLSPEC const char * SDLCALL Mix_GetChunkDecoder(int index);
extern SDL_DECLSPEC const char * SDLCALL Mix_GetChunkDecoder(int index);
/**
* Check if a chunk decoder is available by name.
*/
extern DECLSPEC SDL_bool SDLCALL Mix_HasChunkDecoder(const char *name);
extern SDL_DECLSPEC bool SDLCALL Mix_HasChunkDecoder(const char *name);
typedef void (SDLCALL *Mix_MixCallback)(void *udata, Uint8 *stream, int len);
/**
* Set a function that is called after all mixing is performed.
*/
extern DECLSPEC void SDLCALL Mix_SetPostMix(void (SDLCALL *mix_func)(void *udata, Uint8 *stream, int len), void *arg);
extern SDL_DECLSPEC void SDLCALL Mix_SetPostMix(Mix_MixCallback mix_func, void *arg);
typedef void (SDLCALL *Mix_ChannelFinishedCallback)(int channel);
/**
* Set a callback that runs when a channel has finished playing.
*/
extern DECLSPEC void SDLCALL Mix_ChannelFinished(void (SDLCALL *channel_finished)(int channel));
extern SDL_DECLSPEC void SDLCALL Mix_ChannelFinished(Mix_ChannelFinishedCallback channel_finished);
#define MIX_CHANNEL_POST (-2)
@ -155,18 +151,18 @@ typedef void (SDLCALL *Mix_EffectDone_t)(int chan, void *udata);
/**
* Register a special effect function.
*/
extern DECLSPEC int SDLCALL Mix_RegisterEffect(int chan, Mix_EffectFunc_t f, Mix_EffectDone_t d, void *arg);
extern SDL_DECLSPEC bool SDLCALL Mix_RegisterEffect(int chan, Mix_EffectFunc_t f, Mix_EffectDone_t d, void *arg);
/**
* Explicitly unregister a special effect function.
*/
extern DECLSPEC int SDLCALL Mix_UnregisterEffect(int channel, Mix_EffectFunc_t f);
extern SDL_DECLSPEC bool SDLCALL Mix_UnregisterEffect(int channel, Mix_EffectFunc_t f);
/**
* Explicitly unregister all special effect functions.
*/
extern DECLSPEC int SDLCALL Mix_UnregisterAllEffects(int channel);
extern SDL_DECLSPEC bool SDLCALL Mix_UnregisterAllEffects(int channel);
#define MIX_EFFECTSMAXSPEED "MIX_EFFECTSMAXSPEED"
@ -184,19 +180,19 @@ extern DECLSPEC int SDLCALL Mix_UnregisterAllEffects(int channel);
/**
* Set the panning of a channel.
*/
extern DECLSPEC int SDLCALL Mix_SetPanning(int channel, Uint8 left, Uint8 right);
extern SDL_DECLSPEC bool SDLCALL Mix_SetPanning(int channel, Uint8 left, Uint8 right);
/**
* Set the position of a channel.
*/
extern DECLSPEC int SDLCALL Mix_SetPosition(int channel, Sint16 angle, Uint8 distance);
extern SDL_DECLSPEC bool SDLCALL Mix_SetPosition(int channel, Sint16 angle, Uint8 distance);
/**
* Set the "distance" of a channel.
*/
extern DECLSPEC int SDLCALL Mix_SetDistance(int channel, Uint8 distance);
extern SDL_DECLSPEC bool SDLCALL Mix_SetDistance(int channel, Uint8 distance);
/* end of effects API. */
@ -206,7 +202,7 @@ extern DECLSPEC int SDLCALL Mix_SetDistance(int channel, Uint8 distance);
/**
* Reserve the first channels for the application.
*/
extern DECLSPEC int SDLCALL Mix_ReserveChannels(int num);
extern SDL_DECLSPEC int SDLCALL Mix_ReserveChannels(int num);
/* Channel grouping functions */
@ -214,163 +210,136 @@ extern DECLSPEC int SDLCALL Mix_ReserveChannels(int num);
/**
* Assign a tag to a channel.
*/
extern DECLSPEC int SDLCALL Mix_GroupChannel(int which, int tag);
extern SDL_DECLSPEC bool SDLCALL Mix_GroupChannel(int which, int tag);
/**
* Assign several consecutive channels to the same tag.
*/
extern DECLSPEC int SDLCALL Mix_GroupChannels(int from, int to, int tag);
extern SDL_DECLSPEC bool SDLCALL Mix_GroupChannels(int from, int to, int tag);
/**
* Finds the first available channel in a group of channels.
*/
extern DECLSPEC int SDLCALL Mix_GroupAvailable(int tag);
extern SDL_DECLSPEC int SDLCALL Mix_GroupAvailable(int tag);
/**
* Returns the number of channels in a group.
*/
extern DECLSPEC int SDLCALL Mix_GroupCount(int tag);
extern SDL_DECLSPEC int SDLCALL Mix_GroupCount(int tag);
/**
* Find the "oldest" sample playing in a group of channels.
*/
extern DECLSPEC int SDLCALL Mix_GroupOldest(int tag);
extern SDL_DECLSPEC int SDLCALL Mix_GroupOldest(int tag);
/**
* Find the "most recent" sample playing in a group of channels.
*/
extern DECLSPEC int SDLCALL Mix_GroupNewer(int tag);
extern SDL_DECLSPEC int SDLCALL Mix_GroupNewer(int tag);
/**
* Play an audio chunk on a specific channel.
*/
extern DECLSPEC int SDLCALL Mix_PlayChannel(int channel, Mix_Chunk *chunk, int loops);
extern SDL_DECLSPEC int SDLCALL Mix_PlayChannel(int channel, Mix_Chunk *chunk, int loops);
/**
* Play an audio chunk on a specific channel for a maximum time.
*/
extern DECLSPEC int SDLCALL Mix_PlayChannelTimed(int channel, Mix_Chunk *chunk, int loops, int ticks);
extern SDL_DECLSPEC int SDLCALL Mix_PlayChannelTimed(int channel, Mix_Chunk *chunk, int loops, int ticks);
/**
* Play an audio chunk on a specific channel, fading in the audio.
*/
extern DECLSPEC int SDLCALL Mix_FadeInChannel(int channel, Mix_Chunk *chunk, int loops, int ms);
extern SDL_DECLSPEC int SDLCALL Mix_FadeInChannel(int channel, Mix_Chunk *chunk, int loops, int ms);
/**
* Play an audio chunk on a specific channel, fading in the audio, for a
* maximum time.
*/
extern DECLSPEC int SDLCALL Mix_FadeInChannelTimed(int channel, Mix_Chunk *chunk, int loops, int ms, int ticks);
extern SDL_DECLSPEC int SDLCALL Mix_FadeInChannelTimed(int channel, Mix_Chunk *chunk, int loops, int ms, int ticks);
/**
* Set the volume for a specific channel.
*/
extern DECLSPEC int SDLCALL Mix_Volume(int channel, int volume);
extern SDL_DECLSPEC int SDLCALL Mix_Volume(int channel, int volume);
/**
* Set the volume for a specific chunk.
*/
extern DECLSPEC int SDLCALL Mix_VolumeChunk(Mix_Chunk *chunk, int volume);
extern SDL_DECLSPEC int SDLCALL Mix_VolumeChunk(Mix_Chunk *chunk, int volume);
/**
* Set the master volume for all channels.
*/
extern DECLSPEC int SDLCALL Mix_MasterVolume(int volume);
extern SDL_DECLSPEC int SDLCALL Mix_MasterVolume(int volume);
/**
* Halt playing of a particular channel.
*/
extern DECLSPEC int SDLCALL Mix_HaltChannel(int channel);
extern SDL_DECLSPEC void SDLCALL Mix_HaltChannel(int channel);
/**
* Halt playing of a group of channels by arbitrary tag.
*/
extern DECLSPEC int SDLCALL Mix_HaltGroup(int tag);
extern SDL_DECLSPEC void SDLCALL Mix_HaltGroup(int tag);
/**
* Change the expiration delay for a particular channel.
*/
extern DECLSPEC int SDLCALL Mix_ExpireChannel(int channel, int ticks);
extern SDL_DECLSPEC int SDLCALL Mix_ExpireChannel(int channel, int ticks);
/**
* Halt a channel after fading it out for a specified time.
*/
extern DECLSPEC int SDLCALL Mix_FadeOutChannel(int which, int ms);
extern SDL_DECLSPEC int SDLCALL Mix_FadeOutChannel(int which, int ms);
/**
* Halt a playing group of channels by arbitrary tag, after fading them out
* for a specified time.
*/
extern DECLSPEC int SDLCALL Mix_FadeOutGroup(int tag, int ms);
extern SDL_DECLSPEC int SDLCALL Mix_FadeOutGroup(int tag, int ms);
/**
* Query the fading status of a channel.
*/
extern DECLSPEC Mix_Fading SDLCALL Mix_FadingChannel(int which);
extern SDL_DECLSPEC Mix_Fading SDLCALL Mix_FadingChannel(int which);
/**
* Pause a particular channel.
*/
extern DECLSPEC void SDLCALL Mix_Pause(int channel);
extern SDL_DECLSPEC void SDLCALL Mix_Pause(int channel);
/**
* Resume a particular channel.
*/
extern DECLSPEC void SDLCALL Mix_Resume(int channel);
extern SDL_DECLSPEC void SDLCALL Mix_Resume(int channel);
/**
* Query whether a particular channel is paused.
*/
extern DECLSPEC int SDLCALL Mix_Paused(int channel);
extern SDL_DECLSPEC int SDLCALL Mix_Paused(int channel);
/**
* Check the playing status of a specific channel.
*/
extern DECLSPEC int SDLCALL Mix_Playing(int channel);
extern SDL_DECLSPEC int SDLCALL Mix_Playing(int channel);
/**
* Get the Mix_Chunk currently associated with a mixer channel.
*/
extern DECLSPEC Mix_Chunk * SDLCALL Mix_GetChunk(int channel);
extern SDL_DECLSPEC Mix_Chunk * SDLCALL Mix_GetChunk(int channel);
/**
* Close the mixer, halting all playing audio.
*/
extern DECLSPEC void SDLCALL Mix_CloseAudio(void);
extern SDL_DECLSPEC void SDLCALL Mix_CloseAudio(void);
/* We'll use SDL for reporting errors */
/**
* Report SDL_mixer errors
*
* \sa Mix_GetError
*/
#define Mix_SetError SDL_SetError
/**
* Get last SDL_mixer error
*
* \sa Mix_SetError
*/
#define Mix_GetError SDL_GetError
/**
* Clear last SDL_mixer error
*
* \sa Mix_SetError
*/
#define Mix_ClearError SDL_ClearError
/**
* Set OutOfMemory error
*/
#define Mix_OutOfMemory SDL_OutOfMemory
/* Ends C function definitions when using C++ */
#ifdef __cplusplus
}
#endif
#include "close_code.h"
#include <SDL3/SDL_close_code.h>
#endif /* SDL_MIXER_H_ */

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
/*
SDL_mixer: An audio mixer library based on the SDL library
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@ -112,8 +112,3 @@ void *_Eff_build_volume_table_s8(void)
return _Eff_volume_table;
}
/* end of effects.c ... */
/* vi: set ts=4 sw=4 expandtab: */

View file

@ -1,6 +1,6 @@
/*
SDL_mixer: An audio mixer library based on the SDL library
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@ -22,10 +22,6 @@
#ifndef INCLUDE_EFFECTS_INTERNAL_H_
#define INCLUDE_EFFECTS_INTERNAL_H_
#ifndef MIX_INTERNAL_EFFECT__
#error You should not include this file or use these functions.
#endif
#include "SDL_mixer.h"
extern int _Mix_effects_max_speed;
@ -37,11 +33,8 @@ void _Mix_InitEffects(void);
void _Mix_DeinitEffects(void);
void _Eff_PositionDeinit(void);
int _Mix_RegisterEffect_locked(int channel, Mix_EffectFunc_t f,
Mix_EffectDone_t d, void *arg);
int _Mix_UnregisterEffect_locked(int channel, Mix_EffectFunc_t f);
int _Mix_UnregisterAllEffects_locked(int channel);
bool _Mix_RegisterEffect_locked(int channel, Mix_EffectFunc_t f, Mix_EffectDone_t d, void *arg);
bool _Mix_UnregisterEffect_locked(int channel, Mix_EffectFunc_t f);
bool _Mix_UnregisterAllEffects_locked(int channel);
#endif /* _INCLUDE_EFFECTS_INTERNAL_H_ */
/* vi: set ts=4 sw=4 expandtab: */

File diff suppressed because it is too large Load diff

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