Files
questshock/android/app/src/main/cpp/questshock_native.c
T
2026-09-20 16:49:28 +02:00

299 lines
13 KiB
C

// android.system.Os has no public chdir() (confirmed against the actual
// API 34 stub jar - Java has no way to change a process's working
// directory at all). This is the one bit of native code QuestShockActivity
// needs, compiled into the same "main" library as engine/ (see
// android/engine-patches/01-android-shared-lib.patch's ANDROID_EXTRA_SOURCES)
// - engine/ itself is never modified.
#include <ctype.h>
#include <jni.h>
#include <unistd.h>
#include <SDL.h>
#include "qs_config.h"
#include "qs_splash.h"
#include "xr_input.h"
#include "xr_overlay.h"
#include "xr_session.h"
JNIEXPORT void JNICALL
Java_de_ladkau_questshock_QuestShockActivity_nativeChdir(JNIEnv *env, jclass clazz, jstring path) {
const char *cpath = (*env)->GetStringUTFChars(env, path, NULL);
chdir(cpath);
(*env)->ReleaseStringUTFChars(env, path, cpath);
}
JNIEXPORT void JNICALL Java_de_ladkau_questshock_QuestShockActivity_nativeSetSplashImage(
JNIEnv *env, jclass clazz, jintArray pixels, jint width, jint height) {
jint *data = (*env)->GetIntArrayElements(env, pixels, NULL);
if (data == NULL)
return;
qs_splash_set_image((const uint32_t *)data, width, height);
(*env)->ReleaseIntArrayElements(env, pixels, data, JNI_ABORT);
}
// KeyboardOverlay's hand-built on-screen keyboard (see
// KeyboardOverlay.handleCharKeyDown()/handleCharKeyUp()) has no real
// Android IME session behind it, so printable characters can't reach the
// game via Android's usual IME path - instead this builds and pushes an
// SDL_TEXTINPUT event directly, the same event type/shape SDL's own
// Android backend pushes for typed text, using only the public
// SDL_Event/SDL_PushEvent API. The engine's pump_events() (sdl_events.c)
// only picks up printable characters from this event, not from
// SDL_KEYDOWN.
//
// pump_events()'s SDL_TEXTINPUT handling sets sshockKeyStates[code] for
// this character and only ever clears it again on a matching SDL_KEYUP.
// Any character bound to a continuously-polled motion key (space -> jump,
// w -> forward, etc., see MacSrc/Prefs.c's default keybinds.txt) is
// polled every physics tick for as long as that state stays set, exactly
// like a real held key - so nativeSendPrintableCharDown()/Up() are called
// from the key button's own ACTION_DOWN/ACTION_UP (see
// KeyboardOverlay.charKeyTouchListener()), not both at once: pushing the
// release in the same call as the press would set and clear
// sshockKeyStates[code] within a single pump_events() call, before the
// game loop's poll (which runs once per frame, before pump_events - see
// GameSrc/mainloop.c) ever has a chance to observe the press.
JNIEXPORT void JNICALL
Java_de_ladkau_questshock_KeyboardOverlay_nativeSendPrintableCharDown(JNIEnv *env, jclass clazz,
jchar c) {
SDL_Event event;
SDL_zero(event);
event.type = SDL_TEXTINPUT;
event.text.timestamp = SDL_GetTicks();
event.text.windowID = 0;
event.text.text[0] = (char)c;
event.text.text[1] = '\0';
SDL_PushEvent(&event);
}
JNIEXPORT void JNICALL
Java_de_ladkau_questshock_KeyboardOverlay_nativeSendPrintableCharUp(JNIEnv *env, jclass clazz,
jchar c) {
SDL_Event releaseEvent;
SDL_zero(releaseEvent);
releaseEvent.type = SDL_KEYUP;
releaseEvent.key.timestamp = SDL_GetTicks();
releaseEvent.key.windowID = 0;
releaseEvent.key.state = SDL_RELEASED;
releaseEvent.key.repeat = 0;
// SDL keycodes for printable characters are the character's own
// (lowercased) byte value - see sdl_events.c's own comment on this.
releaseEvent.key.keysym.sym = (SDL_Keycode)tolower((unsigned char)c);
releaseEvent.key.keysym.mod = KMOD_NONE;
SDL_PushEvent(&releaseEvent);
}
// KeyboardOverlay's title-bar Close button - the keyboard is a fully
// independent overlay quad from the menu launcher (see xr_overlay.h), so
// this is the only way to hide it once it's open; the controller's menu
// button only ever affects the menu launcher.
JNIEXPORT void JNICALL
Java_de_ladkau_questshock_KeyboardOverlay_nativeRequestClose(JNIEnv *env, jclass clazz) {
xr_overlay_set_visible(xr_session_get_keyboard_overlay(), false);
}
// MenuOverlay's "Keyboard" button - only opens the keyboard overlay, never
// touches the menu launcher's own visibility, so both can be shown
// together.
JNIEXPORT void JNICALL
Java_de_ladkau_questshock_MenuOverlay_nativeShowKeyboard(JNIEnv *env, jclass clazz) {
xr_overlay_set_visible(xr_session_get_keyboard_overlay(), true);
}
// MenuOverlay's "Save Configuration"/"Load Configuration" buttons - see
// qs_config.h for why these only enqueue an event rather than doing the
// actual file/engine-state work here directly (this runs on Android's UI
// thread, not the engine's own).
JNIEXPORT void JNICALL
Java_de_ladkau_questshock_MenuOverlay_nativeRequestSaveConfig(JNIEnv *env, jclass clazz) {
qs_config_request_save();
}
JNIEXPORT void JNICALL
Java_de_ladkau_questshock_MenuOverlay_nativeRequestLoadConfig(JNIEnv *env, jclass clazz) {
qs_config_request_load();
}
// MenuOverlay's Video Options tab - live-appliable controls, see
// qs_config.h. Same enqueue-only reasoning as the Save/Load buttons above.
JNIEXPORT void JNICALL
Java_de_ladkau_questshock_MenuOverlay_nativeRequestSetRenderer(JNIEnv *env, jclass clazz,
jboolean opengl) {
qs_config_request_set_renderer(opengl);
}
JNIEXPORT void JNICALL
Java_de_ladkau_questshock_MenuOverlay_nativeRequestSetResolution(JNIEnv *env, jclass clazz,
jint resolutionIndex) {
qs_config_request_set_resolution(resolutionIndex);
}
// MenuOverlay's Main Menu tab's Language row - same enqueue-only reasoning as above.
JNIEXPORT void JNICALL
Java_de_ladkau_questshock_MenuOverlay_nativeRequestSetLanguage(JNIEnv *env, jclass clazz,
jint languageIndex) {
qs_config_request_set_language(languageIndex);
}
// MenuOverlay's Main Menu tab's "Reset Position" button - same enqueue-only
// reasoning as above.
JNIEXPORT void JNICALL
Java_de_ladkau_questshock_MenuOverlay_nativeRequestResetPosition(JNIEnv *env, jclass clazz) {
qs_config_request_reset_position();
}
// MenuOverlay's Video Options tab - "Initial Screen"/"Game Screen" width/
// distance preset rows, same enqueue-only reasoning as above.
JNIEXPORT void JNICALL
Java_de_ladkau_questshock_MenuOverlay_nativeRequestSetInitialScreenWidth(JNIEnv *env, jclass clazz,
jint presetIndex) {
qs_config_request_set_initial_screen_width(presetIndex);
}
JNIEXPORT void JNICALL
Java_de_ladkau_questshock_MenuOverlay_nativeRequestSetGameScreenWidth(JNIEnv *env, jclass clazz,
jint presetIndex) {
qs_config_request_set_game_screen_width(presetIndex);
}
JNIEXPORT void JNICALL
Java_de_ladkau_questshock_MenuOverlay_nativeRequestSetInitialScreenDistance(JNIEnv *env,
jclass clazz,
jint presetIndex) {
qs_config_request_set_initial_screen_distance(presetIndex);
}
JNIEXPORT void JNICALL
Java_de_ladkau_questshock_MenuOverlay_nativeRequestSetGameScreenDistance(JNIEnv *env, jclass clazz,
jint presetIndex) {
qs_config_request_set_game_screen_distance(presetIndex);
}
// Read-only queries - safe to call directly (no enqueue needed, see
// qs_config.h's own comment on each of these).
JNIEXPORT jboolean JNICALL
Java_de_ladkau_questshock_MenuOverlay_nativeGetRendererIsOpenGl(JNIEnv *env, jclass clazz) {
return qs_config_current_renderer_is_opengl();
}
JNIEXPORT jint JNICALL
Java_de_ladkau_questshock_MenuOverlay_nativeGetResolutionIndex(JNIEnv *env, jclass clazz) {
return qs_config_current_resolution_index();
}
JNIEXPORT jint JNICALL
Java_de_ladkau_questshock_MenuOverlay_nativeGetInitialScreenWidthIndex(JNIEnv *env, jclass clazz) {
return qs_config_current_initial_screen_width_index();
}
JNIEXPORT jint JNICALL
Java_de_ladkau_questshock_MenuOverlay_nativeGetGameScreenWidthIndex(JNIEnv *env, jclass clazz) {
return qs_config_current_game_screen_width_index();
}
JNIEXPORT jint JNICALL
Java_de_ladkau_questshock_MenuOverlay_nativeGetInitialScreenDistanceIndex(JNIEnv *env,
jclass clazz) {
return qs_config_current_initial_screen_distance_index();
}
JNIEXPORT jint JNICALL
Java_de_ladkau_questshock_MenuOverlay_nativeGetGameScreenDistanceIndex(JNIEnv *env, jclass clazz) {
return qs_config_current_game_screen_distance_index();
}
// Preset button *labels* (not the current value) - shared by both
// "Initial Screen" and "Game Screen" columns, since their offset/step is
// config-file-only, not per-profile (see qs_config.h's own comment).
JNIEXPORT jfloat JNICALL
Java_de_ladkau_questshock_MenuOverlay_nativeGetScreenWidthPresetValue(JNIEnv *env, jclass clazz,
jint index) {
return qs_config_screen_width_preset_value(index);
}
JNIEXPORT jfloat JNICALL
Java_de_ladkau_questshock_MenuOverlay_nativeGetScreenDistancePresetValue(JNIEnv *env, jclass clazz,
jint index) {
return qs_config_screen_distance_preset_value(index);
}
JNIEXPORT jint JNICALL
Java_de_ladkau_questshock_MenuOverlay_nativeGetLanguage(JNIEnv *env, jclass clazz) {
return qs_config_current_language();
}
// MenuOverlay's header - polled on a periodic timer (see its own comment),
// not event-driven, so these are plain synchronous reads too.
JNIEXPORT jfloat JNICALL
Java_de_ladkau_questshock_MenuOverlay_nativeGetFps(JNIEnv *env, jclass clazz) {
return xr_session_get_fps();
}
JNIEXPORT jfloat JNICALL
Java_de_ladkau_questshock_MenuOverlay_nativeGetFrameTimeMs(JNIEnv *env, jclass clazz) {
return xr_session_get_frame_ms();
}
JNIEXPORT jfloat JNICALL
Java_de_ladkau_questshock_MenuOverlay_nativeGetCpuUtilization(JNIEnv *env, jclass clazz) {
return xr_session_get_cpu_utilization();
}
JNIEXPORT jfloat JNICALL
Java_de_ladkau_questshock_MenuOverlay_nativeGetGpuUtilization(JNIEnv *env, jclass clazz) {
return xr_session_get_gpu_utilization();
}
// Controller Options tab: actionIndex follows xr_input.c's action order
// (MenuOverlay.java and Strings.java hold the matching labels).
JNIEXPORT void JNICALL
Java_de_ladkau_questshock_MenuOverlay_nativeRequestBeginControlCapture(JNIEnv *env, jclass clazz,
jint actionIndex) {
qs_config_request_begin_control_capture(actionIndex);
}
JNIEXPORT void JNICALL
Java_de_ladkau_questshock_MenuOverlay_nativeRequestCancelControlCapture(JNIEnv *env, jclass clazz) {
qs_config_request_cancel_control_capture();
}
JNIEXPORT void JNICALL
Java_de_ladkau_questshock_MenuOverlay_nativeRequestResetPlayerControls(JNIEnv *env, jclass clazz) {
qs_config_request_reset_player_controls();
}
JNIEXPORT jstring JNICALL
Java_de_ladkau_questshock_MenuOverlay_nativeGetControlBindingKey(JNIEnv *env, jclass clazz,
jint actionIndex) {
return (*env)->NewStringUTF(env, xr_input_get_action_binding_name(actionIndex));
}
JNIEXPORT jstring JNICALL
Java_de_ladkau_questshock_MenuOverlay_nativeGetActiveInteractionProfilePath(JNIEnv *env,
jclass clazz) {
return (*env)->NewStringUTF(env, xr_input_get_active_interaction_profile_path());
}
JNIEXPORT void JNICALL
Java_de_ladkau_questshock_MenuOverlay_nativeSetControllerOptionsTabOpen(JNIEnv *env, jclass clazz,
jboolean open) {
xr_input_set_controller_options_tab_open(open == JNI_TRUE);
}
// Cheats tab: same enqueue-only pattern as the other requests.
JNIEXPORT void JNICALL
Java_de_ladkau_questshock_MenuOverlay_nativeRequestCheat(JNIEnv *env, jclass clazz, jint cheatId) {
qs_config_request_cheat(cheatId);
}
JNIEXPORT jint JNICALL
Java_de_ladkau_questshock_MenuOverlay_nativeGetCheatUnlimitedMask(JNIEnv *env, jclass clazz) {
return qs_config_cheat_unlimited_mask();
}
JNIEXPORT jboolean JNICALL
Java_de_ladkau_questshock_MenuOverlay_nativeGetCheatPhysicsRunning(JNIEnv *env, jclass clazz) {
return qs_config_cheat_physics_running();
}