gdk_motion_event_push_history discards position information for some devices
Summary
gdk_motion_event_push_history
silently discards position data for certain extended input devices.
Event history relies on the GdkTimeCoord
structure, but this structure stores the axes and not the event position. For devices which do not populate X and Y axis data, the position information from the original event is lost.
When this happens, it makes the event history obtained via gdk_event_get_history
or gtk_gesture_stylus_get_backlog
completely unusable.
Steps to reproduce
- Find an extended input device that doesn't report X and Y position as axes (e.g. Wacom Intuos 4 on Linux)
- Run the attached demo program from the console example.c
- Scribble on the window with the stylus
- Note the console messages about missing X and Y position information, for example:
BUG: event[6]/backlog[0] has no XY position (GdkTimeCoord::flags = 000002e0)
Motion events received: 6
Backlog events received: 4
Backlog events missing XY position: 4
Current behavior
For such devices, GdkEvent::history
does not contain any X or Y position information, making the backlog unusable for most purposes.
Expected outcome
X and Y position should always be recorded in GdkTimeCoord
using GDK_AXIS_X and GDK_AXIS_Y (and GdkTimeCoord::flags
should reflect this), even if the underlying device does not report X and Y as axes itself.
Version information
GTK 4.4.0 Distributor ID: Ubuntu Description: Ubuntu 21.10 Release: 21.10 Codename: impish
Additional information
One the face of it, the existing behavior is correct, in that the GDK API does not promise that X and Y position will be available as axes; users of the API are cautioned not to rely on it. This is reflected in gdkenums.h
:
/**
* GdkAxisUse:
* ...
*
* Note that the X and Y axes are not really needed; pointer devices
* report their location via the x/y members of events regardless. Whether
* X and Y are present as axes depends on the GDK backend.
*/
However, in the case of GdkTimeCoord
, there's nowhere else to record the X and Y position!
In fact, for plain input devices, synthetic X and Y axis data is already populated (from gdkevents.c
):
if (tool)
{
hist.flags = gdk_device_tool_get_axes (tool);
for (i = GDK_AXIS_X; i < GDK_AXIS_LAST; i++)
gdk_event_get_axis (history_event, i, &hist.axes[i]);
}
else
{
hist.flags = GDK_AXIS_FLAG_X | GDK_AXIS_FLAG_Y;
gdk_event_get_position (history_event, &hist.axes[GDK_AXIS_X], &hist.axes[GDK_AXIS_Y]);
}
It would be simple to unconditionally populate the X and Y axes:
hist.flags = GDK_AXIS_FLAG_X | GDK_AXIS_FLAG_Y;
if (tool)
{
hist.flags |= gdk_device_tool_get_axes (tool);
for (i = GDK_AXIS_X; i < GDK_AXIS_LAST; i++)
gdk_event_get_axis (history_event, i, &hist.axes[i]);
}
gdk_event_get_position (history_event, &hist.axes[GDK_AXIS_X], &hist.axes[GDK_AXIS_Y]);
Or, a more conservative approach would be to only add synthetic X and Y axes when they are missing:
hist.flags = 0;
if (tool)
{
hist.flags |= gdk_device_tool_get_axes (tool);
for (i = GDK_AXIS_X; i < GDK_AXIS_LAST; i++)
gdk_event_get_axis (history_event, i, &hist.axes[i]);
}
if (!(hist.flags & (GDK_AXIS_FLAG_X | GDK_AXIS_FLAG_Y)))
{
hist.flags |= GDK_AXIS_FLAG_X | GDK_AXIS_FLAG_Y;
gdk_event_get_position (history_event, &hist.axes[GDK_AXIS_X], &hist.axes[GDK_AXIS_Y]);
}
This would still cover both cases:
- extended input devices which don't provide XY axis data
- devices which don't support extended input at all