Add support for controller on Android

This commit is contained in:
SSimco
2023-07-02 22:05:55 +03:00
parent 8006ec36ef
commit 29841a3fb2
43 changed files with 1801 additions and 135 deletions
+168 -2
View File
@@ -14,7 +14,10 @@
#include "input/InputManager.h"
#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h"
#include "AndroidGui/AndroidAudio.h"
#include "input/api/Android/AndroidControllerProvider.h"
#include "input/api/Android/AndroidController.h"
#include "input/ControllerFactory.h"
#include "AndroidGui/AndroidEmulatedController.h"
#include "JNIUtils.h"
extern "C"
@@ -345,4 +348,167 @@ extern "C"
JNIEXPORT void JNICALL
Java_info_cemu_Cemu_NativeLibrary_addGamePath(JNIEnv *env, jclass clazz, jstring uri) {
gui_addGamePath(JNIUtils::JStringToString(env, uri));
}
}
extern "C"
JNIEXPORT void JNICALL
Java_info_cemu_Cemu_NativeLibrary_onNativeKey(JNIEnv *env, jclass clazz, jstring device_descriptor,
jstring device_name, jint key, jboolean is_pressed) {
auto apiProvider = InputManager::instance().get_api_provider(InputAPI::Android);
auto androidControllerProvider = dynamic_cast<AndroidControllerProvider *>(apiProvider.get());
if (androidControllerProvider)
androidControllerProvider->on_key_event(JNIUtils::JStringToString(env, device_descriptor),
JNIUtils::JStringToString(env, device_name),
key,
is_pressed);
}
extern "C"
JNIEXPORT void JNICALL
Java_info_cemu_Cemu_NativeLibrary_onNativeAxis(JNIEnv *env, jclass clazz, jstring device_descriptor,
jstring device_name, jint axis, jfloat value) {
auto apiProvider = InputManager::instance().get_api_provider(InputAPI::Android);
auto androidControllerProvider = dynamic_cast<AndroidControllerProvider *>(apiProvider.get());
androidControllerProvider->on_axis_event(JNIUtils::JStringToString(env, device_descriptor),
JNIUtils::JStringToString(env, device_name),
axis,
value);
}
extern "C"
JNIEXPORT void JNICALL
Java_info_cemu_Cemu_NativeLibrary_setControllerType(JNIEnv *env, jclass clazz, jint index,
jint emulated_controller_type) {
EmulatedController::Type type = static_cast<EmulatedController::Type>(emulated_controller_type);
auto &androidEmulatedController = AndroidEmulatedController::getAndroidEmulatedController(
index);
if (EmulatedController::Type::VPAD <= type && type < EmulatedController::Type::MAX)
androidEmulatedController.setType(type);
else
androidEmulatedController.setDisabled();
}
extern "C"
JNIEXPORT jint JNICALL
Java_info_cemu_Cemu_NativeLibrary_getControllerType(JNIEnv *env, jclass clazz, jint index) {
auto emulatedController = AndroidEmulatedController::getAndroidEmulatedController(
index).getEmulatedController();
if (emulatedController)
return emulatedController->type();
return -1;
}
extern "C"
JNIEXPORT jint JNICALL
Java_info_cemu_Cemu_NativeLibrary_getWPADControllersCount(JNIEnv *env, jclass clazz) {
jint wpadCount = 0;
for (int i = 0; i < InputManager::kMaxController; i++) {
auto emulatedController = AndroidEmulatedController::getAndroidEmulatedController(
i).getEmulatedController();
if (!emulatedController)
continue;
if (emulatedController->type() != EmulatedController::Type::VPAD)
++wpadCount;
}
return wpadCount;
}
extern "C"
JNIEXPORT jint JNICALL
Java_info_cemu_Cemu_NativeLibrary_getVPADControllersCount(JNIEnv *env, jclass clazz) {
jint vpadCount = 0;
for (int i = 0; i < InputManager::kMaxController; i++) {
auto emulatedController = AndroidEmulatedController::getAndroidEmulatedController(
i).getEmulatedController();
if (!emulatedController)
continue;
if (emulatedController->type() == EmulatedController::Type::VPAD)
++vpadCount;
}
return vpadCount;
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_info_cemu_Cemu_NativeLibrary_isControllerDisabled(JNIEnv *env, jclass clazz, jint index) {
return AndroidEmulatedController::getAndroidEmulatedController(index).getEmulatedController() ==
nullptr;
}
extern "C"
JNIEXPORT void JNICALL
Java_info_cemu_Cemu_NativeLibrary_setControllerMapping(JNIEnv *env, jclass clazz,
jstring device_descriptor,
jstring device_name, jint index,
jint mapping_id, jint button_id) {
auto apiProvider = InputManager::instance().get_api_provider(InputAPI::Android);
auto androidControllerProvider = dynamic_cast<AndroidControllerProvider *>(apiProvider.get());
auto deviceName = JNIUtils::JStringToString(env, device_name);
auto deviceDescriptor = JNIUtils::JStringToString(env, device_descriptor);
auto controller = ControllerFactory::CreateController(InputAPI::Android, deviceDescriptor,
deviceName);
AndroidEmulatedController::getAndroidEmulatedController(index).setMapping(mapping_id,
controller,
button_id);
}
extern "C"
JNIEXPORT jstring JNICALL
Java_info_cemu_Cemu_NativeLibrary_getControllerMapping(JNIEnv *env, jclass clazz, jint index,
jint mapping_id) {
auto mapping = AndroidEmulatedController::getAndroidEmulatedController(index)
.getMapping(mapping_id);
return env->NewStringUTF(mapping.value_or("").c_str());
}
extern "C"
JNIEXPORT void JNICALL
Java_info_cemu_Cemu_NativeLibrary_clearControllerMapping(JNIEnv *env, jclass clazz, jint index,
jint mapping_id) {
AndroidEmulatedController::getAndroidEmulatedController(index).clearMapping(mapping_id);
}
extern "C"
JNIEXPORT jobject JNICALL
Java_info_cemu_Cemu_NativeLibrary_getControllerMappings(JNIEnv *env, jclass clazz, jint index) {
jclass hashMapClass = env->FindClass("java/util/HashMap");
jmethodID hashMapConstructor = env->GetMethodID(hashMapClass, "<init>", "()V");
jmethodID hashMapPut = env->GetMethodID(hashMapClass, "put",
"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
jclass integerClass = env->FindClass("java/lang/Integer");
jmethodID integerConstructor = env->GetMethodID(integerClass, "<init>", "(I)V");
jobject hashMapObj = env->NewObject(hashMapClass, hashMapConstructor);
auto mappings = AndroidEmulatedController::getAndroidEmulatedController(index).getMappings();
for (const auto &pair: mappings) {
jint key = pair.first;
jstring buttonName = env->NewStringUTF(pair.second.c_str());
jobject mappingId = env->NewObject(integerClass, integerConstructor, key);
env->CallObjectMethod(hashMapObj, hashMapPut, mappingId, buttonName);
}
return hashMapObj;
}
extern "C"
JNIEXPORT void JNICALL
Java_info_cemu_Cemu_NativeLibrary_onKeyEvent(JNIEnv *env, jclass clazz, jstring device_descriptor,
jstring device_name, jint key_code,
jboolean is_pressed) {
auto apiProvider = InputManager::instance().get_api_provider(InputAPI::Android);
auto androidControllerProvider = dynamic_cast<AndroidControllerProvider *>(apiProvider.get());
auto deviceDescriptor = JNIUtils::JStringToString(env, device_descriptor);
auto deviceName = JNIUtils::JStringToString(env, device_name);
androidControllerProvider->on_key_event(deviceDescriptor, deviceName, key_code, is_pressed);
}
extern "C"
JNIEXPORT void JNICALL
Java_info_cemu_Cemu_NativeLibrary_onAxisEvent(JNIEnv *env, jclass clazz, jstring device_descriptor,
jstring device_name, jint axis_code,
jfloat value) {
auto apiProvider = InputManager::instance().get_api_provider(InputAPI::Android);
auto androidControllerProvider = dynamic_cast<AndroidControllerProvider *>(apiProvider.get());
auto deviceDescriptor = JNIUtils::JStringToString(env, device_descriptor);
auto deviceName = JNIUtils::JStringToString(env, device_name);
androidControllerProvider->on_axis_event(deviceDescriptor, deviceName, axis_code, value);
}
@@ -0,0 +1,77 @@
package info.cemu.Cemu;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;
public class ButtonListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
public interface OnButtonClickListener {
void onButtonClicked();
}
private static class ButtonInfo {
public ButtonInfo(String text, String description, OnButtonClickListener onButtonClickListener) {
this.text = text;
this.description = description;
this.onButtonClickListener = onButtonClickListener;
}
String text;
String description;
OnButtonClickListener onButtonClickListener;
}
private final List<ButtonInfo> buttonInfoList = new ArrayList<>();
public void addButtonInfo(String text, String description, OnButtonClickListener onButtonClickListener) {
buttonInfoList.add(new ButtonInfo(text, description, onButtonClickListener));
notifyItemInserted(buttonInfoList.size());
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new ButtonViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_button, parent, false));
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
((ButtonViewHolder) holder).bind(buttonInfoList.get(position));
holder.itemView.setOnClickListener(view -> {
ButtonInfo buttonInfo = buttonInfoList.get(holder.getAdapterPosition());
if (buttonInfo.onButtonClickListener != null) {
buttonInfo.onButtonClickListener.onButtonClicked();
}
});
}
@Override
public int getItemCount() {
return buttonInfoList.size();
}
private static class ButtonViewHolder extends RecyclerView.ViewHolder {
TextView text;
TextView description;
void bind(ButtonInfo buttonInfo) {
text.setText(buttonInfo.text);
description.setText(buttonInfo.description);
}
public ButtonViewHolder(View itemView) {
super(itemView);
text = itemView.findViewById(R.id.button_text);
description = itemView.findViewById(R.id.button_description);
}
}
}
@@ -0,0 +1,22 @@
package info.cemu.Cemu;
public class ControllerHeaderItem implements ControllerItem {
private String header;
public ControllerHeaderItem(String header) {
this.header = header;
}
@Override
public int getType() {
return TYPE_HEADER;
}
public String getHeader() {
return header;
}
public void setHeader(String header) {
this.header = header;
}
}
@@ -0,0 +1,34 @@
package info.cemu.Cemu;
public class ControllerInputItem implements ControllerItem {
private final int nameResourceId;
private final int id;
private String boundInput;
public ControllerInputItem(int nameResourceId, int id, String boundInput) {
this.nameResourceId = nameResourceId;
this.id = id;
this.boundInput = boundInput;
}
@Override
public int getType() {
return TYPE_INPUT;
}
public int getNameResourceId() {
return nameResourceId;
}
public int getId() {
return id;
}
public String getBoundInput() {
return boundInput;
}
public void setBoundInput(String boundInput) {
this.boundInput = boundInput;
}
}
@@ -0,0 +1,41 @@
package info.cemu.Cemu;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
public class ControllerInputListener {
private static boolean isController(int sources) {
return ((sources & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK) ||
((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) ||
((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD);
}
public static boolean onKeyEvent(KeyEvent keyEvent) {
InputDevice inputDevice = keyEvent.getDevice();
if (isController(inputDevice.getSources())) {
String descriptor = inputDevice.getDescriptor();
String name = inputDevice.getName();
NativeLibrary.onNativeKey(descriptor == null ? name : descriptor, name, keyEvent.getKeyCode(), keyEvent.getAction() == KeyEvent.ACTION_DOWN);
return true;
}
return false;
}
public static boolean onMotionEvent(MotionEvent motionEvent) {
int actionPointerIndex = motionEvent.getActionIndex();
int action = motionEvent.getActionMasked();
if (action == MotionEvent.ACTION_MOVE) {
InputDevice inputDevice = motionEvent.getDevice();
String descriptor = inputDevice.getDescriptor();
String name = inputDevice.getName();
for (InputDevice.MotionRange range : inputDevice.getMotionRanges()) {
int axis = range.getAxis();
float value = (motionEvent.getAxisValue(axis, actionPointerIndex) - range.getMin()) / range.getRange() * 2.0f - 1.0f;
NativeLibrary.onNativeAxis(descriptor == null ? name : descriptor, name, axis, value);
}
return true;
}
return false;
}
}
@@ -0,0 +1,164 @@
package info.cemu.Cemu;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class ControllerInputsDataProvider {
private int getButtonResourceIdName(int controllerType, int buttonId) {
if (controllerType == NativeLibrary.EMULATED_CONTROLLER_TYPE_VPAD) {
return switch (buttonId) {
case NativeLibrary.VPAD_BUTTON_A -> R.string.button_a;
case NativeLibrary.VPAD_BUTTON_B -> R.string.button_b;
case NativeLibrary.VPAD_BUTTON_X -> R.string.button_x;
case NativeLibrary.VPAD_BUTTON_Y -> R.string.button_y;
case NativeLibrary.VPAD_BUTTON_L -> R.string.button_l;
case NativeLibrary.VPAD_BUTTON_R -> R.string.button_r;
case NativeLibrary.VPAD_BUTTON_ZL -> R.string.button_zl;
case NativeLibrary.VPAD_BUTTON_ZR -> R.string.button_zr;
case NativeLibrary.VPAD_BUTTON_PLUS -> R.string.button_plus;
case NativeLibrary.VPAD_BUTTON_MINUS -> R.string.button_minus;
case NativeLibrary.VPAD_BUTTON_UP -> R.string.button_up;
case NativeLibrary.VPAD_BUTTON_DOWN -> R.string.button_down;
case NativeLibrary.VPAD_BUTTON_LEFT -> R.string.button_left;
case NativeLibrary.VPAD_BUTTON_RIGHT -> R.string.button_right;
case NativeLibrary.VPAD_BUTTON_STICKL -> R.string.button_stickl;
case NativeLibrary.VPAD_BUTTON_STICKR -> R.string.button_stickr;
case NativeLibrary.VPAD_BUTTON_STICKL_UP -> R.string.button_stickl_up;
case NativeLibrary.VPAD_BUTTON_STICKL_DOWN -> R.string.button_stickl_down;
case NativeLibrary.VPAD_BUTTON_STICKL_LEFT -> R.string.button_stickl_left;
case NativeLibrary.VPAD_BUTTON_STICKL_RIGHT -> R.string.button_stickl_right;
case NativeLibrary.VPAD_BUTTON_STICKR_UP -> R.string.button_stickr_up;
case NativeLibrary.VPAD_BUTTON_STICKR_DOWN -> R.string.button_stickr_down;
case NativeLibrary.VPAD_BUTTON_STICKR_LEFT -> R.string.button_stickr_left;
case NativeLibrary.VPAD_BUTTON_STICKR_RIGHT -> R.string.button_stickr_right;
case NativeLibrary.VPAD_BUTTON_MIC -> R.string.button_mic;
case NativeLibrary.VPAD_BUTTON_SCREEN -> R.string.button_screen;
case NativeLibrary.VPAD_BUTTON_HOME -> R.string.button_home;
default ->
throw new RuntimeException("Invalid buttonId " + buttonId + " for controllerType " + controllerType);
};
}
if (controllerType == NativeLibrary.EMULATED_CONTROLLER_TYPE_PRO) {
return switch (buttonId) {
case NativeLibrary.PRO_BUTTON_A -> R.string.button_a;
case NativeLibrary.PRO_BUTTON_B -> R.string.button_b;
case NativeLibrary.PRO_BUTTON_X -> R.string.button_x;
case NativeLibrary.PRO_BUTTON_Y -> R.string.button_y;
case NativeLibrary.PRO_BUTTON_L -> R.string.button_l;
case NativeLibrary.PRO_BUTTON_R -> R.string.button_r;
case NativeLibrary.PRO_BUTTON_ZL -> R.string.button_zl;
case NativeLibrary.PRO_BUTTON_ZR -> R.string.button_zr;
case NativeLibrary.PRO_BUTTON_PLUS -> R.string.button_plus;
case NativeLibrary.PRO_BUTTON_MINUS -> R.string.button_minus;
case NativeLibrary.PRO_BUTTON_HOME -> R.string.button_home;
case NativeLibrary.PRO_BUTTON_UP -> R.string.button_up;
case NativeLibrary.PRO_BUTTON_DOWN -> R.string.button_down;
case NativeLibrary.PRO_BUTTON_LEFT -> R.string.button_left;
case NativeLibrary.PRO_BUTTON_RIGHT -> R.string.button_right;
case NativeLibrary.PRO_BUTTON_STICKL -> R.string.button_stickl;
case NativeLibrary.PRO_BUTTON_STICKR -> R.string.button_stickr;
case NativeLibrary.PRO_BUTTON_STICKL_UP -> R.string.button_stickl_up;
case NativeLibrary.PRO_BUTTON_STICKL_DOWN -> R.string.button_stickl_down;
case NativeLibrary.PRO_BUTTON_STICKL_LEFT -> R.string.button_stickl_left;
case NativeLibrary.PRO_BUTTON_STICKL_RIGHT -> R.string.button_stickl_right;
case NativeLibrary.PRO_BUTTON_STICKR_UP -> R.string.button_stickr_up;
case NativeLibrary.PRO_BUTTON_STICKR_DOWN -> R.string.button_stickr_down;
case NativeLibrary.PRO_BUTTON_STICKR_LEFT -> R.string.button_stickr_left;
case NativeLibrary.PRO_BUTTON_STICKR_RIGHT -> R.string.button_stickr_right;
default ->
throw new RuntimeException("Invalid buttonId " + buttonId + " for controllerType " + controllerType);
};
}
if (controllerType == NativeLibrary.EMULATED_CONTROLLER_TYPE_CLASSIC) {
return switch (buttonId) {
case NativeLibrary.CLASSIC_BUTTON_A -> R.string.button_a;
case NativeLibrary.CLASSIC_BUTTON_B -> R.string.button_b;
case NativeLibrary.CLASSIC_BUTTON_X -> R.string.button_x;
case NativeLibrary.CLASSIC_BUTTON_Y -> R.string.button_y;
case NativeLibrary.CLASSIC_BUTTON_L -> R.string.button_l;
case NativeLibrary.CLASSIC_BUTTON_R -> R.string.button_r;
case NativeLibrary.CLASSIC_BUTTON_ZL -> R.string.button_zl;
case NativeLibrary.CLASSIC_BUTTON_ZR -> R.string.button_zr;
case NativeLibrary.CLASSIC_BUTTON_PLUS -> R.string.button_plus;
case NativeLibrary.CLASSIC_BUTTON_MINUS -> R.string.button_minus;
case NativeLibrary.CLASSIC_BUTTON_HOME -> R.string.button_home;
case NativeLibrary.CLASSIC_BUTTON_UP -> R.string.button_up;
case NativeLibrary.CLASSIC_BUTTON_DOWN -> R.string.button_down;
case NativeLibrary.CLASSIC_BUTTON_LEFT -> R.string.button_left;
case NativeLibrary.CLASSIC_BUTTON_RIGHT -> R.string.button_right;
case NativeLibrary.CLASSIC_BUTTON_STICKL_UP -> R.string.button_stickl_up;
case NativeLibrary.CLASSIC_BUTTON_STICKL_DOWN -> R.string.button_stickl_down;
case NativeLibrary.CLASSIC_BUTTON_STICKL_LEFT -> R.string.button_stickl_left;
case NativeLibrary.CLASSIC_BUTTON_STICKL_RIGHT -> R.string.button_stickl_right;
case NativeLibrary.CLASSIC_BUTTON_STICKR_UP -> R.string.button_stickr_up;
case NativeLibrary.CLASSIC_BUTTON_STICKR_DOWN -> R.string.button_stickr_down;
case NativeLibrary.CLASSIC_BUTTON_STICKR_LEFT -> R.string.button_stickr_left;
case NativeLibrary.CLASSIC_BUTTON_STICKR_RIGHT -> R.string.button_stickr_right;
default ->
throw new RuntimeException("Invalid buttonId " + buttonId + " for controllerType " + controllerType);
};
}
if (controllerType == NativeLibrary.EMULATED_CONTROLLER_TYPE_WIIMOTE) {
return switch (buttonId) {
case NativeLibrary.WIIMOTE_BUTTON_A -> R.string.button_a;
case NativeLibrary.WIIMOTE_BUTTON_B -> R.string.button_b;
case NativeLibrary.WIIMOTE_BUTTON_1 -> R.string.button_1;
case NativeLibrary.WIIMOTE_BUTTON_2 -> R.string.button_2;
case NativeLibrary.WIIMOTE_BUTTON_NUNCHUCK_Z -> R.string.button_nunchuck_z;
case NativeLibrary.WIIMOTE_BUTTON_NUNCHUCK_C -> R.string.button_nunchuck_c;
case NativeLibrary.WIIMOTE_BUTTON_PLUS -> R.string.button_plus;
case NativeLibrary.WIIMOTE_BUTTON_MINUS -> R.string.button_minus;
case NativeLibrary.WIIMOTE_BUTTON_UP -> R.string.button_up;
case NativeLibrary.WIIMOTE_BUTTON_DOWN -> R.string.button_down;
case NativeLibrary.WIIMOTE_BUTTON_LEFT -> R.string.button_left;
case NativeLibrary.WIIMOTE_BUTTON_RIGHT -> R.string.button_right;
case NativeLibrary.WIIMOTE_BUTTON_NUNCHUCK_UP -> R.string.button_nunchuck_up;
case NativeLibrary.WIIMOTE_BUTTON_NUNCHUCK_DOWN -> R.string.button_nunchuck_down;
case NativeLibrary.WIIMOTE_BUTTON_NUNCHUCK_LEFT -> R.string.button_nunchuck_left;
case NativeLibrary.WIIMOTE_BUTTON_NUNCHUCK_RIGHT -> R.string.button_nunchuck_right;
case NativeLibrary.WIIMOTE_BUTTON_HOME -> R.string.button_home;
default ->
throw new RuntimeException("Invalid buttonId " + buttonId + " for controllerType " + controllerType);
};
}
throw new RuntimeException("Invalid controllerType " + controllerType);
}
private void addControllerInputsGroup(List<ControllerItem> controllerInputItems, String groupName, int controllerType, int[] buttons, Map<Integer, String> inputs) {
controllerInputItems.add(new ControllerHeaderItem(groupName));
for (int button : buttons) {
controllerInputItems.add(new ControllerInputItem(getButtonResourceIdName(controllerType, button), button, inputs.getOrDefault(button, "")));
}
}
public List<ControllerItem> getControllerInputs(int controllerType, Map<Integer, String> inputs) {
List<ControllerItem> controllerInputItems = new ArrayList<>();
if (controllerType == NativeLibrary.EMULATED_CONTROLLER_TYPE_VPAD) {
addControllerInputsGroup(controllerInputItems, "Buttons", controllerType, new int[]{NativeLibrary.VPAD_BUTTON_A, NativeLibrary.VPAD_BUTTON_B, NativeLibrary.VPAD_BUTTON_X, NativeLibrary.VPAD_BUTTON_Y, NativeLibrary.VPAD_BUTTON_L, NativeLibrary.VPAD_BUTTON_R, NativeLibrary.VPAD_BUTTON_ZL, NativeLibrary.VPAD_BUTTON_ZR, NativeLibrary.VPAD_BUTTON_PLUS, NativeLibrary.VPAD_BUTTON_MINUS}, inputs);
addControllerInputsGroup(controllerInputItems, "D-Pad", controllerType, new int[]{NativeLibrary.VPAD_BUTTON_UP, NativeLibrary.VPAD_BUTTON_DOWN, NativeLibrary.VPAD_BUTTON_LEFT, NativeLibrary.VPAD_BUTTON_RIGHT}, inputs);
addControllerInputsGroup(controllerInputItems, "Left Axis", controllerType, new int[]{NativeLibrary.VPAD_BUTTON_STICKL, NativeLibrary.VPAD_BUTTON_STICKL_UP, NativeLibrary.VPAD_BUTTON_STICKL_DOWN, NativeLibrary.VPAD_BUTTON_STICKL_LEFT, NativeLibrary.VPAD_BUTTON_STICKL_RIGHT}, inputs);
addControllerInputsGroup(controllerInputItems, "Right Axis", controllerType, new int[]{NativeLibrary.VPAD_BUTTON_STICKR, NativeLibrary.VPAD_BUTTON_STICKR_UP, NativeLibrary.VPAD_BUTTON_STICKR_DOWN, NativeLibrary.VPAD_BUTTON_STICKR_LEFT, NativeLibrary.VPAD_BUTTON_STICKR_RIGHT}, inputs);
addControllerInputsGroup(controllerInputItems, "Extra", controllerType, new int[]{NativeLibrary.VPAD_BUTTON_MIC, NativeLibrary.VPAD_BUTTON_SCREEN, NativeLibrary.VPAD_BUTTON_HOME}, inputs);
}
if (controllerType == NativeLibrary.EMULATED_CONTROLLER_TYPE_PRO) {
addControllerInputsGroup(controllerInputItems, "Buttons", controllerType, new int[]{NativeLibrary.PRO_BUTTON_A, NativeLibrary.PRO_BUTTON_B, NativeLibrary.PRO_BUTTON_X, NativeLibrary.PRO_BUTTON_Y, NativeLibrary.PRO_BUTTON_L, NativeLibrary.PRO_BUTTON_R, NativeLibrary.PRO_BUTTON_ZL, NativeLibrary.PRO_BUTTON_ZR, NativeLibrary.PRO_BUTTON_PLUS, NativeLibrary.PRO_BUTTON_MINUS, NativeLibrary.PRO_BUTTON_HOME}, inputs);
addControllerInputsGroup(controllerInputItems, "Left Axis", controllerType, new int[]{NativeLibrary.PRO_BUTTON_STICKL, NativeLibrary.PRO_BUTTON_STICKL_UP, NativeLibrary.PRO_BUTTON_STICKL_DOWN, NativeLibrary.PRO_BUTTON_STICKL_LEFT, NativeLibrary.PRO_BUTTON_STICKL_RIGHT}, inputs);
addControllerInputsGroup(controllerInputItems, "Right Axis", controllerType, new int[]{NativeLibrary.PRO_BUTTON_STICKR, NativeLibrary.PRO_BUTTON_STICKR_UP, NativeLibrary.PRO_BUTTON_STICKR_DOWN, NativeLibrary.PRO_BUTTON_STICKR_LEFT, NativeLibrary.PRO_BUTTON_STICKR_RIGHT}, inputs);
addControllerInputsGroup(controllerInputItems, "D-Pad", controllerType, new int[]{NativeLibrary.PRO_BUTTON_UP, NativeLibrary.PRO_BUTTON_DOWN, NativeLibrary.PRO_BUTTON_LEFT, NativeLibrary.PRO_BUTTON_RIGHT}, inputs);
}
if (controllerType == NativeLibrary.EMULATED_CONTROLLER_TYPE_CLASSIC) {
addControllerInputsGroup(controllerInputItems, "Buttons", controllerType, new int[]{NativeLibrary.CLASSIC_BUTTON_A, NativeLibrary.CLASSIC_BUTTON_B, NativeLibrary.CLASSIC_BUTTON_X, NativeLibrary.CLASSIC_BUTTON_Y, NativeLibrary.CLASSIC_BUTTON_L, NativeLibrary.CLASSIC_BUTTON_R, NativeLibrary.CLASSIC_BUTTON_ZL, NativeLibrary.CLASSIC_BUTTON_ZR, NativeLibrary.CLASSIC_BUTTON_PLUS, NativeLibrary.CLASSIC_BUTTON_MINUS, NativeLibrary.CLASSIC_BUTTON_HOME}, inputs);
addControllerInputsGroup(controllerInputItems, "Left Axis", controllerType, new int[]{NativeLibrary.CLASSIC_BUTTON_STICKL_UP, NativeLibrary.CLASSIC_BUTTON_STICKL_DOWN, NativeLibrary.CLASSIC_BUTTON_STICKL_LEFT, NativeLibrary.CLASSIC_BUTTON_STICKL_RIGHT}, inputs);
addControllerInputsGroup(controllerInputItems, "Right Axis", controllerType, new int[]{NativeLibrary.CLASSIC_BUTTON_STICKR_UP, NativeLibrary.CLASSIC_BUTTON_STICKR_DOWN, NativeLibrary.CLASSIC_BUTTON_STICKR_LEFT, NativeLibrary.CLASSIC_BUTTON_STICKR_RIGHT}, inputs);
addControllerInputsGroup(controllerInputItems, "D-Pad", controllerType, new int[]{NativeLibrary.CLASSIC_BUTTON_UP, NativeLibrary.CLASSIC_BUTTON_DOWN, NativeLibrary.CLASSIC_BUTTON_LEFT, NativeLibrary.CLASSIC_BUTTON_RIGHT}, inputs);
}
if (controllerType == NativeLibrary.EMULATED_CONTROLLER_TYPE_WIIMOTE) {
addControllerInputsGroup(controllerInputItems, "Buttons", controllerType, new int[]{NativeLibrary.WIIMOTE_BUTTON_A, NativeLibrary.WIIMOTE_BUTTON_B, NativeLibrary.WIIMOTE_BUTTON_1, NativeLibrary.WIIMOTE_BUTTON_2, NativeLibrary.WIIMOTE_BUTTON_NUNCHUCK_Z, NativeLibrary.WIIMOTE_BUTTON_NUNCHUCK_C, NativeLibrary.WIIMOTE_BUTTON_PLUS, NativeLibrary.WIIMOTE_BUTTON_MINUS, NativeLibrary.WIIMOTE_BUTTON_HOME}, inputs);
addControllerInputsGroup(controllerInputItems, "D-Pad", controllerType, new int[]{NativeLibrary.WIIMOTE_BUTTON_UP, NativeLibrary.WIIMOTE_BUTTON_DOWN, NativeLibrary.WIIMOTE_BUTTON_LEFT, NativeLibrary.WIIMOTE_BUTTON_RIGHT}, inputs);
addControllerInputsGroup(controllerInputItems, "Nunchuck", controllerType, new int[]{NativeLibrary.WIIMOTE_BUTTON_NUNCHUCK_UP, NativeLibrary.WIIMOTE_BUTTON_NUNCHUCK_DOWN, NativeLibrary.WIIMOTE_BUTTON_NUNCHUCK_LEFT, NativeLibrary.WIIMOTE_BUTTON_NUNCHUCK_RIGHT}, inputs);
}
return controllerInputItems;
}
}
@@ -0,0 +1,101 @@
package info.cemu.Cemu;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import info.cemu.Cemu.databinding.FragmentControllerInputsBinding;
import java.util.HashMap;
import java.util.Objects;
public class ControllerInputsFragment extends Fragment {
public static final String CONTROLLER_INDEX = "ControllerIndex";
private int controllerIndex;
private int controllerType;
private final InputManager inputManager = new InputManager();
private EmulatedControllerTypeAdapter emulatedControllerTypeAdapter;
private ControllerListAdapter controllerListAdapter;
private void onTypeChanged(int controllerType) {
if (this.controllerType == controllerType) return;
this.controllerType = controllerType;
NativeLibrary.setControllerType(controllerIndex, controllerType);
emulatedControllerTypeAdapter.setCurrentControllerType(controllerType);
controllerListAdapter.setEmulatedControllerListItems(controllerType, new HashMap<>());
emulatedControllerTypeAdapter.setControllerTypeCounts(NativeLibrary.getVPADControllersCount(), NativeLibrary.getWPADControllersCount());
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
controllerIndex = requireArguments().getInt(CONTROLLER_INDEX);
ActionBar actionBar = ((AppCompatActivity) requireActivity()).getSupportActionBar();
if (actionBar != null)
actionBar.setTitle(getString(R.string.controller_numbered, controllerIndex + 1));
if (NativeLibrary.isControllerDisabled(controllerIndex))
controllerType = NativeLibrary.EMULATED_CONTROLLER_TYPE_DISABLED;
else controllerType = NativeLibrary.getControllerType(controllerIndex);
controllerListAdapter = new ControllerListAdapter();
emulatedControllerTypeAdapter = new EmulatedControllerTypeAdapter();
emulatedControllerTypeAdapter.setCurrentControllerType(controllerType);
emulatedControllerTypeAdapter.setControllerTypeCounts(NativeLibrary.getVPADControllersCount(), NativeLibrary.getWPADControllersCount());
FragmentControllerInputsBinding binding = FragmentControllerInputsBinding.inflate(inflater, container, false);
binding.emulatedControllerSpinner.setAdapter(emulatedControllerTypeAdapter);
binding.emulatedControllerSpinner.setSelection(emulatedControllerTypeAdapter.getPosition(controllerType));
binding.emulatedControllerSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> adapterView, View view, int position, long l) {
onTypeChanged(emulatedControllerTypeAdapter.getItem(position));
}
@Override
public void onNothingSelected(AdapterView<?> adapterView) {
}
});
controllerListAdapter.setEmulatedControllerListItems(controllerType, NativeLibrary.getControllerMappings(controllerIndex));
controllerListAdapter.setOnInputClickListener((nameId, id, position) -> {
MotionDialog inputDialog = new MotionDialog(getContext());
inputDialog.setOnKeyListener((dialog, keyCode, keyEvent) -> {
if (inputManager.mapKeyEventToMappingId(controllerIndex, id, keyEvent)) {
controllerListAdapter.setBoundInput(position, NativeLibrary.getControllerMapping(controllerIndex, id));
inputDialog.dismiss();
}
return true;
});
inputDialog.setOnMotionListener(motionEvent -> {
if (inputManager.mapMotionEventToMappingId(controllerIndex, id, motionEvent)) {
controllerListAdapter.setBoundInput(position, NativeLibrary.getControllerMapping(controllerIndex, id));
inputDialog.dismiss();
}
return true;
});
inputDialog.setMessage(getString(R.string.inputBindingDialogMessage, getString(nameId)));
inputDialog.setButton(AlertDialog.BUTTON_NEUTRAL, getString(R.string.clear), (dialogInterface, i) -> {
NativeLibrary.clearControllerMapping(controllerIndex, id);
controllerListAdapter.clearBoundInput(position);
dialogInterface.dismiss();
});
inputDialog.setButton(AlertDialog.BUTTON_NEGATIVE, getString(R.string.cancel), (dialogInterface, i) -> dialogInterface.dismiss());
inputDialog.setTitle(R.string.inputBindingDialogTitle);
inputDialog.show();
});
binding.controllerInputsRecyclerView.setAdapter(controllerListAdapter);
return binding.getRoot();
}
}
@@ -0,0 +1,8 @@
package info.cemu.Cemu;
public interface ControllerItem {
int TYPE_HEADER = 0;
int TYPE_INPUT = 1;
int getType();
}
@@ -0,0 +1,122 @@
package info.cemu.Cemu;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class ControllerListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private List<ControllerItem> controllerItemList = new ArrayList<>();
private final ControllerInputsDataProvider controllerInputs = new ControllerInputsDataProvider();
private OnInputClickListener onInputClickListener;
public void setEmulatedControllerListItems(int controllerType, Map<Integer, String> inputsMapping) {
controllerItemList = controllerInputs.getControllerInputs(controllerType, inputsMapping);
notifyDataSetChanged();
}
public void clearBoundInput(int position) {
ControllerItem listItem = controllerItemList.get(position);
if (listItem.getType() == ControllerItem.TYPE_INPUT) {
ControllerInputItem controllerInputItem = (ControllerInputItem) listItem;
controllerInputItem.setBoundInput("");
controllerItemList.set(position, controllerInputItem);
notifyItemChanged(position);
}
}
public interface OnInputClickListener {
void onInputClicked(int nameId, int id, int position);
}
void setOnInputClickListener(OnInputClickListener onInputClickListener) {
this.onInputClickListener = onInputClickListener;
}
@Override
public int getItemViewType(int position) {
return controllerItemList.get(position).getType();
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (viewType == ControllerItem.TYPE_HEADER) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_controller_header, parent, false);
return new HeaderViewHolder(view);
} else if (viewType == ControllerItem.TYPE_INPUT) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_controller_input, parent, false);
return new InputViewHolder(view);
}
throw new RuntimeException("Invalid viewType: " + viewType);
}
public void setBoundInput(int inputItemPosition, String boundInput) {
ControllerItem listItem = controllerItemList.get(inputItemPosition);
if (listItem.getType() == ControllerItem.TYPE_INPUT) {
ControllerInputItem controllerInputItem = (ControllerInputItem) listItem;
controllerInputItem.setBoundInput(boundInput);
controllerItemList.set(inputItemPosition, controllerInputItem);
notifyItemChanged(inputItemPosition);
}
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
if (holder instanceof HeaderViewHolder) {
((HeaderViewHolder) holder).bind((ControllerHeaderItem) controllerItemList.get(position));
} else if (holder instanceof InputViewHolder inputViewHolder) {
ControllerInputItem inputListItem = (ControllerInputItem) controllerItemList.get(position);
holder.itemView.setOnClickListener(v -> {
if (onInputClickListener != null) {
onInputClickListener.onInputClicked(inputListItem.getNameResourceId(), inputListItem.getId(), holder.getAdapterPosition());
}
});
inputViewHolder.bind(inputListItem);
}
}
@Override
public int getItemCount() {
return controllerItemList.size();
}
public static class InputViewHolder extends RecyclerView.ViewHolder {
TextView inputName;
TextView boundInput;
void bind(ControllerInputItem inputListItem) {
inputName.setText(inputListItem.getNameResourceId());
boundInput.setText(inputListItem.getBoundInput());
}
public InputViewHolder(View itemView) {
super(itemView);
inputName = itemView.findViewById(R.id.controller_input_name);
boundInput = itemView.findViewById(R.id.controller_button_name);
}
}
public static class HeaderViewHolder extends RecyclerView.ViewHolder {
TextView header;
void bind(ControllerHeaderItem headerListItem) {
header.setText(headerListItem.getHeader());
}
public HeaderViewHolder(View itemView) {
super(itemView);
header = itemView.findViewById(R.id.textview_controller_header);
}
}
}
@@ -0,0 +1,71 @@
package info.cemu.Cemu;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import java.util.List;
public class EmulatedControllerTypeAdapter extends BaseAdapter {
private final List<Integer> controllerTypes = List.of(NativeLibrary.EMULATED_CONTROLLER_TYPE_DISABLED, NativeLibrary.EMULATED_CONTROLLER_TYPE_VPAD, NativeLibrary.EMULATED_CONTROLLER_TYPE_PRO, NativeLibrary.EMULATED_CONTROLLER_TYPE_CLASSIC, NativeLibrary.EMULATED_CONTROLLER_TYPE_WIIMOTE);
private int vpadCount = 0;
private int wpadCount = 0;
private int currentControllerType = NativeLibrary.EMULATED_CONTROLLER_TYPE_DISABLED;
public void setCurrentControllerType(int currentControllerType) {
this.currentControllerType = currentControllerType;
notifyDataSetChanged();
}
public void setControllerTypeCounts(int vpadCount, int wpadCount) {
this.vpadCount = vpadCount;
this.wpadCount = wpadCount;
notifyDataSetChanged();
}
@Override
public boolean isEnabled(int position) {
int type = controllerTypes.get(position);
if (type == NativeLibrary.EMULATED_CONTROLLER_TYPE_DISABLED)
return true;
if (type == NativeLibrary.EMULATED_CONTROLLER_TYPE_VPAD)
return currentControllerType == NativeLibrary.EMULATED_CONTROLLER_TYPE_VPAD || vpadCount < NativeLibrary.MAX_VPAD_CONTROLLERS;
boolean isWPAD = currentControllerType != NativeLibrary.EMULATED_CONTROLLER_TYPE_VPAD && currentControllerType != NativeLibrary.EMULATED_CONTROLLER_TYPE_DISABLED;
return isWPAD || wpadCount < NativeLibrary.MAX_WPAD_CONTROLLERS;
}
@Override
public int getCount() {
return controllerTypes.size();
}
@Override
public Integer getItem(int position) {
return controllerTypes.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View view, ViewGroup viewGroup) {
int type = controllerTypes.get(position);
if (view == null)
view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.layout_choice, viewGroup, false);
TextView textView = view.findViewById(R.id.textview_choice);
textView.setText(NativeLibrary.controllerTypeToResourceNameId(type));
float DISABLED_ALPHA = 0.6f;
if (!isEnabled(position))
view.setAlpha(DISABLED_ALPHA);
return view;
}
public int getPosition(int controllerType) {
return controllerTypes.indexOf(controllerType);
}
}
@@ -9,6 +9,10 @@ import androidx.core.view.WindowInsetsControllerCompat;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.MotionEvent;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import info.cemu.Cemu.databinding.ActivityEmulationBinding;
import info.cemu.Cemu.databinding.ActivityMainBinding;
@@ -16,9 +20,24 @@ import info.cemu.Cemu.databinding.ActivityMainBinding;
public class EmulationActivity extends AppCompatActivity {
public static final String GAME_TITLE_ID = "GameTitleId";
long gameTitleId;
ActivityEmulationBinding binding;
EmulationFragment emulationFragment;
private long gameTitleId;
private ActivityEmulationBinding binding;
private EmulationFragment emulationFragment;
private final InputManager inputManager = new InputManager();
@Override
public boolean onGenericMotionEvent(MotionEvent event) {
if (inputManager.onMotionEvent(event))
return true;
return super.onGenericMotionEvent(event);
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (inputManager.onKeyEvent(event))
return true;
return super.dispatchKeyEvent(event);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -32,7 +51,7 @@ public class EmulationActivity extends AppCompatActivity {
setContentView(binding.getRoot());
emulationFragment = (EmulationFragment) getSupportFragmentManager().findFragmentById(R.id.emulation_frame);
if (emulationFragment == null) {
emulationFragment = EmulationFragment.newInstance(gameTitleId);
emulationFragment = new EmulationFragment(gameTitleId);
getSupportFragmentManager()
.beginTransaction()
.add(R.id.emulation_frame, emulationFragment)
@@ -46,19 +65,18 @@ public class EmulationActivity extends AppCompatActivity {
}
private void showExitConfirmationDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Exit Confirmation");
builder.setMessage("Are you sure you want to exit?");
builder.setPositiveButton("Yes", (dialog, which) -> {
finishAffinity();
System.exit(0);
});
builder.setNegativeButton("No", (dialog, which) -> dialog.cancel());
AlertDialog dialog = builder.create();
dialog.show();
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
builder.setTitle(R.string.exit_confirmation_title)
.setMessage(R.string.exit_confirm_message)
.setPositiveButton(R.string.yes, (dialog, which) -> {
finishAffinity();
System.exit(0);
}).setNegativeButton(R.string.no, (dialog, which) -> dialog.cancel())
.create()
.show();
}
void setFullscreen() {
private void setFullscreen() {
WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
WindowInsetsControllerCompat controller = new WindowInsetsControllerCompat(getWindow(), getWindow().getDecorView());
controller.hide(WindowInsetsCompat.Type.systemBars());
@@ -1,6 +1,7 @@
package info.cemu.Cemu;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
@@ -13,35 +14,17 @@ import androidx.fragment.app.Fragment;
import info.cemu.Cemu.databinding.FragmentEmulationBinding;
public class EmulationFragment extends Fragment implements SurfaceHolder.Callback {
FragmentEmulationBinding binding;
public EmulationFragment() {
}
static EmulationFragment newInstance(long gameTitleId) {
EmulationFragment emulationFragment = new EmulationFragment();
Bundle arguments = new Bundle();
arguments.putLong(EmulationActivity.GAME_TITLE_ID, gameTitleId);
emulationFragment.setArguments(arguments);
return emulationFragment;
}
long gameTitleId;
boolean isGameRunning;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle arguments = getArguments();
if (arguments != null) {
gameTitleId = arguments.getLong(EmulationActivity.GAME_TITLE_ID);
}
private final long gameTitleId;
private boolean isGameRunning;
private SurfaceHolder mainCanvasSurfaceHolder;
public EmulationFragment(long gameTitleId) {
this.gameTitleId = gameTitleId;
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
binding = FragmentEmulationBinding.inflate(inflater, container, false);
FragmentEmulationBinding binding = FragmentEmulationBinding.inflate(inflater, container, false);
SurfaceView mainCanvas = binding.mainCanvas;
mainCanvas.getHolder().addCallback(this);
return binding.getRoot();
@@ -49,25 +32,29 @@ public class EmulationFragment extends Fragment implements SurfaceHolder.Callbac
@Override
public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) {
mainCanvasSurfaceHolder = surfaceHolder;
}
@Override
public void surfaceChanged(@NonNull SurfaceHolder surfaceHolder, int format, int width, int height) {
NativeLibrary.setSurface(surfaceHolder.getSurface(), true);
NativeLibrary.setSurfaceSize(width, height, true);
if (!isGameRunning) {
isGameRunning = true;
NativeLibrary.setSurface(surfaceHolder.getSurface(), true);
NativeLibrary.initializerRenderer();
NativeLibrary.initializeRendererSurface(true);
NativeLibrary.startGame(gameTitleId);
}else{
} else {
if (mainCanvasSurfaceHolder == surfaceHolder)
return;
NativeLibrary.setSurface(surfaceHolder.getSurface(), true);
NativeLibrary.recreateRenderSurface(true);
}
}
@Override
public void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder) {
mainCanvasSurfaceHolder = null;
NativeLibrary.clearSurface(true);
}
}
@@ -0,0 +1,122 @@
package info.cemu.Cemu;
import android.util.Log;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
public class InputManager {
private static class InvalidAxisException extends Exception {
public InvalidAxisException(int axis) {
super("Invalid axis " + axis);
}
}
private int getNativeAxisKey(int axis, boolean isPositive) throws InvalidAxisException {
if (isPositive) {
return switch (axis) {
case MotionEvent.AXIS_X -> NativeLibrary.AXIS_X_POS;
case MotionEvent.AXIS_Y -> NativeLibrary.AXIS_Y_POS;
case MotionEvent.AXIS_RX, MotionEvent.AXIS_Z -> NativeLibrary.ROTATION_X_POS;
case MotionEvent.AXIS_RY, MotionEvent.AXIS_RZ -> NativeLibrary.ROTATION_Y_POS;
case MotionEvent.AXIS_LTRIGGER -> NativeLibrary.TRIGGER_X_POS;
case MotionEvent.AXIS_RTRIGGER -> NativeLibrary.TRIGGER_Y_POS;
case MotionEvent.AXIS_HAT_X -> NativeLibrary.DPAD_RIGHT;
case MotionEvent.AXIS_HAT_Y -> NativeLibrary.DPAD_DOWN;
default -> throw new InvalidAxisException(axis);
};
} else {
return switch (axis) {
case MotionEvent.AXIS_X -> NativeLibrary.AXIS_X_NEG;
case MotionEvent.AXIS_Y -> NativeLibrary.AXIS_Y_NEG;
case MotionEvent.AXIS_RX, MotionEvent.AXIS_Z -> NativeLibrary.ROTATION_X_NEG;
case MotionEvent.AXIS_RY, MotionEvent.AXIS_RZ -> NativeLibrary.ROTATION_Y_NEG;
case MotionEvent.AXIS_HAT_X -> NativeLibrary.DPAD_LEFT;
case MotionEvent.AXIS_HAT_Y -> NativeLibrary.DPAD_UP;
default -> throw new InvalidAxisException(axis);
};
}
}
private boolean isMotionEventFromJoystick(MotionEvent event) {
return (event.getSource() & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK && event.getAction() == MotionEvent.ACTION_MOVE;
}
public boolean mapMotionEventToMappingId(int controllerIndex, int mappingId, MotionEvent event) {
if (isMotionEventFromJoystick(event)) {
InputDevice device = event.getDevice();
float maxAbsAxisValue = 0.0f;
int maxAxis = -1;
int actionPointerIndex = event.getActionIndex();
for (InputDevice.MotionRange motionRange : device.getMotionRanges()) {
float axisValue = event.getAxisValue(motionRange.getAxis(), actionPointerIndex);
int axis;
try {
axis = getNativeAxisKey(motionRange.getAxis(), axisValue > 0);
} catch (InvalidAxisException e) {
continue;
}
if (Math.abs(axisValue) > maxAbsAxisValue) {
maxAxis = axis;
maxAbsAxisValue = Math.abs(axisValue);
}
}
float MIN_ABS_AXIS_VALUE = 0.33f;
if (maxAbsAxisValue > MIN_ABS_AXIS_VALUE) {
NativeLibrary.setControllerMapping(device.getDescriptor(), device.getName(), controllerIndex, mappingId, maxAxis);
return true;
}
}
return false;
}
private boolean isSpecialKey(KeyEvent event) {
int keyCode = event.getKeyCode();
return keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ||
keyCode == KeyEvent.KEYCODE_VOLUME_UP ||
keyCode == KeyEvent.KEYCODE_CAMERA ||
keyCode == KeyEvent.KEYCODE_ZOOM_IN ||
keyCode == KeyEvent.KEYCODE_ZOOM_OUT;
}
private boolean isController(InputDevice inputDevice) {
int sources = inputDevice.getSources();
return (sources & InputDevice.SOURCE_CLASS_JOYSTICK) != 0 ||
((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) ||
((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD);
}
public boolean onKeyEvent(KeyEvent event) {
if (isSpecialKey(event))
return false;
if (event.getDeviceId() < 0)
return false;
InputDevice device = event.getDevice();
if (!isController(device))
return false;
NativeLibrary.onNativeKey(device.getDescriptor(), device.getName(), event.getKeyCode(), event.getAction() == KeyEvent.ACTION_DOWN);
return true;
}
public boolean onMotionEvent(MotionEvent event) {
if (!isMotionEventFromJoystick(event))
return false;
InputDevice device = event.getDevice();
int actionPointerIndex = event.getActionIndex();
for (InputDevice.MotionRange motionRange : device.getMotionRanges()) {
float axisValue = event.getAxisValue(motionRange.getAxis(), actionPointerIndex);
int axis = motionRange.getAxis();
NativeLibrary.onNativeAxis(device.getDescriptor(), device.getName(), axis, axisValue);
Log.i(getClass().getName(), "Axis: " + axis + " value: " + axisValue);
}
return true;
}
public boolean mapKeyEventToMappingId(int controllerIndex, int mappingId, KeyEvent event) {
Log.i(getClass().getName(), event.toString());
InputDevice device = event.getDevice();
NativeLibrary.setControllerMapping(device.getDescriptor(), device.getName(), controllerIndex, mappingId, event.getKeyCode());
return true;
}
}
@@ -0,0 +1,37 @@
package info.cemu.Cemu;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.navigation.fragment.NavHostFragment;
import info.cemu.Cemu.databinding.FragmentInputSettingsBinding;
public class InputSettingsFragment extends Fragment {
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
FragmentInputSettingsBinding binding = FragmentInputSettingsBinding.inflate(inflater, container, false);
ButtonListAdapter buttonListAdapter = new ButtonListAdapter();
for (int index = 0; index < NativeLibrary.MAX_CONTROLLERS; index++) {
int controllerIndex = index;
String controllerType = getString(NativeLibrary.controllerTypeToResourceNameId(NativeLibrary.getControllerType(controllerIndex)));
buttonListAdapter.addButtonInfo(getString(R.string.controller_numbered, controllerIndex + 1), getString(R.string.emulated_controller_with_type, controllerType),
() -> {
Bundle bundle = new Bundle();
bundle.putInt(ControllerInputsFragment.CONTROLLER_INDEX, controllerIndex);
NavHostFragment.findNavController(InputSettingsFragment.this).navigate(R.id.action_inputSettingsFragment_to_controllerInputsFragment, bundle);
});
}
binding.inputSettingsRecyclerView.setAdapter(buttonListAdapter);
return binding.getRoot();
}
}
@@ -0,0 +1,33 @@
package info.cemu.Cemu;
import android.app.AlertDialog;
import android.content.Context;
import android.view.MotionEvent;
import android.view.View;
import androidx.annotation.NonNull;
public class MotionDialog extends AlertDialog {
public static interface OnMotionListener {
boolean onMotion(MotionEvent ev);
}
private OnMotionListener onMotionListener;
protected MotionDialog(Context context) {
super(context);
}
public void setOnMotionListener(OnMotionListener onMotionListener) {
this.onMotionListener = onMotionListener;
}
@Override
public boolean onGenericMotionEvent(@NonNull MotionEvent event) {
if (onMotionListener != null && onMotionListener.onMotion(event)) {
return true;
}
return super.onGenericMotionEvent(event);
}
}
@@ -1,8 +1,10 @@
package info.cemu.Cemu;
import android.view.KeyEvent;
import android.view.Surface;
import java.util.ArrayList;
import java.util.Map;
public class NativeLibrary {
static {
@@ -61,4 +63,173 @@ public class NativeLibrary {
public static native void initializeEmulation();
public static native void addGamePath(String uri);
public static native void onNativeKey(String deviceDescriptor, String deviceName, int key, boolean isPressed);
public static native void onNativeAxis(String deviceDescriptor, String deviceName, int axis, float value);
static final int VPAD_BUTTON_NONE = 0;
static final int VPAD_BUTTON_A = 1;
static final int VPAD_BUTTON_B = 2;
static final int VPAD_BUTTON_X = 3;
static final int VPAD_BUTTON_Y = 4;
static final int VPAD_BUTTON_L = 5;
static final int VPAD_BUTTON_R = 6;
static final int VPAD_BUTTON_ZL = 7;
static final int VPAD_BUTTON_ZR = 8;
static final int VPAD_BUTTON_PLUS = 9;
static final int VPAD_BUTTON_MINUS = 10;
static final int VPAD_BUTTON_UP = 11;
static final int VPAD_BUTTON_DOWN = 12;
static final int VPAD_BUTTON_LEFT = 13;
static final int VPAD_BUTTON_RIGHT = 14;
static final int VPAD_BUTTON_STICKL = 15;
static final int VPAD_BUTTON_STICKR = 16;
static final int VPAD_BUTTON_STICKL_UP = 17;
static final int VPAD_BUTTON_STICKL_DOWN = 18;
static final int VPAD_BUTTON_STICKL_LEFT = 19;
static final int VPAD_BUTTON_STICKL_RIGHT = 20;
static final int VPAD_BUTTON_STICKR_UP = 21;
static final int VPAD_BUTTON_STICKR_DOWN = 22;
static final int VPAD_BUTTON_STICKR_LEFT = 23;
static final int VPAD_BUTTON_STICKR_RIGHT = 24;
static final int VPAD_BUTTON_MIC = 25;
static final int VPAD_BUTTON_SCREEN = 26;
static final int VPAD_BUTTON_HOME = 27;
static final int VPAD_BUTTON_MAX = 28;
static final int PRO_BUTTON_NONE = 0;
static final int PRO_BUTTON_A = 1;
static final int PRO_BUTTON_B = 2;
static final int PRO_BUTTON_X = 3;
static final int PRO_BUTTON_Y = 4;
static final int PRO_BUTTON_L = 5;
static final int PRO_BUTTON_R = 6;
static final int PRO_BUTTON_ZL = 7;
static final int PRO_BUTTON_ZR = 8;
static final int PRO_BUTTON_PLUS = 9;
static final int PRO_BUTTON_MINUS = 10;
static final int PRO_BUTTON_HOME = 11;
static final int PRO_BUTTON_UP = 12;
static final int PRO_BUTTON_DOWN = 13;
static final int PRO_BUTTON_LEFT = 14;
static final int PRO_BUTTON_RIGHT = 15;
static final int PRO_BUTTON_STICKL = 16;
static final int PRO_BUTTON_STICKR = 17;
static final int PRO_BUTTON_STICKL_UP = 18;
static final int PRO_BUTTON_STICKL_DOWN = 19;
static final int PRO_BUTTON_STICKL_LEFT = 20;
static final int PRO_BUTTON_STICKL_RIGHT = 21;
static final int PRO_BUTTON_STICKR_UP = 22;
static final int PRO_BUTTON_STICKR_DOWN = 23;
static final int PRO_BUTTON_STICKR_LEFT = 24;
static final int PRO_BUTTON_STICKR_RIGHT = 25;
static final int PRO_BUTTON_MAX = 26;
static final int CLASSIC_BUTTON_NONE = 0;
static final int CLASSIC_BUTTON_A = 1;
static final int CLASSIC_BUTTON_B = 2;
static final int CLASSIC_BUTTON_X = 3;
static final int CLASSIC_BUTTON_Y = 4;
static final int CLASSIC_BUTTON_L = 5;
static final int CLASSIC_BUTTON_R = 6;
static final int CLASSIC_BUTTON_ZL = 7;
static final int CLASSIC_BUTTON_ZR = 8;
static final int CLASSIC_BUTTON_PLUS = 9;
static final int CLASSIC_BUTTON_MINUS = 10;
static final int CLASSIC_BUTTON_HOME = 11;
static final int CLASSIC_BUTTON_UP = 12;
static final int CLASSIC_BUTTON_DOWN = 13;
static final int CLASSIC_BUTTON_LEFT = 14;
static final int CLASSIC_BUTTON_RIGHT = 15;
static final int CLASSIC_BUTTON_STICKL_UP = 16;
static final int CLASSIC_BUTTON_STICKL_DOWN = 17;
static final int CLASSIC_BUTTON_STICKL_LEFT = 18;
static final int CLASSIC_BUTTON_STICKL_RIGHT = 19;
static final int CLASSIC_BUTTON_STICKR_UP = 20;
static final int CLASSIC_BUTTON_STICKR_DOWN = 21;
static final int CLASSIC_BUTTON_STICKR_LEFT = 22;
static final int CLASSIC_BUTTON_STICKR_RIGHT = 23;
static final int CLASSIC_BUTTON_MAX = 24;
static final int WIIMOTE_BUTTON_NONE = 0;
static final int WIIMOTE_BUTTON_A = 1;
static final int WIIMOTE_BUTTON_B = 2;
static final int WIIMOTE_BUTTON_1 = 3;
static final int WIIMOTE_BUTTON_2 = 4;
static final int WIIMOTE_BUTTON_NUNCHUCK_Z = 5;
static final int WIIMOTE_BUTTON_NUNCHUCK_C = 6;
static final int WIIMOTE_BUTTON_PLUS = 7;
static final int WIIMOTE_BUTTON_MINUS = 8;
static final int WIIMOTE_BUTTON_UP = 9;
static final int WIIMOTE_BUTTON_DOWN = 10;
static final int WIIMOTE_BUTTON_LEFT = 11;
static final int WIIMOTE_BUTTON_RIGHT = 12;
static final int WIIMOTE_BUTTON_NUNCHUCK_UP = 13;
static final int WIIMOTE_BUTTON_NUNCHUCK_DOWN = 14;
static final int WIIMOTE_BUTTON_NUNCHUCK_LEFT = 15;
static final int WIIMOTE_BUTTON_NUNCHUCK_RIGHT = 16;
static final int WIIMOTE_BUTTON_HOME = 17;
static final int WIIMOTE_BUTTON_MAX = 18;
static final int EMULATED_CONTROLLER_TYPE_VPAD = 0;
static final int EMULATED_CONTROLLER_TYPE_PRO = 1;
static final int EMULATED_CONTROLLER_TYPE_CLASSIC = 2;
static final int EMULATED_CONTROLLER_TYPE_WIIMOTE = 3;
static final int EMULATED_CONTROLLER_TYPE_DISABLED = -1;
static final int DPAD_UP = 34;
static final int DPAD_DOWN = 35;
static final int DPAD_LEFT = 36;
static final int DPAD_RIGHT = 37;
static final int AXIS_X_POS = 38;
static final int AXIS_Y_POS = 39;
static final int ROTATION_X_POS = 40;
static final int ROTATION_Y_POS = 41;
static final int TRIGGER_X_POS = 42;
static final int TRIGGER_Y_POS = 43;
static final int AXIS_X_NEG = 44;
static final int AXIS_Y_NEG = 45;
static final int ROTATION_X_NEG = 46;
static final int ROTATION_Y_NEG = 47;
static final int MAX_CONTROLLERS = 8;
static final int MAX_VPAD_CONTROLLERS = 2;
static final int MAX_WPAD_CONTROLLERS = 7;
public static int controllerTypeToResourceNameId(int type) {
if (type == NativeLibrary.EMULATED_CONTROLLER_TYPE_DISABLED)
return R.string.disabled;
else if (type == NativeLibrary.EMULATED_CONTROLLER_TYPE_VPAD)
return R.string.vpad_controller;
else if (type == NativeLibrary.EMULATED_CONTROLLER_TYPE_PRO)
return R.string.pro_controller;
else if (type == NativeLibrary.EMULATED_CONTROLLER_TYPE_WIIMOTE)
return R.string.wiimote_controller;
else if (type == NativeLibrary.EMULATED_CONTROLLER_TYPE_CLASSIC)
return R.string.classic_controller;
throw new RuntimeException("Invalid controller type: " + type);
}
public static native void setControllerType(int index, int emulatedControllerType);
public static native boolean isControllerDisabled(int index);
public static native int getControllerType(int index);
public static native int getWPADControllersCount();
public static native int getVPADControllersCount();
public static native void setControllerMapping(String deviceDescriptor, String deviceName, int index, int mappingId, int buttonId);
public static native void clearControllerMapping(int index, int mappingId);
public static native String getControllerMapping(int index, int mappingId);
public static native Map<Integer, String> getControllerMappings(int index);
public static native void onKeyEvent(String deviceDescriptor, String deviceName, int keyCode, boolean isPressed);
public static native void onAxisEvent(String deviceDescriptor, String deviceName, int axisCode, float value);
}
@@ -1,35 +1,35 @@
package info.cemu.Cemu;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import java.util.Objects;
import androidx.appcompat.app.AppCompatActivity;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import androidx.navigation.ui.AppBarConfiguration;
import androidx.navigation.ui.NavigationUI;
import info.cemu.Cemu.databinding.ActivitySettingsBinding;
public class SettingsActivity extends AppCompatActivity {
private AppBarConfiguration appBarConfiguration;
private ActivitySettingsBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivitySettingsBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
setSupportActionBar(binding.toolbarSettings);
Objects.requireNonNull(getSupportActionBar()).setDisplayHomeAsUpEnabled(true);
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_settings);
appBarConfiguration = new AppBarConfiguration.Builder(navController.getGraph()).build();
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
}
@Override
public boolean onSupportNavigateUp() {
if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
getSupportFragmentManager().popBackStack();
} else {
finish();
}
return true;
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_settings);
return NavigationUI.navigateUp(navController, appBarConfiguration)
|| super.onSupportNavigateUp();
}
}
@@ -62,7 +62,7 @@ public class SettingsFragment extends Fragment {
View headerLayout = getLayoutInflater().inflate(R.layout.settings_header, null);
binding.settingsLayout.addView(headerLayout);
SettingsHeaderBinding headerBinding = SettingsHeaderBinding.bind(headerLayout);
headerBinding.textviewHeaderName.setText(title);
headerBinding.settingsTextviewHeaderName.setText(title);
}
protected void addSpinner(String title, List<String> items, int initialPosition, OnItemSelectedPositionListener itemSelectedPositionListener) {
@@ -1,32 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical"
tools:context=".SettingsActivity">
<LinearLayout
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
android:layout_height="wrap_content"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout"
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar_settings"
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="?attr/actionBarSize" />
</com.google.android.material.appbar.AppBarLayout>
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar_settings"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.fragment.app.FragmentContainerView
<fragment
android:id="@+id/nav_host_fragment_content_settings"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_width="0dp"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/nav_settings_graph" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
tools:context=".ControllerInputsFragment">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginVertical="8dp"
android:text="@string/emulated_controller_label" />
<Spinner
android:id="@+id/emulated_controller_spinner"
android:layout_width="match_parent"
android:layout_height="48dp"
app:layout_constraintWidth_max="400dp" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/controller_inputs_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="16dp"
android:scrollbars="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
</LinearLayout>

Some files were not shown because too many files have changed in this diff Show More