Android: Samsung S-Pen stylus support (#19076)

* android: Add Samsung S-Pen stylus input support

Stylus input for the Android driver, squashed from PR #19076 and
rebased onto current master (three conflicts, all additive: the
hover-guard block in android_input.c, an include in runloop.c and the
stylus rows in tools/settings_table.reference).

- toolType-based stylus/finger classification with an input-source
  fallback
- hover, contact and click handled separately; hover can move the
  pointer without pressing
- phantom-touch suppression for Samsung pens (hover guard, proximity
  window)
- both barrel buttons read
- four Android-only settings: input_stylus_enable,
  input_stylus_require_contact_for_click,
  input_stylus_hover_moves_pointer, input_stylus_pressure_sensitivity
- android_mouse_calculate_deltas computed its fallback delta from the
  zeroed delta instead of the event position; fixed

Closes #15490
Closes #18194

* overlay: hide for a stylus without unloading the pack

The S-Pen support unloaded the on-screen overlay from the run loop
whenever a stylus became active and loaded it again two seconds after
the last pen event. A pen that leaves and returns does that over and
over, and each round is the pack's file and texture work, triggered by
input while a core runs.

It was a full unload because a soft-hidden overlay still took over
pointer input: with input_overlay_pointer_enable the overlay poll
tracks every touch as its own pointing device and sets
INP_FLAG_BLOCK_POINTER_INPUT, so input_state never reaches the driver
and the pen's native state (hover included) is lost.

Add INPUT_OVERLAY_STYLUS_HIDDEN and make it complete. While it is set
the overlay:
- is not drawn (alpha 0, as the gamepad soft-hide does);
- reads no touches, so the poll releases whatever a finger was holding
  the way lifting it would, and no hitbox or pointing device sees the
  pen;
- does not answer mouse, lightgun or pointer queries and does not
  block pointer input.

The run loop sets and clears the flag each frame on the current
overlay, the way the gamepad soft-hide is driven. Nothing is unloaded
or reloaded.

* android: report a pen as one pointer, its barrel button as the right mouse button

The S-Pen support gave pointer indices a meaning of its own: index 0
was the cursor, index 1 the tip pressing and index 2 the barrel
button, with the pointer count raised to 2 or 3 to match. Only a core
written for that contract can read it. Everywhere else an index is a
separate touch, so a pressing pen looked like two fingers and a press
with the barrel button like three - to cores that count pointers, to
the on-screen overlay, and to this driver's own lightgun mapping,
where two pointers are turbo and three are reload. With the barrel
button held but the tip not pressing, index 1 was stale and still
inside the count.

Map the pen onto the devices the driver already reports instead:

- position is pointer 0, and moves without a press while hovering
  (input_stylus_hover_moves_pointer);
- a press is pointer 0 pressed with a count of 1: the tip pressing
  past the pressure threshold, or, with
  input_stylus_require_contact_for_click off, the barrel button,
  touching or hovering;
- the barrel button is the right mouse button.

This also puts two settings to work. The contact-for-click option and
the pressure threshold only fed a latch nothing read; they now decide
the press, as their menu text says.

The pen no longer switches the driver into mouse mode
(mouse_activated). That flag is never cleared, and once set the
lightgun position is read from the mouse coordinates, which a pen does
not update, so lightgun aim went stale after the first pen contact.
The mouse_x_viewport/mouse_y_viewport values it wrote alongside are
not read anywhere.

A cancelled pen gesture now releases the press like a lift does;
before it fell through to the touchscreen path and left the pen's
contact latched.

Compiled with the NDK through the Android unity build. Not run on a
device: it needs a pass on S-Pen hardware before merging.

* android: let a mouse's hover events reach the mouse path again

The S-Pen support drops every hover event before the source is looked
at, so hover cannot be taken for a touch. A mouse moved with no button
held reports its motion as hover events too, and they were dropped
with the rest: the pointer only moved while a button was down.

Drop hover events after the mouse branch instead. Mouse motion is
handled as before the stylus work, and hover from any other source is
still kept out of the touchscreen path.

---------

Co-authored-by: LibretroAdmin <[email protected]>
This commit is contained in:
M00NR00STERandLibretroAdmin authored and GitHub committed 2026-10-02 05:48:02 +02:00
1 parent 11dc8ba30d
commit 4592b490bd
17 files changed
+656 -23

No files matched your search

+2
View File
@@ -89,6 +89,8 @@
#define DEFAULT_TOUCH_SCALE 1
#define DEFAULT_INPUT_STYLUS_PRESSURE_SENSITIVITY 70
#if defined(RARCH_MOBILE) || defined(HAVE_LIBNX) || defined(__WINRT__) || defined(__EMSCRIPTEN__) || defined (VITA)
#define DEFAULT_POINTER_ENABLE true
#else
+8 -2
View File
@@ -2913,6 +2913,11 @@ static struct config_bool_setting *populate_settings_bool(
SETTING_BOOL("netplay_request_device_p16", &settings->bools.netplay_request_devices[15], true, false, false);
#endif
#ifdef ANDROID
SETTING_BOOL("input_stylus_enable", &settings->bools.input_stylus_enable, true, true, false);
SETTING_BOOL("input_stylus_require_contact_for_click", &settings->bools.input_stylus_require_contact_for_click, true, true, false);
SETTING_BOOL("input_stylus_hover_moves_pointer", &settings->bools.input_stylus_hover_moves_pointer, true, true, false);
#endif
#ifdef _3DS
SETTING_BOOL("new3ds_speedup_enable", &settings->bools.new3ds_speedup_enable, true, DEFAULT_NEW_3DS_SPEEDUP_ENABLE, false);
@@ -4233,9 +4238,10 @@ static struct config_uint_setting *populate_settings_uint(
#endif
#ifdef ANDROID
SETTING_UINT("input_sensor_orientation", &settings->uints.input_sensor_orientation, true, 0, false);
SETTING_UINT("input_stylus_pressure_sensitivity", &settings->uints.input_stylus_pressure_sensitivity, true, DEFAULT_INPUT_STYLUS_PRESSURE_SENSITIVITY, false);
SETTING_UINT("input_sensor_orientation", &settings->uints.input_sensor_orientation, true, 0, false);
#else
SETTING_UINT("input_sensor_orientation", &settings->uints.input_sensor_orientation, true, 1, false);
SETTING_UINT("input_sensor_orientation", &settings->uints.input_sensor_orientation, true, 1, false);
#endif
#if defined(HAVE_OVERLAY)
+6
View File
@@ -453,6 +453,9 @@ typedef struct settings
unsigned nfs_readahead;
#endif
unsigned input_sensor_orientation;
#ifdef ANDROID
unsigned input_stylus_pressure_sensitivity;
#endif
} uints;
struct
@@ -1152,6 +1155,9 @@ typedef struct settings
#ifdef ANDROID
bool android_input_disconnect_workaround;
bool input_stylus_enable;
bool input_stylus_require_contact_for_click;
bool input_stylus_hover_moves_pointer;
#endif
#if defined(HAVE_COCOATOUCH)
+419 -13
View File
@@ -19,6 +19,7 @@
#include <errno.h>
#include <unistd.h>
#include <dlfcn.h>
#include <math.h>
#include <android/keycodes.h>
@@ -69,9 +70,16 @@ enum {
AMOTION_EVENT_BUTTON_TERTIARY = 1 << 2,
AMOTION_EVENT_BUTTON_BACK = 1 << 3,
AMOTION_EVENT_BUTTON_FORWARD = 1 << 4,
AMOTION_EVENT_BUTTON_STYLUS_PRIMARY = 1 << 5,
AMOTION_EVENT_BUTTON_STYLUS_SECONDARY = 1 << 6,
AMOTION_EVENT_AXIS_VSCROLL = 9,
AMOTION_EVENT_AXIS_DISTANCE = 24,
AMOTION_EVENT_ACTION_HOVER_MOVE = 7,
AINPUT_SOURCE_STYLUS = 0x00004000
AMOTION_EVENT_ACTION_HOVER_ENTER = 9,
AMOTION_EVENT_ACTION_HOVER_EXIT = 10,
AINPUT_SOURCE_STYLUS = 0x00004000,
AMOTION_EVENT_TOOL_TYPE_FINGER = 1,
AMOTION_EVENT_TOOL_TYPE_STYLUS = 2
};
#endif
/* If using an NDK lower than 16b then add missing definition */
@@ -105,6 +113,40 @@ enum {
* writers bound incoming keycodes, readers bound bind keysyms. */
static uint8_t android_key_state[DEFAULT_MAX_PADS + 1][MAX_KEYS];
/* Samsung S-Pen firmware emits phantom touchscreen DOWN events shortly
* after a hover ends. arm() records the last hover point and a deadline;
* drop() returns true for incoming touches inside that spatial/temporal
* window so the caller can discard them. */
static bool g_hover_guard_active = false;
static int64_t g_hover_guard_until_ms = 0;
static float g_hover_guard_x = 0.0f, g_hover_guard_y = 0.0f;
static void hover_guard_arm(float x, float y, int64_t now_ms, int guard_ms)
{
g_hover_guard_active = true;
g_hover_guard_until_ms = now_ms + guard_ms;
g_hover_guard_x = x;
g_hover_guard_y = y;
#ifdef DEBUG_ANDROID_INPUT
RARCH_LOG("[RA Input] Hover guard ARMED at (%.1f,%.1f) for %dms\n",
x, y, guard_ms);
#endif
}
static bool hover_guard_drop(float x, float y, int64_t now_ms, float eps_px)
{
if (!g_hover_guard_active)
return false;
if (now_ms > g_hover_guard_until_ms)
{
g_hover_guard_active = false;
return false;
}
return (fabsf(x - g_hover_guard_x) <= eps_px &&
fabsf(y - g_hover_guard_y) <= eps_px);
}
#define ANDROID_KEYBOARD_PORT_INPUT_PRESSED(binds, id) (BIT_GET(android_key_state[ANDROID_KEYBOARD_PORT], rarch_keysym_lut[RETRO_KEYBIND_KEY(&(binds)[(id)])]))
#define ANDROID_KEYBOARD_INPUT_PRESSED(key) (BIT_GET(android_key_state[0], (key)))
@@ -157,6 +199,12 @@ typedef struct state_device
typedef struct android_input
{
int64_t quick_tap_time;
/* 120ms window after any stylus event during which the touchscreen
* quick-tap emulation is suppressed. */
bool stylus_proximity_active;
int64_t stylus_proximity_until_ns;
bool stylus_contact_active; /* Tip has touched since the last UP */
bool stylus_press_active; /* The pen holds pointer 0 pressed */
state_device_t pad_states[MAX_USERS]; /* int alignment */
int mouse_x, mouse_y;
int16_t mouse_x_viewport_screen, mouse_y_viewport_screen;
@@ -173,10 +221,31 @@ typedef struct android_input
char device_model[256];
} android_input_t;
static void android_stylus_proximity_check_expire(android_input_t *android)
{
if (android->stylus_proximity_active)
{
retro_time_t now = cpu_features_get_time_usec();
if (now / 1000 > android->stylus_proximity_until_ns / 1000000)
android->stylus_proximity_active = false;
}
}
bool (*engine_lookup_name)(char *buf,
int *vendorId, int *productId, size_t len, int id);
void (*engine_handle_dpad)(struct android_app *, AInputEvent*, int, int);
/* Nanosecond timestamp of the most recent stylus event; queried by
* android_input_stylus_recently_active() so runloop.c can auto-hide the
* touch overlay while the S-Pen is in use. */
int64_t g_android_stylus_last_event_ns = 0;
bool android_input_stylus_recently_active(void)
{
int64_t now_ns = (int64_t)cpu_features_get_time_usec() * 1000;
return (now_ns - g_android_stylus_last_event_ns) < 2000000000LL; /* 2 s */
}
static void android_input_poll_input_gingerbread(android_input_t *android);
static void android_input_poll_input_default(android_input_t *android);
static void (*android_input_poll_input)(android_input_t *android);
@@ -202,6 +271,18 @@ static typeof(AMotionEvent_getButtonState) *p_AMotionEvent_getButtonState;
#define AMotionEvent_getButtonState (*p_AMotionEvent_getButtonState)
extern int32_t AMotionEvent_getToolType(const AInputEvent* motion_event, size_t pointer_index);
static typeof(AMotionEvent_getToolType) *p_AMotionEvent_getToolType;
#define AMotionEvent_getToolType (*p_AMotionEvent_getToolType)
extern float AMotionEvent_getPressure(const AInputEvent* motion_event, size_t pointer_index);
static typeof(AMotionEvent_getPressure) *p_AMotionEvent_getPressure;
#define AMotionEvent_getPressure (*p_AMotionEvent_getPressure)
#ifdef HAVE_DYLIB
static void *libandroid_handle;
#endif
@@ -1198,6 +1279,10 @@ static bool android_input_init_handle(void)
p_AMotionEvent_getButtonState = dlsym(RTLD_DEFAULT,
"AMotionEvent_getButtonState");
p_AMotionEvent_getToolType = dlsym(RTLD_DEFAULT,
"AMotionEvent_getToolType");
p_AMotionEvent_getPressure = dlsym(RTLD_DEFAULT,
"AMotionEvent_getPressure");
#endif
pad_id1 = -1;
@@ -1219,6 +1304,10 @@ static void *android_input_init(const char *joypad_driver)
android->mouse_activated = false;
android->pads_connected = 0;
android->quick_tap_time = 0;
android->stylus_proximity_active = false;
android->stylus_proximity_until_ns = 0;
android->stylus_contact_active = false;
android->stylus_press_active = false;
input_keymaps_init_keyboard_lut(rarch_key_map_android);
@@ -1288,6 +1377,14 @@ static void *android_input_init(const char *joypad_driver)
static int android_check_quick_tap(android_input_t *android)
{
/* Suppress quick-tap promotion while the hover guard is active: phantom
* touchscreen events after a stylus hover exit must not fire clicks. */
if (g_hover_guard_active)
{
android->quick_tap_time = 0;
return 0;
}
/* Check if the touch screen has been been quick tapped
* and then not touched again for 200ms
* If so then return true and deactivate quick tap timer */
@@ -1360,8 +1457,8 @@ static INLINE void android_mouse_calculate_deltas(android_input_t *android,
x = AMotionEvent_getX(event, motion_ptr);
y = AMotionEvent_getY(event, motion_ptr);
x_delta = (x_delta - android->mouse_x_prev);
y_delta = (y_delta - android->mouse_y_prev);
x_delta = (x - android->mouse_x_prev);
y_delta = (y - android->mouse_y_prev);
android->mouse_x_prev = x;
android->mouse_y_prev = y;
@@ -1390,23 +1487,273 @@ static INLINE void android_mouse_calculate_deltas(android_input_t *android,
android->mouse_y = y;
}
/* How a pen maps onto the libretro devices this driver already
* reports. Nothing here is pen-specific to a core:
*
* - Position: pointer 0. While hovering it moves without being
* pressed (input_stylus_hover_moves_pointer).
* - Press: pointer 0 pressed, pointer count 1. The tip pressing
* harder than input_stylus_pressure_sensitivity allows is a press;
* with input_stylus_require_contact_for_click off, so is the barrel
* button, touching or hovering.
* - Barrel button: the right mouse button.
*
* Pointer indices keep their meaning: index N is the Nth touch. A pen
* is one touch, so it never occupies more than index 0. Reporting the
* tip and the barrel button as further "touches" would read as a
* two- and three-finger touch to everything that counts pointers,
* this driver's own lightgun mapping included (two pointers are
* turbo, three are reload). */
/* The pen's position, written to pointer 0. */
static void android_stylus_set_position(android_input_t *android,
float x, float y)
{
struct video_viewport vp = {0};
/* The same pair of calls the touchscreen path makes: the confined
* variant for the pointer query, the plain one for the true
* offscreen value. */
video_driver_translate_coord_viewport_confined_wrap(
&vp, x, y,
&android->pointer[0].confined_x,
&android->pointer[0].confined_y,
&android->pointer[0].full_x,
&android->pointer[0].full_y);
video_driver_translate_coord_viewport_wrap(
&vp, x, y,
&android->pointer[0].x,
&android->pointer[0].y,
&android->pointer[0].full_x,
&android->pointer[0].full_y);
}
/* Press or release pointer 0 for the pen. pointer_count is shared
* with the touchscreen. A pressing pen takes it, as a touchscreen
* event does, and says so on every event so a finger lifting in
* between cannot leave it released. A pen that is not pressing leaves
* it alone, apart from letting go of its own press: a hover event
* must not release a finger. */
static void android_stylus_set_pressed(android_input_t *android,
bool pressed)
{
if (pressed)
android->pointer_count = 1;
else if (android->stylus_press_active)
android->pointer_count = 0;
android->stylus_press_active = pressed;
}
static INLINE void android_input_poll_event_type_motion(
android_input_t *android, AInputEvent *event,
int port, int source)
{
int getaction = AMotionEvent_getAction(event);
int action = getaction & AMOTION_EVENT_ACTION_MASK;
size_t motion_ptr = getaction >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
bool keyup = (
action == AMOTION_EVENT_ACTION_UP
|| action == AMOTION_EVENT_ACTION_CANCEL
|| action == AMOTION_EVENT_ACTION_POINTER_UP);
int getaction, action;
size_t motion_ptr;
int64_t event_time_ms;
float x, y;
int32_t tool_type;
bool is_stylus, is_finger, is_hover_action;
settings_t *settings;
bool require_contact, tip_down, stylus_pressed;
bool side_pressed, tip_touching;
float pressure, distance, pressure_threshold;
int buttons;
bool keyup;
getaction = AMotionEvent_getAction(event);
action = getaction & AMOTION_EVENT_ACTION_MASK;
motion_ptr = getaction >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
event_time_ms = AMotionEvent_getEventTime(event) / 1000000;
x = AMotionEvent_getX(event, motion_ptr);
y = AMotionEvent_getY(event, motion_ptr);
tool_type = 0;
if (p_AMotionEvent_getToolType && motion_ptr < AMotionEvent_getPointerCount(event))
tool_type = AMotionEvent_getToolType(event, motion_ptr);
is_stylus = (tool_type == AMOTION_EVENT_TOOL_TYPE_STYLUS);
is_finger = (tool_type == AMOTION_EVENT_TOOL_TYPE_FINGER);
/* Fall back to source bits if toolType is unknown. */
if (!is_stylus && !is_finger)
{
is_stylus = ((source & AINPUT_SOURCE_STYLUS) == AINPUT_SOURCE_STYLUS);
is_finger = ((source & AINPUT_SOURCE_TOUCHSCREEN) == AINPUT_SOURCE_TOUCHSCREEN);
}
is_hover_action = (action == AMOTION_EVENT_ACTION_HOVER_MOVE ||
action == AMOTION_EVENT_ACTION_HOVER_ENTER ||
action == AMOTION_EVENT_ACTION_HOVER_EXIT);
#ifdef DEBUG_ANDROID_INPUT
RARCH_LOG("[RA Input] act=%d src=0x%x tool=%d dev=%d btn=0x%x dropped=%d\n",
action, source, tool_type, AInputEvent_getDeviceId(event),
p_AMotionEvent_getButtonState ?
AMotionEvent_getButtonState(event) : 0, 0);
#endif
if (is_stylus)
{
settings = config_get_ptr();
if (!settings)
return;
if (motion_ptr >= MAX_TOUCH)
return;
if (!settings->bools.input_stylus_enable)
{
#ifdef DEBUG_ANDROID_INPUT
RARCH_LOG("[RA Input] Stylus support disabled - ignoring stylus event\n");
#endif
return;
}
/* Refresh overlay auto-hide timer (runloop.c reads this) only after the
* enable gate, so a passing pen does not hide the overlay for a user
* who has disabled S-Pen support. */
g_android_stylus_last_event_ns = (int64_t)cpu_features_get_time_usec() * 1000;
require_contact = settings->bools.input_stylus_require_contact_for_click;
buttons = p_AMotionEvent_getButtonState ?
AMotionEvent_getButtonState(event) : 0;
/* Which of the two the barrel button reports varies by device. */
side_pressed = (buttons & (AMOTION_EVENT_BUTTON_STYLUS_PRIMARY
| AMOTION_EVENT_BUTTON_STYLUS_SECONDARY)) != 0;
/* The barrel button is the pen's secondary button: report it
* the way a mouse's is (see the mapping above). */
android->mouse_r = side_pressed;
switch (action)
{
case AMOTION_EVENT_ACTION_HOVER_ENTER:
case AMOTION_EVENT_ACTION_HOVER_MOVE:
case AMOTION_EVENT_ACTION_HOVER_EXIT:
#ifdef DEBUG_ANDROID_INPUT
RARCH_LOG("[RA Input] Stylus hover (act=%d) - arming hover guard\n", action);
#endif
hover_guard_arm(x, y, event_time_ms, 100);
android->stylus_proximity_active = true;
android->stylus_proximity_until_ns =
AMotionEvent_getEventTime(event) + 120000000; /* 120ms */
android->quick_tap_time = 0;
if (action == AMOTION_EVENT_ACTION_HOVER_EXIT)
{
/* Out of range: nothing the pen held stays held. */
android->mouse_r = false;
android_stylus_set_pressed(android, false);
return;
}
/* A hovering pen presses only when the user allowed a click
* without contact and holds the barrel button. */
stylus_pressed = !require_contact && side_pressed;
if ( stylus_pressed
|| settings->bools.input_stylus_hover_moves_pointer)
android_stylus_set_position(android, x, y);
android_stylus_set_pressed(android, stylus_pressed);
return;
case AMOTION_EVENT_ACTION_UP:
case AMOTION_EVENT_ACTION_CANCEL:
/* A lifted pen keeps reporting its barrel button through
* the hover events that follow; a cancelled gesture has
* none coming. */
if (action == AMOTION_EVENT_ACTION_CANCEL)
android->mouse_r = false;
android->stylus_contact_active = false;
android_stylus_set_pressed(android, false);
#ifdef DEBUG_ANDROID_INPUT
RARCH_LOG("[Stylus] UP - pointer released\n");
#endif
return;
case AMOTION_EVENT_ACTION_DOWN:
case AMOTION_EVENT_ACTION_MOVE:
if (action == AMOTION_EVENT_ACTION_DOWN)
{
g_hover_guard_active = false;
android->stylus_proximity_active = false;
}
pressure = p_AMotionEvent_getPressure ?
AMotionEvent_getPressure(event, motion_ptr) : 1.0f;
distance = 0.0f;
if (p_AMotionEvent_getAxisValue &&
motion_ptr < AMotionEvent_getPointerCount(event))
distance = AMotionEvent_getAxisValue(event,
AMOTION_EVENT_AXIS_DISTANCE, motion_ptr);
/* Contact is distance-based (instant, no pressure needed);
* click is pressure-based against a user-configurable threshold. */
tip_touching = (distance <= 0.0f);
/* Map sensitivity 1..100 to threshold 0.02475..0.0 (higher = more
* sensitive; 100 = instant click on any pressure > 0). */
{
unsigned sens = settings->uints.input_stylus_pressure_sensitivity;
if (sens > 100)
sens = 100;
pressure_threshold = (100 - (int)sens) * 0.00025f;
}
tip_down = tip_touching && (pressure > pressure_threshold);
stylus_pressed = tip_down || (!require_contact && side_pressed);
#ifdef DEBUG_ANDROID_INPUT
RARCH_LOG("[RA Input] S Pen contact - req_contact:%s tip_down:%s "
"side:%s pressed:%s p=%.3f d=%.3f\n",
require_contact ? "Y" : "N", tip_down ? "Y" : "N",
side_pressed ? "Y" : "N", stylus_pressed ? "Y" : "N",
pressure, distance);
#endif
if (tip_touching)
android->stylus_contact_active = true;
/* Before the tip has touched, a DOWN or MOVE that still
* reports distance is a hover: the position stays where the
* last contact left it, so the cursor does not jump. */
if (android->stylus_contact_active || stylus_pressed)
android_stylus_set_position(android, x, y);
android_stylus_set_pressed(android, stylus_pressed);
return;
default:
break;
}
}
else
{
/* Drop phantom touches near a recent stylus hover (see hover_guard). */
if ((action == AMOTION_EVENT_ACTION_DOWN || action == AMOTION_EVENT_ACTION_MOVE) &&
hover_guard_drop(x, y, event_time_ms, 12.0f))
{
#ifdef DEBUG_ANDROID_INPUT
RARCH_LOG("[RA Input] DROPPED phantom touch near stylus hover (x=%.1f y=%.1f)\n", x, y);
#endif
return;
}
}
keyup = (action == AMOTION_EVENT_ACTION_UP ||
action == AMOTION_EVENT_ACTION_CANCEL ||
action == AMOTION_EVENT_ACTION_POINTER_UP);
/* If source is mouse then calculate button state
* and mouse deltas and don't process as touchscreen event.
* NOTE: AINPUT_SOURCE_* defines have multiple bits set so do full check */
if ( (source & AINPUT_SOURCE_MOUSE) == AINPUT_SOURCE_MOUSE
if (((source & AINPUT_SOURCE_MOUSE) == AINPUT_SOURCE_MOUSE
|| (source & AINPUT_SOURCE_MOUSE_RELATIVE) == AINPUT_SOURCE_MOUSE_RELATIVE)
&& !is_stylus)
{
if (!android->mouse_activated)
{
@@ -1441,7 +1788,18 @@ static INLINE void android_input_poll_event_type_motion(
}
android_mouse_calculate_deltas(android,event,motion_ptr,source);
return;
}
/* Drop hover events from touchscreen processing so they can't drive
* clicks. This has to come after the mouse branch above: a mouse
* moved with no button held reports its motion as hover events, and
* dropping those first would leave it unable to move. */
if (is_hover_action)
{
#ifdef DEBUG_ANDROID_INPUT
RARCH_LOG("[Android Input] Blocking hover event from touchscreen processing - action:%d source:0x%X\n", action, source);
#endif
return;
}
@@ -2873,7 +3231,31 @@ static int16_t android_input_state(
switch (id)
{
case RETRO_DEVICE_ID_MOUSE_LEFT:
return android->mouse_l || android_check_quick_tap(android);
if (android->mouse_activated)
{
#ifdef DEBUG_ANDROID_INPUT
RARCH_LOG("[Mouse Query] mouse_l=%d activated=%d\n",
android->mouse_l, android->mouse_activated);
#endif
return android->mouse_l;
}
else
{
/* Suppress quick-tap emulation while a stylus is nearby;
* only finger taps should trigger it. */
bool allow_quick_tap;
android_stylus_proximity_check_expire(android);
allow_quick_tap = !android->stylus_proximity_active
&& !g_hover_guard_active;
if (allow_quick_tap)
return android_check_quick_tap(android);
else
{
android->quick_tap_time = 0; /* cancel any pending quick tap when stylus nearby */
return 0;
}
}
case RETRO_DEVICE_ID_MOUSE_RIGHT:
return android->mouse_r;
case RETRO_DEVICE_ID_MOUSE_MIDDLE:
@@ -2944,7 +3326,25 @@ static int16_t android_input_state(
case RETRO_DEVICE_ID_LIGHTGUN_TURBO:
return android->mouse_r || android->pointer_count == 2;
case RETRO_DEVICE_ID_LIGHTGUN_TRIGGER:
return android->mouse_l || android_check_quick_tap(android) || android->pointer_count == 1;
if (android->mouse_activated)
return android->mouse_l || android->pointer_count == 1;
else
{
/* Suppress quick-tap emulation while a stylus is nearby;
* only finger taps should trigger it. */
bool allow_quick_tap;
android_stylus_proximity_check_expire(android);
allow_quick_tap = !android->stylus_proximity_active
&& !g_hover_guard_active;
if (allow_quick_tap)
return android_check_quick_tap(android) || android->pointer_count == 1;
else
{
android->quick_tap_time = 0; /* cancel any pending quick tap when stylus nearby */
return android->pointer_count == 1;
}
}
case RETRO_DEVICE_ID_LIGHTGUN_IS_OFFSCREEN:
if (idx >= MAX_TOUCH)
return 0;
@@ -2973,6 +3373,12 @@ static int16_t android_input_state(
return android->pointer[idx].full_y;
return android->pointer[idx].confined_y;
case RETRO_DEVICE_ID_POINTER_PRESSED:
#ifdef DEBUG_ANDROID_INPUT
RARCH_LOG("[PtrQuery] count=%d x0=%d y0=%d\n",
android->pointer_count,
android->pointer_count > 0 ? android->pointer[0].confined_x : 0,
android->pointer_count > 0 ? android->pointer[0].confined_y : 0);
#endif
/* On mobile platforms, touches outside screen / core viewport are not reported. */
if (device == RARCH_DEVICE_POINTER_SCREEN)
return (idx < android->pointer_count) &&
+27
View File
@@ -0,0 +1,27 @@
/* RetroArch - A frontend for libretro.
* Copyright (C) 2010-2014 - Hans-Kristian Arntzen
* Copyright (C) 2011-2017 - Daniel De Matteis
* Copyright (C) 2012-2015 - Michael Lelli
* Copyright (C) 2013-2014 - Steven Crowe
*
* RetroArch is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with RetroArch.
* If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __ANDROID_INPUT_H__
#define __ANDROID_INPUT_H__
#include <boolean.h>
/* True for ~2s after the last stylus event. */
bool android_input_stylus_recently_active(void);
#endif
+14 -4
View File
@@ -2405,6 +2405,7 @@ static int16_t input_state_device(
#ifdef HAVE_OVERLAY
if ( (input_st->overlay_ptr)
&& (input_st->overlay_ptr->flags & INPUT_OVERLAY_ENABLE)
&& !(input_st->overlay_ptr->flags & INPUT_OVERLAY_STYLUS_HIDDEN)
&& (settings->bools.input_overlay_pointer_enable))
res = input_overlay_pointing_device_state(
settings->ints.input_overlay_lightgun_port,
@@ -4433,8 +4434,14 @@ INPUT_NOINLINE static void input_poll_overlay(
/* input_data is dereferenced by the driver's pointer paths, so a
* NULL current_data (mid driver teardown/reinit, e.g. Android
* surface recreation) must skip driver input for this frame
* instead of faulting inside the input driver. */
if (input->input_state && input_data)
* instead of faulting inside the input driver.
*
* A stylus-hidden overlay reads no touches either: with none, the
* rest of this poll releases whatever a finger was holding (button,
* key, pointing device) the same way lifting it would. */
if ( input->input_state
&& input_data
&& !(ol->flags & INPUT_OVERLAY_STYLUS_HIDDEN))
{
rarch_joypad_info_t joypad_info;
unsigned device = (ol->active->flags & OVERLAY_FULL_SCREEN)
@@ -4723,8 +4730,11 @@ INPUT_NOINLINE static void input_poll_overlay(
(input_overlay_show_inputs == OVERLAY_SHOW_INPUT_TOUCHED),
input_overlay_show_inputs_port);
/* Block other touchscreen input as needed. */
if ( button_pressed
/* Block other touchscreen input as needed. A stylus-hidden overlay
* blocks nothing: the pen's pointer state has to reach the core. */
if (ol->flags & INPUT_OVERLAY_STYLUS_HIDDEN)
input_st->flags &= ~INP_FLAG_BLOCK_POINTER_INPUT;
else if (button_pressed
#if TARGET_OS_IPHONE
|| (ptr_state->device_mask & (1 << RETRO_DEVICE_LIGHTGUN))
|| (ol->flags & INPUT_OVERLAY_BLOCKED))
+6 -1
View File
@@ -124,7 +124,12 @@ enum INPUT_OVERLAY_FLAGS
INPUT_OVERLAY_TEXTURES_DECLINED = (1 << 5),
/* The pack names its LED images (overlayN_descM_led): the overlay
* LED driver shows and hides those, and ledN_map is not used. */
INPUT_OVERLAY_HAS_LEDS = (1 << 6)
INPUT_OVERLAY_HAS_LEDS = (1 << 6),
/* A stylus is in use: the overlay stays loaded but is not drawn,
* takes no touches and answers no pointer queries, so the pen
* reaches the core through the input driver. Level-triggered from
* the run loop. */
INPUT_OVERLAY_STYLUS_HIDDEN = (1 << 7)
};
enum OVERLAY_FLAGS
+2 -1
View File
@@ -133,7 +133,8 @@ void input_overlay_alpha_pass(input_overlay_t *ol, float mod,
if (!ol || !ol->active || !ol->iface->set_alpha)
return;
if (ol->flags & INPUT_OVERLAY_GAMEPAD_HIDDEN)
if (ol->flags & (INPUT_OVERLAY_GAMEPAD_HIDDEN
| INPUT_OVERLAY_STYLUS_HIDDEN))
mod = 0.0f;
n = ol->active->load_images_size;
want = (ol->alpha_want && n <= ol->alpha_cap) ? ol->alpha_want : NULL;
+16
View File
@@ -1379,6 +1379,22 @@ MSG_HASH(
MENU_ENUM_LABEL_INPUT_SELECT_PHYSICAL_KEYBOARD,
MENU_ENUM_LABEL_INPUT_SELECT_PHYSICAL_KEYBOARD_STR
)
MSG_HASH(
MENU_ENUM_LABEL_INPUT_STYLUS_ENABLE,
"input_stylus_enable"
)
MSG_HASH(
MENU_ENUM_LABEL_INPUT_STYLUS_REQUIRE_CONTACT_FOR_CLICK,
"input_stylus_require_contact_for_click"
)
MSG_HASH(
MENU_ENUM_LABEL_INPUT_STYLUS_HOVER_MOVES_POINTER,
"input_stylus_hover_moves_pointer"
)
MSG_HASH(
MENU_ENUM_LABEL_INPUT_STYLUS_PRESSURE_SENSITIVITY,
"input_stylus_pressure_sensitivity"
)
#endif
#ifdef ANDROID
MSG_HASH(
+32
View File
@@ -10678,6 +10678,38 @@ MSG_HASH(
MENU_ENUM_LABEL_HELP_INPUT_SELECT_PHYSICAL_KEYBOARD,
"If RetroArch identifies a hardware keyboard as some kind of gamepad, this setting can be used to force RetroArch to treat the misidentified device as a keyboard.\nThis can be useful if you are trying to emulate a computer in some Android TV device and also own a physical keyboard that can be attached to the box."
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_INPUT_STYLUS_ENABLE,
"Stylus Support"
)
MSG_HASH(
MENU_ENUM_SUBLABEL_INPUT_STYLUS_ENABLE,
"Enable stylus devices. Provides precise input for drawing and pointing games. Disable if experiencing input conflicts."
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_INPUT_STYLUS_REQUIRE_CONTACT_FOR_CLICK,
"Stylus Requires Screen Contact"
)
MSG_HASH(
MENU_ENUM_SUBLABEL_INPUT_STYLUS_REQUIRE_CONTACT_FOR_CLICK,
"ON: Stylus must physically touch screen to register as a click. OFF: Stylus can click by touching screen OR by hovering and pressing the side button."
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_INPUT_STYLUS_HOVER_MOVES_POINTER,
"Stylus Hover Moves Cursor"
)
MSG_HASH(
MENU_ENUM_SUBLABEL_INPUT_STYLUS_HOVER_MOVES_POINTER,
"Allow stylus hover (without touching screen) to move the cursor. Essential for drawing and painting games or lightgun games. Turn OFF if cursor moves unexpectedly."
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_INPUT_STYLUS_PRESSURE_SENSITIVITY,
"Stylus Pressure Sensitivity"
)
MSG_HASH(
MENU_ENUM_SUBLABEL_INPUT_STYLUS_PRESSURE_SENSITIVITY,
"Stylus touch sensitivity. Higher values = more sensitive (lighter touch). Default 70 works for most devices. Lower if using a screen protector (40-50)."
)
#endif
/* GENERATED REGION: analog deadzone and sensitivity group (see settings_def_analog_deadzone.h). */
#define SETTINGS_DEF_STRINGS_PASS
+16
View File
@@ -404,6 +404,10 @@ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_touch_vmouse_touchpad, MENU_
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_touch_vmouse_trackball, MENU_ENUM_SUBLABEL_INPUT_TOUCH_VMOUSE_TRACKBALL)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_touch_vmouse_gesture, MENU_ENUM_SUBLABEL_INPUT_TOUCH_VMOUSE_GESTURE)
#endif
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_stylus_enable, MENU_ENUM_SUBLABEL_INPUT_STYLUS_ENABLE)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_stylus_require_contact_for_click, MENU_ENUM_SUBLABEL_INPUT_STYLUS_REQUIRE_CONTACT_FOR_CLICK)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_stylus_hover_moves_pointer, MENU_ENUM_SUBLABEL_INPUT_STYLUS_HOVER_MOVES_POINTER)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_stylus_pressure_sensitivity, MENU_ENUM_SUBLABEL_INPUT_STYLUS_PRESSURE_SENSITIVITY)
#ifndef HAVE_DYNAMIC
@@ -3383,6 +3387,18 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs,
BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_input_touch_vmouse_gesture);
break;
#endif
case MENU_ENUM_LABEL_INPUT_STYLUS_ENABLE:
BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_input_stylus_enable);
break;
case MENU_ENUM_LABEL_INPUT_STYLUS_REQUIRE_CONTACT_FOR_CLICK:
BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_input_stylus_require_contact_for_click);
break;
case MENU_ENUM_LABEL_INPUT_STYLUS_HOVER_MOVES_POINTER:
BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_input_stylus_hover_moves_pointer);
break;
case MENU_ENUM_LABEL_INPUT_STYLUS_PRESSURE_SENSITIVITY:
BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_input_stylus_pressure_sensitivity);
break;
case MENU_ENUM_LABEL_AUDIO_MIXER_VOLUME:
#ifdef HAVE_AUDIOMIXER
BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_audio_mixer_volume);
+4
View File
@@ -9850,6 +9850,10 @@ unsigned menu_displaylist_build_list(
#endif
#ifdef ANDROID
{MENU_ENUM_LABEL_ANDROID_INPUT_DISCONNECT_WORKAROUND, PARSE_ONLY_BOOL, true},
{MENU_ENUM_LABEL_INPUT_STYLUS_ENABLE, PARSE_ONLY_BOOL, true},
{MENU_ENUM_LABEL_INPUT_STYLUS_REQUIRE_CONTACT_FOR_CLICK, PARSE_ONLY_BOOL, true},
{MENU_ENUM_LABEL_INPUT_STYLUS_HOVER_MOVES_POINTER, PARSE_ONLY_BOOL, true},
{MENU_ENUM_LABEL_INPUT_STYLUS_PRESSURE_SENSITIVITY, PARSE_ONLY_UINT, true},
{MENU_ENUM_LABEL_INPUT_BLOCK_TIMEOUT, PARSE_ONLY_UINT, true},
#endif
{MENU_ENUM_LABEL_INPUT_POLL_TYPE_BEHAVIOR, PARSE_ONLY_UINT, true},
+3 -2
View File
@@ -6532,8 +6532,9 @@ MENU_NOINLINE static int menu_input_post_iterate(
/* Normal menu input */
else
{
/* Detect gesture type */
if (!(menu_input->pointer.flags & MENU_INP_PTR_FLG_DRAGGED))
if (menu_input->pointer.type != MENU_POINTER_TOUCHSCREEN)
point.gesture = MENU_INPUT_GESTURE_NONE;
else if (!(menu_input->pointer.flags & MENU_INP_PTR_FLG_DRAGGED))
{
/* Pointer hasn't moved - check press duration */
if (menu_input->pointer.press_duration
+61
View File
@@ -15432,6 +15432,67 @@ static void settings_build_input(
general_write_handler,
general_read_handler,
SD_FLAG_NONE);
CONFIG_BOOL(
list, list_info,
&settings->bools.input_stylus_enable,
MENU_ENUM_LABEL_INPUT_STYLUS_ENABLE,
MENU_ENUM_LABEL_VALUE_INPUT_STYLUS_ENABLE,
true,
MENU_ENUM_LABEL_VALUE_OFF,
MENU_ENUM_LABEL_VALUE_ON,
&group_info,
&subgroup_info,
parent_group,
general_write_handler,
general_read_handler,
SD_FLAG_NONE);
CONFIG_BOOL(
list, list_info,
&settings->bools.input_stylus_require_contact_for_click,
MENU_ENUM_LABEL_INPUT_STYLUS_REQUIRE_CONTACT_FOR_CLICK,
MENU_ENUM_LABEL_VALUE_INPUT_STYLUS_REQUIRE_CONTACT_FOR_CLICK,
true,
MENU_ENUM_LABEL_VALUE_OFF,
MENU_ENUM_LABEL_VALUE_ON,
&group_info,
&subgroup_info,
parent_group,
general_write_handler,
general_read_handler,
SD_FLAG_NONE);
CONFIG_BOOL(
list, list_info,
&settings->bools.input_stylus_hover_moves_pointer,
MENU_ENUM_LABEL_INPUT_STYLUS_HOVER_MOVES_POINTER,
MENU_ENUM_LABEL_VALUE_INPUT_STYLUS_HOVER_MOVES_POINTER,
false,
MENU_ENUM_LABEL_VALUE_OFF,
MENU_ENUM_LABEL_VALUE_ON,
&group_info,
&subgroup_info,
parent_group,
general_write_handler,
general_read_handler,
SD_FLAG_NONE);
CONFIG_UINT(
list, list_info,
&settings->uints.input_stylus_pressure_sensitivity,
MENU_ENUM_LABEL_INPUT_STYLUS_PRESSURE_SENSITIVITY,
MENU_ENUM_LABEL_VALUE_INPUT_STYLUS_PRESSURE_SENSITIVITY,
DEFAULT_INPUT_STYLUS_PRESSURE_SENSITIVITY,
&group_info,
&subgroup_info,
parent_group,
general_write_handler,
general_read_handler);
SETTINGS_ACTION_SET(ok, &(*list)[list_info->index - 1], &setting_action_ok_uint)
SETTINGS_ACTION_SET(repr, &(*list)[list_info->index - 1], &setting_get_string_representation_max_users)
(*list)[list_info->index - 1].offset_by = 1;
menu_settings_list_current_add_range(list, list_info, 1, 100, 1, true, true);
#endif
#ifdef HAVE_SDL3
{
+12
View File
@@ -2149,6 +2149,10 @@ enum msg_hash_enums
MENU_ENUM_LABEL_INPUT_DEVICE_RESERVED_DEVICE_NAME_LAST = MENU_ENUM_LABEL_INPUT_DEVICE_RESERVED_DEVICE_NAME + MAX_USERS,
MENU_ENUM_LABEL_INPUT_MOUSE_INDEX,
MENU_ENUM_LABEL_INPUT_MOUSE_INDEX_LAST = MENU_ENUM_LABEL_INPUT_MOUSE_INDEX + MAX_USERS,
MENU_ENUM_LABEL_INPUT_STYLUS_ENABLE,
MENU_ENUM_LABEL_INPUT_STYLUS_REQUIRE_CONTACT_FOR_CLICK,
MENU_ENUM_LABEL_INPUT_STYLUS_HOVER_MOVES_POINTER,
MENU_ENUM_LABEL_INPUT_STYLUS_PRESSURE_SENSITIVITY,
MENU_ENUM_LABEL_INPUT_REMAP_PORT,
MENU_ENUM_LABEL_INPUT_REMAP_PORT_LAST = MENU_ENUM_LABEL_INPUT_REMAP_PORT + MAX_USERS,
@@ -2571,6 +2575,10 @@ enum msg_hash_enums
MENU_ENUM_LABEL_VALUE_INPUT_MOUSE_WHEEL_DOWN,
MENU_ENUM_LABEL_VALUE_INPUT_MOUSE_HORIZ_WHEEL_UP,
MENU_ENUM_LABEL_VALUE_INPUT_MOUSE_HORIZ_WHEEL_DOWN,
MENU_ENUM_LABEL_VALUE_INPUT_STYLUS_ENABLE,
MENU_ENUM_LABEL_VALUE_INPUT_STYLUS_REQUIRE_CONTACT_FOR_CLICK,
MENU_ENUM_LABEL_VALUE_INPUT_STYLUS_HOVER_MOVES_POINTER,
MENU_ENUM_LABEL_VALUE_INPUT_STYLUS_PRESSURE_SENSITIVITY,
MENU_ENUM_LABEL_VALUE_INPUT_ANALOG_LEFT_X_PLUS,
MENU_ENUM_LABEL_VALUE_INPUT_ANALOG_LEFT_X_MINUS,
MENU_ENUM_LABEL_VALUE_INPUT_ANALOG_LEFT_Y_PLUS,
@@ -2690,6 +2698,10 @@ enum msg_hash_enums
MENU_ENUM_SUBLABEL_INPUT_DEVICE_RESERVATION_TYPE,
MENU_ENUM_LABEL_HELP_INPUT_DEVICE_RESERVATION_TYPE,
MENU_ENUM_SUBLABEL_INPUT_MOUSE_INDEX,
MENU_ENUM_SUBLABEL_INPUT_STYLUS_ENABLE,
MENU_ENUM_SUBLABEL_INPUT_STYLUS_REQUIRE_CONTACT_FOR_CLICK,
MENU_ENUM_SUBLABEL_INPUT_STYLUS_HOVER_MOVES_POINTER,
MENU_ENUM_SUBLABEL_INPUT_STYLUS_PRESSURE_SENSITIVITY,
MENU_ENUM_SUBLABEL_INPUT_ADC_TYPE,
MENU_ENUM_LABEL_HELP_INPUT_ADC_TYPE,
MENU_ENUM_SUBLABEL_INPUT_BIND_ALL,
+24
View File
@@ -103,6 +103,7 @@
#if defined(ANDROID)
#include "play_feature_delivery/play_feature_delivery.h"
#include "frontend/drivers/platform_unix.h"
#include "input/drivers/android_input.h"
#endif
#if defined(ANDROID) && defined(HAVE_SAF)
@@ -6725,6 +6726,29 @@ static enum runloop_state_enum runloop_check_state(
last_controller_connected = controller_connected;
}
#if defined(ANDROID)
/* Hide the on-screen overlay while a stylus is in use, and bring
* it back once the pen has been idle for a moment (the window is
* android_input.c's). The overlay is hidden, not unloaded:
* unloading and reloading a pack is file and texture work, and a
* pen leaving and returning would trigger it over and over while
* a core runs. While hidden it takes no touches and answers no
* pointer queries (INPUT_OVERLAY_STYLUS_HIDDEN), so the pen
* reaches the core through the input driver whether or not the
* overlay is set up as a pointing device.
* Level-triggered, like the gamepad soft-hide above: the overlay
* can be replaced at any time, so the flag is set on whichever
* one is current this frame. */
if (input_st->overlay_ptr)
{
if ( settings->bools.input_stylus_enable
&& android_input_stylus_recently_active())
input_st->overlay_ptr->flags |= INPUT_OVERLAY_STYLUS_HIDDEN;
else
input_st->overlay_ptr->flags &= ~INPUT_OVERLAY_STYLUS_HIDDEN;
}
#endif
/* Check next overlay hotkey */
HOTKEY_CHECK(RARCH_OVERLAY_NEXT, CMD_EVENT_OVERLAY_NEXT, true, &check_next_rotation);
+4
View File
@@ -405,6 +405,9 @@ bool netplay_request_device_p13 1 0
bool netplay_request_device_p14 1 0
bool netplay_request_device_p15 1 0
bool netplay_request_device_p16 1 0
bool input_stylus_enable 1 1
bool input_stylus_require_contact_for_click 1 1
bool input_stylus_hover_moves_pointer 1 1
int custom_viewport_x 0 0
int custom_viewport_y 0 0
int video_windowed_position_x 1 0
@@ -588,6 +591,7 @@ uint video_stream_scale_factor 1 1
uint video_hdr_mode 1 0
uint video_swapchain_bit_depth 1 0
uint streaming_mode 1 STREAMING_MODE_TWITCH
uint input_stylus_pressure_sensitivity 1 70
uint input_sensor_orientation 1 0
uint input_overlay_show_inputs 1 OVERLAY_SHOW_INPUT_TOUCHED
uint input_overlay_show_inputs_port 1 0