diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/ControllerInterface.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/ControllerInterface.java index 0d6a078b4e..b2535465e8 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/ControllerInterface.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/ControllerInterface.java @@ -95,6 +95,8 @@ public final class ControllerInterface */ public static native void refreshDevices(); + public static native String[] getAllDeviceStrings(); + @Keep private static void registerInputDeviceListener() { diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/MappingCommon.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/MappingCommon.java index cb136a908d..b0dc489fec 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/MappingCommon.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/MappingCommon.java @@ -2,6 +2,10 @@ package org.dolphinemu.dolphinemu.features.input.model; +import androidx.annotation.NonNull; + +import org.dolphinemu.dolphinemu.features.input.model.controlleremu.EmulatedController; + public final class MappingCommon { private MappingCommon() @@ -15,10 +19,13 @@ public final class MappingCommon * When this is being called, a separate thread must be calling ControllerInterface's * dispatchKeyEvent and dispatchGenericMotionEvent, otherwise no inputs will be registered. * + * @param controller The device to detect inputs from. + * @param allDevices Whether to also detect inputs from devices other than the specified one. * @return The input(s) pressed by the user in the form of an InputCommon expression, * or an empty string if there were no inputs. */ - public static native String detectInput(); + public static native String detectInput(@NonNull EmulatedController controller, + boolean allDevices); public static native void save(); } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/EmulatedController.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/EmulatedController.java index b0fbd0fd92..5a5793f530 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/EmulatedController.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/EmulatedController.java @@ -21,6 +21,10 @@ public class EmulatedController mPointer = pointer; } + public native String getDefaultDevice(); + + public native void setDefaultDevice(String device); + public native int getGroupCount(); public native ControlGroup getGroup(int index); diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/view/InputDeviceSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/view/InputDeviceSetting.java new file mode 100644 index 0000000000..2462e867d3 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/view/InputDeviceSetting.java @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.model.view; + +import android.content.Context; + +import org.dolphinemu.dolphinemu.features.input.model.ControllerInterface; +import org.dolphinemu.dolphinemu.features.input.model.controlleremu.EmulatedController; +import org.dolphinemu.dolphinemu.features.settings.model.Settings; +import org.dolphinemu.dolphinemu.features.settings.model.view.StringSingleChoiceSetting; + +public class InputDeviceSetting extends StringSingleChoiceSetting +{ + private final EmulatedController mController; + + public InputDeviceSetting(Context context, int titleId, int descriptionId, + EmulatedController controller) + { + super(context, null, titleId, descriptionId, null, null, null); + + mController = controller; + + refreshChoicesAndValues(); + } + + @Override + public String getSelectedChoice(Settings settings) + { + return mController.getDefaultDevice(); + } + + @Override + public String getSelectedValue(Settings settings) + { + return mController.getDefaultDevice(); + } + + @Override + public void setSelectedValue(Settings settings, String newValue) + { + mController.setDefaultDevice(newValue); + } + + @Override + public void refreshChoicesAndValues() + { + String[] devices = ControllerInterface.getAllDeviceStrings(); + + mChoices = devices; + mValues = devices; + } + + @Override + public boolean canClear() + { + return true; + } + + @Override + public void clear(Settings settings) + { + setSelectedValue(settings, ""); + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/view/InputMappingControlSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/view/InputMappingControlSetting.java index 8888e2430b..a858b35099 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/view/InputMappingControlSetting.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/view/InputMappingControlSetting.java @@ -47,4 +47,9 @@ public final class InputMappingControlSetting extends SettingsItem { return null; } + + public EmulatedController getController() + { + return mController; + } } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/MotionAlertDialog.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/MotionAlertDialog.java index f36d9bc478..0a4afc7acf 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/MotionAlertDialog.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/MotionAlertDialog.java @@ -46,7 +46,7 @@ public final class MotionAlertDialog extends AlertDialog mRunning = true; new Thread(() -> { - String result = MappingCommon.detectInput(); + String result = MappingCommon.detectInput(mSetting.getController(), false); mActivity.runOnUiThread(() -> { if (mRunning) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SettingsItem.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SettingsItem.java index 9c7b89c12f..6f06677be3 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SettingsItem.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SettingsItem.java @@ -95,6 +95,11 @@ public abstract class SettingsItem return getSetting() != null; } + public boolean canClear() + { + return hasSetting(); + } + public void clear(Settings settings) { getSetting().delete(settings); diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/StringSingleChoiceSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/StringSingleChoiceSetting.java index df7ee6f4e9..70463f216b 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/StringSingleChoiceSetting.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/StringSingleChoiceSetting.java @@ -14,8 +14,8 @@ public class StringSingleChoiceSetting extends SettingsItem { private AbstractStringSetting mSetting; - private String[] mChoices; - private String[] mValues; + protected String[] mChoices; + protected String[] mValues; private MenuTag mMenuTag; public StringSingleChoiceSetting(Context context, AbstractStringSetting setting, int titleId, @@ -60,6 +60,19 @@ public class StringSingleChoiceSetting extends SettingsItem return mValues; } + public String getChoiceAt(int index) + { + if (mChoices == null) + return null; + + if (index >= 0 && index < mChoices.length) + { + return mChoices[index]; + } + + return ""; + } + public String getValueAt(int index) { if (mValues == null) @@ -73,6 +86,11 @@ public class StringSingleChoiceSetting extends SettingsItem return ""; } + public String getSelectedChoice(Settings settings) + { + return getChoiceAt(getSelectedValueIndex(settings)); + } + public String getSelectedValue(Settings settings) { return mSetting.getString(settings); @@ -102,6 +120,10 @@ public class StringSingleChoiceSetting extends SettingsItem mSetting.setString(settings, selection); } + public void refreshChoicesAndValues() + { + } + @Override public int getType() { diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.java index 8fe3fe4602..cd2d047ecb 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.java @@ -253,6 +253,8 @@ public final class SettingsAdapter extends RecyclerView.Adapter sl, int wiimoteNumber) { + EmulatedController wiimote = EmulatedController.getWiimote(wiimoteNumber); + + addControllerMetaSettings(sl, wiimote); + sl.add(new HeaderSetting(mContext, R.string.wiimote, 0)); sl.add(new SubmenuSetting(mContext, R.string.wiimote_general, MenuTag.getWiimoteGeneralMenuTag(wiimoteNumber))); @@ -1130,44 +1138,58 @@ public final class SettingsFragmentPresenter // TYPE_OTHER is included here instead of in addWiimoteGeneralSubSettings so that touchscreen // users won't have to dig into a submenu to find the Sideways Wii Remote setting - addControllerSettings(sl, EmulatedController.getWiimote(wiimoteNumber), + addControllerMappingSettings(sl, wiimote, new ArraySet<>(Arrays.asList(ControlGroup.TYPE_ATTACHMENTS, ControlGroup.TYPE_OTHER))); } private void addExtensionTypeSettings(ArrayList sl, int wiimoteNumber, int extensionType) { - addControllerSettings(sl, + addControllerMappingSettings(sl, EmulatedController.getWiimoteAttachment(wiimoteNumber, extensionType), null); } private void addWiimoteGeneralSubSettings(ArrayList sl, int wiimoteNumber) { - addControllerSettings(sl, EmulatedController.getWiimote(wiimoteNumber), + addControllerMappingSettings(sl, EmulatedController.getWiimote(wiimoteNumber), Collections.singleton(ControlGroup.TYPE_BUTTONS)); } private void addWiimoteMotionSimulationSubSettings(ArrayList sl, int wiimoteNumber) { - addControllerSettings(sl, EmulatedController.getWiimote(wiimoteNumber), + addControllerMappingSettings(sl, EmulatedController.getWiimote(wiimoteNumber), new ArraySet<>(Arrays.asList(ControlGroup.TYPE_FORCE, ControlGroup.TYPE_TILT, ControlGroup.TYPE_CURSOR, ControlGroup.TYPE_SHAKE))); } private void addWiimoteMotionInputSubSettings(ArrayList sl, int wiimoteNumber) { - addControllerSettings(sl, EmulatedController.getWiimote(wiimoteNumber), + addControllerMappingSettings(sl, EmulatedController.getWiimote(wiimoteNumber), new ArraySet<>(Arrays.asList(ControlGroup.TYPE_IMU_ACCELEROMETER, ControlGroup.TYPE_IMU_GYROSCOPE, ControlGroup.TYPE_IMU_CURSOR))); } /** - * @param sl The list to place controller settings into. + * Adds settings and actions that apply to a controller as a whole. + * For instance, the device setting and the Clear action. + * + * @param sl The list to place controller settings into. * @param controller The controller to add settings for. + */ + private void addControllerMetaSettings(ArrayList sl, EmulatedController controller) + { + sl.add(new InputDeviceSetting(mContext, R.string.input_device, 0, controller)); + } + + /** + * Adds mapping settings and other control-specific settings. + * + * @param sl The list to place controller settings into. + * @param controller The controller to add settings for. * @param groupTypeFilter If this is non-null, only groups whose types match this are considered. */ - private void addControllerSettings(ArrayList sl, EmulatedController controller, - Set groupTypeFilter) + private void addControllerMappingSettings(ArrayList sl, + EmulatedController controller, Set groupTypeFilter) { int groupCount = controller.getGroupCount(); for (int i = 0; i < groupCount; i++) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SettingViewHolder.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SettingViewHolder.java index d34e4a1536..18fd466461 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SettingViewHolder.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SettingViewHolder.java @@ -83,7 +83,7 @@ public abstract class SettingViewHolder extends RecyclerView.ViewHolder { SettingsItem item = getItem(); - if (item == null || !item.hasSetting()) + if (item == null || !item.canClear()) return false; if (!item.isEditable()) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SingleChoiceViewHolder.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SingleChoiceViewHolder.java index db56ebea40..beb15867c5 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SingleChoiceViewHolder.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SingleChoiceViewHolder.java @@ -64,10 +64,8 @@ public final class SingleChoiceViewHolder extends SettingViewHolder else if (item instanceof StringSingleChoiceSetting) { StringSingleChoiceSetting setting = (StringSingleChoiceSetting) item; - String[] choices = setting.getChoices(); - int valueIndex = setting.getSelectedValueIndex(settings); - if (valueIndex != -1) - mBinding.textSettingDescription.setText(choices[valueIndex]); + String choice = setting.getSelectedChoice(settings); + mBinding.textSettingDescription.setText(choice); } else if (item instanceof SingleChoiceSettingDynamicDescriptions) { diff --git a/Source/Android/app/src/main/res/values/strings.xml b/Source/Android/app/src/main/res/values/strings.xml index 7099a1e33f..3cde229c28 100644 --- a/Source/Android/app/src/main/res/values/strings.xml +++ b/Source/Android/app/src/main/res/values/strings.xml @@ -27,6 +27,8 @@ Motion Input Extension + Device + Input Binding Press or move an input to bind it to %1$s. diff --git a/Source/Android/jni/CMakeLists.txt b/Source/Android/jni/CMakeLists.txt index 96dbb9d014..0b0d50a9ec 100644 --- a/Source/Android/jni/CMakeLists.txt +++ b/Source/Android/jni/CMakeLists.txt @@ -17,6 +17,7 @@ add_library(main SHARED Input/ControlReference.cpp Input/ControlReference.h Input/EmulatedController.cpp + Input/EmulatedController.h Input/InputOverrider.cpp Input/MappingCommon.cpp Input/NumericSetting.cpp diff --git a/Source/Android/jni/Input/EmulatedController.cpp b/Source/Android/jni/Input/EmulatedController.cpp index af6ba3b48a..ba9e454dae 100644 --- a/Source/Android/jni/Input/EmulatedController.cpp +++ b/Source/Android/jni/Input/EmulatedController.cpp @@ -16,7 +16,7 @@ #include "jni/Input/ControlGroup.h" #include "jni/Input/ControlReference.h" -static ControllerEmu::EmulatedController* GetPointer(JNIEnv* env, jobject obj) +ControllerEmu::EmulatedController* EmulatedControllerFromJava(JNIEnv* env, jobject obj) { return reinterpret_cast( env->GetLongField(obj, IDCache::GetEmulatedControllerPointer())); @@ -34,25 +34,40 @@ static jobject EmulatedControllerToJava(JNIEnv* env, ControllerEmu::EmulatedCont extern "C" { +JNIEXPORT jstring JNICALL +Java_org_dolphinemu_dolphinemu_features_input_model_controlleremu_EmulatedController_getDefaultDevice( + JNIEnv* env, jobject obj) +{ + return ToJString(env, EmulatedControllerFromJava(env, obj)->GetDefaultDevice().ToString()); +} + +JNIEXPORT void JNICALL +Java_org_dolphinemu_dolphinemu_features_input_model_controlleremu_EmulatedController_setDefaultDevice( + JNIEnv* env, jobject obj, jstring j_device) +{ + return EmulatedControllerFromJava(env, obj)->SetDefaultDevice(GetJString(env, j_device)); +} + JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_features_input_model_controlleremu_EmulatedController_getGroupCount( JNIEnv* env, jobject obj) { - return static_cast(GetPointer(env, obj)->groups.size()); + return static_cast(EmulatedControllerFromJava(env, obj)->groups.size()); } JNIEXPORT jobject JNICALL Java_org_dolphinemu_dolphinemu_features_input_model_controlleremu_EmulatedController_getGroup( JNIEnv* env, jobject obj, jint controller_index) { - return ControlGroupToJava(env, GetPointer(env, obj)->groups[controller_index].get()); + return ControlGroupToJava(env, + EmulatedControllerFromJava(env, obj)->groups[controller_index].get()); } JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_features_input_model_controlleremu_EmulatedController_updateSingleControlReference( JNIEnv* env, jobject obj, jobject control_reference) { - return GetPointer(env, obj)->UpdateSingleControlReference( + return EmulatedControllerFromJava(env, obj)->UpdateSingleControlReference( g_controller_interface, ControlReferenceFromJava(env, control_reference)); } diff --git a/Source/Android/jni/Input/EmulatedController.h b/Source/Android/jni/Input/EmulatedController.h new file mode 100644 index 0000000000..cfe8674ac6 --- /dev/null +++ b/Source/Android/jni/Input/EmulatedController.h @@ -0,0 +1,13 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +namespace ControllerEmu +{ +class ControlReference; +} + +ControllerEmu::EmulatedController* EmulatedControllerFromJava(JNIEnv* env, jobject obj); diff --git a/Source/Android/jni/Input/MappingCommon.cpp b/Source/Android/jni/Input/MappingCommon.cpp index c02f1c2ae5..4e5d88ccf6 100644 --- a/Source/Android/jni/Input/MappingCommon.cpp +++ b/Source/Android/jni/Input/MappingCommon.cpp @@ -17,6 +17,7 @@ #include "InputCommon/ControllerInterface/ControllerInterface.h" #include "InputCommon/ControllerInterface/MappingCommon.h" #include "jni/AndroidCommon/AndroidCommon.h" +#include "jni/Input/EmulatedController.h" namespace { @@ -28,10 +29,19 @@ constexpr auto INPUT_DETECT_MAXIMUM_TIME = std::chrono::seconds(5); extern "C" { JNIEXPORT jstring JNICALL -Java_org_dolphinemu_dolphinemu_features_input_model_MappingCommon_detectInput(JNIEnv* env, jclass) +Java_org_dolphinemu_dolphinemu_features_input_model_MappingCommon_detectInput( + JNIEnv* env, jclass, jobject j_emulated_controller, jboolean all_devices) { - const std::vector device_strings = g_controller_interface.GetAllDeviceStrings(); - const ciface::Core::DeviceQualifier default_device{}; + ControllerEmu::EmulatedController* emulated_controller = + EmulatedControllerFromJava(env, j_emulated_controller); + + const ciface::Core::DeviceQualifier default_device = emulated_controller->GetDefaultDevice(); + + std::vector device_strings; + if (all_devices) + device_strings = g_controller_interface.GetAllDeviceStrings(); + else + device_strings = {default_device.ToString()}; auto detections = g_controller_interface.DetectInput(device_strings, INPUT_DETECT_INITIAL_TIME, diff --git a/Source/Core/InputCommon/ControllerInterface/Android/Android.cpp b/Source/Core/InputCommon/ControllerInterface/Android/Android.cpp index 811343bf73..d2ccec4bb3 100644 --- a/Source/Core/InputCommon/ControllerInterface/Android/Android.cpp +++ b/Source/Core/InputCommon/ControllerInterface/Android/Android.cpp @@ -1122,4 +1122,11 @@ Java_org_dolphinemu_dolphinemu_features_input_model_ControllerInterface_refreshD { g_controller_interface.RefreshDevices(); } + +JNIEXPORT jobjectArray JNICALL +Java_org_dolphinemu_dolphinemu_features_input_model_ControllerInterface_getAllDeviceStrings( + JNIEnv* env, jclass) +{ + return VectorToJStringArray(env, g_controller_interface.GetAllDeviceStrings()); +} }