API for pressure-sensitive pens + XInput2/Wayland

This patch adds an API for querying pressure-
sensitive pens, cf. SDL_pen.h:
- Enumerate all pens
- Get pen capabilities, names, GUIDs
- Distinguishes pens and erasers
- Distinguish attached and detached pens
- Pressure and tilt support
- Rotation, distance, throttle wheel support
  (throttle wheel untested)
- Pen type and meta-information reporting
  (partially tested)

Pen event reporting:
- Three new event structures: PenTip, PenMotion, and
  PenButton
- Report location with sub-pixel precision
- Include axis and button status, is-eraser flag

Internal pen tracker, intended to be independent
of platform APIs, cf. SDL_pen_c.h:
- Track known pens
- Handle pen hotplugging

Automatic test:
- testautomation_pen.c

Other features:
- XInput2 implementation, incl. hotplugging
- Wayland implementation, incl. hotplugging
- Backward compatibility: pen events default to
  emulating pens with mouse ID SDL_PEN_MOUSEID
- Can be toggled via SDL_HINT_PEN_NOT_MOUSE
- Test/demo program (testpen)
- Wacom pen feature identification by pen ID

Acknowledgements:
- Ping Cheng (Wacom) provided extensive feedback
  on Wacom pen features and detection so that
  hopefully untested Wacom devices have a
  realistic chance of working out of the box.
This commit is contained in:
Christoph Reichenbach 2023-08-27 06:20:29 +00:00 committed by Sam Lantinga
parent d3e43668d0
commit 7c80ac6df7
36 changed files with 5843 additions and 241 deletions

View File

@ -330,6 +330,7 @@
<ClInclude Include="..\..\include\SDL3\SDL_opengles2_gl2ext.h" />
<ClInclude Include="..\..\include\SDL3\SDL_opengles2_gl2platform.h" />
<ClInclude Include="..\..\include\SDL3\SDL_opengles2_khrplatform.h" />
<ClInclude Include="..\..\include\SDL3\SDL_pen.h" />
<ClInclude Include="..\..\include\SDL3\SDL_pixels.h" />
<ClInclude Include="..\..\include\SDL3\SDL_platform.h" />
<ClInclude Include="..\..\include\SDL3\SDL_platform_defines.h" />
@ -586,6 +587,7 @@
<ClCompile Include="..\..\src\events\SDL_events.c" />
<ClCompile Include="..\..\src\events\SDL_keyboard.c" />
<ClCompile Include="..\..\src\events\SDL_mouse.c" />
<ClCompile Include="..\..\src\events\SDL_pen.c" />
<ClCompile Include="..\..\src\events\SDL_quit.c" />
<ClCompile Include="..\..\src\events\SDL_touch.c" />
<ClCompile Include="..\..\src\events\SDL_windowevents.c" />

View File

@ -300,6 +300,9 @@
<ClInclude Include="..\..\include\SDL3\SDL_opengles2_khrplatform.h">
<Filter>API Headers</Filter>
</ClInclude>
<ClInclude Include="..\..\include\SDL3\SDL_pen.h">
<Filter>API Headers</Filter>
</ClInclude>
<ClInclude Include="..\..\include\SDL3\SDL_pixels.h">
<Filter>API Headers</Filter>
</ClInclude>
@ -940,6 +943,9 @@
<ClCompile Include="..\..\src\events\SDL_mouse.c">
<Filter>events</Filter>
</ClCompile>
<ClCompile Include="..\..\src\events\SDL_pen.c">
<Filter>events</Filter>
</ClCompile>
<ClCompile Include="..\..\src\events\SDL_quit.c">
<Filter>events</Filter>
</ClCompile>

View File

@ -67,6 +67,7 @@
<ClInclude Include="..\include\SDL3\SDL_mouse.h" />
<ClInclude Include="..\include\SDL3\SDL_mutex.h" />
<ClInclude Include="..\include\SDL3\SDL_opengles2.h" />
<ClInclude Include="..\include\SDL3\SDL_pen.h" />
<ClInclude Include="..\include\SDL3\SDL_pixels.h" />
<ClInclude Include="..\include\SDL3\SDL_platform.h" />
<ClInclude Include="..\include\SDL3\SDL_platform_defines.h" />
@ -306,6 +307,7 @@
<ClCompile Include="..\src\events\SDL_events.c" />
<ClCompile Include="..\src\events\SDL_keyboard.c" />
<ClCompile Include="..\src\events\SDL_mouse.c" />
<ClCompile Include="..\src\events\SDL_pen.c" />
<ClCompile Include="..\src\events\SDL_quit.c" />
<ClCompile Include="..\src\events\SDL_touch.c" />
<ClCompile Include="..\src\events\SDL_windowevents.c" />

View File

@ -105,6 +105,9 @@
<ClInclude Include="..\include\SDL3\SDL_opengles2.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\include\SDL3\SDL_pen.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\include\SDL3\SDL_pixels.h">
<Filter>Header Files</Filter>
</ClInclude>
@ -552,6 +555,9 @@
<ClCompile Include="..\src\events\SDL_mouse.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\events\SDL_pen.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\events\SDL_quit.c">
<Filter>Source Files</Filter>
</ClCompile>

View File

@ -280,6 +280,7 @@
<ClInclude Include="..\..\include\SDL3\SDL_opengles2_gl2ext.h" />
<ClInclude Include="..\..\include\SDL3\SDL_opengles2_gl2platform.h" />
<ClInclude Include="..\..\include\SDL3\SDL_opengles2_khrplatform.h" />
<ClInclude Include="..\..\include\SDL3\SDL_pen.h" />
<ClInclude Include="..\..\include\SDL3\SDL_pixels.h" />
<ClInclude Include="..\..\include\SDL3\SDL_platform.h" />
<ClInclude Include="..\..\include\SDL3\SDL_platform_defines.h" />
@ -507,6 +508,7 @@
<ClCompile Include="..\..\src\events\SDL_events.c" />
<ClCompile Include="..\..\src\events\SDL_keyboard.c" />
<ClCompile Include="..\..\src\events\SDL_mouse.c" />
<ClCompile Include="..\..\src\events\SDL_pen.c" />
<ClCompile Include="..\..\src\events\SDL_quit.c" />
<ClCompile Include="..\..\src\events\SDL_touch.c" />
<ClCompile Include="..\..\src\events\SDL_windowevents.c" />

View File

@ -294,6 +294,9 @@
<ClInclude Include="..\..\include\SDL3\SDL_opengles2_khrplatform.h">
<Filter>API Headers</Filter>
</ClInclude>
<ClInclude Include="..\..\include\SDL3\SDL_pen.h">
<Filter>API Headers</Filter>
</ClInclude>
<ClInclude Include="..\..\include\SDL3\SDL_pixels.h">
<Filter>API Headers</Filter>
</ClInclude>
@ -921,6 +924,9 @@
<ClCompile Include="..\..\src\events\SDL_mouse.c">
<Filter>events</Filter>
</ClCompile>
<ClCompile Include="..\..\src\events\SDL_pen.c">
<Filter>events</Filter>
</ClCompile>
<ClCompile Include="..\..\src\events\SDL_quit.c">
<Filter>events</Filter>
</ClCompile>

View File

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

View File

@ -43,6 +43,27 @@
00D0D0D810675E46004B05EF /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 007317C10858E15000B2BC32 /* Carbon.framework */; platformFilters = (macos, ); };
557D0CFA254586CA003913E3 /* CoreHaptics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F37DC5F225350EBC0002E6F7 /* CoreHaptics.framework */; platformFilters = (ios, maccatalyst, macos, tvos, ); };
557D0CFB254586D7003913E3 /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A75FDABD23E28B6200529352 /* GameController.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
63125C002A790B12008EF011 /* SDL_pen.h in Headers */ = {isa = PBXBuildFile; fileRef = 63125BFF2A790B12008EF011 /* SDL_pen.h */; };
63125C012A790B12008EF011 /* SDL_pen.h in Headers */ = {isa = PBXBuildFile; fileRef = 63125BFF2A790B12008EF011 /* SDL_pen.h */; };
63125C022A790B12008EF011 /* SDL_pen.h in Headers */ = {isa = PBXBuildFile; fileRef = 63125BFF2A790B12008EF011 /* SDL_pen.h */; };
63125C0A2A790B69008EF011 /* SDL_pen.c in Sources */ = {isa = PBXBuildFile; fileRef = 63125C092A790B69008EF011 /* SDL_pen.c */; };
63125C0B2A790B69008EF011 /* SDL_pen.c in Sources */ = {isa = PBXBuildFile; fileRef = 63125C092A790B69008EF011 /* SDL_pen.c */; };
63125C0C2A790B69008EF011 /* SDL_pen.c in Sources */ = {isa = PBXBuildFile; fileRef = 63125C092A790B69008EF011 /* SDL_pen.c */; };
63125C0D2A790B69008EF011 /* SDL_pen.c in Sources */ = {isa = PBXBuildFile; fileRef = 63125C092A790B69008EF011 /* SDL_pen.c */; };
63125C0E2A790B69008EF011 /* SDL_pen.c in Sources */ = {isa = PBXBuildFile; fileRef = 63125C092A790B69008EF011 /* SDL_pen.c */; };
63125C0F2A790B69008EF011 /* SDL_pen.c in Sources */ = {isa = PBXBuildFile; fileRef = 63125C092A790B69008EF011 /* SDL_pen.c */; };
63125C102A790B69008EF011 /* SDL_pen.c in Sources */ = {isa = PBXBuildFile; fileRef = 63125C092A790B69008EF011 /* SDL_pen.c */; };
63125C112A790B69008EF011 /* SDL_pen.c in Sources */ = {isa = PBXBuildFile; fileRef = 63125C092A790B69008EF011 /* SDL_pen.c */; };
63125C122A790B69008EF011 /* SDL_pen.c in Sources */ = {isa = PBXBuildFile; fileRef = 63125C092A790B69008EF011 /* SDL_pen.c */; };
63125C142A790B9A008EF011 /* SDL_pen_c.h in Headers */ = {isa = PBXBuildFile; fileRef = 63125C132A790B9A008EF011 /* SDL_pen_c.h */; };
63125C152A790B9A008EF011 /* SDL_pen_c.h in Headers */ = {isa = PBXBuildFile; fileRef = 63125C132A790B9A008EF011 /* SDL_pen_c.h */; };
63125C162A790B9A008EF011 /* SDL_pen_c.h in Headers */ = {isa = PBXBuildFile; fileRef = 63125C132A790B9A008EF011 /* SDL_pen_c.h */; };
63125C172A790B9A008EF011 /* SDL_pen_c.h in Headers */ = {isa = PBXBuildFile; fileRef = 63125C132A790B9A008EF011 /* SDL_pen_c.h */; };
63125C182A790B9A008EF011 /* SDL_pen_c.h in Headers */ = {isa = PBXBuildFile; fileRef = 63125C132A790B9A008EF011 /* SDL_pen_c.h */; };
63125C192A790B9A008EF011 /* SDL_pen_c.h in Headers */ = {isa = PBXBuildFile; fileRef = 63125C132A790B9A008EF011 /* SDL_pen_c.h */; };
63125C1A2A790B9A008EF011 /* SDL_pen_c.h in Headers */ = {isa = PBXBuildFile; fileRef = 63125C132A790B9A008EF011 /* SDL_pen_c.h */; };
63125C1B2A790B9A008EF011 /* SDL_pen_c.h in Headers */ = {isa = PBXBuildFile; fileRef = 63125C132A790B9A008EF011 /* SDL_pen_c.h */; };
63125C1C2A790B9A008EF011 /* SDL_pen_c.h in Headers */ = {isa = PBXBuildFile; fileRef = 63125C132A790B9A008EF011 /* SDL_pen_c.h */; };
5616CA4C252BB2A6005D5928 /* SDL_url.c in Sources */ = {isa = PBXBuildFile; fileRef = 5616CA49252BB2A5005D5928 /* SDL_url.c */; };
5616CA4D252BB2A6005D5928 /* SDL_sysurl.h in Headers */ = {isa = PBXBuildFile; fileRef = 5616CA4A252BB2A6005D5928 /* SDL_sysurl.h */; };
5616CA4E252BB2A6005D5928 /* SDL_sysurl.m in Sources */ = {isa = PBXBuildFile; fileRef = 5616CA4B252BB2A6005D5928 /* SDL_sysurl.m */; };
@ -52,6 +73,9 @@
566E26D8246274CC00718109 /* SDL_locale.c in Sources */ = {isa = PBXBuildFile; fileRef = 566E26CD246274CB00718109 /* SDL_locale.c */; };
566E26E1246274CC00718109 /* SDL_syslocale.h in Headers */ = {isa = PBXBuildFile; fileRef = 566E26CE246274CC00718109 /* SDL_syslocale.h */; };
56A2373329F9C113003CCA5F /* SDL_sysrwlock.c in Sources */ = {isa = PBXBuildFile; fileRef = 56A2373229F9C113003CCA5F /* SDL_sysrwlock.c */; };
63134A222A7902CF0021E9A6 /* SDL_pen.h in Headers */ = {isa = PBXBuildFile; fileRef = 63134A212A7902CF0021E9A6 /* SDL_pen.h */; };
63134A252A7902FD0021E9A6 /* SDL_pen_c.h in Headers */ = {isa = PBXBuildFile; fileRef = 63134A232A7902FD0021E9A6 /* SDL_pen_c.h */; };
63134A262A7902FD0021E9A6 /* SDL_pen.c in Sources */ = {isa = PBXBuildFile; fileRef = 63134A242A7902FD0021E9A6 /* SDL_pen.c */; };
75E0915A241EA924004729E1 /* SDL_virtualjoystick.c in Sources */ = {isa = PBXBuildFile; fileRef = 75E09158241EA924004729E1 /* SDL_virtualjoystick.c */; };
75E09163241EA924004729E1 /* SDL_virtualjoystick_c.h in Headers */ = {isa = PBXBuildFile; fileRef = 75E09159241EA924004729E1 /* SDL_virtualjoystick_c.h */; };
9846B07C287A9020000C35C8 /* SDL_hidapi_shield.c in Sources */ = {isa = PBXBuildFile; fileRef = 9846B07B287A9020000C35C8 /* SDL_hidapi_shield.c */; };
@ -534,6 +558,9 @@
566E26CD246274CB00718109 /* SDL_locale.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = SDL_locale.c; path = locale/SDL_locale.c; sourceTree = "<group>"; };
566E26CE246274CC00718109 /* SDL_syslocale.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDL_syslocale.h; path = locale/SDL_syslocale.h; sourceTree = "<group>"; };
56A2373229F9C113003CCA5F /* SDL_sysrwlock.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_sysrwlock.c; sourceTree = "<group>"; };
63134A212A7902CF0021E9A6 /* SDL_pen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDL_pen.h; path = SDL3/SDL_pen.h; sourceTree = "<group>"; };
63134A232A7902FD0021E9A6 /* SDL_pen_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_pen_c.h; sourceTree = "<group>"; };
63134A242A7902FD0021E9A6 /* SDL_pen.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_pen.c; sourceTree = "<group>"; };
75E09158241EA924004729E1 /* SDL_virtualjoystick.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_virtualjoystick.c; sourceTree = "<group>"; };
75E09159241EA924004729E1 /* SDL_virtualjoystick_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_virtualjoystick_c.h; sourceTree = "<group>"; };
9846B07B287A9020000C35C8 /* SDL_hidapi_shield.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_shield.c; sourceTree = "<group>"; };
@ -1093,6 +1120,7 @@
F3F7D8C92933074B00816151 /* SDL_opengles2_gl2platform.h */,
F3F7D8B12933074900816151 /* SDL_opengles2_khrplatform.h */,
F3F7D8C72933074B00816151 /* SDL_opengles2.h */,
63134A212A7902CF0021E9A6 /* SDL_pen.h */,
F3F7D8B52933074A00816151 /* SDL_pixels.h */,
F3B38CCB296E2E52005DA6D3 /* SDL_platform_defines.h */,
F3F7D8AB2933074900816151 /* SDL_platform.h */,
@ -2059,6 +2087,8 @@
A7D8A93823E2514000DCD162 /* SDL_keyboard.c */,
A7D8A92B23E2514000DCD162 /* SDL_mouse_c.h */,
A7D8A92A23E2514000DCD162 /* SDL_mouse.c */,
63134A232A7902FD0021E9A6 /* SDL_pen_c.h */,
63134A242A7902FD0021E9A6 /* SDL_pen.c */,
A7D8A93C23E2514000DCD162 /* SDL_quit.c */,
A7D8A93723E2514000DCD162 /* SDL_touch_c.h */,
A7D8A93E23E2514000DCD162 /* SDL_touch.c */,
@ -2365,6 +2395,8 @@
A7D8B3D423E2514300DCD162 /* yuv_rgb.h in Headers */,
A7D8B3C823E2514200DCD162 /* yuv_rgb_sse_func.h in Headers */,
A7D8B3CE23E2514300DCD162 /* yuv_rgb_std_func.h in Headers */,
63134A222A7902CF0021E9A6 /* SDL_pen.h in Headers */,
63134A252A7902FD0021E9A6 /* SDL_pen_c.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -2685,6 +2717,7 @@
A7D8AEA023E2514100DCD162 /* SDL_cocoavulkan.m in Sources */,
A7D8AB6123E2514100DCD162 /* SDL_offscreenwindow.c in Sources */,
566E26D8246274CC00718109 /* SDL_locale.c in Sources */,
63134A262A7902FD0021E9A6 /* SDL_pen.c in Sources */,
000040E76FDC6AE48CBF0000 /* SDL_hashtable.c in Sources */,
0000A4DA2F45A31DC4F00000 /* SDL_sysmain_callbacks.m in Sources */,
000028F8113A53F4333E0000 /* SDL_main_callbacks.c in Sources */,

