diff --git a/input/drivers/sdl3_input.c b/input/drivers/sdl3_input.c index f10fbf7cdfcd..ded7d61b332a 100644 --- a/input/drivers/sdl3_input.c +++ b/input/drivers/sdl3_input.c @@ -75,6 +75,19 @@ typedef struct sdl3_input float x; float y; } touches[SDL3_MAX_TOUCH]; + + /* Pen/stylus state, handled through SDL_EVENT_PEN_*. */ + bool pen_in_proximity; + bool pen_down; + /* Last reported position in window coordinates (points). */ + float pen_raw_x; + float pen_raw_y; + /* Position in output pixels, matching mouse_abs_*. */ + float pen_abs_x; + float pen_abs_y; + + /* The SDL_Window input is read against. */ + SDL_Window *window; } sdl3_input_t; /* Rebuilt on SDL_EVENT_KEYMAP_CHANGED (e.g. system layout switch). */ @@ -284,7 +297,13 @@ static int16_t sdl3_input_state( int16_t pressed = 0; if (id == RETRO_DEVICE_ID_POINTER_COUNT) - return sdl->num_touches ? sdl->num_touches : (sdl->mouse_l ? 1 : 0); + { + if (sdl->num_touches) + return sdl->num_touches; + if (sdl->pen_in_proximity) + return sdl->pen_down ? 1 : 0; + return sdl->mouse_l ? 1 : 0; + } if (!video_driver_get_viewport_info(&vp)) break; @@ -301,6 +320,18 @@ static int16_t sdl3_input_state( abs_y = (int)(sdl->touches[idx].y * (float)vp.full_height); pressed = 1; } + else if (sdl->pen_in_proximity) + { + /* Reading the pen ahead of the mouse fallback dedups + * the mouse state SDL synthesizes from the pen; a real + * mouse click during pen hover is indistinguishable + * from that and reads as unpressed. */ + if (idx != 0) + return 0; + abs_x = (int)sdl->pen_abs_x; + abs_y = (int)sdl->pen_abs_y; + pressed = sdl->pen_down; + } else { if (idx != 0) @@ -489,9 +520,24 @@ static SDL_Window *sdl3_input_window(void) return NULL; } +/* SDL reports mouse and pen coordinates in window coordinates, + * while the video driver's viewport metrics are in output + * pixels. */ +static float sdl3_window_pixel_density(sdl3_input_t *sdl) +{ + if (sdl->window) + { + float density = SDL_GetWindowPixelDensity(sdl->window); + if (density > 0.0f) + return density; + } + + return 1.0f; +} + static void sdl3_poll_mouse(sdl3_input_t *sdl) { - SDL_Window *win; + float density; float dx = 0.0f; float dy = 0.0f; SDL_MouseButtonFlags btn = SDL_GetMouseState(&sdl->mouse_abs_x, &sdl->mouse_abs_y); @@ -513,20 +559,9 @@ static void sdl3_poll_mouse(sdl3_input_t *sdl) sdl->mouse_rel_x -= (float)sdl->mouse_x; sdl->mouse_rel_y -= (float)sdl->mouse_y; - /* SDL reports mouse coordinates in window coordinates (points), - * while the video driver's viewport metrics are in output pixels. */ - if (!(win = sdl3_input_window())) - win = SDL_GetMouseFocus(); - - if (win) - { - float density = SDL_GetWindowPixelDensity(win); - if (density > 0.0f && density != 1.0f) - { - sdl->mouse_abs_x *= density; - sdl->mouse_abs_y *= density; - } - } + density = sdl3_window_pixel_density(sdl); + sdl->mouse_abs_x *= density; + sdl->mouse_abs_y *= density; sdl->mouse_l = (SDL_BUTTON_MASK(SDL_BUTTON_LEFT) & btn) != 0; sdl->mouse_r = (SDL_BUTTON_MASK(SDL_BUTTON_RIGHT) & btn) != 0; @@ -568,6 +603,10 @@ static void sdl3_poll_touch(sdl3_input_t *sdl) int j, num_fingers = 0; SDL_Finger **fingers; + /* Pen events are read elsewhere. */ + if (devices[i] == SDL_PEN_TOUCHID) + continue; + /* Only SDL_TOUCH_DEVICE_DIRECT is a touchscreen. The two indirect * types are trackpads, whose fingers are device or cursor-relative. */ if (SDL_GetTouchDeviceType(devices[i]) != SDL_TOUCH_DEVICE_DIRECT) @@ -598,6 +637,48 @@ static void sdl3_poll_touch(sdl3_input_t *sdl) sdl->num_touch_devices = num_direct; } +/* Polls the pen events. */ +static void sdl3_poll_pen(sdl3_input_t *sdl) +{ + SDL_Event event; + float density; + + while (SDL_PeepEvents(&event, 1, SDL_GETEVENT, + SDL_EVENT_PEN_PROXIMITY_IN, SDL_EVENT_PEN_AXIS) > 0) + { + switch (event.type) + { + case SDL_EVENT_PEN_PROXIMITY_IN: + sdl->pen_in_proximity = true; + break; + case SDL_EVENT_PEN_PROXIMITY_OUT: + sdl->pen_in_proximity = false; + sdl->pen_down = false; + break; + case SDL_EVENT_PEN_DOWN: + case SDL_EVENT_PEN_UP: + sdl->pen_in_proximity = true; + sdl->pen_raw_x = event.ptouch.x; + sdl->pen_raw_y = event.ptouch.y; + sdl->pen_down = event.ptouch.down; + break; + case SDL_EVENT_PEN_MOTION: + sdl->pen_in_proximity = true; + sdl->pen_raw_x = event.pmotion.x; + sdl->pen_raw_y = event.pmotion.y; + break; + } + } + + /* If the pen isn't in proximity, skip calculating its position. */ + if (!sdl->pen_in_proximity) + return; + + density = sdl3_window_pixel_density(sdl); + sdl->pen_abs_x = sdl->pen_raw_x * density; + sdl->pen_abs_y = sdl->pen_raw_y * density; +} + /* Translates an SDL_Keymod to a RETROKMOD. */ static uint16_t sdl3_translate_mod(SDL_Keymod smod) { @@ -673,6 +754,11 @@ static void sdl3_input_poll(void *data) * never updates. */ SDL_PumpEvents(); + /* Find the SDL window, so that window coordinates can be calculated + * properly. */ + if (!(sdl->window = sdl3_input_window())) + sdl->window = SDL_GetMouseFocus(); + sdl3_poll_mouse(sdl); sdl3_poll_touch(sdl); @@ -734,14 +820,12 @@ static void sdl3_input_poll(void *data) sdl3_build_scancode_lut(sdl); } - /* Neither range is consumed anywhere: sdl3_poll_touch reads finger - * state by polling instead of by event, and pens aren't wired up at - * all. Both fire at device rate for as long as there's contact, so - * left in the queue they grow until SDL's queue fills and starts - * refusing pushes - at which point the events that do matter (quit, - * keys) get dropped along with them. */ - SDL_FlushEvents(SDL_EVENT_FINGER_DOWN, SDL_EVENT_FINGER_CANCELED); - SDL_FlushEvents(SDL_EVENT_PEN_PROXIMITY_IN, SDL_EVENT_PEN_AXIS); + /* Fingers are reported as pointer input from polled state + * (sdl3_poll_touch / SDL_GetTouchFingers), rather than these + * events, so flush the finger events. */ + SDL_FlushEvents(SDL_EVENT_FINGER_DOWN, SDL_EVENT_FINGER_CANCELED); + + sdl3_poll_pen(sdl); } static void sdl3_grab_mouse(void *data, bool state)