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

@ -212,12 +212,6 @@
<ClCompile Include="..\..\..\test\testautomation_main.c" />
<ClCompile Include="..\..\..\test\testautomation_math.c" />
<ClCompile Include="..\..\..\test\testautomation_mouse.c" />
<ClCompile Include="..\..\..\test\testautomation_pen.c">
<AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(ProjectDir)\..\..\..\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(ProjectDir)\..\..\..\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(ProjectDir)\..\..\..\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(ProjectDir)\..\..\..\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<ClCompile Include="..\..\..\test\testautomation_pixels.c" />
<ClCompile Include="..\..\..\test\testautomation_platform.c" />
<ClCompile Include="..\..\..\test\testautomation_properties.c" />

View File

@ -1274,7 +1274,6 @@
4537749212091504002F0F45 /* testshape.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = testshape.app; sourceTree = BUILT_PRODUCTS_DIR; };
453774A4120915E3002F0F45 /* testshape.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = testshape.c; sourceTree = "<group>"; };
66E88E8A203B778F0004D44E /* testyuv_cvt.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = testyuv_cvt.c; sourceTree = "<group>"; };
A1A859442BC72FC20045DD6C /* testautomation_pen.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = testautomation_pen.c; sourceTree = "<group>"; };
A1A859482BC72FC20045DD6C /* testautomation_properties.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = testautomation_properties.c; sourceTree = "<group>"; };
A1A859492BC72FC20045DD6C /* testautomation_subsystems.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = testautomation_subsystems.c; sourceTree = "<group>"; };
A1A8594A2BC72FC20045DD6C /* testautomation_log.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = testautomation_log.c; sourceTree = "<group>"; };
@ -1804,7 +1803,6 @@
F35E56B62983130A00A43A5F /* testautomation_main.c */,
F35E56BA2983130B00A43A5F /* testautomation_math.c */,
F35E56CD2983130F00A43A5F /* testautomation_mouse.c */,
A1A859442BC72FC20045DD6C /* testautomation_pen.c */,
F35E56C02983130C00A43A5F /* testautomation_pixels.c */,
F35E56C32983130D00A43A5F /* testautomation_platform.c */,
A1A859482BC72FC20045DD6C /* testautomation_properties.c */,

View File