View File

@ -34,6 +34,7 @@
#include <SDL3/SDL_joystick.h>
#include <SDL3/SDL_keyboard.h>
#include <SDL3/SDL_mouse.h>
#include <SDL3/SDL_pen.h>
#include <SDL3/SDL_quit.h>
#include <SDL3/SDL_stdinc.h>
#include <SDL3/SDL_touch.h>
@ -113,6 +114,8 @@ typedef enum
SDL_EVENT_WINDOW_RESTORED, /**< Window has been restored to normal size and position */
SDL_EVENT_WINDOW_MOUSE_ENTER, /**< Window has gained mouse focus */
SDL_EVENT_WINDOW_MOUSE_LEAVE, /**< Window has lost mouse focus */
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_FOCUS_GAINED, /**< Window has gained keyboard focus */
SDL_EVENT_WINDOW_FOCUS_LOST, /**< Window has lost keyboard focus */
SDL_EVENT_WINDOW_CLOSE_REQUESTED, /**< The window manager requests that the window be closed */
@ -191,6 +194,13 @@ typedef enum
/* Sensor events */
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_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 */
/* Render events */
SDL_EVENT_RENDER_TARGETS_RESET = 0x2000, /**< The render targets have been reset and their contents need to be updated */
SDL_EVENT_RENDER_DEVICE_RESET, /**< The device has been reset and all textures need to be recreated */
@ -296,7 +306,7 @@ typedef struct SDL_MouseMotionEvent
Uint32 type; /**< ::SDL_EVENT_MOUSE_MOTION */
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, or SDL_TOUCH_MOUSEID */
SDL_MouseID which; /**< The mouse instance id, SDL_TOUCH_MOUSEID, or SDL_PEN_MOUSEID */
Uint32 state; /**< The current button state */
float x; /**< X coordinate, relative to window */
float y; /**< Y coordinate, relative to window */
@ -312,7 +322,7 @@ typedef struct SDL_MouseButtonEvent
Uint32 type; /**< ::SDL_EVENT_MOUSE_BUTTON_DOWN or ::SDL_EVENT_MOUSE_BUTTON_UP */
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, or SDL_TOUCH_MOUSEID */
SDL_MouseID which; /**< The mouse instance id, SDL_TOUCH_MOUSEID, or SDL_PEN_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. */
@ -329,7 +339,7 @@ typedef struct SDL_MouseWheelEvent
Uint32 type; /**< ::SDL_EVENT_MOUSE_WHEEL */
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, or SDL_TOUCH_MOUSEID */
SDL_MouseID which; /**< The mouse instance id, SDL_TOUCH_MOUSEID, or SDL_PEN_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 */
Uint32 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 */
@ -512,6 +522,63 @@ typedef struct SDL_TouchFingerEvent
#define SDL_DROPEVENT_DATA_SIZE 64
/**
* Pressure-sensitive pen touched or stopped touching surface (event.ptip.*)
*/
typedef struct SDL_PenTipEvent
{
Uint32 type; /**< ::SDL_EVENT_PEN_DOWN or ::SDL_EVENT_PEN_UP */
Uint64 timestamp; /**< In nanoseconds, populated using SDL_GetTicksNS() */
Uint32 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.*)
*/
typedef struct SDL_PenMotionEvent
{
Uint32 type; /**< ::SDL_EVENT_PEN_MOTION */
Uint64 timestamp; /**< In nanoseconds, populated using SDL_GetTicksNS() */
Uint32 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;
/**
* Pressure-sensitive pen button event structure (event.pbutton.*)
*/
typedef struct SDL_PenButtonEvent
{
Uint32 type; /**< ::SDL_EVENT_PEN_BUTTON_DOWN or ::SDL_EVENT_PEN_BUTTON_UP */
Uint64 timestamp; /**< In nanoseconds, populated using SDL_GetTicksNS() */
Uint32 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;
/**
* An event used to drop text or request a file open by the system (event.drop.*)
*
@ -603,6 +670,9 @@ 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_DropEvent drop; /**< Drag and drop event data */
SDL_ClipboardEvent clipboard; /**< Clipboard event data */

View File

@ -1220,6 +1220,40 @@ extern "C" {
*/
#define SDL_HINT_MOUSE_AUTO_CAPTURE "SDL_MOUSE_AUTO_CAPTURE"
/**
* Treat pen movement as separate from mouse movement
*
* By default, pens report both ::SDL_MouseMotionEvent and ::SDL_PenMotionEvent updates
* (analogously for button presses). This hint allows decoupling mouse and pen updates.
*
* This variable toggles between the following behaviour:
* "0" - (Default) Pen acts as a mouse with mouse ID ::SDL_PEN_MOUSEID.
* Use case: client application is not pen aware, user wants to
* use pen instead of mouse to interact.
* "1" - Pen reports mouse clicks and movement events but does not update
* SDL-internal mouse state (buttons pressed, current mouse location).
* Use case: client application is not pen aware, user frequently
* alternates between pen and "real" mouse.
* "2" - Pen reports no mouse events.
* Use case: pen-aware client application uses this hint to allow user to
* toggle between pen+mouse mode ("2") and pen-only mode ("1" or "0").
*/
#define SDL_HINT_PEN_NOT_MOUSE "SDL_HINT_PEN_NOT_MOUSE"
/**
* Pen mouse button emulation triggers only when the pen touches the tablet surface
*
* "0" - The pen reports mouse button press/release immediately when the pen
* button is pressed/released, and the pen tip touching the surface counts
* as left mouse button press.
* "1" - (Default) Mouse button presses are sent when the pen first touches
* the tablet (analogously for releases). Not pressing a pen button
* simulates mouse button 1, pressing the first pen button simulates
* mouse button 2 etc.; it is not possible to report multiple buttons
* as pressed at the same time.
*/
#define SDL_HINT_PEN_DELAY_MOUSE_BUTTON "SDL_HINT_PEN_DELAY_MOUSE_BUTTON"
/**
* Tell SDL not to catch the SIGINT or SIGTERM signals.
*

285
include/SDL3/SDL_pen.h Normal file
View File

@ -0,0 +1,285 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
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.
*/
/**
* \file SDL_pen.h
*
* Include file for SDL pen event handling.
*
* This file describes operations 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:
* - 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.
*
* This header file describes advanced functionality that can be useful for managing user configuration
* and understanding the capabilities of the attached pens.
*
* 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 .
*/
#ifndef SDL_pen_h_
#define SDL_pen_h_
#include "SDL_error.h"
#include "SDL_guid.h"
#include "SDL_stdinc.h"
/* Set up for C function definitions, even when using C++ */
#ifdef __cplusplus
extern "C" {
#endif
typedef Uint32 SDL_PenID; /**< SDL_PenIDs identify pens uniquely within a session */
#define SDL_PEN_INVALID ((Uint32)0) /**< Reserved invalid ::SDL_PenID is valid */
#define SDL_PEN_MOUSEID ((Uint32)-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
*
* 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".
*
* To convert angles for tilt and rotation into vector representation, use
* \link SDL_sinf \endlink on the XTILT, YTILT, or ROTATION component, e.g., "SDL_sinf(xtilt * SDL_PI_F / 180.0)".
*/
typedef enum
{
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. \link SDL_PenCapabilityInfo \endlink */
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. \link SDL_PenCapabilityInfo \endlink */
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_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 */
/** @} */
/**
* \defgroup SDL_PEN_CAPABILITIES Pen capabilities
* Pen capabilities reported by ::SDL_GetPenCapabilities
* @{
*/
#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)
/**< Masks for all axes that may be bidirectional */
/** @} */
/**
* Pen types
*
* Some pens identify as a particular type of drawing device (e.g., an airbrush or a pencil).
*
*/
typedef enum
{
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[out] 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 error.
* The array must be freed with ::SDL_free().
* On a NULL return, ::SDL_GetError() is set.
*
* \since This function is available since SDL 3.TBD
*/
extern 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[out] x Out-mode parameter for pen x coordinate. May be NULL.
* \param[out] y Out-mode parameter for pen y coordinate. May be NULL.
* \param[out] axes Out-mode parameter for axis information. May be null. The axes are in the same order as for
* ::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.TBD
*/
extern 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.TBD
*
* \sa SDL_GUID()
*/
extern 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.TBD
*
* \sa SDL_PenForID()
*/
extern 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.TBD
*/
extern 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.TBD
*/
extern DECLSPEC const char *SDLCALL SDL_GetPenName(SDL_PenID instance_id);
/**
* Pen capabilities, as reported by ::SDL_GetPenCapabilities()
*/
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[out] capabilities Detail information about pen capabilities, such as the number of buttons
*
* \returns a set of capability flags, cf. \link SDL_PEN_CAPABILITIES \endlink. Returns 0 on error
* (cf. ::SDL_GetError())
*
* \since This function is available since SDL 3.TBD
*/
extern DECLSPEC Uint32 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.TBD
*/
extern DECLSPEC SDL_PenSubtype SDLCALL SDL_GetPenType(SDL_PenID instance_id);
/* Ends C function definitions when using C++ */
#ifdef __cplusplus
}
#endif
#endif /* SDL_pen_h_ */
/* vi: set ts=4 sw=4 expandtab: */

View File

@ -940,6 +940,22 @@ SDL3_0.0.0 {
SDL_GetVideoCaptureDevices;
SDL_GetGamepadButtonLabelForType;
SDL_GetGamepadButtonLabel;
SDL_GetPens;
SDL_GetPenStatus;
SDL_GetPenFromGUID;
SDL_GetPenGUID;
SDL_PenConnected;
SDL_GetPenName;
SDL_GetPenCapabilities;
SDL_GetPenType;
SDL_GetPens;
SDL_GetPenStatus;
SDL_GetPenFromGUID;
SDL_GetPenGUID;
SDL_PenConnected;
SDL_GetPenName;
SDL_GetPenCapabilities;
SDL_GetPenType;
# extra symbols go here (don't modify this line)
local: *;
};

View File

@ -965,3 +965,19 @@
#define SDL_GetVideoCaptureDevices SDL_GetVideoCaptureDevices_REAL
#define SDL_GetGamepadButtonLabelForType SDL_GetGamepadButtonLabelForType_REAL
#define SDL_GetGamepadButtonLabel SDL_GetGamepadButtonLabel_REAL
#define SDL_GetPens SDL_GetPens_REAL
#define SDL_GetPenStatus SDL_GetPenStatus_REAL
#define SDL_GetPenFromGUID SDL_GetPenFromGUID_REAL
#define SDL_GetPenGUID SDL_GetPenGUID_REAL
#define SDL_PenConnected SDL_PenConnected_REAL
#define SDL_GetPenName SDL_GetPenName_REAL
#define SDL_GetPenCapabilities SDL_GetPenCapabilities_REAL
#define SDL_GetPenType SDL_GetPenType_REAL
#define SDL_GetPens SDL_GetPens_REAL
#define SDL_GetPenStatus SDL_GetPenStatus_REAL
#define SDL_GetPenFromGUID SDL_GetPenFromGUID_REAL
#define SDL_GetPenGUID SDL_GetPenGUID_REAL
#define SDL_PenConnected SDL_PenConnected_REAL
#define SDL_GetPenName SDL_GetPenName_REAL
#define SDL_GetPenCapabilities SDL_GetPenCapabilities_REAL
#define SDL_GetPenType SDL_GetPenType_REAL

View File

@ -998,3 +998,11 @@ SDL_DYNAPI_PROC(void,SDL_CloseVideoCapture,(SDL_VideoCaptureDevice *a),(a),)
SDL_DYNAPI_PROC(SDL_VideoCaptureDeviceID*,SDL_GetVideoCaptureDevices,(int *a),(a),return)
SDL_DYNAPI_PROC(SDL_GamepadButtonLabel,SDL_GetGamepadButtonLabelForType,(SDL_GamepadType a, SDL_GamepadButton b),(a,b),return)
SDL_DYNAPI_PROC(SDL_GamepadButtonLabel,SDL_GetGamepadButtonLabel,(SDL_Gamepad *a, SDL_GamepadButton b),(a,b),return)
SDL_DYNAPI_PROC(SDL_PenID*,SDL_GetPens,(int *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_PenID,SDL_GetPenFromGUID,(SDL_GUID a),(a),return)
SDL_DYNAPI_PROC(SDL_GUID,SDL_GetPenGUID,(SDL_PenID a),(a),return)
SDL_DYNAPI_PROC(SDL_bool,SDL_PenConnected,(SDL_PenID a),(a),return)
SDL_DYNAPI_PROC(const char*,SDL_GetPenName,(SDL_PenID a),(a),return)
SDL_DYNAPI_PROC(Uint32,SDL_GetPenCapabilities,(SDL_PenID a, SDL_PenCapabilityInfo *b),(a,b),return)
SDL_DYNAPI_PROC(SDL_PenSubtype,SDL_GetPenType,(SDL_PenID a),(a),return)

View File

@ -379,7 +379,7 @@ def check_comment():
if header != 'SDL_stdinc.h':
parameter_name = i['parameter_name']
for n in parameter_name:
if n != "" and "\\param " + n not in comment:
if n != "" and "\\param " + n not in comment and "\\param[out] " + n not in comment:
check_comment_header()
print(" In file %s: function %s() missing '\\param %s'" % (header, name, n));

View File

@ -192,7 +192,7 @@ static void SDLCALL SDL_PollSentinelChanged(void *userdata, const char *name, co
* Verbosity of logged events as defined in SDL_HINT_EVENT_LOGGING:
* - 0: (default) no logging
* - 1: logging of most events
* - 2: as above, plus mouse and finger motion
* - 2: as above, plus mouse, pen, and finger motion
*/
static int SDL_EventLoggingVerbosity = 0;
@ -206,10 +206,11 @@ static void SDL_LogEvent(const SDL_Event *event)
char name[64];
char details[128];
/* sensor/mouse/finger motion are spammy, ignore these if they aren't demanded. */
/* sensor/mouse/pen/finger motion are spammy, ignore these if they aren't demanded. */
if ((SDL_EventLoggingVerbosity < 2) &&
((event->type == SDL_EVENT_MOUSE_MOTION) ||
(event->type == SDL_EVENT_FINGER_MOTION) ||
(event->type == SDL_EVENT_PEN_MOTION) ||
(event->type == SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION) ||
(event->type == SDL_EVENT_GAMEPAD_SENSOR_UPDATE) ||
(event->type == SDL_EVENT_SENSOR_UPDATE))) {
@ -302,6 +303,8 @@ 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);
@ -470,6 +473,53 @@ 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)
SDL_EVENT_CASE(SDL_EVENT_PEN_DOWN)
PRINT_PTIP_EVENT(event);
break;
SDL_EVENT_CASE(SDL_EVENT_PEN_UP)
PRINT_PTIP_EVENT(event);
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]);
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])
SDL_EVENT_CASE(SDL_EVENT_PEN_BUTTON_DOWN)
PRINT_PBUTTON_EVENT(event);
break;
SDL_EVENT_CASE(SDL_EVENT_PEN_BUTTON_UP)
PRINT_PBUTTON_EVENT(event);
break;
#undef PRINT_PBUTTON_EVENT
#define PRINT_DROP_EVENT(event) (void)SDL_snprintf(details, sizeof(details), " (data='%s' timestamp=%u windowid=%u x=%f y=%f)", event->drop.data, (uint)event->drop.timestamp, (uint)event->drop.windowID, event->drop.x, event->drop.y)
SDL_EVENT_CASE(SDL_EVENT_DROP_FILE)
PRINT_DROP_EVENT(event);

View File

@ -22,9 +22,11 @@
/* General mouse handling code for SDL */
#include "SDL_events_c.h"
#include "../SDL_hints_c.h"
#include "../video/SDL_sysvideo.h"
#include "SDL_events_c.h"
#include "SDL_mouse_c.h"
#include "SDL_pen_c.h"
#if defined(__WIN32__) || defined(__GDK__)
#include "../core/windows/SDL_windows.h" // For GetDoubleClickTime()
#endif
@ -221,6 +223,8 @@ void SDL_PostInitMouse(void)
SDL_DestroySurface(surface);
}
}
SDL_PenInit();
}
void SDL_SetDefaultCursor(SDL_Cursor *cursor)
@ -351,17 +355,25 @@ void SDL_SetMouseFocus(SDL_Window *window)
SDL_SetCursor(NULL);
}
SDL_bool SDL_MousePositionInWindow(SDL_Window *window, SDL_MouseID mouseID, float x, float y)
{
if (!window) {
return SDL_FALSE;
}
if (window && !(window->flags & SDL_WINDOW_MOUSE_CAPTURE)) {
if (x < 0.0f || y < 0.0f || x >= (float)window->w || y >= (float)window->h) {
return SDL_FALSE;
}
}
return SDL_TRUE;
}
/* Check to see if we need to synthesize focus events */
static SDL_bool SDL_UpdateMouseFocus(SDL_Window *window, float x, float y, Uint32 buttonstate, SDL_bool send_mouse_motion)
{
SDL_Mouse *mouse = SDL_GetMouse();
SDL_bool inWindow = SDL_TRUE;
if (window && !(window->flags & SDL_WINDOW_MOUSE_CAPTURE)) {
if (x < 0.0f || y < 0.0f || x >= (float)window->w || y >= (float)window->h) {
inWindow = SDL_FALSE;
}
}
SDL_bool inWindow = SDL_MousePositionInWindow(window, mouse->mouseID, x, y);
if (!inWindow) {
if (window == mouse->focus) {

View File

@ -163,6 +163,9 @@ extern void SDL_PerformWarpMouseInWindow(SDL_Window *window, float x, float y, S
extern void SDL_ResetMouse(void);
#endif /* 0 */
/* Check if mouse position is within window or captured by window */
extern SDL_bool SDL_MousePositionInWindow(SDL_Window *window, SDL_MouseID mouseID, float x, float y);
/* Shutdown the mouse subsystem */
extern void SDL_QuitMouse(void);

1083
src/events/SDL_pen.c Normal file

File diff suppressed because it is too large Load Diff

336
src/events/SDL_pen_c.h Normal file
View File

@ -0,0 +1,336 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
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.
*/
#include "../SDL_internal.h"
#ifndef SDL_pen_c_h_
#define SDL_pen_c_h_
#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 */
#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
{
float x, y;
float axes[SDL_PEN_NUM_AXES];
Uint32 buttons; /* SDL_BUTTON(1) | SDL_BUTTON(2) | ... | SDL_PEN_DOWN_MASK */
} SDL_PenStatusInfo;
/**
* 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: */
struct SDL_Pen_header
{
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 */
} header;
SDL_PenStatusInfo last; /* Last reported status, normally read-only for backend */
/* 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: 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. */
void *deviceinfo; /* implementation-specific information */
} SDL_Pen;
/* ---- API for backend driver only ---- */
/**
* (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);
/**
* (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);
/**
* (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);
/**
* 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);
/**
* 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);
/**
* 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);
#endif /* SDL_pen_c_h_ */
/* vi: set ts=4 sw=4 expandtab: */

View File

@ -572,13 +572,15 @@ static void pointer_handle_leave(void *data, struct wl_pointer *pointer,
}
}
static SDL_bool ProcessHitTest(struct SDL_WaylandInput *input, uint32_t serial)
static SDL_bool ProcessHitTest(SDL_WindowData *window_data,
struct wl_seat *seat,
wl_fixed_t sx_w, wl_fixed_t sy_w,
uint32_t serial)
{
SDL_WindowData *window_data = input->pointer_focus;
SDL_Window *window = window_data->sdlwindow;
if (window->hit_test) {
const SDL_Point point = { wl_fixed_to_int(input->sx_w), wl_fixed_to_int(input->sy_w) };
const SDL_Point point = { wl_fixed_to_int(sx_w), wl_fixed_to_int(sy_w) };
const SDL_HitTestResult rc = window->hit_test(window, &point, window->hit_test_data);
static const uint32_t directions[] = {
@ -602,14 +604,14 @@ static SDL_bool ProcessHitTest(struct SDL_WaylandInput *input, uint32_t serial)
#ifdef HAVE_LIBDECOR_H
if (window_data->shell_surface_type == WAYLAND_SURFACE_LIBDECOR) {
if (window_data->shell_surface.libdecor.frame) {
libdecor_frame_move(window_data->shell_surface.libdecor.frame, input->seat, serial);
libdecor_frame_move(window_data->shell_surface.libdecor.frame, seat, serial);
}
} else
#endif
if (window_data->shell_surface_type == WAYLAND_SURFACE_XDG_TOPLEVEL) {
if (window_data->shell_surface.xdg.roleobj.toplevel) {
xdg_toplevel_move(window_data->shell_surface.xdg.roleobj.toplevel,
input->seat,
seat,
serial);
}
}
@ -626,14 +628,14 @@ static SDL_bool ProcessHitTest(struct SDL_WaylandInput *input, uint32_t serial)
#ifdef HAVE_LIBDECOR_H
if (window_data->shell_surface_type == WAYLAND_SURFACE_LIBDECOR) {
if (window_data->shell_surface.libdecor.frame) {
libdecor_frame_resize(window_data->shell_surface.libdecor.frame, input->seat, serial, directions_libdecor[rc - SDL_HITTEST_RESIZE_TOPLEFT]);
libdecor_frame_resize(window_data->shell_surface.libdecor.frame, seat, serial, directions_libdecor[rc - SDL_HITTEST_RESIZE_TOPLEFT]);
}
} else
#endif
if (window_data->shell_surface_type == WAYLAND_SURFACE_XDG_TOPLEVEL) {
if (window_data->shell_surface.xdg.roleobj.toplevel) {
xdg_toplevel_resize(window_data->shell_surface.xdg.roleobj.toplevel,
input->seat,
seat,
serial,
directions[rc - SDL_HITTEST_RESIZE_TOPLEFT]);
}
@ -660,7 +662,7 @@ static void pointer_handle_button_common(struct SDL_WaylandInput *input, uint32_
switch (button) {
case BTN_LEFT:
sdl_button = SDL_BUTTON_LEFT;
if (ProcessHitTest(input, serial)) {
if (ProcessHitTest(input->pointer_focus, input->seat, input->sx_w, input->sy_w, serial)) {
return; /* don't pass this event on to app. */
}
break;
@ -832,19 +834,19 @@ static void pointer_handle_axis(void *data, struct wl_pointer *pointer,
}
static void pointer_handle_axis_relative_direction(void *data, struct wl_pointer *pointer,
uint32_t axis, uint32_t axis_relative_direction)
uint32_t axis, uint32_t axis_relative_direction)
{
struct SDL_WaylandInput *input = data;
if (axis != WL_POINTER_AXIS_VERTICAL_SCROLL) {
return;
}
switch (axis_relative_direction) {
case WL_POINTER_AXIS_RELATIVE_DIRECTION_IDENTICAL:
input->pointer_curr_axis_info.direction = SDL_MOUSEWHEEL_NORMAL;
break;
case WL_POINTER_AXIS_RELATIVE_DIRECTION_INVERTED:
input->pointer_curr_axis_info.direction = SDL_MOUSEWHEEL_FLIPPED;
break;
case WL_POINTER_AXIS_RELATIVE_DIRECTION_IDENTICAL:
input->pointer_curr_axis_info.direction = SDL_MOUSEWHEEL_NORMAL;
break;
case WL_POINTER_AXIS_RELATIVE_DIRECTION_INVERTED:
input->pointer_curr_axis_info.direction = SDL_MOUSEWHEEL_FLIPPED;
break;
}
}
@ -928,11 +930,11 @@ static const struct wl_pointer_listener pointer_listener = {
pointer_handle_motion,
pointer_handle_button,
pointer_handle_axis,
pointer_handle_frame, /* Version 5 */
pointer_handle_axis_source, /* Version 5 */
pointer_handle_axis_stop, /* Version 5 */
pointer_handle_axis_discrete, /* Version 5 */
pointer_handle_axis_value120, /* Version 8 */
pointer_handle_frame, /* Version 5 */
pointer_handle_axis_source, /* Version 5 */
pointer_handle_axis_stop, /* Version 5 */
pointer_handle_axis_discrete, /* Version 5 */
pointer_handle_axis_value120, /* Version 8 */
pointer_handle_axis_relative_direction /* Version 9 */
};
@ -2361,40 +2363,202 @@ void Wayland_add_text_input_manager(SDL_VideoData *d, uint32_t id, uint32_t vers
}
}
static SDL_PenID Wayland_get_penid(void *data, struct zwp_tablet_tool_v2 *tool)
{
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;
}
static void tablet_tool_handle_type(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t type)
{
/* unimplemented */
SDL_Pen *pen = Wayland_get_current_pen(data, tool);
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 */
}
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)
{
/* unimplemented */
#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
}
static void tablet_tool_handle_hardware_id_wacom(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t id_hi, uint32_t id_lo)
{
/* unimplemented */
#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
}
static void tablet_tool_handle_capability(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t capability)
{
/* unimplemented */
SDL_Pen *pen = Wayland_get_current_pen(data, tool);
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;
}
}
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)
{
/* unimplemented */
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)
{
/* unimplemented */
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);
}
tablet_object_list_remove(input->tools, tool);
}
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_WaylandTabletInput *input = data;
struct SDL_WaylandTool *sdltool = data;
struct SDL_WaylandTabletInput *input = sdltool->tablet;
SDL_WindowData *window;
SDL_PenID penid = Wayland_get_penid(data, tool);
if (!surface) {
return;
@ -2410,155 +2574,214 @@ static void tablet_tool_handle_proximity_in(void *data, struct zwp_tablet_tool_v
input->tool_focus = window;
input->tool_prox_serial = serial;
input->is_down = SDL_FALSE;
input->btn_stylus = SDL_FALSE;
input->btn_stylus2 = SDL_FALSE;
input->btn_stylus3 = SDL_FALSE;
SDL_SetMouseFocus(window->sdlwindow);
if (penid) {
SDL_SendPenWindowEvent(0, penid, window->sdlwindow);
} else {
SDL_SetMouseFocus(window->sdlwindow);
}
SDL_SetCursor(NULL);
}
}
static void tablet_tool_handle_proximity_out(void *data, struct zwp_tablet_tool_v2 *tool)
{
struct SDL_WaylandTabletInput *input = data;
struct SDL_WaylandTool *sdltool = data;
struct SDL_WaylandTabletInput *input = sdltool->tablet;
SDL_PenID penid = Wayland_get_penid(data, tool);
if (input->tool_focus) {
SDL_SetMouseFocus(NULL);
if (penid) {
SDL_SendPenWindowEvent(0, penid, NULL);
} else {
SDL_SetMouseFocus(NULL);
}
input->tool_focus = NULL;
}
}
static uint32_t tablet_tool_btn_to_sdl_button(struct SDL_WaylandTabletInput *input)
{
unsigned int tool_btn = input->btn_stylus3 << 2 | input->btn_stylus2 << 1 | input->btn_stylus << 0;
switch (tool_btn) {
case 0b000:
return SDL_BUTTON_LEFT;
case 0b001:
return SDL_BUTTON_RIGHT;
case 0b010:
return SDL_BUTTON_MIDDLE;
case 0b100:
return SDL_BUTTON_X1;
default:
return SDL_BUTTON_LEFT;
}
}
static void tablet_tool_handle_down(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t serial)
{
struct SDL_WaylandTabletInput *input = data;
SDL_WindowData *window = input->tool_focus;
input->is_down = SDL_TRUE;
Wayland_UpdateImplicitGrabSerial(input->sdlWaylandInput, serial);
if (!window) {
/* tablet_tool_handle_proximity_out gets called when moving over the libdecoration csd.
* that sets input->tool_focus (window) to NULL, but handle_{down,up} events are still
* received. To prevent SIGSEGV this returns when this is the case.
*/
return;
}
struct SDL_WaylandTool *sdltool = data;
struct SDL_WaylandTabletInput *input = sdltool->tablet;
SDL_SendMouseButton(0, window->sdlwindow, 0, SDL_PRESSED, tablet_tool_btn_to_sdl_button(input));
input->current_pen.buttons_pressed |= SDL_PEN_DOWN_MASK;
input->current_pen.serial = serial;
}
static void tablet_tool_handle_up(void *data, struct zwp_tablet_tool_v2 *tool)
{
struct SDL_WaylandTabletInput *input = data;
SDL_WindowData *window = input->tool_focus;
input->is_down = SDL_FALSE;
if (!window) {
/* tablet_tool_handle_proximity_out gets called when moving over the libdecoration csd.
* that sets input->tool_focus (window) to NULL, but handle_{down,up} events are still
* received. To prevent SIGSEGV this returns when this is the case.
*/
return;
}
SDL_SendMouseButton(0, window->sdlwindow, 0, SDL_RELEASED, tablet_tool_btn_to_sdl_button(input));
struct SDL_WaylandTool *sdltool = data;
struct SDL_WaylandTabletInput *input = sdltool->tablet;
input->current_pen.buttons_released |= SDL_PEN_DOWN_MASK;
}
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_WaylandTabletInput *input = data;
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) {
float sx = (float)(wl_fixed_to_double(sx_w) * window->pointer_scale_x);
float sy = (float)(wl_fixed_to_double(sy_w) * window->pointer_scale_y);
SDL_SendMouseMotion(0, window->sdlwindow, 0, 0, sx, sy);
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, 0, 0, sx, sy);
}
}
}
static void tablet_tool_handle_pressure(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t pressure)
{
/* unimplemented */
struct SDL_WaylandTool *sdltool = data;
struct SDL_WaylandTabletInput *input = sdltool->tablet;
input->current_pen.update_status.axes[SDL_PEN_AXIS_PRESSURE] = pressure / 65535.0f;
if (pressure) {
input->current_pen.update_status.axes[SDL_PEN_AXIS_DISTANCE] = 0.0f;
}
}
static void tablet_tool_handle_distance(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t distance)
{
/* unimplemented */
struct SDL_WaylandTool *sdltool = data;
struct SDL_WaylandTabletInput *input = sdltool->tablet;
input->current_pen.update_status.axes[SDL_PEN_AXIS_DISTANCE] = distance / 65535.0f;
if (distance) {
input->current_pen.update_status.axes[SDL_PEN_AXIS_PRESSURE] = 0.0f;
}
}
static void tablet_tool_handle_tilt(void *data, struct zwp_tablet_tool_v2 *tool, wl_fixed_t xtilt, wl_fixed_t ytilt)
{
/* unimplemented */
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));
}
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_WaylandTabletInput *input = (struct SDL_WaylandTabletInput*)data;
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;
if (input->is_down) {
tablet_tool_handle_up(data, tool);
input->is_down = SDL_TRUE;
}
Wayland_UpdateImplicitGrabSerial(input->sdlWaylandInput, serial);
/* record event serial number to report it later in tablet_tool_handle_frame() */
input->current_pen.serial = serial;
switch (button) {
/* see %{_includedir}/linux/input-event-codes.h */
case 0x14b: /* BTN_STYLUS */
input->btn_stylus = (state == ZWP_TABLET_PAD_V2_BUTTON_STATE_PRESSED);
mask = SDL_BUTTON_LMASK;
break;
case 0x14c: /* BTN_STYLUS2 */
input->btn_stylus2 = (state == ZWP_TABLET_PAD_V2_BUTTON_STATE_PRESSED);
mask = SDL_BUTTON_MMASK;
break;
case 0x149: /* BTN_STYLUS3 */
input->btn_stylus3 = (state == ZWP_TABLET_PAD_V2_BUTTON_STATE_PRESSED);
mask = SDL_BUTTON_RMASK;
break;
}
if (input->is_down) {
tablet_tool_handle_down(data, tool, serial);
if (pressed) {
input->current_pen.buttons_pressed |= mask;
} else {
input->current_pen.buttons_released |= mask;
}
}
static void tablet_tool_handle_rotation(void *data, struct zwp_tablet_tool_v2 *tool, wl_fixed_t degrees)
{
/* unimplemented */
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;
}
static void tablet_tool_handle_slider(void *data, struct zwp_tablet_tool_v2 *tool, int32_t position)
{
/* unimplemented */
struct SDL_WaylandTool *sdltool = data;
struct SDL_WaylandTabletInput *input = sdltool->tablet;
input->current_pen.update_status.axes[SDL_PEN_AXIS_SLIDER] = position / 65535.0;
}
static void tablet_tool_handle_wheel(void *data, struct zwp_tablet_tool_v2 *tool, int32_t degrees, int32_t clicks)
{
/* unimplemented */
/* not supported at the moment */
}
static void tablet_tool_handle_frame(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t time)
{
/* unimplemented */
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));
if (penid == 0 || !window) { /* Not a pen, or event reported out of focus */
return;
}
/* 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;
for (button = 1; button_mask; ++button, button_mask >>= 1) {
if (button_mask & 1) {
SDL_SendPenButton(timestamp, penid, SDL_RELEASED, button);
}
}
/* 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);
}
}
SDL_SendPenMotion(timestamp, penid, SDL_TRUE, status);
/* 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;
}
static const struct zwp_tablet_tool_v2_listener tablet_tool_listener = {
@ -2624,6 +2847,26 @@ static void tablet_object_list_destroy(struct SDL_WaylandTabletObjectListNode *h
}
}
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;
@ -2634,9 +2877,12 @@ static void tablet_seat_handle_tablet_added(void *data, struct zwp_tablet_seat_v
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));
zwp_tablet_tool_v2_add_listener(tool, &tablet_tool_listener, data);
zwp_tablet_tool_v2_set_user_data(tool, data);
zwp_tablet_tool_v2_add_listener(tool, &tablet_tool_listener, sdltool);
zwp_tablet_tool_v2_set_user_data(tool, sdltool);
sdltool->tablet = input;
tablet_object_list_append(input->tools, tool);
}
@ -2657,6 +2903,7 @@ static const struct zwp_tablet_seat_v2_listener tablet_seat_listener = {
void Wayland_input_add_tablet(struct SDL_WaylandInput *input, struct SDL_WaylandTabletManager *tablet_manager)
{
struct SDL_WaylandTabletInput *tablet_input;
static Uint32 num_tablets = 0;
if (!tablet_manager || !input || !input->seat) {
return;
@ -2675,6 +2922,7 @@ void Wayland_input_add_tablet(struct SDL_WaylandInput *input, struct SDL_Wayland
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);
}
@ -2683,7 +2931,7 @@ void Wayland_input_add_tablet(struct SDL_WaylandInput *input, struct SDL_Wayland
void Wayland_input_destroy_tablet(struct SDL_WaylandInput *input)
{
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(zwp_tablet_tool_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));
zwp_tablet_seat_v2_destroy(input->tablet->seat);
@ -2808,7 +3056,8 @@ void Wayland_display_destroy_input(SDL_VideoData *d)
wl_touch_destroy(input->touch);
}
wl_list_for_each_safe (tp, tmp, &touch_points, link) {
wl_list_for_each_safe(tp, tmp, &touch_points, link)
{
WAYLAND_wl_list_remove(&tp->link);
SDL_free(tp);
}

View File

@ -25,6 +25,7 @@
#define SDL_waylandevents_h_
#include "../../events/SDL_mouse_c.h"
#include "../../events/SDL_pen_c.h"
#include "SDL_waylandvideo.h"
#include "SDL_waylandwindow.h"
@ -55,31 +56,38 @@ struct SDL_WaylandTabletInput
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 location */
/* 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_bool is_down;
SDL_bool btn_stylus;
SDL_bool btn_stylus2;
SDL_bool btn_stylus3;
};
typedef struct
{
int32_t repeat_rate; /* Repeat rate in range of [1, 1000] character(s) per second */
int32_t repeat_delay_ms; /* Time to first repeat event in milliseconds */
int32_t repeat_rate; /* Repeat rate in range of [1, 1000] character(s) per second */
int32_t repeat_delay_ms; /* Time to first repeat event in milliseconds */
SDL_bool is_initialized;
SDL_bool is_key_down;
uint32_t key;
Uint64 wl_press_time_ns; /* Key press time as reported by the Wayland API */
Uint64 sdl_press_time_ns; /* Key press time expressed in SDL ticks */
Uint64 next_repeat_ns; /* Next repeat event in nanoseconds */
Uint64 wl_press_time_ns; /* Key press time as reported by the Wayland API */
Uint64 sdl_press_time_ns; /* Key press time expressed in SDL ticks */
Uint64 next_repeat_ns; /* Next repeat event in nanoseconds */
uint32_t scancode;
char text[8];
} SDL_WaylandKeyboardRepeat;
@ -169,6 +177,12 @@ 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);
extern void Wayland_PumpEvents(SDL_VideoDevice *_this);

