Added controller support

This commit is contained in:
izzy2lost
2025-08-25 13:56:13 -04:00
parent 26151a6259
commit 30191559c3
11 changed files with 965 additions and 23 deletions
@@ -50,10 +50,11 @@ import org.json.JSONException;
import androidx.fragment.app.FragmentManager;
import java.util.List;
public class MainActivity extends AppCompatActivity implements GamesCoverDialogFragment.OnGameSelectedListener {
public class MainActivity extends AppCompatActivity implements GamesCoverDialogFragment.OnGameSelectedListener, ControllerInputHandler.ControllerInputListener {
private String m_szGamefile = "";
private HIDDeviceManager mHIDDeviceManager;
private ControllerInputHandler mControllerInputHandler;
private Thread mEmulationThread = null;
private boolean mHudVisible = false;
@@ -345,6 +346,12 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
Initialize();
// Initialize controller input handler
mControllerInputHandler = new ControllerInputHandler(this);
// Log connected controllers for debugging
ControllerConfig.logControllerInfo(this);
makeButtonTouch();
setSurfaceView(new SDLSurface(this));
@@ -1153,6 +1160,8 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
SettingsDialogFragment.loadAndApplySettings(this);
mHIDDeviceManager = HIDDeviceManager.acquire(this);
// Initialize HID device manager for USB and Bluetooth controllers
mHIDDeviceManager.initialize(true, true);
// Apply renderer setting again after a delay to override any automatic detection
new android.os.Handler(android.os.Looper.getMainLooper()).postDelayed(() -> {
@@ -1217,38 +1226,40 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
@Override
public boolean onGenericMotionEvent(MotionEvent event) {
if (SDLControllerManager.isDeviceSDLJoystick(event.getDeviceId())) {
SDLControllerManager.handleJoystickMotionEvent(event);
// Use only our controller handler - disable SDL fallback to avoid conflicts
if (mControllerInputHandler != null && mControllerInputHandler.handleMotionEvent(event)) {
return true;
}
// Skip SDL controller manager to avoid mapping conflicts
return super.onGenericMotionEvent(event);
}
@Override
public boolean onKeyDown(int p_keyCode, KeyEvent p_event) {
if ((p_event.getSource() & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) {
if (p_event.getRepeatCount() == 0) {
SDLControllerManager.onNativePadDown(p_event.getDeviceId(), p_keyCode);
return true;
}
// Use only our controller handler - disable SDL fallback to avoid conflicts
if (mControllerInputHandler != null && mControllerInputHandler.handleKeyEvent(p_event)) {
return true;
}
else {
if (p_keyCode == KeyEvent.KEYCODE_BACK) {
showExitDialog();
return true;
}
// Handle back button for non-gamepad sources
if (p_keyCode == KeyEvent.KEYCODE_BACK) {
showExitDialog();
return true;
}
// Skip SDL controller manager to avoid mapping conflicts
return super.onKeyDown(p_keyCode, p_event);
}
@Override
public boolean onKeyUp(int p_keyCode, KeyEvent p_event) {
if ((p_event.getSource() & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) {
if (p_event.getRepeatCount() == 0) {
SDLControllerManager.onNativePadUp(p_event.getDeviceId(), p_keyCode);
return true;
}
// Use only our controller handler - disable SDL fallback to avoid conflicts
if (mControllerInputHandler != null && mControllerInputHandler.handleKeyEvent(p_event)) {
return true;
}
// Skip SDL controller manager to avoid mapping conflicts
return super.onKeyUp(p_keyCode, p_event);
}
@@ -1343,4 +1354,104 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
.setNegativeButton("Cancel", null)
.show();
}
// ControllerInputHandler.ControllerInputListener implementation
@Override
public void onControllerButtonPressed(int controllerId, int button, boolean pressed) {
android.util.Log.d("Controller", "Controller " + controllerId + " button " + button + " (" + getButtonName(button) + ") " + (pressed ? "pressed" : "released"));
// Send controller input to native code using existing setPadButton method
// Range 255 for pressed, 0 for released (matching PS2 button range)
int range = pressed ? 255 : 0;
NativeApp.setPadButton(button, range, pressed);
}
private String getButtonName(int button) {
switch (button) {
case KeyEvent.KEYCODE_BUTTON_A: return "Cross";
case KeyEvent.KEYCODE_BUTTON_B: return "Circle";
case KeyEvent.KEYCODE_BUTTON_X: return "Square";
case KeyEvent.KEYCODE_BUTTON_Y: return "Triangle";
case KeyEvent.KEYCODE_BUTTON_L1: return "L1";
case KeyEvent.KEYCODE_BUTTON_R1: return "R1";
case KeyEvent.KEYCODE_BUTTON_L2: return "L2";
case KeyEvent.KEYCODE_BUTTON_R2: return "R2";
case KeyEvent.KEYCODE_BUTTON_SELECT: return "Select";
case KeyEvent.KEYCODE_BUTTON_START: return "Start";
case KeyEvent.KEYCODE_BUTTON_THUMBL: return "L3";
case KeyEvent.KEYCODE_BUTTON_THUMBR: return "R3";
case KeyEvent.KEYCODE_DPAD_UP: return "D-Up";
case KeyEvent.KEYCODE_DPAD_DOWN: return "D-Down";
case KeyEvent.KEYCODE_DPAD_LEFT: return "D-Left";
case KeyEvent.KEYCODE_DPAD_RIGHT: return "D-Right";
default: return "Unknown(" + button + ")";
}
}
@Override
public void onControllerAnalogInput(int controllerId, int axis, float value) {
android.util.Log.d("Controller", "Controller " + controllerId + " axis " + axis + " (" + getAxisName(axis) + ") value " + value);
// Send analog input to native code using setPadButton
handleAnalogInput(axis, value);
}
@Override
public void onControllerCombo(int controllerId, String comboName) {
android.util.Log.d("Controller", "Controller " + controllerId + " combo: " + comboName);
if ("select_start".equals(comboName)) {
// Show quick actions dialog
runOnUiThread(() -> {
QuickActionsDialogFragment dialog = new QuickActionsDialogFragment();
dialog.show(getSupportFragmentManager(), "quick_actions");
});
}
}
private String getAxisName(int axis) {
switch (axis) {
case 110: return "L-Up";
case 111: return "L-Right";
case 112: return "L-Down";
case 113: return "L-Left";
case 120: return "R-Up";
case 121: return "R-Right";
case 122: return "R-Down";
case 123: return "R-Left";
case KeyEvent.KEYCODE_BUTTON_L2: return "L2-Trigger";
case KeyEvent.KEYCODE_BUTTON_R2: return "R2-Trigger";
default: return "Unknown(" + axis + ")";
}
}
private void handleAnalogInput(int axis, float value) {
// Convert analog input to button presses for the native interface
// This matches how AetherSX2 handles analog input
// For analog sticks, only send positive values (negative values are handled by opposite direction)
int intensity = Math.max(0, Math.round(Math.abs(value) * 255));
boolean pressed = Math.abs(value) > 0.1f;
switch (axis) {
case ControllerInputHandler.PAD_L_UP:
case ControllerInputHandler.PAD_L_DOWN:
case ControllerInputHandler.PAD_L_LEFT:
case ControllerInputHandler.PAD_L_RIGHT:
case ControllerInputHandler.PAD_R_UP:
case ControllerInputHandler.PAD_R_DOWN:
case ControllerInputHandler.PAD_R_LEFT:
case ControllerInputHandler.PAD_R_RIGHT:
// Analog stick directions
NativeApp.setPadButton(axis, intensity, pressed);
break;
case ControllerInputHandler.PAD_L2:
case ControllerInputHandler.PAD_R2:
// Triggers: 0-255 range
NativeApp.setPadButton(axis, Math.round(value * 255), value > 0.1f);
break;
}
}
}