Files
questshock/android/app/build.gradle
T
ml 581af95e7b
build / build (push) Successful in 1m44s
Drive the engine's mouse cursor from the VR aim ray on the game quad
The game quad's laser beam was purely visual - aiming at the game's own
UI (inventory, menus, dialogs) had no way to interact with it. Add a
real mouse pointer: absolute cursor position plus a single left click,
mirroring a plain point-and-click mouse (no mouselook, no right-click,
no drag).

New xr_mouse.c/h bridges the aim-ray hit-test to the vendored engine's
own public mouse API - mouse_put_xy() for position, a synthetic
SDL_MOUSEBUTTONDOWN/UP (SDL_BUTTON_LEFT) via SDL_PushEvent for clicks,
the same technique nativeSendPrintableChar() already uses for
synthetic keyboard input. No engine patch needed. Deliberately bypasses
SDLActivity.onNativeMouse()/SDL_MOUSEMOTION/SetMouseXY(), whose
physical-window-size scaling is unreliable in this headless immersive
build.

xr_input_try_game_quad() now reports the hit u,v; a new
g_game_touch_active[] tracks a held click on the game quad so it
survives the ray straying onto the keyboard/menu before release,
gating those overlays' claims the same way pending overlay touches
already do.
2026-08-14 08:36:09 +02:00

204 lines
11 KiB
Groovy