View File

@ -179,14 +179,14 @@ static SDL_bool X11_KeyRepeat(Display *display, XEvent *event)
return d.found;
}
static SDL_bool X11_IsWheelEvent(Display *display, XEvent *event, int *xticks, int *yticks)
static SDL_bool X11_IsWheelEvent(Display *display, int button, int *xticks, int *yticks)
{
/* according to the xlib docs, no specific mouse wheel events exist.
However, the defacto standard is that the vertical wheel is X buttons
4 (up) and 5 (down) and a horizontal wheel is 6 (left) and 7 (right). */
/* Xlib defines "Button1" through 5, so we just use literals here. */
switch (event->xbutton.button) {
switch (button) {
case 4:
*yticks = 1;
return SDL_TRUE;
@ -333,13 +333,15 @@ void SDL_SetX11EventHook(SDL_X11EventHook callback, void *userdata)
}
#ifdef SDL_VIDEO_DRIVER_X11_SUPPORTS_GENERIC_EVENTS
static void X11_HandleGenericEvent(SDL_VideoData *videodata, XEvent *xev)
static void X11_HandleGenericEvent(SDL_VideoDevice *_this, XEvent *xev)
{
SDL_VideoData *videodata = (SDL_VideoData *)_this->driverdata;
/* event is a union, so cookie == &event, but this is type safe. */
XGenericEventCookie *cookie = &xev->xcookie;
if (X11_XGetEventData(videodata->display, cookie)) {
if (!g_X11EventHook || g_X11EventHook(g_X11EventHookData, xev)) {
X11_HandleXinput2Event(videodata, cookie);
X11_HandleXinput2Event(_this, cookie);
}
X11_XFreeEventData(videodata->display, cookie);
}
@ -557,12 +559,12 @@ static void InitiateWindowResize(SDL_VideoDevice *_this, const SDL_WindowData *d
X11_XSync(display, 0);
}
static SDL_bool ProcessHitTest(SDL_VideoDevice *_this, const SDL_WindowData *data, const XEvent *xev)
SDL_bool X11_ProcessHitTest(SDL_VideoDevice *_this, const SDL_WindowData *data, const float x, const float y)
{
SDL_Window *window = data->window;
if (window->hit_test) {
const SDL_Point point = { xev->xbutton.x, xev->xbutton.y };
const SDL_Point point = { x, y };
const SDL_HitTestResult rc = window->hit_test(window, &point, window->hit_test_data);
static const int directions[] = {
_NET_WM_MOVERESIZE_SIZE_TOPLEFT, _NET_WM_MOVERESIZE_SIZE_TOP,
@ -761,14 +763,14 @@ static Bool isReparentNotify(Display *display, XEvent *ev, XPointer arg)
static SDL_bool IsHighLatin1(const char *string, int length)
{
while (length-- > 0) {
Uint8 ch = (Uint8)*string;
if (ch >= 0x80) {
return SDL_TRUE;
}
++string;
}
return SDL_FALSE;
while (length-- > 0) {
Uint8 ch = (Uint8)*string;
if (ch >= 0x80) {
return SDL_TRUE;
}
++string;
}
return SDL_FALSE;
}
static int XLookupStringAsUTF8(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, XComposeStatus *status_in_out)
@ -787,6 +789,78 @@ static int XLookupStringAsUTF8(XKeyEvent *event_struct, char *buffer_return, int
return result;
}
SDL_WindowData *X11_FindWindow(SDL_VideoDevice *_this, Window window)
{
const SDL_VideoData *videodata = (SDL_VideoData *)_this->driverdata;
int i;
if (videodata && videodata->windowlist) {
for (i = 0; i < videodata->numwindows; ++i) {
if ((videodata->windowlist[i] != NULL) &&
(videodata->windowlist[i]->xwindow == window)) {
return videodata->windowlist[i];
}
}
}
return NULL;
}
void X11_HandleButtonPress(SDL_VideoDevice *_this, SDL_WindowData *windowdata, int button, const float x, const float y, const unsigned long time)
{
SDL_Window *window = windowdata->window;
const SDL_VideoData *videodata = (SDL_VideoData *)_this->driverdata;
Display *display = videodata->display;
int xticks = 0, yticks = 0;
#ifdef DEBUG_XEVENTS
printf("window %p: ButtonPress (X11 button = %d)\n", window, button);
#endif
if (X11_IsWheelEvent(display, button, &xticks, &yticks)) {
SDL_SendMouseWheel(0, window, 0, (float)-xticks, (float)yticks, SDL_MOUSEWHEEL_NORMAL);
} else {
SDL_bool ignore_click = SDL_FALSE;
if (button == Button1) {
if (X11_ProcessHitTest(_this, windowdata, x, y)) {
SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_HIT_TEST, 0, 0);
return; /* don't pass this event on to app. */
}
} else if (button > 7) {
/* X button values 4-7 are used for scrolling, so X1 is 8, X2 is 9, ...
=> subtract (8-SDL_BUTTON_X1) to get value SDL expects */
button -= (8 - SDL_BUTTON_X1);
}
if (windowdata->last_focus_event_time) {
const int X11_FOCUS_CLICK_TIMEOUT = 10;
if (SDL_GetTicks() < (windowdata->last_focus_event_time + X11_FOCUS_CLICK_TIMEOUT)) {
ignore_click = !SDL_GetHintBoolean(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, SDL_FALSE);
}
windowdata->last_focus_event_time = 0;
}
if (!ignore_click) {
SDL_SendMouseButton(0, window, 0, SDL_PRESSED, button);
}
}
X11_UpdateUserTime(windowdata, time);
}
void X11_HandleButtonRelease(SDL_VideoDevice *_this, SDL_WindowData *windowdata, int button)
{
SDL_Window *window = windowdata->window;
const SDL_VideoData *videodata = (SDL_VideoData *)_this->driverdata;
Display *display = videodata->display;
/* The X server sends a Release event for each Press for wheels. Ignore them. */
int xticks = 0, yticks = 0;
#ifdef DEBUG_XEVENTS
printf("window %p: ButtonRelease (X11 button = %d)\n", data, xevent->xbutton.button);
#endif
if (!X11_IsWheelEvent(display, button, &xticks, &yticks)) {
if (button > 7) {
/* see explanation at case ButtonPress */
button -= (8 - SDL_BUTTON_X1);
}
SDL_SendMouseButton(0, window, 0, SDL_RELEASED, button);
}
}
void X11_GetBorderValues(SDL_WindowData *data)
{
SDL_VideoData *videodata = data->videodata;
@ -861,7 +935,7 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent)
#ifdef SDL_VIDEO_DRIVER_X11_SUPPORTS_GENERIC_EVENTS
if (xevent->type == GenericEvent) {
X11_HandleGenericEvent(videodata, xevent);
X11_HandleGenericEvent(_this, xevent);
return;
}
#endif
@ -886,19 +960,19 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent)
#ifdef SDL_VIDEO_DRIVER_X11_XFIXES
if (SDL_X11_HAVE_XFIXES &&
xevent->type == X11_GetXFixesSelectionNotifyEvent()) {
XFixesSelectionNotifyEvent *ev = (XFixesSelectionNotifyEvent *) xevent;
xevent->type == X11_GetXFixesSelectionNotifyEvent()) {
XFixesSelectionNotifyEvent *ev = (XFixesSelectionNotifyEvent *)xevent;
/* !!! FIXME: cache atoms */
Atom XA_CLIPBOARD = X11_XInternAtom(display, "CLIPBOARD", 0);
#ifdef DEBUG_XEVENTS
printf("window CLIPBOARD: XFixesSelectionNotify (selection = %s)\n",
X11_XGetAtomName(display, ev->selection));
X11_XGetAtomName(display, ev->selection));
#endif
if (ev->selection == XA_PRIMARY ||
(XA_CLIPBOARD != None && ev->selection == XA_CLIPBOARD)) {
(XA_CLIPBOARD != None && ev->selection == XA_CLIPBOARD)) {
SDL_SendClipboardUpdate();
return;
}
@ -911,16 +985,8 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent)
return;
}
data = NULL;
if (videodata && videodata->windowlist) {
for (i = 0; i < videodata->numwindows; ++i) {
if ((videodata->windowlist[i] != NULL) &&
(videodata->windowlist[i]->xwindow == xevent->xany.window)) {
data = videodata->windowlist[i];
break;
}
}
}
data = X11_FindWindow(_this, xevent->xany.window);
if (!data) {
/* The window for KeymapNotify, etc events is 0 */
if (xevent->type == KeymapNotify) {
@ -1227,8 +1293,9 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent)
xevent->xconfigure.x, xevent->xconfigure.y,
xevent->xconfigure.width, xevent->xconfigure.height);
#endif
/* Real configure notify events are relative to the parent, synthetic events are absolute. */
if (!xevent->xconfigure.send_event) {
/* Real configure notify events are relative to the parent, synthetic events are absolute. */
if (!xevent->xconfigure.send_event)
{
unsigned int NumChildren;
Window ChildReturn, Root, Parent;
Window *Children;
@ -1318,7 +1385,7 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent)
root_y = xevent->xclient.data.l[2] & 0xffff;
/* Translate from root to current window position */
X11_XTranslateCoordinates(display, DefaultRootWindow(display), data->xwindow,
root_x, root_y, &window_x, &window_y, &ChildReturn);
root_x, root_y, &window_x, &window_y, &ChildReturn);
SDL_SendDropPosition(data->window, (float)window_x, (float)window_y);
}
@ -1402,6 +1469,12 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent)
SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_EXPOSED, 0, 0);
} break;
/* Use XInput2 instead of the xevents API if possible, for:
- MotionNotify
- ButtonPress
- ButtonRelease
XInput2 has more precise information, e.g., to distinguish different input devices. */
#ifndef SDL_VIDEO_DRIVER_X11_XINPUT2
case MotionNotify:
{
SDL_Mouse *mouse = SDL_GetMouse();
@ -1416,55 +1489,15 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent)
case ButtonPress:
{
int xticks = 0, yticks = 0;
#ifdef DEBUG_XEVENTS
printf("window %p: ButtonPress (X11 button = %d)\n", data, xevent->xbutton.button);
#endif
if (X11_IsWheelEvent(display, xevent, &xticks, &yticks)) {
SDL_SendMouseWheel(0, data->window, 0, (float)-xticks, (float)yticks, SDL_MOUSEWHEEL_NORMAL);
} else {
SDL_bool ignore_click = SDL_FALSE;
int button = xevent->xbutton.button;
if (button == Button1) {
if (ProcessHitTest(_this, data, xevent)) {
SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_HIT_TEST, 0, 0);
break; /* don't pass this event on to app. */
}
} else if (button > 7) {
/* X button values 4-7 are used for scrolling, so X1 is 8, X2 is 9, ...
=> subtract (8-SDL_BUTTON_X1) to get value SDL expects */
button -= (8 - SDL_BUTTON_X1);
}
if (data->last_focus_event_time) {
const int X11_FOCUS_CLICK_TIMEOUT = 10;
if (SDL_GetTicks() < (data->last_focus_event_time + X11_FOCUS_CLICK_TIMEOUT)) {
ignore_click = !SDL_GetHintBoolean(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, SDL_FALSE);
}
data->last_focus_event_time = 0;
}
if (!ignore_click) {
SDL_SendMouseButton(0, data->window, 0, SDL_PRESSED, button);
}
}
X11_UpdateUserTime(data, xevent->xbutton.time);
X11_HandleButtonPress(_this, data, xevent->xbutton.button,
xevent->xbutton.x, xevent->xbutton.y, xevent->xbutton.time);
} break;
case ButtonRelease:
{
int button = xevent->xbutton.button;
/* The X server sends a Release event for each Press for wheels. Ignore them. */
int xticks = 0, yticks = 0;
#ifdef DEBUG_XEVENTS
printf("window %p: ButtonRelease (X11 button = %d)\n", data, xevent->xbutton.button);
#endif
if (!X11_IsWheelEvent(display, xevent, &xticks, &yticks)) {
if (button > 7) {
/* see explanation at case ButtonPress */
button -= (8 - SDL_BUTTON_X1);
}
SDL_SendMouseButton(0, data->window, 0, SDL_RELEASED, button);
}
X11_HandleButtonRelease(_this, data, xevent->xbutton.button);
} break;
#endif /* !SDL_VIDEO_DRIVER_X11_XINPUT2 */
case PropertyNotify:
{

View File

@ -29,5 +29,9 @@ extern void X11_SendWakeupEvent(SDL_VideoDevice *_this, SDL_Window *window);
extern int X11_SuspendScreenSaver(SDL_VideoDevice *_this);
extern void X11_ReconcileKeyboardState(SDL_VideoDevice *_this);
extern void X11_GetBorderValues(SDL_WindowData *data);
extern void X11_HandleButtonPress(SDL_VideoDevice *_this, SDL_WindowData *wdata, int button, const float x, const float y, const unsigned long time);
extern void X11_HandleButtonRelease(SDL_VideoDevice *_this, SDL_WindowData *wdata, int button);
extern SDL_WindowData *X11_FindWindow(SDL_VideoDevice *_this, Window window);
extern SDL_bool X11_ProcessHitTest(SDL_VideoDevice *_this, const SDL_WindowData *data, const float x, const float y);
#endif /* SDL_x11events_h_ */

694
src/video/x11/SDL_x11pen.c Normal file
View File

@ -0,0 +1,694 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
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.
*/
#include "../../SDL_internal.h"
#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2
#include "../../events/SDL_pen_c.h"
#include "../SDL_sysvideo.h"
#include "SDL_pen.h"
#include "SDL_x11pen.h"
#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
{
float axis_min[SDL_PEN_NUM_AXES];
float axis_max[SDL_PEN_NUM_AXES];
float slider_bias; /* shift value to add to PEN_AXIS_SLIDER (before normalisation) */
float rotation_bias; /* rotation to add to PEN_AXIS_ROTATION (after normalisation) */
Sint8 valuator_for_axis[SDL_PEN_NUM_AXES]; /* SDL_PEN_AXIS_VALUATOR_MISSING if not supported */
} xinput2_pen;
/* X11 atoms */
static struct
{
int initialized; /* initialised to 0 */
Atom device_product_id;
Atom abs_pressure;
Atom abs_tilt_x;
Atom abs_tilt_y;
Atom wacom_serial_ids;
Atom wacom_tool_type;
} pen_atoms;
/*
* Mapping from X11 device IDs to pen IDs
*
* In X11, the same device ID may represent any number of pens. We
* thus cannot directly use device IDs as pen IDs.
*/
static struct
{
int num_pens_known; /* Number of currently known pens (based on their GUID); used to give pen ID to new pens */
int num_entries; /* Number of X11 device IDs that correspond to pens */
struct pen_device_id_mapping
{
Uint32 deviceid;
Uint32 pen_id;
} * entries; /* Current pen to device ID mappings */
} pen_map;
typedef enum
{
SDL_PEN_VENDOR_UNKNOWN = 0,
SDL_PEN_VENDOR_WACOM
} sdl_pen_vendor;
/* Information to identify pens during discovery */
typedef struct
{
sdl_pen_vendor vendor;
SDL_GUID guid;
SDL_PenSubtype heuristic_type; /* Distinguish pen+eraser devices with shared bus ID */
Uint32 devicetype_id, serial; /* used by PEN_VENDOR_WACOM */
Uint32 deviceid;
} pen_identity;
int X11_PenIDFromDeviceID(int deviceid)
{
int i;
for (i = 0; i < pen_map.num_entries; ++i) {
if (pen_map.entries[i].deviceid == deviceid) {
return pen_map.entries[i].pen_id;
}
}
return SDL_PEN_INVALID;
}
static void pen_atoms_ensure_initialized(SDL_VideoDevice *_this)
{
SDL_VideoData *data = (SDL_VideoData *)_this->driverdata;
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", True);
pen_atoms.abs_tilt_x = X11_XInternAtom(data->display, "Abs Tilt X", True);
pen_atoms.abs_tilt_y = X11_XInternAtom(data->display, "Abs Tilt Y", True);
pen_atoms.initialized = 1;
}
/* 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)
{
const SDL_VideoData *data = (SDL_VideoData *)_this->driverdata;
Atom type_return;
int format_return;
unsigned long num_items_return;
unsigned long bytes_after_return;
unsigned char *output;
if (property == None) {
return 0;
}
if (Success != X11_XIGetProperty(data->display, deviceid,
property,
0, max_words, False,
XA_INTEGER, &type_return, &format_return,
&num_items_return, &bytes_after_return,
&output) ||
num_items_return == 0 || output == NULL) {
return 0;
}
if (type_return == XA_INTEGER) {
int k;
const int to_copy = SDL_min(max_words, num_items_return);
if (format_return == 8) {
Sint8 *numdata = (Sint8 *)output;
for (k = 0; k < to_copy; ++k) {
dest[k] = numdata[k];
}
} else if (format_return == 16) {
Sint16 *numdata = (Sint16 *)output;
for (k = 0; k < to_copy; ++k) {
dest[k] = numdata[k];
}
} else {
SDL_memcpy(dest, output, sizeof(Sint32) * to_copy);
}
X11_XFree(output);
return to_copy;
}
return 0; /* type mismatch */
}
/* 32 bit vendor + device ID from evdev */
static Uint32 xinput2_pen_evdevid(SDL_VideoDevice *_this, int deviceid)
{
#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 */
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) {
*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
return SDL_FALSE;
}
/* Heuristically determines if device is an eraser */
static SDL_bool xinput2_pen_is_eraser(SDL_VideoDevice *_this, int deviceid, char *devicename)
{
SDL_VideoData *data = (SDL_VideoData *)_this->driverdata;
char dev_name[PEN_ERASER_ID_MAXLEN];
int k;
pen_atoms_ensure_initialized(_this);
if (pen_atoms.wacom_tool_type != None) {
Atom type_return;
int format_return;
unsigned long num_items_return;
unsigned long bytes_after_return;
unsigned char *tooltype_name_info = NULL;
/* Try Wacom-specific method */
if (Success == X11_XIGetProperty(data->display, deviceid,
pen_atoms.wacom_tool_type,
0, 32, False,
AnyPropertyType, &type_return, &format_return,
&num_items_return, &bytes_after_return,
&tooltype_name_info) &&
tooltype_name_info != NULL && num_items_return > 0) {
SDL_bool result = SDL_FALSE;
char *tooltype_name = NULL;
if (type_return == XA_ATOM) {
/* Atom instead of string? Un-intern */
Atom atom = *((Atom *)tooltype_name_info);
if (atom != None) {
tooltype_name = X11_XGetAtomName(data->display, atom);
}
} else if (type_return == XA_STRING && format_return == 8) {
tooltype_name = (char *)tooltype_name_info;
}
if (tooltype_name) {
if (0 == SDL_strcasecmp(tooltype_name, PEN_ERASER_NAME_TAG)) {
result = SDL_TRUE;
}
X11_XFree(tooltype_name_info);
return result;
}
}
}
/* Non-Wacom device? */
/* We assume that a device is an eraser if its name contains the string "eraser".
* Unfortunately there doesn't seem to be a clean way to distinguish these cases (as of 2022-03). */
SDL_strlcpy(dev_name, devicename, PEN_ERASER_ID_MAXLEN);
/* lowercase device name string so we can use strstr() */
for (k = 0; dev_name[k]; ++k) {
dev_name[k] = tolower(dev_name[k]);
}
return (SDL_strstr(dev_name, PEN_ERASER_NAME_TAG)) ? SDL_TRUE : SDL_FALSE;
}
/* Gets GUID and other identifying information for the device using the best known method */
static pen_identity xinput2_identify_pen(SDL_VideoDevice *_this, int deviceid, char *name)
{
pen_identity pident;
pident.devicetype_id = 0ul;
pident.serial = 0ul;
pident.deviceid = deviceid;
pident.heuristic_type = SDL_PEN_TYPE_PEN;
SDL_memset(pident.guid.data, 0, sizeof(pident.guid.data));
if (xinput2_pen_is_eraser(_this, deviceid, name)) {
pident.heuristic_type = SDL_PEN_TYPE_ERASER;
}
if (xinput2_wacom_deviceid(_this, deviceid, &pident.devicetype_id, &pident.serial)) {
pident.vendor = SDL_PEN_VENDOR_WACOM;
SDL_PenUpdateGUIDForWacom(&pident.guid, pident.devicetype_id, pident.serial);
#if DEBUG_PEN
printf("[pen] Pen %d reports Wacom device_id %x\n",
deviceid, pident.devicetype_id);
#endif
} 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;
}
static void xinput2_pen_free_deviceinfo(Uint32 deviceid, void *x11_peninfo, void *context)
{
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;
}
}
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(const XIDeviceInfo *dev)
{
int classct;
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->driverdata;
int i;
XIDeviceInfo *device_info;
int num_device_info;
device_info = X11_XIQueryDevice(data->display, XIAllDevices, &num_device_info);
if (!device_info) {
return;
}
/* Reset the device id -> pen map */
if (pen_map.entries) {
SDL_free(pen_map.entries);
pen_map.entries = NULL;
pen_map.num_entries = 0;
}
SDL_PenGCMark();
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 */
if (dev->use != XISlavePointer || dev->enabled == 0 || !xinput2_device_is_pen(dev)) {
continue;
}
pen_device.slider_bias = 0.0f;
pen_device.rotation_bias = 0.0f;
for (k = 0; k < SDL_PEN_NUM_AXES; ++k) {
pen_device.valuator_for_axis[k] = SDL_PEN_AXIS_VALUATOR_MISSING;
}
pident = xinput2_identify_pen(_this, dev->deviceid, dev->name);
pen_id = SDL_GetPenFromGUID(pident.guid);
if (pen_id == SDL_PEN_INVALID) {
/* We have never met this pen */
pen_id = ++pen_map.num_pens_known; /* start at 1 */
}
pen = SDL_PenModifyBegin(pen_id);
/* Complement XF86 driver information with vendor-specific details */
xinput2_vendor_peninfo(_this, dev, pen, pident, &valuator_5_axis, &axis_mask);
for (classct = 0; classct < dev->num_classes; ++classct) {
const XIAnyClassInfo *classinfo = dev->classes[classct];
switch (classinfo->type) {
case XIValuatorClass:
{
XIValuatorClassInfo *val_classinfo = (XIValuatorClassInfo *)classinfo;
Sint8 valuator_nr = val_classinfo->number;
Atom vname = val_classinfo->label;
int axis = -1;
float min = val_classinfo->min;
float max = val_classinfo->max;
if (vname == pen_atoms.abs_pressure) {
axis = SDL_PEN_AXIS_PRESSURE;
} else if (vname == pen_atoms.abs_tilt_x) {
axis = SDL_PEN_AXIS_XTILT;
} else if (vname == pen_atoms.abs_tilt_y) {
axis = SDL_PEN_AXIS_YTILT;
}
if (axis == -1 && valuator_nr == 5) {
/* Wacom model-specific axis support */
/* The meaning of the various axes is highly underspecitied in Xinput2.
* As of 2023-08-26, Wacom seems to be the only vendor to support these axes, so the code below
* captures the de-facto standard. */
axis = valuator_5_axis;
switch (axis) {
case SDL_PEN_AXIS_SLIDER:
/* cf. xinput2_wacom_peninfo for how this axis is used.
In all current cases, our API wants this value in 0..1, but the xf86 driver
starts at a negative offset, so we normalise here. */
pen_device.slider_bias = -min;
max -= min;
min = 0.0f;
break;
case SDL_PEN_AXIS_ROTATION:
/* The "0" value points to the left, rather than up, so we must
rotate 90 degrees counter-clockwise to have 0 point to the top. */
pen_device.rotation_bias = -90.0f;
break;
default:
break;
}
}
if (axis >= 0) {
capabilities |= SDL_PEN_AXIS_CAPABILITY(axis);
pen_device.valuator_for_axis[axis] = valuator_nr;
pen_device.axis_min[axis] = min;
pen_device.axis_max[axis] = max;
}
break;
}
default:
break;
}
}
/* We have a pen if and only if the device measures pressure */
if (capabilities & SDL_PEN_AXIS_PRESSURE_MASK) {
xinput2_pen *xinput2_deviceinfo;
Uint64 guid_a, guid_b;
/* Done collecting data, write to pen */
SDL_PenModifyAddCapabilities(pen, capabilities);
pen->guid = pident.guid;
if (pen->deviceinfo) {
/* Updating a known pen */
xinput2_deviceinfo = (xinput2_pen *)pen->deviceinfo;
xinput2_merge_deviceinfo(xinput2_deviceinfo, &pen_device);
} else {
/* Registering a new pen */
xinput2_deviceinfo = SDL_malloc(sizeof(xinput2_pen));
SDL_memcpy(xinput2_deviceinfo, &pen_device, sizeof(xinput2_pen));
}
pen->deviceinfo = xinput2_deviceinfo;
#if DEBUG_PEN
printf("[pen] pen %d [%04x] valuators pressure=%d, xtilt=%d, ytilt=%d [%s]\n",
pen->header.id, pen->header.flags,
pen_device.valuator_for_axis[SDL_PEN_AXIS_PRESSURE],
pen_device.valuator_for_axis[SDL_PEN_AXIS_XTILT],
pen_device.valuator_for_axis[SDL_PEN_AXIS_YTILT],
pen->name);
#endif
SDL_memcpy(&guid_a, &pen->guid.data[0], 8);
SDL_memcpy(&guid_b, &pen->guid.data[8], 8);
if (!(guid_a | guid_b)) {
#if DEBUG_PEN
printf("[pen] (pen eliminated due to zero GUID)\n");
#endif
pen->type = SDL_PEN_TYPE_NONE;
}
} else {
/* Not a pen, mark for deletion */
pen->type = SDL_PEN_TYPE_NONE;
}
SDL_PenModifyEnd(pen, SDL_TRUE);
if (pen->type != SDL_PEN_TYPE_NONE) {
const int map_pos = pen_map.num_entries;
/* We found a pen: add mapping */
if (pen_map.entries == NULL) {
pen_map.entries = SDL_calloc(sizeof(struct pen_device_id_mapping), 1);
pen_map.num_entries = 1;
} else {
pen_map.num_entries += 1;
pen_map.entries = SDL_realloc(pen_map.entries,
pen_map.num_entries * (sizeof(struct pen_device_id_mapping)));
}
pen_map.entries[map_pos].deviceid = dev->deviceid;
pen_map.entries[map_pos].pen_id = pen_id;
} else {
/* Revert pen number allocation */
pen_map.num_pens_known = old_num_pens_known;
}
}
X11_XIFreeDeviceInfo(device_info);
SDL_PenGCSweep(NULL, xinput2_pen_free_deviceinfo);
}
static void xinput2_normalize_pen_axes(const SDL_Pen *peninfo,
const xinput2_pen *xpen,
/* inout-mode paramters: */
float *coords)
{
int axis;
/* Normalise axes */
for (axis = 0; axis < SDL_PEN_NUM_AXES; ++axis) {
int valuator = xpen->valuator_for_axis[axis];
if (valuator != SDL_PEN_AXIS_VALUATOR_MISSING) {
float value = coords[axis];
float min = xpen->axis_min[axis];
float max = xpen->axis_max[axis];
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) {
value = 0.0f;
} else {
value = (value - min) / max;
}
}
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 */
}
break;
case SDL_PEN_AXIS_ROTATION:
/* normalised to -1..1, so let's convert to degrees */
value *= 180.0;
value += xpen->rotation_bias;
/* handle simple over/underflow */
if (value >= 180.0f) {
value -= 360.0f;
} else if (value < -180.0f) {
value += 360.0f;
}
break;
default:
break;
}
coords[axis] = value;
}
}
}
void X11_PenAxesFromValuators(const SDL_Pen *peninfo,
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) {
const int valuator = pen->valuator_for_axis[i];
if (valuator == SDL_PEN_AXIS_VALUATOR_MISSING || valuator >= mask_len * 8 || !(XIMaskIsSet(mask, valuator))) {
axis_values[i] = 0.0f;
} else {
axis_values[i] = input_values[valuator];
}
}
xinput2_normalize_pen_axes(peninfo, pen, axis_values);
}
#endif /* SDL_VIDEO_DRIVER_X11_XINPUT2 */
/* vi: set ts=4 sw=4 expandtab: */

View File

@ -0,0 +1,54 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
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.
*/
#include "../../SDL_internal.h"
#ifndef SDL_x11pen_h_
#define SDL_x11pen_h_
#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 */
struct SDL_VideoData;
/* Function definitions */
/* Detect XINPUT2 devices that are pens / erasers, or update the list after hotplugging */
extern void X11_InitPen(SDL_VideoDevice *_this);
/* Converts XINPUT2 valuators into pen axis information, including normalisation */
extern void X11_PenAxesFromValuators(const SDL_Pen *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);
#endif /* SDL_VIDEO_DRIVER_X11_XINPUT2 */
#endif /* SDL_x11pen_h_ */
/* vi: set ts=4 sw=4 expandtab: */

View File

@ -289,6 +289,7 @@ SDL_X11_SYM(Status,XIQueryVersion,(Display *a,int *b,int *c),(a,b,c),return)
SDL_X11_SYM(XIEventMask*,XIGetSelectedEvents,(Display *a,Window b,int *c),(a,b,c),return)
SDL_X11_SYM(Bool,XIGetClientPointer,(Display *a,Window b,int *c),(a,b,c),return)
SDL_X11_SYM(Bool,XIWarpPointer,(Display *a,int b,Window c,Window d,double e,double f,int g,int h,double i,double j),(a,b,c,d,e,f,g,h,i,j),return)
SDL_X11_SYM(Status,XIGetProperty,(Display *a,int b,Atom c,long d,long e,Bool f, Atom g, Atom *h, int *i, unsigned long *j, unsigned long *k, unsigned char **l),(a,b,c,d,e,f,g,h,i,j,k,l),return);
#endif
/* XRandR support */

