From a9d70dbacb1ac26ee33ca6de93749619518a3c55 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sun, 26 May 2024 11:38:40 -0400 Subject: [PATCH] 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). --- .../testautomation/testautomation.vcxproj | 6 - .../SDLTest/SDLTest.xcodeproj/project.pbxproj | 2 - include/SDL3/SDL_events.h | 153 +- include/SDL3/SDL_pen.h | 268 +-- src/dynapi/SDL_dynapi.sym | 8 - src/dynapi/SDL_dynapi_overrides.h | 8 - src/dynapi/SDL_dynapi_procs.h | 8 - src/events/SDL_categories.c | 29 +- src/events/SDL_categories_c.h | 4 +- src/events/SDL_events.c | 65 +- src/events/SDL_events_c.h | 1 + src/events/SDL_mouse.c | 9 +- src/events/SDL_pen.c | 1412 ++++-------- src/events/SDL_pen_c.h | 343 +-- src/video/SDL_video.c | 9 + src/video/wayland/SDL_waylandevents.c | 571 ++--- src/video/wayland/SDL_waylandevents_c.h | 47 +- src/video/wayland/SDL_waylandvideo.c | 2 +- src/video/wayland/SDL_waylandvideo.h | 3 +- src/video/x11/SDL_x11events.c | 3 - src/video/x11/SDL_x11pen.c | 770 +++---- src/video/x11/SDL_x11pen.h | 45 +- src/video/x11/SDL_x11video.c | 3 +- src/video/x11/SDL_x11video.h | 9 +- src/video/x11/SDL_x11xinput2.c | 101 +- test/testautomation.c | 3 - test/testautomation_pen.c | 1940 ----------------- test/testautomation_suites.h | 1 - test/testpen.c | 752 +++---- 29 files changed, 1429 insertions(+), 5146 deletions(-) delete mode 100644 test/testautomation_pen.c diff --git a/VisualC/tests/testautomation/testautomation.vcxproj b/VisualC/tests/testautomation/testautomation.vcxproj index af345542d..8dd3dfc44 100644 --- a/VisualC/tests/testautomation/testautomation.vcxproj +++ b/VisualC/tests/testautomation/testautomation.vcxproj @@ -212,12 +212,6 @@ - - $(ProjectDir)\..\..\..\src;%(AdditionalIncludeDirectories) - $(ProjectDir)\..\..\..\src;%(AdditionalIncludeDirectories) - $(ProjectDir)\..\..\..\src;%(AdditionalIncludeDirectories) - $(ProjectDir)\..\..\..\src;%(AdditionalIncludeDirectories) - diff --git a/Xcode/SDLTest/SDLTest.xcodeproj/project.pbxproj b/Xcode/SDLTest/SDLTest.xcodeproj/project.pbxproj index a3b389edc..b1b49715a 100644 --- a/Xcode/SDLTest/SDLTest.xcodeproj/project.pbxproj +++ b/Xcode/SDLTest/SDLTest.xcodeproj/project.pbxproj @@ -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 = ""; }; 66E88E8A203B778F0004D44E /* testyuv_cvt.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = testyuv_cvt.c; sourceTree = ""; }; - A1A859442BC72FC20045DD6C /* testautomation_pen.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = testautomation_pen.c; sourceTree = ""; }; A1A859482BC72FC20045DD6C /* testautomation_properties.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = testautomation_properties.c; sourceTree = ""; }; A1A859492BC72FC20045DD6C /* testautomation_subsystems.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = testautomation_subsystems.c; sourceTree = ""; }; A1A8594A2BC72FC20045DD6C /* testautomation_log.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = testautomation_log.c; sourceTree = ""; }; @@ -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 */, diff --git a/include/SDL3/SDL_events.h b/include/SDL3/SDL_events.h index 1bf311f81..57b412df2 100644 --- a/include/SDL3/SDL_events.h +++ b/include/SDL3/SDL_events.h @@ -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,67 +715,119 @@ 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_PROXIMITY_IN or SDL_EVENT_PEN_PROXIMITY_OUT */ + 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_PenProximityEvent; + +/** + * 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. + */ +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 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 */ +} 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 */ - 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; - -/** - * Pressure-sensitive pen motion / pressure / angle event structure - * (event.pmotion.*) - * - * \since This struct is available since SDL 3.0.0. - */ -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_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_PenMotionEvent; + 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 { - SDL_EventType type; /**< SDL_EVENT_PEN_BUTTON_DOWN or SDL_EVENT_PEN_BUTTON_UP */ + 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 */ diff --git a/include/SDL3/SDL_pen.h b/include/SDL3/SDL_pen.h index c02c0081f..02b22b07f 100644 --- a/include/SDL3/SDL_pen.h +++ b/include/SDL3/SDL_pen.h @@ -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 -#include -#include -#include /* 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 } diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index 7d4b9c23f..7eb3c21f9 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -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; diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index 5212a3b10..9567b4114 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -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 diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index 2deddd021..471bb8432 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -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) diff --git a/src/events/SDL_categories.c b/src/events/SDL_categories.c index cd57c632f..657efdf40 100644 --- a/src/events/SDL_categories.c +++ b/src/events/SDL_categories.c @@ -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; diff --git a/src/events/SDL_categories_c.h b/src/events/SDL_categories_c.h index b184311fb..6eba1e95f 100644 --- a/src/events/SDL_categories_c.h +++ b/src/events/SDL_categories_c.h @@ -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; diff --git a/src/events/SDL_events.c b/src/events/SDL_events.c index 41be580dc..d4793653a 100644 --- a/src/events/SDL_events.c +++ b/src/events/SDL_events.c @@ -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; diff --git a/src/events/SDL_events_c.h b/src/events/SDL_events_c.h index 32e00cb1f..ba37401ea 100644 --- a/src/events/SDL_events_c.h +++ b/src/events/SDL_events_c.h @@ -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 */ diff --git a/src/events/SDL_mouse.c b/src/events/SDL_mouse.c index 06ff77956..5af1906f9 100644 --- a/src/events/SDL_mouse.c +++ b/src/events/SDL_mouse.c @@ -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); diff --git a/src/events/SDL_pen.c b/src/events/SDL_pen.c index ac33ee4cf..df5860fc3 100644 --- a/src/events/SDL_pen.c +++ b/src/events/SDL_pen.c @@ -26,1088 +26,484 @@ #include "SDL_events_c.h" #include "SDL_pen_c.h" -#define PEN_MOUSE_EMULATE 0 /* pen behaves like mouse */ -#define PEN_MOUSE_STATELESS 1 /* pen does not update mouse state */ -#define PEN_MOUSE_DISABLED 2 /* pen does not send mouse events */ - -/* flags that are not SDL_PEN_FLAG_ */ -#define PEN_FLAGS_CAPABILITIES (~(SDL_PEN_FLAG_NEW | SDL_PEN_FLAG_DETACHED | SDL_PEN_FLAG_STALE)) - -#define PEN_GET_PUBLIC_STATUS_MASK(pen) (((pen)->header.flags & (SDL_PEN_ERASER_MASK | SDL_PEN_DOWN_MASK))) - -static int pen_mouse_emulation_mode = PEN_MOUSE_EMULATE; /* SDL_HINT_PEN_NOT_MOUSE */ - -static int pen_delay_mouse_button_mode = 1; /* SDL_HINT_PEN_DELAY_MOUSE_BUTTON */ - -#ifndef SDL_THREADS_DISABLED -static SDL_Mutex *SDL_pen_access_lock; -# define SDL_LOCK_PENS() SDL_LockMutex(SDL_pen_access_lock) -# define SDL_UNLOCK_PENS() SDL_UnlockMutex(SDL_pen_access_lock) -#else -# define SDL_LOCK_PENS() -# define SDL_UNLOCK_PENS() -#endif - -static struct +typedef struct SDL_Pen { - SDL_Pen *pens; /* if "sorted" is SDL_TRUE: - sorted by: (is-attached, id): - - first all attached pens, in ascending ID order - - then all detached pens, in ascending ID order */ - size_t pens_allocated; /* # entries allocated to "pens" */ - size_t pens_known; /* <= pens_allocated; this includes detached pens */ - size_t pens_attached; /* <= pens_known; all attached pens are at the beginning of "pens" */ - SDL_bool sorted; /* This is SDL_FALSE in the period between SDL_PenGCMark() and SDL_PenGCSWeep() */ -} pen_handler; + SDL_PenID instance_id; + char *name; + SDL_PenInfo info; + float axes[SDL_PEN_NUM_AXES]; + float x; + float y; + SDL_PenInputFlags input_state; + void *driverdata; +} SDL_Pen; -static SDL_PenID pen_invalid = { SDL_PEN_INVALID }; +// we assume there's usually 0-1 pens in most cases and this list doesn't +// usually change after startup, so a simple array with a RWlock is fine for now. +static SDL_RWLock *pen_device_rwlock = NULL; +static SDL_Pen *pen_devices SDL_GUARDED_BY(pen_device_rwlock) = NULL; +static int pen_device_count SDL_GUARDED_BY(pen_device_rwlock) = 0; -static SDL_GUID pen_guid_zero = { { 0 } }; - -#define SDL_LOAD_LOCK_PEN(penvar, instance_id, err_return) \ - SDL_Pen *penvar; \ - if (instance_id == SDL_PEN_INVALID) { \ - SDL_SetError("Invalid SDL_PenID"); \ - return (err_return); \ - } \ - SDL_LOCK_PENS();\ - penvar = SDL_GetPenPtr(instance_id); \ - if (!(penvar)) { \ - SDL_SetError("Stale SDL_PenID"); \ - SDL_UNLOCK_PENS(); \ - return (err_return); \ - } - -static int SDL_GUIDCompare(SDL_GUID lhs, SDL_GUID rhs) +// You must hold pen_device_rwlock before calling this, and retval is only safe while lock is held! +// If SDL isn't initialized, grabbing the NULL lock is a no-op and there will be zero devices, so +// locking and calling this in that case will do the right thing. +static SDL_Pen *FindPenByInstanceId(SDL_PenID instance_id) SDL_REQUIRES_SHARED(pen_device_rwlock) { - return SDL_memcmp(lhs.data, rhs.data, sizeof(lhs.data)); -} - -static int SDLCALL pen_compare(const void *lhs, const void *rhs) -{ - int left_inactive = (((const SDL_Pen *)lhs)->header.flags & SDL_PEN_FLAG_DETACHED); - int right_inactive = (((const SDL_Pen *)rhs)->header.flags & SDL_PEN_FLAG_DETACHED); - if (left_inactive && !right_inactive) { - return 1; - } - if (!left_inactive && right_inactive) { - return -1; - } - return ((const SDL_Pen *)lhs)->header.id - ((const SDL_Pen *)rhs)->header.id; -} - -static int SDLCALL pen_header_compare(const void *lhs, const void *rhs) -{ - const SDL_PenHeader *l = (const SDL_PenHeader *)lhs; - const SDL_PenHeader *r = (const SDL_PenHeader *)rhs; - int l_detached = l->flags & SDL_PEN_FLAG_DETACHED; - int r_detached = r->flags & SDL_PEN_FLAG_DETACHED; - - if (l_detached != r_detached) { - if (l_detached) { - return -1; - } - return 1; - } - - return l->id - r->id; -} - -SDL_Pen *SDL_GetPenPtr(Uint32 instance_id) -{ - unsigned int i; - - if (!pen_handler.pens) { - return NULL; - } - - if (pen_handler.sorted) { - SDL_PenHeader key; - SDL_Pen *pen; - - SDL_zero(key); - key.id = instance_id; - - pen = (SDL_Pen *)SDL_bsearch(&key, pen_handler.pens, pen_handler.pens_known, sizeof(SDL_Pen), pen_header_compare); - if (pen) { - return pen; - } - /* If the pen is not active, fall through */ - } - - /* fall back to linear search */ - for (i = 0; i < pen_handler.pens_known; ++i) { - if (pen_handler.pens[i].header.id == instance_id) { - return &pen_handler.pens[i]; + if (instance_id) { + for (int i = 0; i < pen_device_count; i++) { + if (pen_devices[i].instance_id == instance_id) { + return &pen_devices[i]; + } } } + SDL_SetError("Invalid pen instance ID"); return NULL; } -SDL_PenID *SDL_GetPens(int *count) +SDL_PenID SDL_FindPenByHandle(void *handle) { - int i; - int pens_nr = (int)pen_handler.pens_attached; - SDL_PenID *pens = (SDL_PenID *)SDL_calloc(pens_nr + 1, sizeof(SDL_PenID)); - if (!pens) { /* OOM */ - return pens; - } - - for (i = 0; i < pens_nr; ++i) { - pens[i] = pen_handler.pens[i].header.id; - } - - if (count) { - *count = pens_nr; - } - return pens; -} - -SDL_PenID SDL_GetPenFromGUID(SDL_GUID guid) -{ - unsigned int i; - /* Must do linear search */ - SDL_LOCK_PENS(); - for (i = 0; i < pen_handler.pens_known; ++i) { - SDL_Pen *pen = &pen_handler.pens[i]; - - if (0 == SDL_GUIDCompare(guid, pen->guid)) { - SDL_UNLOCK_PENS(); - return pen->header.id; + SDL_PenID retval = 0; + SDL_LockRWLockForReading(pen_device_rwlock); + for (int i = 0; i < pen_device_count; i++) { + if (pen_devices[i].driverdata == handle) { + retval = pen_devices[i].instance_id; + break; } } - SDL_UNLOCK_PENS(); - return pen_invalid; + SDL_UnlockRWLock(pen_device_rwlock); + return retval; } -SDL_bool SDL_PenConnected(SDL_PenID instance_id) +SDL_PenID SDL_FindPenByCallback(SDL_bool (*callback)(void *handle, void *userdata), void *userdata) { - SDL_Pen *pen; - SDL_bool result; - - if (instance_id == SDL_PEN_INVALID) { - return SDL_FALSE; + SDL_PenID retval = 0; + SDL_LockRWLockForReading(pen_device_rwlock); + for (int i = 0; i < pen_device_count; i++) { + if (callback(pen_devices[i].driverdata, userdata)) { + retval = pen_devices[i].instance_id; + break; + } } - - SDL_LOCK_PENS(); - pen = SDL_GetPenPtr(instance_id); - if (!pen) { - SDL_UNLOCK_PENS(); - return SDL_FALSE; - } - result = (pen->header.flags & SDL_PEN_FLAG_DETACHED) ? SDL_FALSE : SDL_TRUE; - SDL_UNLOCK_PENS(); - return result; + SDL_UnlockRWLock(pen_device_rwlock); + return retval; } -SDL_GUID SDL_GetPenGUID(SDL_PenID instance_id) + + +// public API ... + +int SDL_InitPen(void) { - SDL_GUID result; - SDL_LOAD_LOCK_PEN(pen, instance_id, pen_guid_zero); - result = pen->guid; - SDL_UNLOCK_PENS(); - return result; + SDL_assert(pen_device_rwlock == NULL); + SDL_assert(pen_devices == NULL); + SDL_assert(pen_device_count == 0); + pen_device_rwlock = SDL_CreateRWLock(); + if (!pen_device_rwlock) { + return -1; + } + return 0; +} + +void SDL_QuitPen(void) +{ + SDL_DestroyRWLock(pen_device_rwlock); + pen_device_rwlock = 0; + SDL_free(pen_devices); + pen_devices = NULL; + pen_device_count = 0; +} + +#if 0 // not a public API at the moment. +SDL_PenID *SDL_GetPens(int *count) +{ + SDL_LockRWLockForReading(pen_device_rwlock); + const int num_devices = pen_device_count; + SDL_PenID *retval = (SDL_PenID *) SDL_malloc((num_devices + 1) * sizeof (SDL_PenID)); + if (retval) { + for (int i = 0; i < num_devices; i++) { + retval[i] = pen_devices[i].instance_id; + } + retval[num_devices] = 0; // null-terminated. + } + SDL_UnlockRWLock(pen_device_rwlock); + + if (count) { + *count = retval ? num_devices : 0; + } + return retval; } const char *SDL_GetPenName(SDL_PenID instance_id) { - const char *result; - SDL_LOAD_LOCK_PEN(pen, instance_id, NULL); - result = SDL_GetPersistentString(pen->name); - SDL_UNLOCK_PENS(); + SDL_LockRWLockForReading(pen_device_rwlock); + const SDL_Pen *pen = FindPenByInstanceId(instance_id); + const char *result = pen ? SDL_GetPersistentString(pen->name) : NULL; + SDL_UnlockRWLock(pen_device_rwlock); return result; } -SDL_PenSubtype SDL_GetPenType(SDL_PenID instance_id) +int SDL_GetPenInfo(SDL_PenID instance_id, SDL_PenInfo *info) { - SDL_PenSubtype result; - SDL_LOAD_LOCK_PEN(pen, instance_id, SDL_PEN_TYPE_UNKNOWN); - result = pen->type; - SDL_UNLOCK_PENS(); - return result; -} - -SDL_PenCapabilityFlags SDL_GetPenCapabilities(SDL_PenID instance_id, SDL_PenCapabilityInfo *info) -{ - Uint32 result; - SDL_LOAD_LOCK_PEN(pen, instance_id, 0u); + SDL_LockRWLockForReading(pen_device_rwlock); + const SDL_Pen *pen = FindPenByInstanceId(instance_id); + const int retval = pen ? 0 : -1; if (info) { - *info = pen->info; + if (retval == 0) { + SDL_copyp(info, &pen->info); + } else { + SDL_zerop(info); + } } - result = pen->header.flags & PEN_FLAGS_CAPABILITIES; - SDL_UNLOCK_PENS(); - return result; + SDL_UnlockRWLock(pen_device_rwlock); + return retval; } -Uint32 SDL_GetPenStatus(SDL_PenID instance_id, float *x, float *y, float *axes, size_t num_axes) +SDL_PenInputFlags SDL_GetPenStatus(SDL_PenID instance_id, float *axes, int num_axes) { - Uint32 result; - SDL_LOAD_LOCK_PEN(pen, instance_id, 0u); + if (num_axes < 0) { + num_axes = 0; + } - if (x) { - *x = pen->last.x; + SDL_LockRWLockForReading(pen_device_rwlock); + const SDL_Pen *pen = FindPenByInstanceId(instance_id); + SDL_PenInputFlags retval = 0; + if (pen) { + retval = pen->input_state; + if (axes && num_axes) { + SDL_memcpy(axes, pen->axes, SDL_min(num_axes, SDL_PEN_NUM_AXES) * sizeof (*axes)); + // zero out axes we don't know about, in case the caller built with newer SDL headers that support more of them. + if (num_axes > SDL_PEN_NUM_AXES) { + SDL_memset(&axes[SDL_PEN_NUM_AXES], '\0', (num_axes - SDL_PEN_NUM_AXES) * sizeof (*axes)); + } + } } - if (y) { - *y = pen->last.y; - } - if (axes && num_axes) { - size_t axes_to_copy = SDL_min(num_axes, SDL_PEN_NUM_AXES); - SDL_memcpy(axes, pen->last.axes, sizeof(float) * axes_to_copy); - } - result = pen->last.buttons | (pen->header.flags & (SDL_PEN_INK_MASK | SDL_PEN_ERASER_MASK | SDL_PEN_DOWN_MASK)); - SDL_UNLOCK_PENS(); - return result; + SDL_UnlockRWLock(pen_device_rwlock); + return retval; } -/* Backend functionality */ - -/* Sort all pens. Only safe during SDL_LOCK_PENS. */ -static void pen_sort(void) +SDL_bool SDL_PenConnected(SDL_PenID instance_id) { - SDL_qsort(pen_handler.pens, - pen_handler.pens_known, - sizeof(SDL_Pen), - pen_compare); - pen_handler.sorted = SDL_TRUE; + SDL_LockRWLockForReading(pen_device_rwlock); + const SDL_Pen *pen = FindPenByInstanceId(instance_id); + const SDL_bool retval = (pen != NULL); + SDL_UnlockRWLock(pen_device_rwlock); + return retval; +} +#endif + +SDL_PenCapabilityFlags SDL_GetPenCapabilityFromAxis(SDL_PenAxis axis) +{ + // the initial capability bits happen to match up, but as + // more features show up later, the bits may no longer be contiguous! + if ((axis >= SDL_PEN_AXIS_PRESSURE) && (axis <= SDL_PEN_AXIS_SLIDER)) { + return ((SDL_PenCapabilityFlags) 1u) << ((SDL_PenCapabilityFlags) axis); + } + return 0; // oh well. } -SDL_Pen *SDL_PenModifyBegin(Uint32 instance_id) +SDL_PenID SDL_AddPenDevice(Uint64 timestamp, const char *name, const SDL_PenInfo *info, void *handle) { - SDL_PenID id = { 0 }; - const size_t alloc_growth_constant = 1; /* Expect few pens */ - SDL_Pen *pen; + SDL_assert(handle != NULL); // just allocate a Uint8 so you have a unique pointer if not needed! + SDL_assert(SDL_FindPenByHandle(handle) == 0); // Backends shouldn't double-add pens! + SDL_assert(pen_device_rwlock != NULL); // subsystem should be initialized by now! - id = instance_id; - - if (id == SDL_PEN_INVALID) { - SDL_SetError("Invalid SDL_PenID"); - return NULL; + char *namecpy = SDL_strdup(name ? name : "Unnamed pen"); + if (!namecpy) { + return 0; } - SDL_LOCK_PENS(); - pen = SDL_GetPenPtr(id); + SDL_PenID retval = 0; + + SDL_LockRWLockForWriting(pen_device_rwlock); + + SDL_Pen *pen = NULL; + void *ptr = SDL_realloc(pen_devices, (pen_device_count + 1) * sizeof (*pen)); + if (ptr) { + retval = (SDL_PenID) SDL_GetNextObjectID(); + pen_devices = (SDL_Pen *) ptr; + pen = &pen_devices[pen_device_count]; + pen_device_count++; + + SDL_zerop(pen); + pen->instance_id = retval; + pen->name = namecpy; + if (info) { + SDL_copyp(&pen->info, info); + } + pen->driverdata = handle; + // axes and input state defaults to zero. + } + SDL_UnlockRWLock(pen_device_rwlock); if (!pen) { - if (!pen_handler.pens || pen_handler.pens_known == pen_handler.pens_allocated) { - size_t pens_to_allocate = pen_handler.pens_allocated + alloc_growth_constant; - SDL_Pen *pens; - if (pen_handler.pens) { - pens = (SDL_Pen *)SDL_realloc(pen_handler.pens, sizeof(SDL_Pen) * pens_to_allocate); - SDL_memset(pens + pen_handler.pens_known, 0, - sizeof(SDL_Pen) * (pens_to_allocate - pen_handler.pens_allocated)); - } else { - pens = (SDL_Pen *)SDL_calloc(sizeof(SDL_Pen), pens_to_allocate); - } - pen_handler.pens = pens; - pen_handler.pens_allocated = pens_to_allocate; - } - pen = &pen_handler.pens[pen_handler.pens_known]; - pen_handler.pens_known += 1; - - /* Default pen initialization */ - pen->header.id = id; - pen->header.flags = SDL_PEN_FLAG_NEW; - pen->info.num_buttons = SDL_PEN_INFO_UNKNOWN; - pen->info.max_tilt = SDL_PEN_INFO_UNKNOWN; - pen->type = SDL_PEN_TYPE_PEN; - pen->name = (char *)SDL_calloc(1, SDL_PEN_MAX_NAME); /* Never deallocated */ + SDL_free(namecpy); } - return pen; + + if (retval && SDL_EventEnabled(SDL_EVENT_PEN_PROXIMITY_IN)) { + SDL_Event event; + SDL_zero(event); + event.pproximity.type = SDL_EVENT_PEN_PROXIMITY_IN; + event.pproximity.timestamp = timestamp; + event.pproximity.which = retval; + SDL_PushEvent(&event); + } + + return retval; } -void SDL_PenModifyAddCapabilities(SDL_Pen *pen, Uint32 capabilities) +void SDL_RemovePenDevice(Uint64 timestamp, SDL_PenID instance_id) { - if (capabilities & SDL_PEN_ERASER_MASK) { - pen->header.flags &= ~SDL_PEN_INK_MASK; - } else if (capabilities & SDL_PEN_INK_MASK) { - pen->header.flags &= ~SDL_PEN_ERASER_MASK; - } - pen->header.flags |= (capabilities & PEN_FLAGS_CAPABILITIES); -} - -static void pen_hotplug_attach(SDL_Pen *pen) -{ - if (!pen->header.window) { - /* Attach to default window */ - const SDL_Mouse *mouse = SDL_GetMouse(); - SDL_SendPenWindowEvent(0, pen->header.id, mouse->focus); - } -} - -static void pen_hotplug_detach(SDL_Pen *pen) -{ - SDL_SendPenWindowEvent(0, pen->header.id, NULL); -} - -void SDL_PenModifyEnd(SDL_Pen *pen, SDL_bool attach) -{ - SDL_bool is_new = pen->header.flags & SDL_PEN_FLAG_NEW; - SDL_bool was_attached = !(pen->header.flags & (SDL_PEN_FLAG_DETACHED | SDL_PEN_FLAG_NEW)); - SDL_bool broke_sort_order = SDL_FALSE; - - if (pen->type == SDL_PEN_TYPE_NONE) { - /* remove pen */ - if (!is_new) { - if (!(pen->header.flags & SDL_PEN_FLAG_ERROR)) { - SDL_Log("Error: attempt to remove known pen %lu", (unsigned long) pen->header.id); - pen->header.flags |= SDL_PEN_FLAG_ERROR; - } - - /* Treat as detached pen of unknown type instead */ - pen->type = SDL_PEN_TYPE_PEN; - attach = SDL_FALSE; - } else { - pen_handler.pens_known -= 1; - SDL_free(pen->name); - SDL_memset(pen, 0, sizeof(SDL_Pen)); - SDL_UNLOCK_PENS(); - return; - } - } - - pen->header.flags &= ~(SDL_PEN_FLAG_NEW | SDL_PEN_FLAG_STALE | SDL_PEN_FLAG_DETACHED); - if (attach == SDL_FALSE) { - pen->header.flags |= SDL_PEN_FLAG_DETACHED; - if (was_attached) { - broke_sort_order = SDL_TRUE; - if (!is_new) { - pen_handler.pens_attached -= 1; - } - pen_hotplug_detach(pen); - } - } else if (!was_attached || is_new) { - broke_sort_order = SDL_TRUE; - pen_handler.pens_attached += 1; - pen_hotplug_attach(pen); - } - - if (is_new) { - /* default: name */ - if (!pen->name[0]) { - SDL_snprintf(pen->name, SDL_PEN_MAX_NAME, - "%s %lu", - pen->type == SDL_PEN_TYPE_ERASER ? "Eraser" : "Pen", - (unsigned long) pen->header.id); - } - - /* default: enabled axes */ - if (!(pen->header.flags & (SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK))) { - pen->info.max_tilt = 0; /* no tilt => no max_tilt */ - } - - /* sanity-check GUID */ - if (0 == SDL_GUIDCompare(pen->guid, pen_guid_zero)) { - SDL_Log("Error: pen %lu: has GUID 0", (unsigned long) pen->header.id); - } - - /* pen or eraser? */ - if (pen->type == SDL_PEN_TYPE_ERASER || pen->header.flags & SDL_PEN_ERASER_MASK) { - pen->header.flags = (pen->header.flags & ~SDL_PEN_INK_MASK) | SDL_PEN_ERASER_MASK; - pen->type = SDL_PEN_TYPE_ERASER; - } else { - pen->header.flags = (pen->header.flags & ~SDL_PEN_ERASER_MASK) | SDL_PEN_INK_MASK; - } - - broke_sort_order = SDL_TRUE; - } - if (broke_sort_order && pen_handler.sorted) { - /* Maintain sortedness invariant */ - pen_sort(); - } - SDL_UNLOCK_PENS(); -} - -void SDL_PenGCMark(void) -{ - unsigned int i; - SDL_LOCK_PENS(); - for (i = 0; i < pen_handler.pens_known; ++i) { - SDL_Pen *pen = &pen_handler.pens[i]; - pen->header.flags |= SDL_PEN_FLAG_STALE; - } - pen_handler.sorted = SDL_FALSE; - SDL_UNLOCK_PENS(); -} - -void SDL_PenGCSweep(void *context, void (*free_deviceinfo)(Uint32, void *, void *)) -{ - unsigned int i; - pen_handler.pens_attached = 0; - - SDL_LOCK_PENS(); - /* We don't actually free the SDL_Pen entries, so that we can still answer queries about - formerly active SDL_PenIDs later. */ - for (i = 0; i < pen_handler.pens_known; ++i) { - SDL_Pen *pen = &pen_handler.pens[i]; - - if (pen->header.flags & SDL_PEN_FLAG_STALE) { - pen->header.flags |= SDL_PEN_FLAG_DETACHED; - pen_hotplug_detach(pen); - if (pen->deviceinfo) { - free_deviceinfo(pen->header.id, pen->deviceinfo, context); - pen->deviceinfo = NULL; - } - } else { - pen_handler.pens_attached += 1; - } - - pen->header.flags &= ~SDL_PEN_FLAG_STALE; - } - pen_sort(); - /* We could test for changes in the above and send a hotplugging event here */ - SDL_UNLOCK_PENS(); -} - -static void pen_relative_coordinates(SDL_Window *window, SDL_bool window_relative, float *x, float *y) -{ - int win_x, win_y; - - if (window_relative) { + if (!instance_id) { return; } - SDL_GetWindowPosition(window, &win_x, &win_y); - *x -= win_x; - *y -= win_y; -} -/* Initialises timestamp, windowID, which, pen_state, x, y, and axes */ -static void event_setup(const SDL_Pen *pen, const SDL_Window *window, Uint64 timestamp, const SDL_PenStatusInfo *status, SDL_Event *event) -{ - Uint16 last_buttons = pen->last.buttons; + SDL_LockRWLockForWriting(pen_device_rwlock); + SDL_Pen *pen = FindPenByInstanceId(instance_id); + if (pen) { + SDL_free(pen->name); + // we don't free `pen`, it's just part of simple array. Shuffle it out. + const int idx = ((int) (pen - pen_devices)); + SDL_assert((idx >= 0) && (idx < pen_device_count)); + if ( idx < (pen_device_count - 1) ) { + SDL_memmove(&pen_devices[idx], &pen_devices[idx + 1], sizeof (*pen) * ((pen_device_count - idx) - 1)); + } - if (timestamp == 0) { - /* Generate timestamp ourselves, if needed, so that we report the same timestamp - for the pen event and for any emulated mouse event */ - timestamp = SDL_GetTicksNS(); - } + SDL_assert(pen_device_count > 0); + pen_device_count--; - /* This code assumes that all of the SDL_Pen*Event structures have the same layout for these fields. - * This is checked by testautomation_pen.c, pen_memoryLayout(). */ - event->pmotion.timestamp = timestamp; - event->pmotion.windowID = window ? window->id : 0; - event->pmotion.which = pen->header.id; - event->pmotion.pen_state = last_buttons | PEN_GET_PUBLIC_STATUS_MASK(pen); - event->pmotion.x = status->x; - event->pmotion.y = status->y; - SDL_memcpy(event->pmotion.axes, status->axes, SDL_PEN_NUM_AXES * sizeof(float)); -} - - -int SDL_SendPenMotion(Uint64 timestamp, - SDL_PenID instance_id, - SDL_bool window_relative, - const SDL_PenStatusInfo *status) -{ - int i; - SDL_Pen *pen = SDL_GetPenPtr(instance_id); - SDL_Event event; - SDL_bool posted = SDL_FALSE; - float x = status->x; - float y = status->y; - float last_x = pen->last.x; - float last_y = pen->last.y; - /* Suppress mouse updates for axis changes or sub-pixel movement: */ - SDL_bool send_mouse_update; - SDL_bool axes_changed = SDL_FALSE; - SDL_Window *window; - - if (!pen) { - return SDL_FALSE; - } - window = pen->header.window; - if (!window) { - return SDL_FALSE; - } - - pen_relative_coordinates(window, window_relative, &x, &y); - - /* Check if the event actually modifies any cached axis or location, update as neeed */ - if (x != last_x || y != last_y) { - axes_changed = SDL_TRUE; - pen->last.x = status->x; - pen->last.y = status->y; - } - for (i = 0; i < SDL_PEN_NUM_AXES; ++i) { - if ((pen->header.flags & SDL_PEN_AXIS_CAPABILITY(i)) && (status->axes[i] != pen->last.axes[i])) { - axes_changed = SDL_TRUE; - pen->last.axes[i] = status->axes[i]; + if (pen_device_count) { + void *ptr = SDL_realloc(pen_devices, sizeof (*pen) * pen_device_count); // shrink it down. + if (ptr) { + pen_devices = (SDL_Pen *) ptr; + } + } else { + SDL_free(pen_devices); + pen_devices = NULL; } } - if (!axes_changed) { - /* No-op event */ - return SDL_FALSE; + SDL_UnlockRWLock(pen_device_rwlock); + + if (pen && SDL_EventEnabled(SDL_EVENT_PEN_PROXIMITY_OUT)) { + SDL_Event event; + SDL_zero(event); + event.pproximity.type = SDL_EVENT_PEN_PROXIMITY_OUT; + event.pproximity.timestamp = timestamp; + event.pproximity.which = instance_id; + SDL_PushEvent(&event); + } +} + +// This presumably is happening during video quit, so we don't send PROXIMITY_OUT events here. +void SDL_RemoveAllPenDevices(void (*callback)(SDL_PenID instance_id, void *handle, void *userdata), void *userdata) +{ + SDL_LockRWLockForWriting(pen_device_rwlock); + if (pen_device_count > 0) { + SDL_assert(pen_devices != NULL); + for (int i = 0; i < pen_device_count; i++) { + callback(pen_devices[i].instance_id, pen_devices[i].driverdata, userdata); + SDL_free(pen_devices[i].name); + } + } + SDL_free(pen_devices); + pen_devices = NULL; + SDL_UnlockRWLock(pen_device_rwlock); +} + +int SDL_SendPenTouch(Uint64 timestamp, SDL_PenID instance_id, const SDL_Window *window, Uint8 state, Uint8 eraser) +{ + SDL_bool push_event = SDL_FALSE; + SDL_PenInputFlags input_state = 0; + float x = 0.0f; + float y = 0.0f; + + // note that this locks for _reading_ because the lock protects the + // pen_devices array from being reallocated from under us, not the data in it; + // we assume only one thread (in the backend) is modifying an individual pen at + // a time, so it can update input state cleanly here. + SDL_LockRWLockForReading(pen_device_rwlock); + SDL_Pen *pen = FindPenByInstanceId(instance_id); + if (pen) { + input_state = pen->input_state; + x = pen->x; + y = pen->y; + + if (state && ((input_state & SDL_PEN_INPUT_DOWN) == 0)) { + input_state |= SDL_PEN_INPUT_DOWN; + push_event = SDL_TRUE; + } else if (!state && (input_state & SDL_PEN_INPUT_DOWN)) { + input_state &= ~SDL_PEN_INPUT_DOWN; + push_event = SDL_TRUE; + } + + if (eraser && ((input_state & SDL_PEN_INPUT_ERASER_TIP) == 0)) { + input_state |= SDL_PEN_INPUT_ERASER_TIP; + push_event = SDL_TRUE; + } else if (!state && (input_state & SDL_PEN_INPUT_ERASER_TIP)) { + input_state &= ~SDL_PEN_INPUT_ERASER_TIP; + push_event = SDL_TRUE; + } + + pen->input_state = input_state; // we could do an SDL_AtomicSet here if we run into trouble... + } + SDL_UnlockRWLock(pen_device_rwlock); + + int posted = 0; + if (push_event) { + const SDL_EventType evtype = state ? SDL_EVENT_PEN_DOWN : SDL_EVENT_PEN_UP; + if (push_event && SDL_EventEnabled(evtype)) { + SDL_Event event; + SDL_zero(event); + event.ptouch.type = evtype; + event.ptouch.timestamp = timestamp; + event.ptouch.windowID = window ? window->id : 0; + event.ptouch.which = instance_id; + event.ptouch.pen_state = input_state; + event.ptouch.x = x; + event.ptouch.y = y; + event.ptouch.eraser = eraser ? 1 : 0; + event.ptouch.state = state ? SDL_PRESSED : SDL_RELEASED; + posted = (SDL_PushEvent(&event) > 0); + } } - send_mouse_update = (x != last_x) || (y != last_y); + return posted; +} - if (!(SDL_MousePositionInWindow(window, x, y))) { - return SDL_FALSE; +int SDL_SendPenAxis(Uint64 timestamp, SDL_PenID instance_id, const SDL_Window *window, SDL_PenAxis axis, float value) +{ + SDL_assert((axis >= 0) && (axis < SDL_PEN_NUM_AXES)); // fix the backend if this triggers. + + SDL_bool push_event = SDL_FALSE; + SDL_PenInputFlags input_state = 0; + float x = 0.0f; + float y = 0.0f; + + // note that this locks for _reading_ because the lock protects the + // pen_devices array from being reallocated from under us, not the data in it; + // we assume only one thread (in the backend) is modifying an individual pen at + // a time, so it can update input state cleanly here. + SDL_LockRWLockForReading(pen_device_rwlock); + SDL_Pen *pen = FindPenByInstanceId(instance_id); + if (pen) { + if (pen->axes[axis] != value) { + pen->axes[axis] = value; // we could do an SDL_AtomicSet here if we run into trouble... + input_state = pen->input_state; + x = pen->x; + y = pen->y; + push_event = SDL_TRUE; + } + } + SDL_UnlockRWLock(pen_device_rwlock); + + int posted = 0; + if (push_event && SDL_EventEnabled(SDL_EVENT_PEN_AXIS)) { + SDL_Event event; + SDL_zero(event); + event.paxis.type = SDL_EVENT_PEN_AXIS; + event.paxis.timestamp = timestamp; + event.paxis.windowID = window ? window->id : 0; + event.paxis.which = instance_id; + event.paxis.pen_state = input_state; + event.paxis.x = x; + event.paxis.y = y; + event.paxis.axis = axis; + event.paxis.value = value; + posted = (SDL_PushEvent(&event) > 0); } - if (SDL_EventEnabled(SDL_EVENT_PEN_MOTION)) { - event_setup(pen, window, timestamp, status, &event); + return posted; +} + +int SDL_SendPenMotion(Uint64 timestamp, SDL_PenID instance_id, const SDL_Window *window, float x, float y) +{ + SDL_bool push_event = SDL_FALSE; + SDL_PenInputFlags input_state = 0; + + // note that this locks for _reading_ because the lock protects the + // pen_devices array from being reallocated from under us, not the data in it; + // we assume only one thread (in the backend) is modifying an individual pen at + // a time, so it can update input state cleanly here. + SDL_LockRWLockForReading(pen_device_rwlock); + SDL_Pen *pen = FindPenByInstanceId(instance_id); + if (pen) { + if ((pen->x != x) || (pen->y != y)) { + pen->x = x; // we could do an SDL_AtomicSet here if we run into trouble... + pen->y = y; // we could do an SDL_AtomicSet here if we run into trouble... + input_state = pen->input_state; + push_event = SDL_TRUE; + } + } + SDL_UnlockRWLock(pen_device_rwlock); + + int posted = 0; + if (push_event && SDL_EventEnabled(SDL_EVENT_PEN_MOTION)) { + SDL_Event event; + SDL_zero(event); event.pmotion.type = SDL_EVENT_PEN_MOTION; - - posted = SDL_PushEvent(&event) > 0; - - if (!posted) { - return SDL_FALSE; - } - } - - if (send_mouse_update) { - switch (pen_mouse_emulation_mode) { - case PEN_MOUSE_EMULATE: - return (SDL_SendMouseMotion(0, window, SDL_PEN_MOUSEID, SDL_FALSE, x, y)) || posted; - - case PEN_MOUSE_STATELESS: - /* Report mouse event but don't update mouse state */ - if (SDL_EventEnabled(SDL_EVENT_MOUSE_MOTION)) { - event.motion.windowID = window->id; - event.motion.timestamp = timestamp; - event.motion.which = SDL_PEN_MOUSEID; - event.motion.type = SDL_EVENT_MOUSE_MOTION; - event.motion.state = pen->last.buttons | PEN_GET_PUBLIC_STATUS_MASK(pen); - event.motion.x = x; - event.motion.y = y; - event.motion.xrel = last_x - x; - event.motion.yrel = last_y - y; - return (SDL_PushEvent(&event) > 0) || posted; - } - break; - - default: - break; - } - } - return posted; -} - -int SDL_SendPenTipEvent(Uint64 timestamp, SDL_PenID instance_id, Uint8 state) -{ - SDL_Pen *pen = SDL_GetPenPtr(instance_id); - SDL_Event event; - SDL_bool posted = SDL_FALSE; - SDL_PenStatusInfo *last = &pen->last; - Uint8 mouse_button = SDL_BUTTON_LEFT; - SDL_Window *window; - - if (!pen) { - return SDL_FALSE; - } - window = pen->header.window; - - if ((state == SDL_PRESSED) && !(window && SDL_MousePositionInWindow(window, last->x, last->y))) { - return SDL_FALSE; - } - - if (state == SDL_PRESSED) { - event.pbutton.type = SDL_EVENT_PEN_DOWN; - pen->header.flags |= SDL_PEN_DOWN_MASK; - } else { - event.pbutton.type = SDL_EVENT_PEN_UP; - pen->header.flags &= ~SDL_PEN_DOWN_MASK; - } - - if (SDL_EventEnabled(event.ptip.type)) { - event_setup(pen, window, timestamp, &pen->last, &event); - - /* Used as eraser? Report eraser event, otherwise ink event */ - event.ptip.tip = (pen->header.flags & SDL_PEN_ERASER_MASK) ? SDL_PEN_TIP_ERASER : SDL_PEN_TIP_INK; - event.ptip.state = state == SDL_PRESSED ? SDL_PRESSED : SDL_RELEASED; - - posted = SDL_PushEvent(&event) > 0; - - if (!posted) { - return SDL_FALSE; - } - } - - /* Mouse emulation */ - if (pen_delay_mouse_button_mode) { - /* Send button events when pen touches / leaves surface */ - mouse_button = pen->last_mouse_button; - if (mouse_button == 0) { - mouse_button = SDL_BUTTON_LEFT; /* No current button? Instead report left mouse button */ - } - /* A button may get released while drawing, but SDL_SendPenButton doesn't reset last_mouse_button - while touching the surface, so release and reset last_mouse_button on pen tip release */ - if (pen->last.buttons == 0 && state == SDL_RELEASED) { - pen->last_mouse_button = 0; - } - } - - switch (pen_mouse_emulation_mode) { - case PEN_MOUSE_EMULATE: - return (SDL_SendMouseButton(timestamp, window, SDL_PEN_MOUSEID, state, mouse_button)) || posted; - - case PEN_MOUSE_STATELESS: - /* Report mouse event without updating mouse state */ - event.button.type = state == SDL_PRESSED ? SDL_EVENT_MOUSE_BUTTON_DOWN : SDL_EVENT_MOUSE_BUTTON_UP; - if (SDL_EventEnabled(event.button.type)) { - event.button.windowID = window ? window->id : 0; - event.button.timestamp = timestamp; - event.button.which = SDL_PEN_MOUSEID; - - event.button.state = state; - event.button.button = mouse_button; - event.button.clicks = 1; - event.button.x = last->x; - event.button.y = last->y; - return (SDL_PushEvent(&event) > 0) || posted; - } - break; - - default: - break; - } - return posted; -} - -int SDL_SendPenButton(Uint64 timestamp, - SDL_PenID instance_id, - Uint8 state, Uint8 button) -{ - SDL_Pen *pen = SDL_GetPenPtr(instance_id); - SDL_Event event; - SDL_bool posted = SDL_FALSE; - SDL_PenStatusInfo *last = &pen->last; - Uint8 mouse_button = button + 1; /* For mouse emulation, PEN_DOWN counts as button 1, so the first actual button is mouse button 2 */ - SDL_Window *window; - - if (!pen) { - return SDL_FALSE; - } - window = pen->header.window; - - if ((state == SDL_PRESSED) && !(window && SDL_MousePositionInWindow(window, last->x, last->y))) { - return SDL_FALSE; - } - - if (state == SDL_PRESSED) { - event.pbutton.type = SDL_EVENT_PEN_BUTTON_DOWN; - pen->last.buttons |= (1 << (button - 1)); - } else { - event.pbutton.type = SDL_EVENT_PEN_BUTTON_UP; - pen->last.buttons &= ~(1 << (button - 1)); - } - - if (SDL_EventEnabled(event.pbutton.type)) { - event_setup(pen, window, timestamp, &pen->last, &event); - - event.pbutton.button = button; - event.pbutton.state = state == SDL_PRESSED ? SDL_PRESSED : SDL_RELEASED; - - posted = SDL_PushEvent(&event) > 0; - - if (!posted) { - return SDL_FALSE; - } - } - - /* Mouse emulation */ - if (pen_delay_mouse_button_mode) { - /* Can only change active mouse button while not touching the surface */ - if (!(pen->header.flags & SDL_PEN_DOWN_MASK)) { - if (state == SDL_RELEASED) { - pen->last_mouse_button = 0; - } else { - pen->last_mouse_button = mouse_button; - } - } - /* Defer emulation event */ - return SDL_TRUE; - } - - switch (pen_mouse_emulation_mode) { - case PEN_MOUSE_EMULATE: - return (SDL_SendMouseButton(timestamp, window, SDL_PEN_MOUSEID, state, mouse_button)) || posted; - - case PEN_MOUSE_STATELESS: - /* Report mouse event without updating mouse state */ - event.button.type = state == SDL_PRESSED ? SDL_EVENT_MOUSE_BUTTON_DOWN : SDL_EVENT_MOUSE_BUTTON_UP; - if (SDL_EventEnabled(event.button.type)) { - event.button.windowID = window ? window->id : 0; - event.button.timestamp = timestamp; - event.button.which = SDL_PEN_MOUSEID; - - event.button.state = state; - event.button.button = mouse_button; - event.button.clicks = 1; - event.button.x = last->x; - event.button.y = last->y; - return (SDL_PushEvent(&event) > 0) || posted; - } - break; - - default: - break; - } - return posted; -} - -int SDL_SendPenWindowEvent(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *window) -{ - SDL_EventType event_id = window ? SDL_EVENT_WINDOW_PEN_ENTER : SDL_EVENT_WINDOW_PEN_LEAVE; - SDL_Event event = { 0 }; - SDL_Pen *pen = SDL_GetPenPtr(instance_id); - SDL_bool posted; - - if (!pen) { - return SDL_FALSE; - } - - if (pen->header.window == window) { /* (TRIVIAL-EVENT) Nothing new to report */ - return SDL_FALSE; - } - - if (timestamp == 0) { - /* Generate timestamp ourselves, if needed, so that we report the same timestamp - for the pen event and for any emulated mouse event */ - timestamp = SDL_GetTicksNS(); - } - - event.window.type = event_id; - /* If window == NULL and not (TRIVIAL-EVENT), then pen->header.window != NULL */ - event.window.timestamp = timestamp; - event.window.windowID = window ? window->id : pen->header.window->id; - event.window.data1 = instance_id; - posted = (SDL_PushEvent(&event) > 0); - - /* Update after assembling event */ - pen->header.window = window; - - switch (pen_mouse_emulation_mode) { - case PEN_MOUSE_EMULATE: - SDL_SetMouseFocus(event_id == SDL_EVENT_WINDOW_PEN_ENTER ? window : NULL); - break; - - case PEN_MOUSE_STATELESS: - /* Send event without going through mouse API */ - if (event_id == SDL_EVENT_WINDOW_PEN_ENTER) { - event.window.type = SDL_EVENT_WINDOW_MOUSE_ENTER; - } else { - event.window.type = SDL_EVENT_WINDOW_MOUSE_LEAVE; - } - posted = posted || (SDL_PushEvent(&event) > 0); - break; - - default: - break; + event.pmotion.timestamp = timestamp; + event.pmotion.windowID = window ? window->id : 0; + event.pmotion.which = instance_id; + event.pmotion.pen_state = input_state; + event.pmotion.x = x; + event.pmotion.y = y; + posted = (SDL_PushEvent(&event) > 0); } return posted; } -static void SDLCALL SDL_PenUpdateHint(void *userdata, const char *name, const char *oldvalue, const char *newvalue) +int SDL_SendPenButton(Uint64 timestamp, SDL_PenID instance_id, const SDL_Window *window, Uint8 state, Uint8 button) { - int *var = (int *)userdata; - if (newvalue == NULL) { - return; + if ((button < 1) || (button > 5)) { + return 0; // clamp for now. } + SDL_bool push_event = SDL_FALSE; + SDL_PenInputFlags input_state = 0; + float x = 0.0f; + float y = 0.0f; - if (0 == SDL_strcmp("2", newvalue)) { - *var = 2; - } else if (0 == SDL_strcmp("1", newvalue)) { - *var = 1; - } else if (0 == SDL_strcmp("0", newvalue)) { - *var = 0; - } else { - SDL_Log("Unexpected value for pen hint: '%s'", newvalue); - } -} - -void SDL_PenInit(void) -{ -#if (SDL_PEN_DEBUG_NOID | SDL_PEN_DEBUG_NONWACOM | SDL_PEN_DEBUG_UNKNOWN_WACOM) - printf("[pen] Debugging enabled: noid=%d nonwacom=%d unknown-wacom=%d\n", - SDL_PEN_DEBUG_NOID, SDL_PEN_DEBUG_NONWACOM, SDL_PEN_DEBUG_UNKNOWN_WACOM); -#endif - SDL_AddHintCallback(SDL_HINT_PEN_NOT_MOUSE, - SDL_PenUpdateHint, &pen_mouse_emulation_mode); - - SDL_AddHintCallback(SDL_HINT_PEN_DELAY_MOUSE_BUTTON, - SDL_PenUpdateHint, &pen_delay_mouse_button_mode); -#ifndef SDL_THREADS_DISABLED - SDL_pen_access_lock = SDL_CreateMutex(); -#endif -} - -void SDL_PenQuit(void) -{ - unsigned int i; - - SDL_DelHintCallback(SDL_HINT_PEN_NOT_MOUSE, - SDL_PenUpdateHint, &pen_mouse_emulation_mode); - - SDL_DelHintCallback(SDL_HINT_PEN_DELAY_MOUSE_BUTTON, - SDL_PenUpdateHint, &pen_delay_mouse_button_mode); -#ifndef SDL_THREADS_DISABLED - SDL_DestroyMutex(SDL_pen_access_lock); - SDL_pen_access_lock = NULL; -#endif - - if (pen_handler.pens) { - for (i = 0; i < pen_handler.pens_known; ++i) { - SDL_free(pen_handler.pens[i].name); + // note that this locks for _reading_ because the lock protects the + // pen_devices array from being reallocated from under us, not the data in it; + // we assume only one thread (in the backend) is modifying an individual pen at + // a time, so it can update input state cleanly here. + SDL_LockRWLockForReading(pen_device_rwlock); + SDL_Pen *pen = FindPenByInstanceId(instance_id); + if (pen) { + input_state = pen->input_state; + const Uint32 flag = (Uint32) (1u << button); + const Uint8 current = (input_state & flag) ? 1 : 0; + x = pen->x; + y = pen->y; + if (state && !current) { + input_state |= flag; + push_event = SDL_TRUE; + } else if (!state && current) { + input_state &= ~flag; + push_event = SDL_TRUE; } - SDL_free(pen_handler.pens); - /* Reset static pen information */ - SDL_memset(&pen_handler, 0, sizeof(pen_handler)); + pen->input_state = input_state; // we could do an SDL_AtomicSet here if we run into trouble... } -} + SDL_UnlockRWLock(pen_device_rwlock); -SDL_bool SDL_PenPerformHitTest(void) -{ - return pen_mouse_emulation_mode == PEN_MOUSE_EMULATE; -} - -/* Vendor-specific bits */ - -/* Default pen names */ -#define PEN_NAME_AES 0 -#define PEN_NAME_ART 1 -#define PEN_NAME_AIRBRUSH 2 -#define PEN_NAME_GENERAL 3 -#define PEN_NAME_GRIP 4 -#define PEN_NAME_INKING 5 -#define PEN_NAME_PRO 6 -#define PEN_NAME_PRO2 7 -#define PEN_NAME_PRO3 8 -#define PEN_NAME_PRO3D 9 -#define PEN_NAME_PRO_SLIM 10 -#define PEN_NAME_STROKE 11 - -#define PEN_NAME_LAST PEN_NAME_STROKE -#define PEN_NUM_NAMES (PEN_NAME_LAST + 1) - -static const char *default_pen_names[] = { - /* PEN_NAME_AES */ - "AES Pen", - /* PEN_NAME_ART */ - "Art Pen", - /* PEN_NAME_AIRBRUSH */ - "Airbrush Pen", - /* PEN_NAME_GENERAL */ - "Pen", - /* PEN_NAME_GRIP */ - "Grip Pen", - /* PEN_NAME_INKING */ - "Inking Pen", - /* PEN_NAME_PRO */ - "Pro Pen", - /* PEN_NAME_PRO2 */ - "Pro Pen 2", - /* PEN_NAME_PRO3 */ - "Pro Pen 3", - /* PEN_NAME_PRO3D */ - "Pro Pen 3D", - /* PEN_NAME_PRO_SLIM */ - "Pro Pen Slim", - /* PEN_NAME_STROKE */ - "Stroke Pen" -}; -SDL_COMPILE_TIME_ASSERT(default_pen_names, SDL_arraysize(default_pen_names) == PEN_NUM_NAMES); - -#define PEN_SPEC_TYPE_SHIFT 0 -#define PEN_SPEC_TYPE_MASK 0x0000000fu -#define PEN_SPEC_BUTTONS_SHIFT 4 -#define PEN_SPEC_BUTTONS_MASK 0x000000f0u -#define PEN_SPEC_NAME_SHIFT 8 -#define PEN_SPEC_NAME_MASK 0x00000f00u -#define PEN_SPEC_AXES_SHIFT 0 -#define PEN_SPEC_AXES_MASK 0xffff0000u - -#define PEN_WACOM_ID_INVALID 0xffffffffu /* Generic "invalid ID" marker */ - -#define PEN_SPEC(name, buttons, type, axes) (0 | (PEN_SPEC_NAME_MASK & ((name) << PEN_SPEC_NAME_SHIFT)) | (PEN_SPEC_BUTTONS_MASK & ((buttons) << PEN_SPEC_BUTTONS_SHIFT)) | (PEN_SPEC_TYPE_MASK & ((type) << PEN_SPEC_TYPE_SHIFT)) | (PEN_SPEC_AXES_MASK & ((axes) << PEN_SPEC_AXES_SHIFT))) - -/* Returns a suitable pen name string from default_pen_names on success, otherwise NULL. */ -static const char *pen_wacom_identify_tool(Uint32 requested_wacom_id, int *num_buttons, int *tool_type, int *axes) -{ - int i; - - /* List of known Wacom pens, extracted from libwacom.stylus and wacom_wac.c in the Linux kernel. - Could be complemented by dlopen()ing libwacom, in the future (if new pens get added). */ - struct - { - /* Compress properties to 8 bytes per device in order to keep memory cost well below 1k. - Could be compressed further with more complex code. */ - Uint32 wacom_id; /* Must be != PEN_WACOM_ID_INVALID */ - Uint32 properties; - } tools[] = { - { 0x0001, PEN_SPEC(PEN_NAME_AES, 1, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK) }, - { 0x0011, PEN_SPEC(PEN_NAME_AES, 1, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK) }, - { 0x0019, PEN_SPEC(PEN_NAME_AES, 1, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK) }, - { 0x0021, PEN_SPEC(PEN_NAME_AES, 1, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK) }, - { 0x0031, PEN_SPEC(PEN_NAME_AES, 1, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK) }, - { 0x0039, PEN_SPEC(PEN_NAME_AES, 1, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK) }, - { 0x0049, PEN_SPEC(PEN_NAME_GENERAL, 1, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK) }, - { 0x0071, PEN_SPEC(PEN_NAME_GENERAL, 1, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK) }, - { 0x0200, PEN_SPEC(PEN_NAME_PRO3, 3, SDL_PEN_TYPE_PEN, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, - { 0x0221, PEN_SPEC(PEN_NAME_AES, 1, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK) }, - { 0x0231, PEN_SPEC(PEN_NAME_AES, 1, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK) }, - { 0x0271, PEN_SPEC(PEN_NAME_GENERAL, 1, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK) }, - { 0x0421, PEN_SPEC(PEN_NAME_AES, 1, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK) }, - { 0x0431, PEN_SPEC(PEN_NAME_AES, 1, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK) }, - { 0x0621, PEN_SPEC(PEN_NAME_AES, 1, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK) }, - { 0x0631, PEN_SPEC(PEN_NAME_AES, 1, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK) }, - { 0x0801, PEN_SPEC(PEN_NAME_INKING, 0, SDL_PEN_TYPE_PENCIL, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, - { 0x0802, PEN_SPEC(PEN_NAME_GRIP, 2, SDL_PEN_TYPE_PEN, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, - { 0x0804, PEN_SPEC(PEN_NAME_ART, 2, SDL_PEN_TYPE_PEN, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK | SDL_PEN_AXIS_ROTATION_MASK) }, - { 0x080a, PEN_SPEC(PEN_NAME_GRIP, 2, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, - { 0x080c, PEN_SPEC(PEN_NAME_ART, 2, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, - { 0x0812, PEN_SPEC(PEN_NAME_INKING, 0, SDL_PEN_TYPE_PENCIL, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, - { 0x0813, PEN_SPEC(PEN_NAME_GENERAL, 2, SDL_PEN_TYPE_PEN, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, - { 0x081b, PEN_SPEC(PEN_NAME_GENERAL, 2, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, - { 0x0822, PEN_SPEC(PEN_NAME_GENERAL, 2, SDL_PEN_TYPE_PEN, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, - { 0x0823, PEN_SPEC(PEN_NAME_GRIP, 2, SDL_PEN_TYPE_PEN, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, - { 0x082a, PEN_SPEC(PEN_NAME_GENERAL, 2, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, - { 0x082b, PEN_SPEC(PEN_NAME_GRIP, 2, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, - { 0x0832, PEN_SPEC(PEN_NAME_STROKE, 0, SDL_PEN_TYPE_BRUSH, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, - { 0x0842, PEN_SPEC(PEN_NAME_PRO2, 2, SDL_PEN_TYPE_PEN, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, - { 0x084a, PEN_SPEC(PEN_NAME_PRO2, 2, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, - { 0x0852, PEN_SPEC(PEN_NAME_GRIP, 2, SDL_PEN_TYPE_PEN, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, - { 0x085a, PEN_SPEC(PEN_NAME_GRIP, 2, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, - { 0x0862, PEN_SPEC(PEN_NAME_GENERAL, 2, SDL_PEN_TYPE_PEN, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, - { 0x0885, PEN_SPEC(PEN_NAME_ART, 0, SDL_PEN_TYPE_PEN, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK | SDL_PEN_AXIS_ROTATION_MASK) }, - { 0x08e2, PEN_SPEC(PEN_NAME_GENERAL, 2, SDL_PEN_TYPE_PEN, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, - { 0x0902, PEN_SPEC(PEN_NAME_AIRBRUSH, 1, SDL_PEN_TYPE_AIRBRUSH, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK | SDL_PEN_AXIS_SLIDER_MASK) }, - { 0x090a, PEN_SPEC(PEN_NAME_AIRBRUSH, 1, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, - { 0x0912, PEN_SPEC(PEN_NAME_AIRBRUSH, 1, SDL_PEN_TYPE_AIRBRUSH, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK | SDL_PEN_AXIS_SLIDER_MASK) }, - { 0x0913, PEN_SPEC(PEN_NAME_AIRBRUSH, 1, SDL_PEN_TYPE_AIRBRUSH, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, - { 0x091a, PEN_SPEC(PEN_NAME_AIRBRUSH, 1, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, - { 0x091b, PEN_SPEC(PEN_NAME_AIRBRUSH, 1, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, - { 0x0d12, PEN_SPEC(PEN_NAME_AIRBRUSH, 1, SDL_PEN_TYPE_AIRBRUSH, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK | SDL_PEN_AXIS_SLIDER_MASK) }, - { 0x0d1a, PEN_SPEC(PEN_NAME_AIRBRUSH, 1, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, - { 0x8051, PEN_SPEC(PEN_NAME_AES, 0, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK) }, - { 0x805b, PEN_SPEC(PEN_NAME_AES, 1, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK) }, - { 0x806b, PEN_SPEC(PEN_NAME_AES, 1, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK) }, - { 0x807b, PEN_SPEC(PEN_NAME_GENERAL, 1, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK) }, - { 0x826b, PEN_SPEC(PEN_NAME_AES, 1, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK) }, - { 0x846b, PEN_SPEC(PEN_NAME_AES, 1, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK) }, - { 0x2802, PEN_SPEC(PEN_NAME_INKING, 0, SDL_PEN_TYPE_PENCIL, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, - { 0x4200, PEN_SPEC(PEN_NAME_PRO3, 3, SDL_PEN_TYPE_PEN, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, - { 0x4802, PEN_SPEC(PEN_NAME_GENERAL, 2, SDL_PEN_TYPE_PEN, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, - { 0x480a, PEN_SPEC(PEN_NAME_GENERAL, 2, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, - { 0x8842, PEN_SPEC(PEN_NAME_PRO3D, 3, SDL_PEN_TYPE_PEN, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, - { 0x10802, PEN_SPEC(PEN_NAME_GRIP, 2, SDL_PEN_TYPE_PEN, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, - { 0x10804, PEN_SPEC(PEN_NAME_ART, 2, SDL_PEN_TYPE_PEN, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK | SDL_PEN_AXIS_ROTATION_MASK) }, - { 0x1080a, PEN_SPEC(PEN_NAME_GRIP, 2, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, - { 0x1080c, PEN_SPEC(PEN_NAME_ART, 2, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, - { 0x10842, PEN_SPEC(PEN_NAME_PRO_SLIM, 2, SDL_PEN_TYPE_PEN, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, - { 0x1084a, PEN_SPEC(PEN_NAME_PRO_SLIM, 2, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, - { 0x10902, PEN_SPEC(PEN_NAME_AIRBRUSH, 1, SDL_PEN_TYPE_AIRBRUSH, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK | SDL_PEN_AXIS_SLIDER_MASK) }, - { 0x1090a, PEN_SPEC(PEN_NAME_AIRBRUSH, 1, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, - { 0x12802, PEN_SPEC(PEN_NAME_INKING, 0, SDL_PEN_TYPE_PENCIL, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, - { 0x14802, PEN_SPEC(PEN_NAME_GENERAL, 2, SDL_PEN_TYPE_PEN, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, - { 0x1480a, PEN_SPEC(PEN_NAME_GENERAL, 2, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, - { 0x16802, PEN_SPEC(PEN_NAME_PRO, 2, SDL_PEN_TYPE_PEN, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, - { 0x1680a, PEN_SPEC(PEN_NAME_PRO, 2, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, - { 0x18802, PEN_SPEC(PEN_NAME_GENERAL, 2, SDL_PEN_TYPE_PEN, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, - { 0x1880a, PEN_SPEC(PEN_NAME_GENERAL, 2, SDL_PEN_TYPE_ERASER, SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK) }, - { 0, 0 } - }; - - /* The list of pens is sorted, so we could do binary search, but this call should be pretty rare. */ - for (i = 0; tools[i].wacom_id; ++i) { - if (tools[i].wacom_id == requested_wacom_id) { - Uint32 properties = tools[i].properties; - int name_index = (properties & PEN_SPEC_NAME_MASK) >> PEN_SPEC_NAME_SHIFT; - - *num_buttons = (properties & PEN_SPEC_BUTTONS_MASK) >> PEN_SPEC_BUTTONS_SHIFT; - *tool_type = (properties & PEN_SPEC_TYPE_MASK) >> PEN_SPEC_TYPE_SHIFT; - *axes = (properties & PEN_SPEC_AXES_MASK) >> PEN_SPEC_AXES_SHIFT; - - return default_pen_names[name_index]; + int posted = 0; + if (push_event) { + const SDL_EventType evtype = state ? SDL_EVENT_PEN_BUTTON_DOWN : SDL_EVENT_PEN_BUTTON_UP; + if (push_event && SDL_EventEnabled(evtype)) { + SDL_Event event; + SDL_zero(event); + event.pbutton.type = evtype; + event.pbutton.timestamp = timestamp; + event.pbutton.windowID = window ? window->id : 0; + event.pbutton.which = instance_id; + event.pbutton.pen_state = input_state; + event.pbutton.x = x; + event.pbutton.y = y; + event.pbutton.button = button; + event.pbutton.state = state ? SDL_PRESSED : SDL_RELEASED; + posted = (SDL_PushEvent(&event) > 0); } } - return NULL; + + return posted; } -void SDL_PenUpdateGUIDForGeneric(SDL_GUID *guid, Uint32 upper, Uint32 lower) -{ - int i; - - for (i = 0; i < 4; ++i) { - guid->data[8 + i] = (lower >> (i * 8)) & 0xff; - } - - for (i = 0; i < 4; ++i) { - guid->data[12 + i] = (upper >> (i * 8)) & 0xff; - } -} - -void SDL_PenUpdateGUIDForType(SDL_GUID *guid, SDL_PenSubtype pentype) -{ - guid->data[7] = pentype; -} - -void SDL_PenUpdateGUIDForWacom(SDL_GUID *guid, Uint32 wacom_devicetype_id, Uint32 wacom_serial_id) -{ - int i; - - for (i = 0; i < 4; ++i) { - guid->data[0 + i] = (wacom_serial_id >> (i * 8)) & 0xff; - } - - for (i = 0; i < 3; ++i) { /* 24 bit values */ - guid->data[4 + i] = (wacom_devicetype_id >> (i * 8)) & 0xff; - } -} - -int SDL_PenModifyForWacomID(SDL_Pen *pen, Uint32 wacom_devicetype_id, Uint32 *axis_flags) -{ - const char *name = NULL; - int num_buttons = 0; - int tool_type = 0; - int axes = 0; - -#if SDL_PEN_DEBUG_UNKNOWN_WACOM - wacom_devicetype_id = PEN_WACOM_ID_INVALID; /* force detection to fail */ -#endif - -#if defined(SDL_PLATFORM_LINUX) || defined(SDL_PLATFORM_FREEBSD) || defined(SDL_PLATFORM_NETBSD) || defined(SDL_PLATFORM_OPENBSD) - /* According to Ping Cheng, the curent Wacom for Linux maintainer, device IDs on Linux - squeeze a "0" nibble after the 3rd (least significant) nibble. - This may also affect the *BSDs, so they are heuristically included here. - On those platforms, we first try the "patched" ID: */ - if (0 == (wacom_devicetype_id & 0x0000f000u)) { - const Uint32 lower_mask = 0xfffu; - int wacom_devicetype_alt_id = ((wacom_devicetype_id & ~lower_mask) >> 4) | (wacom_devicetype_id & lower_mask); - - name = pen_wacom_identify_tool(wacom_devicetype_alt_id, &num_buttons, &tool_type, &axes); - if (name) { - wacom_devicetype_id = wacom_devicetype_alt_id; - } - } -#endif - if (name == NULL) { - name = pen_wacom_identify_tool(wacom_devicetype_id, &num_buttons, &tool_type, &axes); - } - - if (!name) { - *axis_flags = 0; - return SDL_FALSE; - } - - *axis_flags = axes; - - /* Override defaults */ - if (pen->info.num_buttons == SDL_PEN_INFO_UNKNOWN) { - pen->info.num_buttons = (Sint8)SDL_min(num_buttons, SDL_MAX_SINT8); - } - if (pen->type == SDL_PEN_TYPE_PEN) { - pen->type = (SDL_PenSubtype)tool_type; - } - if (pen->info.max_tilt == SDL_PEN_INFO_UNKNOWN) { - /* supposedly: 64 degrees left, 63 right, as reported by the Wacom X11 driver */ - pen->info.max_tilt = 64.0f; - } - pen->info.wacom_id = wacom_devicetype_id; - if (0 == pen->name[0]) { - SDL_snprintf(pen->name, SDL_PEN_MAX_NAME, - "Wacom %s%s", name, (tool_type == SDL_PEN_TYPE_ERASER) ? " Eraser" : ""); - } - return SDL_TRUE; -} diff --git a/src/events/SDL_pen_c.h b/src/events/SDL_pen_c.h index a82b345db..50f5e9b30 100644 --- a/src/events/SDL_pen_c.h +++ b/src/events/SDL_pen_c.h @@ -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_ */ diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c index f4fc4d286..188715c57 100644 --- a/src/video/SDL_video.c +++ b/src/video/SDL_video.c @@ -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(); diff --git a/src/video/wayland/SDL_waylandevents.c b/src/video/wayland/SDL_waylandevents.c index 70a79586e..3ec8d90ce 100644 --- a/src/video/wayland/SDL_waylandevents.c +++ b/src/video/wayland/SDL_waylandevents.c @@ -2364,421 +2364,255 @@ void Wayland_create_text_input(SDL_VideoData *d) } } -static SDL_PenID Wayland_get_penid(void *data, struct zwp_tablet_tool_v2 *tool) + +// Pen/Tablet support... + +typedef struct SDL_WaylandPenTool // a stylus, etc, on a tablet. { - struct SDL_WaylandTool *sdltool = data; - return sdltool->penid; -} - -/* For registering pens */ -static SDL_Pen *Wayland_get_current_pen(void *data, struct zwp_tablet_tool_v2 *tool) -{ - struct SDL_WaylandTool *sdltool = data; - struct SDL_WaylandTabletInput *input = sdltool->tablet; - - if (!input->current_pen.builder) { - /* Starting new pen or updating one? */ - SDL_PenID penid = sdltool->penid; - - if (penid == 0) { - /* Found completely new pen? */ - penid = ++input->num_pens; - sdltool->penid = penid; - } - input->current_pen.builder = SDL_GetPenPtr(penid); - if (!input->current_pen.builder) { - /* Must register as new pen */ - input->current_pen.builder = SDL_PenModifyBegin(penid); - } - } - return input->current_pen.builder; -} + SDL_PenID instance_id; + SDL_PenInfo info; + SDL_Window *tool_focus; + struct zwp_tablet_tool_v2 *wltool; + float x; + float y; + SDL_bool frame_motion_set; + float frame_axes[SDL_PEN_NUM_AXES]; + Uint32 frame_axes_set; + int frame_pen_down; + int frame_buttons[3]; +} SDL_WaylandPenTool; static void tablet_tool_handle_type(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t type) { - SDL_Pen *pen = Wayland_get_current_pen(data, tool); - + SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data; switch (type) { - case ZWP_TABLET_TOOL_V2_TYPE_ERASER: - pen->type = SDL_PEN_TYPE_ERASER; - break; - - case ZWP_TABLET_TOOL_V2_TYPE_PEN: - pen->type = SDL_PEN_TYPE_PEN; - break; - - case ZWP_TABLET_TOOL_V2_TYPE_PENCIL: - pen->type = SDL_PEN_TYPE_PENCIL; - break; - - case ZWP_TABLET_TOOL_V2_TYPE_AIRBRUSH: - pen->type = SDL_PEN_TYPE_AIRBRUSH; - break; - - case ZWP_TABLET_TOOL_V2_TYPE_BRUSH: - pen->type = SDL_PEN_TYPE_BRUSH; - break; - - case ZWP_TABLET_TOOL_V2_TYPE_FINGER: - case ZWP_TABLET_TOOL_V2_TYPE_MOUSE: - case ZWP_TABLET_TOOL_V2_TYPE_LENS: - default: - pen->type = SDL_PEN_TYPE_NONE; /* Mark for deregistration */ + #define CASE(typ) case ZWP_TABLET_TOOL_V2_TYPE_##typ: sdltool->info.subtype = SDL_PEN_TYPE_##typ; return + CASE(ERASER); + CASE(PEN); + CASE(PENCIL); + CASE(AIRBRUSH); + CASE(BRUSH); + #undef CASE + default: sdltool->info.subtype = SDL_PEN_TYPE_UNKNOWN; // we'll decline to add this when the `done` event comes through. } - - SDL_PenUpdateGUIDForType(&pen->guid, pen->type); } static void tablet_tool_handle_hardware_serial(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t serial_hi, uint32_t serial_lo) { -#if !(SDL_PEN_DEBUG_NOID) - struct SDL_WaylandTool *sdltool = data; - struct SDL_WaylandTabletInput *input = sdltool->tablet; - - if (!input->current_pen.builder_guid_complete) { - SDL_Pen *pen = Wayland_get_current_pen(data, tool); - SDL_PenUpdateGUIDForGeneric(&pen->guid, serial_hi, serial_lo); - if (serial_hi || serial_lo) { - input->current_pen.builder_guid_complete = SDL_TRUE; - } - } -#endif + // don't care about this atm. } static void tablet_tool_handle_hardware_id_wacom(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t id_hi, uint32_t id_lo) { -#if !(SDL_PEN_DEBUG_NOID | SDL_PEN_DEBUG_NONWACOM) - struct SDL_WaylandTool *sdltool = data; - struct SDL_WaylandTabletInput *input = sdltool->tablet; - SDL_Pen *pen = Wayland_get_current_pen(data, tool); - Uint32 axis_flags; - -#if SDL_PEN_DEBUG_NOSERIAL_WACOM /* Check: have we disabled pen serial ID decoding for testing? */ - id_hi = 0; -#endif - - SDL_PenUpdateGUIDForWacom(&pen->guid, id_lo, id_hi); - if (id_hi) { /* Have a serial number? */ - input->current_pen.builder_guid_complete = SDL_TRUE; - } - - if (SDL_PenModifyForWacomID(pen, id_lo, &axis_flags)) { - SDL_PenModifyAddCapabilities(pen, axis_flags); - } -#endif + SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data; + sdltool->info.wacom_id = id_lo; } static void tablet_tool_handle_capability(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t capability) { - SDL_Pen *pen = Wayland_get_current_pen(data, tool); - + SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data; switch (capability) { - case ZWP_TABLET_TOOL_V2_CAPABILITY_TILT: - SDL_PenModifyAddCapabilities(pen, SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK); - break; - - case ZWP_TABLET_TOOL_V2_CAPABILITY_PRESSURE: - SDL_PenModifyAddCapabilities(pen, SDL_PEN_AXIS_PRESSURE_MASK); - break; - - case ZWP_TABLET_TOOL_V2_CAPABILITY_DISTANCE: - SDL_PenModifyAddCapabilities(pen, SDL_PEN_AXIS_DISTANCE_MASK); - break; - - case ZWP_TABLET_TOOL_V2_CAPABILITY_ROTATION: - SDL_PenModifyAddCapabilities(pen, SDL_PEN_AXIS_ROTATION_MASK); - break; - - case ZWP_TABLET_TOOL_V2_CAPABILITY_SLIDER: - SDL_PenModifyAddCapabilities(pen, SDL_PEN_AXIS_SLIDER_MASK); - break; - - case ZWP_TABLET_TOOL_V2_CAPABILITY_WHEEL: - /* Presumably for tools other than pens? */ - break; - - default: - break; + #define CASE(wltyp,sdltyp) case ZWP_TABLET_TOOL_V2_CAPABILITY_##wltyp: sdltool->info.capabilities |= sdltyp; return + CASE(TILT, SDL_PEN_CAPABILITY_XTILT | SDL_PEN_CAPABILITY_YTILT); + CASE(PRESSURE, SDL_PEN_CAPABILITY_PRESSURE); + CASE(DISTANCE, SDL_PEN_CAPABILITY_DISTANCE); + CASE(ROTATION, SDL_PEN_CAPABILITY_ROTATION); + CASE(SLIDER, SDL_PEN_CAPABILITY_SLIDER); + #undef CASE + default: break; // unsupported here. } } -static void Wayland_tool_builder_reset(struct SDL_WaylandTabletInput *input) -{ - input->current_pen.builder = NULL; - input->current_pen.builder_guid_complete = SDL_FALSE; -} - static void tablet_tool_handle_done(void *data, struct zwp_tablet_tool_v2 *tool) { - SDL_Pen *pen = Wayland_get_current_pen(data, tool); - struct SDL_WaylandTool *sdltool = data; - struct SDL_WaylandTabletInput *input = sdltool->tablet; - - if (!input->current_pen.builder_guid_complete) { - /* No complete GUID? Use tablet and tool device index */ - SDL_PenUpdateGUIDForGeneric(&pen->guid, input->id, sdltool->penid); - } - - SDL_PenModifyEnd(pen, SDL_TRUE); - - Wayland_tool_builder_reset(input); } -static void Wayland_tool_destroy(struct zwp_tablet_tool_v2 *tool) -{ - if (tool) { - struct SDL_WaylandTool *waypen = zwp_tablet_tool_v2_get_user_data(tool); - if (waypen) { - SDL_free(waypen); - } - zwp_tablet_tool_v2_destroy(tool); - } -} - -static void tablet_object_list_remove(struct SDL_WaylandTabletObjectListNode *head, void *object); - static void tablet_tool_handle_removed(void *data, struct zwp_tablet_tool_v2 *tool) { - struct SDL_WaylandTool *waypen = zwp_tablet_tool_v2_get_user_data(tool); - struct SDL_WaylandTool *sdltool = data; - struct SDL_WaylandTabletInput *input = sdltool->tablet; - SDL_Pen *pen = Wayland_get_current_pen(data, tool); - if (pen) { - SDL_PenModifyEnd(pen, SDL_FALSE); - Wayland_tool_builder_reset(waypen->tablet); - Wayland_tool_destroy(tool); - } else { - zwp_tablet_tool_v2_destroy(tool); + SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data; + if (sdltool->instance_id) { + SDL_RemovePenDevice(0, sdltool->instance_id); } - - tablet_object_list_remove(input->tools, tool); + zwp_tablet_tool_v2_destroy(tool); + SDL_free(sdltool); } static void tablet_tool_handle_proximity_in(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t serial, struct zwp_tablet_v2 *tablet, struct wl_surface *surface) { - struct SDL_WaylandTool *sdltool = data; - struct SDL_WaylandTabletInput *input = sdltool->tablet; - SDL_WindowData *window; - SDL_PenID penid = Wayland_get_penid(data, tool); + SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data; + SDL_WindowData *windowdata = surface ? Wayland_GetWindowDataForOwnedSurface(surface) : NULL; + sdltool->tool_focus = windowdata ? windowdata->sdlwindow : NULL; - if (!surface) { - return; + SDL_assert(sdltool->instance_id == 0); // shouldn't be added at this point. + if (sdltool->info.subtype != SDL_PEN_TYPE_UNKNOWN) { // don't tell SDL about it if we don't know its role. + sdltool->instance_id = SDL_AddPenDevice(0, NULL, &sdltool->info, sdltool); } - window = Wayland_GetWindowDataForOwnedSurface(surface); - - if (window) { - input->tool_focus = window; - input->tool_prox_serial = serial; - - if (penid) { - SDL_SendPenWindowEvent(0, penid, window->sdlwindow); - } else { - SDL_SetMouseFocus(window->sdlwindow); - } - SDL_SetCursor(NULL); - } + // According to the docs, this should be followed by a motion event, where we'll send our SDL events. } static void tablet_tool_handle_proximity_out(void *data, struct zwp_tablet_tool_v2 *tool) { - struct SDL_WaylandTool *sdltool = data; - struct SDL_WaylandTabletInput *input = sdltool->tablet; - SDL_PenID penid = Wayland_get_penid(data, tool); - if (input->tool_focus) { - if (penid) { - SDL_SendPenWindowEvent(0, penid, NULL); - } else { - SDL_SetMouseFocus(NULL); - } - input->tool_focus = NULL; + SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data; + sdltool->tool_focus = NULL; + + if (sdltool->instance_id) { + SDL_RemovePenDevice(0, sdltool->instance_id); + sdltool->instance_id = 0; } } static void tablet_tool_handle_down(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t serial) { - struct SDL_WaylandTool *sdltool = data; - struct SDL_WaylandTabletInput *input = sdltool->tablet; - - input->current_pen.buttons_pressed |= SDL_PEN_DOWN_MASK; - - input->current_pen.serial = serial; + SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data; + sdltool->frame_pen_down = 1; } static void tablet_tool_handle_up(void *data, struct zwp_tablet_tool_v2 *tool) { - struct SDL_WaylandTool *sdltool = data; - struct SDL_WaylandTabletInput *input = sdltool->tablet; - input->current_pen.buttons_released |= SDL_PEN_DOWN_MASK; + SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data; + sdltool->frame_pen_down = 0; } static void tablet_tool_handle_motion(void *data, struct zwp_tablet_tool_v2 *tool, wl_fixed_t sx_w, wl_fixed_t sy_w) { - struct SDL_WaylandTool *sdltool = data; - struct SDL_WaylandTabletInput *input = sdltool->tablet; - SDL_WindowData *window = input->tool_focus; - SDL_PenID penid = Wayland_get_penid(data, tool); - - input->sx_w = sx_w; - input->sy_w = sy_w; - - if (input->tool_focus) { + SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data; + SDL_Window *window = sdltool->tool_focus; + if (window) { + const SDL_WindowData *windowdata = window->internal; const float sx_f = (float)wl_fixed_to_double(sx_w); const float sy_f = (float)wl_fixed_to_double(sy_w); - const float sx = sx_f * window->pointer_scale.x; - const float sy = sy_f * window->pointer_scale.y; - - if (penid != SDL_PEN_INVALID) { - input->current_pen.update_status.x = sx; - input->current_pen.update_status.y = sy; - input->current_pen.update_window = window; - } else { - /* Plain mouse event */ - SDL_SendMouseMotion(0, window->sdlwindow, SDL_GLOBAL_MOUSE_ID, SDL_FALSE, sx, sy); - } + const float sx = sx_f * windowdata->pointer_scale.x; + const float sy = sy_f * windowdata->pointer_scale.y; + sdltool->x = sx; + sdltool->y = sy; + sdltool->frame_motion_set = SDL_TRUE; } } static void tablet_tool_handle_pressure(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t pressure) { - struct SDL_WaylandTool *sdltool = data; - struct SDL_WaylandTabletInput *input = sdltool->tablet; - input->current_pen.update_status.axes[SDL_PEN_AXIS_PRESSURE] = pressure / 65535.0f; + SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data; + sdltool->frame_axes[SDL_PEN_AXIS_PRESSURE] = ((float) pressure) / 65535.0f; + sdltool->frame_axes_set |= (1u << SDL_PEN_AXIS_PRESSURE); if (pressure) { - input->current_pen.update_status.axes[SDL_PEN_AXIS_DISTANCE] = 0.0f; + sdltool->frame_axes[SDL_PEN_AXIS_DISTANCE] = 0.0f; + sdltool->frame_axes_set |= (1u << SDL_PEN_AXIS_DISTANCE); } } static void tablet_tool_handle_distance(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t distance) { - struct SDL_WaylandTool *sdltool = data; - struct SDL_WaylandTabletInput *input = sdltool->tablet; - input->current_pen.update_status.axes[SDL_PEN_AXIS_DISTANCE] = distance / 65535.0f; + SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data; + sdltool->frame_axes[SDL_PEN_AXIS_DISTANCE] = ((float) distance) / 65535.0f; + sdltool->frame_axes_set |= (1u << SDL_PEN_AXIS_DISTANCE); if (distance) { - input->current_pen.update_status.axes[SDL_PEN_AXIS_PRESSURE] = 0.0f; + sdltool->frame_axes[SDL_PEN_AXIS_PRESSURE] = 0.0f; + sdltool->frame_axes_set |= (1u << SDL_PEN_AXIS_PRESSURE); } } static void tablet_tool_handle_tilt(void *data, struct zwp_tablet_tool_v2 *tool, wl_fixed_t xtilt, wl_fixed_t ytilt) { - struct SDL_WaylandTool *sdltool = data; - struct SDL_WaylandTabletInput *input = sdltool->tablet; - - input->current_pen.update_status.axes[SDL_PEN_AXIS_XTILT] = (float)(wl_fixed_to_double(xtilt)); - input->current_pen.update_status.axes[SDL_PEN_AXIS_YTILT] = (float)(wl_fixed_to_double(ytilt)); + SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data; + sdltool->frame_axes[SDL_PEN_AXIS_XTILT] = (float)(wl_fixed_to_double(xtilt)); + sdltool->frame_axes[SDL_PEN_AXIS_YTILT] = (float)(wl_fixed_to_double(ytilt)); + sdltool->frame_axes_set |= (1u << SDL_PEN_AXIS_XTILT) | (1u << SDL_PEN_AXIS_YTILT); } static void tablet_tool_handle_button(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t serial, uint32_t button, uint32_t state) { - struct SDL_WaylandTool *sdltool = data; - struct SDL_WaylandTabletInput *input = sdltool->tablet; - Uint16 mask = 0; - SDL_bool pressed = state == ZWP_TABLET_PAD_V2_BUTTON_STATE_PRESSED ? SDL_TRUE : SDL_FALSE; - - /* record event serial number to report it later in tablet_tool_handle_frame() */ - input->current_pen.serial = serial; + SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data; + int sdlbutton; switch (button) { /* see %{_includedir}/linux/input-event-codes.h */ case 0x14b: /* BTN_STYLUS */ - mask = SDL_BUTTON_LMASK; + sdlbutton = 1; break; case 0x14c: /* BTN_STYLUS2 */ - mask = SDL_BUTTON_MMASK; + sdlbutton = 2; break; case 0x149: /* BTN_STYLUS3 */ - mask = SDL_BUTTON_RMASK; + sdlbutton = 3; break; + default: + return; // don't care about this button, I guess. } - if (pressed) { - input->current_pen.buttons_pressed |= mask; - } else { - input->current_pen.buttons_released |= mask; - } + SDL_assert((sdlbutton >= 1) && (sdlbutton <= SDL_arraysize(sdltool->frame_buttons))); + sdltool->frame_buttons[sdlbutton-1] = (state == ZWP_TABLET_PAD_V2_BUTTON_STATE_PRESSED) ? 1 : 0; } static void tablet_tool_handle_rotation(void *data, struct zwp_tablet_tool_v2 *tool, wl_fixed_t degrees) { - struct SDL_WaylandTool *sdltool = data; - struct SDL_WaylandTabletInput *input = sdltool->tablet; - float rotation = (float)(wl_fixed_to_double(degrees)); - - /* map to -180.0f ... 179.0f range: */ - input->current_pen.update_status.axes[SDL_PEN_AXIS_ROTATION] = rotation > 180.0f ? rotation - 360.0f : rotation; + SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data; + const float rotation = (float)(wl_fixed_to_double(degrees)); + sdltool->frame_axes[SDL_PEN_AXIS_ROTATION] = (rotation > 180.0f) ? (rotation - 360.0f) : rotation; // map to -180.0f ... 179.0f range + sdltool->frame_axes_set |= (1u << SDL_PEN_AXIS_ROTATION); } static void tablet_tool_handle_slider(void *data, struct zwp_tablet_tool_v2 *tool, int32_t position) { - struct SDL_WaylandTool *sdltool = data; - struct SDL_WaylandTabletInput *input = sdltool->tablet; - input->current_pen.update_status.axes[SDL_PEN_AXIS_SLIDER] = position / 65535.f; + SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data; + sdltool->frame_axes[SDL_PEN_AXIS_SLIDER] = position / 65535.f; + sdltool->frame_axes_set |= (1u << SDL_PEN_AXIS_SLIDER); } static void tablet_tool_handle_wheel(void *data, struct zwp_tablet_tool_v2 *tool, int32_t degrees, int32_t clicks) { - /* not supported at the moment */ + // not supported at the moment } static void tablet_tool_handle_frame(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t time) { - struct SDL_WaylandTool *sdltool = data; - struct SDL_WaylandTabletInput *input = sdltool->tablet; - SDL_PenID penid = Wayland_get_penid(data, tool); - SDL_WindowData *window = input->current_pen.update_window; - SDL_PenStatusInfo *status = &input->current_pen.update_status; - int button; - int button_mask; - Uint64 timestamp = Wayland_GetEventTimestamp(SDL_MS_TO_NS(time)); + SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data; - if (penid == 0 || !window) { /* Not a pen, or event reported out of focus */ - return; + if (!sdltool->instance_id) { + return; // Not a pen we report on. } - /* window == input->tool_focus */ - /* All newly released buttons + PEN_UP event */ - button_mask = input->current_pen.buttons_released; - if (button_mask & SDL_PEN_DOWN_MASK) { - /* Perform hit test, if appropriate */ - if (!SDL_PenPerformHitTest() - || !ProcessHitTest(window, input->sdlWaylandInput->seat, input->sx_w, input->sy_w, input->current_pen.serial)) { - SDL_SendPenTipEvent(timestamp, penid, SDL_RELEASED); - } - } - button_mask &= ~SDL_PEN_DOWN_MASK; + const Uint64 timestamp = Wayland_GetEventTimestamp(SDL_MS_TO_NS(time)); + const SDL_PenID instance_id = sdltool->instance_id; + const SDL_Window *window = sdltool->tool_focus; - for (button = 1; button_mask; ++button, button_mask >>= 1) { - if (button_mask & 1) { - SDL_SendPenButton(timestamp, penid, SDL_RELEASED, button); + // I don't know if this is necessary (or makes sense), but send motion before pen downs, but after pen ups, so you don't get unexpected lines drawn. + if (sdltool->frame_motion_set && (sdltool->frame_pen_down != -1)) { + if (sdltool->frame_pen_down) { + SDL_SendPenMotion(timestamp, instance_id, window, sdltool->x, sdltool->y); + SDL_SendPenTouch(timestamp, instance_id, window, SDL_PRESSED, 0); // !!! FIXME: how do we know what tip is in use? + } else { + SDL_SendPenTouch(timestamp, instance_id, window, SDL_RELEASED, 0); // !!! FIXME: how do we know what tip is in use? + SDL_SendPenMotion(timestamp, instance_id, window, sdltool->x, sdltool->y); + } + } else { + if (sdltool->frame_pen_down != -1) { + SDL_SendPenTouch(timestamp, instance_id, window, sdltool->frame_pen_down ? SDL_PRESSED : SDL_RELEASED, 0); // !!! FIXME: how do we know what tip is in use? + } + + if (sdltool->frame_motion_set) { + SDL_SendPenMotion(timestamp, instance_id, window, sdltool->x, sdltool->y); } } - /* All newly pressed buttons + PEN_DOWN event */ - button_mask = input->current_pen.buttons_pressed; - if (button_mask & SDL_PEN_DOWN_MASK) { - /* Perform hit test, if appropriate */ - if (!SDL_PenPerformHitTest() - || !ProcessHitTest(window, input->sdlWaylandInput->seat, input->sx_w, input->sy_w, input->current_pen.serial)) { - SDL_SendPenTipEvent(timestamp, penid, SDL_PRESSED); - } - } - button_mask &= ~SDL_PEN_DOWN_MASK; - - for (button = 1; button_mask; ++button, button_mask >>= 1) { - if (button_mask & 1) { - SDL_SendPenButton(timestamp, penid, SDL_PRESSED, button); + for (SDL_PenAxis i = 0; i < SDL_PEN_NUM_AXES; i++) { + if (sdltool->frame_axes_set & (1u << i)) { + SDL_SendPenAxis(timestamp, instance_id, window, i, sdltool->frame_axes[i]); } } - SDL_SendPenMotion(timestamp, penid, SDL_TRUE, status); + for (int i = 0; i < SDL_arraysize(sdltool->frame_buttons); i++) { + const int state = sdltool->frame_buttons[i]; + if (state != -1) { + SDL_SendPenButton(timestamp, instance_id, window, state ? SDL_PRESSED : SDL_RELEASED, (Uint8) (i + 1)); + sdltool->frame_buttons[i] = -1; + } + } - /* Wayland_UpdateImplicitGrabSerial will ignore serial 0, so it is safe to call with the default value */ - Wayland_UpdateImplicitGrabSerial(input->sdlWaylandInput, input->current_pen.serial); - - /* Reset masks for next tool frame */ - input->current_pen.buttons_pressed = 0; - input->current_pen.buttons_released = 0; - input->current_pen.serial = 0; + // reset for next frame. + sdltool->frame_pen_down = -1; + sdltool->frame_motion_set = SDL_FALSE; + sdltool->frame_axes_set = 0; } static const struct zwp_tablet_tool_v2_listener tablet_tool_listener = { @@ -2803,92 +2637,34 @@ static const struct zwp_tablet_tool_v2_listener tablet_tool_listener = { tablet_tool_handle_frame }; -static struct SDL_WaylandTabletObjectListNode *tablet_object_list_new_node(void *object) -{ - struct SDL_WaylandTabletObjectListNode *node; - - node = SDL_calloc(1, sizeof(*node)); - if (!node) { - return NULL; - } - - node->next = NULL; - node->object = object; - - return node; -} - -static void tablet_object_list_append(struct SDL_WaylandTabletObjectListNode *head, void *object) -{ - if (!head->object) { - head->object = object; - return; - } - - while (head->next) { - head = head->next; - } - - head->next = tablet_object_list_new_node(object); -} - -static void tablet_object_list_destroy(struct SDL_WaylandTabletObjectListNode *head, void (*deleter)(void *object)) -{ - while (head) { - struct SDL_WaylandTabletObjectListNode *next = head->next; - if (head->object) { - (*deleter)(head->object); - } - SDL_free(head); - head = next; - } -} - -void tablet_object_list_remove(struct SDL_WaylandTabletObjectListNode *head, void *object) -{ - struct SDL_WaylandTabletObjectListNode **head_p = &head; - while (*head_p && (*head_p)->object != object) { - head_p = &((*head_p)->next); - } - - if (*head_p) { - struct SDL_WaylandTabletObjectListNode *object_head = *head_p; - - if (object_head == head) { - /* Must not remove head node */ - head->object = NULL; - } else { - *head_p = object_head->next; - SDL_free(object_head); - } - } -} static void tablet_seat_handle_tablet_added(void *data, struct zwp_tablet_seat_v2 *seat, struct zwp_tablet_v2 *tablet) { - struct SDL_WaylandTabletInput *input = data; - - tablet_object_list_append(input->tablets, tablet); + // don't care atm. } static void tablet_seat_handle_tool_added(void *data, struct zwp_tablet_seat_v2 *seat, struct zwp_tablet_tool_v2 *tool) { - struct SDL_WaylandTabletInput *input = data; - struct SDL_WaylandTool *sdltool = SDL_calloc(1, sizeof(struct SDL_WaylandTool)); + SDL_WaylandPenTool *sdltool = SDL_calloc(1, sizeof(*sdltool)); - zwp_tablet_tool_v2_add_listener(tool, &tablet_tool_listener, sdltool); - zwp_tablet_tool_v2_set_user_data(tool, sdltool); + if (sdltool) { // if allocation failed, oh well, we won't report this device. + sdltool->wltool = tool; + sdltool->info.max_tilt = -1.0f; + sdltool->info.num_buttons = -1; + sdltool->frame_pen_down = -1; + for (int i = 0; i < SDL_arraysize(sdltool->frame_buttons); i++) { + sdltool->frame_buttons[i] = -1; + } - sdltool->tablet = input; - - tablet_object_list_append(input->tools, tool); + // this will send a bunch of zwp_tablet_tool_v2 events right up front to tell + // us device details, with a "done" event to let us know we have everything. + zwp_tablet_tool_v2_add_listener(tool, &tablet_tool_listener, sdltool); + } } static void tablet_seat_handle_pad_added(void *data, struct zwp_tablet_seat_v2 *seat, struct zwp_tablet_pad_v2 *pad) { - struct SDL_WaylandTabletInput *input = data; - - tablet_object_list_append(input->pads, pad); + // we don't care atm. } static const struct zwp_tablet_seat_v2_listener tablet_seat_listener = { @@ -2897,44 +2673,39 @@ static const struct zwp_tablet_seat_v2_listener tablet_seat_listener = { tablet_seat_handle_pad_added }; -void Wayland_input_add_tablet(struct SDL_WaylandInput *input, struct SDL_WaylandTabletManager *tablet_manager) +void Wayland_input_init_tablet_support(struct SDL_WaylandInput *input, struct zwp_tablet_manager_v2 *tablet_manager) { - struct SDL_WaylandTabletInput *tablet_input; - static Uint32 num_tablets = 0; - if (!tablet_manager || !input->seat) { return; } - tablet_input = SDL_calloc(1, sizeof(*tablet_input)); + SDL_WaylandTabletInput *tablet_input = SDL_calloc(1, sizeof(*tablet_input)); if (!tablet_input) { return; } - input->tablet = tablet_input; + tablet_input->input = input; + tablet_input->seat = zwp_tablet_manager_v2_get_tablet_seat(tablet_manager, input->seat); - tablet_input->sdlWaylandInput = input; - tablet_input->seat = zwp_tablet_manager_v2_get_tablet_seat((struct zwp_tablet_manager_v2 *)tablet_manager, input->seat); - - tablet_input->tablets = tablet_object_list_new_node(NULL); - tablet_input->tools = tablet_object_list_new_node(NULL); - tablet_input->pads = tablet_object_list_new_node(NULL); - tablet_input->id = num_tablets++; - - zwp_tablet_seat_v2_add_listener((struct zwp_tablet_seat_v2 *)tablet_input->seat, &tablet_seat_listener, tablet_input); + zwp_tablet_seat_v2_add_listener(tablet_input->seat, &tablet_seat_listener, tablet_input); } -#define TABLET_OBJECT_LIST_DELETER(fun) (void (*)(void *)) fun -void Wayland_input_destroy_tablet(struct SDL_WaylandInput *input) +static void Wayland_remove_all_pens_callback(SDL_PenID instance_id, void *handle, void *userdata) { - tablet_object_list_destroy(input->tablet->pads, TABLET_OBJECT_LIST_DELETER(zwp_tablet_pad_v2_destroy)); - tablet_object_list_destroy(input->tablet->tools, TABLET_OBJECT_LIST_DELETER(Wayland_tool_destroy)); - tablet_object_list_destroy(input->tablet->tablets, TABLET_OBJECT_LIST_DELETER(zwp_tablet_v2_destroy)); + SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) handle; + zwp_tablet_tool_v2_destroy(sdltool->wltool); + SDL_free(sdltool); +} - zwp_tablet_seat_v2_destroy(input->tablet->seat); +void Wayland_input_quit_tablet_support(struct SDL_WaylandInput *input) +{ + SDL_RemoveAllPenDevices(Wayland_remove_all_pens_callback, NULL); - SDL_free(input->tablet); - input->tablet = NULL; + if (input && input->tablet_input) { + zwp_tablet_seat_v2_destroy(input->tablet_input->seat); + SDL_free(input->tablet_input); + input->tablet_input = NULL; + } } void Wayland_input_initialize_seat(SDL_VideoData *d) @@ -2957,7 +2728,7 @@ void Wayland_input_initialize_seat(SDL_VideoData *d) wl_seat_set_user_data(input->seat, input); if (d->tablet_manager) { - Wayland_input_add_tablet(input, d->tablet_manager); + Wayland_input_init_tablet_support(d->input, d->tablet_manager); } WAYLAND_wl_display_flush(d->display); @@ -3049,8 +2820,8 @@ void Wayland_display_destroy_input(SDL_VideoData *d) } } - if (input->tablet) { - Wayland_input_destroy_tablet(input); + if (input->tablet_input) { + Wayland_input_quit_tablet_support(input); } if (input->seat) { diff --git a/src/video/wayland/SDL_waylandevents_c.h b/src/video/wayland/SDL_waylandevents_c.h index a65216f0c..fb6e72e67 100644 --- a/src/video/wayland/SDL_waylandevents_c.h +++ b/src/video/wayland/SDL_waylandevents_c.h @@ -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); diff --git a/src/video/wayland/SDL_waylandvideo.c b/src/video/wayland/SDL_waylandvideo.c index 3aa9b8705..1fae5d6e7 100644 --- a/src/video/wayland/SDL_waylandvideo.c +++ b/src/video/wayland/SDL_waylandvideo.c @@ -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); diff --git a/src/video/wayland/SDL_waylandvideo.h b/src/video/wayland/SDL_waylandvideo.h index 6802fcf0a..51b821aa1 100644 --- a/src/video/wayland/SDL_waylandvideo.h +++ b/src/video/wayland/SDL_waylandvideo.h @@ -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; diff --git a/src/video/x11/SDL_x11events.c b/src/video/x11/SDL_x11events.c index 94ebf47ce..4f6aab611 100644 --- a/src/video/x11/SDL_x11events.c +++ b/src/video/x11/SDL_x11events.c @@ -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; } diff --git a/src/video/x11/SDL_x11pen.c b/src/video/x11/SDL_x11pen.c index acb5170b6..0c3f92e0a 100644 --- a/src/video/x11/SDL_x11pen.c +++ b/src/video/x11/SDL_x11pen.c @@ -28,101 +28,79 @@ #include "SDL_x11video.h" #include "SDL_x11xinput2.h" -#define PEN_ERASER_ID_MAXLEN 256 /* Max # characters of device name to scan */ -#define PEN_ERASER_NAME_TAG "eraser" /* String constant to identify erasers */ - -#define DEBUG_PEN (SDL_PEN_DEBUG_NOID | SDL_PEN_DEBUG_NONWACOM | SDL_PEN_DEBUG_UNKNOWN_WACOM | SDL_PEN_DEBUG_NOSERIAL_WACOM) - -#define SDL_PEN_AXIS_VALUATOR_MISSING -1 - -/* X11-specific information attached to each pen */ -typedef struct xinput2_pen +/* Does this device have a valuator for pressure sensitivity? */ +static SDL_bool X11_XInput2DeviceIsPen(SDL_VideoDevice *_this, const XIDeviceInfo *dev) { - float axis_min[SDL_PEN_NUM_AXES]; - float axis_max[SDL_PEN_NUM_AXES]; - float slider_bias; /* shift value to add to PEN_AXIS_SLIDER (before normalisation) */ - float rotation_bias; /* rotation to add to PEN_AXIS_ROTATION (after normalisation) */ - Sint8 valuator_for_axis[SDL_PEN_NUM_AXES]; /* SDL_PEN_AXIS_VALUATOR_MISSING if not supported */ -} xinput2_pen; - -/* X11 atoms */ -static struct -{ - int initialized; /* initialised to 0 */ - Atom device_product_id; - Atom abs_pressure; - Atom abs_tilt_x; - Atom abs_tilt_y; - Atom wacom_serial_ids; - Atom wacom_tool_type; -} pen_atoms; - -/* - * Mapping from X11 device IDs to pen IDs - * - * In X11, the same device ID may represent any number of pens. We - * thus cannot directly use device IDs as pen IDs. - */ -static struct -{ - int num_pens_known; /* Number of currently known pens (based on their GUID); used to give pen ID to new pens */ - int num_entries; /* Number of X11 device IDs that correspond to pens */ - - struct pen_device_id_mapping - { - Uint32 deviceid; - Uint32 pen_id; - } * entries; /* Current pen to device ID mappings */ -} pen_map; - -typedef enum -{ - SDL_PEN_VENDOR_UNKNOWN = 0, - SDL_PEN_VENDOR_WACOM -} sdl_pen_vendor; - -/* Information to identify pens during discovery */ -typedef struct -{ - sdl_pen_vendor vendor; - SDL_GUID guid; - SDL_PenSubtype heuristic_type; /* Distinguish pen+eraser devices with shared bus ID */ - Uint32 devicetype_id, serial; /* used by PEN_VENDOR_WACOM */ - Uint32 deviceid; -} pen_identity; - -int X11_PenIDFromDeviceID(int deviceid) -{ - int i; - for (i = 0; i < pen_map.num_entries; ++i) { - if (pen_map.entries[i].deviceid == deviceid) { - return pen_map.entries[i].pen_id; + const SDL_VideoData *data = (SDL_VideoData *)_this->internal; + for (int i = 0; i < dev->num_classes; i++) { + const XIAnyClassInfo *classinfo = dev->classes[i]; + if (classinfo->type == XIValuatorClass) { + const XIValuatorClassInfo *val_classinfo = (const XIValuatorClassInfo *)classinfo; + if (val_classinfo->label == data->pen_atom_abs_pressure) { + return SDL_TRUE; + } } } - return SDL_PEN_INVALID; + + return SDL_FALSE; } -static void pen_atoms_ensure_initialized(SDL_VideoDevice *_this) +/* Heuristically determines if device is an eraser */ +static SDL_bool X11_XInput2PenIsEraser(SDL_VideoDevice *_this, int deviceid, char *devicename) { + #define PEN_ERASER_NAME_TAG "eraser" /* String constant to identify erasers */ SDL_VideoData *data = (SDL_VideoData *)_this->internal; - if (pen_atoms.initialized) { - return; - } - /* Create atoms if they don't exist yet to pre-empt hotplugging updates */ - pen_atoms.device_product_id = X11_XInternAtom(data->display, "Device Product ID", False); - pen_atoms.wacom_serial_ids = X11_XInternAtom(data->display, "Wacom Serial IDs", False); - pen_atoms.wacom_tool_type = X11_XInternAtom(data->display, "Wacom Tool Type", False); - pen_atoms.abs_pressure = X11_XInternAtom(data->display, "Abs Pressure", False); - pen_atoms.abs_tilt_x = X11_XInternAtom(data->display, "Abs Tilt X", False); - pen_atoms.abs_tilt_y = X11_XInternAtom(data->display, "Abs Tilt Y", False); + if (data->pen_atom_wacom_tool_type != None) { + Atom type_return; + int format_return; + unsigned long num_items_return; + unsigned long bytes_after_return; + unsigned char *tooltype_name_info = NULL; - pen_atoms.initialized = 1; + /* Try Wacom-specific method */ + if (Success == X11_XIGetProperty(data->display, deviceid, + data->pen_atom_wacom_tool_type, + 0, 32, False, + AnyPropertyType, &type_return, &format_return, + &num_items_return, &bytes_after_return, + &tooltype_name_info) && + tooltype_name_info != NULL && num_items_return > 0) { + + SDL_bool result = SDL_FALSE; + char *tooltype_name = NULL; + + if (type_return == XA_ATOM) { + /* Atom instead of string? Un-intern */ + Atom atom = *((Atom *)tooltype_name_info); + if (atom != None) { + tooltype_name = X11_XGetAtomName(data->display, atom); + } + } else if (type_return == XA_STRING && format_return == 8) { + tooltype_name = (char *)tooltype_name_info; + } + + if (tooltype_name) { + if (0 == SDL_strcasecmp(tooltype_name, PEN_ERASER_NAME_TAG)) { + result = SDL_TRUE; + } + X11_XFree(tooltype_name_info); + + return result; + } + } + } + + /* Non-Wacom device? */ + + /* We assume that a device is an eraser if its name contains the string "eraser". + * Unfortunately there doesn't seem to be a clean way to distinguish these cases (as of 2022-03). */ + return (SDL_strcasestr(devicename, PEN_ERASER_NAME_TAG)) ? SDL_TRUE : SDL_FALSE; } -/* Read out an integer property and store into a preallocated Sint32 array, extending 8 and 16 bit values suitably. - Returns number of Sint32s written (<= max_words), or 0 on error. */ -static size_t xinput2_pen_get_int_property(SDL_VideoDevice *_this, int deviceid, Atom property, Sint32 *dest, size_t max_words) +// Read out an integer property and store into a preallocated Sint32 array, extending 8 and 16 bit values suitably. +// Returns number of Sint32s written (<= max_words), or 0 on error. +static size_t X11_XInput2PenGetIntProperty(SDL_VideoDevice *_this, int deviceid, Atom property, Sint32 *dest, size_t max_words) { const SDL_VideoData *data = (SDL_VideoData *)_this->internal; Atom type_return; @@ -165,498 +143,249 @@ static size_t xinput2_pen_get_int_property(SDL_VideoDevice *_this, int deviceid, X11_XFree(output); return to_copy; } - return 0; /* type mismatch */ + + return 0; // type mismatch } -/* 32 bit vendor + device ID from evdev */ -static Uint32 xinput2_pen_evdevid(SDL_VideoDevice *_this, int deviceid) +// Identify Wacom devices (if SDL_TRUE is returned) and extract their device type and serial IDs +static SDL_bool X11_XInput2PenWacomDeviceID(SDL_VideoDevice *_this, int deviceid, Uint32 *wacom_devicetype_id, Uint32 *wacom_serial) { -#if !(SDL_PEN_DEBUG_NOID) - Sint32 ids[2]; - - pen_atoms_ensure_initialized(_this); - - if (2 != xinput2_pen_get_int_property(_this, deviceid, pen_atoms.device_product_id, ids, 2)) { - return 0; - } - return ((ids[0] << 16) | (ids[1] & 0xffff)); -#else /* Testing: pretend that we have no ID (not sure if this can happen IRL) */ - return 0; -#endif -} - -/* Gets reasonably-unique GUID for the device */ -static void xinput2_pen_update_generic_guid(SDL_VideoDevice *_this, pen_identity *pident, int deviceid) -{ - Uint32 evdevid = xinput2_pen_evdevid(_this, deviceid); /* also initialises pen_atoms */ - - if (!evdevid) { - /* Fallback: if no evdevid is available; try to at least distinguish devices within the - current session. This is a poor GUID and our last resort. */ - evdevid = deviceid; - } - SDL_PenUpdateGUIDForGeneric(&pident->guid, 0, evdevid); -} - -/* Identify Wacom devices (if SDL_TRUE is returned) and extract their device type and serial IDs */ -static SDL_bool xinput2_wacom_deviceid(SDL_VideoDevice *_this, int deviceid, Uint32 *wacom_devicetype_id, Uint32 *wacom_serial) -{ -#if !(SDL_PEN_DEBUG_NONWACOM) /* Can be disabled for testing */ + SDL_VideoData *data = (SDL_VideoData *)_this->internal; Sint32 serial_id_buf[3]; int result; - pen_atoms_ensure_initialized(_this); - - if ((result = xinput2_pen_get_int_property(_this, deviceid, pen_atoms.wacom_serial_ids, serial_id_buf, 3)) == 3) { + if ((result = X11_XInput2PenGetIntProperty(_this, deviceid, data->pen_atom_wacom_serial_ids, serial_id_buf, 3)) == 3) { *wacom_devicetype_id = serial_id_buf[2]; *wacom_serial = serial_id_buf[1]; -#if SDL_PEN_DEBUG_NOSERIAL_WACOM /* Disabled for testing? */ - *wacom_serial = 0; -#endif return SDL_TRUE; } -#endif + + *wacom_devicetype_id = *wacom_serial = 0; return SDL_FALSE; } -/* Heuristically determines if device is an eraser */ -static SDL_bool xinput2_pen_is_eraser(SDL_VideoDevice *_this, int deviceid, char *devicename) + +typedef struct FindPenByDeviceIDData +{ + int x11_deviceid; + void *handle; +} FindPenByDeviceIDData; + +static SDL_bool FindPenByDeviceID(void *handle, void *userdata) +{ + const X11_PenHandle *x11_handle = (const X11_PenHandle *) handle; + FindPenByDeviceIDData *data = (FindPenByDeviceIDData *) userdata; + if (x11_handle->x11_deviceid != data->x11_deviceid) { + return SDL_FALSE; + } + data->handle = handle; + return SDL_TRUE; +} + +X11_PenHandle *X11_FindPenByDeviceID(int deviceid) +{ + FindPenByDeviceIDData data; + data.x11_deviceid = deviceid; + data.handle = NULL; + SDL_FindPenByCallback(FindPenByDeviceID, &data); + return (X11_PenHandle *) data.handle; +} + +static X11_PenHandle *X11_MaybeAddPen(SDL_VideoDevice *_this, const XIDeviceInfo *dev) { SDL_VideoData *data = (SDL_VideoData *)_this->internal; - char dev_name[PEN_ERASER_ID_MAXLEN]; - int k; + SDL_PenCapabilityFlags capabilities = 0; + X11_PenHandle *handle = NULL; - pen_atoms_ensure_initialized(_this); + if ((dev->use != XISlavePointer && (dev->use != XIFloatingSlave)) || dev->enabled == 0 || !X11_XInput2DeviceIsPen(_this, dev)) { + return NULL; // Only track physical devices that are enabled and look like pens + } else if ((handle = X11_FindPenByDeviceID(dev->deviceid)) != 0) { + return handle; // already have this pen, skip it. + } else if ((handle = SDL_calloc(1, sizeof (*handle))) == NULL) { + return NULL; // oh well. + } - if (pen_atoms.wacom_tool_type != None) { - Atom type_return; - int format_return; - unsigned long num_items_return; - unsigned long bytes_after_return; - unsigned char *tooltype_name_info = NULL; + for (int i = 0; i < SDL_arraysize(handle->valuator_for_axis); i++) { + handle->valuator_for_axis[i] = SDL_X11_PEN_AXIS_VALUATOR_MISSING; // until proven otherwise + } - /* Try Wacom-specific method */ - if (Success == X11_XIGetProperty(data->display, deviceid, - pen_atoms.wacom_tool_type, - 0, 32, False, - AnyPropertyType, &type_return, &format_return, - &num_items_return, &bytes_after_return, - &tooltype_name_info) && - tooltype_name_info != NULL && num_items_return > 0) { + int total_buttons = 0; + for (int i = 0; i < dev->num_classes; i++) { + const XIAnyClassInfo *classinfo = dev->classes[i]; + if (classinfo->type == XIButtonClass) { + const XIButtonClassInfo *button_classinfo = (const XIButtonClassInfo *)classinfo; + total_buttons += button_classinfo->num_buttons; + } else if (classinfo->type == XIValuatorClass) { + const XIValuatorClassInfo *val_classinfo = (const XIValuatorClassInfo *)classinfo; + const Sint8 valuator_nr = val_classinfo->number; + const Atom vname = val_classinfo->label; + const float min = (float)val_classinfo->min; + const float max = (float)val_classinfo->max; + SDL_bool use_this_axis = SDL_TRUE; + SDL_PenAxis axis = SDL_PEN_NUM_AXES; - SDL_bool result = SDL_FALSE; - char *tooltype_name = NULL; - - if (type_return == XA_ATOM) { - /* Atom instead of string? Un-intern */ - Atom atom = *((Atom *)tooltype_name_info); - if (atom != None) { - tooltype_name = X11_XGetAtomName(data->display, atom); - } - } else if (type_return == XA_STRING && format_return == 8) { - tooltype_name = (char *)tooltype_name_info; + // afaict, SDL_PEN_AXIS_DISTANCE is never reported by XInput2 (Wayland can offer it, though) + if (vname == data->pen_atom_abs_pressure) { + axis = SDL_PEN_AXIS_PRESSURE; + } else if (vname == data->pen_atom_abs_tilt_x) { + axis = SDL_PEN_AXIS_XTILT; + } else if (vname == data->pen_atom_abs_tilt_y) { + axis = SDL_PEN_AXIS_YTILT; + } else { + use_this_axis = SDL_FALSE; } - if (tooltype_name) { - if (0 == SDL_strcasecmp(tooltype_name, PEN_ERASER_NAME_TAG)) { - result = SDL_TRUE; - } - X11_XFree(tooltype_name_info); + // !!! FIXME: there are wacom-specific hacks for getting SDL_PEN_AXIS_(ROTATION|SLIDER) on some devices, but for simplicity, we're skipping all that for now. - return result; + if (use_this_axis) { + capabilities |= SDL_GetPenCapabilityFromAxis(axis); + handle->valuator_for_axis[axis] = valuator_nr; + handle->axis_min[axis] = min; + handle->axis_max[axis] = max; } } } - /* Non-Wacom device? */ - /* We assume that a device is an eraser if its name contains the string "eraser". - * Unfortunately there doesn't seem to be a clean way to distinguish these cases (as of 2022-03). */ + // We have a pen if and only if the device measures pressure. + // We checked this in X11_XInput2DeviceIsPen, so just assert it here. + SDL_assert(capabilities & SDL_PEN_CAPABILITY_PRESSURE); - SDL_strlcpy(dev_name, devicename, PEN_ERASER_ID_MAXLEN); - /* lowercase device name string so we can use strstr() */ - for (k = 0; dev_name[k]; ++k) { - dev_name[k] = SDL_tolower(dev_name[k]); + const SDL_bool is_eraser = X11_XInput2PenIsEraser(_this, dev->deviceid, dev->name); + Uint32 wacom_devicetype_id = 0; + Uint32 wacom_serial = 0; + X11_XInput2PenWacomDeviceID(_this, dev->deviceid, &wacom_devicetype_id, &wacom_serial); + + SDL_PenInfo peninfo; + SDL_zero(peninfo); + peninfo.capabilities = capabilities; + peninfo.max_tilt = -1; + peninfo.wacom_id = wacom_devicetype_id; + peninfo.num_buttons = total_buttons; + peninfo.subtype = is_eraser ? SDL_PEN_TYPE_ERASER : SDL_PEN_TYPE_PEN; + if (is_eraser) { + peninfo.capabilities |= SDL_PEN_CAPABILITY_ERASER; } - return (SDL_strstr(dev_name, PEN_ERASER_NAME_TAG)) ? SDL_TRUE : SDL_FALSE; + handle->is_eraser = is_eraser; + handle->x11_deviceid = dev->deviceid; + + handle->pen = SDL_AddPenDevice(0, dev->name, &peninfo, handle); + if (!handle->pen) { + SDL_free(handle); + return NULL; + } + + return handle; } -/* Gets GUID and other identifying information for the device using the best known method */ -static pen_identity xinput2_identify_pen(SDL_VideoDevice *_this, int deviceid, char *name) +X11_PenHandle *X11_MaybeAddPenByDeviceID(SDL_VideoDevice *_this, int deviceid) { - pen_identity pident; - - pident.devicetype_id = 0ul; - pident.serial = 0ul; - pident.deviceid = deviceid; - pident.heuristic_type = SDL_PEN_TYPE_PEN; - SDL_memset(pident.guid.data, 0, sizeof(pident.guid.data)); - - if (xinput2_pen_is_eraser(_this, deviceid, name)) { - pident.heuristic_type = SDL_PEN_TYPE_ERASER; + SDL_VideoData *data = (SDL_VideoData *)_this->internal; + int num_device_info = 0; + XIDeviceInfo *device_info = X11_XIQueryDevice(data->display, deviceid, &num_device_info); + if (device_info) { + SDL_assert(num_device_info == 1); + return X11_MaybeAddPen(_this, device_info); } - - if (xinput2_wacom_deviceid(_this, deviceid, &pident.devicetype_id, &pident.serial)) { - pident.vendor = SDL_PEN_VENDOR_WACOM; - SDL_PenUpdateGUIDForWacom(&pident.guid, pident.devicetype_id, pident.serial); - -#if DEBUG_PEN - printf("[pen] Pen %d reports Wacom device_id %x\n", - deviceid, pident.devicetype_id); -#endif - - } else { - pident.vendor = SDL_PEN_VENDOR_UNKNOWN; - } - if (!pident.serial) { - /* If the pen has a serial number, we can move it across tablets and retain its identity. - Otherwise, we use the evdev ID as part of its GUID, which may mean that we identify it with the tablet. */ - xinput2_pen_update_generic_guid(_this, &pident, deviceid); - } - SDL_PenUpdateGUIDForType(&pident.guid, pident.heuristic_type); - return pident; + return NULL; } -static void xinput2_pen_free_deviceinfo(Uint32 deviceid, void *x11_peninfo, void *context) +void X11_RemovePenByDeviceID(int deviceid) { - SDL_free(x11_peninfo); -} - -static void xinput2_merge_deviceinfo(xinput2_pen *dest, xinput2_pen *src) -{ - *dest = *src; -} - -/** - * Fill in vendor-specific device information, if available - * - * For Wacom pens: identify number of buttons and extra axis (if present) - * - * \param _this global state - * \param dev The device to analyse - * \param pen The pen to initialise - * \param pident Pen identity information - * \param[out] valuator_5 Meaning of the valuator with offset 5, if any - * (written only if known and if the device has a 6th axis, - * e.g., for the Wacom Art Pen and Wacom Airbrush Pen) - * \param[out] axes Bitmask of all possibly supported axes - * - * This function identifies Wacom device types through a Wacom-specific device ID. - * It then fills in pen details from an internal database. - * If the device seems to be a Wacom pen/eraser but can't be identified, the function - * leaves "axes" untouched and sets the other outputs to common defaults. - * - * There is no explicit support for other vendors, though vendors that - * emulate the Wacom API might be supported. - * - * Unsupported devices will keep the default settings. - */ -static void xinput2_vendor_peninfo(SDL_VideoDevice *_this, const XIDeviceInfo *dev, SDL_Pen *pen, pen_identity pident, int *valuator_5, Uint32 *axes) -{ - switch (pident.vendor) { - case SDL_PEN_VENDOR_WACOM: - { - if (SDL_PenModifyForWacomID(pen, pident.devicetype_id, axes)) { - if (*axes & SDL_PEN_AXIS_SLIDER_MASK) { - /* Air Brush Pen or eraser */ - *valuator_5 = SDL_PEN_AXIS_SLIDER; - } else if (*axes & SDL_PEN_AXIS_ROTATION_MASK) { - /* Art Pen or eraser, or 6D Art Pen */ - *valuator_5 = SDL_PEN_AXIS_ROTATION; - } - return; - } else { -#if DEBUG_PEN - printf("[pen] Could not identify wacom pen %d with device id %x, using default settings\n", - pident.deviceid, pident.devicetype_id); -#endif - break; - } + X11_PenHandle *handle = X11_FindPenByDeviceID(deviceid); + if (handle) { + SDL_RemovePenDevice(0, handle->pen); + SDL_free(handle); } - - default: -#if DEBUG_PEN - printf("[pen] Pen %d is not from a known vendor\n", pident.deviceid); -#endif - break; - } - - /* Fall back to default heuristics for identifying device type */ - - SDL_strlcpy(pen->name, dev->name, SDL_PEN_MAX_NAME); - - pen->type = pident.heuristic_type; -} - -/* Does this device have a valuator for pressure sensitivity? */ -static SDL_bool xinput2_device_is_pen(SDL_VideoDevice *_this, const XIDeviceInfo *dev) -{ - int classct; - - pen_atoms_ensure_initialized(_this); - - for (classct = 0; classct < dev->num_classes; ++classct) { - const XIAnyClassInfo *classinfo = dev->classes[classct]; - - switch (classinfo->type) { - case XIValuatorClass: - { - XIValuatorClassInfo *val_classinfo = (XIValuatorClassInfo *)classinfo; - Atom vname = val_classinfo->label; - - if (vname == pen_atoms.abs_pressure) { - return SDL_TRUE; - } - } - } - } - return SDL_FALSE; } void X11_InitPen(SDL_VideoDevice *_this) { SDL_VideoData *data = (SDL_VideoData *)_this->internal; - int i; - XIDeviceInfo *device_info; - int num_device_info; - device_info = X11_XIQueryDevice(data->display, XIAllDevices, &num_device_info); - if (!device_info) { - return; + #define LOOKUP_PEN_ATOM(X) X11_XInternAtom(data->display, X, False) + data->pen_atom_device_product_id = LOOKUP_PEN_ATOM("Device Product ID"); + data->pen_atom_wacom_serial_ids = LOOKUP_PEN_ATOM("Wacom Serial IDs"); + data->pen_atom_wacom_tool_type = LOOKUP_PEN_ATOM("Wacom Tool Type"); + data->pen_atom_abs_pressure = LOOKUP_PEN_ATOM("Abs Pressure"); + data->pen_atom_abs_tilt_x = LOOKUP_PEN_ATOM("Abs Tilt X"); + data->pen_atom_abs_tilt_y = LOOKUP_PEN_ATOM("Abs Tilt Y"); + #undef LOOKUP_PEN_ATOM + + // Do an initial check on devices. After this, we'll add/remove individual pens when XI_HierarchyChanged events alert us. + int num_device_info = 0; + XIDeviceInfo *device_info = X11_XIQueryDevice(data->display, XIAllDevices, &num_device_info); + if (device_info) { + for (int i = 0; i < num_device_info; i++) { + X11_MaybeAddPen(_this, &device_info[i]); + } + X11_XIFreeDeviceInfo(device_info); } +} - /* Reset the device id -> pen map */ - if (pen_map.entries) { - SDL_free(pen_map.entries); - pen_map.entries = NULL; - pen_map.num_entries = 0; - } +static void X11_FreePenHandle(SDL_PenID instance_id, void *handle, void *userdata) +{ + SDL_free(handle); +} - SDL_PenGCMark(); +void X11_QuitPen(SDL_VideoDevice *_this) +{ + SDL_RemoveAllPenDevices(X11_FreePenHandle, NULL); +} - for (i = 0; i < num_device_info; ++i) { - const XIDeviceInfo *dev = &device_info[i]; - int classct; - xinput2_pen pen_device; - Uint32 capabilities = 0; - Uint32 axis_mask = ~0; /* Permitted axes (default: all) */ - int valuator_5_axis = -1; /* For Wacom devices, the 6th valuator (offset 5) has a model-specific meaning */ - pen_identity pident; - SDL_PenID pen_id; - SDL_Pen *pen; - int old_num_pens_known = pen_map.num_pens_known; - int k; - - /* Only track physical devices that are enabled and look like pens */ - if (dev->use != XISlavePointer || dev->enabled == 0 || !xinput2_device_is_pen(_this, dev)) { +static void X11_XInput2NormalizePenAxes(const X11_PenHandle *pen, float *coords) +{ + /* Normalise axes */ + for (int axis = 0; axis < SDL_PEN_NUM_AXES; ++axis) { + const int valuator = pen->valuator_for_axis[axis]; + if (valuator == SDL_X11_PEN_AXIS_VALUATOR_MISSING) { continue; } - pen_device.slider_bias = 0.0f; - pen_device.rotation_bias = 0.0f; - for (k = 0; k < SDL_PEN_NUM_AXES; ++k) { - pen_device.valuator_for_axis[k] = SDL_PEN_AXIS_VALUATOR_MISSING; + float value = coords[axis]; + const float min = pen->axis_min[axis]; + const float max = pen->axis_max[axis]; + + if (axis == SDL_PEN_AXIS_SLIDER) { + value += pen->slider_bias; } - pident = xinput2_identify_pen(_this, dev->deviceid, dev->name); - - pen_id = SDL_GetPenFromGUID(pident.guid); - if (pen_id == SDL_PEN_INVALID) { - /* We have never met this pen */ - pen_id = ++pen_map.num_pens_known; /* start at 1 */ - } - pen = SDL_PenModifyBegin(pen_id); - - /* Complement XF86 driver information with vendor-specific details */ - xinput2_vendor_peninfo(_this, dev, pen, pident, &valuator_5_axis, &axis_mask); - - for (classct = 0; classct < dev->num_classes; ++classct) { - const XIAnyClassInfo *classinfo = dev->classes[classct]; - - switch (classinfo->type) { - case XIValuatorClass: - { - XIValuatorClassInfo *val_classinfo = (XIValuatorClassInfo *)classinfo; - Sint8 valuator_nr = val_classinfo->number; - Atom vname = val_classinfo->label; - int axis = -1; - - float min = (float)val_classinfo->min; - float max = (float)val_classinfo->max; - - if (vname == pen_atoms.abs_pressure) { - axis = SDL_PEN_AXIS_PRESSURE; - } else if (vname == pen_atoms.abs_tilt_x) { - axis = SDL_PEN_AXIS_XTILT; - } else if (vname == pen_atoms.abs_tilt_y) { - axis = SDL_PEN_AXIS_YTILT; - } - - if (axis == -1 && valuator_nr == 5) { - /* Wacom model-specific axis support */ - /* The meaning of the various axes is highly underspecitied in Xinput2. - * As of 2023-08-26, Wacom seems to be the only vendor to support these axes, so the code below - * captures the de-facto standard. */ - axis = valuator_5_axis; - - switch (axis) { - case SDL_PEN_AXIS_SLIDER: - /* cf. xinput2_wacom_peninfo for how this axis is used. - In all current cases, our API wants this value in 0..1, but the xf86 driver - starts at a negative offset, so we normalise here. */ - pen_device.slider_bias = -min; - max -= min; - min = 0.0f; - break; - - case SDL_PEN_AXIS_ROTATION: - /* The "0" value points to the left, rather than up, so we must - rotate 90 degrees counter-clockwise to have 0 point to the top. */ - - pen_device.rotation_bias = -90.0f; - break; - - default: - break; - } - } - - if (axis >= 0) { - capabilities |= SDL_PEN_AXIS_CAPABILITY(axis); - - pen_device.valuator_for_axis[axis] = valuator_nr; - pen_device.axis_min[axis] = min; - pen_device.axis_max[axis] = max; - } - break; - } - default: - break; - } - } - - /* We have a pen if and only if the device measures pressure */ - if (capabilities & SDL_PEN_AXIS_PRESSURE_MASK) { - xinput2_pen *xinput2_deviceinfo; - Uint64 guid_a, guid_b; - - /* Done collecting data, write to pen */ - SDL_PenModifyAddCapabilities(pen, capabilities); - pen->guid = pident.guid; - - if (pen->deviceinfo) { - /* Updating a known pen */ - xinput2_deviceinfo = (xinput2_pen *)pen->deviceinfo; - xinput2_merge_deviceinfo(xinput2_deviceinfo, &pen_device); + // min ... 0 ... max + if (min < 0.0) { + // Normalise so that 0 remains 0.0 + if (value < 0) { + value = value / (-min); } else { - /* Registering a new pen */ - xinput2_deviceinfo = SDL_malloc(sizeof(xinput2_pen)); - SDL_memcpy(xinput2_deviceinfo, &pen_device, sizeof(xinput2_pen)); - } - pen->deviceinfo = xinput2_deviceinfo; - -#if DEBUG_PEN - printf("[pen] pen %d [%04x] valuators pressure=%d, xtilt=%d, ytilt=%d [%s]\n", - pen->header.id, pen->header.flags, - pen_device.valuator_for_axis[SDL_PEN_AXIS_PRESSURE], - pen_device.valuator_for_axis[SDL_PEN_AXIS_XTILT], - pen_device.valuator_for_axis[SDL_PEN_AXIS_YTILT], - pen->name); -#endif - SDL_memcpy(&guid_a, &pen->guid.data[0], 8); - SDL_memcpy(&guid_b, &pen->guid.data[8], 8); - if (!(guid_a | guid_b)) { -#if DEBUG_PEN - printf("[pen] (pen eliminated due to zero GUID)\n"); -#endif - pen->type = SDL_PEN_TYPE_NONE; - } - - } else { - /* Not a pen, mark for deletion */ - pen->type = SDL_PEN_TYPE_NONE; - } - SDL_PenModifyEnd(pen, SDL_TRUE); - - if (pen->type != SDL_PEN_TYPE_NONE) { - const int map_pos = pen_map.num_entries; - - /* We found a pen: add mapping */ - if (pen_map.entries == NULL) { - pen_map.entries = SDL_calloc(sizeof(struct pen_device_id_mapping), 1); - pen_map.num_entries = 1; - } else { - pen_map.num_entries += 1; - pen_map.entries = SDL_realloc(pen_map.entries, - pen_map.num_entries * (sizeof(struct pen_device_id_mapping))); - } - pen_map.entries[map_pos].deviceid = dev->deviceid; - pen_map.entries[map_pos].pen_id = pen_id; - } else { - /* Revert pen number allocation */ - pen_map.num_pens_known = old_num_pens_known; - } - } - X11_XIFreeDeviceInfo(device_info); - - SDL_PenGCSweep(NULL, xinput2_pen_free_deviceinfo); -} - -static void xinput2_normalize_pen_axes(const SDL_Pen *peninfo, - const xinput2_pen *xpen, - /* inout-mode paramters: */ - float *coords) -{ - int axis; - - /* Normalise axes */ - for (axis = 0; axis < SDL_PEN_NUM_AXES; ++axis) { - int valuator = xpen->valuator_for_axis[axis]; - if (valuator != SDL_PEN_AXIS_VALUATOR_MISSING) { - float value = coords[axis]; - float min = xpen->axis_min[axis]; - float max = xpen->axis_max[axis]; - - if (axis == SDL_PEN_AXIS_SLIDER) { - value += xpen->slider_bias; - } - - /* min ... 0 ... max */ - if (min < 0.0) { - /* Normalise so that 0 remains 0.0 */ - if (value < 0) { - value = value / (-min); - } else { - if (max == 0.0) { - value = 0.0f; - } else { - value = value / max; - } - } - } else { - /* 0 ... min ... max */ - /* including 0.0 = min */ - if (max == 0.0) { + if (max == 0.0f) { value = 0.0f; } else { - value = (value - min) / max; + value = value / max; } } + } else { + // 0 ... min ... max + // including 0.0 = min + if (max == 0.0f) { + value = 0.0f; + } else { + value = (value - min) / max; + } + } - switch (axis) { + switch (axis) { case SDL_PEN_AXIS_XTILT: case SDL_PEN_AXIS_YTILT: - if (peninfo->info.max_tilt > 0.0f) { - value *= peninfo->info.max_tilt; /* normalise to physical max */ - } + //if (peninfo->info.max_tilt > 0.0f) { + // value *= peninfo->info.max_tilt; // normalize to physical max + //} break; case SDL_PEN_AXIS_ROTATION: - /* normalised to -1..1, so let's convert to degrees */ + // normalised to -1..1, so let's convert to degrees value *= 180.0f; - value += xpen->rotation_bias; + value += pen->rotation_bias; - /* handle simple over/underflow */ + // handle simple over/underflow if (value >= 180.0f) { value -= 360.0f; } else if (value < -180.0f) { @@ -666,31 +395,26 @@ static void xinput2_normalize_pen_axes(const SDL_Pen *peninfo, default: break; - } - coords[axis] = value; } + + coords[axis] = value; } } -void X11_PenAxesFromValuators(const SDL_Pen *peninfo, +void X11_PenAxesFromValuators(const X11_PenHandle *pen, const double *input_values, const unsigned char *mask, const int mask_len, - /* out-mode parameters: */ float axis_values[SDL_PEN_NUM_AXES]) { - const xinput2_pen *pen = (xinput2_pen *)peninfo->deviceinfo; - int i; - - for (i = 0; i < SDL_PEN_NUM_AXES; ++i) { + for (int i = 0; i < SDL_PEN_NUM_AXES; i++) { const int valuator = pen->valuator_for_axis[i]; - if (valuator == SDL_PEN_AXIS_VALUATOR_MISSING || valuator >= mask_len * 8 || !(XIMaskIsSet(mask, valuator))) { + if ((valuator == SDL_X11_PEN_AXIS_VALUATOR_MISSING) || (valuator >= mask_len * 8) || !(XIMaskIsSet(mask, valuator))) { axis_values[i] = 0.0f; } else { axis_values[i] = (float)input_values[valuator]; } } - xinput2_normalize_pen_axes(peninfo, pen, axis_values); + X11_XInput2NormalizePenAxes(pen, axis_values); } -#endif /* SDL_VIDEO_DRIVER_X11_XINPUT2 */ +#endif // SDL_VIDEO_DRIVER_X11_XINPUT2 -/* vi: set ts=4 sw=4 expandtab: */ diff --git a/src/video/x11/SDL_x11pen.h b/src/video/x11/SDL_x11pen.h index ebb7847d8..71a607399 100644 --- a/src/video/x11/SDL_x11pen.h +++ b/src/video/x11/SDL_x11pen.h @@ -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: */ diff --git a/src/video/x11/SDL_x11video.c b/src/video/x11/SDL_x11video.c index e255f3ce4..860f486fc 100644 --- a/src/video/x11/SDL_x11video.c +++ b/src/video/x11/SDL_x11video.c @@ -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); } diff --git a/src/video/x11/SDL_x11video.h b/src/video/x11/SDL_x11video.h index f8e567c77..328ac0c8e 100644 --- a/src/video/x11/SDL_x11video.h +++ b/src/video/x11/SDL_x11video.h @@ -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); diff --git a/src/video/x11/SDL_x11xinput2.c b/src/video/x11/SDL_x11xinput2.c index df96499fd..5470747d6 100644 --- a/src/video/x11/SDL_x11xinput2.c +++ b/src/video/x11/SDL_x11xinput2.c @@ -274,18 +274,6 @@ static SDL_XInput2DeviceInfo *xinput2_get_device_info(SDL_VideoData *videodata, return devinfo; } - -static void xinput2_pen_ensure_window(SDL_VideoDevice *_this, const SDL_Pen *pen, Window window) -{ - /* When "flipping" a Wacom eraser pen, we get an XI_DeviceChanged event - * with the newly-activated pen, but this event is global for the display. - * We won't get a window until the pen starts triggering motion or - * button events, so we instead hook the pen to its window at that point. */ - const SDL_WindowData *windowdata = X11_FindWindow(_this, window); - if (windowdata) { - SDL_SendPenWindowEvent(0, pen->header.id, windowdata->window); - } -} #endif void X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie) @@ -303,6 +291,14 @@ void X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie) const XIHierarchyEvent *hierev = (const XIHierarchyEvent *)cookie->data; int i; for (i = 0; i < hierev->num_info; i++) { + // pen stuff... + if ((hierev->info[i].flags & (XISlaveRemoved | XIDeviceDisabled)) != 0) { + X11_RemovePenByDeviceID(hierev->info[i].deviceid); // it's okay if this thing isn't actually a pen, it'll handle it. + } else if ((hierev->info[i].flags & (XISlaveAdded | XIDeviceEnabled)) != 0) { + X11_MaybeAddPenByDeviceID(_this, hierev->info[i].deviceid); // this will do more checks to make sure this is valid. + } + + // not pen stuff... if (hierev->info[i].flags & XISlaveRemoved) { xinput2_remove_device_info(videodata, hierev->info[i].deviceid); } @@ -310,29 +306,14 @@ void X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie) videodata->xinput_hierarchy_changed = SDL_TRUE; } break; - case XI_PropertyEvent: - case XI_DeviceChanged: - { - // FIXME: We shouldn't rescan all devices for pen changes every time a property or active slave changes - X11_InitPen(_this); - } break; - - case XI_Enter: - case XI_Leave: - { - const XIEnterEvent *enterev = (const XIEnterEvent *)cookie->data; - const SDL_WindowData *windowdata = X11_FindWindow(_this, enterev->event); - const SDL_Pen *pen = SDL_GetPenPtr(X11_PenIDFromDeviceID(enterev->sourceid)); - SDL_Window *window = (windowdata && (cookie->evtype == XI_Enter)) ? windowdata->window : NULL; - if (pen) { - SDL_SendPenWindowEvent(0, pen->header.id, window); - } - } break; + // !!! FIXME: the pen code used to rescan all devices here, but we can do this device-by-device with XI_HierarchyChanged. When do these events fire and why? + //case XI_PropertyEvent: + //case XI_DeviceChanged: case XI_RawMotion: { const XIRawEvent *rawev = (const XIRawEvent *)cookie->data; - const SDL_bool is_pen = X11_PenIDFromDeviceID(rawev->sourceid) != SDL_PEN_INVALID; + const SDL_bool is_pen = X11_FindPenByDeviceID(rawev->sourceid) != NULL; SDL_Mouse *mouse = SDL_GetMouse(); SDL_XInput2DeviceInfo *devinfo; double coords[2]; @@ -341,11 +322,9 @@ void X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie) videodata->global_mouse_changed = SDL_TRUE; if (is_pen) { - break; /* Pens check for XI_Motion instead */ + break; // Pens check for XI_Motion instead } - /* Non-pen: */ - if (!mouse->relative_mode || mouse->relative_mode_warp) { break; } @@ -426,31 +405,17 @@ void X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie) case XI_ButtonRelease: { const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data; - const SDL_Pen *pen = SDL_GetPenPtr(X11_PenIDFromDeviceID(xev->deviceid)); + X11_PenHandle *pen = X11_FindPenByDeviceID(xev->deviceid); const int button = xev->detail; const SDL_bool pressed = (cookie->evtype == XI_ButtonPress) ? SDL_TRUE : SDL_FALSE; if (pen) { - xinput2_pen_ensure_window(_this, pen, xev->event); - - /* Only report button event; if there was also pen movement / pressure changes, we expect - an XI_Motion event first anyway */ - if (button == 1) { - /* button 1 is the pen tip */ - if (pressed && SDL_PenPerformHitTest()) { - /* Check whether we should handle window resize / move events */ - SDL_WindowData *windowdata = X11_FindWindow(_this, xev->event); - if (windowdata && X11_TriggerHitTestAction(_this, windowdata, pen->last.x, pen->last.y)) { - SDL_SendWindowEvent(windowdata->window, SDL_EVENT_WINDOW_HIT_TEST, 0, 0); - break; /* Don't pass on this event */ - } - } - SDL_SendPenTipEvent(0, pen->header.id, - pressed ? SDL_PRESSED : SDL_RELEASED); + // Only report button event; if there was also pen movement / pressure changes, we expect an XI_Motion event first anyway. + SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event); + if (button == 1) { // button 1 is the pen tip + SDL_SendPenTouch(0, pen->pen, window, pressed ? SDL_PRESSED : SDL_RELEASED, pen->is_eraser); } else { - SDL_SendPenButton(0, pen->header.id, - pressed ? SDL_PRESSED : SDL_RELEASED, - button - 1); + SDL_SendPenButton(0, pen->pen, window, pressed ? SDL_PRESSED : SDL_RELEASED, button - 1); } } else { /* Otherwise assume a regular mouse */ @@ -475,7 +440,6 @@ void X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie) case XI_Motion: { const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data; - const SDL_Pen *pen = SDL_GetPenPtr(X11_PenIDFromDeviceID(xev->deviceid)); #if SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH SDL_bool pointer_emulated = ((xev->flags & XIPointerEmulated) != 0); #else @@ -489,25 +453,20 @@ void X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie) break; } + X11_PenHandle *pen = X11_FindPenByDeviceID(xev->deviceid); if (pen) { - SDL_PenStatusInfo pen_status; + SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event); + SDL_SendPenMotion(0, pen->pen, window, (float) xev->event_x, (float) xev->event_y); - pen_status.x = (float)xev->event_x; - pen_status.y = (float)xev->event_y; + float axes[SDL_PEN_NUM_AXES]; + X11_PenAxesFromValuators(pen, xev->valuators.values, xev->valuators.mask, xev->valuators.mask_len, axes); - X11_PenAxesFromValuators(pen, - xev->valuators.values, xev->valuators.mask, xev->valuators.mask_len, - &pen_status.axes[0]); - - xinput2_pen_ensure_window(_this, pen, xev->event); - - SDL_SendPenMotion(0, pen->header.id, - SDL_TRUE, - &pen_status); - break; - } - - if (!pointer_emulated) { + for (int i = 0; i < SDL_arraysize(axes); i++) { + if (pen->valuator_for_axis[i] != SDL_X11_PEN_AXIS_VALUATOR_MISSING) { + SDL_SendPenAxis(0, pen->pen, window, (SDL_PenAxis) i, axes[i]); + } + } + } else if (!pointer_emulated) { SDL_Mouse *mouse = SDL_GetMouse(); if (!mouse->relative_mode || mouse->relative_mode_warp) { SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event); diff --git a/test/testautomation.c b/test/testautomation.c index 42dfeef58..6480feb70 100644 --- a/test/testautomation.c +++ b/test/testautomation.c @@ -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, diff --git a/test/testautomation_pen.c b/test/testautomation_pen.c deleted file mode 100644 index 35f3beedf..000000000 --- a/test/testautomation_pen.c +++ /dev/null @@ -1,1940 +0,0 @@ -/* - Simple DirectMedia Layer - Copyright (C) 1997-2024 Sam Lantinga - - This software is provided 'as-is', without any express or implied - warranty. In no event will the authors be held liable for any damages - arising from the use of this software. - - 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. -*/ - -#ifndef NO_BUILD_CONFIG -#include - -/** - * Pen test suite - */ - -#define SDL_internal_h_ /* Inhibit dynamic symbol redefinitions that clash with ours */ - -/* ================= System Under Test (SUT) ================== */ -/* Renaming SUT operations to avoid link-time symbol clashes */ -#define SDL_GetPens SDL_SUT_GetPens -#define SDL_GetPenStatus SDL_SUT_GetPenStatus -#define SDL_GetPenFromGUID SDL_SUT_GetPenFromGUID -#define SDL_GetPenGUID SDL_SUT_GetPenGUID -#define SDL_PenConnected SDL_SUT_PenConnected -#define SDL_GetPenName SDL_SUT_GetPenName -#define SDL_GetPenCapabilities SDL_SUT_GetPenCapabilities -#define SDL_GetPenType SDL_SUT_GetPenType -#define SDL_GetPersistentString(X) X - -#define SDL_GetPenPtr SDL_SUT_GetPenPtr -#define SDL_PenModifyBegin SDL_SUT_PenModifyBegin -#define SDL_PenModifyAddCapabilities SDL_SUT_PenModifyAddCapabilities -#define SDL_PenModifyForWacomID SDL_SUT_PenModifyForWacomID -#define SDL_PenUpdateGUIDForWacom SDL_SUT_PenUpdateGUIDForWacom -#define SDL_PenUpdateGUIDForType SDL_SUT_PenUpdateGUIDForType -#define SDL_PenUpdateGUIDForGeneric SDL_SUT_PenUpdateGUIDForGeneric -#define SDL_PenModifyEnd SDL_SUT_PenModifyEnd -#define SDL_PenGCMark SDL_SUT_PenGCMark -#define SDL_PenGCSweep SDL_SUT_PenGCSweep -#define SDL_SendPenMotion SDL_SUT_SendPenMotion -#define SDL_SendPenButton SDL_SUT_SendPenButton -#define SDL_SendPenTipEvent SDL_SUT_SendPenTipEvent -#define SDL_SendPenWindowEvent SDL_SUT_SendPenWindowEvent -#define SDL_PenPerformHitTest SDL_SUT_PenPerformHitTest -#define SDL_PenInit SDL_SUT_PenInit -#define SDL_PenQuit SDL_SUT_PenQuit - -/* ================= Mock API ================== */ - -#include -#include -#include -/* For SDL_Window, SDL_Mouse, SDL_MouseID: */ -#include "../src/events/SDL_mouse_c.h" -/* Divert calls to mock mouse API: */ -#define SDL_SendMouseMotion SDL_Mock_SendMouseMotion -#define SDL_SendMouseButton SDL_Mock_SendMouseButton -#define SDL_GetMouse SDL_Mock_GetMouse -#define SDL_MousePositionInWindow SDL_Mock_MousePositionInWindow -#define SDL_SetMouseFocus SDL_Mock_SetMouseFocus - -/* Mock mouse API */ -static int SDL_SendMouseMotion(Uint64 timestamp, SDL_Window *window, SDL_MouseID mouseID, SDL_bool relative, float x, float y); -static int SDL_SendMouseButton(Uint64 timestamp, SDL_Window *window, SDL_MouseID mouseID, Uint8 state, Uint8 button); -static SDL_Mouse *SDL_GetMouse(void); -static SDL_bool SDL_MousePositionInWindow(SDL_Window *window, float x, float y); -static void SDL_SetMouseFocus(SDL_Window *window); - -/* Import SUT code with macro-renamed function names */ -#define SDL_waylanddyn_h_ /* hack: suppress spurious build problem with libdecor.h on Wayland */ -#include "../src/events/SDL_pen.c" -#include "../src/events/SDL_pen_c.h" - - -/* ================= Internal SDL API Compatibility ================== */ -/* Mock implementations of Pen -> Mouse calls */ -/* Not thread-safe! */ - -static SDL_bool SDL_MousePositionInWindow(SDL_Window *window, float x, float y) -{ - return SDL_TRUE; -} - -static int _mouseemu_last_event = 0; -static float _mouseemu_last_x = 0.0f; -static float _mouseemu_last_y = 0.0f; -static int _mouseemu_last_mouseid = 0; -static int _mouseemu_last_button = 0; -static SDL_bool _mouseemu_last_relative = SDL_FALSE; -static int _mouseemu_last_focus = -1; - -static int SDL_SendMouseButton(Uint64 timestamp, SDL_Window *window, SDL_MouseID mouseID, Uint8 state, Uint8 button) -{ - if (mouseID == SDL_PEN_MOUSEID) { - _mouseemu_last_event = (state == SDL_PRESSED) ? SDL_EVENT_MOUSE_BUTTON_DOWN : SDL_EVENT_MOUSE_BUTTON_UP; - _mouseemu_last_button = button; - _mouseemu_last_mouseid = mouseID; - } - return 1; -} - -static int SDL_SendMouseMotion(Uint64 timestamp, SDL_Window *window, SDL_MouseID mouseID, SDL_bool relative, float x, float y) -{ - if (mouseID == SDL_PEN_MOUSEID) { - _mouseemu_last_event = SDL_EVENT_MOUSE_MOTION; - _mouseemu_last_x = x; - _mouseemu_last_y = y; - _mouseemu_last_mouseid = mouseID; - _mouseemu_last_relative = relative; - } - return 1; -} - -static SDL_Mouse *SDL_GetMouse(void) -{ - static SDL_Mouse dummy_mouse; - - return &dummy_mouse; -} - -static void SDL_SetMouseFocus(SDL_Window *window) -{ - _mouseemu_last_focus = window ? 1 : 0; -} - -/* ================= Test Case Support ================== */ - -#define PEN_NUM_TEST_IDS 8 - -/* Helper functions */ - -/* Iterate over all pens to find index for pen ID, otherwise -1 */ -static int _pen_iterationFindsPenIDAt(SDL_PenID needle) -{ - int i; - int num_pens = -1; - - SDL_PenID *pens = SDL_GetPens(&num_pens); - /* Check for (a) consistency and (b) ability to handle NULL parameter */ - SDL_PenID *pens2 = SDL_GetPens(NULL); - - SDLTest_AssertCheck(num_pens >= 0, - "SDL_GetPens() yielded %d pens", num_pens); - SDLTest_AssertCheck(pens[num_pens] == 0, - "SDL_GetPens() not 0 terminated (num_pens = %d)", num_pens); - SDLTest_AssertCheck(pens2[num_pens] == 0, - "SDL_GetPens(NULL) not 0 terminated (num_pens = %d)", num_pens); - - for (i = 0; i < num_pens; ++i) { - SDLTest_AssertCheck(pens[i] == pens2[i], - "SDL_GetPens(&i) and SDL_GetPens(NULL) disagree at index %d/%d", i, num_pens); - SDLTest_AssertCheck(pens[i] != SDL_PEN_INVALID, - "Invalid pen ID %08lx at index %d/%d after SDL_GetPens()", (unsigned long) pens[i], i, num_pens); - } - SDL_free(pens2); - - for (i = 0; pens[i]; ++i) { - SDL_PenID pen_id = pens[i]; - - SDLTest_AssertCheck(pen_id != SDL_PEN_INVALID, - "Invalid pen ID %08lx at index %d/%d after SDL_GetPens()", (unsigned long) pen_id, i, num_pens); - if (pen_id == needle) { - SDL_free(pens); - return i; - } - } - SDL_free(pens); - return -1; -} - -/* Retrieve number of pens and sanity-check SDL_GetPens() */ -static int -_num_pens(void) -{ - int num_pens = -1; - SDL_PenID *pens = SDL_GetPens(&num_pens); - SDLTest_AssertCheck(pens != NULL, - "SDL_GetPens() => NULL"); - SDLTest_AssertCheck(num_pens >= 0, - "SDL_GetPens() reports %d pens", num_pens); - SDLTest_AssertCheck(pens[num_pens] == 0, - "SDL_GetPens()[%d] != 0", num_pens); - SDL_free(pens); - return num_pens; -} - -/* Assert number of pens is as expected */ -static void _AssertCheck_num_pens(int expected, char *location) -{ - int num_pens = _num_pens(); - SDLTest_AssertCheck(expected == num_pens, - "Expected SDL_GetPens() =>count = %d, actual = %d: %s", expected, num_pens, location); -} - -/* ---------------------------------------- */ -/* Test device deallocation */ - -typedef struct /* Collection of pen (de)allocation information */ -{ - unsigned int deallocated_id_flags; /* ith bits set to 1 if the ith test_id is deallocated */ - unsigned int deallocated_deviceinfo_flags; /* ith bits set to 1 if deviceinfo as *int with value i was deallocated */ - SDL_PenID ids[PEN_NUM_TEST_IDS]; - SDL_GUID guids[PEN_NUM_TEST_IDS]; - SDL_Window *window; - int num_ids; - int initial_pen_count; -} pen_testdata; - -/* SDL_PenGCSweep(): callback for tracking pen deallocation */ -static void _pen_testdata_callback(Uint32 deviceid, void *deviceinfo, void *tracker_ref) -{ - pen_testdata *tracker = (pen_testdata *)tracker_ref; - int offset = -1; - int i; - - for (i = 0; i < tracker->num_ids; ++i) { - if (deviceid == tracker->ids[i]) { - tracker->deallocated_id_flags |= (1 << i); - } - } - - SDLTest_AssertCheck(deviceinfo != NULL, - "Device %lu has deviceinfo", - (unsigned long) deviceid); - offset = *((int *)deviceinfo); - SDLTest_AssertCheck(offset >= 0 && offset <= 31, - "Device %lu has well-formed deviceinfo %d", - (unsigned long) deviceid, offset); - tracker->deallocated_deviceinfo_flags |= 1 << offset; - SDL_free(deviceinfo); -} - -/* GC Sweep tracking: update "tracker->deallocated_id_flags" and "tracker->deallocated_deviceinfo_flags" to record deallocations */ -static void _pen_trackGCSweep(pen_testdata *tracker) -{ - tracker->deallocated_id_flags = 0; - tracker->deallocated_deviceinfo_flags = 0; - SDL_PenGCSweep(tracker, _pen_testdata_callback); -} - -/* Finds a number of unused pen IDs (does not allocate them). Also initialises GUIDs. */ -static void _pen_unusedIDs(pen_testdata *tracker, int count) -{ - static Uint8 guidmod = 0; /* Ensure uniqueness as long as we use no more than 256 test pens */ - Uint32 synthetic_penid = 1000u; - int index = 0; - - tracker->num_ids = count; - SDLTest_AssertCheck(count < PEN_NUM_TEST_IDS, "Test setup: Valid number of test IDs requested: %d", (int)count); - - while (count--) { - int k; - - while (SDL_GetPenPtr(synthetic_penid)) { - ++synthetic_penid; - } - tracker->ids[index] = synthetic_penid; - for (k = 0; k < 15; ++k) { - tracker->guids[index].data[k] = (Uint8)((16 * k) + index); - } - tracker->guids[index].data[15] = ++guidmod; - - ++synthetic_penid; - ++index; - } -} - -#define DEVICEINFO_UNCHANGED -17 - -/* Allocate deviceinfo for pen */ -static void _pen_setDeviceinfo(SDL_Pen *pen, int deviceinfo) -{ - if (deviceinfo == DEVICEINFO_UNCHANGED) { - SDLTest_AssertCheck(pen->deviceinfo != NULL, - "pen->deviceinfo was already set for %p (%lu), as expected", - pen, (unsigned long) pen->header.id); - } else { - int *data = (int *)SDL_malloc(sizeof(int)); - *data = deviceinfo; - - SDLTest_AssertCheck(pen->deviceinfo == NULL, - "pen->deviceinfo was NULL for %p (%lu) when requesting deviceinfo %d", - pen, (unsigned long) pen->header.id, deviceinfo); - - pen->deviceinfo = data; - } - SDL_PenModifyEnd(pen, SDL_TRUE); -} - -/* ---------------------------------------- */ -/* Back up and restore device information */ - -typedef struct deviceinfo_backup -{ - Uint32 deviceid; - void *deviceinfo; - struct deviceinfo_backup *next; -} deviceinfo_backup; - -/* SDL_PenGCSweep(): Helper callback for collecting all deviceinfo records */ -static void _pen_accumulate_gc_sweep(Uint32 deviceid, void *deviceinfo, void *backup_ref) -{ - deviceinfo_backup **db_ref = (deviceinfo_backup **)backup_ref; - deviceinfo_backup *next = *db_ref; - - *db_ref = SDL_calloc(sizeof(deviceinfo_backup), 1); - (*db_ref)->deviceid = deviceid; - (*db_ref)->deviceinfo = deviceinfo; - (*db_ref)->next = next; -} - -/* SDL_PenGCSweep(): Helper callback that must never be called */ -static void _pen_assert_impossible(Uint32 deviceid, void *deviceinfo, void *backup_ref) -{ - SDLTest_AssertCheck(0, "Deallocation for deviceid %lu during enableAndRestore: not expected", - (unsigned long) deviceid); -} - -/* Disable all pens and store their status */ -static deviceinfo_backup *_pen_disableAndBackup(void) -{ - deviceinfo_backup *backup = NULL; - - SDL_PenGCMark(); - SDL_PenGCSweep(&backup, _pen_accumulate_gc_sweep); - return backup; -} - -/* Restore all pens to their previous status */ -static void _pen_enableAndRestore(deviceinfo_backup *backup, int test_marksweep) -{ - if (test_marksweep) { - SDL_PenGCMark(); - } - while (backup) { - SDL_Pen *disabledpen = SDL_GetPenPtr(backup->deviceid); - deviceinfo_backup *next = backup->next; - - SDL_PenModifyEnd(SDL_PenModifyBegin(disabledpen->header.id), - SDL_TRUE); - disabledpen->deviceinfo = backup->deviceinfo; - - SDL_free(backup); - backup = next; - } - if (test_marksweep) { - SDL_PenGCSweep(NULL, _pen_assert_impossible); - } -} - -static struct SDL_Window _test_window = { 0 }; - -/* ---------------------------------------- */ -/* Default set-up and tear down routines */ - -/* Back up existing pens, allocate fresh ones but don't assign them yet */ -static deviceinfo_backup *_setup_test(pen_testdata *ptest, int pens_for_testing) -{ - int i; - deviceinfo_backup *backup; - - /* Get number of pens */ - SDL_free(SDL_GetPens(&ptest->initial_pen_count)); - - /* Provide fake window for window enter/exit simulation */ - _test_window.id = 0x7e57da7a; - _test_window.w = 1600; - _test_window.h = 1200; - ptest->window = &_test_window; - - /* Grab unused pen IDs for testing */ - _pen_unusedIDs(ptest, pens_for_testing); - for (i = 0; i < pens_for_testing; ++i) { - int index = _pen_iterationFindsPenIDAt(ptest->ids[i]); - SDLTest_AssertCheck(-1 == index, - "Registered PenID(%lu) since index %d == -1", - (unsigned long) ptest->ids[i], index); - } - - /* Remove existing pens, but back up */ - backup = _pen_disableAndBackup(); - - _AssertCheck_num_pens(0, "after disabling and backing up all current pens"); - SDLTest_AssertPass("Removed existing pens"); - - return backup; -} - -static void _teardown_test_general(pen_testdata *ptest, deviceinfo_backup *backup, int with_gc_test) -{ - /* Restore previously existing pens */ - _pen_enableAndRestore(backup, with_gc_test); - - /* validate */ - SDLTest_AssertPass("Restored pens to pre-test state"); - _AssertCheck_num_pens(ptest->initial_pen_count, "after restoring all initial pens"); -} - -static void _teardown_test(pen_testdata *ptest, deviceinfo_backup *backup) -{ - _teardown_test_general(ptest, backup, 0); -} - -static void _teardown_test_with_gc(pen_testdata *ptest, deviceinfo_backup *backup) -{ - _teardown_test_general(ptest, backup, 1); -} - -/* ---------------------------------------- */ -/* Pen simulation */ - -#define SIMPEN_ACTION_DONE 0 -#define SIMPEN_ACTION_MOVE_X 1 -#define SIMPEN_ACTION_MOVE_Y 2 -#define SIMPEN_ACTION_AXIS 3 -#define SIMPEN_ACTION_MOTION_EVENT 4 /* epxlicit motion event */ -#define SIMPEN_ACTION_MOTION_EVENT_S 5 /* send motion event but expect it to be suppressed */ -#define SIMPEN_ACTION_PRESS 6 /* implicit update event */ -#define SIMPEN_ACTION_RELEASE 7 /* implicit update event */ -#define SIMPEN_ACTION_DOWN 8 /* implicit update event */ -#define SIMPEN_ACTION_UP 9 /* implicit update event */ -#define SIMPEN_ACTION_ERASER_MODE 10 - -/* Individual action in pen simulation script */ -typedef struct simulated_pen_action -{ - int type; - int pen_index; /* index into the list of simulated pens */ - int index; /* button or axis number, if needed */ - float update; /* x,y; for AXIS, update[0] is the updated axis */ -} simulated_pen_action; - -static simulated_pen_action _simpen_event(int type, int pen_index, int index, float v, int line_nr) -{ - simulated_pen_action action; - action.type = type; - action.pen_index = pen_index; - action.index = index; - action.update = v; - - /* Sanity check-- turned out to be necessary */ - if ((type == SIMPEN_ACTION_PRESS || type == SIMPEN_ACTION_RELEASE) && index == 0) { - SDL_Log("Error: SIMPEN_EVENT_BUTTON must have button > 0 (first button has number 1!), in line %d!", line_nr); - exit(1); - } - return action; -} - -/* STEP is passed in later (C macros use dynamic scoping) */ - -#define SIMPEN_DONE() \ - STEP _simpen_event(SIMPEN_ACTION_DONE, 0, 0, 0.0f, __LINE__) -#define SIMPEN_MOVE(pen_index, x, y) \ - STEP _simpen_event(SIMPEN_ACTION_MOVE_X, (pen_index), 0, (x), __LINE__); \ - STEP _simpen_event(SIMPEN_ACTION_MOVE_Y, (pen_index), 0, (y), __LINE__) - -#define SIMPEN_AXIS(pen_index, axis, y) \ - STEP _simpen_event(SIMPEN_ACTION_AXIS, (pen_index), (axis), (y), __LINE__) - -#define SIMPEN_EVENT_MOTION(pen_index) \ - STEP _simpen_event(SIMPEN_ACTION_MOTION_EVENT, (pen_index), 0, 0.0f, __LINE__) - -#define SIMPEN_EVENT_MOTION_SUPPRESSED(pen_index) \ - STEP _simpen_event(SIMPEN_ACTION_MOTION_EVENT_S, (pen_index), 0, 0.0f, __LINE__) - -#define SIMPEN_EVENT_BUTTON(pen_index, push, button) \ - STEP _simpen_event((push) ? SIMPEN_ACTION_PRESS : SIMPEN_ACTION_RELEASE, (pen_index), (button), 0.0f, __LINE__) - -#define SIMPEN_EVENT_TIP(pen_index, touch, tip) \ - STEP _simpen_event((touch) ? SIMPEN_ACTION_DOWN : SIMPEN_ACTION_UP, (pen_index), tip, 0.0f, __LINE__) - -#define SIMPEN_SET_ERASER(pen_index, eraser_mode) \ - STEP _simpen_event(SIMPEN_ACTION_ERASER_MODE, (pen_index), eraser_mode, 0.0f, __LINE__) - -static void -_pen_dump(const char *prefix, SDL_Pen *pen) -{ - int i; - char *axes_str; - - if (!pen) { - SDL_Log("(NULL pen)"); - return; - } - - axes_str = SDL_strdup(""); - for (i = 0; i < SDL_PEN_NUM_AXES; ++i) { - char *old_axes_str = axes_str; - SDL_asprintf(&axes_str, "%s\t%f", old_axes_str, pen->last.axes[i]); - SDL_free(old_axes_str); - } - SDL_Log("%s: pen %lu (%s): status=%04lx, flags=%lx, x,y=(%f, %f) axes = %s", - prefix, - (unsigned long) pen->header.id, - pen->name, - (unsigned long) pen->last.buttons, - (unsigned long) pen->header.flags, - pen->last.x, pen->last.y, - axes_str); - SDL_free(axes_str); -} - -/* Runs until the next event has been issued or we are done and returns pointer to it. - Returns NULL once we hit SIMPEN_ACTION_DONE. - Updates simulated_pens accordingly. There must be as many simulated_pens as the highest pen_index used in - any of the "steps". - Also validates the internal state with expectations (via SDL_GetPenStatus()) and updates the, but does not poll SDL events. */ -static simulated_pen_action * -_pen_simulate(simulated_pen_action *steps, int *step_counter, SDL_Pen *simulated_pens, int num_pens) -{ - SDL_bool done = SDL_FALSE; - SDL_bool dump_pens = SDL_FALSE; - unsigned int mask; - int pen_nr; - - do { - simulated_pen_action step = steps[*step_counter]; - SDL_Pen *simpen = &simulated_pens[step.pen_index]; - - if (step.pen_index >= num_pens) { - SDLTest_AssertCheck(0, - "Unexpected pen index %d at step %d, action %d", step.pen_index, *step_counter, step.type); - return NULL; - } - - switch (step.type) { - case SIMPEN_ACTION_DONE: - SDLTest_AssertPass("SIMPEN_ACTION_DONE"); - return NULL; - - case SIMPEN_ACTION_MOVE_X: - SDLTest_AssertPass("SIMPEN_ACTION_MOVE_X [pen %d] : y <- %f", step.pen_index, step.update); - simpen->last.x = step.update; - break; - - case SIMPEN_ACTION_MOVE_Y: - SDLTest_AssertPass("SIMPEN_ACTION_MOVE_Y [pen %d] : x <- %f", step.pen_index, step.update); - simpen->last.y = step.update; - break; - - case SIMPEN_ACTION_AXIS: - SDLTest_AssertPass("SIMPEN_ACTION_AXIS [pen %d] : axis[%d] <- %f", step.pen_index, step.index, step.update); - simpen->last.axes[step.index] = step.update; - break; - - case SIMPEN_ACTION_MOTION_EVENT: - done = SDL_TRUE; - SDLTest_AssertCheck(SDL_SendPenMotion(0, simpen->header.id, SDL_TRUE, - &simpen->last), - "SIMPEN_ACTION_MOTION_EVENT [pen %d]", step.pen_index); - break; - - case SIMPEN_ACTION_MOTION_EVENT_S: - SDLTest_AssertCheck(!SDL_SendPenMotion(0, simpen->header.id, SDL_TRUE, - &simpen->last), - "SIMPEN_ACTION_MOTION_EVENT_SUPPRESSED [pen %d]", step.pen_index); - break; - - case SIMPEN_ACTION_PRESS: - mask = (1 << (step.index - 1)); - simpen->last.buttons |= mask; - SDLTest_AssertCheck(SDL_SendPenButton(0, simpen->header.id, SDL_PRESSED, (Uint8)step.index), - "SIMPEN_ACTION_PRESS [pen %d]: button %d (mask %x)", step.pen_index, step.index, mask); - done = SDL_TRUE; - break; - - case SIMPEN_ACTION_RELEASE: - mask = ~(1 << (step.index - 1)); - simpen->last.buttons &= mask; - SDLTest_AssertCheck(SDL_SendPenButton(0, simpen->header.id, SDL_RELEASED, (Uint8)step.index), - "SIMPEN_ACTION_RELEASE [pen %d]: button %d (mask %x)", step.pen_index, step.index, mask); - done = SDL_TRUE; - break; - - case SIMPEN_ACTION_DOWN: - simpen->last.buttons |= SDL_PEN_DOWN_MASK; - SDLTest_AssertCheck(SDL_SendPenTipEvent(0, simpen->header.id, SDL_PRESSED), - "SIMPEN_ACTION_DOWN [pen %d]: (mask %lx)", step.pen_index, SDL_PEN_DOWN_MASK); - done = SDL_TRUE; - break; - - case SIMPEN_ACTION_UP: - simpen->last.buttons &= ~SDL_PEN_DOWN_MASK; - SDLTest_AssertCheck(SDL_SendPenTipEvent(0, simpen->header.id, SDL_RELEASED), - "SIMPEN_ACTION_UP [pen %d]: (mask %lx)", step.pen_index, ~SDL_PEN_DOWN_MASK); - done = SDL_TRUE; - break; - - case SIMPEN_ACTION_ERASER_MODE: { - Uint32 pmask; - SDL_Pen *pen = SDL_PenModifyBegin(simpen->header.id); - - if (step.index) { - pmask = SDL_PEN_ERASER_MASK; - } else { - pmask = SDL_PEN_INK_MASK; - } - - SDL_PenModifyAddCapabilities(pen, pmask); - SDL_PenModifyEnd(pen, SDL_TRUE); - - simpen->header.flags &= ~(SDL_PEN_INK_MASK | SDL_PEN_ERASER_MASK); - simpen->header.flags |= pmask; - break; - } - - default: - SDLTest_AssertCheck(0, - "Unexpected pen simulation action %d", step.type); - return NULL; - } - ++(*step_counter); - } while (!done); - - for (pen_nr = 0; pen_nr < num_pens; ++pen_nr) { - SDL_Pen *simpen = &simulated_pens[pen_nr]; - float x = -1.0f, y = -1.0f; - float axes[SDL_PEN_NUM_AXES]; - Uint32 actual_flags = SDL_GetPenStatus(simpen->header.id, &x, &y, axes, SDL_PEN_NUM_AXES); - int i; - - if (simpen->last.x != x || simpen->last.y != y) { - SDLTest_AssertCheck(0, "Coordinate mismatch in pen %d", pen_nr); - dump_pens = SDL_TRUE; - } - if ((actual_flags & ~(SDL_PEN_INK_MASK | SDL_PEN_ERASER_MASK)) != (simpen->last.buttons & ~(SDL_PEN_INK_MASK | SDL_PEN_ERASER_MASK))) { - SDLTest_AssertCheck(0, "Status mismatch in pen %d (reported: %08x)", pen_nr, (unsigned int)actual_flags); - dump_pens = SDL_TRUE; - } - if ((actual_flags & (SDL_PEN_INK_MASK | SDL_PEN_ERASER_MASK)) != (simpen->header.flags & (SDL_PEN_INK_MASK | SDL_PEN_ERASER_MASK))) { - SDLTest_AssertCheck(0, "Flags mismatch in pen %d (reported: %08x)", pen_nr, (unsigned int)actual_flags); - dump_pens = SDL_TRUE; - } - for (i = 0; i < SDL_PEN_NUM_AXES; ++i) { - if (axes[i] != simpen->last.axes[i]) { - SDLTest_AssertCheck(0, "Axis %d mismatch in pen %d", pen_nr, i); - dump_pens = SDL_TRUE; - } - } - } - - if (dump_pens) { - int i; - for (i = 0; i < num_pens; ++i) { - SDL_Log("==== pen #%d", i); - _pen_dump("expect", simulated_pens + i); - _pen_dump("actual", SDL_GetPenPtr(simulated_pens[i].header.id)); - } - } - - return &steps[(*step_counter) - 1]; -} - -/* Init simulated_pens with suitable initial state */ -static void -_pen_simulate_init(pen_testdata *ptest, SDL_Pen *simulated_pens, int num_pens) -{ - int i; - for (i = 0; i < num_pens; ++i) { - simulated_pens[i] = *SDL_GetPenPtr(ptest->ids[i]); - } -} - -/* ---------------------------------------- */ -/* Other helper functions */ - -/* "standard" pen registration process */ -static SDL_Pen * -_pen_register(SDL_PenID penid, SDL_GUID guid, char *name, Uint32 flags) -{ - SDL_Pen *pen = SDL_PenModifyBegin(penid); - pen->guid = guid; - SDL_strlcpy(pen->name, name, SDL_PEN_MAX_NAME); - SDL_PenModifyAddCapabilities(pen, flags); - return pen; -} - -/* Test whether EXPECTED and ACTUAL of type TY agree. Their C format string must be FMT. - MESSAGE is a string with one format string, passed as ARG0. */ -#define SDLTest_AssertEq1(TY, FMT, EXPECTED, ACTUAL, MESSAGE, ARG0) \ - { \ - TY _t_expect = (EXPECTED); \ - TY _t_actual = (ACTUAL); \ - SDLTest_AssertCheck(_t_expect == _t_actual, "L%d: " MESSAGE ": expected " #EXPECTED " = " FMT ", actual = " FMT, __LINE__, (ARG0), _t_expect, _t_actual); \ - } - -/* ================= Test Case Implementation ================== */ - -/** - * @brief Check basic pen device introduction and iteration, as well as basic queries - * - * @sa SDL_GetPens, SDL_GetPenName, SDL_GetPenCapabilities - */ -static int -pen_iteration(void *arg) -{ - pen_testdata ptest; - int i; - char long_pen_name[SDL_PEN_MAX_NAME + 10]; - const char *name; - deviceinfo_backup *backup; - - /* Check initial pens */ - SDL_PumpEvents(); - SDLTest_AssertPass("SDL_GetPens() => count = %d", _num_pens()); - - /* Grab unused pen IDs for testing */ - backup = _setup_test(&ptest, 3); /* validates that we have zero pens */ - - /* Re-run GC, track deallocations */ - SDL_PenGCMark(); - _pen_trackGCSweep(&ptest); - _AssertCheck_num_pens(0, "after second GC pass"); - SDLTest_AssertCheck(ptest.deallocated_id_flags == 0, "No unexpected device deallocations"); - SDLTest_AssertCheck(ptest.deallocated_deviceinfo_flags == 0, "No unexpected deviceinfo deallocations"); - SDLTest_AssertPass("Validated that GC on empty pen set is idempotent"); - - /* Add three pens, validate */ - SDL_PenGCMark(); - - SDL_memset(long_pen_name, 'x', sizeof(long_pen_name)); /* Include pen name that is too long */ - long_pen_name[sizeof(long_pen_name) - 1] = 0; - - _pen_setDeviceinfo(_pen_register(ptest.ids[0], ptest.guids[0], "pen 0", - SDL_PEN_INK_MASK | SDL_PEN_AXIS_PRESSURE_MASK), - 16); - _pen_setDeviceinfo(_pen_register(ptest.ids[2], ptest.guids[2], long_pen_name, - SDL_PEN_INK_MASK | SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK), - 20); - _pen_setDeviceinfo(_pen_register(ptest.ids[1], ptest.guids[1], "pen 1", - SDL_PEN_ERASER_MASK | SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_YTILT_MASK), - 24); - _pen_trackGCSweep(&ptest); - - _AssertCheck_num_pens(3, "after allocating three pens"); - - SDLTest_AssertCheck(ptest.deallocated_id_flags == 0, "No unexpected device deallocations"); - SDLTest_AssertCheck(ptest.deallocated_deviceinfo_flags == 0, "No unexpected deviceinfo deallocations"); - - for (i = 0; i < 3; ++i) { - /* Check that all pens are accounted for */ - int index = _pen_iterationFindsPenIDAt(ptest.ids[i]); - SDLTest_AssertCheck(-1 != index, "Found PenID(%lu)", (unsigned long) ptest.ids[i]); - } - SDLTest_AssertPass("Validated that all three pens are indexable"); - - /* Check pen properties */ - SDLTest_AssertCheck(0 == SDL_strcmp("pen 0", SDL_GetPenName(ptest.ids[0])), - "Pen #0 name"); - SDLTest_AssertCheck((SDL_PEN_INK_MASK | SDL_PEN_AXIS_PRESSURE_MASK) == SDL_GetPenCapabilities(ptest.ids[0], NULL), - "Pen #0 capabilities"); - - SDLTest_AssertCheck(0 == SDL_strcmp("pen 1", SDL_GetPenName(ptest.ids[1])), - "Pen #1 name"); - SDLTest_AssertCheck((SDL_PEN_ERASER_MASK | SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_YTILT_MASK) == SDL_GetPenCapabilities(ptest.ids[1], NULL), - "Pen #1 capabilities"); - - name = SDL_GetPenName(ptest.ids[2]); - SDLTest_AssertCheck(SDL_PEN_MAX_NAME - 1 == SDL_strlen(name), - "Pen #2 name length"); - SDLTest_AssertCheck(0 == SDL_memcmp(name, long_pen_name, SDL_PEN_MAX_NAME - 1), - "Pen #2 name contents"); - SDLTest_AssertCheck((SDL_PEN_INK_MASK | SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK) == SDL_GetPenCapabilities(ptest.ids[2], NULL), - "Pen #2 capabilities"); - SDLTest_AssertPass("Pen registration and basic queries"); - - /* Re-run GC, track deallocations */ - SDL_PenGCMark(); - _pen_trackGCSweep(&ptest); - _AssertCheck_num_pens(0, "after third GC pass"); - SDLTest_AssertCheck(ptest.deallocated_id_flags == 0x07, - "No unexpected device deallocation : %08x", ptest.deallocated_id_flags); - SDLTest_AssertCheck(ptest.deallocated_deviceinfo_flags == 0x01110000, - "No unexpected deviceinfo deallocation : %08x ", ptest.deallocated_deviceinfo_flags); - SDLTest_AssertPass("Validated that GC on empty pen set is idempotent"); - - /* tear down and finish */ - _teardown_test(&ptest, backup); - return TEST_COMPLETED; -} - -static void -_expect_pen_attached(SDL_PenID penid) -{ - SDLTest_AssertCheck(-1 != _pen_iterationFindsPenIDAt(penid), - "Found PenID(%lu)", (unsigned long) penid); - SDLTest_AssertCheck(SDL_PenConnected(penid), - "Pen %lu was attached, as expected", (unsigned long) penid); -} - -static void -_expect_pen_detached(SDL_PenID penid) -{ - SDLTest_AssertCheck(-1 == _pen_iterationFindsPenIDAt(penid), - "Did not find PenID(%lu), as expected", (unsigned long) penid); - SDLTest_AssertCheck(!SDL_PenConnected(penid), - "Pen %lu was detached, as expected", (unsigned long) penid); -} - -#define ATTACHED(i) (1 << (i)) - -static void -_expect_pens_attached_or_detached(SDL_PenID *pen_ids, int ids, Uint32 mask) -{ - int i; - int attached_count = 0; - for (i = 0; i < ids; ++i) { - if (mask & (1 << i)) { - ++attached_count; - _expect_pen_attached(pen_ids[i]); - } else { - _expect_pen_detached(pen_ids[i]); - } - } - _AssertCheck_num_pens(attached_count, "While checking attached/detached status"); -} - -/** - * @brief Check pen device hotplugging - * - * @sa SDL_GetPens, SDL_GetPenName, SDL_GetPenCapabilities, SDL_PenConnected - */ -static int -pen_hotplugging(void *arg) -{ - pen_testdata ptest; - deviceinfo_backup *backup = _setup_test(&ptest, 3); - SDL_GUID checkguid; - - /* Add two pens */ - SDL_PenGCMark(); - - _pen_setDeviceinfo(_pen_register(ptest.ids[0], ptest.guids[0], "pen 0", SDL_PEN_INK_MASK | SDL_PEN_AXIS_PRESSURE_MASK), - 16); - _pen_setDeviceinfo(_pen_register(ptest.ids[2], ptest.guids[2], "pen 2", SDL_PEN_INK_MASK | SDL_PEN_AXIS_PRESSURE_MASK), - 24); - _pen_trackGCSweep(&ptest); - - _AssertCheck_num_pens(2, "after allocating two pens (pass 1)"); - SDLTest_AssertCheck(ptest.deallocated_id_flags == 0, "No unexpected device deallocation (pass 1)"); - SDLTest_AssertCheck(ptest.deallocated_deviceinfo_flags == 0, "No unexpected deviceinfo deallocation (pass 1)"); - - _expect_pens_attached_or_detached(ptest.ids, 3, ATTACHED(0) | ATTACHED(2)); - SDLTest_AssertPass("Validated hotplugging (pass 1): attachmend of two pens"); - - /* Introduce pen #1, remove pen #2 */ - SDL_PenGCMark(); - _pen_setDeviceinfo(_pen_register(ptest.ids[0], ptest.guids[0], "pen 0", SDL_PEN_INK_MASK | SDL_PEN_AXIS_PRESSURE_MASK), - DEVICEINFO_UNCHANGED); - _pen_setDeviceinfo(_pen_register(ptest.ids[1], ptest.guids[1], "pen 1", SDL_PEN_INK_MASK | SDL_PEN_AXIS_PRESSURE_MASK), - 20); - _pen_trackGCSweep(&ptest); - - _AssertCheck_num_pens(2, "after allocating two pens (pass 2)"); - SDLTest_AssertCheck(ptest.deallocated_id_flags == 0x04, "No unexpected device deallocation (pass 2): %x", ptest.deallocated_id_flags); - SDLTest_AssertCheck(ptest.deallocated_deviceinfo_flags == 0x01000000, "No unexpected deviceinfo deallocation (pass 2): %x", ptest.deallocated_deviceinfo_flags); - - _expect_pens_attached_or_detached(ptest.ids, 3, ATTACHED(0) | ATTACHED(1)); - SDLTest_AssertPass("Validated hotplugging (pass 2): unplug one, attach another"); - - /* Return to previous state (#0 and #2 attached) */ - SDL_PenGCMark(); - - _pen_setDeviceinfo(_pen_register(ptest.ids[0], ptest.guids[0], "pen 0", SDL_PEN_INK_MASK | SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_YTILT), - DEVICEINFO_UNCHANGED); - _pen_setDeviceinfo(_pen_register(ptest.ids[2], ptest.guids[2], "pen 2", SDL_PEN_INK_MASK | SDL_PEN_AXIS_PRESSURE_MASK), - 24); - _pen_trackGCSweep(&ptest); - - _AssertCheck_num_pens(2, "after allocating two pens (pass 3)"); - SDLTest_AssertCheck(ptest.deallocated_id_flags == 0x02, "No unexpected device deallocation (pass 3)"); - SDLTest_AssertCheck(ptest.deallocated_deviceinfo_flags == 0x00100000, "No unexpected deviceinfo deallocation (pass 3)"); - - _expect_pens_attached_or_detached(ptest.ids, 3, ATTACHED(0) | ATTACHED(2)); - SDLTest_AssertPass("Validated hotplugging (pass 3): return to state of pass 1"); - - /* Introduce pen #1, remove pen #0 */ - SDL_PenGCMark(); - _pen_setDeviceinfo(_pen_register(ptest.ids[1], ptest.guids[1], "pen 1", SDL_PEN_INK_MASK | SDL_PEN_AXIS_PRESSURE_MASK), - 20); - _pen_setDeviceinfo(_pen_register(ptest.ids[2], ptest.guids[2], "pen 2", SDL_PEN_INK_MASK | SDL_PEN_AXIS_PRESSURE_MASK), - DEVICEINFO_UNCHANGED); - _pen_trackGCSweep(&ptest); - - _AssertCheck_num_pens(2, "after allocating two pens (pass 4)"); - SDLTest_AssertCheck(ptest.deallocated_id_flags == 0x01, "No unexpected device deallocation (pass 4): %x", ptest.deallocated_id_flags); - SDLTest_AssertCheck(ptest.deallocated_deviceinfo_flags == 0x00010000, "No unexpected deviceinfo deallocation (pass 4): %x", ptest.deallocated_deviceinfo_flags); - - _expect_pens_attached_or_detached(ptest.ids, 3, ATTACHED(1) | ATTACHED(2)); - SDLTest_AssertPass("Validated hotplugging (pass 5)"); - - /* Check detached pen */ - SDLTest_AssertCheck(0 == SDL_strcmp("pen 0", SDL_GetPenName(ptest.ids[0])), - "Pen #0 name"); - checkguid = SDL_GetPenGUID(ptest.ids[0]); - SDLTest_AssertCheck(0 == SDL_memcmp(ptest.guids[0].data, checkguid.data, sizeof(ptest.guids[0].data)), - "Pen #0 guid"); - SDLTest_AssertCheck((SDL_PEN_INK_MASK | SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_YTILT) == SDL_GetPenCapabilities(ptest.ids[0], NULL), - "Pen #0 capabilities"); - SDLTest_AssertPass("Validated that detached pens retained name, GUID, axis info after pass 5"); - - /* Individually detach #1 dn #2 */ - _expect_pens_attached_or_detached(ptest.ids, 3, ATTACHED(1) | ATTACHED(2)); - SDL_PenModifyEnd(SDL_PenModifyBegin(ptest.ids[1]), SDL_FALSE); - _expect_pens_attached_or_detached(ptest.ids, 3, ATTACHED(2)); - - SDL_PenModifyEnd(SDL_PenModifyBegin(ptest.ids[2]), SDL_FALSE); - _expect_pens_attached_or_detached(ptest.ids, 3, 0); - - SDLTest_AssertPass("Validated individual hotplugging (pass 6)"); - - /* Individually attach all */ - SDL_PenModifyEnd(SDL_PenModifyBegin(ptest.ids[2]), SDL_TRUE); - _expect_pens_attached_or_detached(ptest.ids, 3, ATTACHED(2)); - - SDL_PenModifyEnd(SDL_PenModifyBegin(ptest.ids[0]), SDL_TRUE); - _expect_pens_attached_or_detached(ptest.ids, 3, ATTACHED(0) | ATTACHED(2)); - - SDL_PenModifyEnd(SDL_PenModifyBegin(ptest.ids[1]), SDL_TRUE); - _expect_pens_attached_or_detached(ptest.ids, 3, ATTACHED(0) | ATTACHED(1) | ATTACHED(2)); - SDLTest_AssertPass("Validated individual hotplugging (pass 7)"); - - SDL_PenGCMark(); - _pen_trackGCSweep(&ptest); - _AssertCheck_num_pens(0, "after hotplugging test (cleanup)"); - SDLTest_AssertCheck(ptest.deallocated_id_flags == 0x06, "No unexpected device deallocation (cleanup): %x", ptest.deallocated_id_flags); - SDLTest_AssertCheck(ptest.deallocated_deviceinfo_flags == 0x01100000, "No unexpected deviceinfo deallocation (pass 4): %x", ptest.deallocated_deviceinfo_flags); - - _teardown_test_with_gc(&ptest, backup); - - return TEST_COMPLETED; -} - -/** - * @brief Check pen device GUID handling - * - * @sa SDL_GetPenGUID - */ -static int -pen_GUIDs(void *arg) -{ - int i; - char *names[4] = { "pen 0", "pen 1", "pen 2", "pen 3" }; - pen_testdata ptest; - deviceinfo_backup *backup; - - backup = _setup_test(&ptest, 4); - - /* Define four pens */ - SDL_PenGCMark(); - for (i = 0; i < 4; ++i) { - _pen_setDeviceinfo(_pen_register(ptest.ids[i], ptest.guids[i], names[i], SDL_PEN_INK_MASK | SDL_PEN_AXIS_PRESSURE_MASK), - 20); - } - _pen_trackGCSweep(&ptest); - - /* Detach pens 0 and 2 */ - SDL_PenGCMark(); - for (i = 1; i < 4; i += 2) { - _pen_setDeviceinfo(_pen_register(ptest.ids[i], ptest.guids[i], names[i], SDL_PEN_INK_MASK | SDL_PEN_AXIS_PRESSURE_MASK), - DEVICEINFO_UNCHANGED); - } - _pen_trackGCSweep(&ptest); - - for (i = 0; i < 4; ++i) { - SDLTest_AssertCheck(ptest.ids[i] == SDL_GetPenFromGUID(ptest.guids[i]), - "GUID search succeeded for %d", i); - } - - /* detach all */ - SDL_PenGCMark(); - _pen_trackGCSweep(&ptest); - - _teardown_test(&ptest, backup); - SDLTest_AssertPass("Pen ID lookup by GUID"); - - return TEST_COMPLETED; -} - -/** - * @brief Check pen device button reporting - * - */ -static int -pen_buttonReporting(void *arg) -{ - int i; - int button_nr, pen_nr; - pen_testdata ptest; - SDL_Event event; - SDL_PenStatusInfo update; - float axes[SDL_PEN_NUM_AXES + 1]; - const float expected_x[2] = { 10.0f, 20.0f }; - const float expected_y[2] = { 11.0f, 21.0f }; - const Uint32 all_axes = SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK | SDL_PEN_AXIS_ROTATION_MASK | SDL_PEN_AXIS_SLIDER_MASK; - - /* Register pen */ - deviceinfo_backup *backup = _setup_test(&ptest, 2); - SDL_PenGCMark(); - _pen_setDeviceinfo(_pen_register(ptest.ids[0], ptest.guids[0], "test pen", - SDL_PEN_INK_MASK | all_axes), - 20); - _pen_setDeviceinfo(_pen_register(ptest.ids[1], ptest.guids[1], "test eraser", - SDL_PEN_ERASER_MASK | all_axes), - 24); - _pen_trackGCSweep(&ptest); - - /* Position mouse suitably before we start */ - for (i = 0; i <= SDL_PEN_NUM_AXES; ++i) { - axes[i] = 0.0625f * i; /* initialise with numbers that can be represented precisely in IEEE 754 and - are > 0.0f and <= 1.0f */ - } - - /* Let pens enter the test window */ - SDL_SendPenWindowEvent(0, ptest.ids[0], ptest.window); - SDL_SendPenWindowEvent(0, ptest.ids[1], ptest.window); - - update.x = expected_x[0]; - update.y = expected_y[0]; - SDL_memcpy(update.axes, axes, sizeof(float) * SDL_PEN_NUM_AXES); - SDL_SendPenMotion(0, ptest.ids[0], SDL_TRUE, &update); - update.x = expected_x[1]; - update.y = expected_y[1]; - SDL_memcpy(update.axes, axes + 1, sizeof(float) * SDL_PEN_NUM_AXES); - SDL_SendPenMotion(0, ptest.ids[1], SDL_TRUE, &update); - - while (SDL_PollEvent(&event)) - ; /* Flush event queue */ - - /* Trigger pen tip events for PEN_DOWN */ - SDLTest_AssertPass("Touch pens to surface"); - - for (pen_nr = 0; pen_nr < 2; ++pen_nr) { - float *expected_axes = axes + pen_nr; - SDL_bool found_event = SDL_FALSE; - Uint16 pen_state = 0x0000 | SDL_PEN_DOWN_MASK; - Uint8 tip = SDL_PEN_TIP_INK; - - if (pen_nr == 1) { - pen_state |= SDL_PEN_ERASER_MASK; - tip = SDL_PEN_TIP_ERASER; - } - - SDL_SendPenTipEvent(0, ptest.ids[pen_nr], SDL_PRESSED); - - while (SDL_PollEvent(&event)) { - if (event.type == SDL_EVENT_PEN_DOWN) { - SDLTest_AssertCheck(event.ptip.which == ptest.ids[pen_nr], - "Received SDL_EVENT_PEN_DOWN from correct pen"); - SDLTest_AssertCheck(event.ptip.tip == (pen_nr == 0)? SDL_PEN_TIP_INK : SDL_PEN_TIP_ERASER, - "Received SDL_EVENT_PEN_DOWN for correct tip"); - SDLTest_AssertCheck(event.ptip.state == SDL_PRESSED, - "Received SDL_EVENT_PEN_DOWN but and marked SDL_PRESSED"); - SDLTest_AssertCheck(event.ptip.tip == tip, - "Received tip %x but expected %x", event.ptip.tip, tip); - SDLTest_AssertCheck(event.ptip.pen_state == pen_state, - "Received SDL_EVENT_PEN_DOWN, and state %04x == %04x (expected)", - event.pbutton.pen_state, pen_state); - SDLTest_AssertCheck((event.ptip.x == expected_x[pen_nr]) && (event.ptip.y == expected_y[pen_nr]), - "Received SDL_EVENT_PEN_DOWN event at correct coordinates: (%f, %f) vs (%f, %f) (expected)", - event.pbutton.x, event.pbutton.y, expected_x[pen_nr], expected_y[pen_nr]); - SDLTest_AssertCheck(0 == SDL_memcmp(expected_axes, event.pbutton.axes, sizeof(float) * SDL_PEN_NUM_AXES), - "Received SDL_EVENT_PEN_DOWN event with correct axis values"); - found_event = SDL_TRUE; - } - SDLTest_AssertCheck(found_event, - "Received the expected SDL_EVENT_PEN_DOWN event"); - } - } - - SDLTest_AssertPass("Pen and eraser set up for button testing"); - - /* Actual tests start: pen, then eraser */ - for (pen_nr = 0; pen_nr < 2; ++pen_nr) { - Uint16 pen_state = 0x0000 | SDL_PEN_DOWN_MASK; - float *expected_axes = axes + pen_nr; - - if (pen_nr == 1) { - pen_state |= SDL_PEN_ERASER_MASK; - } - for (button_nr = 1; button_nr <= 8; ++button_nr) { - SDL_bool found_event = SDL_FALSE; - pen_state |= (1 << (button_nr - 1)); - - SDL_SendPenButton(0, ptest.ids[pen_nr], SDL_PRESSED, (Uint8)button_nr); - while (SDL_PollEvent(&event)) { - if (event.type == SDL_EVENT_PEN_BUTTON_DOWN) { - SDLTest_AssertCheck(event.pbutton.which == ptest.ids[pen_nr], - "Received SDL_EVENT_PEN_BUTTON_DOWN from correct pen"); - SDLTest_AssertCheck(event.pbutton.button == button_nr, - "Received SDL_EVENT_PEN_BUTTON_DOWN from correct button"); - SDLTest_AssertCheck(event.pbutton.state == SDL_PRESSED, - "Received SDL_EVENT_PEN_BUTTON_DOWN but and marked SDL_PRESSED"); - SDLTest_AssertCheck(event.pbutton.pen_state == pen_state, - "Received SDL_EVENT_PEN_BUTTON_DOWN, and state %04x == %04x (expected)", - event.pbutton.pen_state, pen_state); - SDLTest_AssertCheck((event.pbutton.x == expected_x[pen_nr]) && (event.pbutton.y == expected_y[pen_nr]), - "Received SDL_EVENT_PEN_BUTTON_DOWN event at correct coordinates: (%f, %f) vs (%f, %f) (expected)", - event.pbutton.x, event.pbutton.y, expected_x[pen_nr], expected_y[pen_nr]); - SDLTest_AssertCheck(0 == SDL_memcmp(expected_axes, event.pbutton.axes, sizeof(float) * SDL_PEN_NUM_AXES), - "Received SDL_EVENT_PEN_BUTTON_DOWN event with correct axis values"); - if (0 != SDL_memcmp(expected_axes, event.pbutton.axes, sizeof(float) * SDL_PEN_NUM_AXES)) { - int ax; - for (ax = 0; ax < SDL_PEN_NUM_AXES; ++ax) { - SDL_Log("\tax %d\t%.5f\t%.5f expected (equal=%d)", - ax, - event.pbutton.axes[ax], expected_axes[ax], - event.pbutton.axes[ax] == expected_axes[ax]); - } - } - found_event = SDL_TRUE; - } - } - SDLTest_AssertCheck(found_event, - "Received the expected SDL_EVENT_PEN_BUTTON_DOWN event"); - } - } - SDLTest_AssertPass("Pressed all buttons"); - - /* Release every other button */ - for (pen_nr = 0; pen_nr < 2; ++pen_nr) { - Uint16 pen_state = 0x00ff | SDL_PEN_DOWN_MASK; /* 8 buttons pressed */ - float *expected_axes = axes + pen_nr; - - if (pen_nr == 1) { - pen_state |= SDL_PEN_ERASER_MASK; - } - for (button_nr = pen_nr + 1; button_nr <= 8; button_nr += 2) { - SDL_bool found_event = SDL_FALSE; - pen_state &= ~(1 << (button_nr - 1)); - - SDL_SendPenButton(0, ptest.ids[pen_nr], SDL_RELEASED, (Uint8)button_nr); - while (SDL_PollEvent(&event)) { - if (event.type == SDL_EVENT_PEN_BUTTON_UP) { - SDLTest_AssertCheck(event.pbutton.which == ptest.ids[pen_nr], - "Received SDL_EVENT_PEN_BUTTON_UP from correct pen"); - SDLTest_AssertCheck(event.pbutton.button == button_nr, - "Received SDL_EVENT_PEN_BUTTON_UP from correct button"); - SDLTest_AssertCheck(event.pbutton.state == SDL_RELEASED, - "Received SDL_EVENT_PEN_BUTTON_UP and is marked SDL_RELEASED"); - SDLTest_AssertCheck(event.pbutton.pen_state == pen_state, - "Received SDL_EVENT_PEN_BUTTON_UP, and state %04x == %04x (expected)", - event.pbutton.pen_state, pen_state); - SDLTest_AssertCheck((event.pbutton.x == expected_x[pen_nr]) && (event.pbutton.y == expected_y[pen_nr]), - "Received SDL_EVENT_PEN_BUTTON_UP event at correct coordinates"); - SDLTest_AssertCheck(0 == SDL_memcmp(expected_axes, event.pbutton.axes, sizeof(float) * SDL_PEN_NUM_AXES), - "Received SDL_EVENT_PEN_BUTTON_UP event with correct axis values"); - found_event = SDL_TRUE; - } - } - SDLTest_AssertCheck(found_event, - "Received the expected SDL_EVENT_PEN_BUTTON_UP event"); - } - } - SDLTest_AssertPass("Released every other button"); - - /* Trigger pen tip events for PEN_UP */ - SDLTest_AssertPass("Remove pens from surface"); - - for (pen_nr = 0; pen_nr < 2; ++pen_nr) { - float *expected_axes = axes + pen_nr; - SDL_bool found_event = SDL_FALSE; - Uint16 pen_state = 0x0000; - Uint8 tip = SDL_PEN_TIP_INK; - - if (pen_nr == 1) { - pen_state |= SDL_PEN_ERASER_MASK; - tip = SDL_PEN_TIP_ERASER; - } - - SDL_SendPenTipEvent(0, ptest.ids[pen_nr], SDL_RELEASED); - - while (SDL_PollEvent(&event)) { - if (event.type == SDL_EVENT_PEN_UP) { - SDLTest_AssertCheck(event.ptip.which == ptest.ids[pen_nr], - "Received SDL_EVENT_PEN_UP from correct pen"); - SDLTest_AssertCheck(event.ptip.tip == (pen_nr == 0)? SDL_PEN_TIP_INK : SDL_PEN_TIP_ERASER, - "Received SDL_EVENT_PEN_UP for correct tip"); - SDLTest_AssertCheck(event.ptip.state == SDL_RELEASED, - "Received SDL_EVENT_PEN_UP but and marked SDL_RELEASED"); - SDLTest_AssertCheck(event.ptip.tip == tip, - "Received tip %x but expected %x", event.ptip.tip, tip); - SDLTest_AssertCheck((event.ptip.pen_state & 0xff00) == (pen_state & 0xff00), - "Received SDL_EVENT_PEN_UP, and state %04x == %04x (expected)", - event.pbutton.pen_state, pen_state); - SDLTest_AssertCheck((event.ptip.x == expected_x[pen_nr]) && (event.ptip.y == expected_y[pen_nr]), - "Received SDL_EVENT_PEN_UP event at correct coordinates: (%f, %f) vs (%f, %f) (expected)", - event.pbutton.x, event.pbutton.y, expected_x[pen_nr], expected_y[pen_nr]); - SDLTest_AssertCheck(0 == SDL_memcmp(expected_axes, event.pbutton.axes, sizeof(float) * SDL_PEN_NUM_AXES), - "Received SDL_EVENT_PEN_UP event with correct axis values"); - found_event = SDL_TRUE; - } - SDLTest_AssertCheck(found_event, - "Received the expected SDL_EVENT_PEN_UP event"); - } - } - - /* Cleanup */ - SDL_PenGCMark(); - _pen_trackGCSweep(&ptest); - _teardown_test(&ptest, backup); - - return TEST_COMPLETED; -} - -/** - * @brief Check pen device movement and axis update reporting - * - * Also tests SDL_GetPenStatus for agreement with the most recently reported events - * - * @sa SDL_GetPenStatus - */ -static int -pen_movementAndAxes(void *arg) -{ - pen_testdata ptest; - SDL_Event event; -#define MAX_STEPS 80 - /* Pen simulation */ - simulated_pen_action steps[MAX_STEPS]; - size_t num_steps = 0; - - SDL_Pen simulated_pens[2]; - int sim_pc = 0; - simulated_pen_action *last_action; - - /* Register pen */ - deviceinfo_backup *backup = _setup_test(&ptest, 2); - - /* Pen simulation program */ -#define STEP steps[num_steps++] = - - /* #1: Check basic reporting */ - /* Hover eraser, tilt axes */ - SIMPEN_MOVE(0, 30.0f, 31.0f); - SIMPEN_AXIS(0, SDL_PEN_AXIS_PRESSURE, 0.0f); - SIMPEN_AXIS(0, SDL_PEN_AXIS_XTILT, 22.5f); - SIMPEN_AXIS(0, SDL_PEN_AXIS_YTILT, 45.0f); - SIMPEN_EVENT_MOTION(0); - - /* #2: Check that motion events without motion aren't reported */ - SIMPEN_EVENT_MOTION_SUPPRESSED(0); - SIMPEN_EVENT_MOTION_SUPPRESSED(0); - - /* #3: Check multiple pens being reported */ - /* Move pen and touch surface, don't tilt */ - SIMPEN_MOVE(1, 40.0f, 41.0f); - SIMPEN_AXIS(1, SDL_PEN_AXIS_PRESSURE, 0.25f); - SIMPEN_EVENT_MOTION(1); - - /* $4: Multi-buttons */ - /* Press eraser buttons */ - SIMPEN_EVENT_TIP(0, "down", SDL_PEN_TIP_ERASER); - SIMPEN_EVENT_BUTTON(0, "push", 2); - SIMPEN_EVENT_BUTTON(0, "push", 1); - SIMPEN_EVENT_BUTTON(0, 0, 2); /* release again */ - SIMPEN_EVENT_BUTTON(0, "push", 3); - - /* #5: Check move + button actions connecting */ - /* Move and tilt pen, press some pen buttons */ - SIMPEN_MOVE(1, 3.0f, 8.0f); - SIMPEN_AXIS(1, SDL_PEN_AXIS_PRESSURE, 0.5f); - SIMPEN_AXIS(1, SDL_PEN_AXIS_XTILT, -21.0f); - SIMPEN_AXIS(1, SDL_PEN_AXIS_YTILT, -25.0f); - SIMPEN_EVENT_MOTION(1); - SIMPEN_EVENT_BUTTON(1, "push", 2); - SIMPEN_EVENT_TIP(1, "down", SDL_PEN_TIP_INK); - - /* #6: Check nonterference between pens */ - /* Eraser releases buttons */ - SIMPEN_EVENT_BUTTON(0, 0, 1); - SIMPEN_EVENT_TIP(0, 0, SDL_PEN_TIP_ERASER); - - /* #7: Press-move-release action */ - /* Eraser press-move-release */ - SIMPEN_EVENT_BUTTON(0, "push", 1); - SIMPEN_MOVE(0, 99.0f, 88.0f); - SIMPEN_AXIS(0, SDL_PEN_AXIS_PRESSURE, 0.625f); - SIMPEN_EVENT_MOTION(0); - SIMPEN_MOVE(0, 44.5f, 42.25f); - SIMPEN_EVENT_MOTION(0); - SIMPEN_EVENT_BUTTON(0, 0, 1); - - /* #8: Intertwining button release actions some more */ - /* Pen releases button */ - SIMPEN_EVENT_BUTTON(1, 0, 2); - SIMPEN_EVENT_TIP(1, 0, SDL_PEN_TIP_INK); - - /* Push one more pen button, then release all ereaser buttons */ - SIMPEN_EVENT_TIP(1, "down", SDL_PEN_TIP_INK); - SIMPEN_EVENT_BUTTON(0, 0, 2); - SIMPEN_EVENT_BUTTON(0, 0, 3); - - /* Lift up pen, flip it so it becomes an eraser, and touch it again */ - SIMPEN_EVENT_TIP(1, 0, SDL_PEN_TIP_INK); - SIMPEN_SET_ERASER(1, 1); - SIMPEN_EVENT_TIP(1, "push", SDL_PEN_TIP_ERASER); - - /* And back again */ - SIMPEN_EVENT_TIP(1, 0, SDL_PEN_TIP_ERASER); - SIMPEN_SET_ERASER(1, 0); - SIMPEN_EVENT_TIP(1, "push", SDL_PEN_TIP_INK); - - /* #9: Suppress move on unsupported axis */ - SIMPEN_AXIS(1, SDL_PEN_AXIS_DISTANCE, 0.25f); - SIMPEN_EVENT_MOTION_SUPPRESSED(0); - - SIMPEN_DONE(); -#undef STEP - /* End of pen simulation program */ - - SDLTest_AssertCheck(num_steps < MAX_STEPS, "Pen simulation program does not exceed buffer size"); -#undef MAX_STEPS - - SDL_PenGCMark(); - _pen_setDeviceinfo(_pen_register(ptest.ids[0], ptest.guids[0], "test eraser", - SDL_PEN_ERASER_MASK | SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK), - 20); - _pen_setDeviceinfo(_pen_register(ptest.ids[1], ptest.guids[1], "test pen", - SDL_PEN_INK_MASK | SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK), - 24); - _pen_trackGCSweep(&ptest); - SDL_SendPenWindowEvent(0, ptest.ids[0], ptest.window); - SDL_SendPenWindowEvent(0, ptest.ids[1], ptest.window); - while (SDL_PollEvent(&event)) - ; /* Flush event queue */ - SDLTest_AssertPass("Pen and eraser set up for testing"); - - _pen_simulate_init(&ptest, simulated_pens, 2); - /* Simulate pen movements */ - while ((last_action = _pen_simulate(steps, &sim_pc, &simulated_pens[0], 2)) != 0) { - int attempts = 0; - SDL_Pen *simpen = &simulated_pens[last_action->pen_index]; - SDL_PenID reported_which = 0; - float reported_x = -1.0f, reported_y = -1.0f; - float *reported_axes = NULL; - Uint32 reported_pen_state = 0; - Uint32 expected_pen_state = simpen->header.flags & SDL_PEN_ERASER_MASK; - SDL_bool dump_pens = SDL_FALSE; - - do { - SDL_PumpEvents(); - SDL_PollEvent(&event); - if (++attempts > 10000) { - SDLTest_AssertCheck(0, "Never got the anticipated event"); - return TEST_ABORTED; - } - } while (event.type != SDL_EVENT_PEN_DOWN - && event.type != SDL_EVENT_PEN_UP - && event.type != SDL_EVENT_PEN_MOTION - && event.type != SDL_EVENT_PEN_BUTTON_UP - && event.type != SDL_EVENT_PEN_BUTTON_DOWN); /* skip boring events */ - - expected_pen_state |= simpen->last.buttons; - - SDLTest_AssertCheck(0 != event.type, - "Received the anticipated event"); - - switch (last_action->type) { - case SIMPEN_ACTION_MOTION_EVENT: - SDLTest_AssertCheck(event.type == SDL_EVENT_PEN_MOTION, "Expected pen motion event (but got 0x%lx)", (unsigned long) event.type); - reported_which = event.pmotion.which; - reported_x = event.pmotion.x; - reported_y = event.pmotion.y; - reported_pen_state = event.pmotion.pen_state; - reported_axes = &event.pmotion.axes[0]; - break; - - case SIMPEN_ACTION_PRESS: - SDLTest_AssertCheck(event.type == SDL_EVENT_PEN_BUTTON_DOWN, "Expected PENBUTTONDOWN event (but got 0x%lx)", (unsigned long) event.type); - SDLTest_AssertCheck(event.pbutton.state == SDL_PRESSED, "Expected PRESSED button"); - SDL_FALLTHROUGH; - case SIMPEN_ACTION_RELEASE: - if (last_action->type == SIMPEN_ACTION_RELEASE) { - SDLTest_AssertCheck(event.type == SDL_EVENT_PEN_BUTTON_UP, "Expected PENBUTTONUP event (but got 0x%lx)", (unsigned long) event.type); - SDLTest_AssertCheck(event.pbutton.state == SDL_RELEASED, "Expected RELEASED button"); - } - SDLTest_AssertCheck(event.pbutton.button == last_action->index, "Expected button %d, but got %d", - last_action->index, event.pbutton.button); - reported_which = event.pbutton.which; - reported_x = event.pbutton.x; - reported_y = event.pbutton.y; - reported_pen_state = event.pbutton.pen_state; - reported_axes = &event.pbutton.axes[0]; - break; - - case SIMPEN_ACTION_DOWN: - SDLTest_AssertCheck(event.type == SDL_EVENT_PEN_DOWN, "Expected PENBUTTONDOWN event (but got 0x%lx)", (unsigned long) event.type); - SDLTest_AssertCheck(event.ptip.state == SDL_PRESSED, "Expected PRESSED button"); - SDL_FALLTHROUGH; - case SIMPEN_ACTION_UP: - if (last_action->type == SIMPEN_ACTION_UP) { - SDLTest_AssertCheck(event.type == SDL_EVENT_PEN_UP, "Expected PENBUTTONUP event (but got 0x%lx)", (unsigned long) event.type); - SDLTest_AssertCheck(event.ptip.state == SDL_RELEASED, "Expected RELEASED button"); - } - SDLTest_AssertCheck(event.ptip.tip == last_action->index, "Expected tip %d, but got %d", - last_action->index, event.ptip.tip); - reported_which = event.ptip.which; - reported_x = event.ptip.x; - reported_y = event.ptip.y; - reported_pen_state = event.ptip.pen_state; - reported_axes = &event.ptip.axes[0]; - break; - - case SIMPEN_ACTION_ERASER_MODE: - break; - - default: - SDLTest_AssertCheck(0, "Error in pen simulator: unexpected action %d", last_action->type); - return TEST_ABORTED; - } - - if (reported_which != simpen->header.id) { - dump_pens = SDL_TRUE; - SDLTest_AssertCheck(0, "Expected report for pen %lu but got report for pen %lu", - (unsigned long) simpen->header.id, - (unsigned long) reported_which); - } - if (reported_x != simpen->last.x || reported_y != simpen->last.y) { - dump_pens = SDL_TRUE; - SDLTest_AssertCheck(0, "Mismatch in pen coordinates"); - } - if (reported_x != simpen->last.x || reported_y != simpen->last.y) { - dump_pens = SDL_TRUE; - SDLTest_AssertCheck(0, "Mismatch in pen coordinates"); - } - if (reported_pen_state != expected_pen_state) { - dump_pens = SDL_TRUE; - SDLTest_AssertCheck(0, "Mismatch in pen state: %lx vs %lx (expected)", - (unsigned long) reported_pen_state, - (unsigned long) expected_pen_state); - } - if (0 != SDL_memcmp(reported_axes, simpen->last.axes, sizeof(float) * SDL_PEN_NUM_AXES)) { - dump_pens = SDL_TRUE; - SDLTest_AssertCheck(0, "Mismatch in axes"); - } - - if (dump_pens) { - SDL_Log("----- Pen #%d:", last_action->pen_index); - _pen_dump("expect", simpen); - _pen_dump("actual", SDL_GetPenPtr(simpen->header.id)); - } - } - SDLTest_AssertPass("Pen and eraser move and report events correctly and independently"); - - /* Cleanup */ - SDL_PenGCMark(); - _pen_trackGCSweep(&ptest); - _teardown_test(&ptest, backup); - return TEST_COMPLETED; -} - -static void -_expect_pen_config(SDL_PenID penid, - SDL_GUID expected_guid, - SDL_bool expected_attached, - char *expected_name, - int expected_type, - int expected_num_buttons, - float expected_max_tilt, - int expected_axes) -{ - SDL_PenCapabilityInfo actual_info = { 0 }; - const char *actual_name = SDL_GetPenName(penid); - - if (penid == SDL_PEN_INVALID) { - SDLTest_Assert(0, "Invalid pen ID"); - return; - } - - SDLTest_AssertEq1(int, "%d", 0, SDL_GUIDCompare(expected_guid, SDL_GetPenGUID(penid)), - "Pen %lu guid equality", (unsigned long) penid); - - SDLTest_AssertCheck(0 == SDL_strcmp(expected_name, actual_name), - "Expected name='%s' vs actual='%s'", expected_name, actual_name); - - SDLTest_AssertEq1(int, "%d", expected_attached, SDL_PenConnected(penid), - "Pen %lu is attached", (unsigned long) penid); - SDLTest_AssertEq1(int, "%d", expected_type, SDL_GetPenType(penid), - "Pen %lu type", (unsigned long) penid); - SDLTest_AssertEq1(int, "%x", expected_axes, SDL_GetPenCapabilities(penid, &actual_info), - "Pen %lu axis flags", (unsigned long) penid); - SDLTest_AssertEq1(int, "%d", expected_num_buttons, actual_info.num_buttons, - "Pen %lu number of buttons", (unsigned long) penid); - SDLTest_AssertEq1(float, "%f", expected_max_tilt, actual_info.max_tilt, - "Pen %lu max tilt", (unsigned long) penid); -} - -/** - * @brief Check backend pen iniitalisation and pen meta-information - * - * @sa SDL_GetPenCapabilities, SDL_PenAxisInfo - */ -static int -pen_initAndInfo(void *arg) -{ - pen_testdata ptest; - SDL_Pen *pen; - Uint32 mask; - char strbuf[SDL_PEN_MAX_NAME]; - - /* Init */ - deviceinfo_backup *backup = _setup_test(&ptest, 7); - - /* Register default pen */ - _expect_pens_attached_or_detached(ptest.ids, 7, 0); - - /* Register completely default pen */ - pen = SDL_PenModifyBegin(ptest.ids[0]); - SDL_memcpy(pen->guid.data, ptest.guids[0].data, sizeof(ptest.guids[0].data)); - SDL_PenModifyEnd(pen, SDL_TRUE); - - SDL_snprintf(strbuf, sizeof(strbuf), - "Pen %lu", (unsigned long) ptest.ids[0]); - _expect_pen_config(ptest.ids[0], ptest.guids[0], SDL_TRUE, - strbuf, SDL_PEN_TYPE_PEN, SDL_PEN_INFO_UNKNOWN, 0.0f, - SDL_PEN_INK_MASK); - _expect_pens_attached_or_detached(ptest.ids, 7, ATTACHED(0)); - SDLTest_AssertPass("Pass #1: default pen"); - - /* Register mostly-default pen with buttons and custom name */ - pen = SDL_PenModifyBegin(ptest.ids[1]); - SDL_PenModifyAddCapabilities(pen, SDL_PEN_AXIS_PRESSURE_MASK); - SDL_memcpy(pen->guid.data, ptest.guids[1].data, sizeof(ptest.guids[1].data)); - SDL_strlcpy(strbuf, "My special test pen", SDL_PEN_MAX_NAME); - SDL_strlcpy(pen->name, strbuf, SDL_PEN_MAX_NAME); - pen->info.num_buttons = 7; - SDL_PenModifyEnd(pen, SDL_TRUE); - - _expect_pen_config(ptest.ids[1], ptest.guids[1], SDL_TRUE, - strbuf, SDL_PEN_TYPE_PEN, 7, 0.0f, - SDL_PEN_INK_MASK | SDL_PEN_AXIS_PRESSURE_MASK); - _expect_pens_attached_or_detached(ptest.ids, 7, ATTACHED(0) | ATTACHED(1)); - SDLTest_AssertPass("Pass #2: default pen with button and name info"); - - /* Register eraser with default name, but keep initially detached */ - pen = SDL_PenModifyBegin(ptest.ids[2]); - SDL_memcpy(pen->guid.data, ptest.guids[2].data, sizeof(ptest.guids[2].data)); - pen->type = SDL_PEN_TYPE_ERASER; - SDL_PenModifyAddCapabilities(pen, SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK); - SDL_PenModifyEnd(pen, SDL_FALSE); - - SDL_snprintf(strbuf, sizeof(strbuf), - "Eraser %lu", (unsigned long) ptest.ids[2]); - _expect_pen_config(ptest.ids[2], ptest.guids[2], SDL_FALSE, - strbuf, SDL_PEN_TYPE_ERASER, SDL_PEN_INFO_UNKNOWN, SDL_PEN_INFO_UNKNOWN, - SDL_PEN_ERASER_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK); - _expect_pens_attached_or_detached(ptest.ids, 7, ATTACHED(0) | ATTACHED(1)); - /* now make available */ - SDL_PenModifyEnd(SDL_PenModifyBegin(ptest.ids[2]), SDL_TRUE); - _expect_pen_config(ptest.ids[2], ptest.guids[2], SDL_TRUE, - strbuf, SDL_PEN_TYPE_ERASER, SDL_PEN_INFO_UNKNOWN, SDL_PEN_INFO_UNKNOWN, - SDL_PEN_ERASER_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK); - _expect_pens_attached_or_detached(ptest.ids, 7, ATTACHED(0) | ATTACHED(1) | ATTACHED(2)); - SDLTest_AssertPass("Pass #3: eraser-type pen initially detached, then attached"); - - /* Abort pen registration */ - pen = SDL_PenModifyBegin(ptest.ids[3]); - SDL_memcpy(pen->guid.data, ptest.guids[3].data, sizeof(ptest.guids[3].data)); - SDL_PenModifyAddCapabilities(pen, SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK); - pen->type = SDL_PEN_TYPE_NONE; - SDL_PenModifyEnd(pen, SDL_TRUE); - _expect_pens_attached_or_detached(ptest.ids, 7, ATTACHED(0) | ATTACHED(1) | ATTACHED(2)); - SDLTest_AssertCheck(NULL == SDL_GetPenName(ptest.ids[3]), "Pen with aborted registration remains unknown"); - SDLTest_AssertPass("Pass #4: aborted pen registration"); - - /* Brush with custom axes */ - pen = SDL_PenModifyBegin(ptest.ids[4]); - SDL_memcpy(pen->guid.data, ptest.guids[4].data, sizeof(ptest.guids[4].data)); - SDL_strlcpy(pen->name, "Testish Brush", SDL_PEN_MAX_NAME); - pen->type = SDL_PEN_TYPE_BRUSH; - pen->info.num_buttons = 1; - SDL_PenModifyAddCapabilities(pen, SDL_PEN_AXIS_ROTATION_MASK); - pen->info.max_tilt = 72.5f; - SDL_PenModifyAddCapabilities(pen, SDL_PEN_AXIS_XTILT_MASK); - SDL_PenModifyAddCapabilities(pen, SDL_PEN_AXIS_PRESSURE_MASK); - SDL_PenModifyEnd(pen, SDL_TRUE); - _expect_pen_config(ptest.ids[4], ptest.guids[4], SDL_TRUE, - "Testish Brush", SDL_PEN_TYPE_BRUSH, 1, 72.5f, - SDL_PEN_INK_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_ROTATION_MASK | SDL_PEN_AXIS_PRESSURE_MASK); - _expect_pens_attached_or_detached(ptest.ids, 7, ATTACHED(0) | ATTACHED(1) | ATTACHED(2) | ATTACHED(4)); - SDLTest_AssertPass("Pass #5: brush-type pen with unusual axis layout"); - - /* Wacom airbrush pen */ - { - const Uint32 wacom_type_id = 0x0912; - const Uint32 wacom_serial_id = 0xa0b1c2d3; - SDL_GUID guid = { - { 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0 } - }; - guid.data[0] = (wacom_serial_id >> 0) & 0xff; - guid.data[1] = (wacom_serial_id >> 8) & 0xff; - guid.data[2] = (wacom_serial_id >> 16) & 0xff; - guid.data[3] = (wacom_serial_id >> 24) & 0xff; - guid.data[4] = (wacom_type_id >> 0) & 0xff; - guid.data[5] = (wacom_type_id >> 8) & 0xff; - guid.data[6] = (wacom_type_id >> 16) & 0xff; - guid.data[7] = (wacom_type_id >> 24) & 0xff; - - pen = SDL_PenModifyBegin(ptest.ids[5]); - SDL_PenModifyForWacomID(pen, wacom_type_id, &mask); - SDL_PenUpdateGUIDForWacom(&pen->guid, wacom_type_id, wacom_serial_id); - SDL_PenModifyAddCapabilities(pen, mask); - SDL_PenModifyEnd(pen, SDL_TRUE); - _expect_pen_config(ptest.ids[5], guid, SDL_TRUE, - "Wacom Airbrush Pen", SDL_PEN_TYPE_AIRBRUSH, 1, 64.0f, /* Max tilt angle */ - SDL_PEN_INK_MASK | SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT_MASK | SDL_PEN_AXIS_YTILT_MASK | SDL_PEN_AXIS_DISTANCE_MASK | SDL_PEN_AXIS_SLIDER_MASK); - _expect_pens_attached_or_detached(ptest.ids, 7, ATTACHED(0) | ATTACHED(1) | ATTACHED(2) | ATTACHED(4) | ATTACHED(5)); - } - SDLTest_AssertPass("Pass #6: wacom airbrush pen"); - - /* Cleanup */ - SDL_PenGCMark(); - _pen_trackGCSweep(&ptest); - _teardown_test(&ptest, backup); - return TEST_COMPLETED; -} - -#define SET_POS(update, xpos, ypos) \ - (update).x = (xpos); \ - (update).y = (ypos); - -static void -_penmouse_expect_button(int type, int button) -{ - SDL_bool press = type == SDL_PRESSED; - SDLTest_AssertCheck((press ? SDL_EVENT_MOUSE_BUTTON_DOWN : SDL_EVENT_MOUSE_BUTTON_UP) == _mouseemu_last_event, - "Mouse button %s: %x", - (press ? "press" : "release"), _mouseemu_last_event); - SDLTest_AssertCheck(button == _mouseemu_last_button, - "Observed the expected simulated button: %d", _mouseemu_last_button); - SDLTest_AssertCheck(SDL_PEN_MOUSEID == _mouseemu_last_mouseid, - "Observed the expected mouse ID: 0x%x", _mouseemu_last_mouseid); - - _mouseemu_last_event = 0; -} - -/** - * @brief Check pen device mouse emulation and event suppression without SDL_HINT_PEN_DELAY_MOUSE_BUTTON - * - * Since we include SDL_pen.c, we link it against our own mock implementations of SDL_PSendMouseButton - * and SDL_SendMouseMotion; see tehere for details. - */ -static int -pen_mouseEmulation(void *arg) -{ - pen_testdata ptest; - SDL_Event event; - int i; - SDL_PenStatusInfo update; - deviceinfo_backup *backup; - - pen_delay_mouse_button_mode = 0; - pen_mouse_emulation_mode = PEN_MOUSE_EMULATE; /* to trigger our own SDL_SendMouseButton */ - - /* Register pen */ - backup = _setup_test(&ptest, 1); - SDL_PenGCMark(); - _pen_setDeviceinfo(_pen_register(ptest.ids[0], ptest.guids[0], "testpen", - SDL_PEN_INK_MASK | SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT | SDL_PEN_AXIS_YTILT), - 20); - _pen_trackGCSweep(&ptest); - - /* Move pen into window */ - SDL_SendPenWindowEvent(0, ptest.ids[0], ptest.window); - - /* Initialise pen location */ - SDL_memset(update.axes, 0, sizeof(update.axes)); - SET_POS(update, 100.0f, 100.0f); - SDL_SendPenMotion(0, ptest.ids[0], SDL_TRUE, &update); - while (SDL_PollEvent(&event)) - ; /* Flush event queue */ - - /* Test motion forwarding */ - _mouseemu_last_event = 0; - SET_POS(update, 121.25f, 110.75f); - SDL_SendPenMotion(0, ptest.ids[0], SDL_TRUE, &update); - SDLTest_AssertCheck(SDL_EVENT_MOUSE_MOTION == _mouseemu_last_event, - "Mouse motion event: %d", _mouseemu_last_event); - SDLTest_AssertCheck(121.25f == _mouseemu_last_x && 110.75f == _mouseemu_last_y, - "Motion to correct position: %f,%f", _mouseemu_last_x, _mouseemu_last_y); - SDLTest_AssertCheck(SDL_PEN_MOUSEID == _mouseemu_last_mouseid, - "Observed the expected mouse ID: 0x%x", _mouseemu_last_mouseid); - SDLTest_AssertCheck(0 == _mouseemu_last_relative, - "Absolute motion event"); - SDLTest_AssertPass("Motion emulation"); - - /* Test redundant motion report suppression */ - _mouseemu_last_event = 0; - - SET_POS(update, 121.25f, 110.75f); - SDL_SendPenMotion(0, ptest.ids[0], SDL_TRUE, &update); - - SET_POS(update, 121.25f, 110.75f); - SDL_SendPenMotion(0, ptest.ids[0], SDL_TRUE, &update); - - update.axes[0] = 1.0f; - SDL_SendPenMotion(0, ptest.ids[0], SDL_TRUE, &update); - - SET_POS(update, 121.25f, 110.75f); - update.axes[0] = 0.0f; - update.axes[1] = 0.75f; - SDL_SendPenMotion(0, ptest.ids[0], SDL_TRUE, &update); - - SDLTest_AssertCheck(0 == _mouseemu_last_event, - "Redundant mouse motion suppressed: %d", _mouseemu_last_event); - SDLTest_AssertPass("Redundant motion suppression"); - - /* Test button press reporting */ - SDL_SendPenTipEvent(0, ptest.ids[0], SDL_PRESSED); - _penmouse_expect_button(SDL_PRESSED, 1); - - for (i = 1; i <= 3; ++i) { - SDL_SendPenButton(0, ptest.ids[0], SDL_PRESSED, (Uint8)i); - _penmouse_expect_button(SDL_PRESSED, i + 1); - } - SDLTest_AssertPass("Button press mouse emulation"); - - /* Test button release reporting */ - SDL_SendPenTipEvent(0, ptest.ids[0], SDL_RELEASED); - _penmouse_expect_button(SDL_RELEASED, 1); - - for (i = 1; i <= 3; ++i) { - SDL_SendPenButton(0, ptest.ids[0], SDL_RELEASED, (Uint8)i); - _penmouse_expect_button(SDL_RELEASED, i + 1); - } - SDLTest_AssertPass("Button release mouse emulation"); - - /* Cleanup */ - SDL_PenGCMark(); - _pen_trackGCSweep(&ptest); - _teardown_test(&ptest, backup); - return TEST_COMPLETED; -} - -/** - * @brief Check pen device mouse emulation when SDL_HINT_PEN_DELAY_MOUSE_BUTTON is enabled (default) - */ -static int -pen_mouseEmulationDelayed(void *arg) -{ - pen_testdata ptest; - SDL_Event event; - int i; - SDL_PenStatusInfo update; - deviceinfo_backup *backup; - - pen_delay_mouse_button_mode = 1; - pen_mouse_emulation_mode = PEN_MOUSE_EMULATE; /* to trigger our own SDL_SendMouseButton */ - - /* Register pen */ - backup = _setup_test(&ptest, 1); - SDL_PenGCMark(); - _pen_setDeviceinfo(_pen_register(ptest.ids[0], ptest.guids[0], "testpen", - SDL_PEN_INK_MASK | SDL_PEN_AXIS_PRESSURE_MASK | SDL_PEN_AXIS_XTILT | SDL_PEN_AXIS_YTILT), - 20); - _pen_trackGCSweep(&ptest); - - /* Move pen into window */ - SDL_SendPenWindowEvent(0, ptest.ids[0], ptest.window); - - /* Initialise pen location */ - SDL_memset(update.axes, 0, sizeof(update.axes)); - SET_POS(update, 100.0f, 100.0f); - SDL_SendPenMotion(0, ptest.ids[0], SDL_TRUE, &update); - while (SDL_PollEvent(&event)) - ; /* Flush event queue */ - - /* Test motion forwarding */ - _mouseemu_last_event = 0; - SET_POS(update, 121.25f, 110.75f); - SDL_SendPenMotion(0, ptest.ids[0], SDL_TRUE, &update); - SDLTest_AssertCheck(SDL_EVENT_MOUSE_MOTION == _mouseemu_last_event, - "Mouse motion event: %d", _mouseemu_last_event); - SDLTest_AssertCheck(121.25f == _mouseemu_last_x && 110.75f == _mouseemu_last_y, - "Motion to correct position: %f,%f", _mouseemu_last_x, _mouseemu_last_y); - SDLTest_AssertCheck(SDL_PEN_MOUSEID == _mouseemu_last_mouseid, - "Observed the expected mouse ID: 0x%x", _mouseemu_last_mouseid); - SDLTest_AssertCheck(0 == _mouseemu_last_relative, - "Absolute motion event"); - SDLTest_AssertPass("Motion emulation"); - _mouseemu_last_event = 0; - - /* Test button press reporting */ - for (i = 1; i <= 2; ++i) { - SDL_SendPenButton(0, ptest.ids[0], SDL_PRESSED, (Uint8)i); - SDLTest_AssertCheck(0 == _mouseemu_last_event, - "Non-touching button press suppressed: %d", _mouseemu_last_event); - SDL_SendPenButton(0, ptest.ids[0], SDL_RELEASED, (Uint8)i); - SDLTest_AssertCheck(0 == _mouseemu_last_event, - "Non-touching button release suppressed: %d", _mouseemu_last_event); - } - - /* Touch surface */ - SDL_SendPenTipEvent(0, ptest.ids[0], SDL_PRESSED); - _penmouse_expect_button(SDL_PRESSED, 1); - SDL_SendPenTipEvent(0, ptest.ids[0], SDL_RELEASED); - _penmouse_expect_button(SDL_RELEASED, 1); - - /* Test button press reporting, releasing extra button AFTER lifting pen */ - for (i = 1; i <= 2; ++i) { - SDL_SendPenButton(0, ptest.ids[0], SDL_PRESSED, (Uint8)i); - SDLTest_AssertCheck(0 == _mouseemu_last_event, - "Non-touching button press suppressed (A.1): %d", _mouseemu_last_event); - SDL_SendPenTipEvent(0, ptest.ids[0], SDL_PRESSED); - _penmouse_expect_button(SDL_PRESSED, i + 1); - - SDL_SendPenTipEvent(0, ptest.ids[0], SDL_RELEASED); - _penmouse_expect_button(SDL_RELEASED, i + 1); - - SDL_SendPenButton(0, ptest.ids[0], SDL_RELEASED, (Uint8)i); - SDLTest_AssertCheck(0 == _mouseemu_last_event, - "Non-touching button press suppressed (A.2): %d", _mouseemu_last_event); - } - SDLTest_AssertPass("Delayed button press mouse emulation, touching without releasing button"); - - /* Test button press reporting, releasing extra button BEFORE lifting pen */ - for (i = 1; i <= 2; ++i) { - SDL_SendPenButton(0, ptest.ids[0], SDL_PRESSED, (Uint8)i); - SDLTest_AssertCheck(0 == _mouseemu_last_event, - "Non-touching button press suppressed (B.1): %d", _mouseemu_last_event); - SDL_SendPenTipEvent(0, ptest.ids[0], SDL_PRESSED); - _penmouse_expect_button(SDL_PRESSED, i + 1); - - SDL_SendPenButton(0, ptest.ids[0], SDL_RELEASED, (Uint8)i); - SDLTest_AssertCheck(0 == _mouseemu_last_event, - "Non-touching button press suppressed (B.2): %d", _mouseemu_last_event); - SDL_SendPenTipEvent(0, ptest.ids[0], SDL_RELEASED); - _penmouse_expect_button(SDL_RELEASED, i + 1); - } - SDLTest_AssertPass("Delayed button press mouse emulation, touching and then releasing button"); - - /* Cleanup */ - SDL_PenGCMark(); - _pen_trackGCSweep(&ptest); - _teardown_test(&ptest, backup); - return TEST_COMPLETED; -} - -/** - * @brief Ensure that all SDL_Pen*Event structures have compatible memory layout, as expected by SDL_pen.c - */ -static int -pen_memoryLayout(void *arg) -{ -#define LAYOUT_COMPATIBLE(field) \ - SDLTest_AssertCheck(offsetof(SDL_PenTipEvent, field) == offsetof(SDL_PenMotionEvent, field), \ - "Memory layout SDL_PenTipEvent and SDL_PenMotionEvent compatibility: '" #field "'"); \ - SDLTest_AssertCheck(offsetof(SDL_PenTipEvent, field) == offsetof(SDL_PenButtonEvent, field), \ - "Memory layout SDL_PenTipEvent and SDL_PenBUttonEvent compatibility: '" #field "'"); - - LAYOUT_COMPATIBLE(which); - LAYOUT_COMPATIBLE(x); - LAYOUT_COMPATIBLE(y); - LAYOUT_COMPATIBLE(axes); - - return TEST_COMPLETED; -} - -/* ================= Test Setup and Teardown ================== */ - -static void -pen_test_setup(void *arg) { - SDL_PenInit(); -} - -static void -pen_test_teardown(void *arg) { - SDL_PenQuit(); -} - -/* ================= Test References ================== */ - -/* Pen test cases */ -static const SDLTest_TestCaseReference penTest1 = { (SDLTest_TestCaseFp)pen_iteration, "pen_iteration", "Iterate over all pens with SDL_PenIDForIndex", TEST_ENABLED }; - -static const SDLTest_TestCaseReference penTest2 = { (SDLTest_TestCaseFp)pen_hotplugging, "pen_hotplugging", "Hotplug pens and validate their status, including SDL_PenConnected", TEST_ENABLED }; - -static const SDLTest_TestCaseReference penTest3 = { (SDLTest_TestCaseFp)pen_GUIDs, "pen_GUIDs", "Check Pen SDL_GUID operations", TEST_ENABLED }; - -static const SDLTest_TestCaseReference penTest4 = { (SDLTest_TestCaseFp)pen_buttonReporting, "pen_buttonReporting", "Check pen button presses", TEST_ENABLED }; - -static const SDLTest_TestCaseReference penTest5 = { (SDLTest_TestCaseFp)pen_movementAndAxes, "pen_movementAndAxes", "Check pen movement and axis update reporting", TEST_ENABLED }; - -static const SDLTest_TestCaseReference penTest6 = { (SDLTest_TestCaseFp)pen_initAndInfo, "pen_info", "Check pen self-description and initialisation", TEST_ENABLED }; - -static const SDLTest_TestCaseReference penTest7 = { (SDLTest_TestCaseFp)pen_mouseEmulation, "pen_mouseEmulation", "Check pen-as-mouse event forwarding (direct)", TEST_ENABLED }; - -static const SDLTest_TestCaseReference penTest8 = { (SDLTest_TestCaseFp)pen_mouseEmulationDelayed, "pen_mouseEmulationDelayed", "Check pen-as-mouse event forwarding (delayed)", TEST_ENABLED }; - -static const SDLTest_TestCaseReference penTest9 = { (SDLTest_TestCaseFp)pen_memoryLayout, "pen_memoryLayout", "Check that all pen events have compatible layout (required by SDL_pen.c)", TEST_ENABLED }; - -/* Sequence of Pen test cases */ -static const SDLTest_TestCaseReference *penTests[] = { - &penTest1, &penTest2, &penTest3, &penTest4, &penTest5, &penTest6, &penTest7, &penTest8, &penTest9, NULL -}; - -/* Pen test suite (global) */ -SDLTest_TestSuiteReference penTestSuite = { - "Pen", - (SDLTest_TestCaseSetUpFp)pen_test_setup, - penTests, - (SDLTest_TestCaseTearDownFp)pen_test_teardown -}; - -#else - -#include -#include "testautomation_suites.h" - -/* Sequence of Mouse test cases */ -static const SDLTest_TestCaseReference *penTests[] = { - NULL -}; - -/* Mouse test suite (global) */ -SDLTest_TestSuiteReference penTestSuite = { - "Pen", - NULL, - penTests, - NULL -}; - -#endif diff --git a/test/testautomation_suites.h b/test/testautomation_suites.h index db1b98b1c..093bb9b69 100644 --- a/test/testautomation_suites.h +++ b/test/testautomation_suites.h @@ -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; diff --git a/test/testpen.c b/test/testpen.c index 288ec5486..ace0fd656 100644 --- a/test/testpen.c +++ b/test/testpen.c @@ -1,5 +1,4 @@ /* - Simple DirectMedia Layer Copyright (C) 1997-2024 Sam Lantinga 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 +#define SDL_MAIN_USE_CALLBACKS 1 #include #include +#include -#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; + SDL_PenID pen; + Uint8 r, g, b; + float axes[SDL_PEN_NUM_AXES]; + float x; + float y; + Uint32 buttons; + SDL_bool eraser; + SDL_bool touching; + struct Pen *next; +} Pen; - if (!renderer) { - return; - } +static SDL_Renderer *renderer = NULL; +static SDLTest_CommonState *state = NULL; +static SDL_Texture *white_pixel = NULL; +static Pen pens; - 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 SDL_AppInit(void **appstate, int argc, char *argv[]) { 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); - } + SDL_srand(0); - 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]; - 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; - - SDL_GUIDToString(guid, guid_str, sizeof(guid_str)); - - 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) -{ - 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]); - } - } - - 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 1; + return SDL_APP_FAILURE; } - state->window_title = "Pressure-Sensitive Pen Test"; - state->window_w = WIDTH; - state->window_h = HEIGHT; - state->skip_renderer = SDL_FALSE; + /* Enable standard application logging */ + SDL_SetLogPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO); - if (!SDLTest_CommonDefaultArgs(state, argc, argv) || !SDLTest_CommonInit(state)) { - SDLTest_CommonQuit(state); - return 1; + /* 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; } - while (!quitting) { - loop(); + state->num_windows = 1; + + /* 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; } - SDLTest_CommonQuit(state); - return 0; + 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], ¢er, 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], ¢er, 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); +} +