Skip to content

Example of not accepting mouse input #192

@yyy33

Description

@yyy33

Even though I have the relevant axis inputs turned on, when I connect the mouse to my phone via otg and input it, I can't receive any mouse events in rust

use android_activity::{
    input::{Axis, Button, InputEvent, KeyAction, KeyEvent, KeyMapChar, MotionAction},
    AndroidApp, InputStatus, MainEvent, PollEvent,
};
use log::info;

#[no_mangle]
fn android_main(app: AndroidApp) {
    android_logger::init_once(android_logger::Config::default().with_min_level(log::Level::Info));
    app.enable_motion_axis(Axis::X);
    app.enable_motion_axis(Axis::Y);
    app.enable_motion_axis(Axis::Vscroll);
    app.enable_motion_axis(Axis::Hscroll);
    app.enable_motion_axis(Axis::RelativeX);
    app.enable_motion_axis(Axis::RelativeY);

    app.enable_motion_axis(Axis::X);
    app.enable_motion_axis(Axis::Y);
    app.enable_motion_axis(Axis::Pressure);
    app.enable_motion_axis(Axis::Size);
    app.enable_motion_axis(Axis::TouchMajor);
    app.enable_motion_axis(Axis::TouchMinor);
    app.enable_motion_axis(Axis::Orientation);

    let mut quit = false;
    let mut redraw_pending = true;
    let mut native_window: Option<ndk::native_window::NativeWindow> = None;

    let mut combining_accent = None;

    while !quit {
        app.poll_events(
            Some(std::time::Duration::from_secs(1)), /* timeout */
            |event| {
                match event {
                    PollEvent::Wake => {}
                    PollEvent::Timeout => {
                        // Real app would probably rely on vblank sync via graphics API...
                        redraw_pending = true;
                    }
                    PollEvent::Main(main_event) => {
                        match main_event {
                            MainEvent::SaveState { saver, .. } => {
                                saver.store("foo://bar".as_bytes());
                            }
                            MainEvent::Pause => {}
                            MainEvent::Resume { loader, .. } => {
                                if let Some(state) = loader.load() {
                                    if let Ok(uri) = String::from_utf8(state) {}
                                }
                            }
                            MainEvent::InitWindow { .. } => {
                                native_window = app.native_window();
                                redraw_pending = true;
                            }
                            MainEvent::TerminateWindow { .. } => {
                                native_window = None;
                            }
                            MainEvent::WindowResized { .. } => {
                                redraw_pending = true;
                            }
                            MainEvent::RedrawNeeded { .. } => {
                                redraw_pending = true;
                            }
                            MainEvent::InputAvailable { .. } => {
                                redraw_pending = true;
                            }
                            MainEvent::ConfigChanged { .. } => {}
                            MainEvent::LowMemory => {}

                            MainEvent::Destroy => quit = true,
                            _ => { /* ... */ }
                        }
                    }
                    _ => {}
                }

                if redraw_pending {
                    if let Some(native_window) = &native_window {
                        redraw_pending = false;

                        // Handle input, via a lending iterator
                        match app.input_events_iter() {
                            Ok(mut iter) => loop {
                                if !iter.next(|event| {
                                    match event {
                                        InputEvent::KeyEvent(key_event) => {
                                            let combined_key_char = character_map_and_combine_key(
                                                &app,
                                                key_event,
                                                &mut combining_accent,
                                            );
                                        }
                                        InputEvent::MotionEvent(motion_event) => {
                                            let action = match motion_event.action() {
                                                MotionAction::Down => "Down",
                                                MotionAction::Up => "Up",
                                                MotionAction::Move => "Move",
                                                MotionAction::Cancel => "Cancel",
                                                MotionAction::Outside => "Outside",
                                                MotionAction::PointerDown => "PointerDown",
                                                MotionAction::PointerUp => "PointerUp",
                                                MotionAction::HoverMove => "HoverMove",
                                                MotionAction::Scroll => "Scroll",
                                                MotionAction::HoverEnter => "HoverEnter",
                                                MotionAction::HoverExit => "HoverExit",
                                                MotionAction::ButtonPress => "ButtonPress",
                                                MotionAction::ButtonRelease => "ButtonRelease",
                                                _ => "Unknow Action",
                                            };
                                            let button = match motion_event.action_button() {
                                                Button::Back => "Back",
                                                Button::Forward => "Forward",
                                                Button::Primary => "Primary",
                                                Button::Secondary => "Secondary",
                                                Button::StylusPrimary => "StylusPrimary",
                                                Button::StylusSecondary => "StylusSecondary",
                                                Button::Tertiary => "Tertiary",
                                                _ => "Unknow Action",
                                            };
                                            println!(
                                                "MotionEvent, Button: {:?}, Action: {:?}",
                                                button, action
                                            );
                                        }
                                        InputEvent::TextEvent(state) => {}
                                        _ => {}
                                    }

                                    InputStatus::Unhandled
                                }) {
                                    break;
                                }
                            },
                            Err(err) => {
                                log::error!("Failed to get input events iterator: {err:?}");
                            }
                        }

                        dummy_render(native_window);
                    }
                }
            },
        );
    }
}