View File

@ -24,16 +24,17 @@
#include <unistd.h> /* For getpid() and readlink() */
#include "../SDL_sysvideo.h"
#include "../SDL_pixels_c.h"
#include "../../core/linux/SDL_system_theme.h"
#include "../SDL_pixels_c.h"
#include "../SDL_sysvideo.h"
#include "SDL_x11video.h"
#include "SDL_x11framebuffer.h"
#include "SDL_x11pen.h"
#include "SDL_x11shape.h"
#include "SDL_x11touch.h"
#include "SDL_x11xinput2.h"
#include "SDL_x11video.h"
#include "SDL_x11xfixes.h"
#include "SDL_x11xinput2.h"
#ifdef SDL_VIDEO_OPENGL_EGL
#include "SDL_x11opengles.h"
@ -432,6 +433,10 @@ int X11_VideoInit(SDL_VideoDevice *_this)
X11_InitTouch(_this);
#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2
X11_InitPen(_this);
#endif
return 0;
}

View File

@ -592,8 +592,8 @@ int X11_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window)
X11_ConstrainPopup(window);
}
SDL_RelativeToGlobalForWindow(window,
window->windowed.x, window->windowed.y,
&win_x, &win_y);
window->windowed.x, window->windowed.y,
&win_x, &win_y);
/* Always create this with the window->windowed.* fields; if we're
creating a windowed mode window, that's fine. If we're creating a
@ -754,12 +754,20 @@ int X11_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window)
X11_Xinput2SelectTouch(_this, window);
X11_XSelectInput(display, w,
(FocusChangeMask | EnterWindowMask | LeaveWindowMask |
ExposureMask | ButtonPressMask | ButtonReleaseMask |
PointerMotionMask | KeyPressMask | KeyReleaseMask |
PropertyChangeMask | StructureNotifyMask |
KeymapStateMask | fevent));
{
unsigned int x11_pointer_events = ButtonPressMask | ButtonReleaseMask | PointerMotionMask;
if (X11_Xinput2SelectMouse(_this, window)) {
/* If XInput2 can handle pointer events, we don't track them here */
x11_pointer_events = 0;
}
X11_XSelectInput(display, w,
(FocusChangeMask | EnterWindowMask | LeaveWindowMask | ExposureMask |
x11_pointer_events |
KeyPressMask | KeyReleaseMask |
PropertyChangeMask | StructureNotifyMask |
KeymapStateMask | fevent));
}
/* For _ICC_PROFILE. */
X11_XSelectInput(display, RootWindow(display, screen), PropertyChangeMask);
@ -832,7 +840,8 @@ static int X11_CatchAnyError(Display *d, XErrorEvent *e)
return 0;
}
enum check_method {
enum check_method
{
COMPARE_POSITION = 1,
COMPARE_SIZE = 2,
COMPARE_DOUBLE_ATTEMPT = 3,
@ -842,8 +851,8 @@ enum check_method {
/* Wait a brief time, or not, to see if the window manager decided to move/resize the window.
* Send MOVED and RESIZED window events */
static void X11_WaitAndSendWindowEvents(SDL_Window *window, int param_timeout, enum check_method method,
int orig_x, int orig_y, int dest_x, int dest_y,
int orig_w, int orig_h, int dest_w, int dest_h)
int orig_x, int orig_y, int dest_x, int dest_y,
int orig_w, int orig_h, int dest_w, int dest_h)
{
SDL_WindowData *data = window->driverdata;
Display *display = data->videodata->display;
@ -934,7 +943,6 @@ static void X11_WaitAndSendWindowEvents(SDL_Window *window, int param_timeout, e
caught_x11_error = SDL_FALSE;
}
int X11_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *icon)
{
SDL_WindowData *data = window->driverdata;
@ -974,8 +982,8 @@ int X11_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *i
}
X11_XChangeProperty(display, data->xwindow, _NET_WM_ICON, XA_CARDINAL,
32, PropModeReplace, (unsigned char *)propdata,
propsize);
32, PropModeReplace, (unsigned char *)propdata,
propsize);
SDL_free(propdata);
if (caught_x11_error) {
@ -1351,7 +1359,6 @@ void X11_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window)
if (data->border_left == 0 && data->border_right == 0 && data->border_top == 0 && data->border_bottom == 0) {
X11_GetBorderValues(data);
}
}
void X11_HideWindow(SDL_VideoDevice *_this, SDL_Window *window)
@ -1485,7 +1492,6 @@ static void X11_SetWindowMaximized(SDL_VideoDevice *_this, SDL_Window *window, S
/* Send MOVED/RESIZED event, if needed. Compare with initial position and size. Timeout 1000 */
X11_WaitAndSendWindowEvents(window, 1000, COMPARE_ORIG, orig_x, orig_y, 0, 0, orig_w, orig_h, 0, 0);
} else {
X11_SetNetWMState(_this, data->xwindow, window->flags);
}

