pen: Rework public API.

This changes the API in various ways, and updates the backends for this.

Overall, this is a massive simplification of the API, as most future backends
can't support the previously-offered API.

This also removes the testautomation pen code (not only did these interfaces
change completely, it also did something no other test did: mock the internal
API), and replaces testpen.c with a different implementation (the existing
code was fine, it was just easier to start from scratch than update it).
This commit is contained in:
Ryan C. Gordon
2024-05-26 11:38:40 -04:00
parent 2b853121fe
commit a9d70dbacb
29 changed files with 1429 additions and 5146 deletions

View File

@@ -355,13 +355,6 @@ SDL3_0.0.0 {
SDL_GetNumberProperty;
SDL_GetOriginalMemoryFunctions;
SDL_GetPathInfo;
SDL_GetPenCapabilities;
SDL_GetPenFromGUID;
SDL_GetPenGUID;
SDL_GetPenName;
SDL_GetPenStatus;
SDL_GetPenType;
SDL_GetPens;
SDL_GetPerformanceCounter;
SDL_GetPerformanceFrequency;
SDL_GetPixelFormatDetails;
@@ -611,7 +604,6 @@ SDL3_0.0.0 {
SDL_PauseAudioStreamDevice;
SDL_PauseHaptic;
SDL_PeepEvents;
SDL_PenConnected;
SDL_PlayHapticRumble;
SDL_PollEvent;
SDL_PremultiplyAlpha;

View File

@@ -380,13 +380,6 @@
#define SDL_GetNumberProperty SDL_GetNumberProperty_REAL
#define SDL_GetOriginalMemoryFunctions SDL_GetOriginalMemoryFunctions_REAL
#define SDL_GetPathInfo SDL_GetPathInfo_REAL
#define SDL_GetPenCapabilities SDL_GetPenCapabilities_REAL
#define SDL_GetPenFromGUID SDL_GetPenFromGUID_REAL
#define SDL_GetPenGUID SDL_GetPenGUID_REAL
#define SDL_GetPenName SDL_GetPenName_REAL
#define SDL_GetPenStatus SDL_GetPenStatus_REAL
#define SDL_GetPenType SDL_GetPenType_REAL
#define SDL_GetPens SDL_GetPens_REAL
#define SDL_GetPerformanceCounter SDL_GetPerformanceCounter_REAL
#define SDL_GetPerformanceFrequency SDL_GetPerformanceFrequency_REAL
#define SDL_GetPixelFormatDetails SDL_GetPixelFormatDetails_REAL
@@ -636,7 +629,6 @@
#define SDL_PauseAudioStreamDevice SDL_PauseAudioStreamDevice_REAL
#define SDL_PauseHaptic SDL_PauseHaptic_REAL
#define SDL_PeepEvents SDL_PeepEvents_REAL
#define SDL_PenConnected SDL_PenConnected_REAL
#define SDL_PlayHapticRumble SDL_PlayHapticRumble_REAL
#define SDL_PollEvent SDL_PollEvent_REAL
#define SDL_PremultiplyAlpha SDL_PremultiplyAlpha_REAL

View File

@@ -400,13 +400,6 @@ SDL_DYNAPI_PROC(int,SDL_GetNumVideoDrivers,(void),(),return)
SDL_DYNAPI_PROC(Sint64,SDL_GetNumberProperty,(SDL_PropertiesID a, const char *b, Sint64 c),(a,b,c),return)
SDL_DYNAPI_PROC(void,SDL_GetOriginalMemoryFunctions,(SDL_malloc_func *a, SDL_calloc_func *b, SDL_realloc_func *c, SDL_free_func *d),(a,b,c,d),)
SDL_DYNAPI_PROC(int,SDL_GetPathInfo,(const char *a, SDL_PathInfo *b),(a,b),return)
SDL_DYNAPI_PROC(SDL_PenCapabilityFlags,SDL_GetPenCapabilities,(SDL_PenID a, SDL_PenCapabilityInfo *b),(a,b),return)
SDL_DYNAPI_PROC(SDL_PenID,SDL_GetPenFromGUID,(SDL_GUID a),(a),return)
SDL_DYNAPI_PROC(SDL_GUID,SDL_GetPenGUID,(SDL_PenID a),(a),return)
SDL_DYNAPI_PROC(const char*,SDL_GetPenName,(SDL_PenID a),(a),return)
SDL_DYNAPI_PROC(Uint32,SDL_GetPenStatus,(SDL_PenID a, float *b, float *c, float *d, size_t e),(a,b,c,d,e),return)
SDL_DYNAPI_PROC(SDL_PenSubtype,SDL_GetPenType,(SDL_PenID a),(a),return)
SDL_DYNAPI_PROC(SDL_PenID*,SDL_GetPens,(int *a),(a),return)
SDL_DYNAPI_PROC(Uint64,SDL_GetPerformanceCounter,(void),(),return)
SDL_DYNAPI_PROC(Uint64,SDL_GetPerformanceFrequency,(void),(),return)
SDL_DYNAPI_PROC(const SDL_PixelFormatDetails*,SDL_GetPixelFormatDetails,(SDL_PixelFormat a),(a),return)
@@ -647,7 +640,6 @@ SDL_DYNAPI_PROC(int,SDL_PauseAudioDevice,(SDL_AudioDeviceID a),(a),return)
SDL_DYNAPI_PROC(int,SDL_PauseAudioStreamDevice,(SDL_AudioStream *a),(a),return)
SDL_DYNAPI_PROC(int,SDL_PauseHaptic,(SDL_Haptic *a),(a),return)
SDL_DYNAPI_PROC(int,SDL_PeepEvents,(SDL_Event *a, int b, SDL_EventAction c, Uint32 d, Uint32 e),(a,b,c,d,e),return)
SDL_DYNAPI_PROC(SDL_bool,SDL_PenConnected,(SDL_PenID a),(a),return)
SDL_DYNAPI_PROC(int,SDL_PlayHapticRumble,(SDL_Haptic *a, float b, Uint32 c),(a,b,c),return)
SDL_DYNAPI_PROC(SDL_bool,SDL_PollEvent,(SDL_Event *a),(a),return)
SDL_DYNAPI_PROC(int,SDL_PremultiplyAlpha,(int a, int b, SDL_PixelFormat c, const void *d, int e, SDL_PixelFormat f, void *g, int h, SDL_bool i),(a,b,c,d,e,f,g,h,i),return)

View File

@@ -153,17 +153,24 @@ SDL_EventCategory SDL_GetEventCategory(Uint32 type)
case SDL_EVENT_SENSOR_UPDATE:
return SDL_EVENTCATEGORY_SENSOR;
case SDL_EVENT_PEN_PROXIMITY_IN:
case SDL_EVENT_PEN_PROXIMITY_OUT:
return SDL_EVENTCATEGORY_PPROXIMITY;
case SDL_EVENT_PEN_DOWN:
case SDL_EVENT_PEN_UP:
return SDL_EVENTCATEGORY_PTIP;
case SDL_EVENT_PEN_MOTION:
return SDL_EVENTCATEGORY_PMOTION;
return SDL_EVENTCATEGORY_PTOUCH;
case SDL_EVENT_PEN_BUTTON_DOWN:
case SDL_EVENT_PEN_BUTTON_UP:
return SDL_EVENTCATEGORY_PBUTTON;
case SDL_EVENT_PEN_MOTION:
return SDL_EVENTCATEGORY_PMOTION;
case SDL_EVENT_PEN_AXIS:
return SDL_EVENTCATEGORY_PAXIS;
case SDL_EVENT_CAMERA_DEVICE_ADDED:
case SDL_EVENT_CAMERA_DEVICE_REMOVED:
case SDL_EVENT_CAMERA_DEVICE_APPROVED:
@@ -207,14 +214,20 @@ SDL_Window *SDL_GetWindowFromEvent(const SDL_Event *event)
case SDL_EVENTCATEGORY_TFINGER:
windowID = event->tfinger.windowID;
break;
case SDL_EVENTCATEGORY_PTIP:
windowID = event->ptip.windowID;
case SDL_EVENTCATEGORY_PPROXIMITY:
windowID = event->pproximity.windowID;
break;
case SDL_EVENTCATEGORY_PTOUCH:
windowID = event->ptouch.windowID;
break;
case SDL_EVENTCATEGORY_PBUTTON:
windowID = event->pbutton.windowID;
break;
case SDL_EVENTCATEGORY_PMOTION:
windowID = event->pmotion.windowID;
break;
case SDL_EVENTCATEGORY_PBUTTON:
windowID = event->pbutton.windowID;
case SDL_EVENTCATEGORY_PAXIS:
windowID = event->paxis.windowID;
break;
case SDL_EVENTCATEGORY_DROP:
windowID = event->drop.windowID;

View File

@@ -54,9 +54,11 @@ typedef enum SDL_EventCategory
SDL_EVENTCATEGORY_QUIT,
SDL_EVENTCATEGORY_USER,
SDL_EVENTCATEGORY_TFINGER,
SDL_EVENTCATEGORY_PTIP,
SDL_EVENTCATEGORY_PPROXIMITY,
SDL_EVENTCATEGORY_PTOUCH,
SDL_EVENTCATEGORY_PMOTION,
SDL_EVENTCATEGORY_PBUTTON,
SDL_EVENTCATEGORY_PAXIS,
SDL_EVENTCATEGORY_DROP,
SDL_EVENTCATEGORY_CLIPBOARD,
} SDL_EventCategory;

View File

@@ -378,6 +378,9 @@ static void SDLCALL SDL_EventLoggingChanged(void *userdata, const char *name, co
static void SDL_LogEvent(const SDL_Event *event)
{
static const char *pen_axisnames[] = { "PRESSURE", "XTILT", "YTILT", "DISTANCE", "ROTATION", "SLIDER" };
SDL_COMPILE_TIME_ASSERT(pen_axisnames_array_matches, SDL_arraysize(pen_axisnames) == SDL_PEN_NUM_AXES);
char name[64];
char details[128];
@@ -385,6 +388,7 @@ static void SDL_LogEvent(const SDL_Event *event)
if ((SDL_EventLoggingVerbosity < 2) &&
((event->type == SDL_EVENT_MOUSE_MOTION) ||
(event->type == SDL_EVENT_FINGER_MOTION) ||
(event->type == SDL_EVENT_PEN_AXIS) ||
(event->type == SDL_EVENT_PEN_MOTION) ||
(event->type == SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION) ||
(event->type == SDL_EVENT_GAMEPAD_SENSOR_UPDATE) ||
@@ -482,8 +486,6 @@ static void SDL_LogEvent(const SDL_Event *event)
SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_RESTORED);
SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_MOUSE_ENTER);
SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_MOUSE_LEAVE);
SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_PEN_ENTER);
SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_PEN_LEAVE);
SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_FOCUS_GAINED);
SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_FOCUS_LOST);
SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_CLOSE_REQUESTED);
@@ -699,45 +701,44 @@ static void SDL_LogEvent(const SDL_Event *event)
break;
#undef PRINT_FINGER_EVENT
#define PRINT_PTIP_EVENT(event) \
(void)SDL_snprintf(details, sizeof(details), " (timestamp=%u windowid=%u which=%u tip=%u state=%s x=%g y=%g)", \
(uint)event->ptip.timestamp, (uint)event->ptip.windowID, \
(uint)event->ptip.which, (uint)event->ptip.tip, \
event->ptip.state == SDL_PRESSED ? "down" : "up", \
event->ptip.x, event->ptip.y)
#define PRINT_PTOUCH_EVENT(event) \
(void)SDL_snprintf(details, sizeof(details), " (timestamp=%u windowid=%u which=%u pen_state=%u x=%g y=%g eraser=%s state=%s)", \
(uint)event->ptouch.timestamp, (uint)event->ptouch.windowID, (uint)event->ptouch.which, (uint)event->ptouch.pen_state, event->ptouch.x, event->ptouch.y, \
event->ptouch.eraser ? "yes" : "no", event->ptouch.state == SDL_PRESSED ? "down" : "up");
SDL_EVENT_CASE(SDL_EVENT_PEN_DOWN)
PRINT_PTIP_EVENT(event);
PRINT_PTOUCH_EVENT(event);
break;
SDL_EVENT_CASE(SDL_EVENT_PEN_UP)
PRINT_PTIP_EVENT(event);
PRINT_PTOUCH_EVENT(event);
break;
#undef PRINT_PTOUCH_EVENT
#define PRINT_PPROXIMITY_EVENT(event) \
(void)SDL_snprintf(details, sizeof(details), " (timestamp=%u windowid=%u which=%u)", \
(uint)event->pproximity.timestamp, (uint)event->pproximity.windowID, (uint)event->pproximity.which);
SDL_EVENT_CASE(SDL_EVENT_PEN_PROXIMITY_IN)
PRINT_PPROXIMITY_EVENT(event);
break;
SDL_EVENT_CASE(SDL_EVENT_PEN_PROXIMITY_OUT)
PRINT_PPROXIMITY_EVENT(event);
break;
#undef PRINT_PPROXIMITY_EVENT
SDL_EVENT_CASE(SDL_EVENT_PEN_AXIS)
(void)SDL_snprintf(details, sizeof(details), " (timestamp=%u windowid=%u which=%u pen_state=%u x=%g y=%g axis=%s value=%g)",
(uint)event->paxis.timestamp, (uint)event->paxis.windowID, (uint)event->paxis.which, (uint)event->paxis.pen_state, event->paxis.x, event->paxis.y,
((event->paxis.axis >= 0) && (event->paxis.axis < SDL_arraysize(pen_axisnames))) ? pen_axisnames[event->paxis.axis] : "[UNKNOWN]", event->paxis.value);
break;
#undef PRINT_PTIP_EVENT
SDL_EVENT_CASE(SDL_EVENT_PEN_MOTION)
(void)SDL_snprintf(details, sizeof(details), " (timestamp=%u windowid=%u which=%u state=%08x x=%g y=%g [%g, %g, %g, %g, %g, %g])",
(uint)event->pmotion.timestamp, (uint)event->pmotion.windowID,
(uint)event->pmotion.which, (uint)event->pmotion.pen_state,
event->pmotion.x, event->pmotion.y,
event->pmotion.axes[SDL_PEN_AXIS_PRESSURE],
event->pmotion.axes[SDL_PEN_AXIS_XTILT],
event->pmotion.axes[SDL_PEN_AXIS_YTILT],
event->pmotion.axes[SDL_PEN_AXIS_DISTANCE],
event->pmotion.axes[SDL_PEN_AXIS_ROTATION],
event->pmotion.axes[SDL_PEN_AXIS_SLIDER]);
(void)SDL_snprintf(details, sizeof(details), " (timestamp=%u windowid=%u which=%u pen_state=%u x=%g y=%g)",
(uint)event->pmotion.timestamp, (uint)event->pmotion.windowID, (uint)event->pmotion.which, (uint)event->pmotion.pen_state, event->pmotion.x, event->pmotion.y);
break;
#define PRINT_PBUTTON_EVENT(event) \
(void)SDL_snprintf(details, sizeof(details), " (timestamp=%u windowid=%u which=%u tip=%u state=%s x=%g y=%g axes=[%g, %g, %g, %g, %g, %g])", \
(uint)event->pbutton.timestamp, (uint)event->pbutton.windowID, \
(uint)event->pbutton.which, (uint)event->pbutton.button, \
event->pbutton.state == SDL_PRESSED ? "pressed" : "released", \
event->pbutton.x, event->pbutton.y, \
event->pbutton.axes[SDL_PEN_AXIS_PRESSURE], \
event->pbutton.axes[SDL_PEN_AXIS_XTILT], \
event->pbutton.axes[SDL_PEN_AXIS_YTILT], \
event->pbutton.axes[SDL_PEN_AXIS_DISTANCE], \
event->pbutton.axes[SDL_PEN_AXIS_ROTATION], \
event->pbutton.axes[SDL_PEN_AXIS_SLIDER])
(void)SDL_snprintf(details, sizeof(details), " (timestamp=%u windowid=%u which=%u pen_state=%u x=%g y=%g button=%u state=%s)", \
(uint)event->pbutton.timestamp, (uint)event->pbutton.windowID, (uint)event->pbutton.which, (uint)event->pbutton.pen_state, event->pbutton.x, event->pbutton.y, \
(uint)event->pbutton.button, event->pbutton.state == SDL_PRESSED ? "down" : "up");
SDL_EVENT_CASE(SDL_EVENT_PEN_BUTTON_DOWN)
PRINT_PBUTTON_EVENT(event);
break;