/// Tries to map the `key_event` to a `KeyMapChar` containing a unicode character or dead key accent
///
/// This shows how to take a `KeyEvent` and look up its corresponding `KeyCharacterMap` and
/// use that to try and map the `key_code` + `meta_state` to a unicode character or a
/// dead key that be combined with the next key press.
fn character_map_and_combine_key(
    app: &AndroidApp,
    key_event: &KeyEvent,
    combining_accent: &mut Option<char>,
) -> Option<KeyMapChar> {
    let device_id = key_event.device_id();

    let key_map = match app.device_key_character_map(device_id) {
        Ok(key_map) => key_map,
        Err(err) => {
            log::error!("Failed to look up `KeyCharacterMap` for device {device_id}: {err:?}");
            return None;
        }
    };

    match key_map.get(key_event.key_code(), key_event.meta_state()) {
        Ok(KeyMapChar::Unicode(unicode)) => {
            // Only do dead key combining on key down
            if key_event.action() == KeyAction::Down {
                let combined_unicode = if let Some(accent) = combining_accent {
                    match key_map.get_dead_char(*accent, unicode) {
                        Ok(Some(key)) => {
                            info!("KeyEvent: Combined '{unicode}' with accent '{accent}' to give '{key}'");
                            Some(key)
                        }
                        Ok(None) => None,
                        Err(err) => {
                            log::error!("KeyEvent: Failed to combine 'dead key' accent '{accent}' with '{unicode}': {err:?}");
                            None
                        }
                    }
                } else {
                    info!("KeyEvent: Pressed '{unicode}'");
                    Some(unicode)
                };
                *combining_accent = None;
                combined_unicode.map(|unicode| KeyMapChar::Unicode(unicode))
            } else {
                Some(KeyMapChar::Unicode(unicode))
            }
        }
        Ok(KeyMapChar::CombiningAccent(accent)) => {
            if key_event.action() == KeyAction::Down {
                info!("KeyEvent: Pressed 'dead key' combining accent '{accent}'");
                *combining_accent = Some(accent);
            }
            Some(KeyMapChar::CombiningAccent(accent))
        }
        Ok(KeyMapChar::None) => {
            // Leave any combining_accent state in tact (seems to match how other
            // Android apps work)
            info!("KeyEvent: Pressed non-unicode key");
            None
        }
        Err(err) => {
            log::error!("KeyEvent: Failed to get key map character: {err:?}");
            *combining_accent = None;
            None
        }
    }
}

/// Post a NOP frame to the window
///
/// Since this is a bare minimum test app we don't depend
/// on any GPU graphics APIs but we do need to at least
/// convince Android that we're drawing something and are
/// responsive, otherwise it will stop delivering input
/// events to us.
fn dummy_render(native_window: &ndk::native_window::NativeWindow) {
    unsafe {
        let mut buf: ndk_sys::ANativeWindow_Buffer = std::mem::zeroed();
        let mut rect: ndk_sys::ARect = std::mem::zeroed();
        ndk_sys::ANativeWindow_lock(
            native_window.ptr().as_ptr() as _,
            &mut buf as _,
            &mut rect as _,
        );
        // Note: we don't try and touch the buffer since that
        // also requires us to handle various buffer formats
        ndk_sys::ANativeWindow_unlockAndPost(native_window.ptr().as_ptr() as _);
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions