pen: Offer the current window during promixity events on most platforms.

Fixes #12356.
This commit is contained in:
Ryan C. Gordon
2025-11-10 16:07:07 -05:00
parent 7073cfc58e
commit 25ab8c99df
10 changed files with 34 additions and 26 deletions

View File

@@ -818,6 +818,9 @@ typedef struct SDL_PinchFingerEvent
* is there." The pen touching and lifting off from the tablet while not * is there." The pen touching and lifting off from the tablet while not
* leaving the area are handled by SDL_EVENT_PEN_DOWN and SDL_EVENT_PEN_UP. * leaving the area are handled by SDL_EVENT_PEN_DOWN and SDL_EVENT_PEN_UP.
* *
* Not all platforms have a window associated with the pen during proximity
* events. Some wait until motion/button/etc events to offer this info.
*
* \since This struct is available since SDL 3.2.0. * \since This struct is available since SDL 3.2.0.
*/ */
typedef struct SDL_PenProximityEvent typedef struct SDL_PenProximityEvent

View File

@@ -218,7 +218,7 @@ SDL_PenCapabilityFlags SDL_GetPenCapabilityFromAxis(SDL_PenAxis axis)
return 0; // oh well. return 0; // oh well.
} }
SDL_PenID SDL_AddPenDevice(Uint64 timestamp, const char *name, const SDL_PenInfo *info, void *handle) SDL_PenID SDL_AddPenDevice(Uint64 timestamp, const char *name, SDL_Window *window, const SDL_PenInfo *info, void *handle)
{ {
SDL_assert(handle != NULL); // just allocate a Uint8 so you have a unique pointer if not needed! SDL_assert(handle != NULL); // just allocate a Uint8 so you have a unique pointer if not needed!
SDL_assert(SDL_FindPenByHandle(handle) == 0); // Backends shouldn't double-add pens! SDL_assert(SDL_FindPenByHandle(handle) == 0); // Backends shouldn't double-add pens!
@@ -262,13 +262,14 @@ SDL_PenID SDL_AddPenDevice(Uint64 timestamp, const char *name, const SDL_PenInfo
event.pproximity.type = SDL_EVENT_PEN_PROXIMITY_IN; event.pproximity.type = SDL_EVENT_PEN_PROXIMITY_IN;
event.pproximity.timestamp = timestamp; event.pproximity.timestamp = timestamp;
event.pproximity.which = result; event.pproximity.which = result;
event.pproximity.windowID = window ? window->id : 0;
SDL_PushEvent(&event); SDL_PushEvent(&event);
} }
return result; return result;
} }
void SDL_RemovePenDevice(Uint64 timestamp, SDL_PenID instance_id) void SDL_RemovePenDevice(Uint64 timestamp, SDL_Window *window, SDL_PenID instance_id)
{ {
if (!instance_id) { if (!instance_id) {
return; return;
@@ -306,6 +307,7 @@ void SDL_RemovePenDevice(Uint64 timestamp, SDL_PenID instance_id)
event.pproximity.type = SDL_EVENT_PEN_PROXIMITY_OUT; event.pproximity.type = SDL_EVENT_PEN_PROXIMITY_OUT;
event.pproximity.timestamp = timestamp; event.pproximity.timestamp = timestamp;
event.pproximity.which = instance_id; event.pproximity.which = instance_id;
event.pproximity.windowID = window ? window->id : 0;
SDL_PushEvent(&event); SDL_PushEvent(&event);
} }
} }

View File

@@ -61,10 +61,10 @@ typedef struct SDL_PenInfo
// Backend calls this when a new pen device is hotplugged, plus once for each pen already connected at startup. // Backend calls this when a new pen device is hotplugged, plus once for each pen already connected at startup.
// Note that name and info are copied but currently unused; this is placeholder for a potentially more robust API later. // Note that name and info are copied but currently unused; this is placeholder for a potentially more robust API later.
// Both are allowed to be NULL. // Both are allowed to be NULL.
extern SDL_PenID SDL_AddPenDevice(Uint64 timestamp, const char *name, const SDL_PenInfo *info, void *handle); extern SDL_PenID SDL_AddPenDevice(Uint64 timestamp, const char *name, SDL_Window *window, const SDL_PenInfo *info, void *handle);
// Backend calls this when an existing pen device is disconnected during runtime. They must free their own stuff separately. // Backend calls this when an existing pen device is disconnected during runtime. They must free their own stuff separately.
extern void SDL_RemovePenDevice(Uint64 timestamp, SDL_PenID instance_id); extern void SDL_RemovePenDevice(Uint64 timestamp, SDL_Window *window, SDL_PenID instance_id);
// Backend can call this to remove all pens, probably during shutdown, with a callback to let them free their own handle. // Backend can call this to remove all pens, probably during shutdown, with a callback to let them free their own handle.
extern void SDL_RemoveAllPenDevices(void (*callback)(SDL_PenID instance_id, void *handle, void *userdata), void *userdata); extern void SDL_RemoveAllPenDevices(void (*callback)(SDL_PenID instance_id, void *handle, void *userdata), void *userdata);