@ -151,8 +151,6 @@ typedef enum SDL_EventType
in an event watcher, the window handle is still valid and can still be used to retrieve any userdata
associated with the window. Otherwise, the handle has already been destroyed and all resources
associated with it are invalid */
SDL_EVENT_WINDOW_PEN_ENTER, /**< Window has gained focus of the pressure-sensitive pen with ID "data1" */
SDL_EVENT_WINDOW_PEN_LEAVE, /**< Window has lost focus of the pressure-sensitive pen with ID "data1" */
SDL_EVENT_WINDOW_HDR_STATE_CHANGED, /**< Window HDR properties have changed */
SDL_EVENT_WINDOW_FIRST = SDL_EVENT_WINDOW_SHOWN,
SDL_EVENT_WINDOW_LAST = SDL_EVENT_WINDOW_HDR_STATE_CHANGED,
@ -227,11 +225,14 @@ typedef enum SDL_EventType
SDL_EVENT_SENSOR_UPDATE = 0x1200, /**< A sensor was updated */
/* Pressure-sensitive pen events */
SDL_EVENT_PEN_DOWN = 0x1300, /**< Pressure-sensitive pen touched drawing surface */
SDL_EVENT_PEN_PROXIMITY_IN = 0x1300, /**< Pressure-sensitive pen has become available */
SDL_EVENT_PEN_PROXIMITY_OUT, /**< Pressure-sensitive pen has become unavailable */
SDL_EVENT_PEN_DOWN, /**< Pressure-sensitive pen touched drawing surface */
SDL_EVENT_PEN_UP, /**< Pressure-sensitive pen stopped touching drawing surface */
SDL_EVENT_PEN_MOTION, /**< Pressure-sensitive pen moved, or angle/pressure changed */
SDL_EVENT_PEN_BUTTON_DOWN, /**< Pressure-sensitive pen button pressed */
SDL_EVENT_PEN_BUTTON_UP, /**< Pressure-sensitive pen button released */
SDL_EVENT_PEN_MOTION, /**< Pressure-sensitive pen is moving on the tablet */
SDL_EVENT_PEN_AXIS, /**< Pressure-sensitive pen angle/pressure/etc changed */
/* Camera hotplug events */
SDL_EVENT_CAMERA_DEVICE_ADDED = 0x1400, /**< A new camera device is available */
@ -426,7 +427,7 @@ typedef struct SDL_MouseMotionEvent
Uint32 reserved;
Uint64 timestamp; /**< In nanoseconds, populated using SDL_GetTicksNS() */
SDL_WindowID windowID; /**< The window with mouse focus, if any */
SDL_MouseID which; /**< The mouse instance id, SDL_TOUCH_MOUSEID, or SDL_PEN_MOUSEID */
SDL_MouseID which; /**< The mouse instance id or SDL_TOUCH_MOUSEID */
SDL_MouseButtonFlags state; /**< The current button state */
float x; /**< X coordinate, relative to window */
float y; /**< Y coordinate, relative to window */
@ -445,7 +446,7 @@ typedef struct SDL_MouseButtonEvent
Uint32 reserved;
Uint64 timestamp; /**< In nanoseconds, populated using SDL_GetTicksNS() */
SDL_WindowID windowID; /**< The window with mouse focus, if any */
SDL_MouseID which; /**< The mouse instance id, SDL_TOUCH_MOUSEID, or SDL_PEN_MOUSEID */
SDL_MouseID which; /**< The mouse instance id, SDL_TOUCH_MOUSEID */
Uint8 button; /**< The mouse button index */
Uint8 state; /**< SDL_PRESSED or SDL_RELEASED */
Uint8 clicks; /**< 1 for single-click, 2 for double-click, etc. */
@ -465,7 +466,7 @@ typedef struct SDL_MouseWheelEvent
Uint32 reserved;
Uint64 timestamp; /**< In nanoseconds, populated using SDL_GetTicksNS() */
SDL_WindowID windowID; /**< The window with mouse focus, if any */
SDL_MouseID which; /**< The mouse instance id, SDL_TOUCH_MOUSEID, or SDL_PEN_MOUSEID */
SDL_MouseID which; /**< The mouse instance id, SDL_TOUCH_MOUSEID */
float x; /**< The amount scrolled horizontally, positive to the right and negative to the left */
float y; /**< The amount scrolled vertically, positive away from the user and negative toward the user */
SDL_MouseWheelDirection direction; /**< Set to one of the SDL_MOUSEWHEEL_* defines. When FLIPPED the values in X and Y will be opposite. Multiply by -1 to change them back */
@ -714,30 +715,38 @@ typedef struct SDL_TouchFingerEvent
SDL_WindowID windowID; /**< The window underneath the finger, if any */
} SDL_TouchFingerEvent;
/**
* Pressure-sensitive pen touched or stopped touching surface (event.ptip.*)
* Pressure-sensitive pen proximity event structure (event.pmotion.*)
*
* When a pen becomes visible to the system (it is close enough to a tablet,
* etc), SDL will send an SDL_EVENT_PEN_PROXIMITY_IN event with the new
* pen's ID. This ID is valid until the pen leaves proximity again (has
* been removed from the tablet's area, the tablet has been unplugged, etc).
* If the same pen reenters proximity again, it will be given a new ID.
*
* Note that "proximity" means "close enough for the tablet to know the tool
* is there." The pen touching and lifting off from the tablet while not
* leaving the area are handled by SDL_EVENT_PEN_DOWN and SDL_EVENT_PEN_UP.
*
* \since This struct is available since SDL 3.0.0.
*/
typedef struct SDL_PenTipEvent
typedef struct SDL_PenProximityEvent
{
SDL_EventType type; /**< SDL_EVENT_PEN_DOWN or SDL_EVENT_PEN_UP */
SDL_EventType type; /**< SDL_EVENT_PEN_PROXIMITY_IN or SDL_EVENT_PEN_PROXIMITY_OUT */
Uint32 reserved;
Uint64 timestamp; /**< In nanoseconds, populated using SDL_GetTicksNS() */
SDL_WindowID windowID; /**< The window with pen focus, if any */
SDL_WindowID windowID; /**< The window with mouse focus, if any */
SDL_PenID which; /**< The pen instance id */
Uint8 tip; /**< SDL_PEN_TIP_INK when using a regular pen tip, or SDL_PEN_TIP_ERASER if the pen is being used as an eraser (e.g., flipped to use the eraser tip) */
Uint8 state; /**< SDL_PRESSED on SDL_EVENT_PEN_DOWN and SDL_RELEASED on SDL_EVENT_PEN_UP */
Uint16 pen_state; /**< Pen button masks (where SDL_BUTTON(1) is the first button, SDL_BUTTON(2) is the second button etc.), SDL_PEN_DOWN_MASK is set if the pen is touching the surface, and SDL_PEN_ERASER_MASK is set if the pen is (used as) an eraser. */
float x; /**< X coordinate, relative to window */
float y; /**< Y coordinate, relative to window */
float axes[SDL_PEN_NUM_AXES]; /**< Pen axes such as pressure and tilt (ordered as per SDL_PenAxis) */
} SDL_PenTipEvent;
} SDL_PenProximityEvent;
/**
* Pressure-sensitive pen motion / pressure / angle event structure
* (event.pmotion.*)
* Pressure-sensitive pen motion event structure (event.pmotion.*)
*
* Depending on the hardware, you may get motion events when the
* pen is not touching a tablet, for tracking a pen even when it
* isn't drawing. You should listen for SDL_EVENT_PEN_DOWN and
* SDL_EVENT_PEN_UP events, or check `pen_state & SDL_PEN_INPUT_DOWN`
* to decide if a pen is "drawing" when dealing with pen motion.
*
* \since This struct is available since SDL 3.0.0.
*/
@ -746,19 +755,42 @@ typedef struct SDL_PenMotionEvent
SDL_EventType type; /**< SDL_EVENT_PEN_MOTION */
Uint32 reserved;
Uint64 timestamp; /**< In nanoseconds, populated using SDL_GetTicksNS() */
SDL_WindowID windowID; /**< The window with pen focus, if any */
SDL_WindowID windowID; /**< The window with mouse focus, if any */
SDL_PenID which; /**< The pen instance id */
Uint8 padding1;
Uint8 padding2;
Uint16 pen_state; /**< Pen button masks (where SDL_BUTTON(1) is the first button, SDL_BUTTON(2) is the second button etc.), SDL_PEN_DOWN_MASK is set if the pen is touching the surface, and SDL_PEN_ERASER_MASK is set if the pen is (used as) an eraser. */
float x; /**< X coordinate, relative to window */
float y; /**< Y coordinate, relative to window */
float axes[SDL_PEN_NUM_AXES]; /**< Pen axes such as pressure and tilt (ordered as per SDL_PenAxis) */
SDL_PenInputFlags pen_state; /**< Complete pen input state at time of event */
float x; /**< X position of pen on tablet */
float y; /**< Y position of pen on tablet */
} SDL_PenMotionEvent;
/**
* Pressure-sensitive pen touched event structure (event.ptouch.*)
*
* These events come when a pen touches a surface (a tablet, etc),
* or lifts off from one.
*
* \since This struct is available since SDL 3.0.0.
*/
typedef struct SDL_PenTouchEvent
{
SDL_EventType type; /**< SDL_EVENT_PEN_DOWN or SDL_EVENT_PEN_UP */
Uint32 reserved;
Uint64 timestamp; /**< In nanoseconds, populated using SDL_GetTicksNS() */
SDL_WindowID windowID; /**< The window with pen focus, if any */
SDL_PenID which; /**< The pen instance id */
SDL_PenInputFlags pen_state; /**< Complete pen input state at time of event */
float x; /**< X position of pen on tablet */
float y; /**< Y position of pen on tablet */
Uint8 eraser; /**< Non-zero if eraser end is used (not all pens support this). */
Uint8 state; /**< SDL_PRESSED (pen is touching) or SDL_RELEASED (pen is lifted off) */
} SDL_PenTouchEvent;
/**
* Pressure-sensitive pen button event structure (event.pbutton.*)
*
* This is for buttons on the pen itself that the user might click.
* The pen itself pressing down to draw triggers a SDL_EVENT_PEN_DOWN
* event instead.
*
* \since This struct is available since SDL 3.0.0.
*/
typedef struct SDL_PenButtonEvent
@ -766,15 +798,36 @@ typedef struct SDL_PenButtonEvent
SDL_EventType type; /**< SDL_EVENT_PEN_BUTTON_DOWN or SDL_EVENT_PEN_BUTTON_UP */
Uint32 reserved;
Uint64 timestamp; /**< In nanoseconds, populated using SDL_GetTicksNS() */
SDL_WindowID windowID; /**< The window with mouse focus, if any */
SDL_PenID which; /**< The pen instance id */
SDL_PenInputFlags pen_state; /**< Complete pen input state at time of event */
float x; /**< X position of pen on tablet */
float y; /**< Y position of pen on tablet */
Uint8 button; /**< The pen button index (first button is 1). */
Uint8 state; /**< SDL_PRESSED or SDL_RELEASED */
} SDL_PenButtonEvent;
/**
* Pressure-sensitive pen pressure / angle event structure
* (event.paxis.*)
*
* You might get some of these events even if the pen isn't touching the tablet.
*
* \since This struct is available since SDL 3.0.0.
*/
typedef struct SDL_PenAxisEvent
{
SDL_EventType type; /**< SDL_EVENT_PEN_AXIS */
Uint32 reserved;
Uint64 timestamp; /**< In nanoseconds, populated using SDL_GetTicksNS() */
SDL_WindowID windowID; /**< The window with pen focus, if any */
SDL_PenID which; /**< The pen instance id */
Uint8 button; /**< The pen button index (1 represents the pen tip for compatibility with mouse events) */
Uint8 state; /**< SDL_PRESSED or SDL_RELEASED */
Uint16 pen_state; /**< Pen button masks (where SDL_BUTTON(1) is the first button, SDL_BUTTON(2) is the second button etc.), SDL_PEN_DOWN_MASK is set if the pen is touching the surface, and SDL_PEN_ERASER_MASK is set if the pen is (used as) an eraser. */
float x; /**< X coordinate, relative to window */
float y; /**< Y coordinate, relative to window */
float axes[SDL_PEN_NUM_AXES]; /**< Pen axes such as pressure and tilt (ordered as per SDL_PenAxis) */
} SDL_PenButtonEvent;
SDL_PenInputFlags pen_state; /**< Complete pen input state at time of event */
float x; /**< X position of pen on tablet */
float y; /**< Y position of pen on tablet */
SDL_PenAxis axis; /**< Axis that has changed */
float value; /**< New value of axis */
} SDL_PenAxisEvent;
/**
* An event used to drop text or request a file open by the system
@ -894,9 +947,11 @@ typedef union SDL_Event
SDL_QuitEvent quit; /**< Quit request event data */
SDL_UserEvent user; /**< Custom event data */
SDL_TouchFingerEvent tfinger; /**< Touch finger event data */
SDL_PenTipEvent ptip; /**< Pen tip touching or leaving drawing surface */
SDL_PenMotionEvent pmotion; /**< Pen change in position, pressure, or angle */
SDL_PenButtonEvent pbutton; /**< Pen button press */
SDL_PenProximityEvent pproximity; /**< Pen proximity event data */
SDL_PenTouchEvent ptouch; /**< Pen tip touching event data */
SDL_PenMotionEvent pmotion; /**< Pen motion event data */
SDL_PenButtonEvent pbutton; /**< Pen button event data */
SDL_PenAxisEvent paxis; /**< Pen axis event data */
SDL_DropEvent drop; /**< Drag and drop event data */
SDL_ClipboardEvent clipboard; /**< Clipboard event data */

View File