apply plugin: 'com.android.application'
// Paths written by build-image/prepare-android-project.sh (called by
// build-apk.sh, or standalone with --host-paths to prep for a native
// build in Android Studio - see README) since they're only known at
// prep/build time, not something a checked-in build.gradle can hardcode.
// engineDir is the scratch, patched copy of engine/; gl4esDir is the
// scratch, patched copy of android/gl4es-src/ (built from source as a
// CMake subdirectory of engine/'s own native build below, not prebuilt);
// prebuiltDir is the Android SDL2/SDL2_mixer/fluidsynth-lite/openxr
// prebuilts - all point into this container's own paths for
// build-apk.sh's own gradlew call, or host-resolvable paths (plus a
// host-side export of prebuiltDir, since /opt/prebuilt/android only
// exists in the image) when prepared with --host-paths for Android
// Studio.
def engineProps = new Properties()
file("${projectDir}/../engine.properties").withInputStream { engineProps.load(it) }
def engineDir = engineProps.getProperty('engineDir')
if (engineDir == null) {
throw new GradleException("android/engine.properties is missing 'engineDir' - run via build-apk.sh or prepare-android-project.sh, not gradlew directly")
}
def gl4esDir = engineProps.getProperty('gl4esDir')
if (gl4esDir == null) {
throw new GradleException("android/engine.properties is missing 'gl4esDir' - run via build-apk.sh or prepare-android-project.sh, not gradlew directly")
}
def prebuiltDir = engineProps.getProperty('prebuiltDir')
if (prebuiltDir == null) {
throw new GradleException("android/engine.properties is missing 'prebuiltDir' - run via build-apk.sh or prepare-android-project.sh, not gradlew directly")
}
// Set by build-apk.sh (see build-image/version.sh, shared with the
// desktop tarball's versioning) via -PquestshockVersionName/Code. Default
// here only covers a direct, unsupported `gradlew` invocation.
def questshockVersionName = project.hasProperty('questshockVersionName') ? project.property('questshockVersionName') : '0.0.0-dev'
def questshockVersionCode = project.hasProperty('questshockVersionCode') ? project.property('questshockVersionCode').toInteger() : 1
// Auto-refreshes the staged, patched engine/ and gl4es-src/ copies and
// prebuilt libraries (see build-image/prepare-android-project.sh
// --host-paths, and the engineDir/gl4esDir/prebuiltDir properties read
// above) so a plain Android Studio build/run can never silently compile
// against a stale scratch copy after engine/, android/engine-patches/,
// android/gl4es-src/, or android/gl4es-patches/ change - previously a
// manual, easy to forget re-run. inputs/outputs are declared so Gradle
// skips the (Docker-invoking, not free) step entirely when nothing
// relevant actually changed, keeping pure-Java edit/run cycles fast.
//
// This step only re-stages whatever is already baked into the build-image
// (it re-runs prepare-android-project.sh inside the existing image, which
// is fast - no compilation happens here) - engine/ and gl4es-src/ are both
// then compiled from these scratch copies by the externalNativeBuild CMake
// configure below (see android/engine-patches/02-android-opengl-es.patch's
// add_subdirectory of gl4esDir), so Gradle's own incremental CMake/Ninja
// build only recompiles what actually changed, same as any other native
// source edit. This task does NOT rebuild the image itself: if you changed
// build-image/Dockerfile (a real toolchain/dependency change, not
// engine-patches/ or gl4es-patches/, which this task re-stages on its own),
// you still need to run ./build-image.sh yourself first (a slow, deliberate
// step - see the memory on build-image versioning) so the image actually
// contains your changes before this task re-stages them.
def repoRoot = file("${projectDir}/../..")
def stageEngineTask = tasks.register("stageEngine", Exec) {
group = "build setup"
description = "Refreshes the patched engine/ scratch copy and prebuilt libraries for a native Android Studio build (build-image/prepare-android-project.sh --host-paths)."
workingDir repoRoot
commandLine "./run-image.sh", "bash", "build-image/prepare-android-project.sh", "--host-paths"
// /opt/prebuilt/android only ever exists inside the build-image
// container itself (baked in at image-build time, never on the host -
// see build-image/Dockerfile). Its presence means this build is
// build-apk.sh's own gradlew call, running INSIDE that container, which
// already staged everything itself in container mode before invoking
// gradlew - re-running this task there would try to `docker run` from
// inside a container with no docker socket, breaking make apk/CI. Only
// a genuine host-side Android Studio build (where this path is absent)
// needs this task.
onlyIf { !file('/opt/prebuilt/android').isDirectory() }
inputs.dir("${repoRoot}/engine")
inputs.dir("${projectDir}/../engine-patches")
inputs.dir("${projectDir}/../gl4es-src")
inputs.dir("${projectDir}/../gl4es-patches")
inputs.file("${repoRoot}/build-image/Dockerfile")
outputs.dir("${repoRoot}/build/android-engine")
outputs.dir("${repoRoot}/build/android-gl4es")
outputs.dir("${repoRoot}/build/android-prebuilt")
outputs.file("${projectDir}/../engine.properties")
}
// preBuild is what every variant's compile/native-build tasks already
// transitively depend on, regardless of AGP version's exact CMake task
// naming - the simplest reliable hook to run before any of them.
afterEvaluate {
tasks.named("preBuild").configure {
dependsOn stageEngineTask
}
}
android {
namespace "de.ladkau.questshock"
// compileSdk/buildToolsVersion/ndkVersion must all match what
// build-image/Dockerfile actually installs - otherwise AGP defaults to
// whatever version it itself prefers and tries to download it into the
// SDK dir at build time, which isn't writable.
compileSdk 34
buildToolsVersion "34.0.0"
ndkVersion "26.1.10909125"
defaultConfig {
applicationId "de.ladkau.questshock"
// 29, not compileSdk's 34, is deliberate - see build-image/Dockerfile's
// comment on ANDROID_PLATFORM_VERSION: paired with
// requestLegacyExternalStorage in the manifest, this keeps
// /sdcard/questshock/ a plain, unrestricted shared folder instead of
// Android 11+ scoped storage, on the Quest 2/3/3S/Pro headsets this
// targets - not just whatever's the current API level today.
minSdkVersion 24
targetSdkVersion 29
versionCode questshockVersionCode
versionName questshockVersionName
externalNativeBuild {
cmake {
// engine/CMakeLists.txt's own dependency-selection options
// (see engine/CMakeLists.txt near the top): SDL2 via
// find_package (our Android SDL2 build installs a real CMake
// config, unlike the desktop build's older bundled copy);
// SDL2_mixer and FluidSynth via the same build_ext/ BUNDLED
// convention the desktop build already uses (populated in the
// scratch engine copy by prepare-android-project.sh from
// prebuiltDir). ANDROID_GL4ES_DIR is read by
// android/engine-patches/02-android-opengl-es.patch, which
// add_subdirectory()'s it to compile gl4es from source as
// part of this same CMake configure, instead of linking a
// prebuilt .so - so gl4es-patches/ changes just need a normal
// rebuild here, not a build-image rebuild.
// The CMAKE_FIND_ROOT_PATH* overrides below are needed
// because the NDK toolchain file restricts find_package/
// find_path/find_library to its own sysroot by default,
// which would otherwise miss our custom-installed SDL2 (see
// build-image/Dockerfile's SDL2_mixer build, which hit the
// exact same thing).
// -Wl,-z,max-page-size=16384: NDK 26 doesn't 16 KB-align ELF
// LOAD segments by default (only automatic in NDK 28+) - see
// build-image/Dockerfile's ANDROID_16KB_LDFLAGS, which does
// the same for the prebuilt SDL2/SDL2_mixer/fluidsynth-lite
// .so's this links against (gl4es is compiled here with the
// same flag passed straight through, see its add_subdirectory
// above).
// ANDROID_EXTRA_SOURCES is a semicolon-separated CMake list,
// not shell-split - AGP passes each "arguments" string
// straight through as one argv element to cmake, so the
// ';' below survives intact. ANDROID_EXTRA_INCLUDE_DIRS lets
// engine/'s own OpenGL.cc find "xr_session.h" (see
// android/engine-patches/10-android-openxr-cmake.patch,
// which adds it to include_directories()).
// USE_ANDROID_LOG/STATICLIB used to be passed to gl4es's own,
// separate cmake invocation in build-image/Dockerfile - now
// that it's add_subdirectory()'d into this same configure,
// they need to be set here instead, or gl4es silently falls
// back to stdio logging, which never reaches logcat. ANDROID
// itself doesn't need setting here - the NDK toolchain file
// already defines it before any CMakeLists.txt runs.
arguments "-DENABLE_SDL2=ON", "-DENABLE_SOUND=BUNDLED", "-DENABLE_FLUIDSYNTH=BUNDLED", \
"-DANDROID_PREBUILT_DIR=${prebuiltDir}", \
"-DANDROID_GL4ES_DIR=${gl4esDir}", \
"-DUSE_ANDROID_LOG=ON", "-DSTATICLIB=OFF", \
"-DANDROID_EXTRA_INCLUDE_DIRS=${projectDir}/src/main/cpp", \
"-DCMAKE_PREFIX_PATH=${prebuiltDir}/sdl2", \
"-DCMAKE_FIND_ROOT_PATH=${prebuiltDir}/sdl2", \
"-DCMAKE_FIND_ROOT_PATH_MODE_PACKAGE=BOTH", \
"-DCMAKE_FIND_ROOT_PATH_MODE_LIBRARY=BOTH", \
"-DCMAKE_FIND_ROOT_PATH_MODE_INCLUDE=BOTH", \
"-DCMAKE_SHARED_LINKER_FLAGS=-Wl,-z,max-page-size=16384", \
"-DANDROID_EXTRA_SOURCES=${projectDir}/src/main/cpp/questshock_native.c;${projectDir}/src/main/cpp/xr_session.c;${projectDir}/src/main/cpp/xr_input.c;${projectDir}/src/main/cpp/xr_overlay.c;${projectDir}/src/main/cpp/xr_swapchain.c;${projectDir}/src/main/cpp/xr_mouse.c"
abiFilters 'arm64-v8a'
}
}
}
externalNativeBuild {
cmake {
path file("${engineDir}/CMakeLists.txt")
}
}
buildTypes {
release {
minifyEnabled false
}
}
lint {
abortOnError false
}
}
dependencies {
// ActivityCompat.checkSelfPermission/requestPermissions in
// QuestShockActivity - the plain android.app.Activity APIs work the
// same way without androidx, but ActivityCompat is the standard,
// documented way to request runtime permissions safely.
implementation 'androidx.core:core:1.12.0'
}