Android: Multiple fixes
- Fix possible crash when applying settings worker thread (no JNIEnv). - Fix settings not applying until restarting the app. - Support analog controller - auto-binding of axixes. Currently no touchscreen controller for the joysticks. - Add option to auto-hide the touchscreen controller.
This commit is contained in:
parent
c7b457de9e
commit
24ffe6f67e
|
@ -3,7 +3,6 @@ set(SRCS
|
||||||
android_host_interface.h
|
android_host_interface.h
|
||||||
android_settings_interface.cpp
|
android_settings_interface.cpp
|
||||||
android_settings_interface.h
|
android_settings_interface.h
|
||||||
main.cpp
|
|
||||||
)
|
)
|
||||||
|
|
||||||
add_library(duckstation-native SHARED ${SRCS})
|
add_library(duckstation-native SHARED ${SRCS})
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#include "core/system.h"
|
#include "core/system.h"
|
||||||
#include "frontend-common/opengl_host_display.h"
|
#include "frontend-common/opengl_host_display.h"
|
||||||
#include "frontend-common/vulkan_host_display.h"
|
#include "frontend-common/vulkan_host_display.h"
|
||||||
|
#include "frontend-common/imgui_styles.h"
|
||||||
#include <android/native_window_jni.h>
|
#include <android/native_window_jni.h>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
|
@ -195,8 +196,18 @@ void AndroidHostInterface::RunOnEmulationThread(std::function<void()> function,
|
||||||
|
|
||||||
void AndroidHostInterface::EmulationThreadEntryPoint(ANativeWindow* initial_surface, SystemBootParameters boot_params)
|
void AndroidHostInterface::EmulationThreadEntryPoint(ANativeWindow* initial_surface, SystemBootParameters boot_params)
|
||||||
{
|
{
|
||||||
|
JNIEnv* thread_env;
|
||||||
|
if (s_jvm->AttachCurrentThread(&thread_env, nullptr) != JNI_OK)
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("Failed to attach JNI to thread");
|
||||||
|
m_emulation_thread_start_result.store(false);
|
||||||
|
m_emulation_thread_started.Signal();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
CreateImGuiContext();
|
CreateImGuiContext();
|
||||||
m_surface = initial_surface;
|
m_surface = initial_surface;
|
||||||
|
ApplySettings();
|
||||||
|
|
||||||
// Boot system.
|
// Boot system.
|
||||||
if (!BootSystem(boot_params))
|
if (!BootSystem(boot_params))
|
||||||
|
@ -205,6 +216,7 @@ void AndroidHostInterface::EmulationThreadEntryPoint(ANativeWindow* initial_surf
|
||||||
DestroyImGuiContext();
|
DestroyImGuiContext();
|
||||||
m_emulation_thread_start_result.store(false);
|
m_emulation_thread_start_result.store(false);
|
||||||
m_emulation_thread_started.Signal();
|
m_emulation_thread_started.Signal();
|
||||||
|
s_jvm->DetachCurrentThread();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -256,6 +268,7 @@ void AndroidHostInterface::EmulationThreadEntryPoint(ANativeWindow* initial_surf
|
||||||
|
|
||||||
DestroySystem();
|
DestroySystem();
|
||||||
DestroyImGuiContext();
|
DestroyImGuiContext();
|
||||||
|
s_jvm->DetachCurrentThread();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AndroidHostInterface::AcquireHostDisplay()
|
bool AndroidHostInterface::AcquireHostDisplay()
|
||||||
|
@ -297,31 +310,6 @@ void AndroidHostInterface::ReleaseHostDisplay()
|
||||||
m_display.reset();
|
m_display.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<AudioStream> AndroidHostInterface::CreateAudioStream(AudioBackend backend)
|
|
||||||
{
|
|
||||||
std::unique_ptr<AudioStream> stream;
|
|
||||||
|
|
||||||
switch (m_settings.audio_backend)
|
|
||||||
{
|
|
||||||
case AudioBackend::Cubeb:
|
|
||||||
stream = AudioStream::CreateCubebAudioStream();
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
stream = AudioStream::CreateNullAudioStream();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!stream)
|
|
||||||
{
|
|
||||||
ReportFormattedError("Failed to create %s audio stream, falling back to null",
|
|
||||||
Settings::GetAudioBackendName(m_settings.audio_backend));
|
|
||||||
stream = AudioStream::CreateNullAudioStream();
|
|
||||||
}
|
|
||||||
|
|
||||||
return stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AndroidHostInterface::SurfaceChanged(ANativeWindow* surface, int format, int width, int height)
|
void AndroidHostInterface::SurfaceChanged(ANativeWindow* surface, int format, int width, int height)
|
||||||
{
|
{
|
||||||
Log_InfoPrintf("SurfaceChanged %p %d %d %d", surface, format, width, height);
|
Log_InfoPrintf("SurfaceChanged %p %d %d %d", surface, format, width, height);
|
||||||
|
@ -351,9 +339,16 @@ void AndroidHostInterface::CreateImGuiContext()
|
||||||
{
|
{
|
||||||
ImGui::CreateContext();
|
ImGui::CreateContext();
|
||||||
|
|
||||||
ImGui::GetIO().IniFilename = nullptr;
|
const float framebuffer_scale = 2.0f;
|
||||||
// ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
|
|
||||||
// ImGui::GetIO().BackendFlags |= ImGuiBackendFlags_HasGamepad;
|
auto& io = ImGui::GetIO();
|
||||||
|
io.IniFilename = nullptr;
|
||||||
|
io.DisplayFramebufferScale.x = framebuffer_scale;
|
||||||
|
io.DisplayFramebufferScale.y = framebuffer_scale;
|
||||||
|
ImGui::GetStyle().ScaleAllSizes(framebuffer_scale);
|
||||||
|
|
||||||
|
ImGui::StyleColorsDarker();
|
||||||
|
ImGui::AddRobotoRegularFont(15.0f * framebuffer_scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AndroidHostInterface::DestroyImGuiContext()
|
void AndroidHostInterface::DestroyImGuiContext()
|
||||||
|
@ -397,6 +392,22 @@ void AndroidHostInterface::SetControllerButtonState(u32 index, s32 button_code,
|
||||||
false);
|
false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AndroidHostInterface::SetControllerAxisState(u32 index, s32 button_code, float value)
|
||||||
|
{
|
||||||
|
if (!IsEmulationThreadRunning())
|
||||||
|
return;
|
||||||
|
|
||||||
|
RunOnEmulationThread(
|
||||||
|
[this, index, button_code, value]() {
|
||||||
|
Controller* controller = m_system->GetController(index);
|
||||||
|
if (!controller)
|
||||||
|
return;
|
||||||
|
|
||||||
|
controller->SetAxisState(button_code, value);
|
||||||
|
},
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
|
||||||
void AndroidHostInterface::RefreshGameList(bool invalidate_cache, bool invalidate_database)
|
void AndroidHostInterface::RefreshGameList(bool invalidate_cache, bool invalidate_database)
|
||||||
{
|
{
|
||||||
m_game_list->SetSearchDirectoriesFromSettings(m_settings_interface);
|
m_game_list->SetSearchDirectoriesFromSettings(m_settings_interface);
|
||||||
|
@ -544,6 +555,25 @@ DEFINE_JNI_ARGS_METHOD(jint, AndroidHostInterface_getControllerButtonCode, jobje
|
||||||
return code.value_or(-1);
|
return code.value_or(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_setControllerAxisState, jobject obj, jint index, jint button_code,
|
||||||
|
jfloat value)
|
||||||
|
{
|
||||||
|
AndroidHelpers::GetNativeClass(env, obj)->SetControllerAxisState(index, button_code, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_JNI_ARGS_METHOD(jint, AndroidHostInterface_getControllerAxisCode, jobject unused, jstring controller_type,
|
||||||
|
jstring axis_name)
|
||||||
|
{
|
||||||
|
std::optional<ControllerType> type =
|
||||||
|
Settings::ParseControllerTypeName(AndroidHelpers::JStringToString(env, controller_type).c_str());
|
||||||
|
if (!type)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
std::optional<s32> code =
|
||||||
|
Controller::GetAxisCodeByName(type.value(), AndroidHelpers::JStringToString(env, axis_name));
|
||||||
|
return code.value_or(-1);
|
||||||
|
}
|
||||||
|
|
||||||
DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_refreshGameList, jobject obj, jboolean invalidate_cache, jboolean invalidate_database)
|
DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_refreshGameList, jobject obj, jboolean invalidate_cache, jboolean invalidate_database)
|
||||||
{
|
{
|
||||||
AndroidHelpers::GetNativeClass(env, obj)->RefreshGameList(invalidate_cache, invalidate_database);
|
AndroidHelpers::GetNativeClass(env, obj)->RefreshGameList(invalidate_cache, invalidate_database);
|
||||||
|
|
|
@ -43,6 +43,7 @@ public:
|
||||||
|
|
||||||
void SetControllerType(u32 index, std::string_view type_name);
|
void SetControllerType(u32 index, std::string_view type_name);
|
||||||
void SetControllerButtonState(u32 index, s32 button_code, bool pressed);
|
void SetControllerButtonState(u32 index, s32 button_code, bool pressed);
|
||||||
|
void SetControllerAxisState(u32 index, s32 button_code, float value);
|
||||||
|
|
||||||
void RefreshGameList(bool invalidate_cache, bool invalidate_database);
|
void RefreshGameList(bool invalidate_cache, bool invalidate_database);
|
||||||
void ApplySettings();
|
void ApplySettings();
|
||||||
|
@ -54,7 +55,6 @@ protected:
|
||||||
|
|
||||||
bool AcquireHostDisplay() override;
|
bool AcquireHostDisplay() override;
|
||||||
void ReleaseHostDisplay() override;
|
void ReleaseHostDisplay() override;
|
||||||
std::unique_ptr<AudioStream> CreateAudioStream(AudioBackend backend) override;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void EmulationThreadEntryPoint(ANativeWindow* initial_surface, SystemBootParameters boot_params);
|
void EmulationThreadEntryPoint(ANativeWindow* initial_surface, SystemBootParameters boot_params);
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
#include "core/host_interface.h"
|
|
||||||
#include <jni.h>
|
|
||||||
|
|
||||||
#define DEFINE_JNI_METHOD(return_type, name, ...) \
|
|
||||||
extern "C" JNIEXPORT return_type JNICALL Java_com_github_stenzek_duckstation_##name(__VA_ARGS__)
|
|
||||||
|
|
||||||
DEFINE_JNI_METHOD(bool, createSystem)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
DEFINE_JNI_METHOD(bool, bootSystem, const char* filename, const char* state_filename)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
DEFINE_JNI_METHOD(void, runFrame) {}
|
|
|
@ -23,7 +23,9 @@ public class AndroidHostInterface
|
||||||
// TODO: Find a better place for this.
|
// TODO: Find a better place for this.
|
||||||
public native void setControllerType(int index, String typeName);
|
public native void setControllerType(int index, String typeName);
|
||||||
public native void setControllerButtonState(int index, int buttonCode, boolean pressed);
|
public native void setControllerButtonState(int index, int buttonCode, boolean pressed);
|
||||||
|
public native void setControllerAxisState(int index, int axisCode, float value);
|
||||||
public static native int getControllerButtonCode(String controllerType, String buttonName);
|
public static native int getControllerButtonCode(String controllerType, String buttonName);
|
||||||
|
public static native int getControllerAxisCode(String controllerType, String axisName);
|
||||||
|
|
||||||
public native void refreshGameList(boolean invalidateCache, boolean invalidateDatabase);
|
public native void refreshGameList(boolean invalidateCache, boolean invalidateDatabase);
|
||||||
public native GameListEntry[] getGameListEntries();
|
public native GameListEntry[] getGameListEntries();
|
||||||
|
|
|
@ -38,6 +38,9 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
|
||||||
editor.putBoolean(key, value);
|
editor.putBoolean(key, value);
|
||||||
editor.apply();
|
editor.apply();
|
||||||
}
|
}
|
||||||
|
private String getStringSetting(String key, String defaultValue) {
|
||||||
|
return mPreferences.getString(key, defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Touchscreen controller overlay
|
* Touchscreen controller overlay
|
||||||
|
@ -154,15 +157,17 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Hook up controller input.
|
||||||
|
final String controllerType = getStringSetting("Controller1/Type", "DigitalController");
|
||||||
|
Log.i("EmulationActivity", "Controller type: " + controllerType);
|
||||||
|
mContentView.initControllerKeyMapping(controllerType);
|
||||||
|
|
||||||
// Create touchscreen controller.
|
// Create touchscreen controller.
|
||||||
FrameLayout activityLayout = findViewById(R.id.frameLayout);
|
FrameLayout activityLayout = findViewById(R.id.frameLayout);
|
||||||
mTouchscreenController = new TouchscreenControllerView(this);
|
mTouchscreenController = new TouchscreenControllerView(this);
|
||||||
activityLayout.addView(mTouchscreenController);
|
activityLayout.addView(mTouchscreenController);
|
||||||
mTouchscreenController.init(0, "DigitalController", AndroidHostInterface.getInstance());
|
mTouchscreenController.init(0, controllerType);
|
||||||
setTouchscreenControllerVisibility(true);
|
setTouchscreenControllerVisibility(getBooleanSetting("Controller1/EnableTouchscreenController", true));
|
||||||
|
|
||||||
// Hook up controller input.
|
|
||||||
mContentView.initControllerKeyMapping("DigitalController");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -181,6 +186,7 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
|
||||||
getMenuInflater().inflate(R.menu.menu_emulation, menu);
|
getMenuInflater().inflate(R.menu.menu_emulation, menu);
|
||||||
menu.findItem(R.id.show_controller).setChecked(mTouchscreenControllerVisible);
|
menu.findItem(R.id.show_controller).setChecked(mTouchscreenControllerVisible);
|
||||||
menu.findItem(R.id.enable_speed_limiter).setChecked(getBooleanSetting("Main/SpeedLimiterEnabled", true));
|
menu.findItem(R.id.enable_speed_limiter).setChecked(getBooleanSetting("Main/SpeedLimiterEnabled", true));
|
||||||
|
menu.findItem(R.id.show_controller).setChecked(getBooleanSetting("Controller1/EnableTouchscreenController", true));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,10 @@ import android.content.Context;
|
||||||
import android.util.ArrayMap;
|
import android.util.ArrayMap;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.util.Pair;
|
||||||
import android.view.InputDevice;
|
import android.view.InputDevice;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
|
import android.view.MotionEvent;
|
||||||
import android.view.SurfaceView;
|
import android.view.SurfaceView;
|
||||||
|
|
||||||
public class EmulationSurfaceView extends SurfaceView {
|
public class EmulationSurfaceView extends SurfaceView {
|
||||||
|
@ -48,7 +50,49 @@ public class EmulationSurfaceView extends SurfaceView {
|
||||||
return super.onKeyDown(keyCode, event);
|
return super.onKeyDown(keyCode, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onGenericMotionEvent(MotionEvent event) {
|
||||||
|
final int source = event.getSource();
|
||||||
|
if ((source & InputDevice.SOURCE_JOYSTICK) == 0)
|
||||||
|
return super.onGenericMotionEvent(event);
|
||||||
|
|
||||||
|
final InputDevice device = event.getDevice();
|
||||||
|
for (int axis : AXISES) {
|
||||||
|
Integer mapping = mControllerAxisMapping.containsKey(axis) ? mControllerAxisMapping.get(axis) : null;
|
||||||
|
Pair<Integer, Integer> buttonMapping = mControllerAxisButtonMapping.containsKey(axis) ? mControllerAxisButtonMapping.get(axis) : null;
|
||||||
|
if (mapping == null && buttonMapping == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
final float axisValue = event.getAxisValue(axis);
|
||||||
|
float emuValue;
|
||||||
|
|
||||||
|
final InputDevice.MotionRange range = device.getMotionRange(axis, source);
|
||||||
|
if (range != null) {
|
||||||
|
final float transformedValue = (axisValue - range.getMin()) / range.getRange();
|
||||||
|
emuValue = (transformedValue * 2.0f) - 1.0f;
|
||||||
|
} else {
|
||||||
|
emuValue = axisValue;
|
||||||
|
}
|
||||||
|
Log.d("EmulationSurfaceView", String.format("axis %d value %f emuvalue %f", axis, axisValue, emuValue));
|
||||||
|
if (mapping != null) {
|
||||||
|
AndroidHostInterface.getInstance().setControllerAxisState(0, mapping, emuValue);
|
||||||
|
} else {
|
||||||
|
final float DEAD_ZONE = 0.25f;
|
||||||
|
AndroidHostInterface.getInstance().setControllerButtonState(0, buttonMapping.first, (emuValue <= -DEAD_ZONE));
|
||||||
|
AndroidHostInterface.getInstance().setControllerButtonState(0, buttonMapping.second, (emuValue >= DEAD_ZONE));
|
||||||
|
Log.d("EmulationSurfaceView", String.format("using emuValue %f for buttons %d %d", emuValue, buttonMapping.first, buttonMapping.second));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private ArrayMap<Integer, Integer> mControllerKeyMapping;
|
private ArrayMap<Integer, Integer> mControllerKeyMapping;
|
||||||
|
private ArrayMap<Integer, Integer> mControllerAxisMapping;
|
||||||
|
private ArrayMap<Integer, Pair<Integer, Integer>> mControllerAxisButtonMapping;
|
||||||
|
static final int[] AXISES = new int[]{MotionEvent.AXIS_X, MotionEvent.AXIS_Y, MotionEvent.AXIS_RX,
|
||||||
|
MotionEvent.AXIS_RY, MotionEvent.AXIS_Z, MotionEvent.AXIS_RZ,
|
||||||
|
MotionEvent.AXIS_HAT_X, MotionEvent.AXIS_HAT_Y};
|
||||||
|
|
||||||
private void addControllerKeyMapping(int keyCode, String controllerType, String buttonName) {
|
private void addControllerKeyMapping(int keyCode, String controllerType, String buttonName) {
|
||||||
int mapping = AndroidHostInterface.getControllerButtonCode(controllerType, buttonName);
|
int mapping = AndroidHostInterface.getControllerButtonCode(controllerType, buttonName);
|
||||||
|
@ -58,8 +102,31 @@ public class EmulationSurfaceView extends SurfaceView {
|
||||||
mControllerKeyMapping.put(keyCode, mapping);
|
mControllerKeyMapping.put(keyCode, mapping);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addControllerAxisMapping(int axis, String controllerType, String axisName, String negativeButtonName, String positiveButtonName) {
|
||||||
|
if (axisName != null) {
|
||||||
|
int mapping = AndroidHostInterface.getControllerAxisCode(controllerType, axisName);
|
||||||
|
Log.i("EmulationSurfaceView", String.format("Map axis %d to %d (%s)", axis, mapping, axisName));
|
||||||
|
if (mapping >= 0) {
|
||||||
|
mControllerAxisMapping.put(axis, mapping);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (negativeButtonName != null && positiveButtonName != null) {
|
||||||
|
final int negativeMapping = AndroidHostInterface.getControllerButtonCode(controllerType, negativeButtonName);
|
||||||
|
final int positiveMapping = AndroidHostInterface.getControllerButtonCode(controllerType, positiveButtonName);
|
||||||
|
Log.i("EmulationSurfaceView", String.format("Map axis %d to %d %d (button %s %s)", axis, negativeMapping, positiveMapping,
|
||||||
|
negativeButtonName, positiveButtonName));
|
||||||
|
if (negativeMapping >= 0 && positiveMapping >= 0) {
|
||||||
|
mControllerAxisButtonMapping.put(axis, new Pair<Integer, Integer>(negativeMapping, positiveMapping));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void initControllerKeyMapping(String controllerType) {
|
public void initControllerKeyMapping(String controllerType) {
|
||||||
mControllerKeyMapping = new ArrayMap<>();
|
mControllerKeyMapping = new ArrayMap<>();
|
||||||
|
mControllerAxisMapping = new ArrayMap<>();
|
||||||
|
mControllerAxisButtonMapping = new ArrayMap<>();
|
||||||
|
|
||||||
// TODO: Don't hardcode...
|
// TODO: Don't hardcode...
|
||||||
addControllerKeyMapping(KeyEvent.KEYCODE_DPAD_UP, controllerType, "Up");
|
addControllerKeyMapping(KeyEvent.KEYCODE_DPAD_UP, controllerType, "Up");
|
||||||
|
@ -76,6 +143,14 @@ public class EmulationSurfaceView extends SurfaceView {
|
||||||
addControllerKeyMapping(KeyEvent.KEYCODE_BUTTON_X, controllerType, "Square");
|
addControllerKeyMapping(KeyEvent.KEYCODE_BUTTON_X, controllerType, "Square");
|
||||||
addControllerKeyMapping(KeyEvent.KEYCODE_BUTTON_R1, controllerType, "R1");
|
addControllerKeyMapping(KeyEvent.KEYCODE_BUTTON_R1, controllerType, "R1");
|
||||||
addControllerKeyMapping(KeyEvent.KEYCODE_BUTTON_R2, controllerType, "R2");
|
addControllerKeyMapping(KeyEvent.KEYCODE_BUTTON_R2, controllerType, "R2");
|
||||||
|
addControllerAxisMapping(MotionEvent.AXIS_X, controllerType, "LeftX", null, null);
|
||||||
|
addControllerAxisMapping(MotionEvent.AXIS_Y, controllerType, "LeftY", null, null);
|
||||||
|
addControllerAxisMapping(MotionEvent.AXIS_RX, controllerType, "RightX", null, null);
|
||||||
|
addControllerAxisMapping(MotionEvent.AXIS_RY, controllerType, "RightY", null, null);
|
||||||
|
addControllerAxisMapping(MotionEvent.AXIS_Z, controllerType, "L2", "L2", "L2");
|
||||||
|
addControllerAxisMapping(MotionEvent.AXIS_RZ, controllerType, "R2", "R2", "R2");
|
||||||
|
addControllerAxisMapping(MotionEvent.AXIS_HAT_X, controllerType, null, "Left", "Right");
|
||||||
|
addControllerAxisMapping(MotionEvent.AXIS_HAT_Y, controllerType, null, "Up", "Down");
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean handleControllerKey(int keyCode, boolean pressed) {
|
private boolean handleControllerKey(int keyCode, boolean pressed) {
|
||||||
|
@ -84,6 +159,7 @@ public class EmulationSurfaceView extends SurfaceView {
|
||||||
|
|
||||||
final int mapping = mControllerKeyMapping.get(keyCode);
|
final int mapping = mControllerKeyMapping.get(keyCode);
|
||||||
AndroidHostInterface.getInstance().setControllerButtonState(0, mapping, pressed);
|
AndroidHostInterface.getInstance().setControllerButtonState(0, mapping, pressed);
|
||||||
|
Log.d("EmulationSurfaceView", String.format("handleControllerKey %d -> %d %d", keyCode, mapping, pressed ? 1 : 0));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,6 @@ import android.widget.FrameLayout;
|
||||||
public class TouchscreenControllerView extends FrameLayout implements TouchscreenControllerButtonView.ButtonStateChangedListener {
|
public class TouchscreenControllerView extends FrameLayout implements TouchscreenControllerButtonView.ButtonStateChangedListener {
|
||||||
private int mControllerIndex;
|
private int mControllerIndex;
|
||||||
private String mControllerType;
|
private String mControllerType;
|
||||||
private AndroidHostInterface mHostInterface;
|
|
||||||
|
|
||||||
public TouchscreenControllerView(Context context) {
|
public TouchscreenControllerView(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
|
@ -27,14 +26,9 @@ public class TouchscreenControllerView extends FrameLayout implements Touchscree
|
||||||
super(context, attrs, defStyle);
|
super(context, attrs, defStyle);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void init(int controllerIndex, String controllerType,
|
public void init(int controllerIndex, String controllerType) {
|
||||||
AndroidHostInterface hostInterface) {
|
|
||||||
mControllerIndex = controllerIndex;
|
mControllerIndex = controllerIndex;
|
||||||
mControllerType = controllerType;
|
mControllerType = controllerType;
|
||||||
mHostInterface = hostInterface;
|
|
||||||
|
|
||||||
if (mHostInterface != null)
|
|
||||||
mHostInterface.setControllerType(controllerIndex, controllerType);
|
|
||||||
|
|
||||||
LayoutInflater inflater = LayoutInflater.from(getContext());
|
LayoutInflater inflater = LayoutInflater.from(getContext());
|
||||||
View view = inflater.inflate(R.layout.layout_touchscreen_controller, this, true);
|
View view = inflater.inflate(R.layout.layout_touchscreen_controller, this, true);
|
||||||
|
@ -62,25 +56,22 @@ public class TouchscreenControllerView extends FrameLayout implements Touchscree
|
||||||
buttonView.setButtonName(buttonName);
|
buttonView.setButtonName(buttonName);
|
||||||
buttonView.setButtonStateChangedListener(this);
|
buttonView.setButtonStateChangedListener(this);
|
||||||
|
|
||||||
if (mHostInterface != null)
|
int code = AndroidHostInterface.getInstance().getControllerButtonCode(mControllerType, buttonName);
|
||||||
{
|
buttonView.setButtonCode(code);
|
||||||
int code = mHostInterface.getControllerButtonCode(mControllerType, buttonName);
|
Log.i("TouchscreenController", String.format("%s -> %d", buttonName, code));
|
||||||
buttonView.setButtonCode(code);
|
|
||||||
Log.i("TouchscreenController", String.format("%s -> %d", buttonName, code));
|
|
||||||
|
|
||||||
if (code < 0) {
|
if (code < 0) {
|
||||||
Log.e("TouchscreenController", String.format("Unknown button name '%s' " +
|
Log.e("TouchscreenController", String.format("Unknown button name '%s' " +
|
||||||
"for '%s'", buttonName, mControllerType));
|
"for '%s'", buttonName, mControllerType));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onButtonStateChanged(TouchscreenControllerButtonView view, boolean pressed) {
|
public void onButtonStateChanged(TouchscreenControllerButtonView view, boolean pressed) {
|
||||||
if (mHostInterface == null || view.getButtonCode() < 0)
|
if (view.getButtonCode() < 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
mHostInterface.setControllerButtonState(mControllerIndex, view.getButtonCode(), pressed);
|
AndroidHostInterface.getInstance().setControllerButtonState(mControllerIndex, view.getButtonCode(), pressed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,4 +67,12 @@
|
||||||
<item>15</item>
|
<item>15</item>
|
||||||
<item>16</item>
|
<item>16</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
<string-array name="settings_controller_type_entries">
|
||||||
|
<item>Digital Controller (Gamepad)</item>
|
||||||
|
<item>Analog Controller (DualShock)</item>
|
||||||
|
</string-array>
|
||||||
|
<string-array name="settings_controller_type_values">
|
||||||
|
<item>DigitalController</item>
|
||||||
|
<item>AnalogController</item>
|
||||||
|
</string-array>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -41,11 +41,6 @@
|
||||||
app:defaultValue="@string/settings_console_region_default"
|
app:defaultValue="@string/settings_console_region_default"
|
||||||
app:useSimpleSummaryProvider="true" />
|
app:useSimpleSummaryProvider="true" />
|
||||||
|
|
||||||
<EditTextPreference
|
|
||||||
app:key="BIOS/Path"
|
|
||||||
app:title="@string/settings_console_bios_path"
|
|
||||||
app:useSimpleSummaryProvider="true" />
|
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreferenceCompat
|
||||||
app:key="BIOS/PatchTTYEnable"
|
app:key="BIOS/PatchTTYEnable"
|
||||||
app:title="@string/settings_console_tty_output"
|
app:title="@string/settings_console_tty_output"
|
||||||
|
@ -144,4 +139,22 @@
|
||||||
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
|
<PreferenceCategory app:title="Controller">
|
||||||
|
<ListPreference
|
||||||
|
app:key="Controller1/Type"
|
||||||
|
app:title="Controller Type"
|
||||||
|
app:entries="@array/settings_controller_type_entries"
|
||||||
|
app:entryValues="@array/settings_controller_type_values"
|
||||||
|
app:defaultValue="DigitalController"
|
||||||
|
app:useSimpleSummaryProvider="true" />
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
app:key="Controller1/AutoEnableAnalog"
|
||||||
|
app:title="Enable Analog Mode On Reset"
|
||||||
|
app:defaultValue="false" />
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
app:key="Controller1/EnableTouchscreenController"
|
||||||
|
app:title="Display Touchscreen Controller"
|
||||||
|
app:defaultValue="true" />
|
||||||
|
</PreferenceCategory>
|
||||||
|
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
|
|
Loading…
Reference in New Issue