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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions