From fafa543177b3c7be6d76f8d37f2801c190bb0877 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Tue, 14 Jan 2025 23:30:10 -0500 Subject: [PATCH] pen: Send virtual mouse and touch events for pen input. Fixes #11948. --- include/SDL3/SDL_hints.h | 30 ++++++++ include/SDL3/SDL_pen.h | 16 +++++ src/events/SDL_mouse.c | 30 ++++++++ src/events/SDL_mouse_c.h | 2 + src/events/SDL_pen.c | 100 ++++++++++++++++++++++++-- src/events/SDL_pen_c.h | 8 +-- src/events/SDL_touch.c | 26 ++++--- src/video/wayland/SDL_waylandevents.c | 2 +- 8 files changed, 191 insertions(+), 23 deletions(-) diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h index 0a4bc48f7dae0..a33b8174c3ef2 100644 --- a/include/SDL3/SDL_hints.h +++ b/include/SDL3/SDL_hints.h @@ -4185,6 +4185,36 @@ extern "C" { */ #define SDL_HINT_ASSERT "SDL_ASSERT" +/** + * A variable controlling whether pen events should generate synthetic mouse + * events. + * + * The variable can be set to the following values: + * + * - "0": Pen events will not generate mouse events. + * - "1": Pen events will generate mouse events. (default) + * + * This hint can be set anytime. + * + * \since This hint is available since SDL 3.2.0. + */ +#define SDL_HINT_PEN_MOUSE_EVENTS "SDL_PEN_MOUSE_EVENTS" + +/** + * A variable controlling whether pen events should generate synthetic touch + * events. + * + * The variable can be set to the following values: + * + * - "0": Pen events will not generate touch events. + * - "1": Pen events will generate touch events. (default) + * + * This hint can be set anytime. + * + * \since This hint is available since SDL 3.2.0. + */ +#define SDL_HINT_PEN_TOUCH_EVENTS "SDL_PEN_TOUCH_EVENTS" + /** * An enumeration of hint priorities. diff --git a/include/SDL3/SDL_pen.h b/include/SDL3/SDL_pen.h index 838be6d4f0447..61305905a13b2 100644 --- a/include/SDL3/SDL_pen.h +++ b/include/SDL3/SDL_pen.h @@ -40,6 +40,8 @@ #define SDL_pen_h_ #include +#include +#include /* Set up for C function definitions, even when using C++ */ #ifdef __cplusplus @@ -59,6 +61,20 @@ extern "C" { */ typedef Uint32 SDL_PenID; +/** + * The SDL_MouseID for mouse events simulated with pen input. + * + * \since This macro is available since SDL 3.1.3. + */ +#define SDL_PEN_MOUSEID ((SDL_MouseID)-2) + +/** + * The SDL_TouchID for touch events simulated with pen input. + * + * \since This macro is available since SDL 3.1.3. + */ +#define SDL_PEN_TOUCHID ((SDL_TouchID)-2) + /** * Pen input flags, as reported by various pen events' `pen_state` field. diff --git a/src/events/SDL_mouse.c b/src/events/SDL_mouse.c index becfe28e5d1f1..932cf13610c37 100644 --- a/src/events/SDL_mouse.c +++ b/src/events/SDL_mouse.c @@ -173,6 +173,24 @@ static void SDLCALL SDL_MouseTouchEventsChanged(void *userdata, const char *name } } +static void SDLCALL SDL_PenMouseEventsChanged(void *userdata, const char *name, const char *oldValue, const char *hint) +{ + SDL_Mouse *mouse = (SDL_Mouse *)userdata; + + mouse->pen_mouse_events = SDL_GetStringBoolean(hint, true); +} + +static void SDLCALL SDL_PenTouchEventsChanged(void *userdata, const char *name, const char *oldValue, const char *hint) +{ + SDL_Mouse *mouse = (SDL_Mouse *)userdata; + + mouse->pen_touch_events = SDL_GetStringBoolean(hint, true); + + if (mouse->pen_touch_events) { + SDL_AddTouch(SDL_PEN_TOUCHID, SDL_TOUCH_DEVICE_DIRECT, "pen_input"); + } +} + static void SDLCALL SDL_MouseAutoCaptureChanged(void *userdata, const char *name, const char *oldValue, const char *hint) { SDL_Mouse *mouse = (SDL_Mouse *)userdata; @@ -239,6 +257,12 @@ bool SDL_PreInitMouse(void) SDL_AddHintCallback(SDL_HINT_MOUSE_TOUCH_EVENTS, SDL_MouseTouchEventsChanged, mouse); + SDL_AddHintCallback(SDL_HINT_PEN_MOUSE_EVENTS, + SDL_PenMouseEventsChanged, mouse); + + SDL_AddHintCallback(SDL_HINT_PEN_TOUCH_EVENTS, + SDL_PenTouchEventsChanged, mouse); + SDL_AddHintCallback(SDL_HINT_MOUSE_AUTO_CAPTURE, SDL_MouseAutoCaptureChanged, mouse); @@ -1043,6 +1067,12 @@ void SDL_QuitMouse(void) SDL_RemoveHintCallback(SDL_HINT_MOUSE_TOUCH_EVENTS, SDL_MouseTouchEventsChanged, mouse); + SDL_RemoveHintCallback(SDL_HINT_PEN_MOUSE_EVENTS, + SDL_PenMouseEventsChanged, mouse); + + SDL_RemoveHintCallback(SDL_HINT_PEN_TOUCH_EVENTS, + SDL_PenTouchEventsChanged, mouse); + SDL_RemoveHintCallback(SDL_HINT_MOUSE_AUTO_CAPTURE, SDL_MouseAutoCaptureChanged, mouse); diff --git a/src/events/SDL_mouse_c.h b/src/events/SDL_mouse_c.h index 81dad3ace8b7f..01974d9a87590 100644 --- a/src/events/SDL_mouse_c.h +++ b/src/events/SDL_mouse_c.h @@ -118,6 +118,8 @@ typedef struct int double_click_radius; bool touch_mouse_events; bool mouse_touch_events; + bool pen_mouse_events; + bool pen_touch_events; bool was_touch_mouse_events; // Was a touch-mouse event pending? #ifdef SDL_PLATFORM_VITA Uint8 vita_touch_mouse_device; diff --git a/src/events/SDL_pen.c b/src/events/SDL_pen.c index 028d59b0d9241..3296e7bd687cf 100644 --- a/src/events/SDL_pen.c +++ b/src/events/SDL_pen.c @@ -26,6 +26,13 @@ #include "SDL_events_c.h" #include "SDL_pen_c.h" +#define SYNTHESIZE_PEN_TO_MOUSE 1 +#define SYNTHESIZE_PEN_TO_TOUCH 1 + +#if SYNTHESIZE_PEN_TO_MOUSE || SYNTHESIZE_PEN_TO_TOUCH +static SDL_PenID pen_touching = 0; +#endif + typedef struct SDL_Pen { SDL_PenID instance_id; @@ -309,7 +316,7 @@ void SDL_RemoveAllPenDevices(void (*callback)(SDL_PenID instance_id, void *handl SDL_UnlockRWLock(pen_device_rwlock); } -void SDL_SendPenTouch(Uint64 timestamp, SDL_PenID instance_id, const SDL_Window *window, bool eraser, bool down) +void SDL_SendPenTouch(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *window, bool eraser, bool down) { bool send_event = false; SDL_PenInputFlags input_state = 0; @@ -363,10 +370,54 @@ void SDL_SendPenTouch(Uint64 timestamp, SDL_PenID instance_id, const SDL_Window event.ptouch.down = down; SDL_PushEvent(&event); } + + #if SYNTHESIZE_PEN_TO_MOUSE || SYNTHESIZE_PEN_TO_TOUCH + SDL_Mouse *mouse = SDL_GetMouse(); + #endif + + #if SYNTHESIZE_PEN_TO_MOUSE + if (mouse && mouse->pen_mouse_events) { + if (window) { + if (down) { + if (!pen_touching) { + SDL_SendMouseMotion(timestamp, window, SDL_PEN_MOUSEID, false, x, y); + SDL_SendMouseButton(timestamp, window, SDL_PEN_MOUSEID, SDL_BUTTON_LEFT, true); + } + } else { + if (pen_touching == instance_id) { + SDL_SendMouseButton(timestamp, window, SDL_PEN_MOUSEID, SDL_BUTTON_LEFT, false); + } + } + } + } + #endif + + #if SYNTHESIZE_PEN_TO_TOUCH + if (mouse && mouse->pen_touch_events && window) { + const SDL_EventType touchtype = down ? SDL_EVENT_FINGER_DOWN : SDL_EVENT_FINGER_UP; + const float normalized_x = x / (float)window->w; + const float normalized_y = y / (float)window->h; + if (!pen_touching || (pen_touching == instance_id)) { + SDL_SendTouch(timestamp, SDL_PEN_TOUCHID, SDL_BUTTON_LEFT, window, touchtype, normalized_x, normalized_y, pen->axes[SDL_PEN_AXIS_PRESSURE]); + } + } + #endif + + #if SYNTHESIZE_PEN_TO_MOUSE || SYNTHESIZE_PEN_TO_TOUCH + if (down) { + if (!pen_touching) { + pen_touching = instance_id; + } + } else { + if (pen_touching == instance_id) { + pen_touching = 0; + } + } + #endif } } -void SDL_SendPenAxis(Uint64 timestamp, SDL_PenID instance_id, const SDL_Window *window, SDL_PenAxis axis, float value) +void SDL_SendPenAxis(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *window, SDL_PenAxis axis, float value) { SDL_assert((axis >= 0) && (axis < SDL_PEN_AXIS_COUNT)); // fix the backend if this triggers. @@ -405,10 +456,21 @@ void SDL_SendPenAxis(Uint64 timestamp, SDL_PenID instance_id, const SDL_Window * event.paxis.axis = axis; event.paxis.value = value; SDL_PushEvent(&event); + + #if SYNTHESIZE_PEN_TO_TOUCH // update virtual touch with latest pressure value. + if (window && (axis == SDL_PEN_AXIS_PRESSURE) && (pen_touching == instance_id)) { + SDL_Mouse *mouse = SDL_GetMouse(); + if (mouse && mouse->pen_touch_events) { + const float normalized_x = x / (float)window->w; + const float normalized_y = y / (float)window->h; + SDL_SendTouchMotion(timestamp, SDL_PEN_TOUCHID, SDL_BUTTON_LEFT, window, normalized_x, normalized_y, value); + } + } + #endif } } -void SDL_SendPenMotion(Uint64 timestamp, SDL_PenID instance_id, const SDL_Window *window, float x, float y) +void SDL_SendPenMotion(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *window, float x, float y) { bool send_event = false; SDL_PenInputFlags input_state = 0; @@ -440,10 +502,31 @@ void SDL_SendPenMotion(Uint64 timestamp, SDL_PenID instance_id, const SDL_Window event.pmotion.x = x; event.pmotion.y = y; SDL_PushEvent(&event); + + #if SYNTHESIZE_PEN_TO_MOUSE || SYNTHESIZE_PEN_TO_TOUCH + if (window && (pen_touching == instance_id)) { + SDL_Mouse *mouse = SDL_GetMouse(); + if (mouse) { + #if SYNTHESIZE_PEN_TO_MOUSE + if (mouse->pen_mouse_events) { + SDL_SendMouseMotion(timestamp, window, SDL_PEN_MOUSEID, false, x, y); + } + #endif + + #if SYNTHESIZE_PEN_TO_TOUCH + if (mouse->pen_touch_events) { + const float normalized_x = x / (float)window->w; + const float normalized_y = y / (float)window->h; + SDL_SendTouchMotion(timestamp, SDL_PEN_TOUCHID, SDL_BUTTON_LEFT, window, normalized_x, normalized_y, pen->axes[SDL_PEN_AXIS_PRESSURE]); + } + #endif + } + } + #endif } } -void SDL_SendPenButton(Uint64 timestamp, SDL_PenID instance_id, const SDL_Window *window, Uint8 button, bool down) +void SDL_SendPenButton(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *window, Uint8 button, bool down) { bool send_event = false; SDL_PenInputFlags input_state = 0; @@ -492,6 +575,15 @@ void SDL_SendPenButton(Uint64 timestamp, SDL_PenID instance_id, const SDL_Window event.pbutton.button = button; event.pbutton.down = down; SDL_PushEvent(&event); + + #if SYNTHESIZE_PEN_TO_MOUSE + if (window && (pen_touching == instance_id)) { + SDL_Mouse *mouse = SDL_GetMouse(); + if (mouse) { + SDL_SendMouseButton(timestamp, window, SDL_PEN_MOUSEID, button + 1, down); + } + } + #endif } } } diff --git a/src/events/SDL_pen_c.h b/src/events/SDL_pen_c.h index 7175483a53861..1eff47f230716 100644 --- a/src/events/SDL_pen_c.h +++ b/src/events/SDL_pen_c.h @@ -67,16 +67,16 @@ extern void SDL_RemovePenDevice(Uint64 timestamp, SDL_PenID instance_id); extern void SDL_RemoveAllPenDevices(void (*callback)(SDL_PenID instance_id, void *handle, void *userdata), void *userdata); // Backend calls this when a pen's button changes, to generate events and update state. -extern void SDL_SendPenTouch(Uint64 timestamp, SDL_PenID instance_id, const SDL_Window *window, bool eraser, bool down); +extern void SDL_SendPenTouch(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *window, bool eraser, bool down); // Backend calls this when a pen moves on the tablet, to generate events and update state. -extern void SDL_SendPenMotion(Uint64 timestamp, SDL_PenID instance_id, const SDL_Window *window, float x, float y); +extern void SDL_SendPenMotion(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *window, float x, float y); // Backend calls this when a pen's axis changes, to generate events and update state. -extern void SDL_SendPenAxis(Uint64 timestamp, SDL_PenID instance_id, const SDL_Window *window, SDL_PenAxis axis, float value); +extern void SDL_SendPenAxis(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *window, SDL_PenAxis axis, float value); // Backend calls this when a pen's button changes, to generate events and update state. -extern void SDL_SendPenButton(Uint64 timestamp, SDL_PenID instance_id, const SDL_Window *window, Uint8 button, bool down); +extern void SDL_SendPenButton(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *window, Uint8 button, bool down); // Backend can optionally use this to find the SDL_PenID for the `handle` that was passed to SDL_AddPenDevice. extern SDL_PenID SDL_FindPenByHandle(void *handle); diff --git a/src/events/SDL_touch.c b/src/events/SDL_touch.c index de9b6915a78bb..4d21665e1fed3 100644 --- a/src/events/SDL_touch.c +++ b/src/events/SDL_touch.c @@ -257,7 +257,6 @@ static void SDL_DelFinger(SDL_Touch *touch, SDL_FingerID fingerid) void SDL_SendTouch(Uint64 timestamp, SDL_TouchID id, SDL_FingerID fingerid, SDL_Window *window, SDL_EventType type, float x, float y, float pressure) { SDL_Finger *finger; - SDL_Mouse *mouse; bool down = (type == SDL_EVENT_FINGER_DOWN); SDL_Touch *touch = SDL_GetTouch(id); @@ -265,19 +264,19 @@ void SDL_SendTouch(Uint64 timestamp, SDL_TouchID id, SDL_FingerID fingerid, SDL_ return; } - mouse = SDL_GetMouse(); + SDL_Mouse *mouse = SDL_GetMouse(); #if SYNTHESIZE_TOUCH_TO_MOUSE // SDL_HINT_TOUCH_MOUSE_EVENTS: controlling whether touch events should generate synthetic mouse events // SDL_HINT_VITA_TOUCH_MOUSE_DEVICE: controlling which touchpad should generate synthetic mouse events, PSVita-only { + // FIXME: maybe we should only restrict to a few SDL_TouchDeviceType + if ((id != SDL_MOUSE_TOUCHID) && (id != SDL_PEN_TOUCHID)) { #ifdef SDL_PLATFORM_VITA - if (mouse->touch_mouse_events && ((mouse->vita_touch_mouse_device == id) || (mouse->vita_touch_mouse_device == 2))) { + if (mouse->touch_mouse_events && ((mouse->vita_touch_mouse_device == id) || (mouse->vita_touch_mouse_device == 2))) { #else - if (mouse->touch_mouse_events) { + if (mouse->touch_mouse_events) { #endif - // FIXME: maybe we should only restrict to a few SDL_TouchDeviceType - if (id != SDL_MOUSE_TOUCHID) { if (window) { if (down) { if (finger_touching == false) { @@ -321,10 +320,10 @@ void SDL_SendTouch(Uint64 timestamp, SDL_TouchID id, SDL_FingerID fingerid, SDL_ #endif // SDL_HINT_MOUSE_TOUCH_EVENTS: if not set, discard synthetic touch events coming from platform layer - if (mouse->mouse_touch_events == 0) { - if (id == SDL_MOUSE_TOUCHID) { - return; - } + if (!mouse->mouse_touch_events && (id == SDL_MOUSE_TOUCHID)) { + return; + } else if (!mouse->pen_touch_events && (id == SDL_PEN_TOUCHID)) { + return; } finger = SDL_GetFinger(touch, fingerid); @@ -384,7 +383,6 @@ void SDL_SendTouchMotion(Uint64 timestamp, SDL_TouchID id, SDL_FingerID fingerid { SDL_Touch *touch; SDL_Finger *finger; - SDL_Mouse *mouse; float xrel, yrel, prel; touch = SDL_GetTouch(id); @@ -392,13 +390,13 @@ void SDL_SendTouchMotion(Uint64 timestamp, SDL_TouchID id, SDL_FingerID fingerid return; } - mouse = SDL_GetMouse(); + SDL_Mouse *mouse = SDL_GetMouse(); #if SYNTHESIZE_TOUCH_TO_MOUSE // SDL_HINT_TOUCH_MOUSE_EVENTS: controlling whether touch events should generate synthetic mouse events { - if (mouse->touch_mouse_events) { - if (id != SDL_MOUSE_TOUCHID) { + if ((id != SDL_MOUSE_TOUCHID) && (id != SDL_PEN_TOUCHID)) { + if (mouse->touch_mouse_events) { if (window) { if (finger_touching == true && track_touchid == id && track_fingerid == fingerid) { float pos_x = (x * (float)window->w); diff --git a/src/video/wayland/SDL_waylandevents.c b/src/video/wayland/SDL_waylandevents.c index 36e2933970763..03982fbad4109 100644 --- a/src/video/wayland/SDL_waylandevents.c +++ b/src/video/wayland/SDL_waylandevents.c @@ -2957,7 +2957,7 @@ static void tablet_tool_handle_frame(void *data, struct zwp_tablet_tool_v2 *tool const Uint64 timestamp = Wayland_GetEventTimestamp(SDL_MS_TO_NS(time)); const SDL_PenID instance_id = sdltool->instance_id; - const SDL_Window *window = sdltool->tool_focus; + SDL_Window *window = sdltool->tool_focus; // I don't know if this is necessary (or makes sense), but send motion before pen downs, but after pen ups, so you don't get unexpected lines drawn. if (sdltool->frame_motion_set && (sdltool->frame_pen_down != -1)) {