@ -22,56 +22,65 @@
/**
* # CategoryPen
*
* Include file for SDL pen event handling.
* SDL pen event handling.
*
* This file describes operations for pressure-sensitive pen (stylus and/or
* SDL provides an API for pressure-sensitive pen (stylus and/or
* eraser) handling, e.g., for input and drawing tablets or suitably equipped
* mobile / tablet devices.
*
* To get started with pens:
* To get started with pens, simply handle SDL_EVENT_PEN_* events. When a pen
* starts providing input, SDL will assign it a unique SDL_PenID, which will
* remain for the life of the process, as long as the pen stays connected.
*
* - Listen to SDL_PenMotionEvent and SDL_PenButtonEvent
* - To avoid treating pen events as mouse events, ignore SDL_MouseMotionEvent
* and SDL_MouseButtonEvent whenever `which` == SDL_PEN_MOUSEID.
*
* We primarily identify pens by SDL_PenID. The implementation makes a best
* effort to relate each SDL_PenID to the same physical device during a
* session. Formerly valid SDL_PenID values remain valid even if a device
* disappears.
*
* For identifying pens across sessions, the API provides the type SDL_GUID .
* Pens may provide more than simple touch input; they might have other axes,
* such as pressure, tilt, rotation, etc.
*/
#ifndef SDL_pen_h_
#define SDL_pen_h_
#include <SDL3/SDL_error.h>
#include <SDL3/SDL_guid.h>
#include <SDL3/SDL_mouse.h>
#include <SDL3/SDL_stdinc.h>
/* Set up for C function definitions, even when using C++ */
#ifdef __cplusplus
extern "C" {
#endif
typedef Uint32 SDL_PenID; /**< SDL_PenIDs identify pens uniquely within a session */
/**
* SDL pen instance IDs.
*
* Zero is used to signify an invalid/null device.
*
* These show up in pen events when SDL sees input from them. They remain
* consistent as long as SDL can recognize a tool to be the same pen; but if
* a pen physically leaves the area and returns, it might get a new ID.
*
* \since This datatype is available since SDL 3.0.0.
*/
typedef Uint32 SDL_PenID;
#define SDL_PEN_INVALID ((SDL_PenID)0) /**< Reserved invalid SDL_PenID is valid */
#define SDL_PEN_MOUSEID ((SDL_MouseID)-2) /**< Device ID for mouse events triggered by pen events */
#define SDL_PEN_INFO_UNKNOWN (-1) /**< Marks unknown information when querying the pen */
/**
* Pen axis indices
* Pen input flags, as reported by various pen events' `pen_state` field.
*
* Below are the valid indices to the "axis" array from SDL_PenMotionEvent and
* SDL_PenButtonEvent. The axis indices form a contiguous range of ints from 0
* to SDL_PEN_AXIS_LAST, inclusive. All "axis[]" entries are either normalised
* to 0..1 or report a (positive or negative) angle in degrees, with 0.0
* representing the centre. Not all pens/backends support all axes:
* unsupported entries are always "0.0f".
* \since This datatype is available since SDL 3.0.0.
*/
typedef Uint32 SDL_PenInputFlags;
#define SDL_PEN_INPUT_DOWN (1u << 0) /**< & to see if pen is pressed down */
#define SDL_PEN_INPUT_BUTTON_1 (1u << 1) /**< & to see if button 1 is pressed */
#define SDL_PEN_INPUT_BUTTON_2 (1u << 2) /**< & to see if button 2 is pressed */
#define SDL_PEN_INPUT_BUTTON_3 (1u << 3) /**< & to see if button 3 is pressed */
#define SDL_PEN_INPUT_BUTTON_4 (1u << 4) /**< & to see if button 4 is pressed */
#define SDL_PEN_INPUT_BUTTON_5 (1u << 5) /**< & to see if button 5 is pressed */
#define SDL_PEN_INPUT_ERASER_TIP (1u << 30) /**< & to see if eraser tip is used */
/**
* Pen axis indices.
*
* These are the valid values for the `axis` field in SDL_PenAxisEvent.
* All axes are either normalised to 0..1 or report a (positive or negative) angle
* in degrees, with 0.0 representing the centre. Not all pens/backends support all
* axes: unsupported axes are always zero.
*
* To convert angles for tilt and rotation into vector representation, use
* SDL_sinf on the XTILT, YTILT, or ROTATION component, for example:
@ -82,200 +91,17 @@ typedef Uint32 SDL_PenID; /**< SDL_PenIDs identify pens uniquely within a sessio
*/
typedef enum SDL_PenAxis
{
SDL_PEN_AXIS_PRESSURE = 0, /**< Pen pressure. Unidirectional: 0..1.0 */
SDL_PEN_AXIS_XTILT, /**< Pen horizontal tilt angle. Bidirectional: -90.0..90.0 (left-to-right).
The physical max/min tilt may be smaller than -90.0 / 90.0, cf. SDL_PenCapabilityInfo */
SDL_PEN_AXIS_YTILT, /**< Pen vertical tilt angle. Bidirectional: -90.0..90.0 (top-to-down).
The physical max/min tilt may be smaller than -90.0 / 90.0, cf. SDL_PenCapabilityInfo */
SDL_PEN_AXIS_DISTANCE, /**< Pen distance to drawing surface. Unidirectional: 0.0..1.0 */
SDL_PEN_AXIS_ROTATION, /**< Pen barrel rotation. Bidirectional: -180..179.9 (clockwise, 0 is facing up, -180.0 is facing down). */
SDL_PEN_AXIS_SLIDER, /**< Pen finger wheel or slider (e.g., Airbrush Pen). Unidirectional: 0..1.0 */
SDL_PEN_NUM_AXES, /**< Last valid axis index */
SDL_PEN_AXIS_LAST = SDL_PEN_NUM_AXES - 1 /**< Last axis index plus 1 */
SDL_PEN_AXIS_PRESSURE, /**< Pen pressure. Unidirectional: 0 to 1.0 */
SDL_PEN_AXIS_XTILT, /**< Pen horizontal tilt angle. Bidirectional: -90.0 to 90.0 (left-to-right).
The physical max/min tilt may be smaller than -90.0 / 90.0, check SDL_PenCapabilityInfo */
SDL_PEN_AXIS_YTILT, /**< Pen vertical tilt angle. Bidirectional: -90.0 to 90.0 (top-to-down).
The physical max/min tilt may be smaller than -90.0 / 90.0 check SDL_PenCapabilityInfo */
SDL_PEN_AXIS_DISTANCE, /**< Pen distance to drawing surface. Unidirectional: 0.0 to 1.0 */
SDL_PEN_AXIS_ROTATION, /**< Pen barrel rotation. Bidirectional: -180 to 179.9 (clockwise, 0 is facing up, -180.0 is facing down). */
SDL_PEN_AXIS_SLIDER, /**< Pen finger wheel or slider (e.g., Airbrush Pen). Unidirectional: 0 to 1.0 */
SDL_PEN_NUM_AXES /**< Total known pen axis types in this version of SDL. This number may grow in future releases! */
} SDL_PenAxis;
/* Pen flags. These share a bitmask space with SDL_BUTTON_LEFT and friends. */
#define SDL_PEN_FLAG_DOWN_BIT_INDEX 13 /* Bit for storing that pen is touching the surface */
#define SDL_PEN_FLAG_INK_BIT_INDEX 14 /* Bit for storing has-non-eraser-capability status */
#define SDL_PEN_FLAG_ERASER_BIT_INDEX 15 /* Bit for storing is-eraser or has-eraser-capability property */
#define SDL_PEN_FLAG_AXIS_BIT_OFFSET 16 /* Bit for storing has-axis-0 property */
#define SDL_PEN_CAPABILITY(capbit) (1ul << (capbit))
#define SDL_PEN_AXIS_CAPABILITY(axis) SDL_PEN_CAPABILITY((axis) + SDL_PEN_FLAG_AXIS_BIT_OFFSET)
/* Pen tips */
#define SDL_PEN_TIP_INK SDL_PEN_FLAG_INK_BIT_INDEX /**< Regular pen tip (for drawing) touched the surface */
#define SDL_PEN_TIP_ERASER SDL_PEN_FLAG_ERASER_BIT_INDEX /**< Eraser pen tip touched the surface */
/**
* Pen capabilities reported by SDL_GetPenCapabilities.
*
* \since This datatype is available since SDL 3.0.0.
*/
typedef Uint32 SDL_PenCapabilityFlags;
#define SDL_PEN_DOWN_MASK SDL_PEN_CAPABILITY(SDL_PEN_FLAG_DOWN_BIT_INDEX) /**< Pen tip is currently touching the drawing surface. */
#define SDL_PEN_INK_MASK SDL_PEN_CAPABILITY(SDL_PEN_FLAG_INK_BIT_INDEX) /**< Pen has a regular drawing tip (SDL_GetPenCapabilities). For events (SDL_PenButtonEvent, SDL_PenMotionEvent, SDL_GetPenStatus) this flag is mutually exclusive with SDL_PEN_ERASER_MASK . */
#define SDL_PEN_ERASER_MASK SDL_PEN_CAPABILITY(SDL_PEN_FLAG_ERASER_BIT_INDEX) /**< Pen has an eraser tip (SDL_GetPenCapabilities) or is being used as eraser (SDL_PenButtonEvent , SDL_PenMotionEvent , SDL_GetPenStatus) */
#define SDL_PEN_AXIS_PRESSURE_MASK SDL_PEN_AXIS_CAPABILITY(SDL_PEN_AXIS_PRESSURE) /**< Pen provides pressure information in axis SDL_PEN_AXIS_PRESSURE */
#define SDL_PEN_AXIS_XTILT_MASK SDL_PEN_AXIS_CAPABILITY(SDL_PEN_AXIS_XTILT) /**< Pen provides horizontal tilt information in axis SDL_PEN_AXIS_XTILT */
#define SDL_PEN_AXIS_YTILT_MASK SDL_PEN_AXIS_CAPABILITY(SDL_PEN_AXIS_YTILT) /**< Pen provides vertical tilt information in axis SDL_PEN_AXIS_YTILT */
#define SDL_PEN_AXIS_DISTANCE_MASK SDL_PEN_AXIS_CAPABILITY(SDL_PEN_AXIS_DISTANCE) /**< Pen provides distance to drawing tablet in SDL_PEN_AXIS_DISTANCE */
#define SDL_PEN_AXIS_ROTATION_MASK SDL_PEN_AXIS_CAPABILITY(SDL_PEN_AXIS_ROTATION) /**< Pen provides barrel rotation information in axis SDL_PEN_AXIS_ROTATION */
#define SDL_PEN_AXIS_SLIDER_MASK SDL_PEN_AXIS_CAPABILITY(SDL_PEN_AXIS_SLIDER) /**< Pen provides slider / finger wheel or similar in axis SDL_PEN_AXIS_SLIDER */
#define SDL_PEN_AXIS_BIDIRECTIONAL_MASKS (SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK)
/**
* Pen types
*
* Some pens identify as a particular type of drawing device (e.g., an
* airbrush or a pencil).
*
* \since This enum is available since SDL 3.0.0
*/
typedef enum SDL_PenSubtype
{
SDL_PEN_TYPE_UNKNOWN = 0,
SDL_PEN_TYPE_ERASER = 1, /**< 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_PEN_TYPE_LAST = SDL_PEN_TYPE_AIRBRUSH /**< Last valid pen type */
} SDL_PenSubtype;
/* Function prototypes */
/**
* Retrieves all pens that are connected to the system.
*
* Yields an array of SDL_PenID values. These identify and track pens
* throughout a session. To track pens across sessions (program restart), use
* SDL_GUID .
*
* \param count the number of pens in the array (number of array elements
* minus 1, i.e., not counting the terminator 0).
* \returns a 0 terminated array of SDL_PenID values, or NULL on failure. The
* array must be freed with SDL_free(). On a NULL return,
* SDL_GetError() is set.
*
* \since This function is available since SDL 3.0.0.
*/
extern SDL_DECLSPEC SDL_PenID * SDLCALL SDL_GetPens(int *count);
/**
* Retrieves the pen's current status.
*
* If the pen is detached (cf. SDL_PenConnected), this operation may return
* default values.
*
* \param instance_id the pen to query.
* \param x out-mode parameter for pen x coordinate. May be NULL.
* \param y out-mode parameter for pen y coordinate. May be NULL.
* \param axes out-mode parameter for axis information. May be null. The axes
* are in the same order as SDL_PenAxis.
* \param num_axes maximum number of axes to write to "axes".
* \returns a bit mask with the current pen button states (SDL_BUTTON_LMASK
* etc.), possibly SDL_PEN_DOWN_MASK, and exactly one of
* SDL_PEN_INK_MASK or SDL_PEN_ERASER_MASK , or 0 on error (see
* SDL_GetError()).
*
* \since This function is available since SDL 3.0.0.
*/
extern SDL_DECLSPEC Uint32 SDLCALL SDL_GetPenStatus(SDL_PenID instance_id, float *x, float *y, float *axes, size_t num_axes);
/**
* Retrieves an SDL_PenID for the given SDL_GUID.
*
* \param guid a pen GUID.
* \returns a valid SDL_PenID, or SDL_PEN_INVALID if there is no matching
* SDL_PenID.
*
* \since This function is available since SDL 3.0.0.
*/
extern SDL_DECLSPEC SDL_PenID SDLCALL SDL_GetPenFromGUID(SDL_GUID guid);
/**
* Retrieves the SDL_GUID for a given SDL_PenID.
*
* \param instance_id the pen to query.
* \returns the corresponding pen GUID; persistent across multiple sessions.
* If "instance_id" is SDL_PEN_INVALID, returns an all-zeroes GUID.
*
* \since This function is available since SDL 3.0.0.
*/
extern SDL_DECLSPEC SDL_GUID SDLCALL SDL_GetPenGUID(SDL_PenID instance_id);
/**
* Checks whether a pen is still attached.
*
* If a pen is detached, it will not show up for SDL_GetPens(). Other
* operations will still be available but may return default values.
*
* \param instance_id a pen ID.
* \returns SDL_TRUE if "instance_id" is valid and the corresponding pen is
* attached, or SDL_FALSE otherwise.
*
* \since This function is available since SDL 3.0.0.
*/
extern SDL_DECLSPEC SDL_bool SDLCALL SDL_PenConnected(SDL_PenID instance_id);
/**
* Retrieves a human-readable description for a SDL_PenID.
*
* \param instance_id the pen to query.
* \returns a string that contains the name of the pen, intended for human
* consumption. The string might or might not be localised, depending
* on platform settings. It is not guaranteed to be unique; use
* SDL_GetPenGUID() for (best-effort) unique identifiers. The pointer
* is managed by the SDL pen subsystem and must not be deallocated.
* The pointer remains valid until SDL is shut down. Returns NULL on
* error (cf. SDL_GetError()).
*
* \since This function is available since SDL 3.0.0.
*/
extern SDL_DECLSPEC const char * SDLCALL SDL_GetPenName(SDL_PenID instance_id);
/**
* Pen capabilities, as reported by SDL_GetPenCapabilities()
*
* \since This struct is available since SDL 3.0.0.
*/
typedef struct SDL_PenCapabilityInfo
{
float max_tilt; /**< Physical maximum tilt angle, for XTILT and YTILT, or SDL_PEN_INFO_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) */
Sint8 num_buttons; /**< Number of pen buttons (not counting the pen tip), or SDL_PEN_INFO_UNKNOWN */
} SDL_PenCapabilityInfo;
/**
* Retrieves capability flags for a given SDL_PenID.
*
* \param instance_id the pen to query.
* \param capabilities detail information about pen capabilities, such as the
* number of buttons.
* \returns a set of capability flags, cf. SDL_PEN_CAPABILITIES.
*
* \since This function is available since SDL 3.0.0.
*/
extern SDL_DECLSPEC SDL_PenCapabilityFlags SDLCALL SDL_GetPenCapabilities(SDL_PenID instance_id, SDL_PenCapabilityInfo *capabilities);
/**
* Retrieves the pen type for a given SDL_PenID.
*
* \param instance_id the pen to query.
* \returns the corresponding pen type (cf. SDL_PenSubtype) or 0 on error.
* Note that the pen type does not dictate whether the pen tip is
* SDL_PEN_TIP_INK or SDL_PEN_TIP_ERASER; to determine whether a pen
* is being used for drawing or in eraser mode, check either the pen
* tip on SDL_EVENT_PEN_DOWN, or the flag SDL_PEN_ERASER_MASK in the
* pen state.
*
* \since This function is available since SDL 3.0.0.
*/
extern SDL_DECLSPEC SDL_PenSubtype SDLCALL SDL_GetPenType(SDL_PenID instance_id);
/* Ends C function definitions when using C++ */
#ifdef __cplusplus
}

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));
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;
}
// 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);
zwp_tablet_tool_v2_set_user_data(tool, sdltool);
sdltool->tablet = input;
tablet_object_list_append(input->tools, tool);
}
}
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 */
/* Does this device have a valuator for pressure sensitivity? */
static SDL_bool X11_XInput2DeviceIsPen(SDL_VideoDevice *_this, const XIDeviceInfo *dev)
{
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_FALSE;
}
/* 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 */
#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
{
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;
}
}
return SDL_PEN_INVALID;
}
static void pen_atoms_ensure_initialized(SDL_VideoDevice *_this)
{
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;
}
/* 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)
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 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,478 +143,229 @@ 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 (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;
/* 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) {
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 ((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 (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). */
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]);
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
}
return (SDL_strstr(dev_name, PEN_ERASER_NAME_TAG)) ? SDL_TRUE : SDL_FALSE;
}
/* 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)
{
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;
}
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
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;
// 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 {
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;
use_this_axis = SDL_FALSE;
}
static void xinput2_pen_free_deviceinfo(Uint32 deviceid, void *x11_peninfo, void *context)
// !!! 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.
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;
}
}
}
// 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);
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;
}
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;
}
X11_PenHandle *X11_MaybeAddPenByDeviceID(SDL_VideoDevice *_this, int deviceid)
{
SDL_free(x11_peninfo);
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);
}
return NULL;
}
static void xinput2_merge_deviceinfo(xinput2_pen *dest, xinput2_pen *src)
void X11_RemovePenByDeviceID(int deviceid)
{
*dest = *src;
X11_PenHandle *handle = X11_FindPenByDeviceID(deviceid);
if (handle) {
SDL_RemovePenDevice(0, handle->pen);
SDL_free(handle);
}
/**
* 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;
}
}
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;
}
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);
} 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];
const float min = pen->axis_min[axis];
const float max = pen->axis_max[axis];
if (axis == SDL_PEN_AXIS_SLIDER) {
value += xpen->slider_bias;
value += pen->slider_bias;
}
/* min ... 0 ... max */
// min ... 0 ... max
if (min < 0.0) {
/* Normalise so that 0 remains 0.0 */
// Normalise so that 0 remains 0.0
if (value < 0) {
value = value / (-min);
} else {
if (max == 0.0) {
if (max == 0.0f) {
value = 0.0f;
} else {
value = value / max;
}
}
} else {
/* 0 ... min ... max */
/* including 0.0 = min */
if (max == 0.0) {
// 0 ... min ... max
// including 0.0 = min
if (max == 0.0f) {
value = 0.0f;
} else {
value = (value - min) / max;
@ -646,17 +375,17 @@ static void xinput2_normalize_pen_axes(const SDL_Pen *peninfo,
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) {
@ -667,30 +396,25 @@ static void xinput2_normalize_pen_axes(const SDL_Pen *peninfo,
default:
break;
}
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;
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]);
}
if (!pointer_emulated) {
}
} 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);