View File

@@ -33,6 +33,7 @@
#include "SDL_keyboard_c.h"
#include "SDL_mouse_c.h"
#include "SDL_touch_c.h"
#include "SDL_pen_c.h"
#include "SDL_windowevents_c.h"
/* Start and stop the event processing loop */

View File

@@ -26,7 +26,6 @@
#include "../video/SDL_sysvideo.h"
#include "SDL_events_c.h"
#include "SDL_mouse_c.h"
#include "SDL_pen_c.h"
#if defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_GDK)
#include "../core/windows/SDL_windows.h" // For GetDoubleClickTime()
#endif
@@ -254,6 +253,7 @@ int SDL_PreInitMouse(void)
mouse->was_touch_mouse_events = SDL_FALSE; /* no touch to mouse movement event pending */
mouse->cursor_shown = SDL_TRUE;
return 0;
}
@@ -272,8 +272,6 @@ void SDL_PostInitMouse(void)
SDL_DestroySurface(surface);
}
}
SDL_PenInit();
}
SDL_bool SDL_IsMouse(Uint16 vendor, Uint16 product)
@@ -741,7 +739,7 @@ static int SDL_PrivateSendMouseMotion(Uint64 timestamp, SDL_Window *window, SDL_
float xrel = 0.0f;
float yrel = 0.0f;
if ((!mouse->relative_mode || mouse->warp_emulation_active) && mouseID != SDL_TOUCH_MOUSEID && mouseID != SDL_PEN_MOUSEID) {
if ((!mouse->relative_mode || mouse->warp_emulation_active) && mouseID != SDL_TOUCH_MOUSEID) {
/* We're not in relative mode, so all mouse events are global mouse events */
mouseID = SDL_GLOBAL_MOUSE_ID;
}
@@ -935,7 +933,7 @@ static int SDL_PrivateSendMouseButton(Uint64 timestamp, SDL_Window *window, SDL_
Uint32 buttonstate;
SDL_MouseInputSource *source;
if (!mouse->relative_mode && mouseID != SDL_TOUCH_MOUSEID && mouseID != SDL_PEN_MOUSEID) {
if (!mouse->relative_mode && mouseID != SDL_TOUCH_MOUSEID) {
/* We're not in relative mode, so all mouse events are global mouse events */
mouseID = SDL_GLOBAL_MOUSE_ID;
}
@@ -1101,7 +1099,6 @@ void SDL_QuitMouse(void)
}
SDL_SetRelativeMouseMode(SDL_FALSE);
SDL_ShowCursor();
SDL_PenQuit();
if (mouse->def_cursor) {
SDL_SetDefaultCursor(NULL);

File diff suppressed because it is too large Load Diff

View File

@@ -27,316 +27,71 @@
#include "../../include/SDL3/SDL_pen.h"
#include "SDL_mouse_c.h"
/* For testing alternate code paths: */
#define SDL_PEN_DEBUG_NOID 0 /* Pretend that pen device does not supply ID / ID is some default value \
affects: SDL_x11pen.c \
SDL_waylandevents.c */
#define SDL_PEN_DEBUG_NONWACOM 0 /* Pretend that no attached device is a Wacom device \
affects: SDL_x11pen.c \
SDL_waylandevents.c */
#define SDL_PEN_DEBUG_UNKNOWN_WACOM 0 /* Pretend that any attached Wacom device is of an unknown make \
affects: SDL_PenModifyFromWacomID() */
#define SDL_PEN_DEBUG_NOSERIAL_WACOM 0 /* Pretend that any attached Wacom device has serial number 0 \
affects: SDL_x11pen.c \
SDL_waylandevents.c */
typedef Uint32 SDL_PenCapabilityFlags;
#define SDL_PEN_CAPABILITY_PRESSURE (1u << 0) /**< Provides pressure information on SDL_PEN_AXIS_PRESSURE. */
#define SDL_PEN_CAPABILITY_XTILT (1u << 1) /**< Provides horizontal tilt information on SDL_PEN_AXIS_XTILT. */
#define SDL_PEN_CAPABILITY_YTILT (1u << 2) /**< Provides vertical tilt information on SDL_PEN_AXIS_YTILT. */
#define SDL_PEN_CAPABILITY_DISTANCE (1u << 3) /**< Provides distance to drawing tablet on SDL_PEN_AXIS_DISTANCE. */
#define SDL_PEN_CAPABILITY_ROTATION (1u << 4) /**< Provides barrel rotation info on SDL_PEN_AXIS_ROTATION. */
#define SDL_PEN_CAPABILITY_SLIDER (1u << 5) /**< Provides slider/finger wheel/etc on SDL_PEN_AXIS_SLIDER. */
#define SDL_PEN_CAPABILITY_ERASER (1u << 6) /**< Pen also has an eraser tip. */
#define SDL_PEN_TYPE_NONE 0 /**< Pen type for non-pens (use to cancel pen registration) */
#define SDL_PEN_MAX_NAME 64
#define SDL_PEN_FLAG_ERROR (1ul << 28) /* Printed an internal API usage error about this pen (used to prevent spamming) */
#define SDL_PEN_FLAG_NEW (1ul << 29) /* Pen was registered in most recent call to SDL_PenRegisterBegin() */
#define SDL_PEN_FLAG_DETACHED (1ul << 30) /* Detached (not re-registered before last SDL_PenGCSweep()) */
#define SDL_PEN_FLAG_STALE (1ul << 31) /* Not re-registered since last SDL_PenGCMark() */
typedef struct SDL_PenStatusInfo
typedef enum SDL_PenSubtype
{
float x, y;
float axes[SDL_PEN_NUM_AXES];
Uint16 buttons; /* SDL_BUTTON(1) | SDL_BUTTON(2) | ... | SDL_PEN_DOWN_MASK */
} SDL_PenStatusInfo;
SDL_PEN_TYPE_UNKNOWN, /**< Unknown pen device */
SDL_PEN_TYPE_ERASER, /**< Eraser */
SDL_PEN_TYPE_PEN, /**< Generic pen; this is the default. */
SDL_PEN_TYPE_PENCIL, /**< Pencil */
SDL_PEN_TYPE_BRUSH, /**< Brush-like device */
SDL_PEN_TYPE_AIRBRUSH /**< Airbrush device that "sprays" ink */
} SDL_PenSubtype;
typedef struct
typedef struct SDL_PenInfo
{
SDL_PenID id; /* id determines sort order unless SDL_PEN_FLAG_DETACHED is set */
Uint32 flags; /* SDL_PEN_FLAG_* | SDK_PEN_DOWN_MASK | SDL_PEN_INK_MASK | SDL_PEN_ERASER_MASK | SDL_PEN_AXIS_* */
SDL_Window *window; /* Current SDL window for this pen, or NULL */
} SDL_PenHeader;
SDL_PenCapabilityFlags capabilities; /**< bitflags of device capabilities */
float max_tilt; /**< Physical maximum tilt angle, for XTILT and YTILT, or -1.0f if unknown. Pens cannot typically tilt all the way to 90 degrees, so this value is usually less than 90.0. */
Uint32 wacom_id; /**< For Wacom devices: wacom tool type ID, otherwise 0 (useful e.g. with libwacom) */
int num_buttons; /**< Number of pen buttons (not counting the pen tip), or -1 if unknown. */
SDL_PenSubtype subtype; /**< type of pen device */
} SDL_PenInfo;
/**
* Internal (backend driver-independent) pen representation
*
* Implementation-specific backend drivers may read and write most of this structure, and
* are expected to initialise parts of it when registering a new pen. They must not write
* to the "header" section.
*/
typedef struct SDL_Pen
{
/* Backend driver MUST NOT not write to: */
SDL_PenHeader header;
// 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, const SDL_PenInfo *info, void *handle);
SDL_PenStatusInfo last; /* Last reported status, normally read-only for backend */
// 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);
/* Backend: MUST initialise this block when pen is first registered: */
SDL_GUID guid; /* GUID, MUST be set by backend.
MUST be unique (no other pen ID with same GUID).
SHOULD be persistent across sessions. */
// 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);
/* Backend: SHOULD initialise this block when pen is first registered if it can
Otherwise: Set to sane default values during SDL_PenModifyEnd() */
SDL_PenCapabilityInfo info; /* Detail information about the pen (buttons, tilt) */
SDL_PenSubtype type;
Uint8 last_mouse_button; /* For mouse button emulation: last emulated button */
char *name; /* Preallocated; set via SDL_strlcpy(pen->name, src, SDL_PEN_MAX_NAME) */
/* We hand this exact pointer to client code, so it must not be modified after
creation. */
// Backend calls this when a pen's button changes, to generate events and update state.
extern int SDL_SendPenTouch(Uint64 timestamp, SDL_PenID instance_id, const SDL_Window *window, Uint8 state, Uint8 eraser);
void *deviceinfo; /* implementation-specific information */
} SDL_Pen;
// Backend calls this when a pen moves on the tablet, to generate events and update state.
extern int SDL_SendPenMotion(Uint64 timestamp, SDL_PenID instance_id, const SDL_Window *window, float x, float y);
/* ---- API for backend driver only ---- */
// Backend calls this when a pen's axis changes, to generate events and update state.
extern int SDL_SendPenAxis(Uint64 timestamp, SDL_PenID instance_id, const SDL_Window *window, SDL_PenAxis axis, float value);
/**
* (Only for backend driver) Look up a pen by pen ID
*
* \param instance_id A Uint32 pen identifier (driver-dependent meaning). Must not be 0 = SDL_PEN_INVALID.
* The same ID is exposed to clients as SDL_PenID.
*
* The pen pointer is only valid until the next call to SDL_PenModifyEnd() or SDL_PenGCSweep()
*
* \return pen, if it exists, or NULL
*/
extern SDL_Pen *SDL_GetPenPtr(Uint32 instance_id);
// Backend calls this when a pen's button changes, to generate events and update state.
extern int SDL_SendPenButton(Uint64 timestamp, SDL_PenID instance_id, const SDL_Window *window, Uint8 state, Uint8 button);
/**
* (Only for backend driver) Start registering a new pen or updating an existing pen.
*
* Acquires the pen mutex, which is held until the next call to SDL_PenModifyEnd() .
*
* If the PenID already exists, returns the existing entry. Otherwise initialise fresh SDL_Pen.
* For new pens, sets SDL_PEN_FLAG_NEW.
*
* Usage:
* - SDL_PenModifyStart()
* - update pen object, in any order:
* - SDL_PenModifyAddCapabilities()
* - pen->guid (MUST be set for new pens, e.g. via ::SDL_PenUpdateGUIDForGeneric and related operations)
* - pen->info.num_buttons
* - pen->info.max_tilt
* - pen->type
* - pen->name
* - pen->deviceinfo (backend-specific)
* - SDL_PenModifyEnd()
*
* For new pens, sets defaults for:
* - num_buttons (SDL_PEN_INFO_UNKNOWN)
* - max_tilt (SDL_PEN_INFO_UNKNOWN)
* - pen_type (SDL_PEN_TYPE_PEN)
* - Zeroes all other (non-header) fields
*
* \param instance_id Pen ID to allocate (must not be 0 = SDL_PEN_ID_INVALID)
* \returns SDL_Pen pointer; only valid until the call to SDL_PenModifyEnd()
*/
extern SDL_Pen *SDL_PenModifyBegin(Uint32 instance_id);
// 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);
/**
* (Only for backend driver) Add capabilities to a pen (cf. SDL_PenModifyBegin()).
*
* Adds capabilities to a pen obtained via SDL_PenModifyBegin(). Can be called more than once.
*
* \param pen The pen to update
* \param capabilities Capabilities flags, out of: SDL_PEN_AXIS_*, SDL_PEN_ERASER_MASK, SDL_PEN_INK_MASK
* Setting SDL_PEN_ERASER_MASK will clear SDL_PEN_INK_MASK, and vice versa.
*/
extern void SDL_PenModifyAddCapabilities(SDL_Pen *pen, Uint32 capabilities);
// Backend can optionally use this to find a SDL_PenID, selected by a callback examining all devices. Zero if not found.
extern SDL_PenID SDL_FindPenByCallback(SDL_bool (*callback)(void *handle, void *userdata), void *userdata);
/**
* Set up a pen structure for a Wacom device.
*
* Some backends (e.g., XInput2, Wayland) can only partially identify the capabilities of a given
* pen but can identify Wacom pens and obtain their Wacom-specific device type identifiers.
* This function partly automates device setup in those cases.
*
* This function does NOT set up the pen's GUID. Use ::SD_PenModifyGUIDForWacom instead.
*
* This function does NOT call SDL_PenModifyAddCapabilities() ifself, since some backends may
* not have access to all pen axes (e.g., Xinput2).
*
* \param pen The pen to initialise
* \param wacom_devicetype_id The Wacom-specific device type identifier
* \param[out] axis_flags The set of physically supported axes for this pen, suitable for passing to
* SDL_PenModifyAddCapabilities()
*
* \returns SDL_TRUE if the device ID could be identified, otherwise SDL_FALSE
*/
extern int SDL_PenModifyForWacomID(SDL_Pen *pen, Uint32 wacom_devicetype_id, Uint32 *axis_flags);
// Backend can use this to map an axis to a capability bit.
SDL_PenCapabilityFlags SDL_GetPenCapabilityFromAxis(SDL_PenAxis axis);
/**
* Updates a GUID for a generic pen device.
*
* Assumes that the GUID has been pre-initialised (typically to 0).
* Idempotent, and commutative with ::SDL_PenUpdateGUIDForWacom and ::SDL_PenUpdateGUIDForType
*
* \param[out] guid The GUID to update
* \param upper Upper half of the device ID (assume lower entropy than "lower"; pass 0 if not available)
* \param lower Lower half of the device ID (assume higher entropy than "upper")
*/
extern void SDL_PenUpdateGUIDForGeneric(SDL_GUID *guid, Uint32 upper, Uint32 lower);
// Higher-level SDL video subsystem code calls this when starting up. Backends shouldn't.
extern int SDL_InitPen(void);
/**
* Updates a GUID based on a pen type
*
* Assumes that the GUID has been pre-initialised (typically to 0).
* Idempotent, and commutative with ::SDL_PenUpdateGUIDForWacom and ::SDL_PenUpdateGUIDForGeneric
*
* \param[out] guid The GUID to update
* \param pentype The pen type to insert
*/
extern void SDL_PenUpdateGUIDForType(SDL_GUID *guid, SDL_PenSubtype pentype);
/**
* Updates a GUID for a Wacom pen device.
*
* Assumes that the GUID has been pre-initialised (typically to 0).
* Idempotent, and commutative with ::SDL_PenUpdateGUIDForType and ::SDL_PenUpdateGUIDForGeneric
*
* This update is identical to the one written by ::SDL_PenModifyFromWacomID .
*
* \param[out] guid The GUID to update
* \param wacom_devicetype_id The Wacom-specific device type identifier
* \param wacom_serial_id The Wacom-specific serial number
*/
extern void SDL_PenUpdateGUIDForWacom(SDL_GUID *guid, Uint32 wacom_devicetype_id, Uint32 wacom_serial_id);
/**
* (Only for backend driver) Finish updating a pen.
*
* Releases the pen mutex acquired by SDL_PenModifyBegin() .
*
* If pen->type == SDL_PEN_TYPE_NONE, removes the pen entirely (only
* for new pens). This allows backends to start registering a
* potential pen device and to abort if the device turns out to not be
* a pen.
*
* For new pens, this call will also set the following:
* - name (default name, if not yet set)
*
* \param pen The pen to register. That pointer is no longer valid after this call.
* \param attach Whether the pen should be attached (SDL_TRUE) or detached (SDL_FALSE).
*
* If the pen is detached or removed, it is the caller's responsibility to free
* and null "deviceinfo".
*/
extern void SDL_PenModifyEnd(SDL_Pen *pen, SDL_bool attach);
/**
* (Only for backend driver) Mark all current pens for garbage collection.
*
* Must not be called while the pen mutex is held (by SDL_PenModifyBegin() ).
*
* SDL_PenGCMark() / SDL_PenGCSweep() provide a simple mechanism for
* detaching all known pens that are not discoverable. This allows
* backends to use the same code for pen discovery and for
* hotplugging:
*
* - SDL_PenGCMark() and start backend-specific discovery
* - for each discovered pen: SDL_PenModifyBegin() + SDL_PenModifyEnd() (this will retain existing state)
* - SDL_PenGCSweep() (will now detach all pens that were not re-registered).
*/
extern void SDL_PenGCMark(void);
/**
* (Only for backend driver) Detach pens that haven't been reported attached since the last call to SDL_PenGCMark().
*
* Must not be called while the pen mutex is held (by SDL_PenModifyBegin() ).
*
* See SDL_PenGCMark() for details.
*
* \param context Extra parameter to pass through to "free_deviceinfo"
* \param free_deviceinfo Operation to call on any non-NULL "backend.deviceinfo".
*
* \sa SDL_PenGCMark()
*/
extern void SDL_PenGCSweep(void *context, void (*free_deviceinfo)(Uint32 instance_id, void *deviceinfo, void *context));
/**
* (Only for backend driver) Send a pen motion event.
*
* Suppresses pen motion events that do not change the current pen state.
* May also send a mouse motion event, if mouse emulation is enabled and the pen position has
* changed sufficiently for the motion to be visible to mouse event listeners.
*
* \param timestamp Event timestamp in nanoseconds, or 0 to ask SDL to use SDL_GetTicksNS() .
* While 0 is safe to report, your backends may be able to report more precise
* timing information.
* Keep in mind that you should never report timestamps that are greater than
* SDL_GetTicksNS() . In particular, SDL_GetTicksNS() reports nanoseconds since the start
* of the SDL session, and your backend may use a different starting point as "timestamp zero".
* \param instance_id Pen
* \param window_relative Coordinates are already window-relative
* \param status Coordinates and axes (buttons are ignored)
*
* \returns SDL_TRUE if at least one event was sent
*/
extern int SDL_SendPenMotion(Uint64 timestamp, SDL_PenID instance_id, SDL_bool window_relative, const SDL_PenStatusInfo *status);
/**
* (Only for backend driver) Send a pen button event
*
* \param timestamp Event timestamp in nanoseconds, or 0 to ask SDL to use SDL_GetTicksNS() .
* See SDL_SendPenMotion() for a discussion about how to handle timestamps.
* \param instance_id Pen
* \param state SDL_PRESSED or SDL_RELEASED
* \param button Button number: 1 (first physical button) etc.
*
* \returns SDL_TRUE if at least one event was sent
*/
extern int SDL_SendPenButton(Uint64 timestamp, SDL_PenID instance_id, Uint8 state, Uint8 button);
/**
* (Only for backend driver) Send a pen tip event (touching or no longer touching the surface)
*
* Note: the backend should perform hit testing on window decoration elements to allow the pen
* to e.g. resize/move the window, just as for mouse events, unless ::SDL_SendPenTipEvent is false.
*
* \param timestamp Event timestamp in nanoseconds, or 0 to ask SDL to use SDL_GetTicksNS() .
* See SDL_SendPenMotion() for a discussion about how to handle timestamps.
* \param instance_id Pen
* \param state SDL_PRESSED (for PEN_DOWN) or SDL_RELEASED (for PEN_UP)
*
* \returns SDL_TRUE if at least one event was sent
*/
extern int SDL_SendPenTipEvent(Uint64 timestamp, SDL_PenID instance_id, Uint8 state);
/**
* (Only for backend driver) Check if a PEN_DOWN event should perform hit box testing.
*
* \returns SDL_TRUE if and only if the backend should perform hit testing
*/
extern SDL_bool SDL_PenPerformHitTest(void);
/**
* (Only for backend driver) Send a pen window event.
*
* Tracks when a pen has entered/left a window.
* Don't call this when reporting new pens or removing known pens; those cases are handled automatically.
*
* \param timestamp Event timestamp in nanoseconds, or 0 to ask SDL to use SDL_GetTicksNS() .
* See SDL_SendPenMotion() for a discussion about how to handle timestamps.
* \param instance_id Pen
* \param window Window to enter, or NULL to exit
*/
extern int SDL_SendPenWindowEvent(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *window);
/**
* Initialises the pen subsystem.
*/
extern void SDL_PenInit(void);
/**
* De-initialises the pen subsystem.
*/
extern void SDL_PenQuit(void);
// Higher-level SDL video subsystem code calls this when shutting down. Backends shouldn't.
extern void SDL_QuitPen(void);
#endif /* SDL_pen_c_h_ */

View File

@@ -563,6 +563,7 @@ int SDL_VideoInit(const char *driver_name)
SDL_bool init_keyboard = SDL_FALSE;
SDL_bool init_mouse = SDL_FALSE;
SDL_bool init_touch = SDL_FALSE;
SDL_bool init_pen = SDL_FALSE;
int i = 0;
/* Check to make sure we don't overwrite '_this' */
@@ -589,6 +590,10 @@ int SDL_VideoInit(const char *driver_name)
goto pre_driver_error;
}
init_touch = SDL_TRUE;
if (SDL_InitPen() < 0) {
goto pre_driver_error;
}
init_pen = SDL_TRUE;
/* Select the proper video driver */
video = NULL;
@@ -673,6 +678,9 @@ int SDL_VideoInit(const char *driver_name)
pre_driver_error:
SDL_assert(_this == NULL);
if (init_pen) {
SDL_QuitPen();
}
if (init_touch) {
SDL_QuitTouch();
}
@@ -4217,6 +4225,7 @@ void SDL_VideoQuit(void)
}
/* Halt event processing before doing anything else */
SDL_QuitPen();
SDL_QuitTouch();
SDL_QuitMouse();
SDL_QuitKeyboard();