View File

@ -22,9 +22,12 @@
#ifdef SDL_VIDEO_DRIVER_X11
#include "SDL_x11pen.h"
#include "SDL_x11video.h"
#include "SDL_x11xinput2.h"
#include "../../events/SDL_events_c.h"
#include "../../events/SDL_mouse_c.h"
#include "../../events/SDL_pen_c.h"
#include "../../events/SDL_touch_c.h"
#define MAX_AXIS 16
@ -74,19 +77,25 @@ static SDL_bool xinput2_version_atleast(const int version, const int wantmajor,
return version >= ((wantmajor * 1000) + wantminor);
}
#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH
static SDL_Window *xinput2_get_sdlwindow(SDL_VideoData *videodata, Window window)
static SDL_WindowData *xinput2_get_sdlwindowdata(SDL_VideoData *videodata, Window window)
{
int i;
for (i = 0; i < videodata->numwindows; i++) {
SDL_WindowData *d = videodata->windowlist[i];
if (d->xwindow == window) {
return d->window;
return d;
}
}
return NULL;
}
static SDL_Window *xinput2_get_sdlwindow(SDL_VideoData *videodata, Window window)
{
const SDL_WindowData *windowdata = xinput2_get_sdlwindowdata(videodata, window);
return windowdata ? windowdata->window : NULL;
}
#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH
static void xinput2_normalize_touch_coordinates(SDL_Window *window, double in_x, double in_y, float *out_x, float *out_y)
{
if (window) {
@ -262,18 +271,51 @@ 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
int X11_HandleXinput2Event(SDL_VideoData *videodata, XGenericEventCookie *cookie)
int X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie)
{
#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2
SDL_VideoData *videodata = (SDL_VideoData *)_this->driverdata;
if (cookie->extension != xinput2_opcode) {
return 0;
}
switch (cookie->evtype) {
case XI_PropertyEvent:
case XI_DeviceChanged:
{
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;
case XI_RawMotion:
{
const XIRawEvent *rawev = (const XIRawEvent *)cookie->data;
const SDL_bool is_pen = X11_PenIDFromDeviceID(rawev->sourceid) != SDL_PEN_INVALID;
SDL_Mouse *mouse = SDL_GetMouse();
SDL_XInput2DeviceInfo *devinfo;
double coords[2];
@ -281,6 +323,11 @@ int X11_HandleXinput2Event(SDL_VideoData *videodata, XGenericEventCookie *cookie
int i;
videodata->global_mouse_changed = SDL_TRUE;
if (is_pen) {
return 0; /* Pens check for XI_Motion instead */
}
/* Non-pen: */
if (!mouse->relative_mode || mouse->relative_mode_warp) {
return 0;
@ -317,6 +364,7 @@ int X11_HandleXinput2Event(SDL_VideoData *videodata, XGenericEventCookie *cookie
xinput2_remove_device_info(videodata, hierev->info[i].deviceid);
}
}
X11_InitPen(_this);
} break;
case XI_RawButtonPress:
@ -326,17 +374,94 @@ int X11_HandleXinput2Event(SDL_VideoData *videodata, XGenericEventCookie *cookie
case XI_RawTouchUpdate:
case XI_RawTouchEnd:
#endif
{
videodata->global_mouse_changed = SDL_TRUE;
break;
} break;
#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH
/* With multitouch, register to receive XI_Motion (which desctivates MotionNotify),
* so that we can distinguish real mouse motions from synthetic one. */
case XI_ButtonPress:
case XI_ButtonRelease:
{
const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data;
const SDL_Pen *pen = SDL_GetPenPtr(X11_PenIDFromDeviceID(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 */
const SDL_WindowData *windowdata = X11_FindWindow(_this, xev->event);
if (X11_ProcessHitTest(_this, windowdata, pen->last.x, pen->last.y)) {
SDL_SendWindowEvent(windowdata->window, SDL_EVENT_WINDOW_HIT_TEST, 0, 0);
return 1; /* Don't pass on this event */
}
}
SDL_SendPenTipEvent(0, pen->header.id,
pressed ? SDL_PRESSED : SDL_RELEASED);
} else {
SDL_SendPenButton(0, pen->header.id,
pressed ? SDL_PRESSED : SDL_RELEASED,
button - 1);
}
return 1;
} else {
/* Otherwise assume a regular mouse */
SDL_WindowData *windowdata = xinput2_get_sdlwindowdata(videodata, xev->event);
if (xev->deviceid != xev->sourceid) {
/* Discard events from "Master" devices to avoid duplicates. */
return 1;
}
if (pressed) {
X11_HandleButtonPress(_this, windowdata, button,
xev->event_x, xev->event_y, xev->time);
} else {
X11_HandleButtonRelease(_this, windowdata, button);
}
}
} break;
/* Register to receive XI_Motion (which deactivates MotionNotify), so that we can distinguish
real mouse motions from synthetic ones, for multitouch and pen support. */
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
int pointer_emulated = (xev->flags & XIPointerEmulated);
#endif /* SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH */
if (xev->deviceid != xev->sourceid) {
/* Discard events from "Master" devices to avoid duplicates. */
return 1;
}
if (pen) {
SDL_PenStatusInfo pen_status;
pen_status.x = xev->event_x;
pen_status.y = xev->event_y;
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);
return 1;
}
#if SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH
if (!pointer_emulated) {
SDL_Mouse *mouse = SDL_GetMouse();
if (!mouse->relative_mode || mouse->relative_mode_warp) {
@ -347,8 +472,10 @@ int X11_HandleXinput2Event(SDL_VideoData *videodata, XGenericEventCookie *cookie
}
}
return 1;
#endif /* SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH */
} break;
#if SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH
case XI_TouchBegin:
{
const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data;
@ -376,10 +503,9 @@ int X11_HandleXinput2Event(SDL_VideoData *videodata, XGenericEventCookie *cookie
SDL_SendTouchMotion(0, xev->sourceid, xev->detail, window, x, y, 1.0);
return 1;
} break;
#endif
#endif /* SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH */
}
#endif
#endif /* SDL_VIDEO_DRIVER_X11_XINPUT2 */
return 0;
}
@ -460,6 +586,38 @@ int X11_Xinput2IsInitialized(void)
#endif
}
SDL_bool X11_Xinput2SelectMouse(SDL_VideoDevice *_this, SDL_Window *window)
{
#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2
const SDL_VideoData *data = (SDL_VideoData *)_this->driverdata;
XIEventMask eventmask;
unsigned char mask[4] = { 0, 0, 0, 0 };
SDL_WindowData *window_data = (SDL_WindowData *)window->driverdata;
eventmask.mask_len = sizeof(mask);
eventmask.mask = mask;
eventmask.deviceid = XIAllDevices;
XISetMask(mask, XI_ButtonPress);
XISetMask(mask, XI_ButtonRelease);
XISetMask(mask, XI_Motion);
XISetMask(mask, XI_Enter);
XISetMask(mask, XI_Leave);
/* Hotplugging: */
XISetMask(mask, XI_DeviceChanged);
XISetMask(mask, XI_HierarchyChanged);
XISetMask(mask, XI_PropertyEvent); /* E.g., when swapping tablet pens */
if (X11_XISelectEvents(data->display,
window_data->xwindow,
&eventmask, 1) == Success) {
return SDL_TRUE;
}
SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "Could not enable XInput2 mouse event handling\n");
#endif
return SDL_FALSE;
}
int X11_Xinput2IsMultitouchSupported(void)
{
#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH

View File

@ -32,11 +32,12 @@ typedef struct XGenericEventCookie XGenericEventCookie;
extern void X11_InitXinput2(SDL_VideoDevice *_this);
extern void X11_InitXinput2Multitouch(SDL_VideoDevice *_this);
extern int X11_HandleXinput2Event(SDL_VideoData *videodata, XGenericEventCookie *cookie);
extern int X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie);
extern int X11_Xinput2IsInitialized(void);
extern int X11_Xinput2IsMultitouchSupported(void);
extern void X11_Xinput2SelectTouch(SDL_VideoDevice *_this, SDL_Window *window);
extern void X11_Xinput2GrabTouch(SDL_VideoDevice *_this, SDL_Window *window);
extern void X11_Xinput2UngrabTouch(SDL_VideoDevice *_this, SDL_Window *window);
extern SDL_bool X11_Xinput2SelectMouse(SDL_VideoDevice *_this, SDL_Window *window);
#endif /* SDL_x11xinput2_h_ */

View File

@ -328,6 +328,7 @@ add_sdl_test_executable(testgles2 SOURCES testgles2.c)
add_sdl_test_executable(testgles2_sdf NEEDS_RESOURCES TESTUTILS SOURCES testgles2_sdf.c)
add_sdl_test_executable(testhaptic SOURCES testhaptic.c)
add_sdl_test_executable(testhotplug SOURCES testhotplug.c)
add_sdl_test_executable(testpen SOURCES testpen.c)
add_sdl_test_executable(testrumble SOURCES testrumble.c)
add_sdl_test_executable(testthread NONINTERACTIVE NONINTERACTIVE_TIMEOUT 40 SOURCES testthread.c)
add_sdl_test_executable(testiconv NEEDS_RESOURCES TESTUTILS SOURCES testiconv.c)

View File

@ -33,6 +33,7 @@ static SDLTest_TestSuiteReference *testSuites[] = {
&mainTestSuite,
&mathTestSuite,
&mouseTestSuite,
&penTestSuite,
&pixelsTestSuite,
&platformTestSuite,
&propertiesTestSuite,

1908
test/testautomation_pen.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -20,6 +20,7 @@ extern SDLTest_TestSuiteReference keyboardTestSuite;
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;

496
test/testpen.c Normal file
View File

@ -0,0 +1,496 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
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.
*/
#include <math.h>
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <SDL3/SDL_test.h>
#include <SDL3/SDL_test_common.h>
#define WIDTH 1600
#define HEIGHT 1200
#define VERBOSE 0
#define ALWAYS_SHOW_PRESSURE_BOX 1
static SDLTest_CommonState *state;
static int quitting = 0;
static float last_x, last_y;
static float last_xtilt, last_ytilt, last_pressure, last_distance, last_rotation;
static int last_button;
static int last_touching; /* tip touches surface */
static int last_was_eraser;
static SDL_Texture *offscreen_texture = NULL;
static void DrawScreen(SDL_Renderer *renderer)
{
float xdelta, ydelta, endx, endy;
/* off-screen texture to render into */
SDL_Texture *window_texture;
const float X = 128.0f, Y = 128.0f; /* mid-point in the off-screen texture */
SDL_FRect dest_rect;
float tilt_vec_x = SDL_sinf(last_xtilt * SDL_PI_F / 180.0f);
float tilt_vec_y = SDL_sinf(last_ytilt * SDL_PI_F / 180.0f);
int color = last_button + 1;
if (!renderer) {
return;
}
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
SDL_SetRenderDrawColor(renderer, 0x40, 0x40, 0x40, 0xff);
SDL_RenderClear(renderer);
if (offscreen_texture == NULL) {
offscreen_texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, (int)(X * 2.0f), (int)(Y * 2.0f));
}
/* Render into off-screen texture so we can do pixel-precise rendering later */
window_texture = SDL_GetRenderTarget(renderer);
SDL_SetRenderTarget(renderer, offscreen_texture);
/* Rendering starts here */
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
SDL_SetRenderDrawColor(renderer, 0x40, 0x40, 0x40, 0xff);
SDL_RenderClear(renderer);
SDL_SetRenderDrawColor(renderer, 0xa0, 0xa0, 0xa0, 0xff);
if (last_touching) {
SDL_FRect rect;
rect.x = 0;
rect.y = 0;
rect.w = 2.0f * X - 1.0f;
rect.h = 2.0f * Y - 1.0f;
SDL_RenderRect(renderer, &rect);
} else {
/* Show where the pen is rotating when it isn't touching the surface.
Otherwise we draw the rotation angle below together with pressure information. */
float rot_vecx = SDL_sinf(last_rotation / 180.0f * SDL_PI_F);
float rot_vecy = -SDL_cosf(last_rotation / 180.0f * SDL_PI_F);
float px = X + rot_vecx * 100.0f;
float py = Y + rot_vecy * 100.0f;
float px2 = X + rot_vecx * 80.0f;
float py2 = Y + rot_vecy * 80.0f;
SDL_RenderLine(renderer,
px, py,
px2 + rot_vecy * 20.0f,
py2 - rot_vecx * 20.0f);
SDL_RenderLine(renderer,
px, py,
px2 - rot_vecy * 20.0f,
py2 + rot_vecx * 20.0f);
}
if (last_was_eraser) {
SDL_FRect rect;
rect.x = X - 10.0f;
rect.y = Y - 10.0f;
rect.w = 21.0f;
rect.h = 21.0f;
SDL_SetRenderDrawColor(renderer, 0x00, 0xff, 0xff, 0xff);
SDL_RenderFillRect(renderer, &rect);
} else {
float distance = last_distance * 50.0f;
SDL_SetRenderDrawColor(renderer, 0xff, 0, 0, 0xff);
SDL_RenderLine(renderer,
X - 10.0f - distance, Y,
X - distance, Y);
SDL_RenderLine(renderer,
X + 10.0f + distance, Y,
X + distance, Y);
SDL_RenderLine(renderer,
X, Y - 10.0f - distance,
X, Y - distance);
SDL_RenderLine(renderer,
X, Y + 10.0f + distance,
X, Y + distance);
}
/* Draw a cone based on the direction the pen is leaning as if it were shining a light. */
/* Colour derived from pens, intensity based on pressure: */
SDL_SetRenderDrawColor(renderer,
(color & 0x01) ? 0xff : 0,
(color & 0x02) ? 0xff : 0,
(color & 0x04) ? 0xff : 0,
(int)(0xff));
xdelta = -tilt_vec_x * 100.0f;
ydelta = -tilt_vec_y * 100.0f;
endx = X + xdelta;
endy = Y + ydelta;
SDL_RenderLine(renderer, X, Y, endx, endy);
SDL_SetRenderDrawColor(renderer,
(color & 0x01) ? 0xff : 0,
(color & 0x02) ? 0xff : 0,
(color & 0x04) ? 0xff : 0,
(int)(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 || (fabs(tilt_vec_x) < 0.2f && fabs(tilt_vec_y) < 0.2f)) {
int rot;
float pressure = last_pressure * 80.0f;
/* Four times, rotated 90 degrees, so that we get a box */
for (rot = 0; rot < 4; ++rot) {
float vecx = SDL_cosf((last_rotation + (rot * 90.0f)) / 180.0f * SDL_PI_F);
float vecy = SDL_sinf((last_rotation + (rot * 90.0f)) / 180.0f * SDL_PI_F);
float px = X + vecx * pressure;
float py = Y + vecy * pressure;
SDL_RenderLine(renderer,
px + vecy * 10.0f, py - vecx * 10.0f,
px - vecy * 10.0f, py + vecx * 10.0f);
if (rot == 3) {
int r = 0;
for (; r >= 0; r -= 2) {
float delta = 10.0f - ((float) r);
SDL_RenderLine(renderer,
px + vecy * delta, py - vecx * delta,
px + (vecx * pressure * 0.4f),
py + (vecy * pressure * 0.4f));
SDL_RenderLine(renderer,
px - vecy * delta, py + vecx * delta,
px + (vecx * pressure * 0.4f),
py + (vecy * pressure * 0.4f));
}
}
}
}
SDL_SetRenderTarget(renderer, window_texture);
/* Now render to pixel-precise position */
dest_rect.x = last_x - X;
dest_rect.y = last_y - Y;
dest_rect.w = X * 2.0f;
dest_rect.h = Y * 2.0f;
SDL_RenderTexture(renderer, offscreen_texture, NULL, &dest_rect);
SDL_RenderPresent(renderer);
}
static void dump_state(void)
{
int i;
int pens_nr;
/* Make sure this also works with a NULL parameter */
SDL_PenID* pens = SDL_GetPens(NULL);
if (pens) {
SDL_free(pens);
}
pens = SDL_GetPens(&pens_nr);
if (!pens) {
SDL_Log("Couldn't get pens: %s\n", SDL_GetError());
return;
}
SDL_Log("Found %d pens (terminated by %u)\n", pens_nr, (unsigned) pens[pens_nr]);
for (i = 0; i < pens_nr; ++i) {
SDL_PenID penid = pens[i];
SDL_GUID guid = SDL_GetPenGUID(penid);
char guid_str[33];
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);
Uint32 capabilities = SDL_GetPenCapabilities(penid, &info);
char *type;
char *buttons_str;
SDL_GUIDToString(guid, guid_str, 33);
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);
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 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) {
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
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[])
{
state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO);
if (!state) {
return 1;
}
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "2");
state->window_title = "Pressure-Sensitive Pen Test";
state->window_w = WIDTH;
state->window_h = HEIGHT;
state->skip_renderer = SDL_FALSE;
if (!SDLTest_CommonDefaultArgs(state, argc, argv) || !SDLTest_CommonInit(state)) {
SDLTest_CommonQuit(state);
return 1;
}
while (!quitting) {
loop();
}
SDLTest_CommonQuit(state);
return 0;
}