diff --git a/src/events/SDL_events.c b/src/events/SDL_events.c index 4d6fa0e170..a8ac576bc4 100644 --- a/src/events/SDL_events.c +++ b/src/events/SDL_events.c @@ -1484,6 +1484,8 @@ void SDL_PumpEventMaintenance(void) } #endif + SDL_SendPendingPenProximity(); + SDL_UpdateCursorAnimation(); SDL_UpdateTrays(); diff --git a/src/events/SDL_pen.c b/src/events/SDL_pen.c index 79bf93b19d..61d41e1fea 100644 --- a/src/events/SDL_pen.c +++ b/src/events/SDL_pen.c @@ -37,6 +37,8 @@ typedef struct SDL_Pen float x; float y; SDL_PenInputFlags input_state; + bool pending_proximity_out; + SDL_WindowID pending_proximity_window_id; void *driverdata; } SDL_Pen; @@ -45,6 +47,7 @@ typedef struct SDL_Pen static SDL_RWLock *pen_device_rwlock = NULL; static SDL_Pen *pen_devices SDL_GUARDED_BY(pen_device_rwlock) = NULL; static int pen_device_count SDL_GUARDED_BY(pen_device_rwlock) = 0; +static SDL_AtomicInt pending_proximity_out; // You must hold pen_device_rwlock before calling this, and result is only safe while lock is held! // If SDL isn't initialized, grabbing the NULL lock is a no-op and there will be zero devices, so @@ -248,8 +251,8 @@ SDL_PenID SDL_AddPenDevice(Uint64 timestamp, const char *name, SDL_Window *windo SDL_free(namecpy); } - if (result) { - SDL_SendPenProximity(timestamp, result, window, in_proximity); + if (result && in_proximity) { + SDL_SendPenProximity(timestamp, result, window, true, true); } return result; @@ -261,7 +264,7 @@ void SDL_RemovePenDevice(Uint64 timestamp, SDL_Window *window, SDL_PenID instanc return; } - SDL_SendPenProximity(timestamp, instance_id, window, false); // bye bye + SDL_SendPenProximity(timestamp, instance_id, window, false, true); // bye bye SDL_LockRWLockForWriting(pen_device_rwlock); SDL_Pen *pen = FindPenByInstanceId(instance_id); @@ -453,6 +456,15 @@ void SDL_SendPenAxis(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *window } } +static void EnsurePenProximity(Uint64 timestamp, SDL_Pen *pen, SDL_Window *window) +{ + if (pen->pending_proximity_out) { + pen->pending_proximity_out = false; + } else if (!(pen->input_state & SDL_PEN_INPUT_IN_PROXIMITY)) { + SDL_SendPenProximity(timestamp, pen->instance_id, window, true, true); + } +} + void SDL_SendPenMotion(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *window, float x, float y) { bool send_event = false; @@ -465,6 +477,8 @@ void SDL_SendPenMotion(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *wind SDL_LockRWLockForReading(pen_device_rwlock); SDL_Pen *pen = FindPenByInstanceId(instance_id); if (pen) { + EnsurePenProximity(timestamp, pen, window); + if ((pen->x != x) || (pen->y != y)) { pen->x = x; // we could do an SDL_SetAtomicInt here if we run into trouble... pen->y = y; // we could do an SDL_SetAtomicInt here if we run into trouble... @@ -528,6 +542,8 @@ void SDL_SendPenButton(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *wind SDL_LockRWLockForReading(pen_device_rwlock); SDL_Pen *pen = FindPenByInstanceId(instance_id); if (pen) { + EnsurePenProximity(timestamp, pen, window); + input_state = pen->input_state; const Uint32 flag = (Uint32) (1u << button); const bool current = ((input_state & flag) != 0); @@ -579,7 +595,7 @@ void SDL_SendPenButton(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *wind } } -void SDL_SendPenProximity(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *window, bool in) +void SDL_SendPenProximity(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *window, bool in, bool immediate) { bool send_event = false; SDL_PenInputFlags input_state = 0; @@ -591,16 +607,23 @@ void SDL_SendPenProximity(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *w SDL_LockRWLockForReading(pen_device_rwlock); SDL_Pen *pen = FindPenByInstanceId(instance_id); if (pen) { - input_state = pen->input_state; - const bool in_proximity = ((input_state & SDL_PEN_INPUT_IN_PROXIMITY) != 0); - if (in_proximity != in) { - if (in) { - input_state |= SDL_PEN_INPUT_IN_PROXIMITY; - } else { - input_state &= ~SDL_PEN_INPUT_IN_PROXIMITY; + if (in || immediate) { + input_state = pen->input_state; + const bool in_proximity = ((input_state & SDL_PEN_INPUT_IN_PROXIMITY) != 0); + if (in_proximity != in) { + if (in) { + input_state |= SDL_PEN_INPUT_IN_PROXIMITY; + } else { + input_state &= ~SDL_PEN_INPUT_IN_PROXIMITY; + } + send_event = true; + pen->input_state = input_state; // we could do an SDL_SetAtomicInt here if we run into trouble... } - send_event = true; - pen->input_state = input_state; // we could do an SDL_SetAtomicInt here if we run into trouble... + pen->pending_proximity_out = false; + } else { + pen->pending_proximity_out = true; + pen->pending_proximity_window_id = (window ? window->id : 0); + SDL_SetAtomicInt(&pending_proximity_out, true); } } SDL_UnlockRWLock(pen_device_rwlock); @@ -617,3 +640,27 @@ void SDL_SendPenProximity(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *w } } +void SDL_SendPendingPenProximity(void) +{ + if (SDL_CompareAndSwapAtomicInt(&pending_proximity_out, true, false)) { + SDL_LockRWLockForReading(pen_device_rwlock); + for (int i = 0; i < pen_device_count; i++) { + SDL_Pen *pen = &pen_devices[i]; + if (pen->pending_proximity_out) { + pen->pending_proximity_out = false; + + SDL_Window *window = NULL; + if (pen->pending_proximity_window_id) { + window = SDL_GetWindowFromID(pen->pending_proximity_window_id); + if (!window) { + // The window is already gone, ignore this event + continue; + } + } + SDL_SendPenProximity(0, pen->instance_id, window, false, true); + } + } + SDL_UnlockRWLock(pen_device_rwlock); + } +} + diff --git a/src/events/SDL_pen_c.h b/src/events/SDL_pen_c.h index 0d4a6a0939..0aca9a6db9 100644 --- a/src/events/SDL_pen_c.h +++ b/src/events/SDL_pen_c.h @@ -82,7 +82,10 @@ extern void SDL_SendPenAxis(Uint64 timestamp, SDL_PenID instance_id, SDL_Window extern void SDL_SendPenButton(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *window, Uint8 button, bool down); // Backend calls this when a pen's proximity changes, to generate events and update state. -extern void SDL_SendPenProximity(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *window, bool in); +extern void SDL_SendPenProximity(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *window, bool in, bool immediate); + +// Pumping events calls this to generate pending pen proximity events +extern void SDL_SendPendingPenProximity(void); // 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/video/android/SDL_androidpen.c b/src/video/android/SDL_androidpen.c index e47d1d6ab2..02127bafb6 100644 --- a/src/video/android/SDL_androidpen.c +++ b/src/video/android/SDL_androidpen.c @@ -78,12 +78,12 @@ void Android_OnPen(SDL_Window *window, int pen_id_in, SDL_PenDeviceType device_t // we don't compare tip flags above because MotionEvent.getButtonState doesn't return stylus tip/eraser state. switch (action) { case ACTION_HOVER_ENTER: - SDL_SendPenProximity(0, pen, window, true); + SDL_SendPenProximity(0, pen, window, true, true); break; case ACTION_CANCEL: case ACTION_HOVER_EXIT: // strictly speaking, this can mean both "proximity out" and "left the View" but close enough. - SDL_SendPenProximity(0, pen, window, false); + SDL_SendPenProximity(0, pen, window, false, false); break; case ACTION_DOWN: diff --git a/src/video/cocoa/SDL_cocoapen.m b/src/video/cocoa/SDL_cocoapen.m index cb3fc861b6..91429ae7e6 100644 --- a/src/video/cocoa/SDL_cocoapen.m +++ b/src/video/cocoa/SDL_cocoapen.m @@ -87,7 +87,7 @@ static void Cocoa_HandlePenProximityEvent(SDL_CocoaWindowData *_data, NSEvent *e Cocoa_PenHandle *handle = Cocoa_FindPenByDeviceID(devid, toolid); if (handle) { handle->is_eraser = is_eraser; // in case this changed. - SDL_SendPenProximity(Cocoa_GetEventTimestamp([event timestamp]), handle->pen, _data.window, true); + SDL_SendPenProximity(Cocoa_GetEventTimestamp([event timestamp]), handle->pen, _data.window, true, true); return; // already have this one. } @@ -116,7 +116,7 @@ static void Cocoa_HandlePenProximityEvent(SDL_CocoaWindowData *_data, NSEvent *e if (handle) { // We never remove pens (until shutdown), since Apple gives no indication when they are actually gone. // But unless you are plugging and unplugging a tablet millions of times, generating new device IDs, this shouldn't be a massive memory drain. - SDL_SendPenProximity(Cocoa_GetEventTimestamp([event timestamp]), handle->pen, _data.window, false); + SDL_SendPenProximity(Cocoa_GetEventTimestamp([event timestamp]), handle->pen, _data.window, false, false); } } } diff --git a/src/video/emscripten/SDL_emscriptenevents.c b/src/video/emscripten/SDL_emscriptenevents.c index 3f71edee1b..41a172b33a 100644 --- a/src/video/emscripten/SDL_emscriptenevents.c +++ b/src/video/emscripten/SDL_emscriptenevents.c @@ -868,7 +868,7 @@ static void Emscripten_HandlePenEnter(SDL_WindowData *window_data, const Emscrip SDL_PenID pen = SDL_FindPenByHandle((void *) (size_t) 1); // something > 0 for the single pen handle. if (pen) { - SDL_SendPenProximity(0, pen, window_data->window, true); + SDL_SendPenProximity(0, pen, window_data->window, true, true); } else { // Web browsers offer almost none of this information as specifics, but can without warning offer any of these specific things. SDL_PenInfo peninfo; @@ -902,7 +902,7 @@ static void Emscripten_HandlePenLeave(SDL_WindowData *window_data, const Emscrip const SDL_PenID pen = SDL_FindPenByHandle((void *) (size_t) 1); // something > 0 for the single pen handle. if (pen) { Emscripten_UpdatePointerFromEvent(window_data, event); // last data updates? - SDL_SendPenProximity(0, pen, window_data->window, false); + SDL_SendPenProximity(0, pen, window_data->window, false, false); } } diff --git a/src/video/wayland/SDL_waylandevents.c b/src/video/wayland/SDL_waylandevents.c index 11d334917d..169f8a6e88 100644 --- a/src/video/wayland/SDL_waylandevents.c +++ b/src/video/wayland/SDL_waylandevents.c @@ -3471,7 +3471,7 @@ static void tablet_tool_handle_frame(void *data, struct zwp_tablet_tool_v2 *tool SDL_Window *window = sdltool->focus ? sdltool->focus->sdlwindow : NULL; if (sdltool->frame.have_proximity && sdltool->frame.in_proximity) { - SDL_SendPenProximity(timestamp, instance_id, window, true); + SDL_SendPenProximity(timestamp, instance_id, window, true, true); Wayland_TabletToolUpdateCursor(sdltool); } @@ -3510,7 +3510,7 @@ static void tablet_tool_handle_frame(void *data, struct zwp_tablet_tool_v2 *tool } if (sdltool->frame.have_proximity && !sdltool->frame.in_proximity) { - SDL_SendPenProximity(timestamp, instance_id, window, false); + SDL_SendPenProximity(timestamp, instance_id, window, false, false); sdltool->focus = NULL; Wayland_TabletToolUpdateCursor(sdltool); } diff --git a/src/video/windows/SDL_windowsevents.c b/src/video/windows/SDL_windowsevents.c index 4be7bef794..d50a8af0ce 100644 --- a/src/video/windows/SDL_windowsevents.c +++ b/src/video/windows/SDL_windowsevents.c @@ -1282,7 +1282,7 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara void *hpointer = (void *)(size_t)1; // just something > 0. We're using this one ID any possible pen. const SDL_PenID pen = SDL_FindPenByHandle(hpointer); if (pen) { - SDL_SendPenProximity(WIN_GetEventTimestamp(), pen, data->window, true); + SDL_SendPenProximity(WIN_GetEventTimestamp(), pen, data->window, true, true); } else { // one can use GetPointerPenInfo() to get the current state of the pen, and check POINTER_PEN_INFO::penMask, // but the docs aren't clear if these masks are _always_ set for pens with specific features, or if they @@ -1323,7 +1323,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 ((msg == WM_POINTERCAPTURECHANGED) || !IS_POINTER_INCONTACT_WPARAM(wParam)) { // technically this isn't just _proximity_ but maybe just leaving the window. Good enough. WinTab apparently has real proximity info. - SDL_SendPenProximity(WIN_GetEventTimestamp(), pen, data->window, false); + SDL_SendPenProximity(WIN_GetEventTimestamp(), pen, data->window, false, false); } returnCode = 0; } break; diff --git a/src/video/x11/SDL_x11pen.c b/src/video/x11/SDL_x11pen.c index 3294f22c6a..76a542f2cf 100644 --- a/src/video/x11/SDL_x11pen.c +++ b/src/video/x11/SDL_x11pen.c @@ -328,7 +328,7 @@ void X11_NotifyPenProximityChange(SDL_VideoDevice *_this, SDL_Window *window, in bool in_proximity; X11_PenHandle *pen = X11_FindPenByDeviceID(deviceid); if (pen && X11_XInput2PenIsInProximity(_this, deviceid, &in_proximity)) { - SDL_SendPenProximity(0, pen->pen, window, in_proximity); + SDL_SendPenProximity(0, pen->pen, window, in_proximity, in_proximity); } }