From 5850c514f2931fcaf1091cc8bb92cd8b51646df6 Mon Sep 17 00:00:00 2001 From: Logan McNaughton <848146+loganmc10@users.noreply.github.com> Date: Tue, 19 May 2026 19:30:09 +0200 Subject: [PATCH] Populate controller list on android (#920) * list controllers in UI for android * more * more --- Cargo.lock | 1 + Cargo.toml | 1 + src/ui.rs | 2 + src/ui/android_input.rs | 106 ++++++++++++++++++++++++++++++++++++++++ src/ui/input.rs | 14 +++++- 5 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 src/ui/android_input.rs diff --git a/Cargo.lock b/Cargo.lock index 055dda8e..69247f2a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2266,6 +2266,7 @@ dependencies = [ "encoding_rs", "fatfs", "futures", + "jni 0.22.4", "mimalloc", "postcard", "rand 0.10.1", diff --git a/Cargo.toml b/Cargo.toml index bceef272..d91ff647 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,7 @@ dirs = "6.0" [target.'cfg(target_os = "android")'.dependencies] slint = { version = "1.16", default-features = false, features = ["compat-1-2", "std", "backend-android-activity-06", "accessibility"], optional = true } +jni = "0.22" [build-dependencies] winresource = "0.1" diff --git a/src/ui.rs b/src/ui.rs index b191a1bf..b9245424 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1,3 +1,5 @@ +#[cfg(target_os = "android")] +pub mod android_input; pub mod audio; #[cfg(feature = "gui")] pub mod cheats; diff --git a/src/ui/android_input.rs b/src/ui/android_input.rs new file mode 100644 index 00000000..44abac88 --- /dev/null +++ b/src/ui/android_input.rs @@ -0,0 +1,106 @@ +use jni::{Env, JavaVM, bind_java_type}; + +bind_java_type! { + pub AndroidInputDevice => "android.view.InputDevice", + fields { + #[allow(non_snake_case)] + static SOURCE_JOYSTICK: jint, + #[allow(non_snake_case)] + static SOURCE_GAMEPAD: jint, + }, + methods { + static fn get_device_ids() -> jint[], + static fn get_device(device_id: jint) -> AndroidInputDevice, + fn supports_source(source: jint) -> jboolean, + fn is_virtual() -> jboolean, + fn is_external() -> jboolean, + fn get_vendor_id() -> jint, + fn get_product_id() -> jint, + fn get_name() -> JString, + fn get_descriptor() -> JString, + }, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct ControllerInfo { + pub name: String, + /// Stable ID from [`InputDevice.getDescriptor`](https://developer.android.com/reference/android/view/InputDevice#getDescriptor()). + pub descriptor: String, +} + +/// Lists connected gamepads and joysticks using the Android framework. +pub fn list_controllers() -> Vec { + let Some(app) = crate::ui::ANDROID_APP.get() else { + eprintln!("Android app not initialized; cannot list controllers"); + return Vec::new(); + }; + + let vm = unsafe { JavaVM::from_raw(app.vm_as_ptr().cast()) }; + + match vm.attach_current_thread(list_controllers_on_jvm) { + Ok(controllers) => controllers, + Err(err) => { + eprintln!("JNI error while listing controllers: {err:?}"); + Vec::new() + } + } +} + +fn list_controllers_on_jvm(env: &mut Env<'_>) -> jni::errors::Result> { + let source_gamepad = AndroidInputDevice::SOURCE_GAMEPAD(env)?; + let source_joystick = AndroidInputDevice::SOURCE_JOYSTICK(env)?; + + let device_ids = AndroidInputDevice::get_device_ids(env)?; + let count = device_ids.len(env)?; + let mut ids = vec![0i32; count]; + if count > 0 { + device_ids.get_region(env, 0, &mut ids)?; + } + + let mut controllers = Vec::new(); + for device_id in ids { + let device = AndroidInputDevice::get_device(env, device_id)?; + if device.is_null() { + continue; + } + + if device.is_virtual(env)? { + continue; + } + + if !device.is_external(env)? { + continue; + } + + if !device.supports_source(env, source_gamepad & source_joystick)? { + continue; + } + + if device.get_vendor_id(env)? == 0 { + continue; + } + + if device.get_product_id(env)? == 0 { + continue; + } + + let name = if let Ok(name) = device.get_name(env) + && let Ok(name) = name.try_to_string(env) + { + name + } else { + "Unknown controller".to_string() + }; + let descriptor = if let Ok(descriptor) = device.get_descriptor(env) + && let Ok(descriptor) = descriptor.try_to_string(env) + { + descriptor + } else { + String::new() + }; + + controllers.push(ControllerInfo { name, descriptor }); + } + + Ok(controllers) +} diff --git a/src/ui/input.rs b/src/ui/input.rs index 937e73f9..bd890f10 100644 --- a/src/ui/input.rs +++ b/src/ui/input.rs @@ -362,7 +362,12 @@ pub fn get_controller_names() -> Vec { #[cfg(target_os = "android")] pub fn get_controller_names() -> Vec { - vec!["None".into()] + let mut controllers: Vec = ui::android_input::list_controllers() + .into_iter() + .map(|c| c.name) + .collect(); + controllers.insert(0, "None".into()); + controllers } #[cfg(not(target_os = "android"))] @@ -386,7 +391,12 @@ pub fn get_controller_paths() -> Vec { #[cfg(target_os = "android")] #[cfg(feature = "gui")] pub fn get_controller_paths() -> Vec { - vec![String::new()] + let mut controller_paths: Vec = ui::android_input::list_controllers() + .into_iter() + .map(|c| c.descriptor) + .collect(); + controller_paths.insert(0, String::new()); + controller_paths } fn handle_joystick_events(ui: &mut ui::Ui) {