View File

@@ -2364,421 +2364,255 @@ void Wayland_create_text_input(SDL_VideoData *d)
}
}
static SDL_PenID Wayland_get_penid(void *data, struct zwp_tablet_tool_v2 *tool)
// Pen/Tablet support...
typedef struct SDL_WaylandPenTool // a stylus, etc, on a tablet.
{
struct SDL_WaylandTool *sdltool = data;
return sdltool->penid;
}
/* For registering pens */
static SDL_Pen *Wayland_get_current_pen(void *data, struct zwp_tablet_tool_v2 *tool)
{
struct SDL_WaylandTool *sdltool = data;
struct SDL_WaylandTabletInput *input = sdltool->tablet;
if (!input->current_pen.builder) {
/* Starting new pen or updating one? */
SDL_PenID penid = sdltool->penid;
if (penid == 0) {
/* Found completely new pen? */
penid = ++input->num_pens;
sdltool->penid = penid;
}
input->current_pen.builder = SDL_GetPenPtr(penid);
if (!input->current_pen.builder) {
/* Must register as new pen */
input->current_pen.builder = SDL_PenModifyBegin(penid);
}
}
return input->current_pen.builder;
}
SDL_PenID instance_id;
SDL_PenInfo info;
SDL_Window *tool_focus;
struct zwp_tablet_tool_v2 *wltool;
float x;
float y;
SDL_bool frame_motion_set;
float frame_axes[SDL_PEN_NUM_AXES];
Uint32 frame_axes_set;
int frame_pen_down;
int frame_buttons[3];
} SDL_WaylandPenTool;
static void tablet_tool_handle_type(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t type)
{
SDL_Pen *pen = Wayland_get_current_pen(data, tool);
SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data;
switch (type) {
case ZWP_TABLET_TOOL_V2_TYPE_ERASER:
pen->type = SDL_PEN_TYPE_ERASER;
break;
case ZWP_TABLET_TOOL_V2_TYPE_PEN:
pen->type = SDL_PEN_TYPE_PEN;
break;
case ZWP_TABLET_TOOL_V2_TYPE_PENCIL:
pen->type = SDL_PEN_TYPE_PENCIL;
break;
case ZWP_TABLET_TOOL_V2_TYPE_AIRBRUSH:
pen->type = SDL_PEN_TYPE_AIRBRUSH;
break;
case ZWP_TABLET_TOOL_V2_TYPE_BRUSH:
pen->type = SDL_PEN_TYPE_BRUSH;
break;
case ZWP_TABLET_TOOL_V2_TYPE_FINGER:
case ZWP_TABLET_TOOL_V2_TYPE_MOUSE:
case ZWP_TABLET_TOOL_V2_TYPE_LENS:
default:
pen->type = SDL_PEN_TYPE_NONE; /* Mark for deregistration */
#define CASE(typ) case ZWP_TABLET_TOOL_V2_TYPE_##typ: sdltool->info.subtype = SDL_PEN_TYPE_##typ; return
CASE(ERASER);
CASE(PEN);
CASE(PENCIL);
CASE(AIRBRUSH);
CASE(BRUSH);
#undef CASE
default: sdltool->info.subtype = SDL_PEN_TYPE_UNKNOWN; // we'll decline to add this when the `done` event comes through.
}
SDL_PenUpdateGUIDForType(&pen->guid, pen->type);
}
static void tablet_tool_handle_hardware_serial(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t serial_hi, uint32_t serial_lo)
{
#if !(SDL_PEN_DEBUG_NOID)
struct SDL_WaylandTool *sdltool = data;
struct SDL_WaylandTabletInput *input = sdltool->tablet;
if (!input->current_pen.builder_guid_complete) {
SDL_Pen *pen = Wayland_get_current_pen(data, tool);
SDL_PenUpdateGUIDForGeneric(&pen->guid, serial_hi, serial_lo);
if (serial_hi || serial_lo) {
input->current_pen.builder_guid_complete = SDL_TRUE;
}
}
#endif
// don't care about this atm.
}
static void tablet_tool_handle_hardware_id_wacom(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t id_hi, uint32_t id_lo)
{
#if !(SDL_PEN_DEBUG_NOID | SDL_PEN_DEBUG_NONWACOM)
struct SDL_WaylandTool *sdltool = data;
struct SDL_WaylandTabletInput *input = sdltool->tablet;
SDL_Pen *pen = Wayland_get_current_pen(data, tool);
Uint32 axis_flags;
#if SDL_PEN_DEBUG_NOSERIAL_WACOM /* Check: have we disabled pen serial ID decoding for testing? */
id_hi = 0;
#endif
SDL_PenUpdateGUIDForWacom(&pen->guid, id_lo, id_hi);
if (id_hi) { /* Have a serial number? */
input->current_pen.builder_guid_complete = SDL_TRUE;
}
if (SDL_PenModifyForWacomID(pen, id_lo, &axis_flags)) {
SDL_PenModifyAddCapabilities(pen, axis_flags);
}
#endif
SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data;
sdltool->info.wacom_id = id_lo;
}
static void tablet_tool_handle_capability(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t capability)
{
SDL_Pen *pen = Wayland_get_current_pen(data, tool);
SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data;
switch (capability) {
case ZWP_TABLET_TOOL_V2_CAPABILITY_TILT:
SDL_PenModifyAddCapabilities(pen, SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK);
break;
case ZWP_TABLET_TOOL_V2_CAPABILITY_PRESSURE:
SDL_PenModifyAddCapabilities(pen, SDL_PEN_AXIS_PRESSURE_MASK);
break;
case ZWP_TABLET_TOOL_V2_CAPABILITY_DISTANCE:
SDL_PenModifyAddCapabilities(pen, SDL_PEN_AXIS_DISTANCE_MASK);
break;
case ZWP_TABLET_TOOL_V2_CAPABILITY_ROTATION:
SDL_PenModifyAddCapabilities(pen, SDL_PEN_AXIS_ROTATION_MASK);
break;
case ZWP_TABLET_TOOL_V2_CAPABILITY_SLIDER:
SDL_PenModifyAddCapabilities(pen, SDL_PEN_AXIS_SLIDER_MASK);
break;
case ZWP_TABLET_TOOL_V2_CAPABILITY_WHEEL:
/* Presumably for tools other than pens? */
break;
default:
break;
#define CASE(wltyp,sdltyp) case ZWP_TABLET_TOOL_V2_CAPABILITY_##wltyp: sdltool->info.capabilities |= sdltyp; return
CASE(TILT, SDL_PEN_CAPABILITY_XTILT | SDL_PEN_CAPABILITY_YTILT);
CASE(PRESSURE, SDL_PEN_CAPABILITY_PRESSURE);
CASE(DISTANCE, SDL_PEN_CAPABILITY_DISTANCE);
CASE(ROTATION, SDL_PEN_CAPABILITY_ROTATION);
CASE(SLIDER, SDL_PEN_CAPABILITY_SLIDER);
#undef CASE
default: break; // unsupported here.
}
}
static void Wayland_tool_builder_reset(struct SDL_WaylandTabletInput *input)
{
input->current_pen.builder = NULL;
input->current_pen.builder_guid_complete = SDL_FALSE;
}
static void tablet_tool_handle_done(void *data, struct zwp_tablet_tool_v2 *tool)
{
SDL_Pen *pen = Wayland_get_current_pen(data, tool);
struct SDL_WaylandTool *sdltool = data;
struct SDL_WaylandTabletInput *input = sdltool->tablet;
if (!input->current_pen.builder_guid_complete) {
/* No complete GUID? Use tablet and tool device index */
SDL_PenUpdateGUIDForGeneric(&pen->guid, input->id, sdltool->penid);
}
SDL_PenModifyEnd(pen, SDL_TRUE);
Wayland_tool_builder_reset(input);
}
static void Wayland_tool_destroy(struct zwp_tablet_tool_v2 *tool)
{
if (tool) {
struct SDL_WaylandTool *waypen = zwp_tablet_tool_v2_get_user_data(tool);
if (waypen) {
SDL_free(waypen);
}
zwp_tablet_tool_v2_destroy(tool);
}
}
static void tablet_object_list_remove(struct SDL_WaylandTabletObjectListNode *head, void *object);
static void tablet_tool_handle_removed(void *data, struct zwp_tablet_tool_v2 *tool)
{
struct SDL_WaylandTool *waypen = zwp_tablet_tool_v2_get_user_data(tool);
struct SDL_WaylandTool *sdltool = data;
struct SDL_WaylandTabletInput *input = sdltool->tablet;
SDL_Pen *pen = Wayland_get_current_pen(data, tool);
if (pen) {
SDL_PenModifyEnd(pen, SDL_FALSE);
Wayland_tool_builder_reset(waypen->tablet);
Wayland_tool_destroy(tool);
} else {
zwp_tablet_tool_v2_destroy(tool);
SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data;
if (sdltool->instance_id) {
SDL_RemovePenDevice(0, sdltool->instance_id);
}
tablet_object_list_remove(input->tools, tool);
zwp_tablet_tool_v2_destroy(tool);
SDL_free(sdltool);
}
static void tablet_tool_handle_proximity_in(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t serial, struct zwp_tablet_v2 *tablet, struct wl_surface *surface)
{
struct SDL_WaylandTool *sdltool = data;
struct SDL_WaylandTabletInput *input = sdltool->tablet;
SDL_WindowData *window;
SDL_PenID penid = Wayland_get_penid(data, tool);
SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data;
SDL_WindowData *windowdata = surface ? Wayland_GetWindowDataForOwnedSurface(surface) : NULL;
sdltool->tool_focus = windowdata ? windowdata->sdlwindow : NULL;
if (!surface) {
return;
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(0, NULL, &sdltool->info, sdltool);
}
window = Wayland_GetWindowDataForOwnedSurface(surface);
if (window) {
input->tool_focus = window;
input->tool_prox_serial = serial;
if (penid) {
SDL_SendPenWindowEvent(0, penid, window->sdlwindow);
} else {
SDL_SetMouseFocus(window->sdlwindow);
}
SDL_SetCursor(NULL);
}
// According to the docs, this should be followed by a motion event, where we'll send our SDL events.
}
static void tablet_tool_handle_proximity_out(void *data, struct zwp_tablet_tool_v2 *tool)
{
struct SDL_WaylandTool *sdltool = data;
struct SDL_WaylandTabletInput *input = sdltool->tablet;
SDL_PenID penid = Wayland_get_penid(data, tool);
if (input->tool_focus) {
if (penid) {
SDL_SendPenWindowEvent(0, penid, NULL);
} else {
SDL_SetMouseFocus(NULL);
}
input->tool_focus = NULL;
SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data;
sdltool->tool_focus = NULL;
if (sdltool->instance_id) {
SDL_RemovePenDevice(0, sdltool->instance_id);
sdltool->instance_id = 0;
}
}
static void tablet_tool_handle_down(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t serial)
{
struct SDL_WaylandTool *sdltool = data;
struct SDL_WaylandTabletInput *input = sdltool->tablet;
input->current_pen.buttons_pressed |= SDL_PEN_DOWN_MASK;
input->current_pen.serial = serial;
SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data;
sdltool->frame_pen_down = 1;
}
static void tablet_tool_handle_up(void *data, struct zwp_tablet_tool_v2 *tool)
{
struct SDL_WaylandTool *sdltool = data;
struct SDL_WaylandTabletInput *input = sdltool->tablet;
input->current_pen.buttons_released |= SDL_PEN_DOWN_MASK;
SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data;
sdltool->frame_pen_down = 0;
}
static void tablet_tool_handle_motion(void *data, struct zwp_tablet_tool_v2 *tool, wl_fixed_t sx_w, wl_fixed_t sy_w)
{
struct SDL_WaylandTool *sdltool = data;
struct SDL_WaylandTabletInput *input = sdltool->tablet;
SDL_WindowData *window = input->tool_focus;
SDL_PenID penid = Wayland_get_penid(data, tool);
input->sx_w = sx_w;
input->sy_w = sy_w;
if (input->tool_focus) {
SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data;
SDL_Window *window = sdltool->tool_focus;
if (window) {
const SDL_WindowData *windowdata = window->internal;
const float sx_f = (float)wl_fixed_to_double(sx_w);
const float sy_f = (float)wl_fixed_to_double(sy_w);
const float sx = sx_f * window->pointer_scale.x;
const float sy = sy_f * window->pointer_scale.y;
if (penid != SDL_PEN_INVALID) {
input->current_pen.update_status.x = sx;
input->current_pen.update_status.y = sy;
input->current_pen.update_window = window;
} else {
/* Plain mouse event */
SDL_SendMouseMotion(0, window->sdlwindow, SDL_GLOBAL_MOUSE_ID, SDL_FALSE, sx, sy);
}
const float sx = sx_f * windowdata->pointer_scale.x;
const float sy = sy_f * windowdata->pointer_scale.y;
sdltool->x = sx;
sdltool->y = sy;
sdltool->frame_motion_set = SDL_TRUE;
}
}
static void tablet_tool_handle_pressure(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t pressure)
{
struct SDL_WaylandTool *sdltool = data;
struct SDL_WaylandTabletInput *input = sdltool->tablet;
input->current_pen.update_status.axes[SDL_PEN_AXIS_PRESSURE] = pressure / 65535.0f;
SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data;
sdltool->frame_axes[SDL_PEN_AXIS_PRESSURE] = ((float) pressure) / 65535.0f;
sdltool->frame_axes_set |= (1u << SDL_PEN_AXIS_PRESSURE);
if (pressure) {
input->current_pen.update_status.axes[SDL_PEN_AXIS_DISTANCE] = 0.0f;
sdltool->frame_axes[SDL_PEN_AXIS_DISTANCE] = 0.0f;
sdltool->frame_axes_set |= (1u << SDL_PEN_AXIS_DISTANCE);
}
}
static void tablet_tool_handle_distance(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t distance)
{
struct SDL_WaylandTool *sdltool = data;
struct SDL_WaylandTabletInput *input = sdltool->tablet;
input->current_pen.update_status.axes[SDL_PEN_AXIS_DISTANCE] = distance / 65535.0f;
SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data;
sdltool->frame_axes[SDL_PEN_AXIS_DISTANCE] = ((float) distance) / 65535.0f;
sdltool->frame_axes_set |= (1u << SDL_PEN_AXIS_DISTANCE);
if (distance) {
input->current_pen.update_status.axes[SDL_PEN_AXIS_PRESSURE] = 0.0f;
sdltool->frame_axes[SDL_PEN_AXIS_PRESSURE] = 0.0f;
sdltool->frame_axes_set |= (1u << SDL_PEN_AXIS_PRESSURE);
}
}
static void tablet_tool_handle_tilt(void *data, struct zwp_tablet_tool_v2 *tool, wl_fixed_t xtilt, wl_fixed_t ytilt)
{
struct SDL_WaylandTool *sdltool = data;
struct SDL_WaylandTabletInput *input = sdltool->tablet;
input->current_pen.update_status.axes[SDL_PEN_AXIS_XTILT] = (float)(wl_fixed_to_double(xtilt));
input->current_pen.update_status.axes[SDL_PEN_AXIS_YTILT] = (float)(wl_fixed_to_double(ytilt));
SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data;
sdltool->frame_axes[SDL_PEN_AXIS_XTILT] = (float)(wl_fixed_to_double(xtilt));
sdltool->frame_axes[SDL_PEN_AXIS_YTILT] = (float)(wl_fixed_to_double(ytilt));
sdltool->frame_axes_set |= (1u << SDL_PEN_AXIS_XTILT) | (1u << SDL_PEN_AXIS_YTILT);
}
static void tablet_tool_handle_button(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t serial, uint32_t button, uint32_t state)
{
struct SDL_WaylandTool *sdltool = data;
struct SDL_WaylandTabletInput *input = sdltool->tablet;
Uint16 mask = 0;
SDL_bool pressed = state == ZWP_TABLET_PAD_V2_BUTTON_STATE_PRESSED ? SDL_TRUE : SDL_FALSE;
/* record event serial number to report it later in tablet_tool_handle_frame() */
input->current_pen.serial = serial;
SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data;
int sdlbutton;
switch (button) {
/* see %{_includedir}/linux/input-event-codes.h */
case 0x14b: /* BTN_STYLUS */
mask = SDL_BUTTON_LMASK;
sdlbutton = 1;
break;
case 0x14c: /* BTN_STYLUS2 */
mask = SDL_BUTTON_MMASK;
sdlbutton = 2;
break;
case 0x149: /* BTN_STYLUS3 */
mask = SDL_BUTTON_RMASK;
sdlbutton = 3;
break;
default:
return; // don't care about this button, I guess.
}
if (pressed) {
input->current_pen.buttons_pressed |= mask;
} else {
input->current_pen.buttons_released |= mask;
}
SDL_assert((sdlbutton >= 1) && (sdlbutton <= SDL_arraysize(sdltool->frame_buttons)));
sdltool->frame_buttons[sdlbutton-1] = (state == ZWP_TABLET_PAD_V2_BUTTON_STATE_PRESSED) ? 1 : 0;
}
static void tablet_tool_handle_rotation(void *data, struct zwp_tablet_tool_v2 *tool, wl_fixed_t degrees)
{
struct SDL_WaylandTool *sdltool = data;
struct SDL_WaylandTabletInput *input = sdltool->tablet;
float rotation = (float)(wl_fixed_to_double(degrees));
/* map to -180.0f ... 179.0f range: */
input->current_pen.update_status.axes[SDL_PEN_AXIS_ROTATION] = rotation > 180.0f ? rotation - 360.0f : rotation;
SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data;
const float rotation = (float)(wl_fixed_to_double(degrees));
sdltool->frame_axes[SDL_PEN_AXIS_ROTATION] = (rotation > 180.0f) ? (rotation - 360.0f) : rotation; // map to -180.0f ... 179.0f range
sdltool->frame_axes_set |= (1u << SDL_PEN_AXIS_ROTATION);
}
static void tablet_tool_handle_slider(void *data, struct zwp_tablet_tool_v2 *tool, int32_t position)
{
struct SDL_WaylandTool *sdltool = data;
struct SDL_WaylandTabletInput *input = sdltool->tablet;
input->current_pen.update_status.axes[SDL_PEN_AXIS_SLIDER] = position / 65535.f;
SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data;
sdltool->frame_axes[SDL_PEN_AXIS_SLIDER] = position / 65535.f;
sdltool->frame_axes_set |= (1u << SDL_PEN_AXIS_SLIDER);
}
static void tablet_tool_handle_wheel(void *data, struct zwp_tablet_tool_v2 *tool, int32_t degrees, int32_t clicks)
{
/* not supported at the moment */
// not supported at the moment
}
static void tablet_tool_handle_frame(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t time)
{
struct SDL_WaylandTool *sdltool = data;
struct SDL_WaylandTabletInput *input = sdltool->tablet;
SDL_PenID penid = Wayland_get_penid(data, tool);
SDL_WindowData *window = input->current_pen.update_window;
SDL_PenStatusInfo *status = &input->current_pen.update_status;
int button;
int button_mask;
Uint64 timestamp = Wayland_GetEventTimestamp(SDL_MS_TO_NS(time));
SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data;
if (penid == 0 || !window) { /* Not a pen, or event reported out of focus */
return;
if (!sdltool->instance_id) {
return; // Not a pen we report on.
}
/* window == input->tool_focus */
/* All newly released buttons + PEN_UP event */
button_mask = input->current_pen.buttons_released;
if (button_mask & SDL_PEN_DOWN_MASK) {
/* Perform hit test, if appropriate */
if (!SDL_PenPerformHitTest()
|| !ProcessHitTest(window, input->sdlWaylandInput->seat, input->sx_w, input->sy_w, input->current_pen.serial)) {
SDL_SendPenTipEvent(timestamp, penid, SDL_RELEASED);
}
}
button_mask &= ~SDL_PEN_DOWN_MASK;
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;
for (button = 1; button_mask; ++button, button_mask >>= 1) {
if (button_mask & 1) {
SDL_SendPenButton(timestamp, penid, SDL_RELEASED, button);
// 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)) {
if (sdltool->frame_pen_down) {
SDL_SendPenMotion(timestamp, instance_id, window, sdltool->x, sdltool->y);
SDL_SendPenTouch(timestamp, instance_id, window, SDL_PRESSED, 0); // !!! FIXME: how do we know what tip is in use?
} else {
SDL_SendPenTouch(timestamp, instance_id, window, SDL_RELEASED, 0); // !!! FIXME: how do we know what tip is in use?
SDL_SendPenMotion(timestamp, instance_id, window, sdltool->x, sdltool->y);
}
} else {
if (sdltool->frame_pen_down != -1) {
SDL_SendPenTouch(timestamp, instance_id, window, sdltool->frame_pen_down ? SDL_PRESSED : SDL_RELEASED, 0); // !!! FIXME: how do we know what tip is in use?
}
if (sdltool->frame_motion_set) {
SDL_SendPenMotion(timestamp, instance_id, window, sdltool->x, sdltool->y);
}
}
/* All newly pressed buttons + PEN_DOWN event */
button_mask = input->current_pen.buttons_pressed;
if (button_mask & SDL_PEN_DOWN_MASK) {
/* Perform hit test, if appropriate */
if (!SDL_PenPerformHitTest()
|| !ProcessHitTest(window, input->sdlWaylandInput->seat, input->sx_w, input->sy_w, input->current_pen.serial)) {
SDL_SendPenTipEvent(timestamp, penid, SDL_PRESSED);
}
}
button_mask &= ~SDL_PEN_DOWN_MASK;
for (button = 1; button_mask; ++button, button_mask >>= 1) {
if (button_mask & 1) {
SDL_SendPenButton(timestamp, penid, SDL_PRESSED, button);
for (SDL_PenAxis i = 0; i < SDL_PEN_NUM_AXES; i++) {
if (sdltool->frame_axes_set & (1u << i)) {
SDL_SendPenAxis(timestamp, instance_id, window, i, sdltool->frame_axes[i]);
}
}
SDL_SendPenMotion(timestamp, penid, SDL_TRUE, status);
for (int i = 0; i < SDL_arraysize(sdltool->frame_buttons); i++) {
const int state = sdltool->frame_buttons[i];
if (state != -1) {
SDL_SendPenButton(timestamp, instance_id, window, state ? SDL_PRESSED : SDL_RELEASED, (Uint8) (i + 1));
sdltool->frame_buttons[i] = -1;
}
}
/* Wayland_UpdateImplicitGrabSerial will ignore serial 0, so it is safe to call with the default value */
Wayland_UpdateImplicitGrabSerial(input->sdlWaylandInput, input->current_pen.serial);
/* Reset masks for next tool frame */
input->current_pen.buttons_pressed = 0;
input->current_pen.buttons_released = 0;
input->current_pen.serial = 0;
// reset for next frame.
sdltool->frame_pen_down = -1;
sdltool->frame_motion_set = SDL_FALSE;
sdltool->frame_axes_set = 0;
}
static const struct zwp_tablet_tool_v2_listener tablet_tool_listener = {
@@ -2803,92 +2637,34 @@ static const struct zwp_tablet_tool_v2_listener tablet_tool_listener = {
tablet_tool_handle_frame
};
static struct SDL_WaylandTabletObjectListNode *tablet_object_list_new_node(void *object)
{
struct SDL_WaylandTabletObjectListNode *node;
node = SDL_calloc(1, sizeof(*node));
if (!node) {
return NULL;
}
node->next = NULL;
node->object = object;
return node;
}
static void tablet_object_list_append(struct SDL_WaylandTabletObjectListNode *head, void *object)
{
if (!head->object) {
head->object = object;
return;
}
while (head->next) {
head = head->next;
}
head->next = tablet_object_list_new_node(object);
}
static void tablet_object_list_destroy(struct SDL_WaylandTabletObjectListNode *head, void (*deleter)(void *object))
{
while (head) {
struct SDL_WaylandTabletObjectListNode *next = head->next;
if (head->object) {
(*deleter)(head->object);
}
SDL_free(head);
head = next;
}
}
void tablet_object_list_remove(struct SDL_WaylandTabletObjectListNode *head, void *object)
{
struct SDL_WaylandTabletObjectListNode **head_p = &head;
while (*head_p && (*head_p)->object != object) {
head_p = &((*head_p)->next);
}
if (*head_p) {
struct SDL_WaylandTabletObjectListNode *object_head = *head_p;
if (object_head == head) {
/* Must not remove head node */
head->object = NULL;
} else {
*head_p = object_head->next;
SDL_free(object_head);
}
}
}
static void tablet_seat_handle_tablet_added(void *data, struct zwp_tablet_seat_v2 *seat, struct zwp_tablet_v2 *tablet)
{
struct SDL_WaylandTabletInput *input = data;
tablet_object_list_append(input->tablets, tablet);
// don't care atm.
}
static void tablet_seat_handle_tool_added(void *data, struct zwp_tablet_seat_v2 *seat, struct zwp_tablet_tool_v2 *tool)
{
struct SDL_WaylandTabletInput *input = data;
struct SDL_WaylandTool *sdltool = SDL_calloc(1, sizeof(struct SDL_WaylandTool));
SDL_WaylandPenTool *sdltool = SDL_calloc(1, sizeof(*sdltool));
zwp_tablet_tool_v2_add_listener(tool, &tablet_tool_listener, sdltool);
zwp_tablet_tool_v2_set_user_data(tool, sdltool);
if (sdltool) { // if allocation failed, oh well, we won't report this device.
sdltool->wltool = tool;
sdltool->info.max_tilt = -1.0f;
sdltool->info.num_buttons = -1;
sdltool->frame_pen_down = -1;
for (int i = 0; i < SDL_arraysize(sdltool->frame_buttons); i++) {
sdltool->frame_buttons[i] = -1;
}
sdltool->tablet = input;
tablet_object_list_append(input->tools, tool);
// this will send a bunch of zwp_tablet_tool_v2 events right up front to tell
// us device details, with a "done" event to let us know we have everything.
zwp_tablet_tool_v2_add_listener(tool, &tablet_tool_listener, sdltool);
}
}
static void tablet_seat_handle_pad_added(void *data, struct zwp_tablet_seat_v2 *seat, struct zwp_tablet_pad_v2 *pad)
{
struct SDL_WaylandTabletInput *input = data;
tablet_object_list_append(input->pads, pad);
// we don't care atm.
}
static const struct zwp_tablet_seat_v2_listener tablet_seat_listener = {
@@ -2897,44 +2673,39 @@ static const struct zwp_tablet_seat_v2_listener tablet_seat_listener = {
tablet_seat_handle_pad_added
};
void Wayland_input_add_tablet(struct SDL_WaylandInput *input, struct SDL_WaylandTabletManager *tablet_manager)
void Wayland_input_init_tablet_support(struct SDL_WaylandInput *input, struct zwp_tablet_manager_v2 *tablet_manager)
{
struct SDL_WaylandTabletInput *tablet_input;
static Uint32 num_tablets = 0;
if (!tablet_manager || !input->seat) {
return;
}
tablet_input = SDL_calloc(1, sizeof(*tablet_input));
SDL_WaylandTabletInput *tablet_input = SDL_calloc(1, sizeof(*tablet_input));
if (!tablet_input) {
return;
}
input->tablet = tablet_input;
tablet_input->input = input;
tablet_input->seat = zwp_tablet_manager_v2_get_tablet_seat(tablet_manager, input->seat);
tablet_input->sdlWaylandInput = input;
tablet_input->seat = zwp_tablet_manager_v2_get_tablet_seat((struct zwp_tablet_manager_v2 *)tablet_manager, input->seat);
tablet_input->tablets = tablet_object_list_new_node(NULL);
tablet_input->tools = tablet_object_list_new_node(NULL);
tablet_input->pads = tablet_object_list_new_node(NULL);
tablet_input->id = num_tablets++;
zwp_tablet_seat_v2_add_listener((struct zwp_tablet_seat_v2 *)tablet_input->seat, &tablet_seat_listener, tablet_input);
zwp_tablet_seat_v2_add_listener(tablet_input->seat, &tablet_seat_listener, tablet_input);
}
#define TABLET_OBJECT_LIST_DELETER(fun) (void (*)(void *)) fun
void Wayland_input_destroy_tablet(struct SDL_WaylandInput *input)
static void Wayland_remove_all_pens_callback(SDL_PenID instance_id, void *handle, void *userdata)
{
tablet_object_list_destroy(input->tablet->pads, TABLET_OBJECT_LIST_DELETER(zwp_tablet_pad_v2_destroy));
tablet_object_list_destroy(input->tablet->tools, TABLET_OBJECT_LIST_DELETER(Wayland_tool_destroy));
tablet_object_list_destroy(input->tablet->tablets, TABLET_OBJECT_LIST_DELETER(zwp_tablet_v2_destroy));
SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) handle;
zwp_tablet_tool_v2_destroy(sdltool->wltool);
SDL_free(sdltool);
}
zwp_tablet_seat_v2_destroy(input->tablet->seat);
void Wayland_input_quit_tablet_support(struct SDL_WaylandInput *input)
{
SDL_RemoveAllPenDevices(Wayland_remove_all_pens_callback, NULL);
SDL_free(input->tablet);
input->tablet = NULL;
if (input && input->tablet_input) {
zwp_tablet_seat_v2_destroy(input->tablet_input->seat);
SDL_free(input->tablet_input);
input->tablet_input = NULL;
}
}
void Wayland_input_initialize_seat(SDL_VideoData *d)
@@ -2957,7 +2728,7 @@ void Wayland_input_initialize_seat(SDL_VideoData *d)
wl_seat_set_user_data(input->seat, input);
if (d->tablet_manager) {
Wayland_input_add_tablet(input, d->tablet_manager);
Wayland_input_init_tablet_support(d->input, d->tablet_manager);
}
WAYLAND_wl_display_flush(d->display);
@@ -3049,8 +2820,8 @@ void Wayland_display_destroy_input(SDL_VideoData *d)
}
}
if (input->tablet) {
Wayland_input_destroy_tablet(input);
if (input->tablet_input) {
Wayland_input_quit_tablet_support(input);
}
if (input->seat) {

View File

@@ -41,41 +41,11 @@ enum SDL_WaylandAxisEvent
struct SDL_WaylandTabletSeat;
struct SDL_WaylandTabletObjectListNode
typedef struct SDL_WaylandTabletInput
{
void *object;
struct SDL_WaylandTabletObjectListNode *next;
};
struct SDL_WaylandTabletInput
{
struct SDL_WaylandInput *sdlWaylandInput;
struct SDL_WaylandInput *input;
struct zwp_tablet_seat_v2 *seat;
struct SDL_WaylandTabletObjectListNode *tablets;
struct SDL_WaylandTabletObjectListNode *tools;
struct SDL_WaylandTabletObjectListNode *pads;
Uint32 id;
Uint32 num_pens; /* next pen ID is num_pens+1 */
struct SDL_WaylandCurrentPen
{
SDL_Pen *builder; /* pen that is being defined or receiving updates, if any */
SDL_bool builder_guid_complete; /* have complete/precise GUID information */
SDL_PenStatusInfo update_status; /* collects pen update information before sending event */
Uint16 buttons_pressed; /* Mask of newly pressed buttons, plus SDL_PEN_DOWN_MASK for PEN_DOWN */
Uint16 buttons_released; /* Mask of newly pressed buttons, plus SDL_PEN_DOWN_MASK for PEN_UP */
Uint32 serial; /* Most recent serial event number observed, or 0 */
SDL_WindowData *update_window; /* NULL while no event is in progress, otherwise the affected window */
} current_pen;
SDL_WindowData *tool_focus;
uint32_t tool_prox_serial;
/* Last motion end location (kept separate from sx_w, sy_w for the mouse pointer) */
wl_fixed_t sx_w;
wl_fixed_t sy_w;
};
} SDL_WaylandTabletInput;
typedef struct
{
@@ -169,7 +139,7 @@ struct SDL_WaylandInput
SDL_WaylandKeyboardRepeat keyboard_repeat;
struct SDL_WaylandTabletInput *tablet;
SDL_WaylandTabletInput *tablet_input;
SDL_bool keyboard_is_virtual;
@@ -178,11 +148,6 @@ struct SDL_WaylandInput
SDL_Keymod locked_modifiers;
};
struct SDL_WaylandTool
{
SDL_PenID penid;
struct SDL_WaylandTabletInput *tablet;
};
extern Uint64 Wayland_GetTouchTimestamp(struct SDL_WaylandInput *input, Uint32 wl_timestamp_ms);
@@ -209,8 +174,8 @@ extern int Wayland_input_unconfine_pointer(struct SDL_WaylandInput *input, SDL_W
extern int Wayland_input_grab_keyboard(SDL_Window *window, struct SDL_WaylandInput *input);
extern int Wayland_input_ungrab_keyboard(SDL_Window *window);
extern void Wayland_input_add_tablet(struct SDL_WaylandInput *input, struct SDL_WaylandTabletManager *tablet_manager);
extern void Wayland_input_destroy_tablet(struct SDL_WaylandInput *input);
extern void Wayland_input_init_tablet_support(struct SDL_WaylandInput *input, struct zwp_tablet_manager_v2 *tablet_manager);
extern void Wayland_input_quit_tablet_support(struct SDL_WaylandInput *input);
extern void Wayland_RegisterTimestampListeners(struct SDL_WaylandInput *input);
extern void Wayland_CreateCursorShapeDevice(struct SDL_WaylandInput *input);

View File

@@ -1167,7 +1167,7 @@ static void display_handle_global(void *data, struct wl_registry *registry, uint
d->decoration_manager = wl_registry_bind(d->registry, id, &zxdg_decoration_manager_v1_interface, 1);
} else if (SDL_strcmp(interface, "zwp_tablet_manager_v2") == 0) {
d->tablet_manager = wl_registry_bind(d->registry, id, &zwp_tablet_manager_v2_interface, 1);
Wayland_input_add_tablet(d->input, d->tablet_manager);
Wayland_input_init_tablet_support(d->input, d->tablet_manager);
} else if (SDL_strcmp(interface, "zxdg_output_manager_v1") == 0) {
version = SDL_min(version, 3); /* Versions 1 through 3 are supported. */
d->xdg_output_manager = wl_registry_bind(d->registry, id, &zxdg_output_manager_v1_interface, version);

View File

@@ -33,7 +33,6 @@
struct xkb_context;
struct SDL_WaylandInput;
struct SDL_WaylandTabletManager;
typedef struct
{
@@ -84,10 +83,10 @@ struct SDL_VideoData
struct wp_alpha_modifier_v1 *wp_alpha_modifier_v1;
struct kde_output_order_v1 *kde_output_order;
struct frog_color_management_factory_v1 *frog_color_management_factory_v1;
struct zwp_tablet_manager_v2 *tablet_manager;
struct xkb_context *xkb_context;
struct SDL_WaylandInput *input;
struct SDL_WaylandTabletManager *tablet_manager;
struct wl_list output_list;
struct wl_list output_order;

View File

@@ -1996,9 +1996,6 @@ void X11_PumpEvents(SDL_VideoDevice *_this)
}
if (data->xinput_hierarchy_changed) {
#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2
X11_InitPen(_this);
#endif
X11_Xinput2UpdateDevices(_this, SDL_FALSE);
data->xinput_hierarchy_changed = SDL_FALSE;
}

View File

@@ -28,101 +28,79 @@
#include "SDL_x11video.h"
#include "SDL_x11xinput2.h"
#define PEN_ERASER_ID_MAXLEN 256 /* Max # characters of device name to scan */
#define PEN_ERASER_NAME_TAG "eraser" /* String constant to identify erasers */
#define DEBUG_PEN (SDL_PEN_DEBUG_NOID | SDL_PEN_DEBUG_NONWACOM | SDL_PEN_DEBUG_UNKNOWN_WACOM | SDL_PEN_DEBUG_NOSERIAL_WACOM)
#define SDL_PEN_AXIS_VALUATOR_MISSING -1
/* X11-specific information attached to each pen */
typedef struct xinput2_pen
/* Does this device have a valuator for pressure sensitivity? */
static SDL_bool X11_XInput2DeviceIsPen(SDL_VideoDevice *_this, const XIDeviceInfo *dev)
{
float axis_min[SDL_PEN_NUM_AXES];
float axis_max[SDL_PEN_NUM_AXES];
float slider_bias; /* shift value to add to PEN_AXIS_SLIDER (before normalisation) */
float rotation_bias; /* rotation to add to PEN_AXIS_ROTATION (after normalisation) */
Sint8 valuator_for_axis[SDL_PEN_NUM_AXES]; /* SDL_PEN_AXIS_VALUATOR_MISSING if not supported */
} xinput2_pen;
/* X11 atoms */
static struct
{
int initialized; /* initialised to 0 */
Atom device_product_id;
Atom abs_pressure;
Atom abs_tilt_x;
Atom abs_tilt_y;
Atom wacom_serial_ids;
Atom wacom_tool_type;
} pen_atoms;
/*
* Mapping from X11 device IDs to pen IDs
*
* In X11, the same device ID may represent any number of pens. We
* thus cannot directly use device IDs as pen IDs.
*/
static struct
{
int num_pens_known; /* Number of currently known pens (based on their GUID); used to give pen ID to new pens */
int num_entries; /* Number of X11 device IDs that correspond to pens */
struct pen_device_id_mapping
{
Uint32 deviceid;
Uint32 pen_id;
} * entries; /* Current pen to device ID mappings */
} pen_map;
typedef enum
{
SDL_PEN_VENDOR_UNKNOWN = 0,
SDL_PEN_VENDOR_WACOM
} sdl_pen_vendor;
/* Information to identify pens during discovery */
typedef struct
{
sdl_pen_vendor vendor;
SDL_GUID guid;
SDL_PenSubtype heuristic_type; /* Distinguish pen+eraser devices with shared bus ID */
Uint32 devicetype_id, serial; /* used by PEN_VENDOR_WACOM */
Uint32 deviceid;
} pen_identity;
int X11_PenIDFromDeviceID(int deviceid)
{
int i;
for (i = 0; i < pen_map.num_entries; ++i) {
if (pen_map.entries[i].deviceid == deviceid) {
return pen_map.entries[i].pen_id;
const SDL_VideoData *data = (SDL_VideoData *)_this->internal;
for (int i = 0; i < dev->num_classes; i++) {
const XIAnyClassInfo *classinfo = dev->classes[i];
if (classinfo->type == XIValuatorClass) {
const XIValuatorClassInfo *val_classinfo = (const XIValuatorClassInfo *)classinfo;
if (val_classinfo->label == data->pen_atom_abs_pressure) {
return SDL_TRUE;
}
}
}
return SDL_PEN_INVALID;
return SDL_FALSE;
}
static void pen_atoms_ensure_initialized(SDL_VideoDevice *_this)
/* Heuristically determines if device is an eraser */
static SDL_bool X11_XInput2PenIsEraser(SDL_VideoDevice *_this, int deviceid, char *devicename)
{
#define PEN_ERASER_NAME_TAG "eraser" /* String constant to identify erasers */
SDL_VideoData *data = (SDL_VideoData *)_this->internal;
if (pen_atoms.initialized) {
return;
}
/* Create atoms if they don't exist yet to pre-empt hotplugging updates */
pen_atoms.device_product_id = X11_XInternAtom(data->display, "Device Product ID", False);
pen_atoms.wacom_serial_ids = X11_XInternAtom(data->display, "Wacom Serial IDs", False);
pen_atoms.wacom_tool_type = X11_XInternAtom(data->display, "Wacom Tool Type", False);
pen_atoms.abs_pressure = X11_XInternAtom(data->display, "Abs Pressure", False);
pen_atoms.abs_tilt_x = X11_XInternAtom(data->display, "Abs Tilt X", False);
pen_atoms.abs_tilt_y = X11_XInternAtom(data->display, "Abs Tilt Y", False);
if (data->pen_atom_wacom_tool_type != None) {
Atom type_return;
int format_return;
unsigned long num_items_return;
unsigned long bytes_after_return;
unsigned char *tooltype_name_info = NULL;
pen_atoms.initialized = 1;
/* Try Wacom-specific method */
if (Success == X11_XIGetProperty(data->display, deviceid,
data->pen_atom_wacom_tool_type,
0, 32, False,
AnyPropertyType, &type_return, &format_return,
&num_items_return, &bytes_after_return,
&tooltype_name_info) &&
tooltype_name_info != NULL && num_items_return > 0) {
SDL_bool result = SDL_FALSE;
char *tooltype_name = NULL;
if (type_return == XA_ATOM) {
/* Atom instead of string? Un-intern */
Atom atom = *((Atom *)tooltype_name_info);
if (atom != None) {
tooltype_name = X11_XGetAtomName(data->display, atom);
}
} else if (type_return == XA_STRING && format_return == 8) {
tooltype_name = (char *)tooltype_name_info;
}
if (tooltype_name) {
if (0 == SDL_strcasecmp(tooltype_name, PEN_ERASER_NAME_TAG)) {
result = SDL_TRUE;
}
X11_XFree(tooltype_name_info);
return result;
}
}
}
/* Non-Wacom device? */
/* We assume that a device is an eraser if its name contains the string "eraser".
* Unfortunately there doesn't seem to be a clean way to distinguish these cases (as of 2022-03). */
return (SDL_strcasestr(devicename, PEN_ERASER_NAME_TAG)) ? SDL_TRUE : SDL_FALSE;
}
/* Read out an integer property and store into a preallocated Sint32 array, extending 8 and 16 bit values suitably.
Returns number of Sint32s written (<= max_words), or 0 on error. */
static size_t xinput2_pen_get_int_property(SDL_VideoDevice *_this, int deviceid, Atom property, Sint32 *dest, size_t max_words)
// Read out an integer property and store into a preallocated Sint32 array, extending 8 and 16 bit values suitably.
// Returns number of Sint32s written (<= max_words), or 0 on error.
static size_t X11_XInput2PenGetIntProperty(SDL_VideoDevice *_this, int deviceid, Atom property, Sint32 *dest, size_t max_words)
{
const SDL_VideoData *data = (SDL_VideoData *)_this->internal;
Atom type_return;
@@ -165,498 +143,249 @@ static size_t xinput2_pen_get_int_property(SDL_VideoDevice *_this, int deviceid,
X11_XFree(output);
return to_copy;
}
return 0; /* type mismatch */
return 0; // type mismatch
}
/* 32 bit vendor + device ID from evdev */
static Uint32 xinput2_pen_evdevid(SDL_VideoDevice *_this, int deviceid)
// Identify Wacom devices (if SDL_TRUE is returned) and extract their device type and serial IDs
static SDL_bool X11_XInput2PenWacomDeviceID(SDL_VideoDevice *_this, int deviceid, Uint32 *wacom_devicetype_id, Uint32 *wacom_serial)
{
#if !(SDL_PEN_DEBUG_NOID)
Sint32 ids[2];
pen_atoms_ensure_initialized(_this);
if (2 != xinput2_pen_get_int_property(_this, deviceid, pen_atoms.device_product_id, ids, 2)) {
return 0;
}
return ((ids[0] << 16) | (ids[1] & 0xffff));
#else /* Testing: pretend that we have no ID (not sure if this can happen IRL) */
return 0;
#endif
}
/* Gets reasonably-unique GUID for the device */
static void xinput2_pen_update_generic_guid(SDL_VideoDevice *_this, pen_identity *pident, int deviceid)
{
Uint32 evdevid = xinput2_pen_evdevid(_this, deviceid); /* also initialises pen_atoms */
if (!evdevid) {
/* Fallback: if no evdevid is available; try to at least distinguish devices within the
current session. This is a poor GUID and our last resort. */
evdevid = deviceid;
}
SDL_PenUpdateGUIDForGeneric(&pident->guid, 0, evdevid);
}
/* Identify Wacom devices (if SDL_TRUE is returned) and extract their device type and serial IDs */
static SDL_bool xinput2_wacom_deviceid(SDL_VideoDevice *_this, int deviceid, Uint32 *wacom_devicetype_id, Uint32 *wacom_serial)
{
#if !(SDL_PEN_DEBUG_NONWACOM) /* Can be disabled for testing */
SDL_VideoData *data = (SDL_VideoData *)_this->internal;
Sint32 serial_id_buf[3];
int result;
pen_atoms_ensure_initialized(_this);
if ((result = xinput2_pen_get_int_property(_this, deviceid, pen_atoms.wacom_serial_ids, serial_id_buf, 3)) == 3) {
if ((result = X11_XInput2PenGetIntProperty(_this, deviceid, data->pen_atom_wacom_serial_ids, serial_id_buf, 3)) == 3) {
*wacom_devicetype_id = serial_id_buf[2];
*wacom_serial = serial_id_buf[1];
#if SDL_PEN_DEBUG_NOSERIAL_WACOM /* Disabled for testing? */
*wacom_serial = 0;
#endif
return SDL_TRUE;
}
#endif
*wacom_devicetype_id = *wacom_serial = 0;
return SDL_FALSE;
}
/* Heuristically determines if device is an eraser */
static SDL_bool xinput2_pen_is_eraser(SDL_VideoDevice *_this, int deviceid, char *devicename)
typedef struct FindPenByDeviceIDData
{
int x11_deviceid;
void *handle;
} FindPenByDeviceIDData;
static SDL_bool FindPenByDeviceID(void *handle, void *userdata)
{
const X11_PenHandle *x11_handle = (const X11_PenHandle *) handle;
FindPenByDeviceIDData *data = (FindPenByDeviceIDData *) userdata;
if (x11_handle->x11_deviceid != data->x11_deviceid) {
return SDL_FALSE;
}
data->handle = handle;
return SDL_TRUE;
}
X11_PenHandle *X11_FindPenByDeviceID(int deviceid)
{
FindPenByDeviceIDData data;
data.x11_deviceid = deviceid;
data.handle = NULL;
SDL_FindPenByCallback(FindPenByDeviceID, &data);
return (X11_PenHandle *) data.handle;
}
static X11_PenHandle *X11_MaybeAddPen(SDL_VideoDevice *_this, const XIDeviceInfo *dev)
{
SDL_VideoData *data = (SDL_VideoData *)_this->internal;
char dev_name[PEN_ERASER_ID_MAXLEN];
int k;
SDL_PenCapabilityFlags capabilities = 0;
X11_PenHandle *handle = NULL;
pen_atoms_ensure_initialized(_this);
if ((dev->use != XISlavePointer && (dev->use != XIFloatingSlave)) || dev->enabled == 0 || !X11_XInput2DeviceIsPen(_this, dev)) {
return NULL; // Only track physical devices that are enabled and look like pens
} else if ((handle = X11_FindPenByDeviceID(dev->deviceid)) != 0) {
return handle; // already have this pen, skip it.
} else if ((handle = SDL_calloc(1, sizeof (*handle))) == NULL) {
return NULL; // oh well.
}
if (pen_atoms.wacom_tool_type != None) {
Atom type_return;
int format_return;
unsigned long num_items_return;
unsigned long bytes_after_return;
unsigned char *tooltype_name_info = NULL;
for (int i = 0; i < SDL_arraysize(handle->valuator_for_axis); i++) {
handle->valuator_for_axis[i] = SDL_X11_PEN_AXIS_VALUATOR_MISSING; // until proven otherwise
}
/* Try Wacom-specific method */
if (Success == X11_XIGetProperty(data->display, deviceid,
pen_atoms.wacom_tool_type,
0, 32, False,
AnyPropertyType, &type_return, &format_return,
&num_items_return, &bytes_after_return,
&tooltype_name_info) &&
tooltype_name_info != NULL && num_items_return > 0) {
int total_buttons = 0;
for (int i = 0; i < dev->num_classes; i++) {
const XIAnyClassInfo *classinfo = dev->classes[i];
if (classinfo->type == XIButtonClass) {
const XIButtonClassInfo *button_classinfo = (const XIButtonClassInfo *)classinfo;
total_buttons += button_classinfo->num_buttons;
} else if (classinfo->type == XIValuatorClass) {
const XIValuatorClassInfo *val_classinfo = (const XIValuatorClassInfo *)classinfo;
const Sint8 valuator_nr = val_classinfo->number;
const Atom vname = val_classinfo->label;
const float min = (float)val_classinfo->min;
const float max = (float)val_classinfo->max;
SDL_bool use_this_axis = SDL_TRUE;
SDL_PenAxis axis = SDL_PEN_NUM_AXES;
SDL_bool result = SDL_FALSE;
char *tooltype_name = NULL;
if (type_return == XA_ATOM) {
/* Atom instead of string? Un-intern */
Atom atom = *((Atom *)tooltype_name_info);
if (atom != None) {
tooltype_name = X11_XGetAtomName(data->display, atom);
}
} else if (type_return == XA_STRING && format_return == 8) {
tooltype_name = (char *)tooltype_name_info;
// afaict, SDL_PEN_AXIS_DISTANCE is never reported by XInput2 (Wayland can offer it, though)
if (vname == data->pen_atom_abs_pressure) {
axis = SDL_PEN_AXIS_PRESSURE;
} else if (vname == data->pen_atom_abs_tilt_x) {
axis = SDL_PEN_AXIS_XTILT;
} else if (vname == data->pen_atom_abs_tilt_y) {
axis = SDL_PEN_AXIS_YTILT;
} else {
use_this_axis = SDL_FALSE;
}
if (tooltype_name) {
if (0 == SDL_strcasecmp(tooltype_name, PEN_ERASER_NAME_TAG)) {
result = SDL_TRUE;
}
X11_XFree(tooltype_name_info);
// !!! FIXME: there are wacom-specific hacks for getting SDL_PEN_AXIS_(ROTATION|SLIDER) on some devices, but for simplicity, we're skipping all that for now.
return result;
if (use_this_axis) {
capabilities |= SDL_GetPenCapabilityFromAxis(axis);
handle->valuator_for_axis[axis] = valuator_nr;
handle->axis_min[axis] = min;
handle->axis_max[axis] = max;
}
}
}
/* Non-Wacom device? */
/* We assume that a device is an eraser if its name contains the string "eraser".
* Unfortunately there doesn't seem to be a clean way to distinguish these cases (as of 2022-03). */
// We have a pen if and only if the device measures pressure.
// We checked this in X11_XInput2DeviceIsPen, so just assert it here.
SDL_assert(capabilities & SDL_PEN_CAPABILITY_PRESSURE);
SDL_strlcpy(dev_name, devicename, PEN_ERASER_ID_MAXLEN);
/* lowercase device name string so we can use strstr() */
for (k = 0; dev_name[k]; ++k) {
dev_name[k] = SDL_tolower(dev_name[k]);
const SDL_bool is_eraser = X11_XInput2PenIsEraser(_this, dev->deviceid, dev->name);
Uint32 wacom_devicetype_id = 0;
Uint32 wacom_serial = 0;
X11_XInput2PenWacomDeviceID(_this, dev->deviceid, &wacom_devicetype_id, &wacom_serial);
SDL_PenInfo peninfo;
SDL_zero(peninfo);
peninfo.capabilities = capabilities;
peninfo.max_tilt = -1;
peninfo.wacom_id = wacom_devicetype_id;
peninfo.num_buttons = total_buttons;
peninfo.subtype = is_eraser ? SDL_PEN_TYPE_ERASER : SDL_PEN_TYPE_PEN;
if (is_eraser) {
peninfo.capabilities |= SDL_PEN_CAPABILITY_ERASER;
}
return (SDL_strstr(dev_name, PEN_ERASER_NAME_TAG)) ? SDL_TRUE : SDL_FALSE;
handle->is_eraser = is_eraser;
handle->x11_deviceid = dev->deviceid;
handle->pen = SDL_AddPenDevice(0, dev->name, &peninfo, handle);
if (!handle->pen) {
SDL_free(handle);
return NULL;
}
return handle;
}
/* Gets GUID and other identifying information for the device using the best known method */
static pen_identity xinput2_identify_pen(SDL_VideoDevice *_this, int deviceid, char *name)
X11_PenHandle *X11_MaybeAddPenByDeviceID(SDL_VideoDevice *_this, int deviceid)
{
pen_identity pident;
pident.devicetype_id = 0ul;
pident.serial = 0ul;
pident.deviceid = deviceid;
pident.heuristic_type = SDL_PEN_TYPE_PEN;
SDL_memset(pident.guid.data, 0, sizeof(pident.guid.data));
if (xinput2_pen_is_eraser(_this, deviceid, name)) {
pident.heuristic_type = SDL_PEN_TYPE_ERASER;
SDL_VideoData *data = (SDL_VideoData *)_this->internal;
int num_device_info = 0;
XIDeviceInfo *device_info = X11_XIQueryDevice(data->display, deviceid, &num_device_info);
if (device_info) {
SDL_assert(num_device_info == 1);
return X11_MaybeAddPen(_this, device_info);
}
if (xinput2_wacom_deviceid(_this, deviceid, &pident.devicetype_id, &pident.serial)) {
pident.vendor = SDL_PEN_VENDOR_WACOM;
SDL_PenUpdateGUIDForWacom(&pident.guid, pident.devicetype_id, pident.serial);
#if DEBUG_PEN
printf("[pen] Pen %d reports Wacom device_id %x\n",
deviceid, pident.devicetype_id);
#endif
} else {
pident.vendor = SDL_PEN_VENDOR_UNKNOWN;
}
if (!pident.serial) {
/* If the pen has a serial number, we can move it across tablets and retain its identity.
Otherwise, we use the evdev ID as part of its GUID, which may mean that we identify it with the tablet. */
xinput2_pen_update_generic_guid(_this, &pident, deviceid);
}
SDL_PenUpdateGUIDForType(&pident.guid, pident.heuristic_type);
return pident;
return NULL;
}
static void xinput2_pen_free_deviceinfo(Uint32 deviceid, void *x11_peninfo, void *context)
void X11_RemovePenByDeviceID(int deviceid)
{
SDL_free(x11_peninfo);
}
static void xinput2_merge_deviceinfo(xinput2_pen *dest, xinput2_pen *src)
{
*dest = *src;
}
/**
* Fill in vendor-specific device information, if available
*
* For Wacom pens: identify number of buttons and extra axis (if present)
*
* \param _this global state
* \param dev The device to analyse
* \param pen The pen to initialise
* \param pident Pen identity information
* \param[out] valuator_5 Meaning of the valuator with offset 5, if any
* (written only if known and if the device has a 6th axis,
* e.g., for the Wacom Art Pen and Wacom Airbrush Pen)
* \param[out] axes Bitmask of all possibly supported axes
*
* This function identifies Wacom device types through a Wacom-specific device ID.
* It then fills in pen details from an internal database.
* If the device seems to be a Wacom pen/eraser but can't be identified, the function
* leaves "axes" untouched and sets the other outputs to common defaults.
*
* There is no explicit support for other vendors, though vendors that
* emulate the Wacom API might be supported.
*
* Unsupported devices will keep the default settings.
*/
static void xinput2_vendor_peninfo(SDL_VideoDevice *_this, const XIDeviceInfo *dev, SDL_Pen *pen, pen_identity pident, int *valuator_5, Uint32 *axes)
{
switch (pident.vendor) {
case SDL_PEN_VENDOR_WACOM:
{
if (SDL_PenModifyForWacomID(pen, pident.devicetype_id, axes)) {
if (*axes & SDL_PEN_AXIS_SLIDER_MASK) {
/* Air Brush Pen or eraser */
*valuator_5 = SDL_PEN_AXIS_SLIDER;
} else if (*axes & SDL_PEN_AXIS_ROTATION_MASK) {
/* Art Pen or eraser, or 6D Art Pen */
*valuator_5 = SDL_PEN_AXIS_ROTATION;
}
return;
} else {
#if DEBUG_PEN
printf("[pen] Could not identify wacom pen %d with device id %x, using default settings\n",
pident.deviceid, pident.devicetype_id);
#endif
break;
}
X11_PenHandle *handle = X11_FindPenByDeviceID(deviceid);
if (handle) {
SDL_RemovePenDevice(0, handle->pen);
SDL_free(handle);
}
default:
#if DEBUG_PEN
printf("[pen] Pen %d is not from a known vendor\n", pident.deviceid);
#endif
break;
}
/* Fall back to default heuristics for identifying device type */
SDL_strlcpy(pen->name, dev->name, SDL_PEN_MAX_NAME);
pen->type = pident.heuristic_type;
}
/* Does this device have a valuator for pressure sensitivity? */
static SDL_bool xinput2_device_is_pen(SDL_VideoDevice *_this, const XIDeviceInfo *dev)
{
int classct;
pen_atoms_ensure_initialized(_this);
for (classct = 0; classct < dev->num_classes; ++classct) {
const XIAnyClassInfo *classinfo = dev->classes[classct];
switch (classinfo->type) {
case XIValuatorClass:
{
XIValuatorClassInfo *val_classinfo = (XIValuatorClassInfo *)classinfo;
Atom vname = val_classinfo->label;
if (vname == pen_atoms.abs_pressure) {
return SDL_TRUE;
}
}
}
}
return SDL_FALSE;
}
void X11_InitPen(SDL_VideoDevice *_this)
{
SDL_VideoData *data = (SDL_VideoData *)_this->internal;
int i;
XIDeviceInfo *device_info;
int num_device_info;
device_info = X11_XIQueryDevice(data->display, XIAllDevices, &num_device_info);
if (!device_info) {
return;
#define LOOKUP_PEN_ATOM(X) X11_XInternAtom(data->display, X, False)
data->pen_atom_device_product_id = LOOKUP_PEN_ATOM("Device Product ID");
data->pen_atom_wacom_serial_ids = LOOKUP_PEN_ATOM("Wacom Serial IDs");
data->pen_atom_wacom_tool_type = LOOKUP_PEN_ATOM("Wacom Tool Type");
data->pen_atom_abs_pressure = LOOKUP_PEN_ATOM("Abs Pressure");
data->pen_atom_abs_tilt_x = LOOKUP_PEN_ATOM("Abs Tilt X");
data->pen_atom_abs_tilt_y = LOOKUP_PEN_ATOM("Abs Tilt Y");
#undef LOOKUP_PEN_ATOM
// Do an initial check on devices. After this, we'll add/remove individual pens when XI_HierarchyChanged events alert us.
int num_device_info = 0;
XIDeviceInfo *device_info = X11_XIQueryDevice(data->display, XIAllDevices, &num_device_info);
if (device_info) {
for (int i = 0; i < num_device_info; i++) {
X11_MaybeAddPen(_this, &device_info[i]);
}
X11_XIFreeDeviceInfo(device_info);
}
}
/* Reset the device id -> pen map */
if (pen_map.entries) {
SDL_free(pen_map.entries);
pen_map.entries = NULL;
pen_map.num_entries = 0;
}
static void X11_FreePenHandle(SDL_PenID instance_id, void *handle, void *userdata)
{
SDL_free(handle);
}
SDL_PenGCMark();
void X11_QuitPen(SDL_VideoDevice *_this)
{
SDL_RemoveAllPenDevices(X11_FreePenHandle, NULL);
}
for (i = 0; i < num_device_info; ++i) {
const XIDeviceInfo *dev = &device_info[i];
int classct;
xinput2_pen pen_device;
Uint32 capabilities = 0;
Uint32 axis_mask = ~0; /* Permitted axes (default: all) */
int valuator_5_axis = -1; /* For Wacom devices, the 6th valuator (offset 5) has a model-specific meaning */
pen_identity pident;
SDL_PenID pen_id;
SDL_Pen *pen;
int old_num_pens_known = pen_map.num_pens_known;
int k;
/* Only track physical devices that are enabled and look like pens */
if (dev->use != XISlavePointer || dev->enabled == 0 || !xinput2_device_is_pen(_this, dev)) {
static void X11_XInput2NormalizePenAxes(const X11_PenHandle *pen, float *coords)
{
/* Normalise axes */
for (int axis = 0; axis < SDL_PEN_NUM_AXES; ++axis) {
const int valuator = pen->valuator_for_axis[axis];
if (valuator == SDL_X11_PEN_AXIS_VALUATOR_MISSING) {
continue;
}
pen_device.slider_bias = 0.0f;
pen_device.rotation_bias = 0.0f;
for (k = 0; k < SDL_PEN_NUM_AXES; ++k) {
pen_device.valuator_for_axis[k] = SDL_PEN_AXIS_VALUATOR_MISSING;
float value = coords[axis];
const float min = pen->axis_min[axis];
const float max = pen->axis_max[axis];
if (axis == SDL_PEN_AXIS_SLIDER) {
value += pen->slider_bias;
}
pident = xinput2_identify_pen(_this, dev->deviceid, dev->name);
pen_id = SDL_GetPenFromGUID(pident.guid);
if (pen_id == SDL_PEN_INVALID) {
/* We have never met this pen */
pen_id = ++pen_map.num_pens_known; /* start at 1 */
}
pen = SDL_PenModifyBegin(pen_id);
/* Complement XF86 driver information with vendor-specific details */
xinput2_vendor_peninfo(_this, dev, pen, pident, &valuator_5_axis, &axis_mask);
for (classct = 0; classct < dev->num_classes; ++classct) {
const XIAnyClassInfo *classinfo = dev->classes[classct];
switch (classinfo->type) {
case XIValuatorClass:
{
XIValuatorClassInfo *val_classinfo = (XIValuatorClassInfo *)classinfo;
Sint8 valuator_nr = val_classinfo->number;
Atom vname = val_classinfo->label;
int axis = -1;
float min = (float)val_classinfo->min;
float max = (float)val_classinfo->max;
if (vname == pen_atoms.abs_pressure) {
axis = SDL_PEN_AXIS_PRESSURE;
} else if (vname == pen_atoms.abs_tilt_x) {
axis = SDL_PEN_AXIS_XTILT;
} else if (vname == pen_atoms.abs_tilt_y) {
axis = SDL_PEN_AXIS_YTILT;
}
if (axis == -1 && valuator_nr == 5) {
/* Wacom model-specific axis support */
/* The meaning of the various axes is highly underspecitied in Xinput2.
* As of 2023-08-26, Wacom seems to be the only vendor to support these axes, so the code below
* captures the de-facto standard. */
axis = valuator_5_axis;
switch (axis) {
case SDL_PEN_AXIS_SLIDER:
/* cf. xinput2_wacom_peninfo for how this axis is used.
In all current cases, our API wants this value in 0..1, but the xf86 driver
starts at a negative offset, so we normalise here. */
pen_device.slider_bias = -min;
max -= min;
min = 0.0f;
break;
case SDL_PEN_AXIS_ROTATION:
/* The "0" value points to the left, rather than up, so we must
rotate 90 degrees counter-clockwise to have 0 point to the top. */
pen_device.rotation_bias = -90.0f;
break;
default:
break;
}
}
if (axis >= 0) {
capabilities |= SDL_PEN_AXIS_CAPABILITY(axis);
pen_device.valuator_for_axis[axis] = valuator_nr;
pen_device.axis_min[axis] = min;
pen_device.axis_max[axis] = max;
}
break;
}
default:
break;
}
}
/* We have a pen if and only if the device measures pressure */
if (capabilities & SDL_PEN_AXIS_PRESSURE_MASK) {
xinput2_pen *xinput2_deviceinfo;
Uint64 guid_a, guid_b;
/* Done collecting data, write to pen */
SDL_PenModifyAddCapabilities(pen, capabilities);
pen->guid = pident.guid;
if (pen->deviceinfo) {
/* Updating a known pen */
xinput2_deviceinfo = (xinput2_pen *)pen->deviceinfo;
xinput2_merge_deviceinfo(xinput2_deviceinfo, &pen_device);
// min ... 0 ... max
if (min < 0.0) {
// Normalise so that 0 remains 0.0
if (value < 0) {
value = value / (-min);
} else {
/* Registering a new pen */
xinput2_deviceinfo = SDL_malloc(sizeof(xinput2_pen));
SDL_memcpy(xinput2_deviceinfo, &pen_device, sizeof(xinput2_pen));
}
pen->deviceinfo = xinput2_deviceinfo;
#if DEBUG_PEN
printf("[pen] pen %d [%04x] valuators pressure=%d, xtilt=%d, ytilt=%d [%s]\n",
pen->header.id, pen->header.flags,
pen_device.valuator_for_axis[SDL_PEN_AXIS_PRESSURE],
pen_device.valuator_for_axis[SDL_PEN_AXIS_XTILT],
pen_device.valuator_for_axis[SDL_PEN_AXIS_YTILT],
pen->name);
#endif
SDL_memcpy(&guid_a, &pen->guid.data[0], 8);
SDL_memcpy(&guid_b, &pen->guid.data[8], 8);
if (!(guid_a | guid_b)) {
#if DEBUG_PEN
printf("[pen] (pen eliminated due to zero GUID)\n");
#endif
pen->type = SDL_PEN_TYPE_NONE;
}
} else {
/* Not a pen, mark for deletion */
pen->type = SDL_PEN_TYPE_NONE;
}
SDL_PenModifyEnd(pen, SDL_TRUE);
if (pen->type != SDL_PEN_TYPE_NONE) {
const int map_pos = pen_map.num_entries;
/* We found a pen: add mapping */
if (pen_map.entries == NULL) {
pen_map.entries = SDL_calloc(sizeof(struct pen_device_id_mapping), 1);
pen_map.num_entries = 1;
} else {
pen_map.num_entries += 1;
pen_map.entries = SDL_realloc(pen_map.entries,
pen_map.num_entries * (sizeof(struct pen_device_id_mapping)));
}
pen_map.entries[map_pos].deviceid = dev->deviceid;
pen_map.entries[map_pos].pen_id = pen_id;
} else {
/* Revert pen number allocation */
pen_map.num_pens_known = old_num_pens_known;
}
}
X11_XIFreeDeviceInfo(device_info);
SDL_PenGCSweep(NULL, xinput2_pen_free_deviceinfo);
}
static void xinput2_normalize_pen_axes(const SDL_Pen *peninfo,
const xinput2_pen *xpen,
/* inout-mode paramters: */
float *coords)
{
int axis;
/* Normalise axes */
for (axis = 0; axis < SDL_PEN_NUM_AXES; ++axis) {
int valuator = xpen->valuator_for_axis[axis];
if (valuator != SDL_PEN_AXIS_VALUATOR_MISSING) {
float value = coords[axis];
float min = xpen->axis_min[axis];
float max = xpen->axis_max[axis];
if (axis == SDL_PEN_AXIS_SLIDER) {
value += xpen->slider_bias;
}
/* min ... 0 ... max */
if (min < 0.0) {
/* Normalise so that 0 remains 0.0 */
if (value < 0) {
value = value / (-min);
} else {
if (max == 0.0) {
value = 0.0f;
} else {
value = value / max;
}
}
} else {
/* 0 ... min ... max */
/* including 0.0 = min */
if (max == 0.0) {
if (max == 0.0f) {
value = 0.0f;
} else {
value = (value - min) / max;
value = value / max;
}
}
} else {
// 0 ... min ... max
// including 0.0 = min
if (max == 0.0f) {
value = 0.0f;
} else {
value = (value - min) / max;
}
}
switch (axis) {
switch (axis) {
case SDL_PEN_AXIS_XTILT:
case SDL_PEN_AXIS_YTILT:
if (peninfo->info.max_tilt > 0.0f) {
value *= peninfo->info.max_tilt; /* normalise to physical max */
}
//if (peninfo->info.max_tilt > 0.0f) {
// value *= peninfo->info.max_tilt; // normalize to physical max
//}
break;
case SDL_PEN_AXIS_ROTATION:
/* normalised to -1..1, so let's convert to degrees */
// normalised to -1..1, so let's convert to degrees
value *= 180.0f;
value += xpen->rotation_bias;
value += pen->rotation_bias;
/* handle simple over/underflow */
// handle simple over/underflow
if (value >= 180.0f) {
value -= 360.0f;
} else if (value < -180.0f) {
@@ -666,31 +395,26 @@ static void xinput2_normalize_pen_axes(const SDL_Pen *peninfo,
default:
break;
}
coords[axis] = value;
}
coords[axis] = value;
}
}
void X11_PenAxesFromValuators(const SDL_Pen *peninfo,
void X11_PenAxesFromValuators(const X11_PenHandle *pen,
const double *input_values, const unsigned char *mask, const int mask_len,
/* out-mode parameters: */
float axis_values[SDL_PEN_NUM_AXES])
{
const xinput2_pen *pen = (xinput2_pen *)peninfo->deviceinfo;
int i;
for (i = 0; i < SDL_PEN_NUM_AXES; ++i) {
for (int i = 0; i < SDL_PEN_NUM_AXES; i++) {
const int valuator = pen->valuator_for_axis[i];
if (valuator == SDL_PEN_AXIS_VALUATOR_MISSING || valuator >= mask_len * 8 || !(XIMaskIsSet(mask, valuator))) {
if ((valuator == SDL_X11_PEN_AXIS_VALUATOR_MISSING) || (valuator >= mask_len * 8) || !(XIMaskIsSet(mask, valuator))) {
axis_values[i] = 0.0f;
} else {
axis_values[i] = (float)input_values[valuator];
}
}
xinput2_normalize_pen_axes(peninfo, pen, axis_values);
X11_XInput2NormalizePenAxes(pen, axis_values);
}
#endif /* SDL_VIDEO_DRIVER_X11_XINPUT2 */
#endif // SDL_VIDEO_DRIVER_X11_XINPUT2
/* vi: set ts=4 sw=4 expandtab: */

View File

@@ -23,32 +23,51 @@
#ifndef SDL_x11pen_h_
#define SDL_x11pen_h_
// Pressure-sensitive pen support for X11.
#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2
#include "SDL_x11video.h"
#include "../../events/SDL_pen_c.h"
/* Pressure-sensitive pen */
/* Forward definition for SDL_x11video.h */
// Forward definition for SDL_x11video.h
struct SDL_VideoData;
/* Function definitions */
#define SDL_X11_PEN_AXIS_VALUATOR_MISSING -1
/* Detect XINPUT2 devices that are pens / erasers, or update the list after hotplugging */
typedef struct X11_PenHandle
{
SDL_PenID pen;
SDL_bool is_eraser;
int x11_deviceid;
int valuator_for_axis[SDL_PEN_NUM_AXES];
float slider_bias; // shift value to add to PEN_AXIS_SLIDER (before normalisation)
float rotation_bias; // rotation to add to PEN_AXIS_ROTATION (after normalisation)
float axis_min[SDL_PEN_NUM_AXES];
float axis_max[SDL_PEN_NUM_AXES];
} X11_PenHandle;
// Prep pen support (never fails; pens simply won't be added if there's a problem).
extern void X11_InitPen(SDL_VideoDevice *_this);
/* Converts XINPUT2 valuators into pen axis information, including normalisation */
extern void X11_PenAxesFromValuators(const SDL_Pen *pen,
// Clean up pen support.
extern void X11_QuitPen(SDL_VideoDevice *_this);
// Converts XINPUT2 valuators into pen axis information, including normalisation.
extern void X11_PenAxesFromValuators(const X11_PenHandle *pen,
const double *input_values, const unsigned char *mask, const int mask_len,
/* out-mode parameters: */
float axis_values[SDL_PEN_NUM_AXES]);
/* Map X11 device ID to pen ID */
extern int X11_PenIDFromDeviceID(int deviceid);
// Add a pen (if this function's further checks validate it).
extern X11_PenHandle *X11_MaybeAddPenByDeviceID(SDL_VideoDevice *_this, int deviceid);
#endif /* SDL_VIDEO_DRIVER_X11_XINPUT2 */
// Remove a pen. It's okay if deviceid is bogus or not a pen, we'll check it.
extern void X11_RemovePenByDeviceID(int deviceid);
#endif /* SDL_x11pen_h_ */
// Map X11 device ID to pen ID.
extern X11_PenHandle *X11_FindPenByDeviceID(int deviceid);
#endif // SDL_VIDEO_DRIVER_X11_XINPUT2
#endif // SDL_x11pen_h_
/* vi: set ts=4 sw=4 expandtab: */

View File

@@ -456,9 +456,7 @@ int X11_VideoInit(SDL_VideoDevice *_this)
X11_InitTouch(_this);
#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2
X11_InitPen(_this);
#endif
return 0;
}
@@ -485,6 +483,7 @@ void X11_VideoQuit(SDL_VideoDevice *_this)
X11_QuitKeyboard(_this);
X11_QuitMouse(_this);
X11_QuitTouch(_this);
X11_QuitPen(_this);
X11_QuitClipboard(_this);
X11_QuitXsettings(_this);
}

View File

@@ -104,6 +104,14 @@ struct SDL_VideoData
Atom XdndSelection;
Atom XKLAVIER_STATE;
/* Pen atoms (these have names that don't map well to C symbols) */
Atom pen_atom_device_product_id;
Atom pen_atom_abs_pressure;
Atom pen_atom_abs_tilt_x;
Atom pen_atom_abs_tilt_y;
Atom pen_atom_wacom_serial_ids;
Atom pen_atom_wacom_tool_type;
SDL_Scancode key_layout[256];
SDL_bool selection_waiting;
SDL_bool selection_incr_waiting;
@@ -141,7 +149,6 @@ struct SDL_VideoData
SDL_bool steam_keyboard_open;
SDL_bool is_xwayland;
};
extern SDL_bool X11_UseDirectColorVisuals(void);

View File

@@ -274,18 +274,6 @@ static SDL_XInput2DeviceInfo *xinput2_get_device_info(SDL_VideoData *videodata,
return devinfo;
}
static void xinput2_pen_ensure_window(SDL_VideoDevice *_this, const SDL_Pen *pen, Window window)
{
/* When "flipping" a Wacom eraser pen, we get an XI_DeviceChanged event
* with the newly-activated pen, but this event is global for the display.
* We won't get a window until the pen starts triggering motion or
* button events, so we instead hook the pen to its window at that point. */
const SDL_WindowData *windowdata = X11_FindWindow(_this, window);
if (windowdata) {
SDL_SendPenWindowEvent(0, pen->header.id, windowdata->window);
}
}
#endif
void X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie)
@@ -303,6 +291,14 @@ void X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie)
const XIHierarchyEvent *hierev = (const XIHierarchyEvent *)cookie->data;
int i;
for (i = 0; i < hierev->num_info; i++) {
// pen stuff...
if ((hierev->info[i].flags & (XISlaveRemoved | XIDeviceDisabled)) != 0) {
X11_RemovePenByDeviceID(hierev->info[i].deviceid); // it's okay if this thing isn't actually a pen, it'll handle it.
} else if ((hierev->info[i].flags & (XISlaveAdded | XIDeviceEnabled)) != 0) {
X11_MaybeAddPenByDeviceID(_this, hierev->info[i].deviceid); // this will do more checks to make sure this is valid.
}
// not pen stuff...
if (hierev->info[i].flags & XISlaveRemoved) {
xinput2_remove_device_info(videodata, hierev->info[i].deviceid);
}
@@ -310,29 +306,14 @@ void X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie)
videodata->xinput_hierarchy_changed = SDL_TRUE;
} break;
case XI_PropertyEvent:
case XI_DeviceChanged:
{
// FIXME: We shouldn't rescan all devices for pen changes every time a property or active slave changes
X11_InitPen(_this);
} break;
case XI_Enter:
case XI_Leave:
{
const XIEnterEvent *enterev = (const XIEnterEvent *)cookie->data;
const SDL_WindowData *windowdata = X11_FindWindow(_this, enterev->event);
const SDL_Pen *pen = SDL_GetPenPtr(X11_PenIDFromDeviceID(enterev->sourceid));
SDL_Window *window = (windowdata && (cookie->evtype == XI_Enter)) ? windowdata->window : NULL;
if (pen) {
SDL_SendPenWindowEvent(0, pen->header.id, window);
}
} break;
// !!! FIXME: the pen code used to rescan all devices here, but we can do this device-by-device with XI_HierarchyChanged. When do these events fire and why?
//case XI_PropertyEvent:
//case XI_DeviceChanged:
case XI_RawMotion:
{
const XIRawEvent *rawev = (const XIRawEvent *)cookie->data;
const SDL_bool is_pen = X11_PenIDFromDeviceID(rawev->sourceid) != SDL_PEN_INVALID;
const SDL_bool is_pen = X11_FindPenByDeviceID(rawev->sourceid) != NULL;
SDL_Mouse *mouse = SDL_GetMouse();
SDL_XInput2DeviceInfo *devinfo;
double coords[2];
@@ -341,11 +322,9 @@ void X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie)
videodata->global_mouse_changed = SDL_TRUE;
if (is_pen) {
break; /* Pens check for XI_Motion instead */
break; // Pens check for XI_Motion instead
}
/* Non-pen: */
if (!mouse->relative_mode || mouse->relative_mode_warp) {
break;
}
@@ -426,31 +405,17 @@ void X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie)
case XI_ButtonRelease:
{
const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data;
const SDL_Pen *pen = SDL_GetPenPtr(X11_PenIDFromDeviceID(xev->deviceid));
X11_PenHandle *pen = X11_FindPenByDeviceID(xev->deviceid);
const int button = xev->detail;
const SDL_bool pressed = (cookie->evtype == XI_ButtonPress) ? SDL_TRUE : SDL_FALSE;
if (pen) {
xinput2_pen_ensure_window(_this, pen, xev->event);
/* Only report button event; if there was also pen movement / pressure changes, we expect
an XI_Motion event first anyway */
if (button == 1) {
/* button 1 is the pen tip */
if (pressed && SDL_PenPerformHitTest()) {
/* Check whether we should handle window resize / move events */
SDL_WindowData *windowdata = X11_FindWindow(_this, xev->event);
if (windowdata && X11_TriggerHitTestAction(_this, windowdata, pen->last.x, pen->last.y)) {
SDL_SendWindowEvent(windowdata->window, SDL_EVENT_WINDOW_HIT_TEST, 0, 0);
break; /* Don't pass on this event */
}
}
SDL_SendPenTipEvent(0, pen->header.id,
pressed ? SDL_PRESSED : SDL_RELEASED);
// Only report button event; if there was also pen movement / pressure changes, we expect an XI_Motion event first anyway.
SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event);
if (button == 1) { // button 1 is the pen tip
SDL_SendPenTouch(0, pen->pen, window, pressed ? SDL_PRESSED : SDL_RELEASED, pen->is_eraser);
} else {
SDL_SendPenButton(0, pen->header.id,
pressed ? SDL_PRESSED : SDL_RELEASED,
button - 1);
SDL_SendPenButton(0, pen->pen, window, pressed ? SDL_PRESSED : SDL_RELEASED, button - 1);
}
} else {
/* Otherwise assume a regular mouse */
@@ -475,7 +440,6 @@ void X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie)
case XI_Motion:
{
const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data;
const SDL_Pen *pen = SDL_GetPenPtr(X11_PenIDFromDeviceID(xev->deviceid));
#if SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH
SDL_bool pointer_emulated = ((xev->flags & XIPointerEmulated) != 0);
#else
@@ -489,25 +453,20 @@ void X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie)
break;
}
X11_PenHandle *pen = X11_FindPenByDeviceID(xev->deviceid);
if (pen) {
SDL_PenStatusInfo pen_status;
SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event);
SDL_SendPenMotion(0, pen->pen, window, (float) xev->event_x, (float) xev->event_y);
pen_status.x = (float)xev->event_x;
pen_status.y = (float)xev->event_y;
float axes[SDL_PEN_NUM_AXES];
X11_PenAxesFromValuators(pen, xev->valuators.values, xev->valuators.mask, xev->valuators.mask_len, axes);
X11_PenAxesFromValuators(pen,
xev->valuators.values, xev->valuators.mask, xev->valuators.mask_len,
&pen_status.axes[0]);
xinput2_pen_ensure_window(_this, pen, xev->event);
SDL_SendPenMotion(0, pen->header.id,
SDL_TRUE,
&pen_status);
break;
}
if (!pointer_emulated) {
for (int i = 0; i < SDL_arraysize(axes); i++) {
if (pen->valuator_for_axis[i] != SDL_X11_PEN_AXIS_VALUATOR_MISSING) {
SDL_SendPenAxis(0, pen->pen, window, (SDL_PenAxis) i, axes[i]);
}
}
} else if (!pointer_emulated) {
SDL_Mouse *mouse = SDL_GetMouse();
if (!mouse->relative_mode || mouse->relative_mode_warp) {
SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event);