Android: Add input device selection

This commit is contained in:
JosJuice 2022-08-14 11:32:22 +02:00
parent 2113bf5e3a
commit 2b1dd52750
18 changed files with 204 additions and 25 deletions

View file

@ -95,6 +95,8 @@ public final class ControllerInterface
*/ */
public static native void refreshDevices(); public static native void refreshDevices();
public static native String[] getAllDeviceStrings();
@Keep @Keep
private static void registerInputDeviceListener() private static void registerInputDeviceListener()
{ {

View file

@ -2,6 +2,10 @@
package org.dolphinemu.dolphinemu.features.input.model; package org.dolphinemu.dolphinemu.features.input.model;
import androidx.annotation.NonNull;
import org.dolphinemu.dolphinemu.features.input.model.controlleremu.EmulatedController;
public final class MappingCommon public final class MappingCommon
{ {
private MappingCommon() private MappingCommon()
@ -15,10 +19,13 @@ public final class MappingCommon
* When this is being called, a separate thread must be calling ControllerInterface's * When this is being called, a separate thread must be calling ControllerInterface's
* dispatchKeyEvent and dispatchGenericMotionEvent, otherwise no inputs will be registered. * 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, * @return The input(s) pressed by the user in the form of an InputCommon expression,
* or an empty string if there were no inputs. * 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(); public static native void save();
} }

View file

@ -21,6 +21,10 @@ public class EmulatedController
mPointer = pointer; mPointer = pointer;
} }
public native String getDefaultDevice();
public native void setDefaultDevice(String device);
public native int getGroupCount(); public native int getGroupCount();
public native ControlGroup getGroup(int index); public native ControlGroup getGroup(int index);

View file

@ -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, "");
}
}

View file

@ -47,4 +47,9 @@ public final class InputMappingControlSetting extends SettingsItem
{ {
return null; return null;
} }
public EmulatedController getController()
{
return mController;
}
} }

View file