View File

@@ -51,7 +51,7 @@ void Android_OnPen(SDL_Window *window, int pen_id_in, SDL_PenDeviceType device_t
peninfo.num_buttons = 2; peninfo.num_buttons = 2;
peninfo.subtype = SDL_PEN_TYPE_PEN; peninfo.subtype = SDL_PEN_TYPE_PEN;
peninfo.device_type = device_type; peninfo.device_type = device_type;
pen = SDL_AddPenDevice(0, NULL, &peninfo, (void *) (size_t) pen_id_in); pen = SDL_AddPenDevice(0, NULL, window, &peninfo, (void *) (size_t) pen_id_in);
if (!pen) { if (!pen) {
SDL_Log("error: can't add a pen device %d", pen_id_in); SDL_Log("error: can't add a pen device %d", pen_id_in);
return; return;
@@ -78,7 +78,7 @@ void Android_OnPen(SDL_Window *window, int pen_id_in, SDL_PenDeviceType device_t
switch (action) { switch (action) {
case ACTION_CANCEL: case ACTION_CANCEL:
case ACTION_HOVER_EXIT: case ACTION_HOVER_EXIT:
SDL_RemovePenDevice(0, pen); SDL_RemovePenDevice(0, window, pen);
break; break;
case ACTION_DOWN: case ACTION_DOWN:

View File

@@ -105,14 +105,14 @@ static void Cocoa_HandlePenProximityEvent(SDL_CocoaWindowData *_data, NSEvent *e
handle->deviceid = devid; handle->deviceid = devid;
handle->toolid = toolid; handle->toolid = toolid;
handle->is_eraser = is_eraser; handle->is_eraser = is_eraser;
handle->pen = SDL_AddPenDevice(Cocoa_GetEventTimestamp([event timestamp]), NULL, &peninfo, handle); handle->pen = SDL_AddPenDevice(Cocoa_GetEventTimestamp([event timestamp]), NULL, _data.window, &peninfo, handle);
if (!handle->pen) { if (!handle->pen) {
SDL_free(handle); // oh well. SDL_free(handle); // oh well.
} }
} else { // old pen leaving! } else { // old pen leaving!
Cocoa_PenHandle *handle = Cocoa_FindPenByDeviceID(devid, toolid); Cocoa_PenHandle *handle = Cocoa_FindPenByDeviceID(devid, toolid);
if (handle) { if (handle) {
SDL_RemovePenDevice(Cocoa_GetEventTimestamp([event timestamp]), handle->pen); SDL_RemovePenDevice(Cocoa_GetEventTimestamp([event timestamp]), _data.window, handle->pen);
SDL_free(handle); SDL_free(handle);
} }
} }

View File

@@ -855,7 +855,7 @@ static void Emscripten_HandlePenEnter(SDL_WindowData *window_data, const Emscrip
peninfo.max_tilt = 90.0f; peninfo.max_tilt = 90.0f;
peninfo.num_buttons = 2; peninfo.num_buttons = 2;
peninfo.subtype = SDL_PEN_TYPE_PEN; peninfo.subtype = SDL_PEN_TYPE_PEN;
SDL_AddPenDevice(0, NULL, &peninfo, (void *) (size_t) event->pointerid); SDL_AddPenDevice(0, NULL, window_data->window, &peninfo, (void *) (size_t) event->pointerid);
Emscripten_UpdatePenFromEvent(window_data, event); Emscripten_UpdatePenFromEvent(window_data, event);
} }
@@ -878,7 +878,7 @@ static void Emscripten_HandlePenLeave(SDL_WindowData *window_data, const Emscrip
const SDL_PenID pen = SDL_FindPenByHandle((void *) (size_t) event->pointerid); const SDL_PenID pen = SDL_FindPenByHandle((void *) (size_t) event->pointerid);
if (pen) { if (pen) {
Emscripten_UpdatePointerFromEvent(window_data, event); // last data updates? Emscripten_UpdatePointerFromEvent(window_data, event); // last data updates?
SDL_RemovePenDevice(0, pen); SDL_RemovePenDevice(0, window_data->window, pen);
} }
} }

View File

@@ -63,7 +63,7 @@ bool UIKit_InitPen(SDL_VideoDevice *_this)
// we only have one Apple Pencil at a time, and it must be paired to the iOS device. // we only have one Apple Pencil at a time, and it must be paired to the iOS device.
// We only know about its existence when it first sends an event, so add an single SDL pen // We only know about its existence when it first sends an event, so add an single SDL pen
// device here if we haven't already. // device here if we haven't already.
static SDL_PenID UIKit_AddPenIfNecesary() static SDL_PenID UIKit_AddPenIfNecesary(SDL_Window *window)
{ {
if (!apple_pencil_id) { if (!apple_pencil_id) {
SDL_PenInfo info; SDL_PenInfo info;
@@ -86,7 +86,7 @@ static SDL_PenID UIKit_AddPenIfNecesary()
// so we can't use it for tangential pressure. // so we can't use it for tangential pressure.
// There's only ever one Apple Pencil at most, so we just pass a non-zero value for the handle. // There's only ever one Apple Pencil at most, so we just pass a non-zero value for the handle.
apple_pencil_id = SDL_AddPenDevice(0, "Apple Pencil", &info, (void *) (size_t) 0x1); apple_pencil_id = SDL_AddPenDevice(0, "Apple Pencil", window, &info, (void *) (size_t) 0x1);
} }
return apple_pencil_id; return apple_pencil_id;
@@ -95,7 +95,7 @@ static SDL_PenID UIKit_AddPenIfNecesary()
static void UIKit_HandlePenAxes(SDL_Window *window, NSTimeInterval nstimestamp, float zOffset, const CGPoint *point, float force, static void UIKit_HandlePenAxes(SDL_Window *window, NSTimeInterval nstimestamp, float zOffset, const CGPoint *point, float force,
float maximumPossibleForce, float azimuthAngleInView, float altitudeAngle, float rollAngle) float maximumPossibleForce, float azimuthAngleInView, float altitudeAngle, float rollAngle)
{ {
const SDL_PenID penId = UIKit_AddPenIfNecesary(); const SDL_PenID penId = UIKit_AddPenIfNecesary(window);
if (penId) { if (penId) {
const Uint64 timestamp = UIKit_GetEventTimestamp(nstimestamp); const Uint64 timestamp = UIKit_GetEventTimestamp(nstimestamp);
const float radians_to_degrees = 180.0f / SDL_PI_F; const float radians_to_degrees = 180.0f / SDL_PI_F;
@@ -188,18 +188,20 @@ void UIKit_HandlePenMotion(SDL_uikitview *view, UITouch *pencil)
void UIKit_HandlePenPress(SDL_uikitview *view, UITouch *pencil) void UIKit_HandlePenPress(SDL_uikitview *view, UITouch *pencil)
{ {
const SDL_PenID penId = UIKit_AddPenIfNecesary(); SDL_Window *window = [view getSDLWindow];
const SDL_PenID penId = UIKit_AddPenIfNecesary(window);
if (penId) { if (penId) {
UIKit_HandlePenAxesFromUITouch(view, pencil); UIKit_HandlePenAxesFromUITouch(view, pencil);
SDL_SendPenTouch(UIKit_GetEventTimestamp([pencil timestamp]), penId, [view getSDLWindow], false, true); SDL_SendPenTouch(UIKit_GetEventTimestamp([pencil timestamp]), penId, window, false, true);
} }
} }
void UIKit_HandlePenRelease(SDL_uikitview *view, UITouch *pencil) void UIKit_HandlePenRelease(SDL_uikitview *view, UITouch *pencil)
{ {
const SDL_PenID penId = UIKit_AddPenIfNecesary(); SDL_Window *window = [view getSDLWindow];
const SDL_PenID penId = UIKit_AddPenIfNecesary(window);
if (penId) { if (penId) {
SDL_SendPenTouch(UIKit_GetEventTimestamp([pencil timestamp]), penId, [view getSDLWindow], false, false); SDL_SendPenTouch(UIKit_GetEventTimestamp([pencil timestamp]), penId, window, false, false);
UIKit_HandlePenAxesFromUITouch(view, pencil); UIKit_HandlePenAxesFromUITouch(view, pencil);
} }
} }
@@ -207,7 +209,7 @@ void UIKit_HandlePenRelease(SDL_uikitview *view, UITouch *pencil)
void UIKit_QuitPen(SDL_VideoDevice *_this) void UIKit_QuitPen(SDL_VideoDevice *_this)
{ {
if (apple_pencil_id) { if (apple_pencil_id) {
SDL_RemovePenDevice(0, apple_pencil_id); SDL_RemovePenDevice(0, NULL, apple_pencil_id);
apple_pencil_id = 0; apple_pencil_id = 0;
} }
} }

View File

@@ -3306,7 +3306,8 @@ static void tablet_tool_handle_removed(void *data, struct zwp_tablet_tool_v2 *to
{ {
SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data; SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data;
if (sdltool->instance_id) { if (sdltool->instance_id) {
SDL_RemovePenDevice(0, sdltool->instance_id); SDL_Window *window = sdltool->focus ? sdltool->focus->sdlwindow : NULL;
SDL_RemovePenDevice(0, window, sdltool->instance_id);
} }
Wayland_CursorStateRelease(&sdltool->cursor_state); Wayland_CursorStateRelease(&sdltool->cursor_state);
@@ -3439,7 +3440,7 @@ static void tablet_tool_handle_frame(void *data, struct zwp_tablet_tool_v2 *tool
if (sdltool->frame.have_proximity_in) { if (sdltool->frame.have_proximity_in) {
SDL_assert(sdltool->instance_id == 0); // shouldn't be added at this point. SDL_assert(sdltool->instance_id == 0); // shouldn't be added at this point.
if (sdltool->info.subtype != SDL_PEN_TYPE_UNKNOWN) { // don't tell SDL about it if we don't know its role. if (sdltool->info.subtype != SDL_PEN_TYPE_UNKNOWN) { // don't tell SDL about it if we don't know its role.
sdltool->instance_id = SDL_AddPenDevice(timestamp, NULL, &sdltool->info, sdltool); sdltool->instance_id = SDL_AddPenDevice(timestamp, NULL, window, &sdltool->info, sdltool);
Wayland_TabletToolUpdateCursor(sdltool); Wayland_TabletToolUpdateCursor(sdltool);
} }
} }
@@ -3487,7 +3488,7 @@ static void tablet_tool_handle_frame(void *data, struct zwp_tablet_tool_v2 *tool
if (sdltool->frame.have_proximity_out) { if (sdltool->frame.have_proximity_out) {
sdltool->focus = NULL; sdltool->focus = NULL;
Wayland_TabletToolUpdateCursor(sdltool); Wayland_TabletToolUpdateCursor(sdltool);
SDL_RemovePenDevice(timestamp, sdltool->instance_id); SDL_RemovePenDevice(timestamp, window, sdltool->instance_id);
sdltool->instance_id = 0; sdltool->instance_id = 0;
} }
@@ -3656,7 +3657,7 @@ void Wayland_DisplayRemoveWindowReferencesFromSeats(SDL_VideoData *display, SDL_
tool->focus = NULL; tool->focus = NULL;
Wayland_TabletToolUpdateCursor(tool); Wayland_TabletToolUpdateCursor(tool);
if (tool->instance_id) { if (tool->instance_id) {
SDL_RemovePenDevice(0, tool->instance_id); SDL_RemovePenDevice(0, window->sdlwindow, tool->instance_id);
tool->instance_id = 0; tool->instance_id = 0;
} }
} }

View File

@@ -1277,7 +1277,7 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara
info.max_tilt = 90.0f; info.max_tilt = 90.0f;
info.num_buttons = 1; info.num_buttons = 1;
info.subtype = SDL_PEN_TYPE_PENCIL; info.subtype = SDL_PEN_TYPE_PENCIL;
SDL_AddPenDevice(0, NULL, &info, hpointer); SDL_AddPenDevice(0, NULL, data->window, &info, hpointer);
returnCode = 0; returnCode = 0;
} break; } break;
@@ -1293,7 +1293,7 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara
// if this just left the _window_, we don't care. If this is no longer visible to the tablet, time to remove it! // if this just left the _window_, we don't care. If this is no longer visible to the tablet, time to remove it!
if ((msg == WM_POINTERCAPTURECHANGED) || !IS_POINTER_INCONTACT_WPARAM(wParam)) { if ((msg == WM_POINTERCAPTURECHANGED) || !IS_POINTER_INCONTACT_WPARAM(wParam)) {
SDL_RemovePenDevice(WIN_GetEventTimestamp(), pen); SDL_RemovePenDevice(WIN_GetEventTimestamp(), data->window, pen);
} }
returnCode = 0; returnCode = 0;
} break; } break;

View File

@@ -272,7 +272,7 @@ static X11_PenHandle *X11_MaybeAddPen(SDL_VideoDevice *_this, const XIDeviceInfo
handle->is_eraser = is_eraser; handle->is_eraser = is_eraser;
handle->x11_deviceid = dev->deviceid; handle->x11_deviceid = dev->deviceid;
handle->pen = SDL_AddPenDevice(0, dev->name, &peninfo, handle); handle->pen = SDL_AddPenDevice(0, dev->name, NULL, &peninfo, handle);
if (!handle->pen) { if (!handle->pen) {
SDL_free(handle); SDL_free(handle);
return NULL; return NULL;
@@ -301,7 +301,7 @@ void X11_RemovePenByDeviceID(int deviceid)
{ {
X11_PenHandle *handle = X11_FindPenByDeviceID(deviceid); X11_PenHandle *handle = X11_FindPenByDeviceID(deviceid);
if (handle) { if (handle) {
SDL_RemovePenDevice(0, handle->pen); SDL_RemovePenDevice(0, NULL, handle->pen);
SDL_free(handle); SDL_free(handle);
} }
} }