ximagesink: Add touch event support

Send touch events for XI_TouchBegin, XI_TouchEnd, and XI_TouchUpdate
events, grouping events with identical timestamps into one TOUCH_FRAME.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/1633>
This commit is contained in:
Vivienne Watermeier 2022-02-03 15:01:46 +01:00 committed by GStreamer Marge Bot
parent 97bc8f193f
commit 3b7f397863
5 changed files with 318 additions and 7 deletions

View file

@ -65,6 +65,7 @@ option('vorbis', type : 'feature', value : 'auto', description : 'Vorbis audio p
option('x11', type : 'feature', value : 'auto', description : 'X11 ximagesink plugin, and X11 support in libraries, plugins, examples')
option('xshm', type : 'feature', value : 'auto', description : 'X11 shared memory support for X11 plugins')
option('xvideo', type : 'feature', value : 'auto', description : 'X11 XVideo xvimagesink plugin')
option('xi', type : 'feature', value : 'auto', description : 'X11 input extension for touch support in ximagesink')
# Common feature options
option('examples', type : 'feature', value : 'auto', yield : true)

View file

@ -2,7 +2,9 @@ if x11_dep.found()
# XShm is only used by ximage and xvimage
# FIXME: Need to check for XShmAttach inside libXext
xshm_dep = dependency('xext', required : get_option('xshm'))
xi_dep = dependency('xi', required : get_option('xi'))
core_conf.set('HAVE_XSHM', xshm_dep.found())
core_conf.set('HAVE_XI2', xi_dep.found())
subdir('ximage')
subdir('xvimage')

View file

@ -8,7 +8,7 @@ gstximage = library('gstximagesink',
'ximagesink.c', 'ximage.c', 'ximagepool.c',
c_args : gst_plugins_base_args + no_warn_args,
include_directories: [configinc, libsinc],
dependencies : glib_deps + [video_dep, gst_base_dep, gst_dep, x11_dep, xshm_dep],
dependencies : glib_deps + [video_dep, gst_base_dep, gst_dep, x11_dep, xshm_dep, xi_dep],
install : true,
install_dir : plugins_install_dir,
)

View file

@ -115,6 +115,11 @@
/* for XkbKeycodeToKeysym */
#include <X11/XKBlib.h>
/* used for touchscreen events */
#ifdef HAVE_XI2
#include <X11/extensions/XInput2.h>
#endif
GST_DEBUG_CATEGORY_EXTERN (gst_debug_x_image_pool);
GST_DEBUG_CATEGORY (gst_debug_x_image_sink);
GST_DEBUG_CATEGORY_STATIC (CAT_PERFORMANCE);
@ -432,6 +437,98 @@ gst_x_image_sink_xwindow_set_title (GstXImageSink * ximagesink,
}
}
#ifdef HAVE_XI2
static void
gst_x_image_sink_xtouchdevice_free (GstXTouchDevice * device)
{
g_free (device->name);
}
static void
gst_x_image_sink_xwindow_select_touch_events (GstXImageSink * ximagesink,
GstXWindow * xwindow)
{
XIDeviceInfo *devices;
int ndevices, i, j, mask_len;
unsigned char *mask;
ximagesink->touch_devices = g_array_remove_range (ximagesink->touch_devices,
0, ximagesink->touch_devices->len);
mask_len = (XI_LASTEVENT + 7) << 3;
mask = g_new0 (unsigned char, mask_len);
XISetMask (mask, XI_TouchBegin);
XISetMask (mask, XI_TouchUpdate);
XISetMask (mask, XI_TouchEnd);
devices = XIQueryDevice (ximagesink->xcontext->disp, XIAllDevices, &ndevices);
/* Find suitable touch screen devices, and select touch events for each */
for (i = 0; i < ndevices; i++) {
XIEventMask mask_data;
GstXTouchDevice temp, *device;
gboolean has_touch = FALSE;
if (devices[i].use != XISlavePointer)
continue;
temp.pressure_valuator = -1;
temp.id = devices[i].deviceid;
temp.name = devices[i].name;
for (j = 0; j < devices[i].num_classes; j++) {
XIAnyClassInfo *class = devices[i].classes[j];
/* only pick devices with direct touch, to avoid touchpads and similar */
if (class->type == XITouchClass &&
((XITouchClassInfo *) class)->mode == XIDirectTouch) {
has_touch = TRUE;
}
/* Find the valuator representing pressure, if present */
if (class->type == XIValuatorClass) {
XIValuatorClassInfo *val_info = (XIValuatorClassInfo *) class;
if (val_info->label == XInternAtom (ximagesink->xcontext->disp,
"Abs Pressure", TRUE))
temp.abs_pressure = TRUE;
else if (val_info->label == XInternAtom (ximagesink->xcontext->disp,
"Rel Pressure", TRUE))
temp.abs_pressure = FALSE;
else
continue;
temp.pressure_valuator = i;
temp.pressure_min = val_info->min;
temp.pressure_max = val_info->max;
temp.current_pressure = temp.pressure_min;
}
}
if (has_touch) {
GST_DEBUG ("found%s touch screen with id %d: %s",
temp.pressure_valuator < 0 ? "" : (temp.abs_pressure ?
"pressure-sensitive (abs)" : "pressure-sensitive (rel)"),
temp.id, temp.name);
device = g_new (GstXTouchDevice, 1);
*device = temp;
device->name = g_strdup (device->name);
ximagesink->touch_devices =
g_array_append_vals (ximagesink->touch_devices, device, 1);
mask_data.deviceid = temp.id;
mask_data.mask_len = mask_len;
mask_data.mask = mask;
XISelectEvents (ximagesink->xcontext->disp, xwindow->win, &mask_data, 1);
}
}
g_free (mask);
XIFreeDeviceInfo (devices);
}
#endif
/* This function handles a GstXWindow creation */
static GstXWindow *
gst_x_image_sink_xwindow_new (GstXImageSink * ximagesink, gint width,
@ -474,6 +571,23 @@ gst_x_image_sink_xwindow_new (GstXImageSink * ximagesink, gint width,
"WM_DELETE_WINDOW", False);
(void) XSetWMProtocols (ximagesink->xcontext->disp, xwindow->win,
&wm_delete, 1);
#ifdef HAVE_XI2
if (ximagesink->xcontext->use_xi2) {
XIEventMask mask_data;
unsigned char mask[2];
gst_x_image_sink_xwindow_select_touch_events (ximagesink, xwindow);
XISetMask (mask, XI_HierarchyChanged);
mask_data.deviceid = XIAllDevices;
mask_data.mask_len = sizeof (mask);
mask_data.mask = mask;
/* register for HierarchyChanged events to see device changes */
XISelectEvents (ximagesink->xcontext->disp, xwindow->win, &mask_data, 1);
}
#endif
}
xwindow->gc = XCreateGC (ximagesink->xcontext->disp, xwindow->win,
@ -577,7 +691,7 @@ gst_x_image_sink_handle_xevents (GstXImageSink * ximagesink)
{
XEvent e;
gint pointer_x = 0, pointer_y = 0;
gboolean pointer_moved = FALSE;
gboolean pointer_moved = FALSE, touch_frame_open = FALSE;
gboolean exposed = FALSE, configured = FALSE;
g_return_if_fail (GST_IS_X_IMAGE_SINK (ximagesink));
@ -708,11 +822,11 @@ gst_x_image_sink_handle_xevents (GstXImageSink * ximagesink)
g_mutex_lock (&ximagesink->x_lock);
}
/* Handle Display events */
while (XPending (ximagesink->xcontext->disp)) {
XNextEvent (ximagesink->xcontext->disp, &e);
switch (e.type) {
/* Handle Display events */
case ClientMessage:{
Atom wm_delete;
@ -730,13 +844,137 @@ gst_x_image_sink_handle_xevents (GstXImageSink * ximagesink)
}
break;
}
default:{
#ifdef HAVE_XI2
/* Handle XInput2 touch screen events */
if (ximagesink->xcontext->use_xi2
&& XGetEventData (ximagesink->xcontext->disp,
(XGenericEventCookie *) & e)
&& e.xcookie.extension == ximagesink->xcontext->xi_opcode) {
XGenericEventCookie *cookie;
XIDeviceEvent *touch;
GstXTouchDevice device;
GstEvent *nav;
gdouble pressure;
gboolean device_found;
unsigned int ev_id, i;
cookie = &e.xcookie;
nav = NULL;
if (cookie->evtype == XI_HierarchyChanged) {
GST_DEBUG ("ximagesink devices changed, searching for "
"touch devices again");
gst_x_image_sink_xwindow_select_touch_events (ximagesink,
ximagesink->xwindow);
break;
}
if (cookie->evtype != XI_TouchBegin
&& cookie->evtype != XI_TouchUpdate
&& cookie->evtype != XI_TouchEnd)
break;
touch = cookie->data;
ev_id = ((unsigned int) touch->deviceid) << 16 |
(((unsigned int) touch->detail) & 0x00ff);
/* find device that the event belongs to */
device_found = FALSE;
for (i = 0; i < ximagesink->touch_devices->len; i++) {
device = g_array_index (ximagesink->touch_devices,
GstXTouchDevice, i);
if (device.id == touch->deviceid) {
device_found = TRUE;
break;
}
}
if (!device_found)
break;
if (device.pressure_valuator >= 0) {
pressure = touch->valuators.values[device.pressure_valuator];
pressure -= device.pressure_min;
pressure /= device.pressure_max - device.pressure_min;
if (!device.abs_pressure)
pressure += device.current_pressure;
} else
pressure = NAN;
device.current_pressure = pressure;
g_mutex_unlock (&ximagesink->x_lock);
g_mutex_unlock (&ximagesink->flow_lock);
/* assume the event queue is ordered chronologically, and end */
/* the previous touch event frame when the timestamp increases */
if (touch->time != ximagesink->last_touch && touch_frame_open) {
if (touch->time < ximagesink->last_touch) {
GST_WARNING ("ximagesink out of order touch event "
"with timestamp %lu, not ending touch frame", touch->time);
} else {
GST_DEBUG ("ximagesink ending touch frame for %lu",
ximagesink->last_touch);
gst_navigation_send_event_simple (GST_NAVIGATION (ximagesink),
gst_navigation_event_new_touch_frame ());
}
}
switch (cookie->evtype) {
case XI_TouchBegin:{
GST_DEBUG ("ximagesink new touch point %d on device %d "
"at %.0f,%.0f", touch->detail, touch->deviceid,
touch->event_x, touch->event_y);
nav = gst_navigation_event_new_touch_down (ev_id, touch->event_x,
touch->event_y, pressure);
break;
}
case XI_TouchEnd:{
GST_DEBUG ("ximagesink removed touch point %d from device %d "
"at %.0f,%.0f", touch->detail, touch->deviceid,
touch->event_x, touch->event_y);
nav = gst_navigation_event_new_touch_up (ev_id, touch->event_x,
touch->event_y);
break;
}
case XI_TouchUpdate:{
GST_DEBUG ("ximagesink touch point %d on device %d moved "
"to %.0f,%.0f", touch->detail, touch->deviceid,
touch->event_x, touch->event_y);
nav = gst_navigation_event_new_touch_motion (ev_id,
touch->event_x, touch->event_y, pressure);
break;
}
default:
break;
}
gst_navigation_send_event_simple (GST_NAVIGATION (ximagesink), nav);
ximagesink->last_touch = touch->time;
touch_frame_open = TRUE;
g_mutex_lock (&ximagesink->flow_lock);
g_mutex_lock (&ximagesink->x_lock);
XFreeEventData (ximagesink->xcontext->disp, cookie);
}
#endif
break;
}
}
}
g_mutex_unlock (&ximagesink->x_lock);
g_mutex_unlock (&ximagesink->flow_lock);
#ifdef HAVE_XI2
/* send a touch-frame event now to avoid delay from having to wait for the */
/* next invocation */
if (touch_frame_open) {
GST_DEBUG ("ximagesink ending touch frame for %lu", ximagesink->last_touch);
gst_navigation_send_event_simple (GST_NAVIGATION (ximagesink),
gst_navigation_event_new_touch_frame ());
}
#endif
}
static gpointer
@ -872,8 +1110,12 @@ gst_x_image_sink_xcontext_get (GstXImageSink * ximagesink)
GstVideoFormat vformat;
guint32 alpha_mask;
int opcode, event, err;
int major = XkbMajorVersion;
int minor = XkbMinorVersion;
int xkb_major = XkbMajorVersion;
int xkb_minor = XkbMinorVersion;
#ifdef HAVE_XI2
int xi2_major = XI_2_Major;
int xi2_minor = XI_2_Minor;
#endif
g_return_val_if_fail (GST_IS_X_IMAGE_SINK (ximagesink), NULL);
@ -945,7 +1187,8 @@ gst_x_image_sink_xcontext_get (GstXImageSink * ximagesink)
xcontext->use_xshm = FALSE;
GST_DEBUG ("ximagesink is not using XShm extension");
}
if (XkbQueryExtension (xcontext->disp, &opcode, &event, &err, &major, &minor)) {
if (XkbQueryExtension (xcontext->disp, &opcode, &event, &err,
&xkb_major, &xkb_minor)) {
xcontext->use_xkb = TRUE;
GST_DEBUG ("ximagesink is using Xkb extension");
} else {
@ -953,6 +1196,22 @@ gst_x_image_sink_xcontext_get (GstXImageSink * ximagesink)
GST_DEBUG ("ximagesink is not using Xkb extension");
}
/* Search for XInput extension support */
#ifdef HAVE_XI2
if (XQueryExtension (xcontext->disp, "XInputExtension",
&(xcontext->xi_opcode), &event, &err)
&& (XIQueryVersion (xcontext->disp, &xi2_major, &xi2_minor) == Success
&& (xi2_major > 2 || (xi2_major == 2 && xi2_minor >= 2)))) {
xcontext->use_xi2 = TRUE;
GST_DEBUG ("ximagesink is using XInput extension");
} else
#endif /* HAVE_XI2 */
{
xcontext->use_xi2 = FALSE;
GST_DEBUG ("ximagesink is not using XInput extension: "
"XInput is not present");
}
/* extrapolate alpha mask */
if (xcontext->depth == 32) {
alpha_mask = ~(xcontext->visual->red_mask
@ -1865,6 +2124,11 @@ gst_x_image_sink_reset (GstXImageSink * ximagesink)
g_mutex_lock (&ximagesink->flow_lock);
#ifdef HAVE_XI2
ximagesink->touch_devices = g_array_remove_range (ximagesink->touch_devices,
0, ximagesink->touch_devices->len);
#endif
if (ximagesink->pool) {
gst_object_unref (ximagesink->pool);
ximagesink->pool = NULL;
@ -1897,6 +2161,9 @@ gst_x_image_sink_finalize (GObject * object)
g_free (ximagesink->par);
ximagesink->par = NULL;
}
#ifdef HAVE_XI2
g_array_free (ximagesink->touch_devices, TRUE);
#endif
g_mutex_clear (&ximagesink->x_lock);
g_mutex_clear (&ximagesink->flow_lock);
@ -1919,6 +2186,14 @@ gst_x_image_sink_init (GstXImageSink * ximagesink)
ximagesink->fps_n = 0;
ximagesink->fps_d = 1;
#ifdef HAVE_XI2
ximagesink->last_touch = 0;
ximagesink->touch_devices = g_array_new (FALSE, FALSE,
sizeof (GstXTouchDevice));
g_array_set_clear_func (ximagesink->touch_devices,
(GDestroyNotify) gst_x_image_sink_xtouchdevice_free);
#endif
g_mutex_init (&ximagesink->x_lock);
g_mutex_init (&ximagesink->flow_lock);

View file

@ -55,6 +55,7 @@ G_BEGIN_DECLS
typedef struct _GstXContext GstXContext;
typedef struct _GstXWindow GstXWindow;
typedef struct _GstXTouchDevice GstXTouchDevice;
typedef struct _GstXImageSink GstXImageSink;
typedef struct _GstXImageSinkClass GstXImageSinkClass;
@ -83,6 +84,9 @@ typedef struct _GstXImageSinkClass GstXImageSinkClass;
* if the Extension is present
* @use_xkb: used to known wether of not Xkb extension is usable or not even
* if the Extension is present
* @use_xi2: used to known wether of not XInput2 extension is usable or not even
* if the Extension is present
* @xi_opcode: XInput opcode
* @caps: the #GstCaps that Display @disp can accept
*
* Structure used to store various information collected/calculated for a
@ -110,6 +114,9 @@ struct _GstXContext
gboolean use_xshm;
gboolean use_xkb;
gboolean use_xi2;
int xi_opcode;
GstCaps *caps;
GstCaps *last_caps;
@ -134,6 +141,25 @@ struct _GstXWindow
GC gc;
};
/*
* GstXTouchDevice:
* @name: the name of this decive
* @id: the device ID of this device
* @pressure_valuator: index of the valuator that encodes pressure data, if present
* @abs_pressure: if pressure is reported in absolute or relative values
* @current_pressure: stores the most recent pressure value for this device
* @pressure_min: lowest possible pressure value
* @pressure_max: highest possible pressure value
*
* Structure used to store information about a touchscreen device.
*/
struct _GstXTouchDevice {
gchar *name;
gint id, pressure_valuator;
gboolean abs_pressure;
gdouble current_pressure, pressure_min, pressure_max;
};
/**
* GstXImageSink:
* @display_name: the name of the Display we want to render to
@ -147,6 +173,8 @@ struct _GstXWindow
* @running: used to inform @event_thread if it should run/shutdown
* @fps_n: the framerate fraction numerator
* @fps_d: the framerate fraction denominator
* @last_touch: timestamp of the last received touch event
* @touch_devices: list of known touchscreen devices
* @x_lock: used to protect X calls as we are not using the XLib in threaded
* mode
* @flow_lock: used to protect data flow routines from external calls such as
@ -183,6 +211,11 @@ struct _GstXImageSink
gint fps_n;
gint fps_d;
#ifdef HAVE_XI2
Time last_touch;
GArray *touch_devices;
#endif
GMutex x_lock;
GMutex flow_lock;