diff options
author | sapier <Sapier at GMX dot net> | 2014-04-21 14:10:59 +0200 |
---|---|---|
committer | sapier <Sapier at GMX dot net> | 2014-06-29 18:17:56 +0200 |
commit | 1cc40c0a7c260f0562572bc99f39a666a12f1b09 (patch) | |
tree | c5af6b9787f4c69faa634e82f6484ca4540a7f88 | |
parent | ff36071d93266c1dd18708f8924d80aa1af5b33e (diff) | |
download | minetest-1cc40c0a7c260f0562572bc99f39a666a12f1b09.tar.gz minetest-1cc40c0a7c260f0562572bc99f39a666a12f1b09.tar.bz2 minetest-1cc40c0a7c260f0562572bc99f39a666a12f1b09.zip |
Add support for Android 2.3+
There have been plenty of ppl involved in creating this version.
I don't wanna mention names as I'm sure I'd forget someone so I
just tell where help has been done:
- The partial android versions done by various ppl
- Testing on different android devices
- reviewing code (especially the in core changes)
- testing controls
- reviewing texts
A big thank you to everyone helping this to be completed!
66 files changed, 4424 insertions, 161 deletions
diff --git a/.gitignore b/.gitignore index 2e1584b1d..98a7f35b9 100644 --- a/.gitignore +++ b/.gitignore @@ -64,3 +64,16 @@ locale/ *.layout *.o +#build variants +build/android/assets +build/android/bin +build/android/Debug +build/android/deps +build/android/gen +build/android/jni/src/* +build/android/libs +build/android/obj +timestamp + + + diff --git a/build/android/AndroidManifest.xml.template b/build/android/AndroidManifest.xml.template new file mode 100644 index 000000000..dcffa35f6 --- /dev/null +++ b/build/android/AndroidManifest.xml.template @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="org.minetest.minetest" + android:versionCode="###ANDROID_VERSION###" + android:versionName="###BASE_VERSION###.###ANDROID_VERSION###" + android:installLocation="auto"> + <uses-sdk android:minSdkVersion="9"/> + <uses-feature android:glEsVersion="0x00010000" android:required="true"/> + <uses-permission android:name="android.permission.INTERNET" /> + <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + ###DEBUG_BUILD### + <application android:icon="@drawable/irr_icon" android:label="Minetest" android:theme="@android:style/Theme.NoTitleBar.Fullscreen" ###DEBUG_FLAG###> + <activity android:name=".MtNativeActivity" + android:label="Minetest" + android:launchMode="singleTask" + android:configChanges="orientation|keyboardHidden" + android:screenOrientation="landscape" + android:clearTaskOnLaunch="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + <meta-data android:name="android.app.lib_name" android:value="minetest" /> + </activity> + <activity android:name=".MinetestTextEntry" + android:theme="@style/Theme.Transparent" + android:excludeFromRecents="true"> + </activity> + <activity android:name=".MinetestAssetCopy" + android:theme="@style/Theme.Transparent" + android:excludeFromRecents="true"> + </activity> + </application> +</manifest> diff --git a/build/android/Makefile b/build/android/Makefile new file mode 100644 index 000000000..9e693432b --- /dev/null +++ b/build/android/Makefile @@ -0,0 +1,724 @@ +# build options + +OS := $(shell uname) + +#automaticaly set number of jobs +ifeq ($(OS),Linux) + PARALLEL := $(shell grep -c ^processor /proc/cpuinfo) +else + PARALLEL := 1 +endif + +# compile with GPROF +# GPROF = 1 + +# build for build platform +APP_PLATFORM = android-9 + +# paths used for timestaps, dependencys, tree config and libs +PATHCFGFILE = path.cfg + +ROOT = $(shell pwd) + +################################################################################ +# Android Version code +# Increase for each build! +################################################################################ +ANDROID_VERSION_CODE = 3 + +################################################################################ +# toolchain config for arm old processors +################################################################################ +TARGET_HOST = arm-linux +TARGET_ABI = armeabi +TARGET_LIBDIR = armeabi +TARGET_TOOLCHAIN = arm-linux-androideabi- +TARGET_CFLAGS_ADDON = -mfloat-abi=softfp -mfpu=vfp +CROSS_PREFIX = arm-linux-androideabi- +COMPILER_VERSION = 4.8 + +################################################################################ +# toolchain config for arm new processors +################################################################################ +#TARGET_HOST = arm-linux +#TARGET_ABI = armeabi-v7a-hard +#TARGET_LIBDIR = armeabi-v7a +#TARGET_TOOLCHAIN = arm-linux-androideabi- +#TARGET_CFLAGS_ADDON = -mfpu=vfpv3-d16 -D_NDK_MATH_NO_SOFTFP=1 \ +# -mfloat-abi=hard -march=armv7-a +#TARGET_CXXFLAGS_ADDON = $(TARGET_CFLAGS_ADDON) +#TARGET_LDFLAGS_ADDON = -Wl,--no-warn-mismatch -lm_hard +#CROSS_PREFIX = arm-linux-androideabi- +#COMPILER_VERSION = 4.8 + +################################################################################ +# toolchain config for little endian mips +################################################################################ +#TARGET_HOST = mipsel-linux +#TARGET_ABI = mips +#TARGET_LIBDIR = mips +#TARGET_TOOLCHAIN = mipsel-linux-android- +#CROSS_PREFIX = mipsel-linux-android- +#COMPILER_VERSION = 4.8 + +################################################################################ +# toolchain config for x86 +################################################################################ +#TARGET_HOST = x86-linux +#TARGET_ABI = x86 +#TARGET_LIBDIR = x86 +#TARGET_TOOLCHAIN = x86- +#CROSS_PREFIX = i686-linux-android- +#COMPILER_VERSION = 4.8 + +################################################################################ +ASSETS_TIMESTAMP = deps/assets_timestamp + +LEVELDB_DIR = $(ROOT)/deps/leveldb/ +LEVELDB_LIB = $(LEVELDB_DIR)libleveldb.a +LEVELDB_TIMESTAMP = $(LEVELDB_DIR)/timestamp +LEVELDB_TIMESTAMP_INT = $(ROOT)/deps/leveldb_timestamp +LEVELDB_URL_GIT = https://code.google.com/p/leveldb/ + +OPENAL_DIR = $(ROOT)/deps/openal-soft/ +OPENAL_LIB = $(OPENAL_DIR)libs/$(TARGET_ABI)/libopenal.so +OPENAL_TIMESTAMP = $(OPENAL_DIR)/timestamp +OPENAL_TIMESTAMP_INT = $(ROOT)/deps/openal_timestamp +OPENAL_URL_GIT = https://github.com/apportable/openal-soft + +OGG_DIR = $(ROOT)/deps/libvorbis-libogg-android/ +OGG_LIB = $(OGG_DIR)libs/$(TARGET_ABI)/libogg.so +VORBIS_LIB = $(OGG_DIR)libs/$(TARGET_ABI)/libogg.so +OGG_TIMESTAMP = $(OGG_DIR)timestamp +OGG_TIMESTAMP_INT = $(ROOT)/deps/ogg_timestamp +OGG_URL_GIT = https://github.com/vincentjames501/libvorbis-libogg-android + +IRRLICHT_DIR = $(ROOT)/deps/irrlicht/ +IRRLICHT_LIB = $(IRRLICHT_DIR)lib/Android/libIrrlicht.a +IRRLICHT_TIMESTAMP = $(IRRLICHT_DIR)timestamp +IRRLICHT_TIMESTAMP_INT = $(ROOT)/deps/irrlicht_timestamp +IRRLICHT_URL_SVN = http://svn.code.sf.net/p/irrlicht/code/branches/ogl-es/ + +OPENSSL_BASEDIR = openssl-android +OPENSSL_DIR = $(ROOT)/deps/$(OPENSSL_BASEDIR)/ +OPENSSL_LIB = $(OPENSSL_DIR)libs/$(TARGET_ABI)/libopenssl.so +OPENSSL_TIMESTAMP = $(OPENSSL_DIR)timestamp +OPENSSL_TIMESTAMP_INT = $(ROOT)/deps/openssl_timestamp +OPENSSL_URL_GIT = https://github.com/wobbals/openssl-android + +CURL_VERSION = 7.35.0 +CURL_DIR = $(ROOT)/deps/curl-$(CURL_VERSION) +CURL_LIB = $(CURL_DIR)/lib/.libs/libcurl.a +CURL_TIMESTAMP = $(CURL_DIR)/timestamp +CURL_TIMESTAMP_INT = $(ROOT)/deps/curl_timestamp +CURL_URL_HTTP = http://curl.haxx.se/download/curl-${CURL_VERSION}.tar.bz2 + +FREETYPE_DIR = $(ROOT)/deps/freetype2-android/ +FREETYPE_LIB = $(FREETYPE_DIR)/Android/obj/local/$(TARGER_ABI)/libfreetype2-static.a +FREETYPE_TIMESTAMP = $(FREETYPE_DIR)timestamp +FREETYPE_TIMESTAMP_INT = $(ROOT)/deps/freetype_timestamp +FREETYPE_URL_GIT = https://github.com/cdave1/freetype2-android + +-include $(PATHCFGFILE) + +.PHONY : debug release reconfig delconfig \ + leveldb_download clean_leveldb leveldb\ + irrlicht_download clean_irrlicht irrlicht \ + clean_assets assets \ + freetype_download clean_freetype freetype \ + apk clean_apk \ + clean_all clean prep_srcdir \ + install_debug install envpaths all \ + manifest clean_manifest\ + $(ASSETS_TIMESTAMP) $(LEVELDB_TIMESTAMP) \ + $(OPENAL_TIMESTAMP) $(OGG_TIMESTAMP) \ + $(IRRLICHT_TIMESTAMP) $(CURL_TIMESTAMP) \ + $(OPENSSL_TIMESTAMP) curl_binary \ + $(ROOT)/jni/src/android_version.h + +debug : $(PATHCFGFILE) + export NDEBUG=; \ + export BUILD_TYPE=debug; \ + $(MAKE) -j${PARALLEL} apk + +all : debug release + +release : $(PATHCFGFILE) + @export NDEBUG=1; \ + export BUILD_TYPE=release; \ + $(MAKE) -j${PARALLEL} apk + +reconfig: delconfig + @$(MAKE) -j${PARALLEL} $(PATHCFGFILE) + +delconfig : + $(RM) ${PATHCFGFILE} + +$(PATHCFGFILE) : + @echo "Please specify path of ANDROID NDK"; \ + echo "e.g. /home/user/android-ndk-r9c/"; \ + read ANDROID_NDK ; \ + if [ ! -d $$ANDROID_NDK ] ; then \ + echo "$$ANDROID_NDK is not a valid folder"; \ + exit 1; \ + fi; \ + echo "ANDROID_NDK = $$ANDROID_NDK" > ${PATHCFGFILE}; \ + echo "NDK_MODULE_PATH = $$ANDROID_NDK/tools" >> ${PATHCFGFILE}; \ + echo "+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++";\ + echo "+ Note: NDK_MODULE_PATH is set to $$ANDROID_NDK/tools"; \ + echo "+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++";\ + echo "Please specify path of ANDROID SDK"; \ + echo "e.g. /home/user/adt-bundle-linux-x86_64-20131030/sdk/"; \ + read SDKFLDR ; \ + if [ ! -d $$SDKFLDR ] ; then \ + echo "$$SDKFLDR is not a valid folder"; \ + exit 1; \ + fi; \ + echo "SDKFOLDER = $$SDKFLDR" >> ${PATHCFGFILE}; + +$(OPENAL_TIMESTAMP) : openal_download + @LAST_MODIF=$$(find ${OPENAL_DIR} -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -f2- -d" "); \ + if [ $$(basename $$LAST_MODIF) != "timestamp" ] ; then \ + touch ${OPENAL_TIMESTAMP}; \ + fi + +openal_download : + @if [ ! -d ${OPENAL_DIR} ] ; then \ + echo "openal sources missing, downloading..."; \ + mkdir -p ${ROOT}/deps; \ + cd ${ROOT}/deps ; \ + git clone ${OPENAL_URL_GIT} || exit 1; \ + fi + +openal : $(OPENAL_LIB) + +$(OPENAL_LIB): $(OPENAL_TIMESTAMP) + @REFRESH=0; \ + if [ ! -e ${OPENAL_TIMESTAMP_INT} ] ; then \ + REFRESH=1; \ + fi; \ + if [ ${OPENAL_TIMESTAMP} -nt ${OPENAL_TIMESTAMP_INT} ] ; then \ + REFRESH=1; \ + fi; \ + if [ $$REFRESH -ne 0 ] ; then \ + export PATH=$$PATH:${SDKFOLDER}/platform-tools:${ANDROID_NDK}; \ + echo "changed timestamp for openal detected building..."; \ + cd ${OPENAL_DIR}; \ + ndk-build NDEBUG=${NDEBUG} NDK_MODULE_PATH=${NDK_MODULE_PATH} \ + APP_ABI=${TARGET_ABI} APP_PLATFORM=${APP_PLATFORM} -j${PARALLEL} \ + TARGET_CFLAGS+="${TARGET_CFLAGS_ADDON}" \ + TARGET_LDFLAGS+="${TARGET_LDFLAGS_ADDON}" \ + TARGET_CXXFLAGS+="${TARGET_CXXFLAGS_ADDON}" || exit 1; \ + touch ${OPENAL_TIMESTAMP}; \ + touch ${OPENAL_TIMESTAMP_INT}; \ + else \ + echo "nothing to be done for openal"; \ + fi + +clean_openal : + $(RM) -rf ${OPENAL_DIR} + +$(OGG_TIMESTAMP) : ogg_download + @LAST_MODIF=$$(find ${OGG_DIR} -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -f2- -d" "); \ + if [ $$(basename $$LAST_MODIF) != "timestamp" ] ; then \ + touch ${OGG_TIMESTAMP}; \ + fi + +ogg_download : + @if [ ! -d ${OGG_DIR} ] ; then \ + echo "ogg sources missing, downloading..."; \ + mkdir -p ${ROOT}/deps; \ + cd ${ROOT}/deps ; \ + git clone ${OGG_URL_GIT}|| exit 1; \ + cd libvorbis-libogg-android ; \ + patch -p1 < ../../libvorbis-libogg-fpu.patch || exit 1; \ + fi + +ogg : $(OGG_LIB) + +$(OGG_LIB): $(OGG_TIMESTAMP) + @REFRESH=0; \ + if [ ! -e ${OGG_TIMESTAMP_INT} ] ; then \ + echo "${OGG_TIMESTAMP_INT} doesn't exist"; \ + REFRESH=1; \ + fi; \ + if [ ${OGG_TIMESTAMP} -nt ${OGG_TIMESTAMP_INT} ] ; then \ + REFRESH=1; \ + fi; \ + if [ $$REFRESH -ne 0 ] ; then \ + export PATH=$$PATH:${SDKFOLDER}/platform-tools:${ANDROID_NDK}; \ + echo "changed timestamp for ogg detected building..."; \ + cd ${OGG_DIR}; \ + ndk-build NDEBUG=${NDEBUG} NDK_MODULE_PATH=${NDK_MODULE_PATH} \ + APP_ABI=${TARGET_ABI} APP_PLATFORM=${APP_PLATFORM} -j${PARALLEL} \ + TARGET_CFLAGS+="${TARGET_CFLAGS_ADDON}" \ + TARGET_LDFLAGS+="${TARGET_LDFLAGS_ADDON}" \ + TARGET_CXXFLAGS+="${TARGET_CXXFLAGS_ADDON}" || exit 1; \ + touch ${OGG_TIMESTAMP}; \ + touch ${OGG_TIMESTAMP_INT}; \ + else \ + echo "nothing to be done for libogg/libvorbis"; \ + fi + +clean_ogg : + $(RM) -rf ${OGG_DIR} + +$(OPENSSL_TIMESTAMP) : openssl_download + @LAST_MODIF=$$(find ${OPENSSL_DIR} -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -f2- -d" "); \ + if [ $$(basename $$LAST_MODIF) != "timestamp" ] ; then \ + touch ${OPENSSL_TIMESTAMP}; \ + fi + +openssl_download : + @if [ ! -d ${OPENSSL_DIR} ] ; then \ + echo "openssl sources missing, downloading..."; \ + mkdir -p ${ROOT}/deps; \ + cd ${ROOT}/deps ; \ + git clone ${OPENSSL_URL_GIT} || exit 1; \ + fi + +openssl : $(OPENSSL_LIB) + +$(OPENSSL_LIB): $(OPENSSL_TIMESTAMP) + @REFRESH=0; \ + if [ ! -e ${OPENSSL_TIMESTAMP_INT} ] ; then \ + echo "${OPENSSL_TIMESTAMP_INT} doesn't exist"; \ + REFRESH=1; \ + fi; \ + if [ ${OPENSSL_TIMESTAMP} -nt ${OPENSSL_TIMESTAMP_INT} ] ; then \ + REFRESH=1; \ + fi; \ + if [ $$REFRESH -ne 0 ] ; then \ + export PATH=$$PATH:${SDKFOLDER}/platform-tools:${ANDROID_NDK}; \ + echo "changed timestamp for openssl detected building..."; \ + cd ${OPENSSL_DIR}; \ + cat jni/Application.mk | grep -v NDK_TOOLCHAIN_VERSION >jni/Application.mk.new;\ + mv jni/Application.mk.new jni/Application.mk; \ + ndk-build NDEBUG=${NDEBUG} NDK_MODULE_PATH=${NDK_MODULE_PATH} \ + APP_ABI=${TARGET_ABI} APP_PLATFORM=${APP_PLATFORM} -j${PARALLEL} \ + TARGET_CFLAGS+="${TARGET_CFLAGS_ADDON}" \ + TARGET_LDFLAGS+="${TARGET_LDFLAGS_ADDON}" \ + TARGET_CXXFLAGS+="${TARGET_CXXFLAGS_ADDON}" || exit 1; \ + touch ${OPENSSL_TIMESTAMP}; \ + touch ${OPENSSL_TIMESTAMP_INT}; \ + else \ + echo "nothing to be done for openssl"; \ + fi + +clean_openssl : + $(RM) -rf ${OPENSSL_DIR} + +$(LEVELDB_TIMESTAMP) : leveldb_download + @LAST_MODIF=$$(find ${LEVELDB_DIR} -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -f2- -d" "); \ + if [ $$(basename $$LAST_MODIF) != "timestamp" ] ; then \ + touch ${LEVELDB_TIMESTAMP}; \ + fi + +leveldb_download : + @if [ ! -d ${LEVELDB_DIR} ] ; then \ + echo "leveldb sources missing, downloading..."; \ + mkdir -p ${ROOT}/deps; \ + cd ${ROOT}/deps ; \ + git clone ${LEVELDB_URL_GIT} || exit 1; \ + fi + +leveldb : $(LEVELDB_LIB) + +$(LEVELDB_LIB): $(LEVELDB_TIMESTAMP) + @REFRESH=0; \ + if [ ! -e ${LEVELDB_TIMESTAMP_INT} ] ; then \ + REFRESH=1; \ + fi; \ + if [ ${LEVELDB_TIMESTAMP} -nt ${LEVELDB_TIMESTAMP_INT} ] ; then \ + REFRESH=1; \ + fi; \ + if [ $$REFRESH -ne 0 ] ; then \ + export PATH=$${PATH}:${SDKFOLDER}/platform-tools:${ANDROID_NDK}; \ + echo "changed timestamp for leveldb detected building..."; \ + cd deps/leveldb; \ + export CROSS_PREFIX=${CROSS_PREFIX}; \ + export TOOLCHAIN=/tmp/ndk-arm; \ + ${ANDROID_NDK}/build/tools/make-standalone-toolchain.sh \ + --toolchain=${TARGET_TOOLCHAIN}${COMPILER_VERSION} \ + --install-dir=$${TOOLCHAIN} --system=linux-x86_64; \ + export PATH="$${TOOLCHAIN}/bin:$${PATH}"; \ + export CC=${CROSS_PREFIX}gcc; \ + export CXX=${CROSS_PREFIX}g++; \ + export CFLAGS="$${CFLAGS} ${TARGET_CFLAGS_ADDON}"; \ + export CPPFLAGS="$${CPPFLAGS} ${TARGET_CFLAGS_ADDON}"; \ + export LDFLAGS="$${LDFLAGS} ${TARGET_LDFLAGS_ADDON}"; \ + export TARGET_OS=OS_ANDROID_CROSSCOMPILE; \ + $(MAKE) -j${PARALLEL} -s || exit 1; \ + touch ${LEVELDB_TIMESTAMP}; \ + touch ${LEVELDB_TIMESTAMP_INT}; \ + else \ + echo "nothing to be done for leveldb"; \ + fi + +clean_leveldb : + $(RM) -rf deps/leveldb + +$(FREETYPE_TIMESTAMP) : freetype_download + @LAST_MODIF=$$(find ${FREETYPE_DIR} -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -f2- -d" "); \ + if [ $$(basename $$LAST_MODIF) != "timestamp" ] ; then \ + touch ${FREETYPE_TIMESTAMP}; \ + fi + +freetype_download : + @if [ ! -d ${FREETYPE_DIR} ] ; then \ + echo "freetype sources missing, downloading..."; \ + mkdir -p ${ROOT}/deps; \ + cd deps; \ + git clone ${FREETYPE_URL_GIT} || exit 1; \ + fi + +$(IRRLICHT_TIMESTAMP) : irrlicht_download + @LAST_MODIF=$$(find ${IRRLICHT_DIR} -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -f2- -d" "); \ + if [ $$(basename $$LAST_MODIF) != "timestamp" ] ; then \ + touch ${IRRLICHT_TIMESTAMP}; \ + fi + +freetype : $(FREETYPE_LIB) + +$(FREETYPE_LIB) : $(FREETYPE_TIMESTAMP) + @REFRESH=0; \ + if [ ! -e ${FREETYPE_TIMESTAMP_INT} ] ; then \ + REFRESH=1; \ + fi; \ + if [ ! -e ${FREETYPE_LIB} ] ; then \ + REFRESH=1; \ + fi; \ + if [ ${FREETYPE_TIMESTAMP} -nt ${FREETYPE_TIMESTAMP_INT} ] ; then \ + REFRESH=1; \ + fi; \ + if [ $$REFRESH -ne 0 ] ; then \ + mkdir -p ${FREETYPE_DIR}; \ + export PATH=$$PATH:${SDKFOLDER}/platform-tools:${ANDROID_NDK}; \ + echo "changed timestamp for freetype detected building..."; \ + cd ${FREETYPE_DIR}/Android/jni; \ + ndk-build NDEBUG=${NDEBUG} NDK_MODULE_PATH=${NDK_MODULE_PATH} \ + APP_PLATFORM=${APP_PLATFORM} APP_ABI=${TARGET_ABI} -j${PARALLEL} \ + TARGET_CFLAGS+="${TARGET_CFLAGS_ADDON}" \ + TARGET_LDFLAGS+="${TARGET_LDFLAGS_ADDON}" \ + TARGET_CXXFLAGS+="${TARGET_CXXFLAGS_ADDON}" || exit 1; \ + touch ${FREETYPE_TIMESTAMP}; \ + touch ${FREETYPE_TIMESTAMP_INT}; \ + else \ + echo "nothing to be done for freetype"; \ + fi + +clean_freetype : + $(RM) -rf ${FREETYPE_DIR} + +#Note: Texturehack patch is required for gpu's not supporting color format +# correctly. Known bad GPU: +# -geforce on emulator +# -Vivante Corporation GC1000 core (e.g. Galaxy Tab 3) + +irrlicht_download : + @if [ ! -d "deps/irrlicht" ] ; then \ + echo "irrlicht sources missing, downloading..."; \ + mkdir -p ${ROOT}/deps; \ + cd deps; \ + svn co ${IRRLICHT_URL_SVN} irrlicht || exit 1; \ + cd irrlicht; \ + patch -p1 < ../../irrlicht-touchcount.patch || exit 1; \ + patch -p1 < ../../irrlicht-back_button.patch || exit 1; \ + patch -p1 < ../../irrlicht-texturehack.patch || exit 1; \ + fi + +irrlicht : $(IRRLICHT_LIB) + +$(IRRLICHT_LIB): $(IRRLICHT_TIMESTAMP) $(FREETYPE_LIB) + @REFRESH=0; \ + if [ ! -e ${IRRLICHT_TIMESTAMP_INT} ] ; then \ + REFRESH=1; \ + fi; \ + if [ ! -e ${IRRLICHT_LIB} ] ; then \ + REFRESH=1; \ + fi; \ + if [ ${IRRLICHT_TIMESTAMP} -nt ${IRRLICHT_TIMESTAMP_INT} ] ; then \ + REFRESH=1; \ + fi; \ + if [ $$REFRESH -ne 0 ] ; then \ + mkdir -p ${IRRLICHT_DIR}; \ + export PATH=$$PATH:${SDKFOLDER}/platform-tools:${ANDROID_NDK}; \ + echo "changed timestamp for irrlicht detected building..."; \ + cd deps/irrlicht/source/Irrlicht/Android; \ + ndk-build NDEBUG=${NDEBUG} NDK_MODULE_PATH=${NDK_MODULE_PATH} \ + APP_ABI=${TARGET_ABI} APP_PLATFORM=${APP_PLATFORM} -j${PARALLEL} \ + TARGET_CFLAGS+="${TARGET_CFLAGS_ADDON}" \ + TARGET_LDFLAGS+="${TARGET_LDFLAGS_ADDON}" \ + TARGET_CXXFLAGS+="${TARGET_CXXFLAGS_ADDON}" || exit 1; \ + touch ${IRRLICHT_TIMESTAMP}; \ + touch ${IRRLICHT_TIMESTAMP_INT}; \ + else \ + echo "nothing to be done for irrlicht"; \ + fi + +clean_irrlicht : + $(RM) -rf deps/irrlicht + +$(CURL_TIMESTAMP) : curl_download + @LAST_MODIF=$$(find ${CURL_DIR} -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -f2- -d" "); \ + if [ $$(basename $$LAST_MODIF) != "timestamp" ] ; then \ + touch ${CURL_TIMESTAMP}; \ + fi + +curl_download : + @if [ ! -d "deps/curl-${CURL_VERSION}" ] ; then \ + echo "curl sources missing, downloading..."; \ + mkdir -p ${ROOT}/deps; \ + cd deps; \ + wget ${CURL_URL_HTTP} || exit 1; \ + tar -xjf curl-${CURL_VERSION}.tar.bz2 || exit 1; \ + rm curl-${CURL_VERSION}.tar.bz2; \ + fi + +curl : $(CURL_LIB) + +$(CURL_LIB): $(CURL_TIMESTAMP) $(OPENSSL_LIB) + @REFRESH=0; \ + if [ ! -e ${CURL_TIMESTAMP_INT} ] ; then \ + REFRESH=1; \ + fi; \ + if [ ! -e ${CURL_LIB} ] ; then \ + REFRESH=1; \ + fi; \ + if [ ${CURL_TIMESTAMP} -nt ${CURL_TIMESTAMP_INT} ] ; then \ + REFRESH=1; \ + fi; \ + if [ $$REFRESH -ne 0 ] ; then \ + mkdir -p ${CURL_DIR}; \ + export PATH="$${PATH}:${SDKFOLDER}/platform-tools:${ANDROID_NDK}"; \ + echo "changed timestamp for curl detected building..."; \ + cd deps/curl-${CURL_VERSION}; \ + export CROSS_PREFIX=${CROSS_PREFIX}; \ + export TOOLCHAIN=/tmp/ndk-arm; \ + ${ANDROID_NDK}/build/tools/make-standalone-toolchain.sh \ + --toolchain=${TARGET_TOOLCHAIN}${COMPILER_VERSION} \ + --install-dir=$${TOOLCHAIN} --system=linux-x86_64; \ + export PATH="$${TOOLCHAIN}/bin:$${PATH}"; \ + export CC=${CROSS_PREFIX}gcc; \ + export CXX=${CROSS_PREFIX}g++; \ + export TARGET_OS=OS_ANDROID_CROSSCOMPILE; \ + export CPPFLAGS="$${CPPFLAGS} -I${OPENSSL_DIR}/include \ + -L${OPENSSL_DIR}/libs/${TARGET_ABI}/ ${TARGET_CFLAGS_ADDON}"; \ + export CFLAGS="$${CFLAGS} ${TARGET_CFLAGS_ADDON}"; \ + export LDFLAGS="$${LDFLAGS} -L${OPENSSL_DIR}/libs/${TARGET_ABI}/ \ + ${TARGET_LDFLAGS_ADDON}"; \ + ./configure --host=${TARGET_HOST} --disable-shared --enable-static --with-ssl; \ + $(MAKE) -j${PARALLEL} -s || exit 1; \ + touch ${CURL_TIMESTAMP}; \ + touch ${CURL_TIMESTAMP_INT}; \ + else \ + echo "nothing to be done for curl"; \ + fi + +clean_curl : + $(RM) -rf deps/curl-${CURL_VERSION} + + +curl_binary: + @if [ ! -d "deps/curl-${CURL_VERSION_BINARY}" ] ; then \ + echo "curl sources missing, downloading..."; \ + mkdir -p ${ROOT}/deps; \ + cd deps; \ + wget http://curl.haxx.se/gknw.net/7.34.0/dist-android/curl-7.34.0-rtmp-ssh2-ssl-zlib-static-bin-android.tar.gz || exit 1;\ + tar -xzf curl-7.34.0-rtmp-ssh2-ssl-zlib-static-bin-android.tar.gz || exit 1;\ + mv curl-7.34.0-rtmp-ssh2-ssl-zlib-static-bin-android curl-${CURL_VERSION_BINARY};\ + rm curl-7.34.0-rtmp-ssh2-ssl-zlib-static-bin-android.tar.gz; \ + fi + +$(ASSETS_TIMESTAMP) : $(IRRLICHT_LIB) + @mkdir -p ${ROOT}/deps; \ + LAST_MODIF=$$(find ${ROOT}/../../builtin -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -f2- -d" "); \ + if [ $$(basename $$LAST_MODIF) != "timestamp" ] ; then \ + touch ${ROOT}/../../builtin/timestamp; \ + touch ${ASSETS_TIMESTAMP}; \ + echo builtin changed $$LAST_MODIF; \ + fi; \ + LAST_MODIF=$$(find ${ROOT}/../../client -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -f2- -d" "); \ + if [ $$(basename $$LAST_MODIF) != "timestamp" ] ; then \ + touch ${ROOT}/../../client/timestamp; \ + touch ${ASSETS_TIMESTAMP}; \ + fi; \ + LAST_MODIF=$$(find ${ROOT}/../../doc -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -f2- -d" "); \ + if [ $$(basename $$LAST_MODIF) != "timestamp" ] ; then \ + touch ${ROOT}/../../doc/timestamp; \ + touch ${ASSETS_TIMESTAMP}; \ + fi; \ + LAST_MODIF=$$(find ${ROOT}/../../fonts -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -f2- -d" "); \ + if [ $$(basename $$LAST_MODIF) != "timestamp" ] ; then \ + touch ${ROOT}/../../fonts/timestamp; \ + touch ${ASSETS_TIMESTAMP}; \ + fi; \ + LAST_MODIF=$$(find ${ROOT}/../../games -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -f2- -d" "); \ + if [ $$(basename $$LAST_MODIF) != "timestamp" ] ; then \ + touch ${ROOT}/../../games/timestamp; \ + touch ${ASSETS_TIMESTAMP}; \ + fi; \ + LAST_MODIF=$$(find ${ROOT}/../../mods -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -f2- -d" "); \ + if [ $$(basename $$LAST_MODIF) != "timestamp" ] ; then \ + touch ${ROOT}/../../mods/timestamp; \ + touch ${ASSETS_TIMESTAMP}; \ + fi; \ + LAST_MODIF=$$(find ${ROOT}/../../po -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -f2- -d" "); \ + if [ $$(basename $$LAST_MODIF) != "timestamp" ] ; then \ + touch ${ROOT}/../../po/timestamp; \ + touch ${ASSETS_TIMESTAMP}; \ + fi; \ + LAST_MODIF=$$(find ${ROOT}/../../textures -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -f2- -d" "); \ + if [ $$(basename $$LAST_MODIF) != "timestamp" ] ; then \ + touch ${ROOT}/../../textures/timestamp; \ + touch ${ASSETS_TIMESTAMP}; \ + fi; \ + LAST_MODIF=$$(find ${IRRLICHT_DIR}/media -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -f2- -d" "); \ + if [ $$(basename $$LAST_MODIF) != "timestamp" ] ; then \ + touch ${IRRLICHT_DIR}/media/timestamp; \ + touch ${ASSETS_TIMESTAMP}; \ + fi; \ + if [ ${ROOT}/../../minetest.conf.example -nt ${ASSETS_TIMESTAMP} ] ; then \ + echo "conf changed"; \ + touch ${ASSETS_TIMESTAMP}; \ + fi; \ + if [ ${ROOT}/../../README.txt -nt ${ASSETS_TIMESTAMP} ] ; then \ + touch ${ASSETS_TIMESTAMP}; \ + fi; \ + if [ ! -e $(ASSETS_TIMESTAMP) ] ; then \ + touch $(ASSETS_TIMESTAMP); \ + fi + +assets : $(ASSETS_TIMESTAMP) + @REFRESH=0; \ + if [ ! -e ${ASSETS_TIMESTAMP}.old ] ; then \ + REFRESH=1; \ + fi; \ + if [ ${ASSETS_TIMESTAMP} -nt ${ASSETS_TIMESTAMP}.old ] ; then \ + REFRESH=1; \ + fi; \ + if [ ! -d ${ROOT}/assets ] ; then \ + REFRESH=1; \ + fi; \ + if [ $$REFRESH -ne 0 ] ; then \ + echo "assets changed, refreshing..."; \ + $(MAKE) -j${PARALLEL} clean_assets; \ + mkdir -p ${ROOT}/assets/Minetest; \ + cp ${ROOT}/../../minetest.conf.example ${ROOT}/assets/Minetest; \ + cp ${ROOT}/../../README.txt ${ROOT}/assets/Minetest; \ + cp -r ${ROOT}/../../builtin ${ROOT}/assets/Minetest; \ + cp -r ${ROOT}/../../client ${ROOT}/assets/Minetest; \ + cp -r ${ROOT}/../../doc ${ROOT}/assets/Minetest; \ + cp -r ${ROOT}/../../fonts ${ROOT}/assets/Minetest; \ + cp -r ${ROOT}/../../games ${ROOT}/assets/Minetest; \ + cp -r ${ROOT}/../../mods ${ROOT}/assets/Minetest; \ + cp -r ${ROOT}/../../po ${ROOT}/assets/Minetest; \ + cp -r ${ROOT}/../../textures ${ROOT}/assets/Minetest; \ + mkdir -p ${ROOT}/assets/Minetest/media; \ + cp -r ${IRRLICHT_DIR}/media/Shaders ${ROOT}/assets/Minetest/media; \ + cd ${ROOT}/assets; \ + find . -name "timestamp" -exec rm {} \; ; \ + find . -name "*.blend" -exec rm {} \; ; \ + ls -R | grep ":$$" | sed -e 's/:$$//' -e 's/\.//' -e 's/^\///' > "index.txt"; \ + cp ${ROOT}/${ASSETS_TIMESTAMP} ${ROOT}/${ASSETS_TIMESTAMP}.old; \ + else \ + echo "nothing to be done for assets"; \ + fi + +clean_assets : + @$(RM) -r assets + +apk: $(PATHCFGFILE) assets $(IRRLICHT_LIB) $(CURL_LIB) \ + $(OPENAL_LIB) $(OGG_LIB) prep_srcdir $(ROOT)/jni/src/android_version.h + @export NDEBUG=$$NDEBUG; $(MAKE) -j${PARALLEL} manifest; \ + export PATH=$$PATH:${SDKFOLDER}/platform-tools:${ANDROID_NDK}; \ + export ANDROID_HOME=${SDKFOLDER}; \ + mkdir -p ${ROOT}/src; \ + ndk-build NDK_MODULE_PATH=${NDK_MODULE_PATH} -j${PARALLEL} \ + GPROF=${GPROF} APP_ABI=${TARGET_ABI} \ + APP_PLATFORM=${APP_PLATFORM} \ + TARGET_LIBDIR=${TARGET_LIBDIR} \ + TARGET_CFLAGS+="${TARGET_CFLAGS_ADDON}" \ + TARGET_LDFLAGS+="${TARGET_LDFLAGS_ADDON}" \ + TARGET_CXXFLAGS+="${TARGET_CXXFLAGS_ADDON}" && \ + ant $$BUILD_TYPE && \ + echo "++ Success!" && \ + echo "APK: bin/Minetest-$$BUILD_TYPE.apk" && \ + echo "You can install it with \`adb install -r bin/Minetest-$$BUILD_TYPE.apk\`" + +prep_srcdir : + @rm ${ROOT}/jni/src; \ + ln -s ${ROOT}/../../src ${ROOT}/jni/src + +clean_apk : manifest + @export PATH=$$PATH:${SDKFOLDER}platform-tools:${ANDROID_NDK}; \ + export ANDROID_HOME=${SDKFOLDER}; \ + ant clean + +install_debug : + @export PATH=$$PATH:${SDKFOLDER}platform-tools:${ANDROID_NDK}; \ + adb install -r ${ROOT}/bin/Minetest-debug.apk + +install : + @export PATH=$$PATH:${SDKFOLDER}platform-tools:${ANDROID_NDK}; \ + adb install -r ${ROOT}/bin/Minetest-release.apk + +envpaths : + @echo "export PATH=$$PATH:${SDKFOLDER}platform-tools:${ANDROID_NDK}" > and_env;\ + echo "export ANDROID_HOME=${SDKFOLDER}" >> and_env; + +clean_all : + @$(MAKE) -j${PARALLEL} clean_apk; \ + $(MAKE) clean_assets clean_irrlicht clean_leveldb clean_curl clean_openssl \ + clean_openal clean_ogg clean_manifest; \ + sleep 1; \ + $(RM) -r gen libs obj deps bin Debug and_env + +$(ROOT)/jni/src/android_version.h : + @echo "#define STR_HELPER(x) #x" \ + >${ROOT}/jni/src/android_version.h; \ + echo "#define STR(x) STR_HELPER(x)" \ + >> ${ROOT}/jni/src/android_version.h; \ + echo "#define VERSION_MAJOR $$(cat ${ROOT}/../../CMakeLists.txt | \ + grep ^set\(VERSION_MAJOR\ | sed 's/)/ /' | awk '{print $$2;}')" \ + >> ${ROOT}/jni/src/android_version.h; \ + echo "#define VERSION_MINOR $$(cat ${ROOT}/../../CMakeLists.txt | \ + grep ^set\(VERSION_MINOR\ | sed 's/)/ /' | awk '{print $$2;}')" \ + >> ${ROOT}/jni/src/android_version.h; \ + echo "#define VERSION_PATCH $$(cat ${ROOT}/../../CMakeLists.txt | \ + grep ^set\(VERSION_PATCH\ | sed 's/)/ /' | awk '{print $$2;}')" \ + >> ${ROOT}/jni/src/android_version.h; \ + echo "#define VERSION_PATCH_ORIG $$(cat ${ROOT}/../../CMakeLists.txt | \ + grep ^set\(VERSION_PATCH\ | sed 's/)/ /' | awk '{print $$2;}')" \ + >> ${ROOT}/jni/src/android_version.h; \ + echo "#define CMAKE_VERSION_GITHASH \"$$(git rev-parse --short=8 HEAD)\"" \ + >> ${ROOT}/jni/src/android_version.h; \ + echo "#define CMAKE_VERSION_STRING STR(VERSION_MAJOR)\".\"STR(VERSION_MINOR)\ + \".\"STR(VERSION_PATCH)" \ + >> ${ROOT}/jni/src/android_version.h; + +manifest : + @VERS_MAJOR=$$(cat ${ROOT}/../../CMakeLists.txt | \ + grep ^set\(VERSION_MAJOR\ | sed 's/)/ /' | awk '{print $$2;}'); \ + VERS_MINOR=$$(cat ${ROOT}/../../CMakeLists.txt | \ + grep ^set\(VERSION_MINOR\ | sed 's/)/ /' | awk '{print $$2;}'); \ + VERS_PATCH=$$(cat ${ROOT}/../../CMakeLists.txt | \ + grep ^set\(VERSION_PATCH\ | sed 's/)/ /' | awk '{print $$2;}'); \ + BASE_VERSION="$$VERS_MAJOR.$$VERS_MINOR.$$VERS_PATCH"; \ + if [ "${NDEBUG}x" != "x" ] ; then \ + DBG=''; \ + DBG_FLAG="android:debuggable=\"false\""; \ + else \ + DBG="<uses-permission android:name=\"android.permission.SET_DEBUG_APP\" />"; \ + DBG_FLAG="android:debuggable=\"true\""; \ + fi; \ + cat ${ROOT}/AndroidManifest.xml.template | \ + sed s/###ANDROID_VERSION###/${ANDROID_VERSION_CODE}/g | \ + sed s/###BASE_VERSION###/$$BASE_VERSION/g | \ + sed -e "s@###DEBUG_BUILD###@$$DBG@g" | \ + sed -e "s@###DEBUG_FLAG###@$$DBG_FLAG@g" >${ROOT}/AndroidManifest.xml + +clean_manifest : + rm -rf ${ROOT}/AndroidManifest.xml + +clean : clean_apk clean_assets diff --git a/build/android/build.xml b/build/android/build.xml new file mode 100644 index 000000000..50a3e95ac --- /dev/null +++ b/build/android/build.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project name="Minetest" default="help"> + <property file="local.properties" /> + <property file="ant.properties" /> + <property environment="env" /> + <condition property="sdk.dir" value="${env.ANDROID_HOME}"> + <isset property="env.ANDROID_HOME" /> + </condition> + <loadproperties srcFile="project.properties" /> + <fail + message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable." + unless="sdk.dir" + /> + <import file="custom_rules.xml" optional="true" /> + <import file="${sdk.dir}/tools/ant/build.xml" /> +</project> diff --git a/build/android/irrlicht-back_button.patch b/build/android/irrlicht-back_button.patch new file mode 100644 index 000000000..227749ba7 --- /dev/null +++ b/build/android/irrlicht-back_button.patch @@ -0,0 +1,19 @@ +--- irrlicht/source/Irrlicht/Android/CIrrDeviceAndroid.cpp 2014-06-03 20:56:21.289559503 +0200 ++++ irrlicht/source/Irrlicht/Android/CIrrDeviceAndroid.cpp.orig 2014-06-03 20:57:39.281556749 +0200 +@@ -423,6 +423,7 @@ + }
+
+ device->postEventFromUser(event);
++ status = 1;
+ }
+ break;
+ default:
+@@ -479,7 +480,7 @@ + KeyMap[1] = KEY_LBUTTON; // AKEYCODE_SOFT_LEFT
+ KeyMap[2] = KEY_RBUTTON; // AKEYCODE_SOFT_RIGHT
+ KeyMap[3] = KEY_HOME; // AKEYCODE_HOME
+- KeyMap[4] = KEY_BACK; // AKEYCODE_BACK
++ KeyMap[4] = KEY_CANCEL; // AKEYCODE_BACK
+ KeyMap[5] = KEY_UNKNOWN; // AKEYCODE_CALL
+ KeyMap[6] = KEY_UNKNOWN; // AKEYCODE_ENDCALL
+ KeyMap[7] = KEY_KEY_0; // AKEYCODE_0
diff --git a/build/android/irrlicht-texturehack.patch b/build/android/irrlicht-texturehack.patch new file mode 100644 index 000000000..a458ede72 --- /dev/null +++ b/build/android/irrlicht-texturehack.patch @@ -0,0 +1,240 @@ +--- irrlicht/source/Irrlicht/COGLESTexture.cpp.orig 2014-06-22 17:01:13.266568869 +0200 ++++ irrlicht/source/Irrlicht/COGLESTexture.cpp 2014-06-22 17:03:59.298572810 +0200 +@@ -366,112 +366,140 @@ + void(*convert)(const void*, s32, void*) = 0;
+ getFormatParameters(ColorFormat, InternalFormat, filtering, PixelFormat, PixelType, convert);
+
+- // make sure we don't change the internal format of existing images
+- if (!newTexture)
+- InternalFormat = oldInternalFormat;
+-
+- Driver->setActiveTexture(0, this);
+-
+- if (Driver->testGLError())
+- os::Printer::log("Could not bind Texture", ELL_ERROR);
+-
+- // mipmap handling for main texture
+- if (!level && newTexture)
+- {
+- // auto generate if possible and no mipmap data is given
+- if (!IsCompressed && HasMipMaps && !mipmapData && Driver->queryFeature(EVDF_MIP_MAP_AUTO_UPDATE))
+- {
+- if (Driver->getTextureCreationFlag(ETCF_OPTIMIZED_FOR_SPEED))
+- glHint(GL_GENERATE_MIPMAP_HINT, GL_FASTEST);
+- else if (Driver->getTextureCreationFlag(ETCF_OPTIMIZED_FOR_QUALITY))
+- glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST);
+- else
+- glHint(GL_GENERATE_MIPMAP_HINT, GL_DONT_CARE);
++ bool retry = false;
++
++ do {
++ if (retry) {
++ InternalFormat = GL_RGBA;
++ PixelFormat = GL_RGBA;
++ convert = CColorConverter::convert_A8R8G8B8toA8B8G8R8;
++ }
++ // make sure we don't change the internal format of existing images
++ if (!newTexture)
++ InternalFormat = oldInternalFormat;
+
+- glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
+- AutomaticMipmapUpdate=true;
+- }
++ Driver->setActiveTexture(0, this);
+
+- // enable bilinear filter without mipmaps
+- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filtering);
+- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering);
+- }
++ if (Driver->testGLError())
++ os::Printer::log("Could not bind Texture", ELL_ERROR);
+
+- // now get image data and upload to GPU
++ // mipmap handling for main texture
++ if (!level && newTexture)
++ {
++ // auto generate if possible and no mipmap data is given
++ if (!IsCompressed && HasMipMaps && !mipmapData && Driver->queryFeature(EVDF_MIP_MAP_AUTO_UPDATE))
++ {
++ if (Driver->getTextureCreationFlag(ETCF_OPTIMIZED_FOR_SPEED))
++ glHint(GL_GENERATE_MIPMAP_HINT, GL_FASTEST);
++ else if (Driver->getTextureCreationFlag(ETCF_OPTIMIZED_FOR_QUALITY))
++ glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST);
++ else
++ glHint(GL_GENERATE_MIPMAP_HINT, GL_DONT_CARE);
++
++ glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
++ AutomaticMipmapUpdate=true;
++ }
++
++ // enable bilinear filter without mipmaps
++ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filtering);
++ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering);
++ }
+
+- u32 compressedImageSize = IImage::getCompressedImageSize(ColorFormat, image->getDimension().Width, image->getDimension().Height);
++ // now get image data and upload to GPU
+
+- void* source = image->lock();
++ u32 compressedImageSize = IImage::getCompressedImageSize(ColorFormat, image->getDimension().Width, image->getDimension().Height);
+
+- IImage* tmpImage = 0;
++ void* source = image->lock();
+
+- if (convert)
+- {
+- tmpImage = new CImage(image->getColorFormat(), image->getDimension());
+- void* dest = tmpImage->lock();
+- convert(source, image->getDimension().getArea(), dest);
+- image->unlock();
+- source = dest;
+- }
++ IImage* tmpImage = 0;
+
+- if (newTexture)
+- {
+- if (IsCompressed)
++ if (convert)
+ {
+- glCompressedTexImage2D(GL_TEXTURE_2D, 0, InternalFormat, image->getDimension().Width,
+- image->getDimension().Height, 0, compressedImageSize, source);
++ tmpImage = new CImage(image->getColorFormat(), image->getDimension());
++ void* dest = tmpImage->lock();
++ convert(source, image->getDimension().getArea(), dest);
++ image->unlock();
++ source = dest;
+ }
+- else
+- glTexImage2D(GL_TEXTURE_2D, level, InternalFormat, image->getDimension().Width,
+- image->getDimension().Height, 0, PixelFormat, PixelType, source);
+- }
+- else
+- {
+- if (IsCompressed)
++
++ if (newTexture)
+ {
+- glCompressedTexSubImage2D(GL_TEXTURE_2D, level, 0, 0, image->getDimension().Width,
+- image->getDimension().Height, PixelFormat, compressedImageSize, source);
++ if (IsCompressed)
++ {
++ glCompressedTexImage2D(GL_TEXTURE_2D, 0, InternalFormat, image->getDimension().Width,
++ image->getDimension().Height, 0, compressedImageSize, source);
++ }
++ else
++ glTexImage2D(GL_TEXTURE_2D, level, InternalFormat, image->getDimension().Width,
++ image->getDimension().Height, 0, PixelFormat, PixelType, source);
+ }
+ else
+- glTexSubImage2D(GL_TEXTURE_2D, level, 0, 0, image->getDimension().Width,
+- image->getDimension().Height, PixelFormat, PixelType, source);
+- }
+-
+- if (convert)
+- {
+- tmpImage->unlock();
+- tmpImage->drop();
+- }
+- else
+- image->unlock();
+-
+- if (!level && newTexture)
+- {
+- if (IsCompressed && !mipmapData)
+ {
+- if (image->hasMipMaps())
+- mipmapData = static_cast<u8*>(image->lock())+compressedImageSize;
++ if (IsCompressed)
++ {
++ glCompressedTexSubImage2D(GL_TEXTURE_2D, level, 0, 0, image->getDimension().Width,
++ image->getDimension().Height, PixelFormat, compressedImageSize, source);
++ }
+ else
+- HasMipMaps = false;
++ glTexSubImage2D(GL_TEXTURE_2D, level, 0, 0, image->getDimension().Width,
++ image->getDimension().Height, PixelFormat, PixelType, source);
+ }
+
+- regenerateMipMapLevels(mipmapData);
+-
+- if (HasMipMaps) // might have changed in regenerateMipMapLevels
++ if (convert)
+ {
+- // enable bilinear mipmap filter
+- GLint filteringMipMaps = GL_LINEAR_MIPMAP_NEAREST;
+-
+- if (filtering != GL_LINEAR)
+- filteringMipMaps = GL_NEAREST_MIPMAP_NEAREST;
++ tmpImage->unlock();
++ tmpImage->drop();
++ }
++ else
++ image->unlock();
++
++ if (glGetError() != GL_NO_ERROR) {
++ static bool warned = false;
++ if ((!retry) && (ColorFormat == ECF_A8R8G8B8)) {
++
++ if (!warned) {
++ os::Printer::log("Your driver claims to support GL_BGRA but fails on trying to upload a texture, converting to GL_RGBA and trying again", ELL_ERROR);
++ warned = true;
++ }
++ }
++ else if (retry) {
++ os::Printer::log("Neither uploading texture as GL_BGRA nor, converted one using GL_RGBA succeeded", ELL_ERROR);
++ }
++ retry = !retry;
++ continue;
++ } else {
++ retry = false;
++ }
+
+- glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filteringMipMaps);
+- glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering);
++ if (!level && newTexture)
++ {
++ if (IsCompressed && !mipmapData)
++ {
++ if (image->hasMipMaps())
++ mipmapData = static_cast<u8*>(image->lock())+compressedImageSize;
++ else
++ HasMipMaps = false;
++ }
++
++ regenerateMipMapLevels(mipmapData);
++
++ if (HasMipMaps) // might have changed in regenerateMipMapLevels
++ {
++ // enable bilinear mipmap filter
++ GLint filteringMipMaps = GL_LINEAR_MIPMAP_NEAREST;
++
++ if (filtering != GL_LINEAR)
++ filteringMipMaps = GL_NEAREST_MIPMAP_NEAREST;
++
++ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filteringMipMaps);
++ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering);
++ }
+ }
+- }
+
+- if (Driver->testGLError())
+- os::Printer::log("Could not glTexImage2D", ELL_ERROR);
++ if (Driver->testGLError())
++ os::Printer::log("Could not glTexImage2D", ELL_ERROR);
++ }
++ while(retry);
+ }
+
+
+--- irrlicht/source/Irrlicht/COGLESTexture.cpp.orig 2014-06-25 00:28:50.820501856 +0200 ++++ irrlicht/source/Irrlicht/COGLESTexture.cpp 2014-06-25 00:08:37.712544692 +0200 +@@ -422,6 +422,9 @@ + source = dest;
+ }
+
++ //clear old error
++ glGetError();
++
+ if (newTexture)
+ {
+ if (IsCompressed)
diff --git a/build/android/irrlicht-touchcount.patch b/build/android/irrlicht-touchcount.patch new file mode 100644 index 000000000..d4e4b9c3e --- /dev/null +++ b/build/android/irrlicht-touchcount.patch @@ -0,0 +1,30 @@ +--- irrlicht.orig/include/IEventReceiver.h 2014-06-03 19:43:50.433713133 +0200 ++++ irrlicht/include/IEventReceiver.h 2014-06-03 19:44:36.993711489 +0200 +@@ -375,6 +375,9 @@ + // Y position of simple touch.
+ s32 Y;
+
++ // number of current touches
++ s32 touchedCount;
++
+ //! Type of touch event.
+ ETOUCH_INPUT_EVENT Event;
+ };
+--- irrlicht.orig/source/Irrlicht/Android/CIrrDeviceAndroid.cpp 2014-06-03 19:43:50.505713130 +0200 ++++ irrlicht/source/Irrlicht/Android/CIrrDeviceAndroid.cpp 2014-06-03 19:45:37.265709359 +0200 +@@ -315,6 +315,7 @@ + event.TouchInput.ID = AMotionEvent_getPointerId(androidEvent, i);
+ event.TouchInput.X = AMotionEvent_getX(androidEvent, i);
+ event.TouchInput.Y = AMotionEvent_getY(androidEvent, i);
++ event.TouchInput.touchedCount = AMotionEvent_getPointerCount(androidEvent);
+
+ device->postEventFromUser(event);
+ }
+@@ -326,6 +327,7 @@ + event.TouchInput.ID = AMotionEvent_getPointerId(androidEvent, pointerIndex);
+ event.TouchInput.X = AMotionEvent_getX(androidEvent, pointerIndex);
+ event.TouchInput.Y = AMotionEvent_getY(androidEvent, pointerIndex);
++ event.TouchInput.touchedCount = AMotionEvent_getPointerCount(androidEvent);
+
+ device->postEventFromUser(event);
+ }
diff --git a/build/android/jni/Android.mk b/build/android/jni/Android.mk new file mode 100644 index 000000000..3559c2b6d --- /dev/null +++ b/build/android/jni/Android.mk @@ -0,0 +1,310 @@ +LOCAL_PATH := $(call my-dir)/.. + +#LOCAL_ADDRESS_SANITIZER:=true + +include $(CLEAR_VARS) +LOCAL_MODULE := Irrlicht +LOCAL_SRC_FILES := deps/irrlicht/lib/Android/libIrrlicht.a +include $(PREBUILT_STATIC_LIBRARY) + +#include $(CLEAR_VARS) +#LOCAL_MODULE := LevelDB +#LOCAL_SRC_FILES := deps/leveldb/libleveldb.a +#include $(PREBUILT_STATIC_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := curl +LOCAL_SRC_FILES := deps/curl-7.35.0/lib/.libs/libcurl.a +include $(PREBUILT_STATIC_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := freetype +LOCAL_SRC_FILES := deps/freetype2-android/Android/obj/local/$(TARGET_ARCH_ABI)/libfreetype2-static.a +include $(PREBUILT_STATIC_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := openal +LOCAL_SRC_FILES := deps/openal-soft/libs/$(TARGET_LIBDIR)/libopenal.so +include $(PREBUILT_SHARED_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := ogg +LOCAL_SRC_FILES := deps/libvorbis-libogg-android/libs/$(TARGET_LIBDIR)/libogg.so +include $(PREBUILT_SHARED_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := vorbis +LOCAL_SRC_FILES := deps/libvorbis-libogg-android/libs/$(TARGET_LIBDIR)/libvorbis.so +include $(PREBUILT_SHARED_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := ssl +LOCAL_SRC_FILES := deps/openssl-android/libs/$(TARGET_LIBDIR)/libssl.so +include $(PREBUILT_SHARED_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := crypto +LOCAL_SRC_FILES := deps/openssl-android/libs/$(TARGET_LIBDIR)/libcrypto.so +include $(PREBUILT_SHARED_LIBRARY) + + +include $(CLEAR_VARS) +LOCAL_MODULE := minetest + +LOCAL_CPP_FEATURES += exceptions + +ifdef GPROF +GPROF_DEF=-DGPROF +endif + +LOCAL_CFLAGS := -D_IRR_ANDROID_PLATFORM_ \ + -DHAVE_TOUCHSCREENGUI \ + -DUSE_CURL=1 \ + -DUSE_SOUND=1 \ + -DUSE_FREETYPE=1 \ + $(GPROF_DEF) \ + -pipe -fstrict-aliasing + +ifndef NDEBUG +LOCAL_CFLAGS += -g -D_DEBUG -O0 -fno-omit-frame-pointer +else +LOCAL_CFLAGS += -fexpensive-optimizations -O3 +endif + +ifdef GPROF +PROFILER_LIBS := android-ndk-profiler +LOCAL_CFLAGS += -pg +endif + +# LOCAL_CFLAGS += -fsanitize=address +# LOCAL_LDFLAGS += -fsanitize=address + +ifeq ($(TARGET_ARCH_ABI),x86) +LOCAL_CFLAGS += -fno-stack-protector +endif + +LOCAL_C_INCLUDES := \ + jni/src jni/src/sqlite \ + jni/src/script \ + jni/src/lua/src \ + jni/src/json \ + jni/src/cguittfont \ + deps/irrlicht/include \ + deps/freetype2-android/include \ + deps/curl-7.35.0/include \ + deps/openal-soft/jni/OpenAL/include \ + deps/libvorbis-libogg-android/jni/include + +# deps/leveldb/include \ + +LOCAL_SRC_FILES := \ + jni/src/ban.cpp \ + jni/src/base64.cpp \ + jni/src/biome.cpp \ + jni/src/camera.cpp \ + jni/src/cavegen.cpp \ + jni/src/chat.cpp \ + jni/src/client.cpp \ + jni/src/clientiface.cpp \ + jni/src/clientmap.cpp \ + jni/src/clientmedia.cpp \ + jni/src/clientobject.cpp \ + jni/src/clouds.cpp \ + jni/src/collision.cpp \ + jni/src/connection.cpp \ + jni/src/content_abm.cpp \ + jni/src/content_cao.cpp \ + jni/src/content_cso.cpp \ + jni/src/content_mapblock.cpp \ + jni/src/content_mapnode.cpp \ + jni/src/content_nodemeta.cpp \ + jni/src/content_sao.cpp \ + jni/src/convert_json.cpp \ + jni/src/craftdef.cpp \ + jni/src/database-dummy.cpp \ + jni/src/database-sqlite3.cpp \ + jni/src/database.cpp \ + jni/src/debug.cpp \ + jni/src/defaultsettings.cpp \ + jni/src/drawscene.cpp \ + jni/src/dungeongen.cpp \ + jni/src/emerge.cpp \ + jni/src/environment.cpp \ + jni/src/filecache.cpp \ + jni/src/filesys.cpp \ + jni/src/game.cpp \ + jni/src/genericobject.cpp \ + jni/src/gettext.cpp \ + jni/src/guiChatConsole.cpp \ + jni/src/guiEngine.cpp \ + jni/src/guiFileSelectMenu.cpp \ + jni/src/guiFormSpecMenu.cpp \ + jni/src/guiKeyChangeMenu.cpp \ + jni/src/guiPasswordChange.cpp \ + jni/src/guiTable.cpp \ + jni/src/guiVolumeChange.cpp \ + jni/src/httpfetch.cpp \ + jni/src/hud.cpp \ + jni/src/inventory.cpp \ + jni/src/inventorymanager.cpp \ + jni/src/itemdef.cpp \ + jni/src/keycode.cpp \ + jni/src/light.cpp \ + jni/src/localplayer.cpp \ + jni/src/log.cpp \ + jni/src/main.cpp \ + jni/src/map.cpp \ + jni/src/mapblock.cpp \ + jni/src/mapblock_mesh.cpp \ + jni/src/mapgen.cpp \ + jni/src/mapgen_indev.cpp \ + jni/src/mapgen_math.cpp \ + jni/src/mapgen_singlenode.cpp \ + jni/src/mapgen_v6.cpp \ + jni/src/mapgen_v7.cpp \ + jni/src/mapnode.cpp \ + jni/src/mapsector.cpp \ + jni/src/mesh.cpp \ + jni/src/mods.cpp \ + jni/src/nameidmapping.cpp \ + jni/src/nodedef.cpp \ + jni/src/nodemetadata.cpp \ + jni/src/nodetimer.cpp \ + jni/src/noise.cpp \ + jni/src/object_properties.cpp \ + jni/src/particles.cpp \ + jni/src/pathfinder.cpp \ + jni/src/player.cpp \ + jni/src/porting_android.cpp \ + jni/src/porting.cpp \ + jni/src/quicktune.cpp \ + jni/src/rollback.cpp \ + jni/src/rollback_interface.cpp \ + jni/src/serialization.cpp \ + jni/src/server.cpp \ + jni/src/serverlist.cpp \ + jni/src/serverobject.cpp \ + jni/src/sha1.cpp \ + jni/src/shader.cpp \ + jni/src/sky.cpp \ + jni/src/socket.cpp \ + jni/src/sound.cpp \ + jni/src/sound_openal.cpp \ + jni/src/staticobject.cpp \ + jni/src/subgame.cpp \ + jni/src/test.cpp \ + jni/src/tile.cpp \ + jni/src/tool.cpp \ + jni/src/treegen.cpp \ + jni/src/version.cpp \ + jni/src/voxel.cpp \ + jni/src/voxelalgorithms.cpp \ + jni/src/util/directiontables.cpp \ + jni/src/util/numeric.cpp \ + jni/src/util/pointedthing.cpp \ + jni/src/util/serialize.cpp \ + jni/src/util/string.cpp \ + jni/src/util/timetaker.cpp \ + jni/src/touchscreengui.cpp + +# jni/src/database-leveldb.cpp \ + +# lua api +LOCAL_SRC_FILES += \ + jni/src/script/common/c_content.cpp \ + jni/src/script/common/c_converter.cpp \ + jni/src/script/common/c_internal.cpp \ + jni/src/script/common/c_types.cpp \ + jni/src/script/cpp_api/s_base.cpp \ + jni/src/script/cpp_api/s_entity.cpp \ + jni/src/script/cpp_api/s_env.cpp \ + jni/src/script/cpp_api/s_inventory.cpp \ + jni/src/script/cpp_api/s_item.cpp \ + jni/src/script/cpp_api/s_mainmenu.cpp \ + jni/src/script/cpp_api/s_node.cpp \ + jni/src/script/cpp_api/s_nodemeta.cpp \ + jni/src/script/cpp_api/s_player.cpp \ + jni/src/script/cpp_api/s_server.cpp \ + jni/src/script/cpp_api/s_async.cpp \ + jni/src/script/lua_api/l_base.cpp \ + jni/src/script/lua_api/l_craft.cpp \ + jni/src/script/lua_api/l_env.cpp \ + jni/src/script/lua_api/l_inventory.cpp \ + jni/src/script/lua_api/l_item.cpp \ + jni/src/script/lua_api/l_mainmenu.cpp \ + jni/src/script/lua_api/l_mapgen.cpp \ + jni/src/script/lua_api/l_nodemeta.cpp \ + jni/src/script/lua_api/l_nodetimer.cpp \ + jni/src/script/lua_api/l_noise.cpp \ + jni/src/script/lua_api/l_object.cpp \ + jni/src/script/lua_api/l_particles.cpp \ + jni/src/script/lua_api/l_rollback.cpp \ + jni/src/script/lua_api/l_server.cpp \ + jni/src/script/lua_api/l_settings.cpp \ + jni/src/script/lua_api/l_util.cpp \ + jni/src/script/lua_api/l_vmanip.cpp \ + jni/src/script/scripting_game.cpp \ + jni/src/script/scripting_mainmenu.cpp + +#freetype2 support +LOCAL_SRC_FILES += \ + jni/src/cguittfont/xCGUITTFont.cpp + +# lua +LOCAL_SRC_FILES += \ + jni/src/lua/src/lapi.c \ + jni/src/lua/src/lauxlib.c \ + jni/src/lua/src/lbaselib.c \ + jni/src/lua/src/lcode.c \ + jni/src/lua/src/ldblib.c \ + jni/src/lua/src/ldebug.c \ + jni/src/lua/src/ldo.c \ + jni/src/lua/src/ldump.c \ + jni/src/lua/src/lfunc.c \ + jni/src/lua/src/lgc.c \ + jni/src/lua/src/linit.c \ + jni/src/lua/src/liolib.c \ + jni/src/lua/src/llex.c \ + jni/src/lua/src/lmathlib.c \ + jni/src/lua/src/lmem.c \ + jni/src/lua/src/loadlib.c \ + jni/src/lua/src/lobject.c \ + jni/src/lua/src/lopcodes.c \ + jni/src/lua/src/loslib.c \ + jni/src/lua/src/lparser.c \ + jni/src/lua/src/lstate.c \ + jni/src/lua/src/lstring.c \ + jni/src/lua/src/lstrlib.c \ + jni/src/lua/src/ltable.c \ + jni/src/lua/src/ltablib.c \ + jni/src/lua/src/ltm.c \ + jni/src/lua/src/lundump.c \ + jni/src/lua/src/lvm.c \ + jni/src/lua/src/lzio.c \ + jni/src/lua/src/print.c + +# sqlite +LOCAL_SRC_FILES += jni/src/sqlite/sqlite3.c + +# jthread +LOCAL_SRC_FILES += \ + jni/src/jthread/pthread/jevent.cpp \ + jni/src/jthread/pthread/jmutex.cpp \ + jni/src/jthread/pthread/jsemaphore.cpp \ + jni/src/jthread/pthread/jthread.cpp + +# json +LOCAL_SRC_FILES += jni/src/json/jsoncpp.cpp + +LOCAL_SHARED_LIBRARIES := openal ogg vorbis ssl crypto +LOCAL_STATIC_LIBRARIES := Irrlicht freetype curl android_native_app_glue $(PROFILER_LIBS) +# LevelDB +LOCAL_LDLIBS := -lEGL -llog -lGLESv1_CM -lGLESv2 -lz -landroid + +include $(BUILD_SHARED_LIBRARY) + +# at the end of Android.mk +ifdef GPROF +$(call import-module,android-ndk-profiler) +endif +$(call import-module,android/native_app_glue) diff --git a/build/android/jni/Application.mk b/build/android/jni/Application.mk new file mode 100644 index 000000000..b7ffc56a1 --- /dev/null +++ b/build/android/jni/Application.mk @@ -0,0 +1,8 @@ +# NDK_TOOLCHAIN_VERSION := clang3.3 + +APP_PLATFORM := android-9 +APP_MODULES := minetest +APP_STL := gnustl_static + +APP_CPPFLAGS += -fexceptions +APP_GNUSTL_FORCE_CPP_FEATURES := rtti diff --git a/build/android/libvorbis-libogg-fpu.patch b/build/android/libvorbis-libogg-fpu.patch new file mode 100644 index 000000000..52ab397ac --- /dev/null +++ b/build/android/libvorbis-libogg-fpu.patch @@ -0,0 +1,37 @@ +--- libvorbis-libogg-android/jni/libvorbis-jni/Android.mk.orig 2014-06-17 19:22:50.621559073 +0200 ++++ libvorbis-libogg-android/jni/libvorbis-jni/Android.mk 2014-06-17 19:38:20.641581140 +0200 +@@ -4,9 +4,6 @@ + + LOCAL_MODULE := vorbis-jni + LOCAL_CFLAGS += -I$(LOCAL_PATH)/../include -fsigned-char +-ifeq ($(TARGET_ARCH),arm) +- LOCAL_CFLAGS += -march=armv6 -marm -mfloat-abi=softfp -mfpu=vfp +-endif + + LOCAL_SHARED_LIBRARIES := libogg libvorbis + +--- libvorbis-libogg-android/jni/libvorbis/Android.mk.orig 2014-06-17 19:22:39.077558797 +0200 ++++ libvorbis-libogg-android/jni/libvorbis/Android.mk 2014-06-17 19:38:52.121581887 +0200 +@@ -4,9 +4,6 @@ + + LOCAL_MODULE := libvorbis + LOCAL_CFLAGS += -I$(LOCAL_PATH)/../include -ffast-math -fsigned-char +-ifeq ($(TARGET_ARCH),arm) +- LOCAL_CFLAGS += -march=armv6 -marm -mfloat-abi=softfp -mfpu=vfp +-endif + LOCAL_SHARED_LIBRARIES := libogg + + LOCAL_SRC_FILES := \ +--- libvorbis-libogg-android/jni/libogg/Android.mk.orig 2014-06-17 19:22:33.965558675 +0200 ++++ libvorbis-libogg-android/jni/libogg/Android.mk 2014-06-17 19:38:25.337581252 +0200 +@@ -4,10 +4,6 @@ + + LOCAL_MODULE := libogg + LOCAL_CFLAGS += -I$(LOCAL_PATH)/../include -ffast-math -fsigned-char +-ifeq ($(TARGET_ARCH),arm) +- LOCAL_CFLAGS += -march=armv6 -marm -mfloat-abi=softfp -mfpu=vfp +-endif +- + + LOCAL_SRC_FILES := \ + bitwise.c \ diff --git a/build/android/project.properties b/build/android/project.properties new file mode 100644 index 000000000..cc2a7c5cf --- /dev/null +++ b/build/android/project.properties @@ -0,0 +1 @@ +target=android-10 diff --git a/build/android/res/drawable-hdpi/irr_icon.png b/build/android/res/drawable-hdpi/irr_icon.png Binary files differnew file mode 100644 index 000000000..0b6861a0d --- /dev/null +++ b/build/android/res/drawable-hdpi/irr_icon.png diff --git a/build/android/res/drawable-ldpi/irr_icon.png b/build/android/res/drawable-ldpi/irr_icon.png Binary files differnew file mode 100644 index 000000000..b8c5d0177 --- /dev/null +++ b/build/android/res/drawable-ldpi/irr_icon.png diff --git a/build/android/res/drawable-mdpi/irr_icon.png b/build/android/res/drawable-mdpi/irr_icon.png Binary files differnew file mode 100644 index 000000000..951a7f8c1 --- /dev/null +++ b/build/android/res/drawable-mdpi/irr_icon.png diff --git a/build/android/res/drawable-xhdpi/irr_icon.png b/build/android/res/drawable-xhdpi/irr_icon.png Binary files differnew file mode 100644 index 000000000..2ec528ef7 --- /dev/null +++ b/build/android/res/drawable-xhdpi/irr_icon.png diff --git a/build/android/res/layout/assetcopy.xml b/build/android/res/layout/assetcopy.xml new file mode 100644 index 000000000..ade4b0c98 --- /dev/null +++ b/build/android/res/layout/assetcopy.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:orientation="vertical" > + + <ProgressBar + android:id="@+id/progressBar1" + style="?android:attr/progressBarStyleHorizontal" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" /> + + <TextView + android:id="@+id/textView1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:text="preparing media ..." + android:textAppearance="?android:attr/textAppearanceSmall" /> + +</LinearLayout> diff --git a/build/android/res/values/styles.xml b/build/android/res/values/styles.xml new file mode 100644 index 000000000..25b8df5a3 --- /dev/null +++ b/build/android/res/values/styles.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <style name="Theme.Transparent" parent="android:Theme"> + <item name="android:windowIsTranslucent">true</item> + <item name="android:windowBackground">@android:color/transparent</item> + <item name="android:windowContentOverlay">@null</item> + <item name="android:windowNoTitle">true</item> + <item name="android:windowIsFloating">true</item> + <item name="android:backgroundDimEnabled">false</item> + </style> +</resources>
\ No newline at end of file diff --git a/build/android/src/org/minetest/minetest/MinetestAssetCopy.java b/build/android/src/org/minetest/minetest/MinetestAssetCopy.java new file mode 100644 index 000000000..652a00831 --- /dev/null +++ b/build/android/src/org/minetest/minetest/MinetestAssetCopy.java @@ -0,0 +1,288 @@ +package org.minetest.minetest; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.util.Vector; + +import android.app.Activity; +import android.content.res.AssetFileDescriptor; + +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.Environment; +import android.util.Log; +import android.view.Display; +import android.widget.ProgressBar; +import android.widget.TextView; + +public class MinetestAssetCopy extends Activity { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.assetcopy); + + m_ProgressBar = (ProgressBar) findViewById(R.id.progressBar1); + m_Filename = (TextView) findViewById(R.id.textView1); + + Display display = getWindowManager().getDefaultDisplay(); + m_ProgressBar.getLayoutParams().width = (int) (display.getWidth() * 0.8); + m_ProgressBar.invalidate(); + + m_AssetCopy = new copyAssetTask(); + m_AssetCopy.execute(); + } + + ProgressBar m_ProgressBar; + TextView m_Filename; + + copyAssetTask m_AssetCopy; + + private class copyAssetTask extends AsyncTask<String, Integer, String>{ + + private void copyElement(String name, String path) { + String baseDir = Environment.getExternalStorageDirectory().getAbsolutePath(); + String full_path; + if (path != "") { + full_path = path + "/" + name; + } + else { + full_path = name; + } + //is a folder read asset list + if (m_foldernames.contains(full_path)) { + m_Foldername = full_path; + publishProgress(0); + File current_folder = new File(baseDir + "/" + full_path); + if (!current_folder.exists()) { + if (!current_folder.mkdirs()) { + Log.w("MinetestAssetCopy","\t failed create folder: " + baseDir + "/" + full_path); + } + else { + Log.w("MinetestAssetCopy","\t created folder: " + baseDir + "/" + full_path); + } + } + try { + String[] current_assets = getAssets().list(full_path); + for(int i=0; i < current_assets.length; i++) { + copyElement(current_assets[i],full_path); + } + } catch (IOException e) { + Log.w("MinetestAssetCopy","\t failed to read contents of folder"); + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + //is a file just copy + else { + boolean refresh = true; + + File testme = new File(baseDir + "/" + full_path); + + long asset_filesize = -1; + long stored_filesize = -1; + + if (testme.exists()) { + try { + AssetFileDescriptor fd = getAssets().openFd(full_path); + asset_filesize = fd.getLength(); + fd.close(); + } catch (IOException e) { + refresh = true; + m_asset_size_unknown.add(full_path); + } + + stored_filesize = testme.length(); + + if (asset_filesize == stored_filesize) { + refresh = false; + } + + } + + if (refresh) { + m_tocopy.add(full_path); + } + } + } + + private long getFullSize(String filename) { + long size = 0; + try { + InputStream src = getAssets().open(filename); + byte[] buf = new byte[1024]; + + int len = 0; + while ((len = src.read(buf)) > 0) { + size += len; + } + } + catch (IOException e) { + e.printStackTrace(); + } + return size; + } + + @Override + protected String doInBackground(String... files) { + + m_foldernames = new Vector<String>(); + m_tocopy = new Vector<String>(); + m_asset_size_unknown = new Vector<String>(); + String baseDir = Environment.getExternalStorageDirectory().getAbsolutePath() + "/"; + + File TempFolder = new File(baseDir + "Minetest/tmp/"); + + if (!TempFolder.exists()) { + TempFolder.mkdir(); + } + else { + File[] todel = TempFolder.listFiles(); + + for(int i=0; i < todel.length; i++) { + Log.w("MinetestAssetCopy","deleting: " + todel[i].getAbsolutePath()); + todel[i].delete(); + } + } + + // add a .nomedia file + try { + OutputStream dst = new FileOutputStream(baseDir + "Minetest/.nomedia"); + dst.close(); + } catch (IOException e) { + Log.w("MinetestAssetCopy","Failed to create .nomedia file"); + e.printStackTrace(); + } + + try { + InputStream is = getAssets().open("index.txt"); + BufferedReader reader = new BufferedReader(new InputStreamReader(is)); + + String line = reader.readLine(); + while(line != null){ + m_foldernames.add(line); + line = reader.readLine(); + } + } catch (IOException e1) { + // TODO Auto-generated catch block + e1.printStackTrace(); + } + + copyElement("Minetest",""); + + m_copy_started = true; + m_ProgressBar.setMax(m_tocopy.size()); + + for (int i = 0; i < m_tocopy.size(); i++) { + try { + String filename = m_tocopy.get(i); + publishProgress(i); + + boolean asset_size_unknown = false; + long filesize = -1; + + if (m_asset_size_unknown.contains(filename)) { + File testme = new File(baseDir + "/" + filename); + + if(testme.exists()) { + filesize = testme.length(); + } + asset_size_unknown = true; + } + + InputStream src; + try { + src = getAssets().open(filename); + } catch (IOException e) { + Log.w("MinetestAssetCopy","Copying file: " + filename + " FAILED (not in assets)"); + // TODO Auto-generated catch block + e.printStackTrace(); + continue; + } + + // Transfer bytes from in to out + byte[] buf = new byte[1*1024]; + int len = src.read(buf, 0, 1024); + + /* following handling is crazy but we need to deal with */ + /* compressed assets.Flash chips limited livetime sue to */ + /* write operations, we can't allow large files to destroy */ + /* users flash. */ + if (asset_size_unknown) { + if ( (len > 0) && (len < buf.length) && (len == filesize)) { + src.close(); + continue; + } + + if (len == buf.length) { + src.close(); + long size = getFullSize(filename); + if ( size == filesize) { + continue; + } + src = getAssets().open(filename); + len = src.read(buf, 0, 1024); + } + } + if (len > 0) { + int total_filesize = 0; + OutputStream dst; + try { + dst = new FileOutputStream(baseDir + "/" + filename); + } catch (IOException e) { + Log.w("MinetestAssetCopy","Copying file: " + baseDir + + "/" + filename + " FAILED (couldn't open output file)"); + e.printStackTrace(); + src.close(); + continue; + } + dst.write(buf, 0, len); + total_filesize += len; + + while ((len = src.read(buf)) > 0) { + dst.write(buf, 0, len); + total_filesize += len; + } + + dst.close(); + Log.w("MinetestAssetCopy","Copied file: " + m_tocopy.get(i) + " (" + total_filesize + " bytes)"); + } + else if (len < 0) { + Log.w("MinetestAssetCopy","Copying file: " + m_tocopy.get(i) + " failed, size < 0"); + } + src.close(); + } catch (IOException e) { + Log.w("MinetestAssetCopy","Copying file: " + m_tocopy.get(i) + " failed"); + e.printStackTrace(); + } + } + + return ""; + } + + protected void onProgressUpdate(Integer... progress) { + if (m_copy_started) { + m_ProgressBar.setProgress(progress[0]); + m_Filename.setText(m_tocopy.get(progress[0])); + } + else { + m_Filename.setText("scanning " + m_Foldername + " ..."); + } + } + + protected void onPostExecute (String result) { + finish(); + } + boolean m_copy_started = false; + String m_Foldername = "media"; + Vector<String> m_foldernames; + Vector<String> m_tocopy; + Vector<String> m_asset_size_unknown; + } +} diff --git a/build/android/src/org/minetest/minetest/MinetestTextEntry.java b/build/android/src/org/minetest/minetest/MinetestTextEntry.java new file mode 100644 index 000000000..db175a483 --- /dev/null +++ b/build/android/src/org/minetest/minetest/MinetestTextEntry.java @@ -0,0 +1,91 @@ +package org.minetest.minetest; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.text.InputType; +import android.util.Log; +import android.view.KeyEvent; +import android.view.View; +import android.view.View.OnKeyListener; +import android.widget.EditText; + +public class MinetestTextEntry extends Activity { + public AlertDialog mTextInputDialog; + public EditText mTextInputWidget; + + private final int MultiLineTextInput = 1; + private final int SingleLineTextInput = 2; + private final int SingleLinePasswordInput = 3; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Bundle b = getIntent().getExtras(); + String acceptButton = b.getString("EnterButton"); + String hint = b.getString("hint"); + String current = b.getString("current"); + int editType = b.getInt("editType"); + + AlertDialog.Builder builder = new AlertDialog.Builder(this); + mTextInputWidget = new EditText(this); + mTextInputWidget.setHint(hint); + mTextInputWidget.setText(current); + mTextInputWidget.setMinWidth(300); + if (editType == SingleLinePasswordInput) { + mTextInputWidget.setInputType(InputType.TYPE_CLASS_TEXT | + InputType.TYPE_TEXT_VARIATION_PASSWORD); + } + else { + mTextInputWidget.setInputType(InputType.TYPE_CLASS_TEXT); + } + + + builder.setView(mTextInputWidget); + + if (editType == MultiLineTextInput) { + builder.setPositiveButton(acceptButton, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) + { pushResult(mTextInputWidget.getText().toString()); } + }); + } + + builder.setOnCancelListener(new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + cancelDialog(); + } + }); + + mTextInputWidget.setOnKeyListener(new OnKeyListener() { + @Override + public boolean onKey(View view, int KeyCode, KeyEvent event) { + if ( KeyCode == KeyEvent.KEYCODE_ENTER){ + + pushResult(mTextInputWidget.getText().toString()); + return true; + } + return false; + } + }); + + mTextInputDialog = builder.create(); + mTextInputDialog.show(); + } + + public void pushResult(String text) { + Intent resultData = new Intent(); + resultData.putExtra("text", text); + setResult(Activity.RESULT_OK,resultData); + mTextInputDialog.dismiss(); + finish(); + } + + public void cancelDialog() { + setResult(Activity.RESULT_CANCELED); + mTextInputDialog.dismiss(); + finish(); + } +} diff --git a/build/android/src/org/minetest/minetest/MtNativeActivity.java b/build/android/src/org/minetest/minetest/MtNativeActivity.java new file mode 100644 index 000000000..ba7d62169 --- /dev/null +++ b/build/android/src/org/minetest/minetest/MtNativeActivity.java @@ -0,0 +1,93 @@ +package org.minetest.minetest; + +import android.app.NativeActivity; +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; +import android.view.WindowManager; + +public class MtNativeActivity extends NativeActivity { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + m_MessagReturnCode = -1; + m_MessageReturnValue = ""; + + } + + @Override + public void onDestroy() { + super.onDestroy(); + } + + + public void copyAssets() { + Intent intent = new Intent(this, MinetestAssetCopy.class); + startActivity(intent); + } + + public void showDialog(String acceptButton, String hint, String current, + int editType) { + + Intent intent = new Intent(this, MinetestTextEntry.class); + Bundle params = new Bundle(); + params.putString("acceptButton", acceptButton); + params.putString("hint", hint); + params.putString("current", current); + params.putInt("editType", editType); + intent.putExtras(params); + startActivityForResult(intent, 101); + m_MessageReturnValue = ""; + m_MessagReturnCode = -1; + } + + public static native void putMessageBoxResult(String text); + + /* ugly code to workaround putMessageBoxResult not beeing found */ + public int getDialogState() { + return m_MessagReturnCode; + } + + public String getDialogValue() { + m_MessagReturnCode = -1; + return m_MessageReturnValue; + } + + public float getDensity() { + return getResources().getDisplayMetrics().density; + } + + public int getDisplayWidth() { + return getResources().getDisplayMetrics().widthPixels; + } + + public int getDisplayHeight() { + return getResources().getDisplayMetrics().heightPixels; + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, + Intent data) { + if (requestCode == 101) { + if (resultCode == RESULT_OK) { + String text = data.getStringExtra("text"); + m_MessagReturnCode = 0; + m_MessageReturnValue = text; + } + else { + m_MessagReturnCode = 1; + } + } + } + + static { + System.loadLibrary("openal"); + System.loadLibrary("ogg"); + System.loadLibrary("vorbis"); + System.loadLibrary("ssl"); + System.loadLibrary("crypto"); + } + + private int m_MessagReturnCode; + private String m_MessageReturnValue; +} diff --git a/builtin/mainmenu/dlg_delete_world.lua b/builtin/mainmenu/dlg_delete_world.lua index e979bd555..aa710ef3b 100644 --- a/builtin/mainmenu/dlg_delete_world.lua +++ b/builtin/mainmenu/dlg_delete_world.lua @@ -16,7 +16,7 @@ --51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -local function create_world_formspec(dialogdata) +local function delete_world_formspec(dialogdata) local retval = "size[12,6,true]" .. @@ -27,7 +27,7 @@ local function create_world_formspec(dialogdata) return retval end -local function create_world_buttonhandler(this, fields) +local function delete_world_buttonhandler(this, fields) if fields["world_delete_confirm"] then if this.data.delete_index > 0 and @@ -53,9 +53,9 @@ function create_delete_world_dlg(name_to_del,index_to_del) assert(name_to_del ~= nil and type(name_to_del) == "string" and name_to_del ~= "") assert(index_to_del ~= nil and type(index_to_del) == "number") - local retval = dialog_create("sp_create_world", - create_world_formspec, - create_world_buttonhandler, + local retval = dialog_create("delete_world", + delete_world_formspec, + delete_world_buttonhandler, nil) retval.data.delete_name = name_to_del retval.data.delete_index = index_to_del diff --git a/builtin/mainmenu/init_android.lua b/builtin/mainmenu/init_android.lua new file mode 100644 index 000000000..348923fa5 --- /dev/null +++ b/builtin/mainmenu/init_android.lua @@ -0,0 +1,102 @@ +--Minetest +--Copyright (C) 2014 sapier +-- +--This program is free software; you can redistribute it and/or modify +--it under the terms of the GNU Lesser General Public License as published by +--the Free Software Foundation; either version 2.1 of the License, or +--(at your option) any later version. +-- +--This program is distributed in the hope that it will be useful, +--but WITHOUT ANY WARRANTY; without even the implied warranty of +--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +--GNU Lesser General Public License for more details. +-- +--You should have received a copy of the GNU Lesser General Public License along +--with this program; if not, write to the Free Software Foundation, Inc., +--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +mt_color_grey = "#AAAAAA" +mt_color_blue = "#0000DD" +mt_color_green = "#00DD00" +mt_color_dark_green = "#003300" + +--marker for android specific code +ANDROID = true + +local menupath = core.get_mainmenu_path() +local basepath = core.get_builtin_path() +defaulttexturedir = core.get_texturepath_share() .. DIR_DELIM .. "base" .. + DIR_DELIM .. "pack" .. DIR_DELIM + +dofile(basepath .. DIR_DELIM .. "common" .. DIR_DELIM .. "async_event.lua") +dofile(basepath .. DIR_DELIM .. "common" .. DIR_DELIM .. "filterlist.lua") +dofile(basepath .. DIR_DELIM .. "fstk" .. DIR_DELIM .. "buttonbar.lua") +dofile(basepath .. DIR_DELIM .. "fstk" .. DIR_DELIM .. "dialog.lua") +dofile(basepath .. DIR_DELIM .. "fstk" .. DIR_DELIM .. "tabview.lua") +dofile(basepath .. DIR_DELIM .. "fstk" .. DIR_DELIM .. "ui.lua") +dofile(menupath .. DIR_DELIM .. "common.lua") +dofile(menupath .. DIR_DELIM .. "gamemgr.lua") +dofile(menupath .. DIR_DELIM .. "modmgr.lua") +dofile(menupath .. DIR_DELIM .. "store.lua") +dofile(menupath .. DIR_DELIM .. "dlg_config_world.lua") +dofile(menupath .. DIR_DELIM .. "tab_simple_main.lua") +dofile(menupath .. DIR_DELIM .. "tab_credits.lua") +dofile(menupath .. DIR_DELIM .. "tab_mods.lua") +dofile(menupath .. DIR_DELIM .. "tab_settings.lua") + +-------------------------------------------------------------------------------- +local function main_event_handler(tabview,event) + if event == "MenuQuit" then + core.close() + end + return true +end + +local function init_globals() + --init gamedata + gamedata.worldindex = 0 + + local worldlist = core.get_worlds() + + local found_singleplayerworld = false + + for i=1,#worldlist,1 do + if worldlist[i].name == "singleplayerworld" then + found_singleplayerworld = true + gamedata.worldindex = i + end + end + + if not found_singleplayerworld then + core.create_world("singleplayerworld", 1) + + local worldlist = core.get_worlds() + + for i=1,#worldlist,1 do + if worldlist[i].name == "singleplayerworld" then + gamedata.worldindex = i + end + end + end + + --create main tabview + local tv_main = tabview_create("maintab",{x=12,y=5.2},{x=-0,y=-0}) + tv_main:add(tab_simple_main) + tv_main:add(tab_mods) + tv_main:add(tab_settings) + tv_main:add(tab_credits) + tv_main:set_global_event_handler(main_event_handler) + tv_main:set_fixed_size(false) + ui.set_default("maintab") + tv_main:show() + + --create modstore ui + modstore.init({x=12,y=6},3,2) + + ui.update() + + core.sound_play("main_menu", true) +end + +init_globals() + diff --git a/builtin/mainmenu/tab_settings.lua b/builtin/mainmenu/tab_settings.lua index b6ffa86ed..dec28a961 100644 --- a/builtin/mainmenu/tab_settings.lua +++ b/builtin/mainmenu/tab_settings.lua @@ -31,7 +31,6 @@ end local function dlg_confirm_reset_btnhandler(this, fields, dialogdata) if fields["dlg_reset_singleplayer_confirm"] ~= nil then - local worldlist = core.get_worlds() local found_singleplayerworld = false @@ -63,19 +62,43 @@ local function dlg_confirm_reset_btnhandler(this, fields, dialogdata) this.parent:show() this:hide() this:delete() + return true end local function showconfirm_reset(tabview) local new_dlg = dialog_create("reset_spworld", dlg_confirm_reset_formspec, dlg_confirm_reset_btnhandler, - nil, - tabview) + nil) + new_dlg:set_parent(tabview) tabview:hide() new_dlg:show() end +local function gui_scale_index() + + local current_value = tonumber(core.setting_get("gui_scaling")) + if (current_value == nil) then + return 0 + elseif current_value <= 0.5 then + return 1 + elseif current_value <= 0.625 then + return 2 + elseif current_value <= 0.75 then + return 3 + elseif current_value <= 0.875 then + return 4 + elseif current_value <= 1.0 then + return 5 + elseif current_value <= 1.25 then + return 6 + elseif current_value <= 1.5 then + return 7 + else + return 8 + end +end local function formspec(tabview, name, tabdata) local tab_string = @@ -93,8 +116,6 @@ local function formspec(tabview, name, tabdata) .. dump(core.setting_getbool("preload_item_visuals")) .. "]".. "checkbox[1,2.5;cb_particles;".. fgettext("Enable Particles") .. ";" .. dump(core.setting_getbool("enable_particles")) .. "]".. - "checkbox[1,3.0;cb_finite_liquid;".. fgettext("Finite Liquid") .. ";" - .. dump(core.setting_getbool("liquid_finite")) .. "]".. "box[4.25,0;3.25,2.5;#999999]" .. "checkbox[4.5,0;cb_mipmapping;".. fgettext("Mip-Mapping") .. ";" .. dump(core.setting_getbool("mip_map")) .. "]".. @@ -106,16 +127,25 @@ local function formspec(tabview, name, tabdata) .. dump(core.setting_getbool("trilinear_filter")) .. "]".. "box[7.75,0;4,4;#999999]" .. "checkbox[8,0;cb_shaders;".. fgettext("Shaders") .. ";" - .. dump(core.setting_getbool("enable_shaders")) .. "]".. - "button[1,4.5;2.25,0.5;btn_change_keys;".. fgettext("Change keys") .. "]" + .. dump(core.setting_getbool("enable_shaders")) .. "]" + if not ANDROID then + tab_string = tab_string .. + "button[8,4.75;3.75,0.5;btn_change_keys;".. fgettext("Change keys") .. "]" + else + tab_string = tab_string .. + "button[8,4.75;3.75,0.5;btn_reset_singleplayer;".. fgettext("Reset singleplayer world") .. "]" + end + tab_string = tab_string .. + "box[0.75,4.25;3.25,1.25;#999999]" .. + "label[1,4.25;" .. fgettext("GUI scale factor") .. "]" .. + "dropdown[1,4.75;3.0;dd_gui_scaling;0.5,0.625,0.75,0.875,1.0,1.25,1.5,2.0;" + .. gui_scale_index() .. "]" - local android = false - if android then + if ANDROID then tab_string = tab_string .. - "box[4.25,2.75;3.25,2.5;#999999]" .. + "box[4.25,2.75;3.25,2.15;#999999]" .. "checkbox[4.5,2.75;cb_touchscreen_target;".. fgettext("Touch free target") .. ";" - .. dump(core.setting_getbool("touchtarget")) .. "]" .. - "button[8,4.5;3.75,0.5;btn_reset_singleplayer;".. fgettext("Reset singleplayer world") .. "]" + .. dump(core.setting_getbool("touchtarget")) .. "]" end if core.setting_get("touchscreen_threshold") ~= nil then @@ -202,10 +232,6 @@ local function handle_settings_buttons(this, fields, tabname, tabdata) core.setting_set("enable_particles", fields["cb_particles"]) return true end - if fields["cb_finite_liquid"] then - core.setting_set("liquid_finite", fields["cb_finite_liquid"]) - return true - end if fields["cb_bumpmapping"] then core.setting_set("enable_bumpmapping", fields["cb_bumpmapping"]) end @@ -235,14 +261,23 @@ local function handle_settings_buttons(this, fields, tabname, tabdata) core.setting_set("touchtarget", fields["cb_touchscreen_target"]) return true end - if fields["dd_touchthreshold"] then - core.setting_set("touchscreen_threshold",fields["dd_touchthreshold"]) - return true - end if fields["btn_reset_singleplayer"] then + print("sp reset") showconfirm_reset(this) return true end + --Note dropdowns have to be handled LAST! + local ddhandled = false + if fields["dd_touchthreshold"] then + core.setting_set("touchscreen_threshold",fields["dd_touchthreshold"]) + ddhandled = true + end + if fields["dd_gui_scaling"] then + core.setting_set("gui_scaling",fields["dd_gui_scaling"]) + ddhandled = true + end + + return ddhandled end tab_settings = { diff --git a/builtin/mainmenu/tab_simple_main.lua b/builtin/mainmenu/tab_simple_main.lua index 46f4b6190..09cd685ce 100644 --- a/builtin/mainmenu/tab_simple_main.lua +++ b/builtin/mainmenu/tab_simple_main.lua @@ -22,7 +22,6 @@ local function get_formspec(tabview, name, tabdata) local render_details = dump(core.setting_getbool("public_serverlist")) retval = retval .. - "label[0,3.0;".. fgettext("Address/Port") .. "]".. "label[8,0.5;".. fgettext("Name/Password") .. "]" .. "field[0.25,3.25;5.5,0.5;te_address;;" ..core.setting_get("address") .."]" .. "field[5.75,3.25;2.25,0.5;te_port;;" ..core.setting_get("remote_port") .."]" .. @@ -66,7 +65,8 @@ local function get_formspec(tabview, name, tabdata) dump(core.setting_getbool("free_move")) .. "]" -- buttons retval = retval .. - "button[3.0,4.5;6,1.5;btn_start_singleplayer;" .. fgettext("Start Singleplayer") .. "]" + "button[2.0,4.5;6,1.5;btn_start_singleplayer;" .. fgettext("Start Singleplayer") .. "]" .. + "button[8.25,4.5;2.5,1.5;btn_config_sp_world;" .. fgettext("Config MODs") .. "]" return retval end @@ -74,19 +74,21 @@ end -------------------------------------------------------------------------------- local function main_button_handler(tabview, fields, name, tabdata) + if fields["btn_start_singleplayer"] then gamedata.selected_world = gamedata.worldindex gamedata.singleplayer = true core.start() + return true end if fields["favourites"] ~= nil then local event = core.explode_textlist_event(fields["favourites"]) if event.type == "CHG" then - if event.index <= #maintab_favorites then - local address = maintab_favorites[event.index].address - local port = maintab_favorites[event.index].port + if event.index <= #menudata.favorites then + local address = menudata.favorites[event.index].address + local port = menudata.favorites[event.index].port if address ~= nil and port ~= nil then @@ -97,7 +99,7 @@ local function main_button_handler(tabview, fields, name, tabdata) tabdata.fav_selected = event.index end end - return + return true end if fields["cb_public_serverlist"] ~= nil then @@ -106,21 +108,24 @@ local function main_button_handler(tabview, fields, name, tabdata) if core.setting_getbool("public_serverlist") then asyncOnlineFavourites() else - maintab_favorites = core.get_favorites("local") + menudata.favorites = core.get_favorites("local") end - return + return true end if fields["cb_creative"] then core.setting_set("creative_mode", fields["cb_creative"]) + return true end if fields["cb_damage"] then core.setting_set("enable_damage", fields["cb_damage"]) + return true end if fields["cb_fly_mode"] then core.setting_set("free_move", fields["cb_fly_mode"]) + return true end if fields["btn_mp_connect"] ~= nil or @@ -150,7 +155,18 @@ local function main_button_handler(tabview, fields, name, tabdata) core.setting_set("remote_port",fields["te_port"]) core.start() - return + return true + end + + if fields["btn_config_sp_world"] ~= nil then + local configdialog = create_configure_world_dlg(1) + + if (configdialog ~= nil) then + configdialog:set_parent(tabview) + tabview:hide() + configdialog:show() + end + return true end end diff --git a/doc/README.android b/doc/README.android new file mode 100644 index 000000000..d515b48d5 --- /dev/null +++ b/doc/README.android @@ -0,0 +1,130 @@ +Minetest Android port +===================== +Date: 2014 06 28 + +Controls +-------- +The Android port doesn't support everything you can do on PC due to the +limited capabilities of common devices. What can be done is described +below: + +While you're playing the game normally (that is, no menu or inventory is +shown), the following controls are available: +* Look around: touch screen and slide finger +* double tap: place a node or use selected item +* long tap: dig node +* touch shown buttons: press button +* Buttons: +** left upper corner: chat +** right lower corner: jump +** right lower corner: crouch +** left lower corner: walk/step... + left up right + down +** left lower corner: display inventory + +When a menu or inventory is displayed: +* double tap outside menu area: close menu +* tap on an item stack: select that stack +* tap on an empty slot: if you selected a stack already, that stack is placed here +* drag and drop: touch stack and hold finger down, move the stack to another + slot, tap another finger while keeping first finger on screen + --> places a single item from dragged stack into current (first touched) slot + +Special settings +---------------- +There are some settings esspecially usefull for Android users. Minetest's config +file can usually be found at /mnt/sdcard/Minetest. + +* gui_scaling: this is a user-specified scaling factor for the GUI- In case + main menu is to big or small on your device, try changing this + value. +* inventory_image_hack: if your inventory items are messed up, try setting + this to true + +Known issues +------------ +Not all issues are fixed by now: + +* Unable to exit from volume menu -- don't use the volume menu, use Android's + volume controls instead. +* 512 MB RAM seems to be inadequate -- this depends on the server you join. + Try to play on more lightweight servers. + +Versioning +---------- +Android version numbers are 4 digits instead of Minetest's 3 digits. The last +number of Android's version represents the Android internal version code. This +version code is strictly incremental. It's incremented for each official +Minetest Android build. + +E.g. pre-release Minetest Android builds have been 0.4.9.3, while the first +official version most likely will be 0.4.10.4 + +Requirements +------------ + +In order to build, your PC has to be set up to build Minetest in the usual +manner (see the regular Minetest documentation for how to get this done). +In addition to what is required for Minetest in general, you will need the +following software packages. The version number in parenthesis denotes the +version that was tested at the time this README was drafted; newer/older +versions may or may not work. + +* android SDK (x86_64 20131030) +* android NDK (r9d) +* wget (1.13.4) + +Additionally, you'll need to have an Internet connection available on the +build system, as the Android build will download some source packages. + +Build +----- + +Debug build: +* Enter "build/android" subdirectory +* Execute "make" +* Answer the questions about where SDK and NDK are located on your filesystem +* Wait for build to finish + +After the build is finished, the resulting apk can be fond in +build/android/bin/. It will be called Minetest-debug.apk + +Release build: + +* In order to make a release build you'll have to have a keystore setup to sign + the resulting apk package. How this is done is not part of this README. There + are different tutorials on the web explaining how to do it + - choose one yourself. + +* Once your keystore is setup, enter build/android subdirectory and create a new + file "ant.properties" there. Add following lines to that file: + + > key.store=<path to your keystore> + > key.alias=Minetest + +* Execute "make release" +* Enter your keystore as well as your Mintest key password once asked. Be + carefull it's shown on console in clear text! +* The result can be found at "bin/Minetest-release.apk" + +Other things that may be nice to know +------------ +* The environment for Android development tools is saved within Android build + build folder. If you want direct access to it do: + + > make envpaths + > . and_env + + After you've done this you'll have your path and path variables set correct + to use adb and all other Android development tools + +* You can build a single dependency by calling make and the dependency's name, + e.g.: + + > make irrlicht + +* You can completely cleanup a dependency by calling make and the "clean" target, + e.g.: + + > make clean_irrlicht diff --git a/src/client.cpp b/src/client.cpp index 8b89dd63c..601561f7d 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -47,7 +47,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "serialization.h" #include "util/serialize.h" #include "config.h" -#include "cmake_config_githash.h" #include "util/directiontables.h" #include "util/pointedthing.h" #include "version.h" diff --git a/src/config.h b/src/config.h index 8a9d7d63d..54c13d440 100644 --- a/src/config.h +++ b/src/config.h @@ -8,7 +8,10 @@ #define PROJECT_NAME "Minetest" #define RUN_IN_PLACE 0 +#define STATIC_SHAREDIR "" + #define USE_GETTEXT 0 + #ifndef USE_SOUND #define USE_SOUND 0 #endif @@ -17,8 +20,9 @@ #define USE_CURL 0 #endif -#define USE_FREETYPE 0 -#define STATIC_SHAREDIR "" +#ifndef USE_FREETYPE + #define USE_FREETYPE 0 +#endif #ifndef USE_LEVELDB #define USE_LEVELDB 0 @@ -70,5 +74,11 @@ #define VERSION_EXTRA_STRING CMAKE_VERSION_EXTRA_STRING #endif +#ifdef __ANDROID__ + #include "android_version.h" +#else + #include "cmake_config_githash.h" +#endif + #endif diff --git a/src/debug.cpp b/src/debug.cpp index 278902a08..8c02f1d6b 100644 --- a/src/debug.cpp +++ b/src/debug.cpp @@ -18,6 +18,7 @@ with this program; if not, write to the Free Software Foundation, Inc., */ +#include "porting.h" #include "debug.h" #include "exceptions.h" #include "threads.h" @@ -27,7 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <map> #include "jthread/jmutex.h" #include "jthread/jmutexautolock.h" - +#include "config.h" /* Debug output */ @@ -95,6 +96,9 @@ public: } std::streamsize xsputn(const char *s, std::streamsize n) { +#ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_VERBOSE, PROJECT_NAME, "%s", s); +#endif for(int i=0; i<DEBUGSTREAM_COUNT; i++) { if(g_debugstreams[i] == stderr && m_disable_stderr) diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index cf9101e34..f356aaf96 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -64,7 +64,7 @@ void set_default_settings(Settings *settings) settings->setDefault("doubletap_jump", "false"); settings->setDefault("always_fly_fast", "true"); settings->setDefault("directional_colored_fog", "true"); - settings->setDefault("tooltip_show_delay", "400"); + settings->setDefault("tooltip_show_delay", "400"); // Some (temporary) keys for debugging settings->setDefault("keymap_print_debug_stacks", "KEY_KEY_P"); @@ -154,6 +154,7 @@ void set_default_settings(Settings *settings) settings->setDefault("curl_timeout", "5000"); settings->setDefault("curl_parallel_limit", "8"); settings->setDefault("curl_file_download_timeout", "300000"); + settings->setDefault("curl_verify_cert", "true"); settings->setDefault("enable_remote_media_server", "true"); @@ -278,6 +279,39 @@ void set_default_settings(Settings *settings) settings->setDefault("high_precision_fpu", "true"); settings->setDefault("language", ""); + +#ifdef __ANDROID__ + settings->setDefault("screenW", "0"); + settings->setDefault("screenH", "0"); + settings->setDefault("enable_shaders", "false"); + settings->setDefault("fullscreen", "true"); + settings->setDefault("enable_particles", "false"); + settings->setDefault("video_driver", "ogles1"); + settings->setDefault("touchtarget", "true"); + settings->setDefault("main_menu_script","/sdcard/Minetest/builtin/mainmenu/init_android.lua"); + settings->setDefault("TMPFolder","/sdcard/Minetest/tmp/"); + settings->setDefault("touchscreen_threshold","20"); + settings->setDefault("smooth_lighting", "false"); + settings->setDefault("max_simultaneous_block_sends_per_client", "3"); + settings->setDefault("emergequeue_limit_diskonly", "8"); + settings->setDefault("emergequeue_limit_generate", "8"); + settings->setDefault("preload_item_visuals", "false"); + + settings->setDefault("viewing_range_nodes_max", "50"); + settings->setDefault("viewing_range_nodes_min", "20"); + settings->setDefault("inventory_image_hack", "false"); + + //check for device with small screen + float x_inches = ((double) porting::getDisplaySize().X / + (160 * porting::getDisplayDensity())); + if (x_inches < 3.5) { + settings->setDefault("gui_scaling", "0.6"); + } + else if (x_inches < 4.5) { + settings->setDefault("gui_scaling", "0.7"); + } + settings->setDefault("curl_verify_cert","false"); +#endif } void late_init_default_settings(Settings* settings) diff --git a/src/drawscene.cpp b/src/drawscene.cpp index a69cf4403..9672affea 100644 --- a/src/drawscene.cpp +++ b/src/drawscene.cpp @@ -427,6 +427,13 @@ void draw_scene(video::IVideoDriver* driver, scene::ISceneManager* smgr, bool draw_crosshair = ((player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) && (camera.getCameraMode() != CAMERA_MODE_THIRD_FRONT)); +#ifdef HAVE_TOUCHSCREENGUI + try { + draw_crosshair = !g_settings->getBool("touchtarget"); + } + catch(SettingNotFoundException) {} +#endif + std::string draw_mode = g_settings->get("3d_mode"); smgr->drawAll(); diff --git a/src/filesys.cpp b/src/filesys.cpp index eda36c833..7c72a4b27 100644 --- a/src/filesys.cpp +++ b/src/filesys.cpp @@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <errno.h> #include <fstream> #include "log.h" +#include "config.h" namespace fs { @@ -34,8 +35,8 @@ namespace fs #define _WIN32_WINNT 0x0501 #include <windows.h> #include <malloc.h> -#include <tchar.h> -#include <wchar.h> +#include <tchar.h> +#include <wchar.h> #define BUFSIZE MAX_PATH @@ -73,12 +74,12 @@ std::vector<DirListNode> GetDirListing(std::string pathstring) // Find the first file in the directory. hFind = FindFirstFile(DirSpec, &FindFileData); - if (hFind == INVALID_HANDLE_VALUE) + if (hFind == INVALID_HANDLE_VALUE) { retval = (-1); goto Cleanup; - } - else + } + else { // NOTE: // Be very sure to not include '..' in the results, it will @@ -91,7 +92,7 @@ std::vector<DirListNode> GetDirListing(std::string pathstring) listing.push_back(node); // List all the other files in the directory. - while (FindNextFile(hFind, &FindFileData) != 0) + while (FindNextFile(hFind, &FindFileData) != 0) { DirListNode node; node.name = FindFileData.cFileName; @@ -102,7 +103,7 @@ std::vector<DirListNode> GetDirListing(std::string pathstring) dwError = GetLastError(); FindClose(hFind); - if (dwError != ERROR_NO_MORE_FILES) + if (dwError != ERROR_NO_MORE_FILES) { errorstream<<"GetDirListing: FindNextFile error. Error is " <<dwError<<std::endl; @@ -401,7 +402,11 @@ std::string TempPath() compatible with lua's os.tmpname which under the default configuration hardcodes mkstemp("/tmp/lua_XXXXXX"). */ - return std::string(DIR_DELIM) + "tmp"; +#ifdef __ANDROID__ + return DIR_DELIM "sdcard" DIR_DELIM PROJECT_NAME DIR_DELIM "tmp"; +#else + return DIR_DELIM "tmp"; +#endif } #endif diff --git a/src/game.cpp b/src/game.cpp index e74e4697b..4f034676c 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -70,6 +70,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "drawscene.h" #include "content_cao.h" +#ifdef HAVE_TOUCHSCREENGUI +#include "touchscreengui.h" +#endif + /* Text input system */ @@ -942,14 +946,20 @@ static inline void create_formspec_menu(GUIFormSpecMenu** cur_formspec, } } +#ifdef __ANDROID__ +#define SIZE_TAG "size[11,5.5]" +#else +#define SIZE_TAG "size[11,5.5,true]" +#endif + static void show_chat_menu(GUIFormSpecMenu** cur_formspec, InventoryManager *invmgr, IGameDef *gamedef, IWritableTextureSource* tsrc, IrrlichtDevice * device, Client* client, std::string text) { std::string formspec = - FORMSPEC_VERSION_STRING - "size[11,5.5,true]" + FORMSPEC_VERSION_STRING + SIZE_TAG "field[3,2.35;6,0.5;f_text;;" + text + "]" "button_exit[4,3;3,0.5;btn_send;" + wide_to_narrow(wstrgettext("Proceed")) + "]" ; @@ -969,7 +979,7 @@ static void show_deathscreen(GUIFormSpecMenu** cur_formspec, { std::string formspec = std::string(FORMSPEC_VERSION_STRING) + - "size[11,5.5,true]" + SIZE_TAG "bgcolor[#320000b4;true]" "label[4.85,1.35;You died.]" "button_exit[4,3;3,0.5;btn_respawn;" + gettext("Respawn") + "]" @@ -990,6 +1000,21 @@ static void show_pause_menu(GUIFormSpecMenu** cur_formspec, IWritableTextureSource* tsrc, IrrlichtDevice * device, bool singleplayermode) { +#ifdef __ANDROID__ + std::string control_text = wide_to_narrow(wstrgettext("Default Controls:\n" + "No menu visible:\n" + "- single tap: button activate\n" + "- double tap: place/use\n" + "- slide finger: look around\n" + "Menu/Inventory visible:\n" + "- double tap (outside):\n" + " -->close\n" + "- touch stack, touch slot:\n" + " --> move stack\n" + "- touch&drag, tap 2nd finger\n" + " --> place single item to slot\n" + )); +#else std::string control_text = wide_to_narrow(wstrgettext("Default Controls:\n" "- WASD: move\n" "- Space: jump/climb\n" @@ -1002,11 +1027,11 @@ static void show_pause_menu(GUIFormSpecMenu** cur_formspec, "- Mouse wheel: select item\n" "- T: chat\n" )); - +#endif float ypos = singleplayermode ? 1.0 : 0.5; std::ostringstream os; - os << FORMSPEC_VERSION_STRING << "size[11,5.5,true]" + os << FORMSPEC_VERSION_STRING << SIZE_TAG << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;" << wide_to_narrow(wstrgettext("Continue")) << "]"; @@ -1021,7 +1046,7 @@ static void show_pause_menu(GUIFormSpecMenu** cur_formspec, << wide_to_narrow(wstrgettext("Exit to Menu")) << "]"; os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;" << wide_to_narrow(wstrgettext("Exit to OS")) << "]" - << "textarea[7.5,0.25;3.75,6;;" << control_text << ";]" + << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]" << "textarea[0.4,0.25;3.5,6;;" << "Minetest\n" << minetest_build_info << "\n" << "path_user = " << wrap_rows(porting::path_user, 20) @@ -1253,18 +1278,18 @@ void the_game(bool &kill, bool random_input, InputHandler *input, server->step(dtime); // End condition - if(client.getState() == LC_Init){ + if(client.getState() == LC_Init) { could_connect = true; break; } // Break conditions - if(client.accessDenied()){ + if(client.accessDenied()) { error_message = L"Access denied. Reason: " +client.accessDeniedReason(); errorstream<<wide_to_narrow(error_message)<<std::endl; break; } - if(input->wasKeyDown(EscapeKey)){ + if(input->wasKeyDown(EscapeKey) || input->wasKeyDown(CancelKey)) { connect_aborted = true; infostream<<"Connect aborted [Escape]"<<std::endl; break; @@ -1310,8 +1335,8 @@ void the_game(bool &kill, bool random_input, InputHandler *input, /* Handle failure to connect */ - if(!could_connect){ - if(error_message == L"" && !connect_aborted){ + if(!could_connect) { + if(error_message == L"" && !connect_aborted) { error_message = L"Connection failed"; errorstream<<wide_to_narrow(error_message)<<std::endl; } @@ -1330,8 +1355,7 @@ void the_game(bool &kill, bool random_input, InputHandler *input, float fps_max = g_settings->getFloat("fps_max"); bool cloud_menu_background = g_settings->getBool("menu_clouds"); u32 lasttime = device->getTimer()->getTime(); - while(device->run()) - { + while (device->run()) { f32 dtime = 0.033; // in seconds if (cloud_menu_background) { u32 time = device->getTimer()->getTime(); @@ -1343,29 +1367,29 @@ void the_game(bool &kill, bool random_input, InputHandler *input, } // Update client and server client.step(dtime); - if(server != NULL) + if (server != NULL) server->step(dtime); // End condition - if(client.mediaReceived() && + if (client.mediaReceived() && client.itemdefReceived() && - client.nodedefReceived()){ + client.nodedefReceived()) { got_content = true; break; } // Break conditions - if(client.accessDenied()){ + if (client.accessDenied()) { error_message = L"Access denied. Reason: " +client.accessDeniedReason(); errorstream<<wide_to_narrow(error_message)<<std::endl; break; } - if(client.getState() < LC_Init){ + if (client.getState() < LC_Init) { error_message = L"Client disconnected"; errorstream<<wide_to_narrow(error_message)<<std::endl; break; } - if(input->wasKeyDown(EscapeKey)){ + if (input->wasKeyDown(EscapeKey) || input->wasKeyDown(CancelKey)) { content_aborted = true; infostream<<"Connect aborted [Escape]"<<std::endl; break; @@ -1548,6 +1572,11 @@ void the_game(bool &kill, bool random_input, InputHandler *input, guitext_profiler->setVisible(false); guitext_profiler->setWordWrap(true); +#ifdef HAVE_TOUCHSCREENGUI + if (g_touchscreengui) + g_touchscreengui->init(tsrc,porting::getDisplayDensity()); +#endif + /* Some statistics are collected in these */ @@ -1641,7 +1670,8 @@ void the_game(bool &kill, bool random_input, InputHandler *input, for(;;) { - if(device->run() == false || kill == true) + if(device->run() == false || kill == true || + g_gamecallback->shutdown_requested) break; v2u32 screensize = driver->getScreenSize(); @@ -1858,6 +1888,15 @@ void the_game(bool &kill, bool random_input, InputHandler *input, // Input handler step() (used by the random input generator) input->step(dtime); +#ifdef HAVE_TOUCHSCREENGUI + if (g_touchscreengui) { + g_touchscreengui->step(dtime); + } +#endif +#ifdef __ANDROID__ + if (current_formspec != 0) + current_formspec->getAndroidUIInput(); +#endif // Increase timer for doubleclick of "jump" if(g_settings->getBool("doubletap_jump") && jump_timer <= 0.2) @@ -1890,7 +1929,7 @@ void the_game(bool &kill, bool random_input, InputHandler *input, inventoryloc.setCurrentPlayer(); current_formspec->setFormSpec(fs_src->getForm(), inventoryloc); } - else if(input->wasKeyDown(EscapeKey)) + else if(input->wasKeyDown(EscapeKey) || input->wasKeyDown(CancelKey)) { show_pause_menu(¤t_formspec, &client, gamedef, tsrc, device, simple_singleplayer_mode); @@ -2214,21 +2253,29 @@ void the_game(bool &kill, bool random_input, InputHandler *input, float turn_amount = 0; if((device->isWindowActive() && noMenuActive()) || random_input) { +#ifndef __ANDROID__ if(!random_input) { // Mac OSX gets upset if this is set every frame if(device->getCursorControl()->isVisible()) device->getCursorControl()->setVisible(false); } +#endif if(first_loop_after_window_activation){ //infostream<<"window active, first loop"<<std::endl; first_loop_after_window_activation = false; - } - else{ - s32 dx = input->getMousePos().X - (driver->getScreenSize().Width/2); - s32 dy = input->getMousePos().Y - (driver->getScreenSize().Height/2); - if(invert_mouse || camera.getCameraMode() == CAMERA_MODE_THIRD_FRONT) { + } else { +#ifdef HAVE_TOUCHSCREENGUI + if (g_touchscreengui) { + camera_yaw = g_touchscreengui->getYaw(); + camera_pitch = g_touchscreengui->getPitch(); + } else { +#endif + s32 dx = input->getMousePos().X - (driver->getScreenSize().Width/2); + s32 dy = input->getMousePos().Y - (driver->getScreenSize().Height/2); + if ((invert_mouse) + || (camera.getCameraMode() == CAMERA_MODE_THIRD_FRONT)) { dy = -dy; } //infostream<<"window active, pos difference "<<dx<<","<<dy<<std::endl; @@ -2247,18 +2294,23 @@ void the_game(bool &kill, bool random_input, InputHandler *input, d = rangelim(d, 0.01, 100.0); camera_yaw -= dx*d; camera_pitch += dy*d; + turn_amount = v2f(dx, dy).getLength() * d; + +#ifdef HAVE_TOUCHSCREENGUI + } +#endif if(camera_pitch < -89.5) camera_pitch = -89.5; if(camera_pitch > 89.5) camera_pitch = 89.5; - - turn_amount = v2f(dx, dy).getLength() * d; } input->setMousePos((driver->getScreenSize().Width/2), (driver->getScreenSize().Height/2)); } else{ +#ifndef ANDROID // Mac OSX gets upset if this is set every frame if(device->getCursorControl()->isVisible() == false) device->getCursorControl()->setVisible(true); +#endif //infostream<<"window inactive"<<std::endl; first_loop_after_window_activation = true; @@ -2668,10 +2720,19 @@ void the_game(bool &kill, bool random_input, InputHandler *input, core::line3d<f32> shootline(camera_position, camera_position + camera_direction * BS * (d+1)); + // prevent player pointing anything in front-view if (camera.getCameraMode() == CAMERA_MODE_THIRD_FRONT) shootline = core::line3d<f32>(0,0,0,0,0,0); +#ifdef HAVE_TOUCHSCREENGUI + if ((g_settings->getBool("touchtarget")) && (g_touchscreengui)) { + shootline = g_touchscreengui->getShootline(); + shootline.start += intToFloat(camera_offset,BS); + shootline.end += intToFloat(camera_offset,BS); + } +#endif + ClientActiveObject *selected_object = NULL; PointedThing pointed = getPointedThing( @@ -3156,8 +3217,9 @@ void the_game(bool &kill, bool random_input, InputHandler *input, } else if(show_hud || show_chat) { + u16 fps = (1.0/dtime_avg1); std::ostringstream os(std::ios_base::binary); - os<<"Minetest "<<minetest_version_hash; + os<<"Minetest "<<minetest_version_hash <<" FPS = "<<fps; guitext->setText(narrow_to_wide(os.str()).c_str()); guitext->setVisible(true); } diff --git a/src/guiEngine.cpp b/src/guiEngine.cpp index 530733216..0a1d72206 100644 --- a/src/guiEngine.cpp +++ b/src/guiEngine.cpp @@ -32,6 +32,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "clouds.h" #include "httpfetch.h" #include "util/numeric.h" +#ifdef __ANDROID__ +#include "tile.h" +#include <GLES/gl.h> +#endif #include <IGUIStaticText.h> #include <ICameraSceneNode.h> @@ -83,6 +87,16 @@ video::ITexture* MenuTextureSource::getTexture(const std::string &name, u32 *id) if(name.empty()) return NULL; m_to_delete.insert(name); + +#ifdef __ANDROID__ + video::IImage *image = m_driver->createImageFromFile(name.c_str()); + if (image) { + image = Align2Npot2(image, m_driver); + video::ITexture* retval = m_driver->addTexture(name.c_str(), image); + image->drop(); + return retval; + } +#endif return m_driver->getTexture(name.c_str()); } @@ -266,6 +280,10 @@ void GUIEngine::run() sleep_ms(25); m_script->step(); + +#ifdef __ANDROID__ + m_menu->getAndroidUIInput(); +#endif } } diff --git a/src/guiFormSpecMenu.cpp b/src/guiFormSpecMenu.cpp index fd12c4d4d..54414d7e7 100644 --- a/src/guiFormSpecMenu.cpp +++ b/src/guiFormSpecMenu.cpp @@ -88,6 +88,9 @@ GUIFormSpecMenu::GUIFormSpecMenu(irr::IrrlichtDevice* dev, m_ext_ptr(ext_ptr), m_font(dev->getGUIEnvironment()->getSkin()->getFont()), m_formspec_version(0) +#ifdef __ANDROID__ + ,m_JavaDialogFieldName(L"") +#endif { current_keys_pending.key_down = false; current_keys_pending.key_up = false; @@ -1878,6 +1881,52 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) setInitialFocus(); } +#ifdef __ANDROID__ +bool GUIFormSpecMenu::getAndroidUIInput() +{ + /* no dialog shown */ + if (m_JavaDialogFieldName == L"") { + return false; + } + + /* still waiting */ + if (porting::getInputDialogState() == -1) { + return true; + } + + std::wstring fieldname = m_JavaDialogFieldName; + m_JavaDialogFieldName = L""; + + /* no value abort dialog processing */ + if (porting::getInputDialogState() != 0) { + return false; + } + + for(std::vector<FieldSpec>::iterator iter = m_fields.begin(); + iter != m_fields.end(); iter++) { + + if (iter->fname != fieldname) { + continue; + } + IGUIElement* tochange = getElementFromId(iter->fid); + + if (tochange == 0) { + return false; + } + + if (tochange->getType() != irr::gui::EGUIET_EDIT_BOX) { + return false; + } + + std::string text = porting::getInputDialogValue(); + + ((gui::IGUIEditBox*) tochange)-> + setText(narrow_to_wide(text).c_str()); + } + return false; +} +#endif + GUIFormSpecMenu::ItemSpec GUIFormSpecMenu::getItemAtPos(v2s32 p) const { core::rect<s32> imgrect(0,0,imgsize.X,imgsize.Y); @@ -1886,8 +1935,7 @@ GUIFormSpecMenu::ItemSpec GUIFormSpecMenu::getItemAtPos(v2s32 p) const { const ListDrawSpec &s = m_inventorylists[i]; - for(s32 i=0; i<s.geom.X*s.geom.Y; i++) - { + for(s32 i=0; i<s.geom.X*s.geom.Y; i++) { s32 item_i = i + s.start_item_i; s32 x = (i%s.geom.X) * spacing.X; s32 y = (i/s.geom.X) * spacing.Y; @@ -2051,8 +2099,6 @@ void GUIFormSpecMenu::drawMenu() } } - m_pointer = m_device->getCursorControl()->getPosition(); - updateSelectedItem(); gui::IGUISkin* skin = Environment->getSkin(); @@ -2195,6 +2241,11 @@ void GUIFormSpecMenu::drawMenu() */ gui::IGUIElement::draw(); +/* TODO find way to show tooltips on touchscreen */ +#ifndef HAVE_TOUCHSCREENGUI + m_pointer = m_device->getCursorControl()->getPosition(); +#endif + /* Draw fields/buttons tooltips */ @@ -2491,7 +2542,8 @@ bool GUIFormSpecMenu::preprocessEvent(const SEvent& event) // Fix Esc/Return key being eaten by checkboxen and tables if(event.EventType==EET_KEY_INPUT_EVENT) { KeyPress kp(event.KeyInput); - if (kp == EscapeKey || kp == getKeySetting("keymap_inventory") + if (kp == EscapeKey || kp == CancelKey + || kp == getKeySetting("keymap_inventory") || event.KeyInput.Key==KEY_RETURN) { gui::IGUIElement *focused = Environment->getFocus(); if (focused && isMyChild(focused) && @@ -2533,6 +2585,156 @@ bool GUIFormSpecMenu::preprocessEvent(const SEvent& event) } } + #ifdef __ANDROID__ + // display software keyboard when clicking edit boxes + if (event.EventType == EET_MOUSE_INPUT_EVENT + && event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) { + gui::IGUIElement *hovered = + Environment->getRootGUIElement()->getElementFromPoint( + core::position2d<s32>(event.MouseInput.X, event.MouseInput.Y)); + if ((hovered) && (hovered->getType() == irr::gui::EGUIET_EDIT_BOX)) { + bool retval = hovered->OnEvent(event); + if (retval) { + Environment->setFocus(hovered); + } + m_JavaDialogFieldName = getNameByID(hovered->getID()); + std::string message = gettext("Enter "); + std::string label = wide_to_narrow(getLabelByID(hovered->getID())); + if (label == "") { + label = "text"; + } + message += gettext(label) + ":"; + + /* single line text input */ + int type = 2; + + /* multi line text input */ + if (((gui::IGUIEditBox*) hovered)->isMultiLineEnabled()) { + type = 1; + } + + /* passwords are always single line */ + if (((gui::IGUIEditBox*) hovered)->isPasswordBox()) { + type = 3; + } + + porting::showInputDialog(gettext("ok"), "", + wide_to_narrow(((gui::IGUIEditBox*) hovered)->getText()), + type); + return retval; + } + } + + if (event.EventType == EET_TOUCH_INPUT_EVENT) + { + SEvent translated; + memset(&translated, 0, sizeof(SEvent)); + translated.EventType = EET_MOUSE_INPUT_EVENT; + gui::IGUIElement* root = Environment->getRootGUIElement(); + + if (!root) { + errorstream + << "GUIFormSpecMenu::preprocessEvent unable to get root element" + << std::endl; + return false; + } + gui::IGUIElement* hovered = root->getElementFromPoint( + core::position2d<s32>( + event.TouchInput.X, + event.TouchInput.Y)); + + translated.MouseInput.X = event.TouchInput.X; + translated.MouseInput.Y = event.TouchInput.Y; + translated.MouseInput.Control = false; + + bool dont_send_event = false; + + if (event.TouchInput.touchedCount == 1) { + switch (event.TouchInput.Event) { + case ETIE_PRESSED_DOWN: + m_pointer = v2s32(event.TouchInput.X,event.TouchInput.Y); + translated.MouseInput.Event = EMIE_LMOUSE_PRESSED_DOWN; + translated.MouseInput.ButtonStates = EMBSM_LEFT; + m_down_pos = m_pointer; + break; + case ETIE_MOVED: + m_pointer = v2s32(event.TouchInput.X,event.TouchInput.Y); + translated.MouseInput.Event = EMIE_MOUSE_MOVED; + translated.MouseInput.ButtonStates = EMBSM_LEFT; + break; + case ETIE_LEFT_UP: + translated.MouseInput.Event = EMIE_LMOUSE_LEFT_UP; + translated.MouseInput.ButtonStates = 0; + hovered = root->getElementFromPoint(m_down_pos); + /* we don't have a valid pointer element use last + * known pointer pos */ + translated.MouseInput.X = m_pointer.X; + translated.MouseInput.Y = m_pointer.Y; + + /* reset down pos */ + m_down_pos = v2s32(0,0); + break; + default: + dont_send_event = true; + //this is not supposed to happen + errorstream + << "GUIFormSpecMenu::preprocessEvent unexpected usecase Event=" + << event.TouchInput.Event << std::endl; + } + } else if ( (event.TouchInput.touchedCount == 2) && + (event.TouchInput.Event == ETIE_PRESSED_DOWN) ) { + hovered = root->getElementFromPoint(m_down_pos); + + translated.MouseInput.Event = EMIE_RMOUSE_PRESSED_DOWN; + translated.MouseInput.ButtonStates = EMBSM_LEFT | EMBSM_RIGHT; + translated.MouseInput.X = m_pointer.X; + translated.MouseInput.Y = m_pointer.Y; + + if (hovered) { + hovered->OnEvent(translated); + } + + translated.MouseInput.Event = EMIE_RMOUSE_LEFT_UP; + translated.MouseInput.ButtonStates = EMBSM_LEFT; + + + if (hovered) { + hovered->OnEvent(translated); + } + dont_send_event = true; + } + /* ignore unhandled 2 touch events ... accidental moving for example */ + else if (event.TouchInput.touchedCount == 2) { + dont_send_event = true; + } + else if (event.TouchInput.touchedCount > 2) { + errorstream + << "GUIFormSpecMenu::preprocessEvent to many multitouch events " + << event.TouchInput.touchedCount << " ignoring them" << std::endl; + } + + if (dont_send_event) { + return true; + } + + /* check if translated event needs to be preprocessed again */ + if (preprocessEvent(translated)) { + return true; + } + if (hovered) { + grab(); + bool retval = hovered->OnEvent(translated); + + if (event.TouchInput.Event == ETIE_LEFT_UP) { + /* reset pointer */ + m_pointer = v2s32(0,0); + } + drop(); + return retval; + } + } + #endif + return false; } @@ -2584,8 +2786,8 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) { if(event.EventType==EET_KEY_INPUT_EVENT) { KeyPress kp(event.KeyInput); - if (event.KeyInput.PressedDown && (kp == EscapeKey || - kp == getKeySetting("keymap_inventory"))) { + if (event.KeyInput.PressedDown && ( (kp == EscapeKey) || + (kp == getKeySetting("keymap_inventory")) || (kp == CancelKey))) { if (m_allowclose) { doPause = false; acceptInput(quit_mode_cancel); @@ -3015,6 +3217,38 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) return Parent ? Parent->OnEvent(event) : false; } +/** + * get name of element by element id + * @param id of element + * @return name string or empty string + */ +std::wstring GUIFormSpecMenu::getNameByID(s32 id) +{ + for(std::vector<FieldSpec>::iterator iter = m_fields.begin(); + iter != m_fields.end(); iter++) { + if (iter->fid == id) { + return iter->fname; + } + } + return L""; +} + +/** + * get label of element by id + * @param id of element + * @return label string or empty string + */ +std::wstring GUIFormSpecMenu::getLabelByID(s32 id) +{ + for(std::vector<FieldSpec>::iterator iter = m_fields.begin(); + iter != m_fields.end(); iter++) { + if (iter->fid == id) { + return iter->flabel; + } + } + return L""; +} + bool GUIFormSpecMenu::parseColor(const std::string &value, video::SColor &color, bool quiet) { diff --git a/src/guiFormSpecMenu.h b/src/guiFormSpecMenu.h index 5d74978a9..72a188bc5 100644 --- a/src/guiFormSpecMenu.h +++ b/src/guiFormSpecMenu.h @@ -151,7 +151,7 @@ class GUIFormSpecMenu : public GUIModalMenu { } FieldSpec(const std::wstring &name, const std::wstring &label, - const std::wstring &fdeflt, int id) : + const std::wstring &fdeflt, int id) : fname(name), flabel(label), fdefault(fdeflt), @@ -274,6 +274,10 @@ public: static bool parseColor(const std::string &value, video::SColor &color, bool quiet); +#ifdef __ANDROID__ + bool getAndroidUIInput(); +#endif + protected: v2s32 getBasePos() const { @@ -409,6 +413,14 @@ private: clickpos m_doubleclickdetect[2]; int m_btn_height; + + std::wstring getLabelByID(s32 id); + std::wstring getNameByID(s32 id); +#ifdef __ANDROID__ + v2s32 m_down_pos; + std::wstring m_JavaDialogFieldName; +#endif + }; class FormspecFormSource: public IFormSource diff --git a/src/httpfetch.cpp b/src/httpfetch.cpp index 313988fd8..69c366ee0 100644 --- a/src/httpfetch.cpp +++ b/src/httpfetch.cpp @@ -46,7 +46,7 @@ HTTPFetchRequest::HTTPFetchRequest() request_id = 0; timeout = g_settings->getS32("curl_timeout"); connect_timeout = timeout; - + useragent = std::string("Minetest/") + minetest_version_hash + " (" + porting::get_sysinfo() + ")"; } @@ -259,6 +259,10 @@ struct HTTPFetchOngoing request.extra_headers[i].c_str()); } curl_easy_setopt(curl, CURLOPT_HTTPHEADER, httpheader); + + if (!g_settings->getBool("curl_verify_cert")) { + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false); + } } } @@ -302,7 +306,7 @@ struct HTTPFetchOngoing } if (res != CURLE_OK) { - infostream<<request.url<<" not found (" + errorstream<<request.url<<" not found (" <<curl_easy_strerror(res)<<")" <<" (response code "<<result.response_code<<")" <<std::endl; diff --git a/src/hud.cpp b/src/hud.cpp index f29e5249d..02071835f 100644 --- a/src/hud.cpp +++ b/src/hud.cpp @@ -33,6 +33,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "porting.h" #include <IGUIStaticText.h> +#ifdef HAVE_TOUCHSCREENGUI +#include "touchscreengui.h" +#endif Hud::Hud(video::IVideoDriver *driver, scene::ISceneManager* smgr, gui::IGUIEnvironment* guienv, gui::IGUIFont *font, @@ -160,6 +163,11 @@ void Hud::drawItem(const ItemStack &item, const core::rect<s32>& rect, bool sele void Hud::drawItems(v2s32 upperleftpos, s32 itemcount, s32 offset, InventoryList *mainlist, u16 selectitem, u16 direction) { +#ifdef HAVE_TOUCHSCREENGUI + if ( (g_touchscreengui) && (offset == 0)) + g_touchscreengui->resetHud(); +#endif + s32 height = m_hotbar_imagesize + m_padding * 2; s32 width = (itemcount - offset) * (m_hotbar_imagesize + m_padding * 2); @@ -222,6 +230,11 @@ void Hud::drawItems(v2s32 upperleftpos, s32 itemcount, s32 offset, } drawItem(mainlist->getItem(i), (imgrect + pos + steppos), (i +1) == selectitem ); + +#ifdef HAVE_TOUCHSCREENGUI + if (g_touchscreengui) + g_touchscreengui->registerHudItem(i, (imgrect + pos + steppos)); +#endif } } diff --git a/src/itemdef.cpp b/src/itemdef.cpp index 0187c7387..10e1afe2d 100644 --- a/src/itemdef.cpp +++ b/src/itemdef.cpp @@ -38,6 +38,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <map> #include <set> +#ifdef __ANDROID__ +#include <GLES/gl.h> +#endif + /* ItemDefinition */ @@ -433,6 +437,11 @@ public: params.light_color.set(1.0, 0.5, 0.5, 0.5); params.light_radius = 1000; +#ifdef __ANDROID__ + params.camera_position.set(0, -1.0, -1.5); + params.camera_position.rotateXZBy(45); + params.light_position.set(10, -100, -50); +#endif cc->inventory_texture = tsrc->generateTextureFromMesh(params); diff --git a/src/jthread/pthread/jsemaphore.cpp b/src/jthread/pthread/jsemaphore.cpp index f6d7f022f..609e2f518 100644 --- a/src/jthread/pthread/jsemaphore.cpp +++ b/src/jthread/pthread/jsemaphore.cpp @@ -51,7 +51,15 @@ JSemaphore::JSemaphore() { JSemaphore::~JSemaphore() { int sem_destroy_retval = sem_destroy(&m_semaphore); +#ifdef __ANDROID__ +// WORKAROUND for broken bionic semaphore implementation! + assert( + (sem_destroy_retval == 0) || + (errno == EBUSY) + ); +#else assert(sem_destroy_retval == 0); +#endif UNUSED(sem_destroy_retval); } diff --git a/src/jthread/pthread/jthread.cpp b/src/jthread/pthread/jthread.cpp index a8e54e315..e90c03456 100644 --- a/src/jthread/pthread/jthread.cpp +++ b/src/jthread/pthread/jthread.cpp @@ -111,7 +111,11 @@ int JThread::Kill() } return ERR_JTHREAD_NOTRUNNING; } +#ifdef __ANDROID__ + pthread_kill(threadid, SIGKILL); +#else pthread_cancel(threadid); +#endif if (started) { int pthread_join_retval = pthread_join(threadid,&status); assert(pthread_join_retval == 0); diff --git a/src/keycode.cpp b/src/keycode.cpp index 96631b4ea..890c97cc2 100644 --- a/src/keycode.cpp +++ b/src/keycode.cpp @@ -334,6 +334,7 @@ const char *KeyPress::name() const } const KeyPress EscapeKey("KEY_ESCAPE"); +const KeyPress CancelKey("KEY_CANCEL"); const KeyPress NumberKey[] = { KeyPress("KEY_KEY_0"), KeyPress("KEY_KEY_1"), KeyPress("KEY_KEY_2"), KeyPress("KEY_KEY_3"), KeyPress("KEY_KEY_4"), KeyPress("KEY_KEY_5"), diff --git a/src/keycode.h b/src/keycode.h index 65f04d8d7..459a85a46 100644 --- a/src/keycode.h +++ b/src/keycode.h @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #define KEYCODE_HEADER #include "irrlichttypes.h" +#include "Keycodes.h" #include <IEventReceiver.h> #include <string> @@ -57,6 +58,7 @@ protected: }; extern const KeyPress EscapeKey; +extern const KeyPress CancelKey; extern const KeyPress NumberKey[10]; // Key configuration getter @@ -65,5 +67,7 @@ KeyPress getKeySetting(const char *settingname); // Clear fast lookup cache void clearKeyCache(); +irr::EKEY_CODE keyname_to_keycode(const char *name); + #endif diff --git a/src/log.cpp b/src/log.cpp index 97f25cc77..ff2e16333 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -26,6 +26,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "threads.h" #include "debug.h" #include "gettime.h" +#include "porting.h" +#include "config.h" std::list<ILogOutput*> log_outputs[LMT_NUM_VALUES]; std::map<threadid_t, std::string> log_threadnames; @@ -139,6 +141,9 @@ public: void printbuf() { log_printline(m_lev, m_buf); +#ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_ERROR, PROJECT_NAME, "%s", m_buf.c_str()); +#endif } void bufchar(char c) diff --git a/src/lua/src/llex.c b/src/lua/src/llex.c index 6dc319358..98068c1aa 100644 --- a/src/lua/src/llex.c +++ b/src/lua/src/llex.c @@ -176,9 +176,15 @@ static void buffreplace (LexState *ls, char from, char to) { static void trydecpoint (LexState *ls, SemInfo *seminfo) { /* format error: try to update decimal point separator */ +#ifndef __ANDROID__ struct lconv *cv = localeconv(); +#endif char old = ls->decpoint; +#ifndef __ANDROID__ ls->decpoint = (cv ? cv->decimal_point[0] : '.'); +#else + ls->decpoint = '.'; +#endif buffreplace(ls, old, ls->decpoint); /* try updated decimal separator */ if (!luaO_str2d(luaZ_buffer(ls->buff), &seminfo->r)) { /* format error with correct decimal point: no more options */ diff --git a/src/main.cpp b/src/main.cpp index 5a0be2f75..1caa918b8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -84,10 +84,14 @@ with this program; if not, write to the Free Software Foundation, Inc., #ifdef USE_LEVELDB #include "database-leveldb.h" #endif + #if USE_REDIS #include "database-redis.h" #endif +#ifdef HAVE_TOUCHSCREENGUI +#include "touchscreengui.h" +#endif /* Settings. These are loaded from the config file. @@ -253,6 +257,11 @@ public: React to nothing here if a menu is active */ if (noMenuActive() == false) { +#ifdef HAVE_TOUCHSCREENGUI + if (m_touchscreengui != 0) { + m_touchscreengui->Toggle(false); + } +#endif return g_menumgr.preprocessEvent(event); } @@ -266,7 +275,16 @@ public: } } - if (event.EventType == irr::EET_MOUSE_INPUT_EVENT) { +#ifdef HAVE_TOUCHSCREENGUI + // case of touchscreengui we have to handle different events + if ((m_touchscreengui != 0) && + (event.EventType == irr::EET_TOUCH_INPUT_EVENT)) { + m_touchscreengui->translateEvent(event); + return true; + } +#endif + // handle mouse events + if(event.EventType == irr::EET_MOUSE_INPUT_EVENT) { if (noMenuActive() == false) { left_active = false; middle_active = false; @@ -293,8 +311,8 @@ public: } } } - if (event.EventType == irr::EET_LOG_TEXT_EVENT) { - dstream << "Irrlicht log: " << event.LogEvent.Text << std::endl; + if(event.EventType == irr::EET_LOG_TEXT_EVENT) { + dstream<< std::string("Irrlicht log: ") + std::string(event.LogEvent.Text)<<std::endl; return true; } /* always return false in order to continue processing events */ @@ -342,6 +360,9 @@ public: MyEventReceiver() { clearInput(); +#ifdef HAVE_TOUCHSCREENGUI + m_touchscreengui = NULL; +#endif } bool leftclicked; @@ -355,7 +376,12 @@ public: s32 mouse_wheel; +#ifdef HAVE_TOUCHSCREENGUI + TouchScreenGUI* m_touchscreengui; +#endif + private: + IrrlichtDevice *m_device; // The current state of keys KeyList keyIsDown; @@ -372,7 +398,8 @@ class RealInputHandler : public InputHandler public: RealInputHandler(IrrlichtDevice *device, MyEventReceiver *receiver): m_device(device), - m_receiver(receiver) + m_receiver(receiver), + m_mousepos(0,0) { } virtual bool isKeyDown(const KeyPress &keyCode) @@ -385,11 +412,21 @@ public: } virtual v2s32 getMousePos() { - return m_device->getCursorControl()->getPosition(); + if (m_device->getCursorControl()) { + return m_device->getCursorControl()->getPosition(); + } + else { + return m_mousepos; + } } virtual void setMousePos(s32 x, s32 y) { - m_device->getCursorControl()->setPosition(x, y); + if (m_device->getCursorControl()) { + m_device->getCursorControl()->setPosition(x, y); + } + else { + m_mousepos = v2s32(x,y); + } } virtual bool getLeftState() @@ -445,8 +482,9 @@ public: m_receiver->clearInput(); } private: - IrrlichtDevice *m_device; + IrrlichtDevice *m_device; MyEventReceiver *m_receiver; + v2s32 m_mousepos; }; class RandomInputHandler : public InputHandler @@ -855,8 +893,18 @@ int main(int argc, char *argv[]) porting::initializePaths(); +#ifdef __ANDROID__ + porting::initAndroid(); + + porting::setExternalStorageDir(porting::jnienv); + if (!fs::PathExists(porting::path_user)) { + fs::CreateDir(porting::path_user); + } + porting::copyAssets(); +#else // Create user data directory fs::CreateDir(porting::path_user); +#endif infostream << "path_share = " << porting::path_share << std::endl; infostream << "path_user = " << porting::path_user << std::endl; @@ -975,14 +1023,15 @@ int main(int argc, char *argv[]) // Initialize HTTP fetcher httpfetch_init(g_settings->getS32("curl_parallel_limit")); +#ifndef __ANDROID__ /* Run unit tests */ - if ((ENABLE_TESTS && cmd_args.getFlag("disable-unittests") == false) || cmd_args.getFlag("enable-unittests") == true) { run_tests(); } +#endif #ifdef _MSC_VER init_gettext((porting::path_share + DIR_DELIM + "locale").c_str(), g_settings->get("language"), argc, argv); @@ -1348,7 +1397,7 @@ int main(int argc, char *argv[]) List video modes if requested */ - MyEventReceiver receiver; + MyEventReceiver* receiver = new MyEventReceiver(); if (cmd_args.getFlag("videomodes")) { IrrlichtDevice *nulldevice; @@ -1361,7 +1410,7 @@ int main(int argc, char *argv[]) params.Fullscreen = false; params.Stencilbuffer = false; params.Vsync = vsync; - params.EventReceiver = &receiver; + params.EventReceiver = receiver; params.HighPrecisionFPU = g_settings->getBool("high_precision_fpu"); nulldevice = createDeviceEx(params); @@ -1397,15 +1446,13 @@ int main(int argc, char *argv[]) nulldevice->drop(); + delete receiver; return 0; } /* Create device and exit if creation failed */ - - IrrlichtDevice *device; - SIrrlichtCreationParameters params = SIrrlichtCreationParameters(); params.DriverType = driverType; params.WindowSize = core::dimension2d<u32>(screenW, screenH); @@ -1414,12 +1461,18 @@ int main(int argc, char *argv[]) params.Fullscreen = fullscreen; params.Stencilbuffer = false; params.Vsync = vsync; - params.EventReceiver = &receiver; + params.EventReceiver = receiver; params.HighPrecisionFPU = g_settings->getBool("high_precision_fpu"); +#ifdef __ANDROID__ + params.PrivateData = porting::app_global; + params.OGLES2ShaderPath = std::string(porting::path_user + DIR_DELIM + + "media" + DIR_DELIM + "Shaders" + DIR_DELIM).c_str(); +#endif - device = createDeviceEx(params); + IrrlichtDevice * device = createDeviceEx(params); if (device == 0) { + delete receiver; return 1; // could not create selected driver. } @@ -1476,10 +1529,11 @@ int main(int argc, char *argv[]) bool random_input = g_settings->getBool("random_input") || cmd_args.getFlag("random-input"); InputHandler *input = NULL; + if (random_input) { input = new RandomInputHandler(); } else { - input = new RealInputHandler(device, &receiver); + input = new RealInputHandler(device,receiver); } scene::ISceneManager* smgr = device->getSceneManager(); @@ -1564,7 +1618,8 @@ int main(int argc, char *argv[]) /* Menu-game loop */ - while (device->run() && kill == false) + while (device->run() && (kill == false) && + (g_gamecallback->shutdown_requested == false)) { // Set the window caption wchar_t* text = wgettext("Main Menu"); @@ -1612,7 +1667,9 @@ int main(int argc, char *argv[]) first_loop = false; // Cursor can be non-visible when coming from the game + #ifndef ANDROID device->getCursorControl()->setVisible(true); + #endif // Some stuff are left to scene manager when coming from the game // (map at least?) smgr->clear(); @@ -1661,10 +1718,9 @@ int main(int argc, char *argv[]) } infostream << "Waited for other menus" << std::endl; - GUIEngine* temp = new GUIEngine(device, guiroot, - &g_menumgr, smgr, &menudata, kill); + /* show main menu */ + GUIEngine mymenu(device, guiroot, &g_menumgr,smgr,&menudata,kill); - delete temp; //once finished you'll never end up here smgr->clear(); } @@ -1788,6 +1844,10 @@ int main(int argc, char *argv[]) /* Run game */ +#ifdef HAVE_TOUCHSCREENGUI + receiver->m_touchscreengui = new TouchScreenGUI(device, receiver); + g_touchscreengui = receiver->m_touchscreengui; +#endif the_game( kill, random_input, @@ -1805,6 +1865,11 @@ int main(int argc, char *argv[]) simple_singleplayer_mode ); smgr->clear(); +#ifdef HAVE_TOUCHSCREENGUI + delete g_touchscreengui; + g_touchscreengui = NULL; + receiver->m_touchscreengui = NULL; +#endif } //try catch(con::PeerNotFoundException &e) @@ -1849,7 +1914,7 @@ int main(int argc, char *argv[]) if (use_freetype) font->drop(); #endif - + delete receiver; #endif // !SERVER // Update configuration file diff --git a/src/mainmenumanager.h b/src/mainmenumanager.h index 78ae1fcfa..28fe1ac11 100644 --- a/src/mainmenumanager.h +++ b/src/mainmenumanager.h @@ -124,13 +124,17 @@ public: disconnect_requested(false), changepassword_requested(false), changevolume_requested(false), + shutdown_requested(false), device(a_device) { } virtual void exitToOS() { + shutdown_requested = true; +#ifndef __ANDROID__ device->closeDevice(); +#endif } virtual void disconnect() @@ -151,6 +155,7 @@ public: bool disconnect_requested; bool changepassword_requested; bool changevolume_requested; + bool shutdown_requested; IrrlichtDevice *device; }; diff --git a/src/modalMenu.h b/src/modalMenu.h index 251b7aa3b..2c512d3ca 100644 --- a/src/modalMenu.h +++ b/src/modalMenu.h @@ -21,6 +21,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #define MODALMENU_HEADER #include "irrlichttypes_extrabloated.h" +#ifdef HAVE_TOUCHSCREENGUI +#include "touchscreengui.h" +#endif class GUIModalMenu; @@ -101,6 +104,10 @@ public: Environment->removeFocus(this); m_menumgr->deletingMenu(this); this->remove(); +#ifdef HAVE_TOUCHSCREENGUI + if (g_touchscreengui) + g_touchscreengui->Show(); +#endif } void removeChildren() diff --git a/src/porting.cpp b/src/porting.cpp index 3c2a5c824..ad942b0bc 100644 --- a/src/porting.cpp +++ b/src/porting.cpp @@ -167,6 +167,7 @@ int getNumberOfProcessors() { } +#ifndef __ANDROID__ bool threadBindToProcessor(threadid_t tid, int pnumber) { #if defined(_WIN32) @@ -194,7 +195,7 @@ bool threadBindToProcessor(threadid_t tid, int pnumber) { pnumber, NULL) == 0; #elif defined(_AIX) - + return bindprocessor(BINDTHREAD, (tid_t)tid, pnumber) == 0; #elif defined(__hpux) || defined(hpux) @@ -203,11 +204,11 @@ bool threadBindToProcessor(threadid_t tid, int pnumber) { return pthread_processor_bind_np(PTHREAD_BIND_ADVISORY_NP, &answer, pnumber, tid) == 0; - + #elif defined(__APPLE__) struct thread_affinity_policy tapol; - + thread_port_t threadport = pthread_mach_thread_np(tid); tapol.affinity_tag = pnumber + 1; return thread_policy_set(threadport, THREAD_AFFINITY_POLICY, @@ -219,7 +220,7 @@ bool threadBindToProcessor(threadid_t tid, int pnumber) { #endif } - +#endif bool threadSetPriority(threadid_t tid, int prio) { #if defined(_WIN32) @@ -232,21 +233,21 @@ bool threadSetPriority(threadid_t tid, int prio) { CloseHandle(hThread); return success; - + #else struct sched_param sparam; int policy; - + if (pthread_getschedparam(tid, &policy, &sparam) != 0) return false; - + int min = sched_get_priority_min(policy); int max = sched_get_priority_max(policy); sparam.sched_priority = min + prio * (max - min) / THREAD_PRIORITY_HIGHEST; return pthread_setschedparam(tid, policy, &sparam) == 0; - + #endif } @@ -458,9 +459,15 @@ void initializePaths() { char buf[BUFSIZ]; memset(buf, 0, BUFSIZ); - assert(readlink("/proc/self/exe", buf, BUFSIZ-1) != -1); - pathRemoveFile(buf, '/'); - bindir = buf; + if (readlink("/proc/self/exe", buf, BUFSIZ-1) == -1) { + errorstream << "Unable to read bindir "<< std::endl; +#ifndef __ANDROID__ + assert("Unable to read bindir" == 0); +#endif + } else { + pathRemoveFile(buf, '/'); + bindir = buf; + } } // Find share directory from these. @@ -472,6 +479,9 @@ void initializePaths() trylist.push_back( bindir + DIR_DELIM + ".." + DIR_DELIM + "share" + DIR_DELIM + PROJECT_NAME); trylist.push_back(bindir + DIR_DELIM + ".."); +#ifdef __ANDROID__ + trylist.push_back(DIR_DELIM "sdcard" DIR_DELIM PROJECT_NAME); +#endif for(std::list<std::string>::const_iterator i = trylist.begin(); i != trylist.end(); i++) @@ -490,8 +500,11 @@ void initializePaths() path_share = trypath; break; } - +#ifndef __ANDROID__ path_user = std::string(getenv("HOME")) + DIR_DELIM + "." + PROJECT_NAME; +#else + path_user = std::string(DIR_DELIM "sdcard" DIR_DELIM PROJECT_NAME DIR_DELIM); +#endif /* OS X @@ -539,6 +552,7 @@ v2u32 getWindowSize() { return device->getVideoDriver()->getScreenSize(); } +#ifndef __ANDROID__ float getDisplayDensity() { float gui_scaling = g_settings->getFloat("gui_scaling"); @@ -562,6 +576,7 @@ v2u32 getDisplaySize() { return deskres; } #endif +#endif } //namespace porting diff --git a/src/porting.h b/src/porting.h index b5a5d00f2..9be09da75 100644 --- a/src/porting.h +++ b/src/porting.h @@ -53,12 +53,12 @@ with this program; if not, write to the Free Software Foundation, Inc., #ifdef _WIN32 #include <windows.h> - + #define sleep_ms(x) Sleep(x) #else #include <unistd.h> #include <stdint.h> //for uintptr_t - + #if (defined(linux) || defined(__linux)) && !defined(_GNU_SOURCE) #define _GNU_SOURCE #endif @@ -79,7 +79,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #endif #define sleep_ms(x) usleep(x*1000) - + #define THREAD_PRIORITY_LOWEST 0 #define THREAD_PRIORITY_BELOW_NORMAL 1 #define THREAD_PRIORITY_NORMAL 2 @@ -197,17 +197,17 @@ void initIrrlicht(irr::IrrlichtDevice * ); #define _WIN32_WINNT 0x0501 #endif #include <windows.h> - + inline u32 getTimeS() { return GetTickCount() / 1000; } - + inline u32 getTimeMs() { return GetTickCount(); } - + inline u32 getTimeUs() { LARGE_INTEGER freq, t; @@ -215,7 +215,7 @@ void initIrrlicht(irr::IrrlichtDevice * ); QueryPerformanceCounter(&t); return (double)(t.QuadPart) / ((double)(freq.QuadPart) / 1000000.0); } - + inline u32 getTimeNs() { LARGE_INTEGER freq, t; @@ -223,7 +223,7 @@ void initIrrlicht(irr::IrrlichtDevice * ); QueryPerformanceCounter(&t); return (double)(t.QuadPart) / ((double)(freq.QuadPart) / 1000000000.0); } - + #else // Posix #include <sys/time.h> #include <time.h> @@ -238,21 +238,21 @@ void initIrrlicht(irr::IrrlichtDevice * ); gettimeofday(&tv, NULL); return tv.tv_sec; } - + inline u32 getTimeMs() { struct timeval tv; gettimeofday(&tv, NULL); return tv.tv_sec * 1000 + tv.tv_usec / 1000; } - + inline u32 getTimeUs() { struct timeval tv; gettimeofday(&tv, NULL); return tv.tv_sec * 1000000 + tv.tv_usec; } - + inline u32 getTimeNs() { struct timespec ts; @@ -270,7 +270,7 @@ void initIrrlicht(irr::IrrlichtDevice * ); #endif return ts.tv_sec * 1000000000 + ts.tv_nsec; } - + /*#include <sys/timeb.h> inline u32 getTimeMs() { @@ -373,5 +373,9 @@ v2u32 getWindowSize(); } // namespace porting +#ifdef __ANDROID__ +#include "porting_android.h" +#endif + #endif // PORTING_HEADER diff --git a/src/porting_android.cpp b/src/porting_android.cpp new file mode 100644 index 000000000..96c9385a6 --- /dev/null +++ b/src/porting_android.cpp @@ -0,0 +1,295 @@ +/* +Minetest +Copyright (C) 2014 celeron55, Perttu Ahola <celeron55@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifndef __ANDROID__ +#error This file may only be compiled for android! +#endif + +#include "porting.h" +#include "porting_android.h" +#include "config.h" +#include "filesys.h" +#include "log.h" +#include <sstream> + +#ifdef GPROF +#include "prof.h" +#endif + +extern int main(int argc, char *argv[]); + +void android_main(android_app *app) +{ + int retval = 0; + porting::app_global = app; + + porting::setThreadName("MainThread"); + + try { + app_dummy(); + char *argv[] = { (char*) "minetest" }; + main(sizeof(argv) / sizeof(argv[0]), argv); + } + catch(BaseException e) { + std::stringstream msg; + msg << "Exception handled by main: " << e.what(); + const char* message = msg.str().c_str(); + __android_log_print(ANDROID_LOG_ERROR, PROJECT_NAME, "%s", message); + errorstream << msg << std::endl; + retval = -1; + } + catch(...) { + __android_log_print(ANDROID_LOG_ERROR, PROJECT_NAME, + "Some exception occured"); + errorstream << "Uncaught exception in main thread!" << std::endl; + retval = -1; + } + + porting::cleanupAndroid(); + errorstream << "Shutting down minetest." << std::endl; + exit(retval); +} + +/* handler for finished message box input */ +/* Intentionally NOT in namespace porting */ +/* TODO this doesn't work as expected, no idea why but there's a workaround */ +/* for it right now */ +extern "C" { + JNIEXPORT void JNICALL Java_org_minetest_MtNativeActivity_putMessageBoxResult( + JNIEnv * env, jclass thiz, jstring text) + { + errorstream << "Java_org_minetest_MtNativeActivity_putMessageBoxResult got: " + << std::string((const char*)env->GetStringChars(text,0)) + << std::endl; + } +} + +namespace porting { + +std::string path_storage = DIR_DELIM "sdcard" DIR_DELIM; + +android_app* app_global; +JNIEnv* jnienv; +jclass nativeActivity; + +jclass findClass(std::string classname) +{ + if (jnienv == 0) { + return 0; + } + + jclass nativeactivity = jnienv->FindClass("android/app/NativeActivity"); + jmethodID getClassLoader = + jnienv->GetMethodID(nativeactivity,"getClassLoader", + "()Ljava/lang/ClassLoader;"); + jobject cls = + jnienv->CallObjectMethod(app_global->activity->clazz, getClassLoader); + jclass classLoader = jnienv->FindClass("java/lang/ClassLoader"); + jmethodID findClass = + jnienv->GetMethodID(classLoader, "loadClass", + "(Ljava/lang/String;)Ljava/lang/Class;"); + jstring strClassName = + jnienv->NewStringUTF(classname.c_str()); + return (jclass) jnienv->CallObjectMethod(cls, findClass, strClassName); +} + +void copyAssets() +{ + jmethodID assetcopy = jnienv->GetMethodID(nativeActivity,"copyAssets","()V"); + + if (assetcopy == 0) { + assert("porting::copyAssets unable to find copy assets method" == 0); + } + + jnienv->CallVoidMethod(app_global->activity->clazz, assetcopy); +} + +void initAndroid() +{ + porting::jnienv = NULL; + JavaVM *jvm = app_global->activity->vm; + JavaVMAttachArgs lJavaVMAttachArgs; + lJavaVMAttachArgs.version = JNI_VERSION_1_6; + lJavaVMAttachArgs.name = "MinetestNativeThread"; + lJavaVMAttachArgs.group = NULL; +#ifdef NDEBUG + // This is a ugly hack as arm v7a non debuggable builds crash without this + // printf ... if someone finds out why please fix it! + infostream << "Attaching native thread. " << std::endl; +#endif + if ( jvm->AttachCurrentThread(&porting::jnienv, &lJavaVMAttachArgs) == JNI_ERR) { + errorstream << "Failed to attach native thread to jvm" << std::endl; + exit(-1); + } + + nativeActivity = findClass("org/minetest/minetest/MtNativeActivity"); + if (nativeActivity == 0) { + errorstream << + "porting::initAndroid unable to find java native activity class" << + std::endl; + } + +#ifdef GPROF + /* in the start-up code */ + __android_log_print(ANDROID_LOG_ERROR, PROJECT_NAME, + "Initializing GPROF profiler"); + monstartup("libminetest.so"); +#endif +} + +void cleanupAndroid() +{ + +#ifdef GPROF + errorstream << "Shutting down GPROF profiler" << std::endl; + setenv("CPUPROFILE", (path_user + DIR_DELIM + "gmon.out").c_str(), 1); + moncleanup(); +#endif + + JavaVM *jvm = app_global->activity->vm; + jvm->DetachCurrentThread(); +} + +void setExternalStorageDir(JNIEnv* lJNIEnv) +{ + // Android: Retrieve ablsolute path to external storage device (sdcard) + jclass ClassEnv = lJNIEnv->FindClass("android/os/Environment"); + jmethodID MethodDir = + lJNIEnv->GetStaticMethodID(ClassEnv, + "getExternalStorageDirectory","()Ljava/io/File;"); + jobject ObjectFile = lJNIEnv->CallStaticObjectMethod(ClassEnv, MethodDir); + jclass ClassFile = lJNIEnv->FindClass("java/io/File"); + + jmethodID MethodPath = + lJNIEnv->GetMethodID(ClassFile, "getAbsolutePath", + "()Ljava/lang/String;"); + jstring StringPath = + (jstring) lJNIEnv->CallObjectMethod(ObjectFile, MethodPath); + + const char *externalPath = lJNIEnv->GetStringUTFChars(StringPath, NULL); + std::string userPath(externalPath); + lJNIEnv->ReleaseStringUTFChars(StringPath, externalPath); + + path_storage = userPath; + path_user = userPath + DIR_DELIM + PROJECT_NAME; + path_share = userPath + DIR_DELIM + PROJECT_NAME; +} + +void showInputDialog(const std::string& acceptButton, const std::string& hint, + const std::string& current, int editType) +{ + jmethodID showdialog = jnienv->GetMethodID(nativeActivity,"showDialog", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)V"); + + if (showdialog == 0) { + assert("porting::showInputDialog unable to find java show dialog method" == 0); + } + + jstring jacceptButton = jnienv->NewStringUTF(acceptButton.c_str()); + jstring jhint = jnienv->NewStringUTF(hint.c_str()); + jstring jcurrent = jnienv->NewStringUTF(current.c_str()); + jint jeditType = editType; + + jnienv->CallVoidMethod(app_global->activity->clazz, showdialog, + jacceptButton, jhint, jcurrent, jeditType); +} + +int getInputDialogState() +{ + jmethodID dialogstate = jnienv->GetMethodID(nativeActivity, + "getDialogState", "()I"); + + if (dialogstate == 0) { + assert("porting::getInputDialogState unable to find java dialog state method" == 0); + } + + return jnienv->CallIntMethod(app_global->activity->clazz, dialogstate); +} + +std::string getInputDialogValue() +{ + jmethodID dialogvalue = jnienv->GetMethodID(nativeActivity, + "getDialogValue", "()Ljava/lang/String;"); + + if (dialogvalue == 0) { + assert("porting::getInputDialogValue unable to find java dialog value method" == 0); + } + + jobject result = jnienv->CallObjectMethod(app_global->activity->clazz, + dialogvalue); + + const char* javachars = jnienv->GetStringUTFChars((jstring) result,0); + std::string text(javachars); + jnienv->ReleaseStringUTFChars((jstring) result, javachars); + + return text; +} + +#if not defined(SERVER) +float getDisplayDensity() +{ + static bool firstrun = true; + static float value = 0; + + if (firstrun) { + jmethodID getDensity = jnienv->GetMethodID(nativeActivity, "getDensity", + "()F"); + + if (getDensity == 0) { + assert("porting::getDisplayDensity unable to find java getDensity method" == 0); + } + + value = jnienv->CallFloatMethod(app_global->activity->clazz, getDensity); + firstrun = false; + } + return value; +} + +v2u32 getDisplaySize() +{ + static bool firstrun = true; + static v2u32 retval; + + if (firstrun) { + jmethodID getDisplayWidth = jnienv->GetMethodID(nativeActivity, + "getDisplayWidth", "()I"); + + if (getDisplayWidth == 0) { + assert("porting::getDisplayWidth unable to find java getDisplayWidth method" == 0); + } + + retval.X = jnienv->CallIntMethod(app_global->activity->clazz, + getDisplayWidth); + + jmethodID getDisplayHeight = jnienv->GetMethodID(nativeActivity, + "getDisplayHeight", "()I"); + + if (getDisplayHeight == 0) { + assert("porting::getDisplayHeight unable to find java getDisplayHeight method" == 0); + } + + retval.Y = jnienv->CallIntMethod(app_global->activity->clazz, + getDisplayHeight); + + firstrun = false; + } + return retval; +} +#endif //SERVER +} diff --git a/src/porting_android.h b/src/porting_android.h new file mode 100644 index 000000000..bfdadfbff --- /dev/null +++ b/src/porting_android.h @@ -0,0 +1,81 @@ +/* +Minetest +Copyright (C) 2014 celeron55, Perttu Ahola <celeron55@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#ifndef __PORTING_ANDROID_H__ +#define __PORTING_ANDROID_H__ + +#ifndef __ANDROID__ +#error this include has to be included on android port only! +#endif + +#include <jni.h> +#include <android_native_app_glue.h> +#include <android/log.h> + +#include <string> + +namespace porting { +/** java app **/ +extern android_app *app_global; + +/** java <-> c++ interaction interface **/ +extern JNIEnv *jnienv; + +/** + * do initialization required on android only + */ +void initAndroid(); +void cleanupAndroid(); + +/** + * set storage dir on external sdcard# + * @param lJNIEnv environment from android + */ +void setExternalStorageDir(JNIEnv* lJNIEnv); + +/** + * use java function to copy media from assets to external storage + */ +void copyAssets(); + +/** + * show text input dialog in java + * @param acceptButton text to display on accept button + * @param hint hint to show + * @param current initial value to display + * @param editType type of texfield + * (1==multiline text input; 2==single line text input; 3=password field) + */ +void showInputDialog(const std::string& acceptButton, + const std::string& hint, const std::string& current, int editType); + +/** + * WORKAROUND for not working callbacks from java -> c++ + * get current state of input dialog + */ +int getInputDialogState(); + +/** + * WORKAROUND for not working callbacks from java -> c++ + * get text in current input dialog + */ +std::string getInputDialogValue(); + +} + +#endif diff --git a/src/tile.cpp b/src/tile.cpp index c9a36918a..17ec51614 100644 --- a/src/tile.cpp +++ b/src/tile.cpp @@ -32,6 +32,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/thread.h" #include "util/numeric.h" +#ifdef __ANDROID__ +#include <GLES/gl.h> +#endif + /* A cache from texture name to texture path */ @@ -702,6 +706,9 @@ u32 TextureSource::getTextureIdDirect(const std::string &name) if(baseimg != NULL) { +#ifdef __ANDROID__ + baseimg = Align2Npot2(baseimg, driver); +#endif // Create texture from resulting image t = driver->addTexture(name.c_str(), baseimg); baseimg->drop(); @@ -790,11 +797,17 @@ void TextureSource::rebuildImagesAndTextures() JMutexAutoLock lock(m_textureinfo_cache_mutex); video::IVideoDriver* driver = m_device->getVideoDriver(); + assert(driver != 0); // Recreate textures for(u32 i=0; i<m_textureinfo_cache.size(); i++){ TextureInfo *ti = &m_textureinfo_cache[i]; video::IImage *img = generateImageFromScratch(ti->name); +#ifdef __ANDROID__ + img = Align2Npot2(img,driver); + assert(img->getDimension().Height == npot2(img->getDimension().Height)); + assert(img->getDimension().Width == npot2(img->getDimension().Width)); +#endif // Create texture from resulting image video::ITexture *t = NULL; if(img) { @@ -816,6 +829,126 @@ video::ITexture* TextureSource::generateTextureFromMesh( video::IVideoDriver *driver = m_device->getVideoDriver(); assert(driver); +#ifdef __ANDROID__ + const GLubyte* renderstr = glGetString(GL_RENDERER); + std::string renderer((char*) renderstr); + + // use no render to texture hack + if ( + (renderer.find("Adreno") != std::string::npos) || + (renderer.find("Mali") != std::string::npos) || + (renderer.find("Immersion") != std::string::npos) || + (renderer.find("Tegra") != std::string::npos) || + g_settings->getBool("inventory_image_hack") + ) { + // Get a scene manager + scene::ISceneManager *smgr_main = m_device->getSceneManager(); + assert(smgr_main); + scene::ISceneManager *smgr = smgr_main->createNewSceneManager(); + assert(smgr); + + const float scaling = 0.2; + + scene::IMeshSceneNode* meshnode = + smgr->addMeshSceneNode(params.mesh, NULL, + -1, v3f(0,0,0), v3f(0,0,0), + v3f(1.0 * scaling,1.0 * scaling,1.0 * scaling), true); + meshnode->setMaterialFlag(video::EMF_LIGHTING, true); + meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true); + meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter); + meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter); + meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter); + + scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0, + params.camera_position, params.camera_lookat); + // second parameter of setProjectionMatrix (isOrthogonal) is ignored + camera->setProjectionMatrix(params.camera_projection_matrix, false); + + smgr->setAmbientLight(params.ambient_light); + smgr->addLightSceneNode(0, + params.light_position, + params.light_color, + params.light_radius*scaling); + + core::dimension2d<u32> screen = driver->getScreenSize(); + + // Render scene + driver->beginScene(true, true, video::SColor(0,0,0,0)); + driver->clearZBuffer(); + smgr->drawAll(); + + core::dimension2d<u32> partsize(screen.Width * scaling,screen.Height * scaling); + + irr::video::IImage* rawImage = + driver->createImage(irr::video::ECF_A8R8G8B8, partsize); + + u8* pixels = static_cast<u8*>(rawImage->lock()); + if (!pixels) + { + rawImage->drop(); + return NULL; + } + + core::rect<s32> source( + screen.Width /2 - (screen.Width * (scaling / 2)), + screen.Height/2 - (screen.Height * (scaling / 2)), + screen.Width /2 + (screen.Width * (scaling / 2)), + screen.Height/2 + (screen.Height * (scaling / 2)) + ); + + glReadPixels(source.UpperLeftCorner.X, source.UpperLeftCorner.Y, + partsize.Width, partsize.Height, GL_RGBA, + GL_UNSIGNED_BYTE, pixels); + + driver->endScene(); + + // Drop scene manager + smgr->drop(); + + unsigned int pixelcount = partsize.Width*partsize.Height; + + u8* runptr = pixels; + for (unsigned int i=0; i < pixelcount; i++) { + + u8 B = *runptr; + u8 G = *(runptr+1); + u8 R = *(runptr+2); + u8 A = *(runptr+3); + + //BGRA -> RGBA + *runptr = R; + runptr ++; + *runptr = G; + runptr ++; + *runptr = B; + runptr ++; + *runptr = A; + runptr ++; + } + + video::IImage* inventory_image = + driver->createImage(irr::video::ECF_A8R8G8B8, params.dim); + + rawImage->copyToScaling(inventory_image); + rawImage->drop(); + + video::ITexture *rtt = driver->addTexture(params.rtt_texture_name.c_str(), inventory_image); + inventory_image->drop(); + + if (rtt == NULL) { + errorstream << "TextureSource::generateTextureFromMesh(): failed to recreate texture from image: " << params.rtt_texture_name << std::endl; + return NULL; + } + + driver->makeColorKeyTexture(rtt, v2s32(0,0)); + + if(params.delete_texture_on_shutdown) + m_texture_trash.push_back(rtt); + + return rtt; + } +#endif + if(driver->queryFeature(video::EVDF_RENDER_TO_TARGET) == false) { static bool warned = false; @@ -840,7 +973,12 @@ video::ITexture* TextureSource::generateTextureFromMesh( } // Set render target - driver->setRenderTarget(rtt, false, true, video::SColor(0,0,0,0)); + if (!driver->setRenderTarget(rtt, false, true, video::SColor(0,0,0,0))) { + driver->removeTexture(rtt); + errorstream<<"TextureSource::generateTextureFromMesh(): " + <<"failed to set render target"<<std::endl; + return NULL; + } // Get a scene manager scene::ISceneManager *smgr_main = m_device->getSceneManager(); @@ -848,7 +986,9 @@ video::ITexture* TextureSource::generateTextureFromMesh( scene::ISceneManager *smgr = smgr_main->createNewSceneManager(); assert(smgr); - scene::IMeshSceneNode* meshnode = smgr->addMeshSceneNode(params.mesh, NULL, -1, v3f(0,0,0), v3f(0,0,0), v3f(1,1,1), true); + scene::IMeshSceneNode* meshnode = + smgr->addMeshSceneNode(params.mesh, NULL, + -1, v3f(0,0,0), v3f(0,0,0), v3f(1,1,1), true); meshnode->setMaterialFlag(video::EMF_LIGHTING, true); meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true); meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter); @@ -871,11 +1011,6 @@ video::ITexture* TextureSource::generateTextureFromMesh( smgr->drawAll(); driver->endScene(); - // NOTE: The scene nodes should not be dropped, otherwise - // smgr->drop() segfaults - /*cube->drop(); - camera->drop(); - light->drop();*/ // Drop scene manager smgr->drop(); @@ -938,6 +1073,57 @@ video::IImage* TextureSource::generateImageFromScratch(std::string name) return baseimg; } +#ifdef __ANDROID__ +#include <GLES/gl.h> +/** + * Check and align image to npot2 if required by hardware + * @param image image to check for npot2 alignment + * @param driver driver to use for image operations + * @return image or copy of image aligned to npot2 + */ +video::IImage * Align2Npot2(video::IImage * image, + video::IVideoDriver* driver) +{ + if(image == NULL) { + return image; + } + + core::dimension2d<u32> dim = image->getDimension(); + + std::string extensions = (char*) glGetString(GL_EXTENSIONS); + if (extensions.find("GL_OES_texture_npot") != std::string::npos) { + return image; + } + + unsigned int height = npot2(dim.Height); + unsigned int width = npot2(dim.Width); + + if ((dim.Height == height) && + (dim.Width == width)) { + return image; + } + + if (dim.Height > height) { + height *= 2; + } + + if (dim.Width > width) { + width *= 2; + } + + video::IImage *targetimage = + driver->createImage(video::ECF_A8R8G8B8, + core::dimension2d<u32>(width, height)); + + if (targetimage != NULL) { + image->copyToScaling(targetimage); + } + image->drop(); + return targetimage; +} + +#endif + bool TextureSource::generateImage(std::string part_of_name, video::IImage *& baseimg) { video::IVideoDriver* driver = m_device->getVideoDriver(); @@ -947,21 +1133,9 @@ bool TextureSource::generateImage(std::string part_of_name, video::IImage *& bas if(part_of_name.size() == 0 || part_of_name[0] != '[') { video::IImage *image = m_sourcecache.getOrLoad(part_of_name, m_device); - - if (image != NULL) { - if (!driver->queryFeature(irr::video::EVDF_TEXTURE_NPOT)) { - core::dimension2d<u32> dim = image->getDimension(); - - - if ((dim.Height %2 != 0) || - (dim.Width %2 != 0)) { - infostream << "TextureSource::generateImage " - << part_of_name << " size npot2 x=" << dim.Width - << " y=" << dim.Height << std::endl; - } - } - } - +#ifdef __ANDROID__ + image = Align2Npot2(image,driver); +#endif if (image == NULL) { if (part_of_name != "") { if (part_of_name.find("_normal.png") == std::string::npos){ @@ -1284,7 +1458,16 @@ bool TextureSource::generateImage(std::string part_of_name, video::IImage *& bas video::IImage *img_right = generateImageFromScratch(imagename_right); assert(img_top && img_left && img_right); +#ifdef __ANDROID__ + assert(img_top->getDimension().Height == npot2(img_top->getDimension().Height)); + assert(img_top->getDimension().Width == npot2(img_top->getDimension().Width)); + + assert(img_left->getDimension().Height == npot2(img_left->getDimension().Height)); + assert(img_left->getDimension().Width == npot2(img_left->getDimension().Width)); + assert(img_right->getDimension().Height == npot2(img_right->getDimension().Height)); + assert(img_right->getDimension().Width == npot2(img_right->getDimension().Width)); +#endif // Create textures from images video::ITexture *texture_top = driver->addTexture( (imagename_top + "__temp__").c_str(), img_top); diff --git a/src/tile.h b/src/tile.h index 486d193fd..29c6b69f2 100644 --- a/src/tile.h +++ b/src/tile.h @@ -131,6 +131,25 @@ public: IWritableTextureSource* createTextureSource(IrrlichtDevice *device); +#ifdef __ANDROID__ +/** + * @param size get next npot2 value + * @return npot2 value + */ +inline unsigned int npot2(unsigned int size) +{ + if (size == 0) return 0; + unsigned int npot = 1; + + while ((size >>= 1) > 0) { + npot <<= 1; + } + return npot; +} + +video::IImage * Align2Npot2(video::IImage * image, video::IVideoDriver* driver); +#endif + enum MaterialType{ TILE_MATERIAL_BASIC, TILE_MATERIAL_ALPHA, diff --git a/src/touchscreengui.cpp b/src/touchscreengui.cpp new file mode 100644 index 000000000..a04b7fe5e --- /dev/null +++ b/src/touchscreengui.cpp @@ -0,0 +1,690 @@ +/* +Copyright (C) 2014 sapier + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "touchscreengui.h" +#include "irrlichttypes.h" +#include "irr_v2d.h" +#include "log.h" +#include "keycode.h" +#include "settings.h" +#include "gettime.h" +#include "util/numeric.h" +#include "porting.h" + +#include <iostream> +#include <algorithm> + +#include <ISceneCollisionManager.h> + +using namespace irr::core; + +extern Settings *g_settings; + +const char** touchgui_button_imagenames = (const char*[]) { + "up_arrow.png", + "down_arrow.png", + "left_arrow.png", + "right_arrow.png", + "jump_btn.png", + "down.png", + "inventory_btn.png", + "chat_btn.png" +}; + +static irr::EKEY_CODE id2keycode(touch_gui_button_id id) +{ + std::string key = ""; + switch (id) { + case forward_id: + key = "forward"; + break; + case left_id: + key = "left"; + break; + case right_id: + key = "right"; + break; + case backward_id: + key = "backward"; + break; + case jump_id: + key = "jump"; + break; + case inventory_id: + key = "inventory"; + break; + case chat_id: + key = "chat"; + break; + case crunch_id: + key = "sneak"; + break; + } + assert(key != ""); + return keyname_to_keycode(g_settings->get("keymap_" + key).c_str()); +} + +TouchScreenGUI *g_touchscreengui; + +TouchScreenGUI::TouchScreenGUI(IrrlichtDevice *device, IEventReceiver* receiver): + m_device(device), + m_guienv(device->getGUIEnvironment()), + m_camera_yaw(0.0), + m_camera_pitch(0.0), + m_visible(false), + m_move_id(-1), + m_receiver(receiver) +{ + for (unsigned int i=0; i < after_last_element_id; i++) { + m_buttons[i].guibutton = 0; + m_buttons[i].repeatcounter = -1; + } + + m_screensize = m_device->getVideoDriver()->getScreenSize(); +} + +void TouchScreenGUI::loadButtonTexture(button_info* btn, const char* path) +{ + unsigned int tid; + video::ITexture *texture = m_texturesource->getTexture(path,&tid); + if (texture) { + btn->guibutton->setUseAlphaChannel(true); + btn->guibutton->setImage(texture); + btn->guibutton->setPressedImage(texture); + btn->guibutton->setScaleImage(true); + btn->guibutton->setDrawBorder(false); + btn->guibutton->setText(L""); + } +} + +void TouchScreenGUI::initButton(touch_gui_button_id id, rect<s32> button_rect, + std::wstring caption, bool immediate_release ) +{ + + button_info* btn = &m_buttons[id]; + btn->guibutton = m_guienv->addButton(button_rect, 0, id, caption.c_str()); + btn->guibutton->grab(); + btn->repeatcounter = -1; + btn->keycode = id2keycode(id); + btn->immediate_release = immediate_release; + btn->ids.clear(); + + loadButtonTexture(btn,touchgui_button_imagenames[id]); +} + +static int getMaxControlPadSize(float density) { + return 200 * density * g_settings->getFloat("gui_scaling"); +} + +void TouchScreenGUI::init(ISimpleTextureSource* tsrc, float density) +{ + assert(tsrc != 0); + + u32 control_pad_size = + MYMIN((2 * m_screensize.Y) / 3,getMaxControlPadSize(density)); + + u32 button_size = control_pad_size / 3; + m_visible = true; + m_texturesource = tsrc; + m_control_pad_rect = rect<s32>(0, m_screensize.Y - 3 * button_size, + 3 * button_size, m_screensize.Y); + /* + draw control pad + 0 1 2 + 3 4 5 + for now only 0, 1, 2, and 4 are used + */ + int number = 0; + for (int y = 0; y < 2; ++y) + for (int x = 0; x < 3; ++x, ++number) { + rect<s32> button_rect( + x * button_size, m_screensize.Y - button_size * (2 - y), + (x + 1) * button_size, m_screensize.Y - button_size * (1 - y) + ); + touch_gui_button_id id = after_last_element_id; + std::wstring caption; + switch (number) { + case 0: + id = left_id; + caption = L"<"; + break; + case 1: + id = forward_id; + caption = L"^"; + break; + case 2: + id = right_id; + caption = L">"; + break; + case 4: + id = backward_id; + caption = L"v"; + break; + } + if (id != after_last_element_id) { + initButton(id, button_rect, caption, false); + } + } + + /* init inventory button */ + initButton(inventory_id, + rect<s32>(0, m_screensize.Y - (button_size/2), + (button_size/2), m_screensize.Y), L"inv", true); + + /* init jump button */ + initButton(jump_id, + rect<s32>(m_screensize.X-(1.75*button_size), + m_screensize.Y - (0.5*button_size), + m_screensize.X-(0.25*button_size), + m_screensize.Y), + L"x",false); + + /* init crunch button */ + initButton(crunch_id, + rect<s32>(m_screensize.X-(3.25*button_size), + m_screensize.Y - (0.5*button_size), + m_screensize.X-(1.75*button_size), + m_screensize.Y), + L"H",false); + + /* init chat button */ + initButton(chat_id, + rect<s32>(m_screensize.X-(1.5*button_size), 0, + m_screensize.X, button_size), + L"Chat", true); +} + +touch_gui_button_id TouchScreenGUI::getButtonID(s32 x, s32 y) +{ + IGUIElement* rootguielement = m_guienv->getRootGUIElement(); + + if (rootguielement != NULL) { + gui::IGUIElement *element = + rootguielement->getElementFromPoint(core::position2d<s32>(x,y)); + + if (element) { + for (unsigned int i=0; i < after_last_element_id; i++) { + if (element == m_buttons[i].guibutton) { + return (touch_gui_button_id) i; + } + } + } + } + return after_last_element_id; +} + +touch_gui_button_id TouchScreenGUI::getButtonID(int eventID) +{ + for (unsigned int i=0; i < after_last_element_id; i++) { + button_info* btn = &m_buttons[i]; + + std::vector<int>::iterator id = + std::find(btn->ids.begin(),btn->ids.end(), eventID); + + if (id != btn->ids.end()) + return (touch_gui_button_id) i; + } + + return after_last_element_id; +} + +bool TouchScreenGUI::isHUDButton(const SEvent &event) +{ + // check if hud item is pressed + for (std::map<int,rect<s32> >::iterator iter = m_hud_rects.begin(); + iter != m_hud_rects.end(); iter++) { + if (iter->second.isPointInside( + v2s32(event.TouchInput.X, + event.TouchInput.Y) + )) { + if ( iter->first < 8) { + SEvent* translated = new SEvent(); + memset(translated,0,sizeof(SEvent)); + translated->EventType = irr::EET_KEY_INPUT_EVENT; + translated->KeyInput.Key = (irr::EKEY_CODE) (KEY_KEY_1 + iter->first); + translated->KeyInput.Control = false; + translated->KeyInput.Shift = false; + translated->KeyInput.PressedDown = true; + m_receiver->OnEvent(*translated); + m_hud_ids[event.TouchInput.ID] = translated->KeyInput.Key; + delete translated; + return true; + } + } + } + return false; +} + +bool TouchScreenGUI::isReleaseHUDButton(int eventID) +{ + std::map<int,irr::EKEY_CODE>::iterator iter = m_hud_ids.find(eventID); + + if (iter != m_hud_ids.end()) { + SEvent* translated = new SEvent(); + memset(translated,0,sizeof(SEvent)); + translated->EventType = irr::EET_KEY_INPUT_EVENT; + translated->KeyInput.Key = iter->second; + translated->KeyInput.PressedDown = false; + translated->KeyInput.Control = false; + translated->KeyInput.Shift = false; + m_receiver->OnEvent(*translated); + m_hud_ids.erase(iter); + delete translated; + return true; + } + return false; +} + +void TouchScreenGUI::ButtonEvent(touch_gui_button_id button, + int eventID, bool action) +{ + button_info* btn = &m_buttons[button]; + SEvent* translated = new SEvent(); + memset(translated,0,sizeof(SEvent)); + translated->EventType = irr::EET_KEY_INPUT_EVENT; + translated->KeyInput.Key = btn->keycode; + translated->KeyInput.Control = false; + translated->KeyInput.Shift = false; + translated->KeyInput.Char = 0; + + /* add this event */ + if (action) { + assert(std::find(btn->ids.begin(),btn->ids.end(), eventID) == btn->ids.end()); + + btn->ids.push_back(eventID); + + if (btn->ids.size() > 1) return; + + btn->repeatcounter = 0; + translated->KeyInput.PressedDown = true; + translated->KeyInput.Key = btn->keycode; + m_receiver->OnEvent(*translated); + } + /* remove event */ + if ((!action) || (btn->immediate_release)) { + + std::vector<int>::iterator pos = + std::find(btn->ids.begin(),btn->ids.end(), eventID); + /* has to be in touch list */ + assert(pos != btn->ids.end()); + btn->ids.erase(pos); + + if (btn->ids.size() > 0) { return; } + + translated->KeyInput.PressedDown = false; + btn->repeatcounter = -1; + m_receiver->OnEvent(*translated); + } + delete translated; +} + +void TouchScreenGUI::translateEvent(const SEvent &event) +{ + if (!m_visible) { + infostream << "TouchScreenGUI::translateEvent got event but not visible?!" << std::endl; + return; + } + + if (event.EventType != EET_TOUCH_INPUT_EVENT) { + return; + } + + if (event.TouchInput.Event == ETIE_PRESSED_DOWN) { + + /* add to own copy of eventlist ... + * android would provide this information but irrlicht guys don't + * wanna design a efficient interface + */ + id_status toadd; + toadd.id = event.TouchInput.ID; + toadd.X = event.TouchInput.X; + toadd.Y = event.TouchInput.Y; + m_known_ids.push_back(toadd); + + int eventID = event.TouchInput.ID; + + touch_gui_button_id button = + getButtonID(event.TouchInput.X, event.TouchInput.Y); + + /* handle button events */ + if (button != after_last_element_id) { + ButtonEvent(button,eventID,true); + } + else if (isHUDButton(event)) + { + /* already handled in isHUDButton() */ + } + /* handle non button events */ + else { + /* if we don't already have a moving point make this the moving one */ + if (m_move_id == -1) { + m_move_id = event.TouchInput.ID; + m_move_has_really_moved = false; + m_move_downtime = getTimeMs(); + m_move_downlocation = v2s32(event.TouchInput.X, event.TouchInput.Y); + m_move_sent_as_mouse_event = false; + } + } + + m_pointerpos[event.TouchInput.ID] = v2s32(event.TouchInput.X, event.TouchInput.Y); + } + else if (event.TouchInput.Event == ETIE_LEFT_UP) { + verbosestream << "Up event for pointerid: " << event.TouchInput.ID << std::endl; + + touch_gui_button_id button = getButtonID(event.TouchInput.ID); + + /* handle button events */ + if (button != after_last_element_id) { + ButtonEvent(button,event.TouchInput.ID,false); + } + /* handle hud button events */ + else if (isReleaseHUDButton(event.TouchInput.ID)) { + /* nothing to do here */ + } + /* handle the point used for moving view */ + else if (event.TouchInput.ID == m_move_id) { + m_move_id = -1; + + /* if this pointer issued a mouse event issue symmetric release here */ + if (m_move_sent_as_mouse_event) { + SEvent* translated = new SEvent; + memset(translated,0,sizeof(SEvent)); + translated->EventType = EET_MOUSE_INPUT_EVENT; + translated->MouseInput.X = m_move_downlocation.X; + translated->MouseInput.Y = m_move_downlocation.Y; + translated->MouseInput.Shift = false; + translated->MouseInput.Control = false; + translated->MouseInput.ButtonStates = 0; + translated->MouseInput.Event = EMIE_LMOUSE_LEFT_UP; + m_receiver->OnEvent(*translated); + delete translated; + } + else { + /* do double tap detection */ + doubleTapDetection(); + } + } + else { + infostream + << "TouchScreenGUI::translateEvent released unknown button: " + << event.TouchInput.ID << std::endl; + } + + for (std::vector<id_status>::iterator iter = m_known_ids.begin(); + iter != m_known_ids.end(); iter++) { + if (iter->id == event.TouchInput.ID) { + m_known_ids.erase(iter); + break; + } + } + } + else { + assert(event.TouchInput.Event == ETIE_MOVED); + int move_idx = event.TouchInput.ID; + + if (m_pointerpos[event.TouchInput.ID] == + v2s32(event.TouchInput.X, event.TouchInput.Y)) { + return; + } + + if (m_move_id != -1) { + if ((event.TouchInput.ID == m_move_id) && + (!m_move_sent_as_mouse_event)) { + + double distance = sqrt( + (m_pointerpos[event.TouchInput.ID].X - event.TouchInput.X) * + (m_pointerpos[event.TouchInput.ID].X - event.TouchInput.X) + + (m_pointerpos[event.TouchInput.ID].Y - event.TouchInput.Y) * + (m_pointerpos[event.TouchInput.ID].Y - event.TouchInput.Y)); + + if ((distance > g_settings->getU16("touchscreen_threshold")) || + (m_move_has_really_moved)) { + m_move_has_really_moved = true; + s32 X = event.TouchInput.X; + s32 Y = event.TouchInput.Y; + + // update camera_yaw and camera_pitch + s32 dx = X - m_pointerpos[event.TouchInput.ID].X; + s32 dy = Y - m_pointerpos[event.TouchInput.ID].Y; + + /* adapt to similar behaviour as pc screen */ + double d = g_settings->getFloat("mouse_sensitivity") *4; + double old_yaw = m_camera_yaw; + double old_pitch = m_camera_pitch; + + m_camera_yaw -= dx * d; + m_camera_pitch = MYMIN(MYMAX( m_camera_pitch + (dy * d),-180),180); + + while (m_camera_yaw < 0) + m_camera_yaw += 360; + + while (m_camera_yaw > 360) + m_camera_yaw -= 360; + + // update shootline + m_shootline = m_device + ->getSceneManager() + ->getSceneCollisionManager() + ->getRayFromScreenCoordinates(v2s32(X, Y)); + m_pointerpos[event.TouchInput.ID] = v2s32(X, Y); + } + } + else if ((event.TouchInput.ID == m_move_id) && + (m_move_sent_as_mouse_event)) { + m_shootline = m_device + ->getSceneManager() + ->getSceneCollisionManager() + ->getRayFromScreenCoordinates( + v2s32(event.TouchInput.X,event.TouchInput.Y)); + } + } + else { + handleChangedButton(event); + } + } +} + +void TouchScreenGUI::handleChangedButton(const SEvent &event) +{ + for (unsigned int i = 0; i < after_last_element_id; i++) { + + if (m_buttons[i].ids.empty()) { + continue; + } + for(std::vector<int>::iterator iter = m_buttons[i].ids.begin(); + iter != m_buttons[i].ids.end(); iter++) { + + if (event.TouchInput.ID == *iter) { + + int current_button_id = + getButtonID(event.TouchInput.X, event.TouchInput.Y); + + if (current_button_id == i) { + continue; + } + + /* remove old button */ + ButtonEvent((touch_gui_button_id) i,*iter,false); + + if (current_button_id == after_last_element_id) { + return; + } + ButtonEvent((touch_gui_button_id) current_button_id,*iter,true); + return; + + } + } + } + + int current_button_id = getButtonID(event.TouchInput.X, event.TouchInput.Y); + + if (current_button_id == after_last_element_id) { + return; + } + + button_info* btn = &m_buttons[current_button_id]; + if (std::find(btn->ids.begin(),btn->ids.end(), event.TouchInput.ID) == btn->ids.end()) { + ButtonEvent((touch_gui_button_id) current_button_id,event.TouchInput.ID,true); + } + +} + +bool TouchScreenGUI::doubleTapDetection() +{ + m_key_events[0].down_time = m_key_events[1].down_time; + m_key_events[0].x = m_key_events[1].x; + m_key_events[0].y = m_key_events[1].y; + m_key_events[1].down_time = m_move_downtime; + m_key_events[1].x = m_move_downlocation.X; + m_key_events[1].y = m_move_downlocation.Y; + + u32 delta = porting::getDeltaMs(m_key_events[0].down_time,getTimeMs()); + if (delta > 400) + return false; + + double distance = sqrt( + (m_key_events[0].x - m_key_events[1].x) * (m_key_events[0].x - m_key_events[1].x) + + (m_key_events[0].y - m_key_events[1].y) * (m_key_events[0].y - m_key_events[1].y)); + + + if (distance >(20 + g_settings->getU16("touchscreen_threshold"))) + return false; + + SEvent* translated = new SEvent(); + memset(translated,0,sizeof(SEvent)); + translated->EventType = EET_MOUSE_INPUT_EVENT; + translated->MouseInput.X = m_key_events[0].x; + translated->MouseInput.Y = m_key_events[0].y; + translated->MouseInput.Shift = false; + translated->MouseInput.Control = false; + translated->MouseInput.ButtonStates = EMBSM_RIGHT; + + // update shootline + m_shootline = m_device + ->getSceneManager() + ->getSceneCollisionManager() + ->getRayFromScreenCoordinates(v2s32(m_key_events[0].x, m_key_events[0].y)); + + translated->MouseInput.Event = EMIE_RMOUSE_PRESSED_DOWN; + verbosestream << "TouchScreenGUI::translateEvent right click press" << std::endl; + m_receiver->OnEvent(*translated); + + translated->MouseInput.ButtonStates = 0; + translated->MouseInput.Event = EMIE_RMOUSE_LEFT_UP; + verbosestream << "TouchScreenGUI::translateEvent right click release" << std::endl; + m_receiver->OnEvent(*translated); + delete translated; + return true; + +} + +TouchScreenGUI::~TouchScreenGUI() +{ + for (unsigned int i=0; i < after_last_element_id; i++) { + button_info* btn = &m_buttons[i]; + if (btn->guibutton != 0) { + btn->guibutton->drop(); + btn->guibutton = NULL; + } + } +} + +void TouchScreenGUI::step(float dtime) +{ + /* simulate keyboard repeats */ + for (unsigned int i=0; i < after_last_element_id; i++) { + button_info* btn = &m_buttons[i]; + + if (btn->ids.size() > 0) { + btn->repeatcounter += dtime; + + if (btn->repeatcounter < 0.2) continue; + + btn->repeatcounter = 0; + SEvent translated; + memset(&translated,0,sizeof(SEvent)); + translated.EventType = irr::EET_KEY_INPUT_EVENT; + translated.KeyInput.Key = btn->keycode; + translated.KeyInput.PressedDown = false; + m_receiver->OnEvent(translated); + + translated.KeyInput.PressedDown = true; + m_receiver->OnEvent(translated); + } + } + + /* if a new placed pointer isn't moved for some time start digging */ + if ((m_move_id != -1) && + (!m_move_has_really_moved) && + (!m_move_sent_as_mouse_event)) { + + u32 delta = porting::getDeltaMs(m_move_downtime,getTimeMs()); + + if (delta > MIN_DIG_TIME_MS) { + m_shootline = m_device + ->getSceneManager() + ->getSceneCollisionManager() + ->getRayFromScreenCoordinates( + v2s32(m_move_downlocation.X,m_move_downlocation.Y)); + + SEvent translated; + memset(&translated,0,sizeof(SEvent)); + translated.EventType = EET_MOUSE_INPUT_EVENT; + translated.MouseInput.X = m_move_downlocation.X; + translated.MouseInput.Y = m_move_downlocation.Y; + translated.MouseInput.Shift = false; + translated.MouseInput.Control = false; + translated.MouseInput.ButtonStates = EMBSM_LEFT; + translated.MouseInput.Event = EMIE_LMOUSE_PRESSED_DOWN; + verbosestream << "TouchScreenGUI::step left click press" << std::endl; + m_receiver->OnEvent(translated); + m_move_sent_as_mouse_event = true; + } + } +} + +void TouchScreenGUI::resetHud() +{ + m_hud_rects.clear(); +} + +void TouchScreenGUI::registerHudItem(int index, const rect<s32> &rect) +{ + m_hud_rects[index] = rect; +} + +void TouchScreenGUI::Toggle(bool visible) +{ + m_visible = visible; + for (unsigned int i=0; i < after_last_element_id; i++) { + button_info* btn = &m_buttons[i]; + if (btn->guibutton != 0) { + btn->guibutton->setVisible(visible); + } + } +} + +void TouchScreenGUI::Hide() +{ + Toggle(false); +} + +void TouchScreenGUI::Show() +{ + Toggle(true); +} diff --git a/src/touchscreengui.h b/src/touchscreengui.h new file mode 100644 index 000000000..8dc482034 --- /dev/null +++ b/src/touchscreengui.h @@ -0,0 +1,160 @@ +/* +Copyright (C) 2014 sapier + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifndef TOUCHSCREENGUI_HEADER +#define TOUCHSCREENGUI_HEADER + +#include <IGUIEnvironment.h> +#include <IGUIButton.h> +#include <IEventReceiver.h> + +#include <vector> +#include <map> + +#include "game.h" +#include "tile.h" + +using namespace irr; +using namespace irr::core; +using namespace irr::gui; + +typedef enum { + forward_id = 0, + backward_id, + left_id, + right_id, + jump_id, + crunch_id, + inventory_id, + chat_id, + after_last_element_id +} touch_gui_button_id; + +#define MIN_DIG_TIME_MS 500 +#define MAX_TOUCH_COUNT 64 + +extern const char** touchgui_button_imagenames; + +class TouchScreenGUI +{ +public: + TouchScreenGUI(IrrlichtDevice *device, IEventReceiver* receiver); + ~TouchScreenGUI(); + + void translateEvent(const SEvent &event); + + void init(ISimpleTextureSource* tsrc,float density); + + double getYaw() { return m_camera_yaw; } + double getPitch() { return m_camera_pitch; } + line3d<f32> getShootline() { return m_shootline; } + + void step(float dtime); + void resetHud(); + void registerHudItem(int index, const rect<s32> &rect); + void Toggle(bool visible); + + void Hide(); + void Show(); + +private: + IrrlichtDevice* m_device; + IGUIEnvironment* m_guienv; + IEventReceiver* m_receiver; + ISimpleTextureSource* m_texturesource; + v2u32 m_screensize; + std::map<int,rect<s32> > m_hud_rects; + std::map<int,irr::EKEY_CODE> m_hud_ids; + bool m_visible; // is the gui visible + + /* value in degree */ + double m_camera_yaw; + double m_camera_pitch; + + line3d<f32> m_shootline; + + rect<s32> m_control_pad_rect; + + int m_move_id; + bool m_move_has_really_moved; + s32 m_move_downtime; + bool m_move_sent_as_mouse_event; + v2s32 m_move_downlocation; + + struct button_info { + float repeatcounter; + irr::EKEY_CODE keycode; + std::vector<int> ids; + IGUIButton* guibutton; + bool immediate_release; + }; + + button_info m_buttons[after_last_element_id]; + + /* gui button detection */ + touch_gui_button_id getButtonID(s32 x, s32 y); + + /* gui button by eventID */ + touch_gui_button_id getButtonID(int eventID); + + /* check if a button has changed */ + void handleChangedButton(const SEvent &event); + + /* initialize a button */ + void initButton(touch_gui_button_id id, rect<s32> button_rect, + std::wstring caption, bool immediate_release ); + + /* load texture */ + void loadButtonTexture(button_info* btn, const char* path); + + struct id_status{ + int id; + int X; + int Y; + }; + + /* vector to store known ids and their initial touch positions*/ + std::vector<id_status> m_known_ids; + + /* handle a button event */ + void ButtonEvent(touch_gui_button_id bID, int eventID, bool action); + + /* handle pressed hud buttons */ + bool isHUDButton(const SEvent &event); + + /* handle released hud buttons */ + bool isReleaseHUDButton(int eventID); + + /* handle double taps */ + bool doubleTapDetection(); + + /* doubleclick detection variables */ + struct key_event { + unsigned int down_time; + s32 x; + s32 y; + }; + + /* array for saving last known position of a pointer */ + v2s32 m_pointerpos[MAX_TOUCH_COUNT]; + + /* array for doubletap detection */ + key_event m_key_events[2]; +}; +extern TouchScreenGUI *g_touchscreengui; +#endif diff --git a/src/util/string.cpp b/src/util/string.cpp index a3888c9ce..363a15e65 100644 --- a/src/util/string.cpp +++ b/src/util/string.cpp @@ -29,6 +29,58 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "../hex.h" #include "../porting.h" +#ifdef __ANDROID__ +const wchar_t* wide_chars = L" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"; + +int wctomb(char *s, wchar_t wc) +{ + for (unsigned int j = 0; j < (sizeof(wide_chars)/sizeof(wchar_t));j++) { + if (wc == wide_chars[j]) { + *s = (char) (j+32); + return 1; + } + else if (wc == L'\n') { + *s = '\n'; + return 1; + } + } + return -1; +} + +int mbtowc(wchar_t *pwc, const char *s, size_t n) +{ + std::wstring intermediate = narrow_to_wide(s); + + if (intermediate.length() > 0) { + *pwc = intermediate[0]; + return 1; + } + else { + return -1; + } +} + +std::wstring narrow_to_wide(const std::string& mbs) { + size_t wcl = mbs.size(); + + std::wstring retval = L""; + + for (unsigned int i = 0; i < wcl; i++) { + if (((unsigned char) mbs[i] >31) && + ((unsigned char) mbs[i] < 127)) { + + retval += wide_chars[(unsigned char) mbs[i] -32]; + } + //handle newline + else if (mbs[i] == '\n') { + retval += L'\n'; + } + } + + return retval; +} +#else + std::wstring narrow_to_wide(const std::string& mbs) { size_t wcl = mbs.size(); @@ -40,6 +92,35 @@ std::wstring narrow_to_wide(const std::string& mbs) return *wcs; } +#endif + +#ifdef __ANDROID__ +std::string wide_to_narrow(const std::wstring& wcs) { + size_t mbl = wcs.size()*4; + + std::string retval = ""; + for (unsigned int i = 0; i < wcs.size(); i++) { + wchar_t char1 = (wchar_t) wcs[i]; + + if (char1 == L'\n') { + retval += '\n'; + continue; + } + + for (unsigned int j = 0; j < wcslen(wide_chars);j++) { + wchar_t char2 = (wchar_t) wide_chars[j]; + + if (char1 == char2) { + char toadd = (j+32); + retval += toadd; + break; + } + } + } + + return retval; +} +#else std::string wide_to_narrow(const std::wstring& wcs) { size_t mbl = wcs.size()*4; @@ -53,6 +134,8 @@ std::string wide_to_narrow(const std::wstring& wcs) return *mbs; } +#endif + // Get an sha-1 hash of the player's name combined with // the password entered. That's what the server uses as // their password. (Exception : if the password field is diff --git a/src/version.cpp b/src/version.cpp index e5de8a61e..83b0a4c40 100644 --- a/src/version.cpp +++ b/src/version.cpp @@ -20,19 +20,15 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "version.h" #include "config.h" -#ifdef USE_CMAKE_CONFIG_H - -#include "cmake_config_githash.h" - const char *minetest_version_simple = CMAKE_VERSION_STRING; const char *minetest_version_hash = CMAKE_VERSION_GITHASH; + +#ifdef USE_CMAKE_CONFIG_H const char *minetest_build_info = "VER=" CMAKE_VERSION_GITHASH " " CMAKE_BUILD_INFO; - +#elif defined(ANDROID) +const char *minetest_build_info = "android jni"; #else - -const char *minetest_version_simple = "unknown"; -const char *minetest_version_hash = "unknown"; const char *minetest_build_info = "non-cmake"; #endif diff --git a/textures/base/pack/chat_btn.png b/textures/base/pack/chat_btn.png Binary files differnew file mode 100644 index 000000000..a452bd0b9 --- /dev/null +++ b/textures/base/pack/chat_btn.png diff --git a/textures/base/pack/down.png b/textures/base/pack/down.png Binary files differnew file mode 100644 index 000000000..7d13857ca --- /dev/null +++ b/textures/base/pack/down.png diff --git a/textures/base/pack/down_arrow.png b/textures/base/pack/down_arrow.png Binary files differnew file mode 100644 index 000000000..7b34b1b3a --- /dev/null +++ b/textures/base/pack/down_arrow.png diff --git a/textures/base/pack/inventory_btn.png b/textures/base/pack/inventory_btn.png Binary files differnew file mode 100644 index 000000000..58f892b55 --- /dev/null +++ b/textures/base/pack/inventory_btn.png diff --git a/textures/base/pack/jump_btn.png b/textures/base/pack/jump_btn.png Binary files differnew file mode 100644 index 000000000..573fd1a10 --- /dev/null +++ b/textures/base/pack/jump_btn.png diff --git a/textures/base/pack/ladder_down.png b/textures/base/pack/ladder_down.png Binary files differnew file mode 100644 index 000000000..e3cb7dbd1 --- /dev/null +++ b/textures/base/pack/ladder_down.png diff --git a/textures/base/pack/left_arrow.png b/textures/base/pack/left_arrow.png Binary files differnew file mode 100644 index 000000000..30ec327a2 --- /dev/null +++ b/textures/base/pack/left_arrow.png diff --git a/textures/base/pack/right_arrow.png b/textures/base/pack/right_arrow.png Binary files differnew file mode 100644 index 000000000..04594b6e3 --- /dev/null +++ b/textures/base/pack/right_arrow.png diff --git a/textures/base/pack/up_arrow.png b/textures/base/pack/up_arrow.png Binary files differnew file mode 100644 index 000000000..070529a41 --- /dev/null +++ b/textures/base/pack/up_arrow.png |