@ -46,7 +46,7 @@ public final class MotionAlertDialog extends AlertDialog
mRunning = true; mRunning = true;
new Thread(() -> new Thread(() ->
{ {
String result = MappingCommon.detectInput(); String result = MappingCommon.detectInput(mSetting.getController(), false);
mActivity.runOnUiThread(() -> mActivity.runOnUiThread(() ->
{ {
if (mRunning) if (mRunning)

View file

@ -95,6 +95,11 @@ public abstract class SettingsItem
return getSetting() != null; return getSetting() != null;
} }
public boolean canClear()
{
return hasSetting();
}
public void clear(Settings settings) public void clear(Settings settings)
{ {
getSetting().delete(settings); getSetting().delete(settings);

View file

@ -14,8 +14,8 @@ public class StringSingleChoiceSetting extends SettingsItem
{ {
private AbstractStringSetting mSetting; private AbstractStringSetting mSetting;
private String[] mChoices; protected String[] mChoices;
private String[] mValues; protected String[] mValues;
private MenuTag mMenuTag; private MenuTag mMenuTag;
public StringSingleChoiceSetting(Context context, AbstractStringSetting setting, int titleId, public StringSingleChoiceSetting(Context context, AbstractStringSetting setting, int titleId,
@ -60,6 +60,19 @@ public class StringSingleChoiceSetting extends SettingsItem
return mValues; 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) public String getValueAt(int index)
{ {
if (mValues == null) if (mValues == null)
@ -73,6 +86,11 @@ public class StringSingleChoiceSetting extends SettingsItem
return ""; return "";
} }
public String getSelectedChoice(Settings settings)
{
return getChoiceAt(getSelectedValueIndex(settings));
}
public String getSelectedValue(Settings settings) public String getSelectedValue(Settings settings)
{ {
return mSetting.getString(settings); return mSetting.getString(settings);
@ -102,6 +120,10 @@ public class StringSingleChoiceSetting extends SettingsItem
mSetting.setString(settings, selection); mSetting.setString(settings, selection);
} }
public void refreshChoicesAndValues()
{
}
@Override @Override
public int getType() public int getType()
{ {

View file

@ -253,6 +253,8 @@ public final class SettingsAdapter extends RecyclerView.Adapter<SettingViewHolde
mClickedItem = item; mClickedItem = item;
mClickedPosition = position; mClickedPosition = position;
item.refreshChoicesAndValues();
mDialog = new MaterialAlertDialogBuilder(mView.getActivity()) mDialog = new MaterialAlertDialogBuilder(mView.getActivity())
.setTitle(item.getName()) .setTitle(item.getName())
.setSingleChoiceItems(item.getChoices(), item.getSelectedValueIndex(getSettings()), .setSingleChoiceItems(item.getChoices(), item.getSelectedValueIndex(getSettings()),

View file

@ -22,6 +22,7 @@ import org.dolphinemu.dolphinemu.features.input.model.controlleremu.ControlGroup
import org.dolphinemu.dolphinemu.features.input.model.ControlGroupEnabledSetting; import org.dolphinemu.dolphinemu.features.input.model.ControlGroupEnabledSetting;
import org.dolphinemu.dolphinemu.features.input.model.controlleremu.EmulatedController; import org.dolphinemu.dolphinemu.features.input.model.controlleremu.EmulatedController;
import org.dolphinemu.dolphinemu.features.input.model.controlleremu.NumericSetting; import org.dolphinemu.dolphinemu.features.input.model.controlleremu.NumericSetting;
import org.dolphinemu.dolphinemu.features.input.model.view.InputDeviceSetting;
import org.dolphinemu.dolphinemu.features.input.model.view.InputMappingControlSetting; import org.dolphinemu.dolphinemu.features.input.model.view.InputMappingControlSetting;
import org.dolphinemu.dolphinemu.features.settings.model.AbstractBooleanSetting; import org.dolphinemu.dolphinemu.features.settings.model.AbstractBooleanSetting;
import org.dolphinemu.dolphinemu.features.settings.model.AbstractIntSetting; import org.dolphinemu.dolphinemu.features.settings.model.AbstractIntSetting;
@ -1107,7 +1108,10 @@ public final class SettingsFragmentPresenter
{ {
if (gcPadType == 6) // Emulated if (gcPadType == 6) // Emulated
{ {
addControllerSettings(sl, EmulatedController.getGcPad(gcPadNumber), null); EmulatedController gcPad = EmulatedController.getGcPad(gcPadNumber);
addControllerMetaSettings(sl, gcPad);
addControllerMappingSettings(sl, gcPad, null);
} }
else if (gcPadType == 12) // Adapter else if (gcPadType == 12) // Adapter
{ {
@ -1120,6 +1124,10 @@ public final class SettingsFragmentPresenter
private void addWiimoteSubSettings(ArrayList<SettingsItem> sl, int wiimoteNumber) private void addWiimoteSubSettings(ArrayList<SettingsItem> sl, int wiimoteNumber)
{ {
EmulatedController wiimote = EmulatedController.getWiimote(wiimoteNumber);
addControllerMetaSettings(sl, wiimote);
sl.add(new HeaderSetting(mContext, R.string.wiimote, 0)); sl.add(new HeaderSetting(mContext, R.string.wiimote, 0));
sl.add(new SubmenuSetting(mContext, R.string.wiimote_general, sl.add(new SubmenuSetting(mContext, R.string.wiimote_general,
MenuTag.getWiimoteGeneralMenuTag(wiimoteNumber))); MenuTag.getWiimoteGeneralMenuTag(wiimoteNumber)));
@ -1130,44 +1138,58 @@ public final class SettingsFragmentPresenter
// TYPE_OTHER is included here instead of in addWiimoteGeneralSubSettings so that touchscreen // 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 // 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))); new ArraySet<>(Arrays.asList(ControlGroup.TYPE_ATTACHMENTS, ControlGroup.TYPE_OTHER)));
} }
private void addExtensionTypeSettings(ArrayList<SettingsItem> sl, int wiimoteNumber, private void addExtensionTypeSettings(ArrayList<SettingsItem> sl, int wiimoteNumber,
int extensionType) int extensionType)
{ {
addControllerSettings(sl, addControllerMappingSettings(sl,
EmulatedController.getWiimoteAttachment(wiimoteNumber, extensionType), null); EmulatedController.getWiimoteAttachment(wiimoteNumber, extensionType), null);
} }
private void addWiimoteGeneralSubSettings(ArrayList<SettingsItem> sl, int wiimoteNumber) private void addWiimoteGeneralSubSettings(ArrayList<SettingsItem> sl, int wiimoteNumber)
{ {
addControllerSettings(sl, EmulatedController.getWiimote(wiimoteNumber), addControllerMappingSettings(sl, EmulatedController.getWiimote(wiimoteNumber),
Collections.singleton(ControlGroup.TYPE_BUTTONS)); Collections.singleton(ControlGroup.TYPE_BUTTONS));
} }
private void addWiimoteMotionSimulationSubSettings(ArrayList<SettingsItem> sl, int wiimoteNumber) private void addWiimoteMotionSimulationSubSettings(ArrayList<SettingsItem> sl, int wiimoteNumber)
{ {
addControllerSettings(sl, EmulatedController.getWiimote(wiimoteNumber), addControllerMappingSettings(sl, EmulatedController.getWiimote(wiimoteNumber),
new ArraySet<>(Arrays.asList(ControlGroup.TYPE_FORCE, ControlGroup.TYPE_TILT, new ArraySet<>(Arrays.asList(ControlGroup.TYPE_FORCE, ControlGroup.TYPE_TILT,
ControlGroup.TYPE_CURSOR, ControlGroup.TYPE_SHAKE))); ControlGroup.TYPE_CURSOR, ControlGroup.TYPE_SHAKE)));
} }
private void addWiimoteMotionInputSubSettings(ArrayList<SettingsItem> sl, int wiimoteNumber) private void addWiimoteMotionInputSubSettings(ArrayList<SettingsItem> sl, int wiimoteNumber)
{ {
addControllerSettings(sl, EmulatedController.getWiimote(wiimoteNumber), addControllerMappingSettings(sl, EmulatedController.getWiimote(wiimoteNumber),
new ArraySet<>(Arrays.asList(ControlGroup.TYPE_IMU_ACCELEROMETER, new ArraySet<>(Arrays.asList(ControlGroup.TYPE_IMU_ACCELEROMETER,
ControlGroup.TYPE_IMU_GYROSCOPE, ControlGroup.TYPE_IMU_CURSOR))); 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. * @param controller The controller to add settings for.
*/
private void addControllerMetaSettings(ArrayList<SettingsItem> 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. * @param groupTypeFilter If this is non-null, only groups whose types match this are considered.
*/ */
private void addControllerSettings(ArrayList<SettingsItem> sl, EmulatedController controller, private void addControllerMappingSettings(ArrayList<SettingsItem> sl,
Set<Integer> groupTypeFilter) EmulatedController controller, Set<Integer> groupTypeFilter)
{ {
int groupCount = controller.getGroupCount(); int groupCount = controller.getGroupCount();
for (int i = 0; i < groupCount; i++) for (int i = 0; i < groupCount; i++)

View file

@ -83,7 +83,7 @@ public abstract class SettingViewHolder extends RecyclerView.ViewHolder
{ {
SettingsItem item = getItem(); SettingsItem item = getItem();
if (item == null || !item.hasSetting()) if (item == null || !item.canClear())
return false; return false;
if (!item.isEditable()) if (!item.isEditable())

View file

@ -64,10 +64,8 @@ public final class SingleChoiceViewHolder extends SettingViewHolder
else if (item instanceof StringSingleChoiceSetting) else if (item instanceof StringSingleChoiceSetting)
{ {
StringSingleChoiceSetting setting = (StringSingleChoiceSetting) item; StringSingleChoiceSetting setting = (StringSingleChoiceSetting) item;
String[] choices = setting.getChoices(); String choice = setting.getSelectedChoice(settings);
int valueIndex = setting.getSelectedValueIndex(settings); mBinding.textSettingDescription.setText(choice);
if (valueIndex != -1)
mBinding.textSettingDescription.setText(choices[valueIndex]);
} }
else if (item instanceof SingleChoiceSettingDynamicDescriptions) else if (item instanceof SingleChoiceSettingDynamicDescriptions)
{ {

View file

@ -27,6 +27,8 @@
<string name="wiimote_motion_input">Motion Input</string> <string name="wiimote_motion_input">Motion Input</string>
<string name="wiimote_extensions">Extension</string> <string name="wiimote_extensions">Extension</string>
<string name="input_device">Device</string>
<string name="input_binding">Input Binding</string> <string name="input_binding">Input Binding</string>
<string name="input_binding_description">Press or move an input to bind it to %1$s.</string> <string name="input_binding_description">Press or move an input to bind it to %1$s.</string>

View file

@ -17,6 +17,7 @@ add_library(main SHARED
Input/ControlReference.cpp Input/ControlReference.cpp
Input/ControlReference.h Input/ControlReference.h
Input/EmulatedController.cpp Input/EmulatedController.cpp
Input/EmulatedController.h
Input/InputOverrider.cpp Input/InputOverrider.cpp
Input/MappingCommon.cpp Input/MappingCommon.cpp
Input/NumericSetting.cpp Input/NumericSetting.cpp

View file

@ -16,7 +16,7 @@
#include "jni/Input/ControlGroup.h" #include "jni/Input/ControlGroup.h"
#include "jni/Input/ControlReference.h" #include "jni/Input/ControlReference.h"
static ControllerEmu::EmulatedController* GetPointer(JNIEnv* env, jobject obj) ControllerEmu::EmulatedController* EmulatedControllerFromJava(JNIEnv* env, jobject obj)
{ {
return reinterpret_cast<ControllerEmu::EmulatedController*>( return reinterpret_cast<ControllerEmu::EmulatedController*>(
env->GetLongField(obj, IDCache::GetEmulatedControllerPointer())); env->GetLongField(obj, IDCache::GetEmulatedControllerPointer()));
@ -34,25 +34,40 @@ static jobject EmulatedControllerToJava(JNIEnv* env, ControllerEmu::EmulatedCont
extern "C" { 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 JNIEXPORT jint JNICALL
Java_org_dolphinemu_dolphinemu_features_input_model_controlleremu_EmulatedController_getGroupCount( Java_org_dolphinemu_dolphinemu_features_input_model_controlleremu_EmulatedController_getGroupCount(
JNIEnv* env, jobject obj) JNIEnv* env, jobject obj)
{ {
return static_cast<jint>(GetPointer(env, obj)->groups.size()); return static_cast<jint>(EmulatedControllerFromJava(env, obj)->groups.size());
} }
JNIEXPORT jobject JNICALL JNIEXPORT jobject JNICALL
Java_org_dolphinemu_dolphinemu_features_input_model_controlleremu_EmulatedController_getGroup( Java_org_dolphinemu_dolphinemu_features_input_model_controlleremu_EmulatedController_getGroup(
JNIEnv* env, jobject obj, jint controller_index) 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 JNIEXPORT void JNICALL
Java_org_dolphinemu_dolphinemu_features_input_model_controlleremu_EmulatedController_updateSingleControlReference( Java_org_dolphinemu_dolphinemu_features_input_model_controlleremu_EmulatedController_updateSingleControlReference(
JNIEnv* env, jobject obj, jobject control_reference) JNIEnv* env, jobject obj, jobject control_reference)
{ {
return GetPointer(env, obj)->UpdateSingleControlReference( return EmulatedControllerFromJava(env, obj)->UpdateSingleControlReference(
g_controller_interface, ControlReferenceFromJava(env, control_reference)); g_controller_interface, ControlReferenceFromJava(env, control_reference));
} }

View file

@ -0,0 +1,13 @@
// Copyright 2022 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <jni.h>
namespace ControllerEmu
{
class ControlReference;
}
ControllerEmu::EmulatedController* EmulatedControllerFromJava(JNIEnv* env, jobject obj);

View file

@ -17,6 +17,7 @@
#include "InputCommon/ControllerInterface/ControllerInterface.h" #include "InputCommon/ControllerInterface/ControllerInterface.h"
#include "InputCommon/ControllerInterface/MappingCommon.h" #include "InputCommon/ControllerInterface/MappingCommon.h"
#include "jni/AndroidCommon/AndroidCommon.h" #include "jni/AndroidCommon/AndroidCommon.h"
#include "jni/Input/EmulatedController.h"
namespace namespace
{ {
@ -28,10 +29,19 @@ constexpr auto INPUT_DETECT_MAXIMUM_TIME = std::chrono::seconds(5);
extern "C" { extern "C" {
JNIEXPORT jstring JNICALL 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<std::string> device_strings = g_controller_interface.GetAllDeviceStrings(); ControllerEmu::EmulatedController* emulated_controller =
const ciface::Core::DeviceQualifier default_device{}; EmulatedControllerFromJava(env, j_emulated_controller);
const ciface::Core::DeviceQualifier default_device = emulated_controller->GetDefaultDevice();
std::vector<std::string> device_strings;
if (all_devices)
device_strings = g_controller_interface.GetAllDeviceStrings();
else
device_strings = {default_device.ToString()};
auto detections = auto detections =
g_controller_interface.DetectInput(device_strings, INPUT_DETECT_INITIAL_TIME, g_controller_interface.DetectInput(device_strings, INPUT_DETECT_INITIAL_TIME,

View file

@ -1122,4 +1122,11 @@ Java_org_dolphinemu_dolphinemu_features_input_model_ControllerInterface_refreshD
{ {
g_controller_interface.RefreshDevices(); 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());
}
} }