View File

@ -34,9 +34,6 @@ static SDLTest_TestSuiteReference *testSuites[] = {
&mainTestSuite,
&mathTestSuite,
&mouseTestSuite,
#if !defined(SDL_PLATFORM_IOS) && !defined(SDL_PLATFORM_TVOS)
&penTestSuite,
#endif
&pixelsTestSuite,
&platformTestSuite,
&propertiesTestSuite,

File diff suppressed because it is too large Load Diff

View File

@ -25,7 +25,6 @@ extern SDLTest_TestSuiteReference logTestSuite;
extern SDLTest_TestSuiteReference mainTestSuite;
extern SDLTest_TestSuiteReference mathTestSuite;
extern SDLTest_TestSuiteReference mouseTestSuite;
extern SDLTest_TestSuiteReference penTestSuite;
extern SDLTest_TestSuiteReference pixelsTestSuite;
extern SDLTest_TestSuiteReference platformTestSuite;
extern SDLTest_TestSuiteReference propertiesTestSuite;

View File

@ -1,5 +1,4 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
@ -8,519 +7,284 @@
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
freely.
*/
#include <SDL3/SDL.h>
#define SDL_MAIN_USE_CALLBACKS 1
#include <SDL3/SDL_main.h>
#include <SDL3/SDL_test.h>
#include <SDL3/SDL_test_common.h>
#define WIDTH 1600
#define HEIGHT 1200
#define VERBOSE 0
#define ALWAYS_SHOW_PRESSURE_BOX 1
static SDLTest_CommonState *state;
static int quitting = 0;
static float last_x, last_y;
static float last_xtilt, last_ytilt, last_pressure, last_distance, last_rotation;
static int last_button;
static int last_touching; /* tip touches surface */
static int last_was_eraser;
static SDL_Texture *offscreen_texture = NULL;
static void DrawScreen(SDL_Renderer *renderer)
typedef struct Pen
{
float xdelta, ydelta, endx, endy;
/* off-screen texture to render into */
SDL_Texture *window_texture;
const float X = 128.0f, Y = 128.0f; /* mid-point in the off-screen texture */
SDL_FRect dest_rect;
float tilt_vec_x = SDL_sinf(last_xtilt * SDL_PI_F / 180.0f);
float tilt_vec_y = SDL_sinf(last_ytilt * SDL_PI_F / 180.0f);
int color = last_button + 1;
if (!renderer) {
return;
}
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
SDL_SetRenderDrawColor(renderer, 0x40, 0x40, 0x40, 0xff);
SDL_RenderClear(renderer);
if (offscreen_texture == NULL) {
offscreen_texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, (int)(X * 2.0f), (int)(Y * 2.0f));
}
/* Render into off-screen texture so we can do pixel-precise rendering later */
window_texture = SDL_GetRenderTarget(renderer);
SDL_SetRenderTarget(renderer, offscreen_texture);
/* Rendering starts here */
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
SDL_SetRenderDrawColor(renderer, 0x40, 0x40, 0x40, 0xff);
SDL_RenderClear(renderer);
SDL_SetRenderDrawColor(renderer, 0xa0, 0xa0, 0xa0, 0xff);
if (last_touching) {
SDL_FRect rect;
rect.x = 0;
rect.y = 0;
rect.w = 2.0f * X - 1.0f;
rect.h = 2.0f * Y - 1.0f;
SDL_RenderRect(renderer, &rect);
} else {
/* Show where the pen is rotating when it isn't touching the surface.
Otherwise we draw the rotation angle below together with pressure information. */
float rot_vecx = SDL_sinf(last_rotation / 180.0f * SDL_PI_F);
float rot_vecy = -SDL_cosf(last_rotation / 180.0f * SDL_PI_F);
float px = X + rot_vecx * 100.0f;
float py = Y + rot_vecy * 100.0f;
float px2 = X + rot_vecx * 80.0f;
float py2 = Y + rot_vecy * 80.0f;
SDL_RenderLine(renderer,
px, py,
px2 + rot_vecy * 20.0f,
py2 - rot_vecx * 20.0f);
SDL_RenderLine(renderer,
px, py,
px2 - rot_vecy * 20.0f,
py2 + rot_vecx * 20.0f);
}
if (last_was_eraser) {
SDL_FRect rect;
rect.x = X - 10.0f;
rect.y = Y - 10.0f;
rect.w = 21.0f;
rect.h = 21.0f;
SDL_SetRenderDrawColor(renderer, 0x00, 0xff, 0xff, 0xff);
SDL_RenderFillRect(renderer, &rect);
} else {
float distance = last_distance * 50.0f;
SDL_SetRenderDrawColor(renderer, 0xff, 0, 0, 0xff);
SDL_RenderLine(renderer,
X - 10.0f - distance, Y,
X - distance, Y);
SDL_RenderLine(renderer,
X + 10.0f + distance, Y,
X + distance, Y);
SDL_RenderLine(renderer,
X, Y - 10.0f - distance,
X, Y - distance);
SDL_RenderLine(renderer,
X, Y + 10.0f + distance,
X, Y + distance);
}
/* Draw a cone based on the direction the pen is leaning as if it were shining a light. */
/* Colour derived from pens, intensity based on pressure: */
SDL_SetRenderDrawColor(renderer,
(color & 0x01) ? 0xff : 0,
(color & 0x02) ? 0xff : 0,
(color & 0x04) ? 0xff : 0,
(int)(0xff));
xdelta = -tilt_vec_x * 100.0f;
ydelta = -tilt_vec_y * 100.0f;
endx = X + xdelta;
endy = Y + ydelta;
SDL_RenderLine(renderer, X, Y, endx, endy);
SDL_SetRenderDrawColor(renderer,
(color & 0x01) ? 0xff : 0,
(color & 0x02) ? 0xff : 0,
(color & 0x04) ? 0xff : 0,
(Uint8)(0xff * last_pressure));
/* Cone base width based on pressure: */
SDL_RenderLine(renderer, X, Y, endx + (ydelta * last_pressure / 3.0f), endy - (xdelta * last_pressure / 3.0f));
SDL_RenderLine(renderer, X, Y, endx - (ydelta * last_pressure / 3.0f), endy + (xdelta * last_pressure / 3.0f));
/* If tilt is very small (or zero, for pens that don't have tilt), add some extra lines, rotated by the current rotation value */
if (ALWAYS_SHOW_PRESSURE_BOX || (SDL_fabsf(tilt_vec_x) < 0.2f && SDL_fabsf(tilt_vec_y) < 0.2f)) {
int rot;
float pressure = last_pressure * 80.0f;
/* Four times, rotated 90 degrees, so that we get a box */
for (rot = 0; rot < 4; ++rot) {
float vecx = SDL_cosf((last_rotation + (rot * 90.0f)) / 180.0f * SDL_PI_F);
float vecy = SDL_sinf((last_rotation + (rot * 90.0f)) / 180.0f * SDL_PI_F);
float px = X + vecx * pressure;
float py = Y + vecy * pressure;
SDL_RenderLine(renderer,
px + vecy * 10.0f, py - vecx * 10.0f,
px - vecy * 10.0f, py + vecx * 10.0f);
if (rot == 3) {
int r = 0;
for (; r >= 0; r -= 2) {
float delta = 10.0f - ((float) r);
SDL_RenderLine(renderer,
px + vecy * delta, py - vecx * delta,
px + (vecx * pressure * 0.4f),
py + (vecy * pressure * 0.4f));
SDL_RenderLine(renderer,
px - vecy * delta, py + vecx * delta,
px + (vecx * pressure * 0.4f),
py + (vecy * pressure * 0.4f));
}
}
}
}
SDL_SetRenderTarget(renderer, window_texture);
/* Now render to pixel-precise position */
dest_rect.x = last_x - X;
dest_rect.y = last_y - Y;
dest_rect.w = X * 2.0f;
dest_rect.h = Y * 2.0f;
SDL_RenderTexture(renderer, offscreen_texture, NULL, &dest_rect);
SDL_RenderPresent(renderer);
}
static void dump_state(void)
{
int i;
int pens_nr;
/* Make sure this also works with a NULL parameter */
SDL_PenID* pens = SDL_GetPens(NULL);
if (pens) {
SDL_free(pens);
}
pens = SDL_GetPens(&pens_nr);
if (!pens) {
SDL_Log("Couldn't get pens: %s\n", SDL_GetError());
return;
}
SDL_Log("Found %d pens (terminated by %u)\n", pens_nr, (unsigned) pens[pens_nr]);
for (i = 0; i < pens_nr; ++i) {
SDL_PenID penid = pens[i];
SDL_GUID guid = SDL_GetPenGUID(penid);
char guid_str[33];
SDL_PenID pen;
Uint8 r, g, b;
float axes[SDL_PEN_NUM_AXES];
float x, y;
int k;
SDL_PenCapabilityInfo info;
Uint32 status = SDL_GetPenStatus(penid, &x, &y, axes, SDL_PEN_NUM_AXES);
const SDL_PenCapabilityFlags capabilities = SDL_GetPenCapabilities(penid, &info);
char *type;
char *buttons_str;
float x;
float y;
Uint32 buttons;
SDL_bool eraser;
SDL_bool touching;
struct Pen *next;
} Pen;
SDL_GUIDToString(guid, guid_str, sizeof(guid_str));
static SDL_Renderer *renderer = NULL;
static SDLTest_CommonState *state = NULL;
static SDL_Texture *white_pixel = NULL;
static Pen pens;
switch (SDL_GetPenType(penid)) {
case SDL_PEN_TYPE_ERASER:
type = "Eraser";
break;
case SDL_PEN_TYPE_PEN:
type = "Pen";
break;
case SDL_PEN_TYPE_PENCIL:
type = "Pencil";
break;
case SDL_PEN_TYPE_BRUSH:
type = "Brush";
break;
case SDL_PEN_TYPE_AIRBRUSH:
type = "Airbrush";
break;
default:
type = "Unknown (bug?)";
}
switch (info.num_buttons) {
case SDL_PEN_INFO_UNKNOWN:
SDL_asprintf(&buttons_str, "? buttons");
break;
case 1:
SDL_asprintf(&buttons_str, "1 button");
break;
default:
SDL_asprintf(&buttons_str, "%d button", info.num_buttons);
break;
}
SDL_Log("%s %lu: [%s] attached=%d, %s [cap= %08lx:%08lx =status] '%s'\n",
type,
(unsigned long) penid, guid_str,
SDL_PenConnected(penid), /* should always be SDL_TRUE during iteration */
buttons_str,
(unsigned long) capabilities,
(unsigned long) status,
SDL_GetPenName(penid));
SDL_free(buttons_str);
SDL_Log(" pos=(%.2f, %.2f)", x, y);
for (k = 0; k < SDL_PEN_NUM_AXES; ++k) {
SDL_bool supported = ((capabilities & SDL_PEN_AXIS_CAPABILITY(k)) != 0);
if (supported) {
if (k == SDL_PEN_AXIS_XTILT || k == SDL_PEN_AXIS_YTILT) {
if (info.max_tilt == SDL_PEN_INFO_UNKNOWN) {
SDL_Log(" axis %d: %.3f (max tilt unknown)", k, axes[k]);
} else {
SDL_Log(" axis %d: %.3f (tilt -%.1f..%.1f)", k, axes[k],
info.max_tilt, info.max_tilt);
}
} else {
SDL_Log(" axis %d: %.3f", k, axes[k]);
}
} else {
SDL_Log(" axis %d: unsupported (%.3f)", k, axes[k]);
}
}
}
SDL_free(pens);
}
static void update_axes(float *axes)
int SDL_AppInit(void **appstate, int argc, char *argv[])
{
last_xtilt = axes[SDL_PEN_AXIS_XTILT];
last_ytilt = axes[SDL_PEN_AXIS_YTILT];
last_pressure = axes[SDL_PEN_AXIS_PRESSURE];
last_distance = axes[SDL_PEN_AXIS_DISTANCE];
last_rotation = axes[SDL_PEN_AXIS_ROTATION];
}
static void update_axes_from_touch(const float pressure)
{
last_xtilt = 0;
last_ytilt = 0;
last_pressure = pressure;
last_distance = 0;
last_rotation = 0;
}
static void process_event(SDL_Event event)
{
SDLTest_CommonEvent(state, &event, &quitting);
switch (event.type) {
case SDL_EVENT_KEY_DOWN:
{
dump_state();
break;
}
case SDL_EVENT_MOUSE_MOTION:
case SDL_EVENT_MOUSE_BUTTON_DOWN:
case SDL_EVENT_MOUSE_BUTTON_UP:
#if VERBOSE
{
float x, y;
SDL_GetMouseState(&x, &y);
if (event.type == SDL_EVENT_MOUSE_MOTION) {
SDL_Log("[%lu] mouse motion: mouse ID %d is at (%.2f, %.2f) (state: %.2f,%.2f) delta (%.2f, %.2f)\n",
event.motion.timestamp,
event.motion.which,
event.motion.x, event.motion.y,
event.motion.xrel, event.motion.yrel,
x, y);
} else {
SDL_Log("[%lu] mouse button: mouse ID %d is at (%.2f, %.2f) (state: %.2f,%.2f)\n",
event.button.timestamp,
event.button.which,
event.button.x, event.button.y,
x, y);
}
}
#endif
if (event.motion.which != SDL_PEN_MOUSEID && event.motion.which != SDL_TOUCH_MOUSEID) {
SDL_ShowCursor();
} break;
case SDL_EVENT_PEN_MOTION:
{
SDL_PenMotionEvent *ev = &event.pmotion;
SDL_HideCursor();
last_x = ev->x;
last_y = ev->y;
update_axes(ev->axes);
last_was_eraser = ev->pen_state & SDL_PEN_ERASER_MASK;
#if VERBOSE
SDL_Log("[%lu] pen motion: %s %u at (%.4f, %.4f); pressure=%.3f, tilt=%.3f/%.3f, dist=%.3f [buttons=%02x]\n",
(unsigned long) ev->timestamp,
last_was_eraser ? "eraser" : "pen",
(unsigned int)ev->which, ev->x, ev->y, last_pressure, last_xtilt, last_ytilt, last_distance,
ev->pen_state);
#endif
} break;
case SDL_EVENT_PEN_UP:
case SDL_EVENT_PEN_DOWN: {
SDL_PenTipEvent *ev = &event.ptip;
last_x = ev->x;
last_y = ev->y;
update_axes(ev->axes);
last_was_eraser = ev->tip == SDL_PEN_TIP_ERASER;
last_button = ev->pen_state & 0xf; /* button mask */
last_touching = (event.type == SDL_EVENT_PEN_DOWN);
} break;
case SDL_EVENT_PEN_BUTTON_UP:
case SDL_EVENT_PEN_BUTTON_DOWN:
{
SDL_PenButtonEvent *ev = &event.pbutton;
SDL_HideCursor();
last_x = ev->x;
last_y = ev->y;
update_axes(ev->axes);
if (last_pressure > 0.0f && !last_touching) {
SDL_LogWarn(SDL_LOG_CATEGORY_TEST,
"[%lu] : reported pressure %.5f even though pen is not touching surface",
(unsigned long) ev->timestamp, last_pressure);
}
last_was_eraser = ev->pen_state & SDL_PEN_ERASER_MASK;
last_button = ev->pen_state & 0xf; /* button mask */
if ((ev->pen_state & SDL_PEN_DOWN_MASK) && !last_touching) {
SDL_LogWarn(SDL_LOG_CATEGORY_TEST,
"[%lu] : reported flags %x (SDL_PEN_FLAG_DOWN_MASK) despite not receiving SDL_EVENT_PEN_DOWN",
(unsigned long) ev->timestamp, ev->pen_state);
}
if (!(ev->pen_state & SDL_PEN_DOWN_MASK) && last_touching) {
SDL_LogWarn(SDL_LOG_CATEGORY_TEST,
"[%lu] : reported flags %x (no SDL_PEN_FLAG_DOWN_MASK) despite receiving SDL_EVENT_PEN_DOWN without SDL_EVENT_PEN_UP afterwards",
(unsigned long) ev->timestamp, ev->pen_state);
}
#if VERBOSE
SDL_Log("[%lu] pen button: %s %u at (%.4f, %.4f); BUTTON %d reported %s with event %s [pressure=%.3f, tilt=%.3f/%.3f, dist=%.3f]\n",
(unsigned long) ev->timestamp,
last_was_eraser ? "eraser" : "pen",
(unsigned int)ev->which, ev->x, ev->y,
ev->button,
(ev->state == SDL_PRESSED) ? "PRESSED"
: ((ev->state == SDL_RELEASED) ? "RELEASED" : "--invalid--"),
event.type == SDL_EVENT_PEN_BUTTON_UP ? "PENBUTTONUP" : "PENBUTTONDOWN",
last_pressure, last_xtilt, last_ytilt, last_distance);
#endif
} break;
case SDL_EVENT_WINDOW_PEN_ENTER:
SDL_Log("[%lu] Pen %lu entered window %lx",
(unsigned long) event.window.timestamp,
(unsigned long) event.window.data1,
(unsigned long) event.window.windowID);
break;
case SDL_EVENT_WINDOW_PEN_LEAVE:
SDL_Log("[%lu] Pen %lu left window %lx",
(unsigned long) event.window.timestamp,
(unsigned long) event.window.data1,
(unsigned long) event.window.windowID);
break;
#if VERBOSE
case SDL_EVENT_WINDOW_MOUSE_ENTER:
SDL_Log("[%lu] Mouse entered window %lx",
(unsigned long) event.window.timestamp,
(unsigned long) event.window.windowID);
break;
case SDL_EVENT_WINDOW_MOUSE_LEAVE:
SDL_Log("[%lu] Mouse left window %lx",
(unsigned long) event.window.timestamp,
(unsigned long) event.window.windowID);
break;
#endif
case SDL_EVENT_FINGER_DOWN:
case SDL_EVENT_FINGER_MOTION:
case SDL_EVENT_FINGER_UP:
{
SDL_TouchFingerEvent *ev = &event.tfinger;
int w, h;
SDL_HideCursor();
SDL_GetWindowSize(SDL_GetWindowFromID(ev->windowID), &w, &h);
last_x = ev->x * w;
last_y = ev->y * h;
update_axes_from_touch(ev->pressure);
last_was_eraser = SDL_FALSE;
last_button = 0;
last_touching = (ev->type != SDL_EVENT_FINGER_UP);
#if VERBOSE
SDL_Log("[%lu] finger %s: %s (touchId: %" SDL_PRIu64 ", fingerId: %" SDL_PRIu64 ") at (%.4f, %.4f); pressure=%.3f\n",
(unsigned long) ev->timestamp,
ev->type == SDL_EVENT_FINGER_DOWN ? "down" : (ev->type == SDL_EVENT_FINGER_MOTION ? "motion" : "up"),
SDL_GetTouchDeviceName(ev->touchId),
ev->touchId,
ev->fingerId,
last_x, last_y, last_pressure);
#endif
} break;
default:
break;
}
}
static void loop(void)
{
SDL_Event event;
int i;
for (i = 0; i < state->num_windows; ++i) {
if (state->renderers[i]) {
DrawScreen(state->renderers[i]);
}
}
SDL_srand(0);
if (SDL_WaitEventTimeout(&event, 10)) {
process_event(event);
}
while (SDL_PollEvent(&event)) {
process_event(event);
}
}
int main(int argc, char *argv[])
{
/* Initialize test framework */
state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO);
if (!state) {
return SDL_APP_FAILURE;
}
/* Enable standard application logging */
SDL_SetLogPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO);
/* Parse commandline */
for (i = 1; i < argc;) {
int consumed = SDLTest_CommonArg(state, i);
if (consumed <= 0) {
static const char *options[] = {
NULL,
};
SDLTest_CommonLogUsage(state, argv[0], options);
SDL_Quit();
SDLTest_CommonDestroyState(state);
return 1;
}
i += consumed;
}
state->window_title = "Pressure-Sensitive Pen Test";
state->window_w = WIDTH;
state->window_h = HEIGHT;
state->skip_renderer = SDL_FALSE;
state->num_windows = 1;
if (!SDLTest_CommonDefaultArgs(state, argc, argv) || !SDLTest_CommonInit(state)) {
/* Load the SDL library */
if (!SDLTest_CommonInit(state)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't initialize SDL: %s", SDL_GetError());
return SDL_APP_FAILURE;
}
SDL_SetLogPriorities(SDL_LOG_PRIORITY_VERBOSE);
renderer = state->renderers[0];
if (!renderer) {
/* SDL_Log("Couldn't create renderer: %s", SDL_GetError()); */
return SDL_APP_FAILURE;
}
white_pixel = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STATIC, 16, 16);
if (!white_pixel) {
SDL_Log("Couldn't create white_pixel texture: %s", SDL_GetError());
return SDL_APP_FAILURE;
} else {
const SDL_Rect rect = { 0, 0, 16, 16 };
Uint32 pixels[16 * 16];
SDL_memset(pixels, 0xFF, sizeof (pixels));
SDL_UpdateTexture(white_pixel, &rect, pixels, 16 * sizeof (Uint32));
}
SDL_HideCursor();
return SDL_APP_CONTINUE;
}
static Pen *FindPen(SDL_PenID which)
{
Pen *i;
for (i = pens.next; i != NULL; i = i->next) {
if (i->pen == which) {
return i;
}
}
return NULL;
}
int SDL_AppEvent(void *appstate, const SDL_Event *event)
{
Pen *pen = NULL;
switch (event->type) {
case SDL_EVENT_PEN_PROXIMITY_IN: {
pen = (Pen *) SDL_calloc(1, sizeof (*pen));
if (!pen) {
SDL_Log("Out of memory!");
return SDL_APP_FAILURE;
}
SDL_Log("Pen %" SDL_PRIu32 " enters proximity!", event->pproximity.which);
pen->pen = event->pproximity.which;
pen->r = (Uint8) SDL_rand(256);
pen->g = (Uint8) SDL_rand(256);
pen->b = (Uint8) SDL_rand(256);
pen->x = 320.0f;
pen->y = 240.0f;
pen->next = pens.next;
pens.next = pen;
return SDL_APP_CONTINUE;
}
case SDL_EVENT_PEN_PROXIMITY_OUT: {
Pen *prev = &pens;
Pen *i;
SDL_Log("Pen %" SDL_PRIu32 " leaves proximity!", event->pproximity.which);
for (i = pens.next; i != NULL; i = i->next) {
if (i->pen == event->pproximity.which) {
prev->next = i->next;
SDL_free(i);
break;
}
prev = i;
}
return SDL_APP_CONTINUE;
}
case SDL_EVENT_PEN_DOWN:
/*SDL_Log("Pen %" SDL_PRIu32 " down!", event->ptouch.which);*/
pen = FindPen(event->ptouch.which);
if (pen) {
pen->touching = SDL_TRUE;
pen->eraser = (event->ptouch.eraser != 0);
}
return SDL_APP_CONTINUE;
case SDL_EVENT_PEN_UP:
/*SDL_Log("Pen %" SDL_PRIu32 " up!", event->ptouch.which);*/
pen = FindPen(event->ptouch.which);
if (pen) {
pen->touching = SDL_FALSE;
pen->axes[SDL_PEN_AXIS_PRESSURE] = 0.0f;
}
return SDL_APP_CONTINUE;
case SDL_EVENT_PEN_BUTTON_DOWN:
/*SDL_Log("Pen %" SDL_PRIu32 " button %d down!", event->pbutton.which, (int) event->pbutton.button);*/
pen = FindPen(event->ptouch.which);
if (pen) {
pen->buttons |= (1 << event->pbutton.button);
}
return SDL_APP_CONTINUE;
case SDL_EVENT_PEN_BUTTON_UP:
/*SDL_Log("Pen %" SDL_PRIu32 " button %d up!", event->pbutton.which, (int) event->pbutton.button);*/
pen = FindPen(event->ptouch.which);
if (pen) {
pen->buttons &= ~(1 << event->pbutton.button);
}
return SDL_APP_CONTINUE;
case SDL_EVENT_PEN_MOTION:
/*SDL_Log("Pen %" SDL_PRIu32 " moved to (%f,%f)!", event->pmotion.which, event->pmotion.x, event->pmotion.y);*/
pen = FindPen(event->ptouch.which);
if (pen) {
pen->x = event->pmotion.x;
pen->y = event->pmotion.y;
}
return SDL_APP_CONTINUE;
case SDL_EVENT_PEN_AXIS:
/*SDL_Log("Pen %" SDL_PRIu32 " axis %d is now %f!", event->paxis.which, (int) event->paxis.axis, event->paxis.value);*/
pen = FindPen(event->ptouch.which);
if (pen && (event->paxis.axis < SDL_arraysize(pen->axes))) {
pen->axes[event->paxis.axis] = event->paxis.value;
}
return SDL_APP_CONTINUE;
case SDL_EVENT_KEY_DOWN: {
const SDL_Keycode sym = event->key.key;
if (sym == SDLK_ESCAPE || sym == SDLK_AC_BACK) {
SDL_Log("Key : Escape!");
return SDL_APP_SUCCESS;
}
break;
}
case SDL_EVENT_QUIT:
return SDL_APP_SUCCESS;
default:
break;
}
return SDLTest_CommonEventMainCallbacks(state, event);
}
static void DrawOnePen(Pen *pen, int num)
{
int i;
/* draw button presses for this pen. A square for each in the pen's color, offset down the screen so they don't overlap. */
SDL_SetRenderDrawColor(renderer, pen->r, pen->g, pen->b, 255);
for (i = 0; i < 8; i++) { /* we assume you don't have more than 8 buttons atm... */
if (pen->buttons & (1 << i)) {
const SDL_FRect rect = { 30.0f * ((float) i), ((float) num) * 30.0f, 30.0f, 30.0f };
SDL_RenderFillRect(renderer, &rect);
}
}
/* draw a square to represent pressure. Always green for eraser and blue for pen */
/* we do this with a texture, so we can trivially rotate it, which SDL_RenderFillRect doesn't offer. */
if (pen->axes[SDL_PEN_AXIS_PRESSURE] > 0.0f) {
const float size = (150.0f * pen->axes[SDL_PEN_AXIS_PRESSURE]) + 20.0f;
const float halfsize = size / 2.0f;
const SDL_FRect rect = { pen->x - halfsize, pen->y - halfsize, size, size };
const SDL_FPoint center = { halfsize, halfsize };
if (pen->eraser) {
SDL_SetTextureColorMod(white_pixel, 0, 255, 0);
} else {
SDL_SetTextureColorMod(white_pixel, 0, 0, 255);
}
SDL_RenderTextureRotated(renderer, white_pixel, NULL, &rect, pen->axes[SDL_PEN_AXIS_ROTATION], &center, SDL_FLIP_NONE);
}
/* draw a little square for position in the center of the pressure, with the pen-specific color. */
{
const float distance = pen->touching ? 0.0f : SDL_clamp(pen->axes[SDL_PEN_AXIS_DISTANCE], 0.0f, 1.0f);
const float size = 10 + (30.0f * (1.0f - distance));
const float halfsize = size / 2.0f;
const SDL_FRect rect = { pen->x - halfsize, pen->y - halfsize, size, size };
const SDL_FPoint center = { halfsize, halfsize };
SDL_SetTextureColorMod(white_pixel, pen->r, pen->g, pen->b);
SDL_RenderTextureRotated(renderer, white_pixel, NULL, &rect, pen->axes[SDL_PEN_AXIS_ROTATION], &center, SDL_FLIP_NONE);
}
}
int SDL_AppIterate(void *appstate)
{
int num = 0;
Pen *pen;
SDL_SetRenderDrawColor(renderer, 0x99, 0x99, 0x99, 255);
SDL_RenderClear(renderer);
for (pen = pens.next; pen != NULL; pen = pen->next, num++) {
DrawOnePen(pen, num);
}
SDL_RenderPresent(renderer);
return SDL_APP_CONTINUE;
}
void SDL_AppQuit(void *appstate)
{
Pen *i, *next;
for (i = pens.next; i != NULL; i = next) {
next = i->next;
SDL_free(i);
}
pens.next = NULL;
SDL_DestroyTexture(white_pixel);
SDLTest_CommonQuit(state);
return 1;
}
while (!quitting) {
loop();
}
SDLTest_CommonQuit(state);
return 0;
}