diff --git a/include/SDL3/SDL_pen.h b/include/SDL3/SDL_pen.h index d09b00b620..5dc1731c73 100644 --- a/include/SDL3/SDL_pen.h +++ b/include/SDL3/SDL_pen.h @@ -92,13 +92,14 @@ typedef Uint32 SDL_PenID; */ typedef Uint32 SDL_PenInputFlags; -#define SDL_PEN_INPUT_DOWN (1u << 0) /**< pen is pressed down */ -#define SDL_PEN_INPUT_BUTTON_1 (1u << 1) /**< button 1 is pressed */ -#define SDL_PEN_INPUT_BUTTON_2 (1u << 2) /**< button 2 is pressed */ -#define SDL_PEN_INPUT_BUTTON_3 (1u << 3) /**< button 3 is pressed */ -#define SDL_PEN_INPUT_BUTTON_4 (1u << 4) /**< button 4 is pressed */ -#define SDL_PEN_INPUT_BUTTON_5 (1u << 5) /**< button 5 is pressed */ -#define SDL_PEN_INPUT_ERASER_TIP (1u << 30) /**< eraser tip is used */ +#define SDL_PEN_INPUT_DOWN (1u << 0) /**< pen is pressed down */ +#define SDL_PEN_INPUT_BUTTON_1 (1u << 1) /**< button 1 is pressed */ +#define SDL_PEN_INPUT_BUTTON_2 (1u << 2) /**< button 2 is pressed */ +#define SDL_PEN_INPUT_BUTTON_3 (1u << 3) /**< button 3 is pressed */ +#define SDL_PEN_INPUT_BUTTON_4 (1u << 4) /**< button 4 is pressed */ +#define SDL_PEN_INPUT_BUTTON_5 (1u << 5) /**< button 5 is pressed */ +#define SDL_PEN_INPUT_ERASER_TIP (1u << 30) /**< eraser tip is used */ +#define SDL_PEN_INPUT_IN_PROXIMITY (1u << 31) /**< pen is in proximity (since SDL 3.4.0) */ /** * Pen axis indices. diff --git a/src/events/SDL_pen.c b/src/events/SDL_pen.c index 18a862e865..c6104864c2 100644 --- a/src/events/SDL_pen.c +++ b/src/events/SDL_pen.c @@ -218,7 +218,7 @@ SDL_PenCapabilityFlags SDL_GetPenCapabilityFromAxis(SDL_PenAxis axis) return 0; // oh well. } -SDL_PenID SDL_AddPenDevice(Uint64 timestamp, const char *name, SDL_Window *window, const SDL_PenInfo *info, void *handle) +SDL_PenID SDL_AddPenDevice(Uint64 timestamp, const char *name, SDL_Window *window, const SDL_PenInfo *info, void *handle, bool in_proximity) { 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! @@ -256,14 +256,8 @@ SDL_PenID SDL_AddPenDevice(Uint64 timestamp, const char *name, SDL_Window *windo SDL_free(namecpy); } - if (result && SDL_EventEnabled(SDL_EVENT_PEN_PROXIMITY_IN)) { - SDL_Event event; - SDL_zero(event); - event.pproximity.type = SDL_EVENT_PEN_PROXIMITY_IN; - event.pproximity.timestamp = timestamp; - event.pproximity.which = result; - event.pproximity.windowID = window ? window->id : 0; - SDL_PushEvent(&event); + if (result) { + SDL_SendPenProximity(timestamp, result, window, in_proximity); } return result; @@ -275,6 +269,8 @@ void SDL_RemovePenDevice(Uint64 timestamp, SDL_Window *window, SDL_PenID instanc return; } + SDL_SendPenProximity(timestamp, instance_id, window, false); // bye bye + SDL_LockRWLockForWriting(pen_device_rwlock); SDL_Pen *pen = FindPenByInstanceId(instance_id); if (pen) { @@ -300,16 +296,6 @@ void SDL_RemovePenDevice(Uint64 timestamp, SDL_Window *window, SDL_PenID instanc } } SDL_UnlockRWLock(pen_device_rwlock); - - if (pen && SDL_EventEnabled(SDL_EVENT_PEN_PROXIMITY_OUT)) { - SDL_Event event; - SDL_zero(event); - event.pproximity.type = SDL_EVENT_PEN_PROXIMITY_OUT; - event.pproximity.timestamp = timestamp; - event.pproximity.which = instance_id; - event.pproximity.windowID = window ? window->id : 0; - SDL_PushEvent(&event); - } } // This presumably is happening during video quit, so we don't send PROXIMITY_OUT events here. @@ -595,3 +581,41 @@ 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) +{ + bool send_event = false; + SDL_PenInputFlags input_state = 0; + + // note that this locks for _reading_ because the lock protects the + // pen_devices array from being reallocated from under us, not the data in it; + // we assume only one thread (in the backend) is modifying an individual pen at + // a time, so it can update input state cleanly here. + 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; + } + send_event = true; + pen->input_state = input_state; // we could do an SDL_SetAtomicInt here if we run into trouble... + } + } + SDL_UnlockRWLock(pen_device_rwlock); + + const Uint32 event_type = in ? SDL_EVENT_PEN_PROXIMITY_IN : SDL_EVENT_PEN_PROXIMITY_OUT; + if (send_event && SDL_EventEnabled(event_type)) { + SDL_Event event; + SDL_zero(event); + event.pproximity.type = event_type; + event.pproximity.timestamp = timestamp; + event.pproximity.windowID = window ? window->id : 0; + event.pproximity.which = instance_id; + SDL_PushEvent(&event); + } +} + diff --git a/src/events/SDL_pen_c.h b/src/events/SDL_pen_c.h index 69539bde2a..d3cc0d471a 100644 --- a/src/events/SDL_pen_c.h +++ b/src/events/SDL_pen_c.h @@ -61,7 +61,7 @@ typedef struct SDL_PenInfo // 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. // Both are allowed to be NULL. -extern SDL_PenID SDL_AddPenDevice(Uint64 timestamp, const char *name, SDL_Window *window, 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, bool in_proximity); // 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_Window *window, SDL_PenID instance_id); @@ -81,6 +81,9 @@ extern void SDL_SendPenAxis(Uint64 timestamp, SDL_PenID instance_id, SDL_Window // 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, SDL_Window *window, Uint8 button, bool down); +// Backend calls this when a pen's button changes, to generate events and update state. +extern void SDL_SendPenProximity(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *window, bool in); + // 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 7bfde272e7..feb03bbd96 100644 --- a/src/video/android/SDL_androidpen.c +++ b/src/video/android/SDL_androidpen.c @@ -31,6 +31,7 @@ #define ACTION_CANCEL 3 #define ACTION_POINTER_DOWN 5 #define ACTION_POINTER_UP 6 +#define ACTION_HOVER_ENTER 9 #define ACTION_HOVER_EXIT 10 void Android_OnPen(SDL_Window *window, int pen_id_in, SDL_PenDeviceType device_type, int button, int action, float x, float y, float p) @@ -51,7 +52,7 @@ void Android_OnPen(SDL_Window *window, int pen_id_in, SDL_PenDeviceType device_t peninfo.num_buttons = 2; peninfo.subtype = SDL_PEN_TYPE_PEN; peninfo.device_type = device_type; - pen = SDL_AddPenDevice(0, NULL, window, &peninfo, (void *) (size_t) pen_id_in); + pen = SDL_AddPenDevice(0, NULL, window, &peninfo, (void *) (size_t) pen_id_in, true); if (!pen) { SDL_Log("error: can't add a pen device %d", pen_id_in); return; @@ -76,9 +77,13 @@ void Android_OnPen(SDL_Window *window, int pen_id_in, SDL_PenDeviceType device_t // button contains DOWN/ERASER_TIP on DOWN/UP regardless of pressed state, use action to distinguish // 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); + break; + case ACTION_CANCEL: - case ACTION_HOVER_EXIT: - SDL_RemovePenDevice(0, window, pen); + 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); break; case ACTION_DOWN: diff --git a/src/video/cocoa/SDL_cocoapen.m b/src/video/cocoa/SDL_cocoapen.m index b698bc571c..d0c3b40831 100644 --- a/src/video/cocoa/SDL_cocoapen.m +++ b/src/video/cocoa/SDL_cocoapen.m @@ -86,6 +86,8 @@ 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); return; // already have this one. } @@ -105,15 +107,16 @@ static void Cocoa_HandlePenProximityEvent(SDL_CocoaWindowData *_data, NSEvent *e handle->deviceid = devid; handle->toolid = toolid; handle->is_eraser = is_eraser; - handle->pen = SDL_AddPenDevice(Cocoa_GetEventTimestamp([event timestamp]), NULL, _data.window, &peninfo, handle); + handle->pen = SDL_AddPenDevice(Cocoa_GetEventTimestamp([event timestamp]), NULL, _data.window, &peninfo, handle, true); if (!handle->pen) { SDL_free(handle); // oh well. } } else { // old pen leaving! Cocoa_PenHandle *handle = Cocoa_FindPenByDeviceID(devid, toolid); if (handle) { - SDL_RemovePenDevice(Cocoa_GetEventTimestamp([event timestamp]), _data.window, handle->pen); - SDL_free(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); } } } diff --git a/src/video/emscripten/SDL_emscriptenevents.c b/src/video/emscripten/SDL_emscriptenevents.c index 07f9b2e43e..e378ff8cc7 100644 --- a/src/video/emscripten/SDL_emscriptenevents.c +++ b/src/video/emscripten/SDL_emscriptenevents.c @@ -848,14 +848,23 @@ static void Emscripten_HandleMouseFocus(SDL_WindowData *window_data, const Emscr static void Emscripten_HandlePenEnter(SDL_WindowData *window_data, const Emscripten_PointerEvent *event) { SDL_assert(event->pointer_type == PTRTYPE_PEN); - // Web browsers offer almost none of this information as specifics, but can without warning offer any of these specific things. - SDL_PenInfo peninfo; - SDL_zero(peninfo); - peninfo.capabilities = SDL_PEN_CAPABILITY_PRESSURE | SDL_PEN_CAPABILITY_ROTATION | SDL_PEN_CAPABILITY_XTILT | SDL_PEN_CAPABILITY_YTILT | SDL_PEN_CAPABILITY_TANGENTIAL_PRESSURE | SDL_PEN_CAPABILITY_ERASER; - peninfo.max_tilt = 90.0f; - peninfo.num_buttons = 2; - peninfo.subtype = SDL_PEN_TYPE_PEN; - SDL_AddPenDevice(0, NULL, window_data->window, &peninfo, (void *) (size_t) event->pointerid); + +SDL_Log("PEN ENTER pointerid=%d", event->pointerid); + + SDL_PenID pen = SDL_FindPenByHandle((void *) (size_t) event->pointerid); + if (pen) { + SDL_SendPenProximity(0, pen, window_data->window, 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; + SDL_zero(peninfo); + peninfo.capabilities = SDL_PEN_CAPABILITY_PRESSURE | SDL_PEN_CAPABILITY_ROTATION | SDL_PEN_CAPABILITY_XTILT | SDL_PEN_CAPABILITY_YTILT | SDL_PEN_CAPABILITY_TANGENTIAL_PRESSURE | SDL_PEN_CAPABILITY_ERASER; + peninfo.max_tilt = 90.0f; + peninfo.num_buttons = 2; + peninfo.subtype = SDL_PEN_TYPE_PEN; + SDL_AddPenDevice(0, NULL, window_data->window, &peninfo, (void *) (size_t) event->pointerid, true); + } + Emscripten_UpdatePenFromEvent(window_data, event); } @@ -875,10 +884,11 @@ EMSCRIPTEN_KEEPALIVE void Emscripten_HandlePointerEnter(SDL_WindowData *window_d static void Emscripten_HandlePenLeave(SDL_WindowData *window_data, const Emscripten_PointerEvent *event) { +SDL_Log("PEN LEAVE pointerid=%d", event->pointerid); const SDL_PenID pen = SDL_FindPenByHandle((void *) (size_t) event->pointerid); if (pen) { Emscripten_UpdatePointerFromEvent(window_data, event); // last data updates? - SDL_RemovePenDevice(0, window_data->window, pen); + SDL_SendPenProximity(0, pen, window_data->window, false); } } diff --git a/src/video/uikit/SDL_uikitpen.m b/src/video/uikit/SDL_uikitpen.m index 9c58e17104..25cd0f4013 100644 --- a/src/video/uikit/SDL_uikitpen.m +++ b/src/video/uikit/SDL_uikitpen.m @@ -86,7 +86,7 @@ static SDL_PenID UIKit_AddPenIfNecesary(SDL_Window *window) // 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. - apple_pencil_id = SDL_AddPenDevice(0, "Apple Pencil", window, &info, (void *) (size_t) 0x1); + apple_pencil_id = SDL_AddPenDevice(0, "Apple Pencil", window, &info, (void *) (size_t) 0x1, true); } return apple_pencil_id; diff --git a/src/video/wayland/SDL_waylandevents.c b/src/video/wayland/SDL_waylandevents.c index 4069052630..3b3a50bcb9 100644 --- a/src/video/wayland/SDL_waylandevents.c +++ b/src/video/wayland/SDL_waylandevents.c @@ -3301,6 +3301,11 @@ static void tablet_tool_handle_capability(void *data, struct zwp_tablet_tool_v2 static void tablet_tool_handle_done(void *data, struct zwp_tablet_tool_v2 *tool) { + SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data; + if (sdltool->info.subtype != SDL_PEN_TYPE_UNKNOWN) { // don't tell SDL about it if we don't know its role. + SDL_Window *window = sdltool->focus ? sdltool->focus->sdlwindow : NULL; + sdltool->instance_id = SDL_AddPenDevice(0, NULL, window, &sdltool->info, sdltool, false); + } } static void tablet_tool_handle_removed(void *data, struct zwp_tablet_tool_v2 *tool) @@ -3323,7 +3328,8 @@ static void tablet_tool_handle_proximity_in(void *data, struct zwp_tablet_tool_v SDL_WindowData *windowdata = surface ? Wayland_GetWindowDataForOwnedSurface(surface) : NULL; sdltool->focus = windowdata; sdltool->proximity_serial = serial; - sdltool->frame.have_proximity_in = true; + sdltool->frame.have_proximity = true; + sdltool->frame.in_proximity = true; // According to the docs, this should be followed by a frame event, where we'll send our SDL events. } @@ -3331,7 +3337,8 @@ static void tablet_tool_handle_proximity_in(void *data, struct zwp_tablet_tool_v static void tablet_tool_handle_proximity_out(void *data, struct zwp_tablet_tool_v2 *tool) { SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data; - sdltool->frame.have_proximity_out = true; + sdltool->frame.have_proximity = true; + sdltool->frame.in_proximity = false; } static void tablet_tool_handle_down(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t serial) @@ -3434,22 +3441,17 @@ static void tablet_tool_handle_wheel(void *data, struct zwp_tablet_tool_v2 *tool static void tablet_tool_handle_frame(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t time) { SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data; + const SDL_PenID instance_id = sdltool->instance_id; + if (!instance_id) { + return; // Not a pen we report on. + } const Uint64 timestamp = Wayland_AdjustEventTimestampBase(Wayland_EventTimestampMSToNS(time)); SDL_Window *window = sdltool->focus ? sdltool->focus->sdlwindow : NULL; - if (sdltool->frame.have_proximity_in) { - 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. - sdltool->instance_id = SDL_AddPenDevice(timestamp, NULL, window, &sdltool->info, sdltool); - Wayland_TabletToolUpdateCursor(sdltool); - } - } - - const SDL_PenID instance_id = sdltool->instance_id; - - if (!instance_id) { - return; // Not a pen we report on. + if (sdltool->frame.have_proximity && sdltool->frame.in_proximity) { + SDL_SendPenProximity(timestamp, instance_id, window, true); + Wayland_TabletToolUpdateCursor(sdltool); } // !!! FIXME: Should hit testing be done if pens generate pointer motion? @@ -3486,11 +3488,10 @@ 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 && !sdltool->frame.in_proximity) { + SDL_SendPenProximity(timestamp, instance_id, window, false); sdltool->focus = NULL; Wayland_TabletToolUpdateCursor(sdltool); - SDL_RemovePenDevice(timestamp, window, sdltool->instance_id); - sdltool->instance_id = 0; } // Reset for the next frame. diff --git a/src/video/wayland/SDL_waylandevents_c.h b/src/video/wayland/SDL_waylandevents_c.h index 6a5274eeff..b82d6d8680 100644 --- a/src/video/wayland/SDL_waylandevents_c.h +++ b/src/video/wayland/SDL_waylandevents_c.h @@ -107,9 +107,10 @@ typedef struct SDL_WaylandPenTool // a stylus, etc, on a tablet. WAYLAND_TABLET_TOOL_STATE_UP } tool_state; + bool in_proximity; + bool have_motion; - bool have_proximity_in; - bool have_proximity_out; + bool have_proximity; } frame; SDL_WaylandCursorState cursor_state; diff --git a/src/video/windows/SDL_windowsevents.c b/src/video/windows/SDL_windowsevents.c index 3c8dd7c0d3..5abc751ea8 100644 --- a/src/video/windows/SDL_windowsevents.c +++ b/src/video/windows/SDL_windowsevents.c @@ -666,7 +666,7 @@ static void WIN_HandleRawMouseInput(Uint64 timestamp, SDL_VideoData *data, HANDL int screen_y = virtual_desktop ? GetSystemMetrics(SM_YVIRTUALSCREEN) : 0; if (!data->raw_input_fake_pen_id) { - data->raw_input_fake_pen_id = SDL_AddPenDevice(timestamp, "raw mouse input", window, NULL, (void *)(size_t)-1); + data->raw_input_fake_pen_id = SDL_AddPenDevice(timestamp, "raw mouse input", window, NULL, (void *)(size_t)-1, true); } SDL_SendPenMotion(timestamp, data->raw_input_fake_pen_id, window, (float)(x + screen_x - window->x), (float)(y + screen_y - window->y)); } @@ -1275,22 +1275,25 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara break; // oh well. } else if (pointer_type != PT_PEN) { break; // we only care about pens here. - } else if (SDL_FindPenByHandle(hpointer)) { - break; // we already have this one, don't readd it. } - // 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 - // could be unset at this moment because Windows is still deciding what capabilities the pen has, and/or - // doesn't yet have valid data for them. As such, just say everything that the interface supports is - // available...we don't expose this information through the public API at the moment anyhow. - SDL_PenInfo info; - SDL_zero(info); - info.capabilities = SDL_PEN_CAPABILITY_PRESSURE | SDL_PEN_CAPABILITY_XTILT | SDL_PEN_CAPABILITY_YTILT | SDL_PEN_CAPABILITY_DISTANCE | SDL_PEN_CAPABILITY_ROTATION | SDL_PEN_CAPABILITY_ERASER; - info.max_tilt = 90.0f; - info.num_buttons = 1; - info.subtype = SDL_PEN_TYPE_PENCIL; - SDL_AddPenDevice(0, NULL, data->window, &info, hpointer); + const SDL_PenID pen = SDL_FindPenByHandle(hpointer); + if (pen) { + SDL_SendPenProximity(WIN_GetEventTimestamp(), pen, data->window, 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 + // could be unset at this moment because Windows is still deciding what capabilities the pen has, and/or + // doesn't yet have valid data for them. As such, just say everything that the interface supports is + // available...we don't expose this information through the public API at the moment anyhow. + SDL_PenInfo info; + SDL_zero(info); + info.capabilities = SDL_PEN_CAPABILITY_PRESSURE | SDL_PEN_CAPABILITY_XTILT | SDL_PEN_CAPABILITY_YTILT | SDL_PEN_CAPABILITY_DISTANCE | SDL_PEN_CAPABILITY_ROTATION | SDL_PEN_CAPABILITY_ERASER; + info.max_tilt = 90.0f; + info.num_buttons = 1; + info.subtype = SDL_PEN_TYPE_PENCIL; + SDL_AddPenDevice(WIN_GetEventTimestamp(), NULL, data->window, &info, hpointer, true); + } returnCode = 0; } break; @@ -1306,7 +1309,8 @@ 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)) { - SDL_RemovePenDevice(WIN_GetEventTimestamp(), data->window, pen); + // 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); } returnCode = 0; } break; diff --git a/src/video/x11/SDL_x11pen.c b/src/video/x11/SDL_x11pen.c index 24bec559b0..c885de2f9a 100644 --- a/src/video/x11/SDL_x11pen.c +++ b/src/video/x11/SDL_x11pen.c @@ -167,6 +167,18 @@ static bool X11_XInput2PenWacomDeviceID(SDL_VideoDevice *_this, int deviceid, Ui return false; } +// Check if a Wacom device is in proximity of the tablet +static bool X11_XInput2PenIsInProximity(SDL_VideoDevice *_this, int deviceid, bool *in_proximity) +{ + SDL_VideoData *data = _this->internal; + Sint32 serial_id_buf[5]; + if (X11_XInput2PenGetIntProperty(_this, deviceid, data->atoms.pen_atom_wacom_serial_ids, serial_id_buf, 5) == 5) { + *in_proximity = serial_id_buf[4] != 0 || serial_id_buf[3] != 0; + return true; + } + return false; +} + typedef struct FindPenByDeviceIDData { @@ -272,7 +284,12 @@ static X11_PenHandle *X11_MaybeAddPen(SDL_VideoDevice *_this, const XIDeviceInfo handle->is_eraser = is_eraser; handle->x11_deviceid = dev->deviceid; - handle->pen = SDL_AddPenDevice(0, dev->name, NULL, &peninfo, handle); + bool in_proximity = false; + if (!X11_XInput2PenIsInProximity(_this, dev->deviceid, &in_proximity)) { + in_proximity = true; // just say it's in proximity if we can't detect this state. + } + + handle->pen = SDL_AddPenDevice(0, dev->name, NULL, &peninfo, handle, in_proximity); if (!handle->pen) { SDL_free(handle); return NULL; @@ -306,6 +323,15 @@ void X11_RemovePenByDeviceID(int deviceid) } } +void X11_NotifyPenProximityChange(SDL_VideoDevice *_this, SDL_Window *window, int deviceid) +{ + 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); + } +} + void X11_InitPen(SDL_VideoDevice *_this) { if (!X11_Xinput2IsInitialized()) { diff --git a/src/video/x11/SDL_x11pen.h b/src/video/x11/SDL_x11pen.h index de7518151e..fd1533b53f 100644 --- a/src/video/x11/SDL_x11pen.h +++ b/src/video/x11/SDL_x11pen.h @@ -67,6 +67,9 @@ extern void X11_RemovePenByDeviceID(int deviceid); // Map X11 device ID to pen ID. extern X11_PenHandle *X11_FindPenByDeviceID(int deviceid); +// Notify that the pen has entered/left proximity +extern void X11_NotifyPenProximityChange(SDL_VideoDevice *_this, SDL_Window *window, int deviceid); + #endif // SDL_VIDEO_DRIVER_X11_XINPUT2 #endif // SDL_x11pen_h_ diff --git a/src/video/x11/SDL_x11xinput2.c b/src/video/x11/SDL_x11xinput2.c index a4487dcfab..5d241617c3 100644 --- a/src/video/x11/SDL_x11xinput2.c +++ b/src/video/x11/SDL_x11xinput2.c @@ -130,6 +130,7 @@ static bool xinput2_version_atleast(const int version, const int wantmajor, cons return version >= ((wantmajor * 1000) + wantminor); } +// !!! FIXME: isn't this just X11_FindWindow? static SDL_WindowData *xinput2_get_sdlwindowdata(SDL_VideoData *videodata, Window window) { int i; @@ -512,6 +513,17 @@ void X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie) //case XI_PropertyEvent: //case XI_DeviceChanged: + case XI_PropertyEvent: + { + const XIPropertyEvent *proev = (const XIPropertyEvent *)cookie->data; + // Handle pen proximity enter/leave + if (proev->what == XIPropertyModified && proev->property == videodata->atoms.pen_atom_wacom_serial_ids) { + const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data; + SDL_WindowData *windowdata = X11_FindWindow(_this, xev->event); + X11_NotifyPenProximityChange(_this, windowdata ? windowdata->window : NULL, proev->deviceid); + } + } break; + case XI_RawMotion: { const XIRawEvent *rawev = (const XIRawEvent *)cookie->data; diff --git a/test/testpen.c b/test/testpen.c index bb6e7d2cc0..6626b5145c 100644 --- a/test/testpen.c +++ b/test/testpen.c @@ -25,6 +25,7 @@ typedef struct Pen Uint32 buttons; bool eraser; bool touching; + bool in_proximity; struct Pen *next; } Pen; @@ -107,44 +108,49 @@ static Pen *FindPen(SDL_PenID which) SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) { Pen *pen = NULL; + Pen *i = NULL; switch (event->type) { - case SDL_EVENT_PEN_PROXIMITY_IN: { - pen = (Pen *) SDL_calloc(1, sizeof (*pen)); - if (!pen) { - SDL_Log("Out of memory!"); - return SDL_APP_FAILURE; + case SDL_EVENT_PEN_PROXIMITY_IN: + SDL_Log("Pen %" SDL_PRIu32 " enters proximity!", event->pproximity.which); + + for (i = pens.next; i != NULL; i = i->next) { + if (i->pen == event->pproximity.which) { + pen = i; + break; + } } - SDL_Log("Pen %" SDL_PRIu32 " enters proximity!", event->pproximity.which); - pen->pen = event->pproximity.which; - pen->r = (Uint8) SDL_rand(256); - pen->g = (Uint8) SDL_rand(256); - pen->b = (Uint8) SDL_rand(256); - pen->x = 320.0f; - pen->y = 240.0f; - pen->next = pens.next; - pens.next = pen; + if (!pen) { + SDL_Log("This is the first time we've seen this pen."); + pen = (Pen *) SDL_calloc(1, sizeof (*pen)); + if (!pen) { + SDL_Log("Out of memory!"); + return SDL_APP_FAILURE; + } + pen->pen = event->pproximity.which; + pen->r = (Uint8) SDL_rand(256); + pen->g = (Uint8) SDL_rand(256); + pen->b = (Uint8) SDL_rand(256); + pen->x = 320.0f; + pen->y = 240.0f; + pen->next = pens.next; + pens.next = pen; + } + + pen->in_proximity = true; return SDL_APP_CONTINUE; - } - - case SDL_EVENT_PEN_PROXIMITY_OUT: { - Pen *prev = &pens; - Pen *i; + case SDL_EVENT_PEN_PROXIMITY_OUT: SDL_Log("Pen %" SDL_PRIu32 " leaves proximity!", event->pproximity.which); for (i = pens.next; i != NULL; i = i->next) { if (i->pen == event->pproximity.which) { - prev->next = i->next; - SDL_free(i); + i->in_proximity = false; break; } - prev = i; } - return SDL_APP_CONTINUE; - } case SDL_EVENT_PEN_DOWN: /*SDL_Log("Pen %" SDL_PRIu32 " down!", event->ptouch.which);*/ @@ -220,6 +226,10 @@ static void DrawOnePen(Pen *pen, int num) { int i; + if (!pen->in_proximity) { + return; + } + /* draw button presses for this pen. A square for each in the pen's color, offset down the screen so they don't overlap. */ SDL_SetRenderDrawColor(renderer, pen->r, pen->g, pen->b, 255); for (i = 0; i < 8; i++) { /* we assume you don't have more than 8 buttons atm... */