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!
This commit is contained in:
sapier 2014-04-21 14:10:59 +02:00
parent ff36071d93
commit 1cc40c0a7c
66 changed files with 4425 additions and 162 deletions

13
.gitignore vendored

@ -64,3 +64,16 @@ locale/
*.layout *.layout
*.o *.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

@ -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>

724
build/android/Makefile Normal file

@ -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

16
build/android/build.xml Normal file

@ -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>

@ -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

@ -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)

@ -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);
}

@ -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)

@ -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

@ -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 \

@ -0,0 +1 @@
target=android-10

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

@ -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>

@ -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>

@ -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;
}
}

@ -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();
}
}

@ -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;
}

@ -16,7 +16,7 @@
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. --51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
local function create_world_formspec(dialogdata) local function delete_world_formspec(dialogdata)
local retval = local retval =
"size[12,6,true]" .. "size[12,6,true]" ..
@ -27,7 +27,7 @@ local function create_world_formspec(dialogdata)
return retval return retval
end end
local function create_world_buttonhandler(this, fields) local function delete_world_buttonhandler(this, fields)
if fields["world_delete_confirm"] then if fields["world_delete_confirm"] then
if this.data.delete_index > 0 and 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(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") assert(index_to_del ~= nil and type(index_to_del) == "number")
local retval = dialog_create("sp_create_world", local retval = dialog_create("delete_world",
create_world_formspec, delete_world_formspec,
create_world_buttonhandler, delete_world_buttonhandler,
nil) nil)
retval.data.delete_name = name_to_del retval.data.delete_name = name_to_del
retval.data.delete_index = index_to_del retval.data.delete_index = index_to_del

@ -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()

@ -31,7 +31,6 @@ end
local function dlg_confirm_reset_btnhandler(this, fields, dialogdata) local function dlg_confirm_reset_btnhandler(this, fields, dialogdata)
if fields["dlg_reset_singleplayer_confirm"] ~= nil then if fields["dlg_reset_singleplayer_confirm"] ~= nil then
local worldlist = core.get_worlds() local worldlist = core.get_worlds()
local found_singleplayerworld = false local found_singleplayerworld = false
@ -63,19 +62,43 @@ local function dlg_confirm_reset_btnhandler(this, fields, dialogdata)
this.parent:show() this.parent:show()
this:hide() this:hide()
this:delete() this:delete()
return true
end end
local function showconfirm_reset(tabview) local function showconfirm_reset(tabview)
local new_dlg = dialog_create("reset_spworld", local new_dlg = dialog_create("reset_spworld",
dlg_confirm_reset_formspec, dlg_confirm_reset_formspec,
dlg_confirm_reset_btnhandler, dlg_confirm_reset_btnhandler,
nil, nil)
tabview) new_dlg:set_parent(tabview)
tabview:hide() tabview:hide()
new_dlg:show() new_dlg:show()
end 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 function formspec(tabview, name, tabdata)
local tab_string = local tab_string =
@ -93,8 +116,6 @@ local function formspec(tabview, name, tabdata)
.. dump(core.setting_getbool("preload_item_visuals")) .. "]".. .. dump(core.setting_getbool("preload_item_visuals")) .. "]"..
"checkbox[1,2.5;cb_particles;".. fgettext("Enable Particles") .. ";" "checkbox[1,2.5;cb_particles;".. fgettext("Enable Particles") .. ";"
.. dump(core.setting_getbool("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]" .. "box[4.25,0;3.25,2.5;#999999]" ..
"checkbox[4.5,0;cb_mipmapping;".. fgettext("Mip-Mapping") .. ";" "checkbox[4.5,0;cb_mipmapping;".. fgettext("Mip-Mapping") .. ";"
.. dump(core.setting_getbool("mip_map")) .. "]".. .. dump(core.setting_getbool("mip_map")) .. "]"..
@ -106,16 +127,25 @@ local function formspec(tabview, name, tabdata)
.. dump(core.setting_getbool("trilinear_filter")) .. "]".. .. dump(core.setting_getbool("trilinear_filter")) .. "]"..
"box[7.75,0;4,4;#999999]" .. "box[7.75,0;4,4;#999999]" ..
"checkbox[8,0;cb_shaders;".. fgettext("Shaders") .. ";" "checkbox[8,0;cb_shaders;".. fgettext("Shaders") .. ";"
.. dump(core.setting_getbool("enable_shaders")) .. "]".. .. dump(core.setting_getbool("enable_shaders")) .. "]"
"button[1,4.5;2.25,0.5;btn_change_keys;".. fgettext("Change keys") .. "]" if not ANDROID then
local android = false
if android then
tab_string = tab_string .. tab_string = tab_string ..
"box[4.25,2.75;3.25,2.5;#999999]" .. "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() .. "]"
if ANDROID then
tab_string = tab_string ..
"box[4.25,2.75;3.25,2.15;#999999]" ..
"checkbox[4.5,2.75;cb_touchscreen_target;".. fgettext("Touch free target") .. ";" "checkbox[4.5,2.75;cb_touchscreen_target;".. fgettext("Touch free target") .. ";"
.. dump(core.setting_getbool("touchtarget")) .. "]" .. .. dump(core.setting_getbool("touchtarget")) .. "]"
"button[8,4.5;3.75,0.5;btn_reset_singleplayer;".. fgettext("Reset singleplayer world") .. "]"
end end
if core.setting_get("touchscreen_threshold") ~= nil then 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"]) core.setting_set("enable_particles", fields["cb_particles"])
return true return true
end end
if fields["cb_finite_liquid"] then
core.setting_set("liquid_finite", fields["cb_finite_liquid"])
return true
end
if fields["cb_bumpmapping"] then if fields["cb_bumpmapping"] then
core.setting_set("enable_bumpmapping", fields["cb_bumpmapping"]) core.setting_set("enable_bumpmapping", fields["cb_bumpmapping"])
end end
@ -235,14 +261,23 @@ local function handle_settings_buttons(this, fields, tabname, tabdata)
core.setting_set("touchtarget", fields["cb_touchscreen_target"]) core.setting_set("touchtarget", fields["cb_touchscreen_target"])
return true return true
end end
if fields["dd_touchthreshold"] then
core.setting_set("touchscreen_threshold",fields["dd_touchthreshold"])
return true
end
if fields["btn_reset_singleplayer"] then if fields["btn_reset_singleplayer"] then
print("sp reset")
showconfirm_reset(this) showconfirm_reset(this)
return true return true
end 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 end
tab_settings = { tab_settings = {

@ -22,7 +22,6 @@ local function get_formspec(tabview, name, tabdata)
local render_details = dump(core.setting_getbool("public_serverlist")) local render_details = dump(core.setting_getbool("public_serverlist"))
retval = retval .. retval = retval ..
"label[0,3.0;".. fgettext("Address/Port") .. "]"..
"label[8,0.5;".. fgettext("Name/Password") .. "]" .. "label[8,0.5;".. fgettext("Name/Password") .. "]" ..
"field[0.25,3.25;5.5,0.5;te_address;;" ..core.setting_get("address") .."]" .. "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") .."]" .. "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")) .. "]" dump(core.setting_getbool("free_move")) .. "]"
-- buttons -- buttons
retval = retval .. 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 return retval
end end
@ -74,19 +74,21 @@ end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
local function main_button_handler(tabview, fields, name, tabdata) local function main_button_handler(tabview, fields, name, tabdata)
if fields["btn_start_singleplayer"] then if fields["btn_start_singleplayer"] then
gamedata.selected_world = gamedata.worldindex gamedata.selected_world = gamedata.worldindex
gamedata.singleplayer = true gamedata.singleplayer = true
core.start() core.start()
return true
end end
if fields["favourites"] ~= nil then if fields["favourites"] ~= nil then
local event = core.explode_textlist_event(fields["favourites"]) local event = core.explode_textlist_event(fields["favourites"])
if event.type == "CHG" then if event.type == "CHG" then
if event.index <= #maintab_favorites then if event.index <= #menudata.favorites then
local address = maintab_favorites[event.index].address local address = menudata.favorites[event.index].address
local port = maintab_favorites[event.index].port local port = menudata.favorites[event.index].port
if address ~= nil and if address ~= nil and
port ~= nil then port ~= nil then
@ -97,7 +99,7 @@ local function main_button_handler(tabview, fields, name, tabdata)
tabdata.fav_selected = event.index tabdata.fav_selected = event.index
end end
end end
return return true
end end
if fields["cb_public_serverlist"] ~= nil then 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 if core.setting_getbool("public_serverlist") then
asyncOnlineFavourites() asyncOnlineFavourites()
else else
maintab_favorites = core.get_favorites("local") menudata.favorites = core.get_favorites("local")
end end
return return true
end end
if fields["cb_creative"] then if fields["cb_creative"] then
core.setting_set("creative_mode", fields["cb_creative"]) core.setting_set("creative_mode", fields["cb_creative"])
return true
end end
if fields["cb_damage"] then if fields["cb_damage"] then
core.setting_set("enable_damage", fields["cb_damage"]) core.setting_set("enable_damage", fields["cb_damage"])
return true
end end
if fields["cb_fly_mode"] then if fields["cb_fly_mode"] then
core.setting_set("free_move", fields["cb_fly_mode"]) core.setting_set("free_move", fields["cb_fly_mode"])
return true
end end
if fields["btn_mp_connect"] ~= nil or 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.setting_set("remote_port",fields["te_port"])
core.start() 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
end end

130
doc/README.android Normal file

@ -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

@ -47,7 +47,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "serialization.h" #include "serialization.h"
#include "util/serialize.h" #include "util/serialize.h"
#include "config.h" #include "config.h"
#include "cmake_config_githash.h"
#include "util/directiontables.h" #include "util/directiontables.h"
#include "util/pointedthing.h" #include "util/pointedthing.h"
#include "version.h" #include "version.h"

@ -8,7 +8,10 @@
#define PROJECT_NAME "Minetest" #define PROJECT_NAME "Minetest"
#define RUN_IN_PLACE 0 #define RUN_IN_PLACE 0
#define STATIC_SHAREDIR ""
#define USE_GETTEXT 0 #define USE_GETTEXT 0
#ifndef USE_SOUND #ifndef USE_SOUND
#define USE_SOUND 0 #define USE_SOUND 0
#endif #endif
@ -17,8 +20,9 @@
#define USE_CURL 0 #define USE_CURL 0
#endif #endif
#define USE_FREETYPE 0 #ifndef USE_FREETYPE
#define STATIC_SHAREDIR "" #define USE_FREETYPE 0
#endif
#ifndef USE_LEVELDB #ifndef USE_LEVELDB
#define USE_LEVELDB 0 #define USE_LEVELDB 0
@ -70,5 +74,11 @@
#define VERSION_EXTRA_STRING CMAKE_VERSION_EXTRA_STRING #define VERSION_EXTRA_STRING CMAKE_VERSION_EXTRA_STRING
#endif #endif
#ifdef __ANDROID__
#include "android_version.h"
#else
#include "cmake_config_githash.h"
#endif
#endif #endif

@ -18,6 +18,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
*/ */
#include "porting.h"
#include "debug.h" #include "debug.h"
#include "exceptions.h" #include "exceptions.h"
#include "threads.h" #include "threads.h"
@ -27,7 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <map> #include <map>
#include "jthread/jmutex.h" #include "jthread/jmutex.h"
#include "jthread/jmutexautolock.h" #include "jthread/jmutexautolock.h"
#include "config.h"
/* /*
Debug output Debug output
*/ */
@ -95,6 +96,9 @@ public:
} }
std::streamsize xsputn(const char *s, std::streamsize n) 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++) for(int i=0; i<DEBUGSTREAM_COUNT; i++)
{ {
if(g_debugstreams[i] == stderr && m_disable_stderr) if(g_debugstreams[i] == stderr && m_disable_stderr)

@ -154,6 +154,7 @@ void set_default_settings(Settings *settings)
settings->setDefault("curl_timeout", "5000"); settings->setDefault("curl_timeout", "5000");
settings->setDefault("curl_parallel_limit", "8"); settings->setDefault("curl_parallel_limit", "8");
settings->setDefault("curl_file_download_timeout", "300000"); settings->setDefault("curl_file_download_timeout", "300000");
settings->setDefault("curl_verify_cert", "true");
settings->setDefault("enable_remote_media_server", "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("high_precision_fpu", "true");
settings->setDefault("language", ""); 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) void late_init_default_settings(Settings* settings)

@ -427,6 +427,13 @@ void draw_scene(video::IVideoDriver* driver, scene::ISceneManager* smgr,
bool draw_crosshair = ((player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) && bool draw_crosshair = ((player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) &&
(camera.getCameraMode() != CAMERA_MODE_THIRD_FRONT)); (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"); std::string draw_mode = g_settings->get("3d_mode");
smgr->drawAll(); smgr->drawAll();

@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <errno.h> #include <errno.h>
#include <fstream> #include <fstream>
#include "log.h" #include "log.h"
#include "config.h"
namespace fs namespace fs
{ {
@ -401,7 +402,11 @@ std::string TempPath()
compatible with lua's os.tmpname which under the default compatible with lua's os.tmpname which under the default
configuration hardcodes mkstemp("/tmp/lua_XXXXXX"). 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 #endif

@ -70,6 +70,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "drawscene.h" #include "drawscene.h"
#include "content_cao.h" #include "content_cao.h"
#ifdef HAVE_TOUCHSCREENGUI
#include "touchscreengui.h"
#endif
/* /*
Text input system 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, static void show_chat_menu(GUIFormSpecMenu** cur_formspec,
InventoryManager *invmgr, IGameDef *gamedef, InventoryManager *invmgr, IGameDef *gamedef,
IWritableTextureSource* tsrc, IrrlichtDevice * device, IWritableTextureSource* tsrc, IrrlichtDevice * device,
Client* client, std::string text) Client* client, std::string text)
{ {
std::string formspec = std::string formspec =
FORMSPEC_VERSION_STRING FORMSPEC_VERSION_STRING
"size[11,5.5,true]" SIZE_TAG
"field[3,2.35;6,0.5;f_text;;" + text + "]" "field[3,2.35;6,0.5;f_text;;" + text + "]"
"button_exit[4,3;3,0.5;btn_send;" + wide_to_narrow(wstrgettext("Proceed")) + "]" "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 =
std::string(FORMSPEC_VERSION_STRING) + std::string(FORMSPEC_VERSION_STRING) +
"size[11,5.5,true]" SIZE_TAG
"bgcolor[#320000b4;true]" "bgcolor[#320000b4;true]"
"label[4.85,1.35;You died.]" "label[4.85,1.35;You died.]"
"button_exit[4,3;3,0.5;btn_respawn;" + gettext("Respawn") + "]" "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, IWritableTextureSource* tsrc, IrrlichtDevice * device,
bool singleplayermode) 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" std::string control_text = wide_to_narrow(wstrgettext("Default Controls:\n"
"- WASD: move\n" "- WASD: move\n"
"- Space: jump/climb\n" "- Space: jump/climb\n"
@ -1002,11 +1027,11 @@ static void show_pause_menu(GUIFormSpecMenu** cur_formspec,
"- Mouse wheel: select item\n" "- Mouse wheel: select item\n"
"- T: chat\n" "- T: chat\n"
)); ));
#endif
float ypos = singleplayermode ? 1.0 : 0.5; float ypos = singleplayermode ? 1.0 : 0.5;
std::ostringstream os; 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;" << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;"
<< wide_to_narrow(wstrgettext("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")) << "]"; << wide_to_narrow(wstrgettext("Exit to Menu")) << "]";
os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;" os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;"
<< wide_to_narrow(wstrgettext("Exit to 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" << "textarea[0.4,0.25;3.5,6;;" << "Minetest\n"
<< minetest_build_info << "\n" << minetest_build_info << "\n"
<< "path_user = " << wrap_rows(porting::path_user, 20) << "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); server->step(dtime);
// End condition // End condition
if(client.getState() == LC_Init){ if(client.getState() == LC_Init) {
could_connect = true; could_connect = true;
break; break;
} }
// Break conditions // Break conditions
if(client.accessDenied()){ if(client.accessDenied()) {
error_message = L"Access denied. Reason: " error_message = L"Access denied. Reason: "
+client.accessDeniedReason(); +client.accessDeniedReason();
errorstream<<wide_to_narrow(error_message)<<std::endl; errorstream<<wide_to_narrow(error_message)<<std::endl;
break; break;
} }
if(input->wasKeyDown(EscapeKey)){ if(input->wasKeyDown(EscapeKey) || input->wasKeyDown(CancelKey)) {
connect_aborted = true; connect_aborted = true;
infostream<<"Connect aborted [Escape]"<<std::endl; infostream<<"Connect aborted [Escape]"<<std::endl;
break; break;
@ -1310,8 +1335,8 @@ void the_game(bool &kill, bool random_input, InputHandler *input,
/* /*
Handle failure to connect Handle failure to connect
*/ */
if(!could_connect){ if(!could_connect) {
if(error_message == L"" && !connect_aborted){ if(error_message == L"" && !connect_aborted) {
error_message = L"Connection failed"; error_message = L"Connection failed";
errorstream<<wide_to_narrow(error_message)<<std::endl; 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"); float fps_max = g_settings->getFloat("fps_max");
bool cloud_menu_background = g_settings->getBool("menu_clouds"); bool cloud_menu_background = g_settings->getBool("menu_clouds");
u32 lasttime = device->getTimer()->getTime(); u32 lasttime = device->getTimer()->getTime();
while(device->run()) while (device->run()) {
{
f32 dtime = 0.033; // in seconds f32 dtime = 0.033; // in seconds
if (cloud_menu_background) { if (cloud_menu_background) {
u32 time = device->getTimer()->getTime(); u32 time = device->getTimer()->getTime();
@ -1343,29 +1367,29 @@ void the_game(bool &kill, bool random_input, InputHandler *input,
} }
// Update client and server // Update client and server
client.step(dtime); client.step(dtime);
if(server != NULL) if (server != NULL)
server->step(dtime); server->step(dtime);
// End condition // End condition
if(client.mediaReceived() && if (client.mediaReceived() &&
client.itemdefReceived() && client.itemdefReceived() &&
client.nodedefReceived()){ client.nodedefReceived()) {
got_content = true; got_content = true;
break; break;
} }
// Break conditions // Break conditions
if(client.accessDenied()){ if (client.accessDenied()) {
error_message = L"Access denied. Reason: " error_message = L"Access denied. Reason: "
+client.accessDeniedReason(); +client.accessDeniedReason();
errorstream<<wide_to_narrow(error_message)<<std::endl; errorstream<<wide_to_narrow(error_message)<<std::endl;
break; break;
} }
if(client.getState() < LC_Init){ if (client.getState() < LC_Init) {
error_message = L"Client disconnected"; error_message = L"Client disconnected";
errorstream<<wide_to_narrow(error_message)<<std::endl; errorstream<<wide_to_narrow(error_message)<<std::endl;
break; break;
} }
if(input->wasKeyDown(EscapeKey)){ if (input->wasKeyDown(EscapeKey) || input->wasKeyDown(CancelKey)) {
content_aborted = true; content_aborted = true;
infostream<<"Connect aborted [Escape]"<<std::endl; infostream<<"Connect aborted [Escape]"<<std::endl;
break; break;
@ -1548,6 +1572,11 @@ void the_game(bool &kill, bool random_input, InputHandler *input,
guitext_profiler->setVisible(false); guitext_profiler->setVisible(false);
guitext_profiler->setWordWrap(true); guitext_profiler->setWordWrap(true);
#ifdef HAVE_TOUCHSCREENGUI
if (g_touchscreengui)
g_touchscreengui->init(tsrc,porting::getDisplayDensity());
#endif
/* /*
Some statistics are collected in these Some statistics are collected in these
*/ */
@ -1641,7 +1670,8 @@ void the_game(bool &kill, bool random_input, InputHandler *input,
for(;;) for(;;)
{ {
if(device->run() == false || kill == true) if(device->run() == false || kill == true ||
g_gamecallback->shutdown_requested)
break; break;
v2u32 screensize = driver->getScreenSize(); 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 handler step() (used by the random input generator)
input->step(dtime); 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" // Increase timer for doubleclick of "jump"
if(g_settings->getBool("doubletap_jump") && jump_timer <= 0.2) 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(); inventoryloc.setCurrentPlayer();
current_formspec->setFormSpec(fs_src->getForm(), inventoryloc); current_formspec->setFormSpec(fs_src->getForm(), inventoryloc);
} }
else if(input->wasKeyDown(EscapeKey)) else if(input->wasKeyDown(EscapeKey) || input->wasKeyDown(CancelKey))
{ {
show_pause_menu(&current_formspec, &client, gamedef, tsrc, device, show_pause_menu(&current_formspec, &client, gamedef, tsrc, device,
simple_singleplayer_mode); simple_singleplayer_mode);
@ -2214,21 +2253,29 @@ void the_game(bool &kill, bool random_input, InputHandler *input,
float turn_amount = 0; float turn_amount = 0;
if((device->isWindowActive() && noMenuActive()) || random_input) if((device->isWindowActive() && noMenuActive()) || random_input)
{ {
#ifndef __ANDROID__
if(!random_input) if(!random_input)
{ {
// Mac OSX gets upset if this is set every frame // Mac OSX gets upset if this is set every frame
if(device->getCursorControl()->isVisible()) if(device->getCursorControl()->isVisible())
device->getCursorControl()->setVisible(false); device->getCursorControl()->setVisible(false);
} }
#endif
if(first_loop_after_window_activation){ if(first_loop_after_window_activation){
//infostream<<"window active, first loop"<<std::endl; //infostream<<"window active, first loop"<<std::endl;
first_loop_after_window_activation = false; first_loop_after_window_activation = false;
} } else {
else{ #ifdef HAVE_TOUCHSCREENGUI
s32 dx = input->getMousePos().X - (driver->getScreenSize().Width/2); if (g_touchscreengui) {
s32 dy = input->getMousePos().Y - (driver->getScreenSize().Height/2); camera_yaw = g_touchscreengui->getYaw();
if(invert_mouse || camera.getCameraMode() == CAMERA_MODE_THIRD_FRONT) { 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; dy = -dy;
} }
//infostream<<"window active, pos difference "<<dx<<","<<dy<<std::endl; //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); d = rangelim(d, 0.01, 100.0);
camera_yaw -= dx*d; camera_yaw -= dx*d;
camera_pitch += dy*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;
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), input->setMousePos((driver->getScreenSize().Width/2),
(driver->getScreenSize().Height/2)); (driver->getScreenSize().Height/2));
} }
else{ else{
#ifndef ANDROID
// Mac OSX gets upset if this is set every frame // Mac OSX gets upset if this is set every frame
if(device->getCursorControl()->isVisible() == false) if(device->getCursorControl()->isVisible() == false)
device->getCursorControl()->setVisible(true); device->getCursorControl()->setVisible(true);
#endif
//infostream<<"window inactive"<<std::endl; //infostream<<"window inactive"<<std::endl;
first_loop_after_window_activation = true; 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, core::line3d<f32> shootline(camera_position,
camera_position + camera_direction * BS * (d+1)); camera_position + camera_direction * BS * (d+1));
// prevent player pointing anything in front-view // prevent player pointing anything in front-view
if (camera.getCameraMode() == CAMERA_MODE_THIRD_FRONT) if (camera.getCameraMode() == CAMERA_MODE_THIRD_FRONT)
shootline = core::line3d<f32>(0,0,0,0,0,0); 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; ClientActiveObject *selected_object = NULL;
PointedThing pointed = getPointedThing( PointedThing pointed = getPointedThing(
@ -3156,8 +3217,9 @@ void the_game(bool &kill, bool random_input, InputHandler *input,
} }
else if(show_hud || show_chat) else if(show_hud || show_chat)
{ {
u16 fps = (1.0/dtime_avg1);
std::ostringstream os(std::ios_base::binary); 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->setText(narrow_to_wide(os.str()).c_str());
guitext->setVisible(true); guitext->setVisible(true);
} }

@ -32,6 +32,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "clouds.h" #include "clouds.h"
#include "httpfetch.h" #include "httpfetch.h"
#include "util/numeric.h" #include "util/numeric.h"
#ifdef __ANDROID__
#include "tile.h"
#include <GLES/gl.h>
#endif
#include <IGUIStaticText.h> #include <IGUIStaticText.h>
#include <ICameraSceneNode.h> #include <ICameraSceneNode.h>
@ -83,6 +87,16 @@ video::ITexture* MenuTextureSource::getTexture(const std::string &name, u32 *id)
if(name.empty()) if(name.empty())
return NULL; return NULL;
m_to_delete.insert(name); 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()); return m_driver->getTexture(name.c_str());
} }
@ -266,6 +280,10 @@ void GUIEngine::run()
sleep_ms(25); sleep_ms(25);
m_script->step(); m_script->step();
#ifdef __ANDROID__
m_menu->getAndroidUIInput();
#endif
} }
} }

@ -88,6 +88,9 @@ GUIFormSpecMenu::GUIFormSpecMenu(irr::IrrlichtDevice* dev,
m_ext_ptr(ext_ptr), m_ext_ptr(ext_ptr),
m_font(dev->getGUIEnvironment()->getSkin()->getFont()), m_font(dev->getGUIEnvironment()->getSkin()->getFont()),
m_formspec_version(0) m_formspec_version(0)
#ifdef __ANDROID__
,m_JavaDialogFieldName(L"")
#endif
{ {
current_keys_pending.key_down = false; current_keys_pending.key_down = false;
current_keys_pending.key_up = false; current_keys_pending.key_up = false;
@ -1878,6 +1881,52 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
setInitialFocus(); 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 GUIFormSpecMenu::ItemSpec GUIFormSpecMenu::getItemAtPos(v2s32 p) const
{ {
core::rect<s32> imgrect(0,0,imgsize.X,imgsize.Y); 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]; 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 item_i = i + s.start_item_i;
s32 x = (i%s.geom.X) * spacing.X; s32 x = (i%s.geom.X) * spacing.X;
s32 y = (i/s.geom.X) * spacing.Y; s32 y = (i/s.geom.X) * spacing.Y;
@ -2051,8 +2099,6 @@ void GUIFormSpecMenu::drawMenu()
} }
} }
m_pointer = m_device->getCursorControl()->getPosition();
updateSelectedItem(); updateSelectedItem();
gui::IGUISkin* skin = Environment->getSkin(); gui::IGUISkin* skin = Environment->getSkin();
@ -2195,6 +2241,11 @@ void GUIFormSpecMenu::drawMenu()
*/ */
gui::IGUIElement::draw(); 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 Draw fields/buttons tooltips
*/ */
@ -2491,7 +2542,8 @@ bool GUIFormSpecMenu::preprocessEvent(const SEvent& event)
// Fix Esc/Return key being eaten by checkboxen and tables // Fix Esc/Return key being eaten by checkboxen and tables
if(event.EventType==EET_KEY_INPUT_EVENT) { if(event.EventType==EET_KEY_INPUT_EVENT) {
KeyPress kp(event.KeyInput); 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) { || event.KeyInput.Key==KEY_RETURN) {
gui::IGUIElement *focused = Environment->getFocus(); gui::IGUIElement *focused = Environment->getFocus();
if (focused && isMyChild(focused) && 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; return false;
} }
@ -2584,8 +2786,8 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
{ {
if(event.EventType==EET_KEY_INPUT_EVENT) { if(event.EventType==EET_KEY_INPUT_EVENT) {
KeyPress kp(event.KeyInput); KeyPress kp(event.KeyInput);
if (event.KeyInput.PressedDown && (kp == EscapeKey || if (event.KeyInput.PressedDown && ( (kp == EscapeKey) ||
kp == getKeySetting("keymap_inventory"))) { (kp == getKeySetting("keymap_inventory")) || (kp == CancelKey))) {
if (m_allowclose) { if (m_allowclose) {
doPause = false; doPause = false;
acceptInput(quit_mode_cancel); acceptInput(quit_mode_cancel);
@ -3015,6 +3217,38 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
return Parent ? Parent->OnEvent(event) : false; 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 GUIFormSpecMenu::parseColor(const std::string &value, video::SColor &color,
bool quiet) bool quiet)
{ {

@ -151,7 +151,7 @@ class GUIFormSpecMenu : public GUIModalMenu
{ {
} }
FieldSpec(const std::wstring &name, const std::wstring &label, FieldSpec(const std::wstring &name, const std::wstring &label,
const std::wstring &fdeflt, int id) : const std::wstring &fdeflt, int id) :
fname(name), fname(name),
flabel(label), flabel(label),
fdefault(fdeflt), fdefault(fdeflt),
@ -274,6 +274,10 @@ public:
static bool parseColor(const std::string &value, static bool parseColor(const std::string &value,
video::SColor &color, bool quiet); video::SColor &color, bool quiet);
#ifdef __ANDROID__
bool getAndroidUIInput();
#endif
protected: protected:
v2s32 getBasePos() const v2s32 getBasePos() const
{ {
@ -409,6 +413,14 @@ private:
clickpos m_doubleclickdetect[2]; clickpos m_doubleclickdetect[2];
int m_btn_height; 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 class FormspecFormSource: public IFormSource

@ -259,6 +259,10 @@ struct HTTPFetchOngoing
request.extra_headers[i].c_str()); request.extra_headers[i].c_str());
} }
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, httpheader); 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) { if (res != CURLE_OK) {
infostream<<request.url<<" not found (" errorstream<<request.url<<" not found ("
<<curl_easy_strerror(res)<<")" <<curl_easy_strerror(res)<<")"
<<" (response code "<<result.response_code<<")" <<" (response code "<<result.response_code<<")"
<<std::endl; <<std::endl;

@ -33,6 +33,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "porting.h" #include "porting.h"
#include <IGUIStaticText.h> #include <IGUIStaticText.h>
#ifdef HAVE_TOUCHSCREENGUI
#include "touchscreengui.h"
#endif
Hud::Hud(video::IVideoDriver *driver, scene::ISceneManager* smgr, Hud::Hud(video::IVideoDriver *driver, scene::ISceneManager* smgr,
gui::IGUIEnvironment* guienv, gui::IGUIFont *font, 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, void Hud::drawItems(v2s32 upperleftpos, s32 itemcount, s32 offset,
InventoryList *mainlist, u16 selectitem, u16 direction) 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 height = m_hotbar_imagesize + m_padding * 2;
s32 width = (itemcount - offset) * (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 ); drawItem(mainlist->getItem(i), (imgrect + pos + steppos), (i +1) == selectitem );
#ifdef HAVE_TOUCHSCREENGUI
if (g_touchscreengui)
g_touchscreengui->registerHudItem(i, (imgrect + pos + steppos));
#endif
} }
} }

@ -38,6 +38,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <map> #include <map>
#include <set> #include <set>
#ifdef __ANDROID__
#include <GLES/gl.h>
#endif
/* /*
ItemDefinition ItemDefinition
*/ */
@ -433,6 +437,11 @@ public:
params.light_color.set(1.0, 0.5, 0.5, 0.5); params.light_color.set(1.0, 0.5, 0.5, 0.5);
params.light_radius = 1000; 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 = cc->inventory_texture =
tsrc->generateTextureFromMesh(params); tsrc->generateTextureFromMesh(params);

@ -51,7 +51,15 @@ JSemaphore::JSemaphore() {
JSemaphore::~JSemaphore() { JSemaphore::~JSemaphore() {
int sem_destroy_retval = sem_destroy(&m_semaphore); 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); assert(sem_destroy_retval == 0);
#endif
UNUSED(sem_destroy_retval); UNUSED(sem_destroy_retval);
} }

@ -111,7 +111,11 @@ int JThread::Kill()
} }
return ERR_JTHREAD_NOTRUNNING; return ERR_JTHREAD_NOTRUNNING;
} }
#ifdef __ANDROID__
pthread_kill(threadid, SIGKILL);
#else
pthread_cancel(threadid); pthread_cancel(threadid);
#endif
if (started) { if (started) {
int pthread_join_retval = pthread_join(threadid,&status); int pthread_join_retval = pthread_join(threadid,&status);
assert(pthread_join_retval == 0); assert(pthread_join_retval == 0);

@ -334,6 +334,7 @@ const char *KeyPress::name() const
} }
const KeyPress EscapeKey("KEY_ESCAPE"); const KeyPress EscapeKey("KEY_ESCAPE");
const KeyPress CancelKey("KEY_CANCEL");
const KeyPress NumberKey[] = { const KeyPress NumberKey[] = {
KeyPress("KEY_KEY_0"), KeyPress("KEY_KEY_1"), KeyPress("KEY_KEY_2"), KeyPress("KEY_KEY_0"), KeyPress("KEY_KEY_1"), KeyPress("KEY_KEY_2"),
KeyPress("KEY_KEY_3"), KeyPress("KEY_KEY_4"), KeyPress("KEY_KEY_5"), KeyPress("KEY_KEY_3"), KeyPress("KEY_KEY_4"), KeyPress("KEY_KEY_5"),

@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#define KEYCODE_HEADER #define KEYCODE_HEADER
#include "irrlichttypes.h" #include "irrlichttypes.h"
#include "Keycodes.h"
#include <IEventReceiver.h> #include <IEventReceiver.h>
#include <string> #include <string>
@ -57,6 +58,7 @@ protected:
}; };
extern const KeyPress EscapeKey; extern const KeyPress EscapeKey;
extern const KeyPress CancelKey;
extern const KeyPress NumberKey[10]; extern const KeyPress NumberKey[10];
// Key configuration getter // Key configuration getter
@ -65,5 +67,7 @@ KeyPress getKeySetting(const char *settingname);
// Clear fast lookup cache // Clear fast lookup cache
void clearKeyCache(); void clearKeyCache();
irr::EKEY_CODE keyname_to_keycode(const char *name);
#endif #endif

@ -26,6 +26,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "threads.h" #include "threads.h"
#include "debug.h" #include "debug.h"
#include "gettime.h" #include "gettime.h"
#include "porting.h"
#include "config.h"
std::list<ILogOutput*> log_outputs[LMT_NUM_VALUES]; std::list<ILogOutput*> log_outputs[LMT_NUM_VALUES];
std::map<threadid_t, std::string> log_threadnames; std::map<threadid_t, std::string> log_threadnames;
@ -139,6 +141,9 @@ public:
void printbuf() void printbuf()
{ {
log_printline(m_lev, m_buf); 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) void bufchar(char c)

@ -176,9 +176,15 @@ static void buffreplace (LexState *ls, char from, char to) {
static void trydecpoint (LexState *ls, SemInfo *seminfo) { static void trydecpoint (LexState *ls, SemInfo *seminfo) {
/* format error: try to update decimal point separator */ /* format error: try to update decimal point separator */
#ifndef __ANDROID__
struct lconv *cv = localeconv(); struct lconv *cv = localeconv();
#endif
char old = ls->decpoint; char old = ls->decpoint;
#ifndef __ANDROID__
ls->decpoint = (cv ? cv->decimal_point[0] : '.'); ls->decpoint = (cv ? cv->decimal_point[0] : '.');
#else
ls->decpoint = '.';
#endif
buffreplace(ls, old, ls->decpoint); /* try updated decimal separator */ buffreplace(ls, old, ls->decpoint); /* try updated decimal separator */
if (!luaO_str2d(luaZ_buffer(ls->buff), &seminfo->r)) { if (!luaO_str2d(luaZ_buffer(ls->buff), &seminfo->r)) {
/* format error with correct decimal point: no more options */ /* format error with correct decimal point: no more options */

@ -84,10 +84,14 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#ifdef USE_LEVELDB #ifdef USE_LEVELDB
#include "database-leveldb.h" #include "database-leveldb.h"
#endif #endif
#if USE_REDIS #if USE_REDIS
#include "database-redis.h" #include "database-redis.h"
#endif #endif
#ifdef HAVE_TOUCHSCREENGUI
#include "touchscreengui.h"
#endif
/* /*
Settings. Settings.
These are loaded from the config file. These are loaded from the config file.
@ -253,6 +257,11 @@ public:
React to nothing here if a menu is active React to nothing here if a menu is active
*/ */
if (noMenuActive() == false) { if (noMenuActive() == false) {
#ifdef HAVE_TOUCHSCREENGUI
if (m_touchscreengui != 0) {
m_touchscreengui->Toggle(false);
}
#endif
return g_menumgr.preprocessEvent(event); 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) { if (noMenuActive() == false) {
left_active = false; left_active = false;
middle_active = false; middle_active = false;
@ -293,8 +311,8 @@ public:
} }
} }
} }
if (event.EventType == irr::EET_LOG_TEXT_EVENT) { if(event.EventType == irr::EET_LOG_TEXT_EVENT) {
dstream << "Irrlicht log: " << event.LogEvent.Text << std::endl; dstream<< std::string("Irrlicht log: ") + std::string(event.LogEvent.Text)<<std::endl;
return true; return true;
} }
/* always return false in order to continue processing events */ /* always return false in order to continue processing events */
@ -342,6 +360,9 @@ public:
MyEventReceiver() MyEventReceiver()
{ {
clearInput(); clearInput();
#ifdef HAVE_TOUCHSCREENGUI
m_touchscreengui = NULL;
#endif
} }
bool leftclicked; bool leftclicked;
@ -355,7 +376,12 @@ public:
s32 mouse_wheel; s32 mouse_wheel;
#ifdef HAVE_TOUCHSCREENGUI
TouchScreenGUI* m_touchscreengui;
#endif
private: private:
IrrlichtDevice *m_device;
// The current state of keys // The current state of keys
KeyList keyIsDown; KeyList keyIsDown;
@ -372,7 +398,8 @@ class RealInputHandler : public InputHandler
public: public:
RealInputHandler(IrrlichtDevice *device, MyEventReceiver *receiver): RealInputHandler(IrrlichtDevice *device, MyEventReceiver *receiver):
m_device(device), m_device(device),
m_receiver(receiver) m_receiver(receiver),
m_mousepos(0,0)
{ {
} }
virtual bool isKeyDown(const KeyPress &keyCode) virtual bool isKeyDown(const KeyPress &keyCode)
@ -385,11 +412,21 @@ public:
} }
virtual v2s32 getMousePos() 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) 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() virtual bool getLeftState()
@ -445,8 +482,9 @@ public:
m_receiver->clearInput(); m_receiver->clearInput();
} }
private: private:
IrrlichtDevice *m_device; IrrlichtDevice *m_device;
MyEventReceiver *m_receiver; MyEventReceiver *m_receiver;
v2s32 m_mousepos;
}; };
class RandomInputHandler : public InputHandler class RandomInputHandler : public InputHandler
@ -855,8 +893,18 @@ int main(int argc, char *argv[])
porting::initializePaths(); 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 // Create user data directory
fs::CreateDir(porting::path_user); fs::CreateDir(porting::path_user);
#endif
infostream << "path_share = " << porting::path_share << std::endl; infostream << "path_share = " << porting::path_share << std::endl;
infostream << "path_user = " << porting::path_user << std::endl; infostream << "path_user = " << porting::path_user << std::endl;
@ -975,14 +1023,15 @@ int main(int argc, char *argv[])
// Initialize HTTP fetcher // Initialize HTTP fetcher
httpfetch_init(g_settings->getS32("curl_parallel_limit")); httpfetch_init(g_settings->getS32("curl_parallel_limit"));
#ifndef __ANDROID__
/* /*
Run unit tests Run unit tests
*/ */
if ((ENABLE_TESTS && cmd_args.getFlag("disable-unittests") == false) if ((ENABLE_TESTS && cmd_args.getFlag("disable-unittests") == false)
|| cmd_args.getFlag("enable-unittests") == true) { || cmd_args.getFlag("enable-unittests") == true) {
run_tests(); run_tests();
} }
#endif
#ifdef _MSC_VER #ifdef _MSC_VER
init_gettext((porting::path_share + DIR_DELIM + "locale").c_str(), init_gettext((porting::path_share + DIR_DELIM + "locale").c_str(),
g_settings->get("language"), argc, argv); g_settings->get("language"), argc, argv);
@ -1348,7 +1397,7 @@ int main(int argc, char *argv[])
List video modes if requested List video modes if requested
*/ */
MyEventReceiver receiver; MyEventReceiver* receiver = new MyEventReceiver();
if (cmd_args.getFlag("videomodes")) { if (cmd_args.getFlag("videomodes")) {
IrrlichtDevice *nulldevice; IrrlichtDevice *nulldevice;
@ -1361,7 +1410,7 @@ int main(int argc, char *argv[])
params.Fullscreen = false; params.Fullscreen = false;
params.Stencilbuffer = false; params.Stencilbuffer = false;
params.Vsync = vsync; params.Vsync = vsync;
params.EventReceiver = &receiver; params.EventReceiver = receiver;
params.HighPrecisionFPU = g_settings->getBool("high_precision_fpu"); params.HighPrecisionFPU = g_settings->getBool("high_precision_fpu");
nulldevice = createDeviceEx(params); nulldevice = createDeviceEx(params);
@ -1397,15 +1446,13 @@ int main(int argc, char *argv[])
nulldevice->drop(); nulldevice->drop();
delete receiver;
return 0; return 0;
} }
/* /*
Create device and exit if creation failed Create device and exit if creation failed
*/ */
IrrlichtDevice *device;
SIrrlichtCreationParameters params = SIrrlichtCreationParameters(); SIrrlichtCreationParameters params = SIrrlichtCreationParameters();
params.DriverType = driverType; params.DriverType = driverType;
params.WindowSize = core::dimension2d<u32>(screenW, screenH); params.WindowSize = core::dimension2d<u32>(screenW, screenH);
@ -1414,12 +1461,18 @@ int main(int argc, char *argv[])
params.Fullscreen = fullscreen; params.Fullscreen = fullscreen;
params.Stencilbuffer = false; params.Stencilbuffer = false;
params.Vsync = vsync; params.Vsync = vsync;
params.EventReceiver = &receiver; params.EventReceiver = receiver;
params.HighPrecisionFPU = g_settings->getBool("high_precision_fpu"); 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) { if (device == 0) {
delete receiver;
return 1; // could not create selected driver. 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") bool random_input = g_settings->getBool("random_input")
|| cmd_args.getFlag("random-input"); || cmd_args.getFlag("random-input");
InputHandler *input = NULL; InputHandler *input = NULL;
if (random_input) { if (random_input) {
input = new RandomInputHandler(); input = new RandomInputHandler();
} else { } else {
input = new RealInputHandler(device, &receiver); input = new RealInputHandler(device,receiver);
} }
scene::ISceneManager* smgr = device->getSceneManager(); scene::ISceneManager* smgr = device->getSceneManager();
@ -1564,7 +1618,8 @@ int main(int argc, char *argv[])
/* /*
Menu-game loop Menu-game loop
*/ */
while (device->run() && kill == false) while (device->run() && (kill == false) &&
(g_gamecallback->shutdown_requested == false))
{ {
// Set the window caption // Set the window caption
wchar_t* text = wgettext("Main Menu"); wchar_t* text = wgettext("Main Menu");
@ -1612,7 +1667,9 @@ int main(int argc, char *argv[])
first_loop = false; first_loop = false;
// Cursor can be non-visible when coming from the game // Cursor can be non-visible when coming from the game
#ifndef ANDROID
device->getCursorControl()->setVisible(true); device->getCursorControl()->setVisible(true);
#endif
// Some stuff are left to scene manager when coming from the game // Some stuff are left to scene manager when coming from the game
// (map at least?) // (map at least?)
smgr->clear(); smgr->clear();
@ -1661,10 +1718,9 @@ int main(int argc, char *argv[])
} }
infostream << "Waited for other menus" << std::endl; infostream << "Waited for other menus" << std::endl;
GUIEngine* temp = new GUIEngine(device, guiroot, /* show main menu */
&g_menumgr, smgr, &menudata, kill); GUIEngine mymenu(device, guiroot, &g_menumgr,smgr,&menudata,kill);
delete temp;
//once finished you'll never end up here //once finished you'll never end up here
smgr->clear(); smgr->clear();
} }
@ -1788,6 +1844,10 @@ int main(int argc, char *argv[])
/* /*
Run game Run game
*/ */
#ifdef HAVE_TOUCHSCREENGUI
receiver->m_touchscreengui = new TouchScreenGUI(device, receiver);
g_touchscreengui = receiver->m_touchscreengui;
#endif
the_game( the_game(
kill, kill,
random_input, random_input,
@ -1805,6 +1865,11 @@ int main(int argc, char *argv[])
simple_singleplayer_mode simple_singleplayer_mode
); );
smgr->clear(); smgr->clear();
#ifdef HAVE_TOUCHSCREENGUI
delete g_touchscreengui;
g_touchscreengui = NULL;
receiver->m_touchscreengui = NULL;
#endif
} //try } //try
catch(con::PeerNotFoundException &e) catch(con::PeerNotFoundException &e)
@ -1849,7 +1914,7 @@ int main(int argc, char *argv[])
if (use_freetype) if (use_freetype)
font->drop(); font->drop();
#endif #endif
delete receiver;
#endif // !SERVER #endif // !SERVER
// Update configuration file // Update configuration file

@ -124,13 +124,17 @@ public:
disconnect_requested(false), disconnect_requested(false),
changepassword_requested(false), changepassword_requested(false),
changevolume_requested(false), changevolume_requested(false),
shutdown_requested(false),
device(a_device) device(a_device)
{ {
} }
virtual void exitToOS() virtual void exitToOS()
{ {
shutdown_requested = true;
#ifndef __ANDROID__
device->closeDevice(); device->closeDevice();
#endif
} }
virtual void disconnect() virtual void disconnect()
@ -151,6 +155,7 @@ public:
bool disconnect_requested; bool disconnect_requested;
bool changepassword_requested; bool changepassword_requested;
bool changevolume_requested; bool changevolume_requested;
bool shutdown_requested;
IrrlichtDevice *device; IrrlichtDevice *device;
}; };

@ -21,6 +21,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#define MODALMENU_HEADER #define MODALMENU_HEADER
#include "irrlichttypes_extrabloated.h" #include "irrlichttypes_extrabloated.h"
#ifdef HAVE_TOUCHSCREENGUI
#include "touchscreengui.h"
#endif
class GUIModalMenu; class GUIModalMenu;
@ -101,6 +104,10 @@ public:
Environment->removeFocus(this); Environment->removeFocus(this);
m_menumgr->deletingMenu(this); m_menumgr->deletingMenu(this);
this->remove(); this->remove();
#ifdef HAVE_TOUCHSCREENGUI
if (g_touchscreengui)
g_touchscreengui->Show();
#endif
} }
void removeChildren() void removeChildren()

@ -167,6 +167,7 @@ int getNumberOfProcessors() {
} }
#ifndef __ANDROID__
bool threadBindToProcessor(threadid_t tid, int pnumber) { bool threadBindToProcessor(threadid_t tid, int pnumber) {
#if defined(_WIN32) #if defined(_WIN32)
@ -219,7 +220,7 @@ bool threadBindToProcessor(threadid_t tid, int pnumber) {
#endif #endif
} }
#endif
bool threadSetPriority(threadid_t tid, int prio) { bool threadSetPriority(threadid_t tid, int prio) {
#if defined(_WIN32) #if defined(_WIN32)
@ -458,9 +459,15 @@ void initializePaths()
{ {
char buf[BUFSIZ]; char buf[BUFSIZ];
memset(buf, 0, BUFSIZ); memset(buf, 0, BUFSIZ);
assert(readlink("/proc/self/exe", buf, BUFSIZ-1) != -1); if (readlink("/proc/self/exe", buf, BUFSIZ-1) == -1) {
pathRemoveFile(buf, '/'); errorstream << "Unable to read bindir "<< std::endl;
bindir = buf; #ifndef __ANDROID__
assert("Unable to read bindir" == 0);
#endif
} else {
pathRemoveFile(buf, '/');
bindir = buf;
}
} }
// Find share directory from these. // Find share directory from these.
@ -472,6 +479,9 @@ void initializePaths()
trylist.push_back( trylist.push_back(
bindir + DIR_DELIM + ".." + DIR_DELIM + "share" + DIR_DELIM + PROJECT_NAME); bindir + DIR_DELIM + ".." + DIR_DELIM + "share" + DIR_DELIM + PROJECT_NAME);
trylist.push_back(bindir + DIR_DELIM + ".."); 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(); for(std::list<std::string>::const_iterator i = trylist.begin();
i != trylist.end(); i++) i != trylist.end(); i++)
@ -490,8 +500,11 @@ void initializePaths()
path_share = trypath; path_share = trypath;
break; break;
} }
#ifndef __ANDROID__
path_user = std::string(getenv("HOME")) + DIR_DELIM + "." + PROJECT_NAME; 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 OS X
@ -539,6 +552,7 @@ v2u32 getWindowSize() {
return device->getVideoDriver()->getScreenSize(); return device->getVideoDriver()->getScreenSize();
} }
#ifndef __ANDROID__
float getDisplayDensity() { float getDisplayDensity() {
float gui_scaling = g_settings->getFloat("gui_scaling"); float gui_scaling = g_settings->getFloat("gui_scaling");
@ -562,6 +576,7 @@ v2u32 getDisplaySize() {
return deskres; return deskres;
} }
#endif #endif
#endif
} //namespace porting } //namespace porting

@ -373,5 +373,9 @@ v2u32 getWindowSize();
} // namespace porting } // namespace porting
#ifdef __ANDROID__
#include "porting_android.h"
#endif
#endif // PORTING_HEADER #endif // PORTING_HEADER

295
src/porting_android.cpp Normal file

@ -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
}

81
src/porting_android.h Normal file

@ -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

@ -32,6 +32,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "util/thread.h" #include "util/thread.h"
#include "util/numeric.h" #include "util/numeric.h"
#ifdef __ANDROID__
#include <GLES/gl.h>
#endif
/* /*
A cache from texture name to texture path A cache from texture name to texture path
*/ */
@ -702,6 +706,9 @@ u32 TextureSource::getTextureIdDirect(const std::string &name)
if(baseimg != NULL) if(baseimg != NULL)
{ {
#ifdef __ANDROID__
baseimg = Align2Npot2(baseimg, driver);
#endif
// Create texture from resulting image // Create texture from resulting image
t = driver->addTexture(name.c_str(), baseimg); t = driver->addTexture(name.c_str(), baseimg);
baseimg->drop(); baseimg->drop();
@ -790,11 +797,17 @@ void TextureSource::rebuildImagesAndTextures()
JMutexAutoLock lock(m_textureinfo_cache_mutex); JMutexAutoLock lock(m_textureinfo_cache_mutex);
video::IVideoDriver* driver = m_device->getVideoDriver(); video::IVideoDriver* driver = m_device->getVideoDriver();
assert(driver != 0);
// Recreate textures // Recreate textures
for(u32 i=0; i<m_textureinfo_cache.size(); i++){ for(u32 i=0; i<m_textureinfo_cache.size(); i++){
TextureInfo *ti = &m_textureinfo_cache[i]; TextureInfo *ti = &m_textureinfo_cache[i];
video::IImage *img = generateImageFromScratch(ti->name); 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 // Create texture from resulting image
video::ITexture *t = NULL; video::ITexture *t = NULL;
if(img) { if(img) {
@ -816,6 +829,126 @@ video::ITexture* TextureSource::generateTextureFromMesh(
video::IVideoDriver *driver = m_device->getVideoDriver(); video::IVideoDriver *driver = m_device->getVideoDriver();
assert(driver); 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) if(driver->queryFeature(video::EVDF_RENDER_TO_TARGET) == false)
{ {
static bool warned = false; static bool warned = false;
@ -840,7 +973,12 @@ video::ITexture* TextureSource::generateTextureFromMesh(
} }
// Set render target // 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 // Get a scene manager
scene::ISceneManager *smgr_main = m_device->getSceneManager(); scene::ISceneManager *smgr_main = m_device->getSceneManager();
@ -848,7 +986,9 @@ video::ITexture* TextureSource::generateTextureFromMesh(
scene::ISceneManager *smgr = smgr_main->createNewSceneManager(); scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
assert(smgr); 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_LIGHTING, true);
meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true); meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter); meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
@ -871,11 +1011,6 @@ video::ITexture* TextureSource::generateTextureFromMesh(
smgr->drawAll(); smgr->drawAll();
driver->endScene(); driver->endScene();
// NOTE: The scene nodes should not be dropped, otherwise
// smgr->drop() segfaults
/*cube->drop();
camera->drop();
light->drop();*/
// Drop scene manager // Drop scene manager
smgr->drop(); smgr->drop();
@ -938,6 +1073,57 @@ video::IImage* TextureSource::generateImageFromScratch(std::string name)
return baseimg; 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) bool TextureSource::generateImage(std::string part_of_name, video::IImage *& baseimg)
{ {
video::IVideoDriver* driver = m_device->getVideoDriver(); 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] != '[') if(part_of_name.size() == 0 || part_of_name[0] != '[')
{ {
video::IImage *image = m_sourcecache.getOrLoad(part_of_name, m_device); video::IImage *image = m_sourcecache.getOrLoad(part_of_name, m_device);
#ifdef __ANDROID__
if (image != NULL) { image = Align2Npot2(image,driver);
if (!driver->queryFeature(irr::video::EVDF_TEXTURE_NPOT)) { #endif
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;
}
}
}
if (image == NULL) { if (image == NULL) {
if (part_of_name != "") { if (part_of_name != "") {
if (part_of_name.find("_normal.png") == std::string::npos){ 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 = video::IImage *img_right =
generateImageFromScratch(imagename_right); generateImageFromScratch(imagename_right);
assert(img_top && img_left && img_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 // Create textures from images
video::ITexture *texture_top = driver->addTexture( video::ITexture *texture_top = driver->addTexture(
(imagename_top + "__temp__").c_str(), img_top); (imagename_top + "__temp__").c_str(), img_top);

@ -131,6 +131,25 @@ public:
IWritableTextureSource* createTextureSource(IrrlichtDevice *device); 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{ enum MaterialType{
TILE_MATERIAL_BASIC, TILE_MATERIAL_BASIC,
TILE_MATERIAL_ALPHA, TILE_MATERIAL_ALPHA,

690
src/touchscreengui.cpp Normal file

@ -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);
}

160
src/touchscreengui.h Normal file

@ -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

@ -29,6 +29,58 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "../hex.h" #include "../hex.h"
#include "../porting.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) std::wstring narrow_to_wide(const std::string& mbs)
{ {
size_t wcl = mbs.size(); size_t wcl = mbs.size();
@ -40,6 +92,35 @@ std::wstring narrow_to_wide(const std::string& mbs)
return *wcs; 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) std::string wide_to_narrow(const std::wstring& wcs)
{ {
size_t mbl = wcs.size()*4; size_t mbl = wcs.size()*4;
@ -53,6 +134,8 @@ std::string wide_to_narrow(const std::wstring& wcs)
return *mbs; return *mbs;
} }
#endif
// Get an sha-1 hash of the player's name combined with // Get an sha-1 hash of the player's name combined with
// the password entered. That's what the server uses as // the password entered. That's what the server uses as
// their password. (Exception : if the password field is // their password. (Exception : if the password field is

@ -20,19 +20,15 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "version.h" #include "version.h"
#include "config.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_simple = CMAKE_VERSION_STRING;
const char *minetest_version_hash = CMAKE_VERSION_GITHASH; const char *minetest_version_hash = CMAKE_VERSION_GITHASH;
#ifdef USE_CMAKE_CONFIG_H
const char *minetest_build_info = const char *minetest_build_info =
"VER=" CMAKE_VERSION_GITHASH " " CMAKE_BUILD_INFO; "VER=" CMAKE_VERSION_GITHASH " " CMAKE_BUILD_INFO;
#elif defined(ANDROID)
const char *minetest_build_info = "android jni";
#else #else
const char *minetest_version_simple = "unknown";
const char *minetest_version_hash = "unknown";
const char *minetest_build_info = "non-cmake"; const char *minetest_build_info = "non-cmake";
#endif #endif

Binary file not shown.

After

Width:  |  Height:  |  Size: 777 B

BIN
textures/base/pack/down.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 843 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 733 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 621 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 742 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 770 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 704 B