Skip to content

Commit 5452999

Browse files
Fix mouse grab behavior on Android (#16203)
* Add grab_mouse interface for Android Makes mouse grabbing and 'Game Focus' work on Android with a real mouse Properly handle relative mouse motion events on Android (SDK 28 and newer) * Enable workflow_dispatch on CI Android * Update android_mouse_calculate_deltas callsites * Add RETRO_DEVICE_MOUSE to android_input_get_capabilities * Use Handler to trigger UI events (toggle mouse, immersive mode) with 300ms delay * Enable input_auto_mouse_grab by default for Android * Handle RARCH_DEVICE_MOUSE_SCREEN in Android input driver * Add android.hardware.type.pc to manifest * Don't attempt to set pointer speed via scaling in android_mouse_calculate_deltas * Keep x/y values within viewport resolution for screen mouse * Use video_driver_get_size to get width/height --------- Co-authored-by: Bernhard Schelling <[email protected]>
1 parent 338c9a4 commit 5452999

9 files changed

Lines changed: 281 additions & 115 deletions

File tree

.github/workflows/Android.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ name: CI Android
33
on:
44
push:
55
pull_request:
6+
workflow_dispatch:
67
repository_dispatch:
78
types: [run_build]
9+
810

911
permissions:
1012
contents: read

config.def.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1552,6 +1552,14 @@
15521552
#define DEFAULT_TURBO_DEFAULT_BTN RETRO_DEVICE_ID_JOYPAD_B
15531553
#define DEFAULT_ALLOW_TURBO_DPAD false
15541554

1555+
/* Enable automatic mouse grab by default
1556+
* only on Android */
1557+
#if defined(ANDROID)
1558+
#define DEFAULT_INPUT_AUTO_MOUSE_GRAB true
1559+
#else
1560+
#define DEFAULT_INPUT_AUTO_MOUSE_GRAB false
1561+
#endif
1562+
15551563
#if TARGET_OS_IPHONE
15561564
#define DEFAULT_INPUT_KEYBOARD_GAMEPAD_ENABLE false
15571565
#else

configuration.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2086,7 +2086,7 @@ static struct config_bool_setting *populate_settings_bool(
20862086
SETTING_BOOL("keyboard_gamepad_enable", &settings->bools.input_keyboard_gamepad_enable, true, DEFAULT_INPUT_KEYBOARD_GAMEPAD_ENABLE, false);
20872087
SETTING_BOOL("input_autodetect_enable", &settings->bools.input_autodetect_enable, true, DEFAULT_INPUT_AUTODETECT_ENABLE, false);
20882088
SETTING_BOOL("input_allow_turbo_dpad", &settings->bools.input_allow_turbo_dpad, true, DEFAULT_ALLOW_TURBO_DPAD, false);
2089-
SETTING_BOOL("input_auto_mouse_grab", &settings->bools.input_auto_mouse_grab, true, false, false);
2089+
SETTING_BOOL("input_auto_mouse_grab", &settings->bools.input_auto_mouse_grab, true, DEFAULT_INPUT_AUTO_MOUSE_GRAB, false);
20902090
SETTING_BOOL("input_remap_binds_enable", &settings->bools.input_remap_binds_enable, true, true, false);
20912091
SETTING_BOOL("input_hotkey_device_merge", &settings->bools.input_hotkey_device_merge, true, DEFAULT_INPUT_HOTKEY_DEVICE_MERGE, false);
20922092
SETTING_BOOL("all_users_control_menu", &settings->bools.input_all_users_control_menu, true, DEFAULT_ALL_USERS_CONTROL_MENU, false);

frontend/drivers/platform_unix.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2090,6 +2090,8 @@ static void frontend_unix_init(void *data)
20902090
"getVolumeCount", "()I");
20912091
GET_METHOD_ID(env, android_app->getVolumePath, class,
20922092
"getVolumePath", "(Ljava/lang/String;)Ljava/lang/String;");
2093+
GET_METHOD_ID(env, android_app->inputGrabMouse, class,
2094+
"inputGrabMouse", "(Z)V");
20932095

20942096
GET_OBJECT_CLASS(env, class, obj);
20952097
GET_METHOD_ID(env, android_app->getStringExtra, class,

frontend/drivers/platform_unix.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ struct android_app
178178

179179
jmethodID getVolumeCount;
180180
jmethodID getVolumePath;
181+
jmethodID inputGrabMouse;
181182

182183
struct
183184
{

input/drivers/android_input.c

Lines changed: 107 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,17 @@ enum {
6161
AMOTION_EVENT_BUTTON_FORWARD = 1 << 4,
6262
AMOTION_EVENT_AXIS_VSCROLL = 9,
6363
AMOTION_EVENT_ACTION_HOVER_MOVE = 7,
64-
AINPUT_SOURCE_STYLUS = 0x00004002,
64+
AINPUT_SOURCE_STYLUS = 0x00004000 | AINPUT_SOURCE_CLASS_POINTER,
6565
AMOTION_EVENT_BUTTON_STYLUS_PRIMARY = 1 << 5,
6666
AMOTION_EVENT_BUTTON_STYLUS_SECONDARY = 1 << 6
6767
};
6868
#endif
69+
/* If using an NDK lower than 16b then add missing definition */
70+
#ifndef __ANDROID_API_O_MR1__
71+
enum {
72+
AINPUT_SOURCE_MOUSE_RELATIVE = 0x00020000 | AINPUT_SOURCE_CLASS_NAVIGATION
73+
};
74+
#endif
6975

7076
/* If using an SDK lower than 24 then add missing relative axis codes */
7177
#ifndef AMOTION_EVENT_AXIS_RELATIVE_X
@@ -144,6 +150,7 @@ typedef struct android_input
144150
{
145151
int64_t quick_tap_time;
146152
state_device_t pad_states[MAX_USERS]; /* int alignment */
153+
int mouse_x, mouse_y;
147154
int mouse_x_delta, mouse_y_delta;
148155
int mouse_l, mouse_r, mouse_m, mouse_wu, mouse_wd;
149156
unsigned pads_connected;
@@ -638,53 +645,77 @@ static int android_check_quick_tap(android_input_t *android)
638645
}
639646

640647
static INLINE void android_mouse_calculate_deltas(android_input_t *android,
641-
AInputEvent *event,size_t motion_ptr)
648+
AInputEvent *event,size_t motion_ptr,int source)
642649
{
643-
/* Adjust mouse speed based on ratio
644-
* between core resolution and system resolution */
645-
float x = 0, y = 0;
646-
float x_scale = 1;
647-
float y_scale = 1;
648-
settings_t *settings = config_get_ptr();
649-
video_driver_state_t *video_st = video_state_get_ptr();
650-
struct retro_system_av_info *av_info = &video_st->av_info;
651-
652-
if (av_info)
650+
unsigned video_width, video_height;
651+
video_driver_get_size(&video_width, &video_height);
652+
653+
float x = 0;
654+
float x_delta = 0;
655+
float x_min = 0;
656+
float x_max = (float)video_width;
657+
658+
float y = 0;
659+
float y_delta = 0;
660+
float y_min = 0;
661+
float y_max = (float)video_height;
662+
663+
/* AINPUT_SOURCE_MOUSE_RELATIVE is available on Oreo (SDK 26) and newer,
664+
* it passes the relative coordinates in the regular X and Y parts.
665+
* NOTE: AINPUT_SOURCE_* defines have multiple bits set so do full check */
666+
if ((source & AINPUT_SOURCE_MOUSE_RELATIVE) == AINPUT_SOURCE_MOUSE_RELATIVE)
653667
{
654-
video_viewport_t *custom_vp = &settings->video_viewport_custom;
655-
const struct retro_game_geometry *geom = (const struct retro_game_geometry*)&av_info->geometry;
656-
x_scale = 2 * (float)geom->base_width / (float)custom_vp->width;
657-
y_scale = 2 * (float)geom->base_height / (float)custom_vp->height;
668+
x_delta = AMotionEvent_getX(event, motion_ptr);
669+
y_delta = AMotionEvent_getY(event, motion_ptr);
658670
}
659-
660-
/* This axis is only available on Android Nougat and on
661-
* Android devices with NVIDIA extensions */
662-
if (p_AMotionEvent_getAxisValue)
671+
else
663672
{
664-
x = AMotionEvent_getAxisValue(event,AMOTION_EVENT_AXIS_RELATIVE_X,
665-
motion_ptr);
666-
y = AMotionEvent_getAxisValue(event,AMOTION_EVENT_AXIS_RELATIVE_Y,
667-
motion_ptr);
668-
}
673+
/* This axis is only available on Android Nougat or on
674+
* Android devices with NVIDIA extensions */
675+
if (p_AMotionEvent_getAxisValue)
676+
{
677+
x_delta = AMotionEvent_getAxisValue(event,AMOTION_EVENT_AXIS_RELATIVE_X,
678+
motion_ptr);
679+
y_delta = AMotionEvent_getAxisValue(event,AMOTION_EVENT_AXIS_RELATIVE_Y,
680+
motion_ptr);
681+
}
669682

670-
/* If AXIS_RELATIVE had 0 values it might be because we're not
671-
* running Android Nougat or on a device
672-
* with NVIDIA extension, so re-calculate deltas based on
673-
* AXIS_X and AXIS_Y. This has limitations
674-
* compared to AXIS_RELATIVE because once the Android mouse cursor
675-
* hits the edge of the screen it is
676-
* not possible to move the in-game mouse any further in that direction.
677-
*/
678-
if (!x && !y)
679-
{
680-
x = (AMotionEvent_getX(event, motion_ptr) - android->mouse_x_prev);
681-
y = (AMotionEvent_getY(event, motion_ptr) - android->mouse_y_prev);
682-
android->mouse_x_prev = AMotionEvent_getX(event, motion_ptr);
683-
android->mouse_y_prev = AMotionEvent_getY(event, motion_ptr);
683+
/* If AXIS_RELATIVE had 0 values it might be because we're not
684+
* running Android Nougat or on a device
685+
* with NVIDIA extension, so re-calculate deltas based on
686+
* AXIS_X and AXIS_Y. This has limitations
687+
* compared to AXIS_RELATIVE because once the Android mouse cursor
688+
* hits the edge of the screen it is
689+
* not possible to move the in-game mouse any further in that direction.
690+
*/
691+
if (!x_delta && !y_delta)
692+
{
693+
x = AMotionEvent_getX(event, motion_ptr);
694+
y = AMotionEvent_getY(event, motion_ptr);
695+
696+
x_delta = (x_delta - android->mouse_x_prev);
697+
y_delta = (y_delta - android->mouse_y_prev);
698+
699+
android->mouse_x_prev = x;
700+
android->mouse_y_prev = y;
701+
}
684702
}
685703

686-
android->mouse_x_delta = ceil(x) * x_scale;
687-
android->mouse_y_delta = ceil(y) * y_scale;
704+
android->mouse_x_delta = x_delta;
705+
android->mouse_y_delta = y_delta;
706+
707+
if (!x) x = android->mouse_x + android->mouse_x_delta;
708+
if (!y) y = android->mouse_y + android->mouse_y_delta;
709+
710+
/* x and y are used for the screen mouse, so we want
711+
* to avoid values outside of the viewport resolution */
712+
if (x < x_min) x = x_min;
713+
else if (x > x_max) x = x_max;
714+
if (y < y_min) y = y_min;
715+
else if (y > y_max) y = y_max;
716+
717+
android->mouse_x = x;
718+
android->mouse_y = y;
688719
}
689720

690721
static INLINE void android_input_poll_event_type_motion(
@@ -697,13 +728,13 @@ static INLINE void android_input_poll_event_type_motion(
697728
bool keyup = (
698729
action == AMOTION_EVENT_ACTION_UP
699730
|| action == AMOTION_EVENT_ACTION_CANCEL
700-
|| action == AMOTION_EVENT_ACTION_POINTER_UP)
701-
|| (source == AINPUT_SOURCE_MOUSE &&
702-
action != AMOTION_EVENT_ACTION_DOWN);
731+
|| action == AMOTION_EVENT_ACTION_POINTER_UP);
703732

704733
/* If source is mouse then calculate button state
705-
* and mouse deltas and don't process as touchscreen event */
706-
if (source == AINPUT_SOURCE_MOUSE)
734+
* and mouse deltas and don't process as touchscreen event.
735+
* NOTE: AINPUT_SOURCE_* defines have multiple bits set so do full check */
736+
if ( (source & AINPUT_SOURCE_MOUSE) == AINPUT_SOURCE_MOUSE
737+
|| (source & AINPUT_SOURCE_MOUSE_RELATIVE) == AINPUT_SOURCE_MOUSE_RELATIVE)
707738
{
708739
/* getButtonState requires API level 14 */
709740
if (p_AMotionEvent_getButtonState)
@@ -732,7 +763,7 @@ static INLINE void android_input_poll_event_type_motion(
732763
android->mouse_l = 0;
733764
}
734765

735-
android_mouse_calculate_deltas(android,event,motion_ptr);
766+
android_mouse_calculate_deltas(android,event,motion_ptr,source);
736767

737768
return;
738769
}
@@ -785,7 +816,7 @@ static INLINE void android_input_poll_event_type_motion(
785816
if (( action == AMOTION_EVENT_ACTION_MOVE
786817
|| action == AMOTION_EVENT_ACTION_HOVER_MOVE)
787818
&& ENABLE_TOUCH_SCREEN_MOUSE)
788-
android_mouse_calculate_deltas(android,event,motion_ptr);
819+
android_mouse_calculate_deltas(android,event,motion_ptr,source);
789820

790821
for (motion_ptr = 0; motion_ptr < pointer_max; motion_ptr++)
791822
{
@@ -850,7 +881,7 @@ static INLINE void android_input_poll_event_type_motion_stylus(
850881
android->mouse_l = 0;
851882
}
852883

853-
android_mouse_calculate_deltas(android,event,motion_ptr);
884+
android_mouse_calculate_deltas(android,event,motion_ptr,source);
854885
}
855886

856887
if (action == AMOTION_EVENT_ACTION_MOVE) {
@@ -893,7 +924,7 @@ static INLINE void android_input_poll_event_type_motion_stylus(
893924
{
894925
android->mouse_l = 0;
895926

896-
android_mouse_calculate_deltas(android,event,motion_ptr);
927+
android_mouse_calculate_deltas(android,event,motion_ptr,source);
897928
}
898929

899930
// pointer was already released during AMOTION_EVENT_ACTION_HOVER_MOVE
@@ -967,7 +998,7 @@ static int android_input_get_id_port(android_input_t *android, int id,
967998
unsigned i;
968999
int ret = -1;
9691000
if (source & (AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_MOUSE |
970-
AINPUT_SOURCE_TOUCHPAD))
1001+
AINPUT_SOURCE_MOUSE_RELATIVE | AINPUT_SOURCE_TOUCHPAD))
9711002
ret = 0; /* touch overlay is always user 1 */
9721003

9731004
for (i = 0; i < android->pads_connected; i++)
@@ -1565,7 +1596,10 @@ static void android_input_poll_input_default(android_input_t *android)
15651596
else if ((source & AINPUT_SOURCE_STYLUS) == AINPUT_SOURCE_STYLUS)
15661597
android_input_poll_event_type_motion_stylus(android, event,
15671598
port, source);
1568-
else if ((source & (AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_MOUSE)))
1599+
/* Only handle events from a touchscreen or mouse */
1600+
else if ((source & (AINPUT_SOURCE_TOUCHSCREEN
1601+
| AINPUT_SOURCE_MOUSE
1602+
| AINPUT_SOURCE_MOUSE_RELATIVE)))
15691603
android_input_poll_event_type_motion(android, event,
15701604
port, source);
15711605
else
@@ -1774,6 +1808,7 @@ static int16_t android_input_state(
17741808
case RETRO_DEVICE_KEYBOARD:
17751809
return (id && id < RETROK_LAST) && BIT_GET(android_key_state[ANDROID_KEYBOARD_PORT], rarch_keysym_lut[id]);
17761810
case RETRO_DEVICE_MOUSE:
1811+
case RARCH_DEVICE_MOUSE_SCREEN:
17771812
{
17781813
int val = 0;
17791814
if (port > 0)
@@ -1788,11 +1823,17 @@ static int16_t android_input_state(
17881823
case RETRO_DEVICE_ID_MOUSE_MIDDLE:
17891824
return android->mouse_m;
17901825
case RETRO_DEVICE_ID_MOUSE_X:
1826+
if (device == RARCH_DEVICE_MOUSE_SCREEN)
1827+
return android->mouse_x;
1828+
17911829
val = android->mouse_x_delta;
17921830
android->mouse_x_delta = 0;
17931831
/* flush delta after it has been read */
17941832
return val;
17951833
case RETRO_DEVICE_ID_MOUSE_Y:
1834+
if (device == RARCH_DEVICE_MOUSE_SCREEN)
1835+
return android->mouse_y;
1836+
17961837
val = android->mouse_y_delta;
17971838
android->mouse_y_delta = 0;
17981839
/* flush delta after it has been read */
@@ -1907,6 +1948,7 @@ static uint64_t android_input_get_capabilities(void *data)
19071948
return
19081949
(1 << RETRO_DEVICE_JOYPAD)
19091950
| (1 << RETRO_DEVICE_POINTER)
1951+
| (1 << RETRO_DEVICE_MOUSE)
19101952
| (1 << RETRO_DEVICE_KEYBOARD)
19111953
| (1 << RETRO_DEVICE_LIGHTGUN)
19121954
| (1 << RETRO_DEVICE_ANALOG);
@@ -2056,6 +2098,18 @@ static float android_input_get_sensor_input(void *data,
20562098
return 0.0f;
20572099
}
20582100

2101+
static void android_input_grab_mouse(void *data, bool state)
2102+
{
2103+
JNIEnv *env = jni_thread_getenv();
2104+
2105+
if (!env || !g_android)
2106+
return;
2107+
2108+
if (g_android->inputGrabMouse)
2109+
CALL_VOID_METHOD_PARAM(env, g_android->activity->clazz,
2110+
g_android->inputGrabMouse, state);
2111+
}
2112+
20592113
static void android_input_keypress_vibrate()
20602114
{
20612115
static const int keyboard_press = 3;
@@ -2077,8 +2131,7 @@ input_driver_t input_android = {
20772131
android_input_get_sensor_input,
20782132
android_input_get_capabilities,
20792133
"android",
2080-
2081-
NULL, /* grab_mouse */
2134+
android_input_grab_mouse,
20822135
NULL,
20832136
android_input_keypress_vibrate
20842137
};

menu/menu_setting.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15272,7 +15272,7 @@ static bool setting_append_list(
1527215272
&settings->bools.input_auto_mouse_grab,
1527315273
MENU_ENUM_LABEL_INPUT_AUTO_MOUSE_GRAB,
1527415274
MENU_ENUM_LABEL_VALUE_INPUT_AUTO_MOUSE_GRAB,
15275-
false,
15275+
DEFAULT_INPUT_AUTO_MOUSE_GRAB,
1527615276
MENU_ENUM_LABEL_VALUE_OFF,
1527715277
MENU_ENUM_LABEL_VALUE_ON,
1527815278
&group_info,

pkg/android/phoenix/AndroidManifest.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
android:versionName="1.17.0"
77
android:installLocation="internalOnly">
88
<uses-feature android:glEsVersion="0x00020000" />
9+
<uses-feature android:name="android.hardware.type.pc" android:required="false"/>
910
<uses-feature android:name="android.hardware.touchscreen" android:required="false"/>
1011
<uses-feature android:name="android.software.leanback" android:required="false" />
1112
<uses-feature android:name="android.hardware.gamepad" android:required="false"/>

0 commit comments

Comments
 (0)