xvimagesink: 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-03-22 21:13:31 +01:00 committed by GStreamer Marge Bot
parent 3b7f397863
commit 9389754d1a
4 changed files with 328 additions and 7 deletions

View file

@ -19,7 +19,7 @@ if xvideo_dep.found()
xvimage_sources, xvimage_sources,
c_args : gst_plugins_base_args + no_warn_args, c_args : gst_plugins_base_args + no_warn_args,
include_directories: [configinc, libsinc], include_directories: [configinc, libsinc],
dependencies : glib_deps + [video_dep, gst_base_dep, gst_dep, x11_dep, xshm_dep, xvideo_dep, libm], dependencies : glib_deps + [video_dep, gst_base_dep, gst_dep, x11_dep, xshm_dep, xvideo_dep, xi_dep, libm],
install : true, install : true,
install_dir : plugins_install_dir, install_dir : plugins_install_dir,
) )

View file

@ -33,6 +33,11 @@
/* for XkbKeycodeToKeysym */ /* for XkbKeycodeToKeysym */
#include <X11/XKBlib.h> #include <X11/XKBlib.h>
/* for touchscreen events */
#ifdef HAVE_XI2
#include <X11/extensions/XInput2.h>
#endif
GST_DEBUG_CATEGORY (gst_debug_xv_context); GST_DEBUG_CATEGORY (gst_debug_xv_context);
#define GST_CAT_DEFAULT gst_debug_xv_context #define GST_CAT_DEFAULT gst_debug_xv_context
@ -642,8 +647,12 @@ gst_xvcontext_new (GstXvContextConfig * config, GError ** error)
"XV_BRIGHTNESS", "XV_CONTRAST" "XV_BRIGHTNESS", "XV_CONTRAST"
}; };
int opcode, event, err; int opcode, event, err;
int major = XkbMajorVersion; int xkb_major = XkbMajorVersion;
int minor = XkbMinorVersion; 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 (config != NULL, NULL); g_return_val_if_fail (config != NULL, NULL);
@ -726,7 +735,8 @@ gst_xvcontext_new (GstXvContextConfig * config, GError ** error)
context->use_xshm = FALSE; context->use_xshm = FALSE;
GST_DEBUG ("xvimagesink is not using XShm extension"); GST_DEBUG ("xvimagesink is not using XShm extension");
} }
if (XkbQueryExtension (context->disp, &opcode, &event, &err, &major, &minor)) { if (XkbQueryExtension (context->disp, &opcode, &event, &err, &xkb_major,
&xkb_minor)) {
context->use_xkb = TRUE; context->use_xkb = TRUE;
GST_DEBUG ("xvimagesink is using Xkb extension"); GST_DEBUG ("xvimagesink is using Xkb extension");
} else { } else {
@ -734,6 +744,22 @@ gst_xvcontext_new (GstXvContextConfig * config, GError ** error)
GST_DEBUG ("xvimagesink is not using Xkb extension"); GST_DEBUG ("xvimagesink is not using Xkb extension");
} }
/* Search for XInput extension support */
#ifdef HAVE_XI2
if (XQueryExtension (context->disp, "XInputExtension",
&(context->xi_opcode), &event, &err)
&& (XIQueryVersion (context->disp, &xi2_major, &xi2_minor) == Success
&& (xi2_major > 2 || (xi2_major == 2 && xi2_minor >= 2)))) {
context->use_xi2 = TRUE;
GST_DEBUG ("xvimagesink is using XInput extension");
} else
#endif /* HAVE_XI2 */
{
context->use_xi2 = FALSE;
GST_DEBUG ("xvimagesink is not using XInput extension: "
"XInput is not present");
}
xv_attr = XvQueryPortAttributes (context->disp, context->xv_port_id, &N_attr); xv_attr = XvQueryPortAttributes (context->disp, context->xv_port_id, &N_attr);
/* Generate the channels list */ /* Generate the channels list */
@ -943,6 +969,14 @@ gst_xvcontext_set_colorimetry (GstXvContext * context,
g_mutex_unlock (&context->lock); g_mutex_unlock (&context->lock);
} }
#ifdef HAVE_XI2
static void
gst_xv_touchdevice_free (GstXvTouchDevice * device)
{
g_free (device->name);
}
#endif
GstXWindow * GstXWindow *
gst_xvcontext_create_xwindow (GstXvContext * context, gint width, gint height) gst_xvcontext_create_xwindow (GstXvContext * context, gint width, gint height)
{ {
@ -964,6 +998,13 @@ gst_xvcontext_create_xwindow (GstXvContext * context, gint width, gint height)
window->height = height; window->height = height;
window->internal = TRUE; window->internal = TRUE;
#ifdef HAVE_XI2
window->last_touch = 0;
window->touch_devices = g_array_new (FALSE, FALSE, sizeof (GstXvTouchDevice));
g_array_set_clear_func (window->touch_devices,
(GDestroyNotify) gst_xv_touchdevice_free);
#endif
g_mutex_lock (&context->lock); g_mutex_lock (&context->lock);
window->win = XCreateSimpleWindow (context->disp, window->win = XCreateSimpleWindow (context->disp,
@ -1033,6 +1074,13 @@ gst_xvcontext_create_xwindow_from_xid (GstXvContext * context, XID xid)
window->render_rect.w = attr.width; window->render_rect.w = attr.width;
window->render_rect.h = attr.height; window->render_rect.h = attr.height;
#ifdef HAVE_XI2
window->last_touch = 0;
window->touch_devices = g_array_new (FALSE, FALSE, sizeof (GstXvTouchDevice));
g_array_set_clear_func (window->touch_devices,
(GDestroyNotify) gst_xv_touchdevice_free);
#endif
window->gc = XCreateGC (context->disp, window->win, 0, NULL); window->gc = XCreateGC (context->disp, window->win, 0, NULL);
g_mutex_unlock (&context->lock); g_mutex_unlock (&context->lock);
@ -1050,6 +1098,10 @@ gst_xwindow_destroy (GstXWindow * window)
g_mutex_lock (&context->lock); g_mutex_lock (&context->lock);
#ifdef HAVE_XI2
g_array_free (window->touch_devices, TRUE);
#endif
/* If we did not create that window we just free the GC and let it live */ /* If we did not create that window we just free the GC and let it live */
if (window->internal) if (window->internal)
XDestroyWindow (context->disp, window->win); XDestroyWindow (context->disp, window->win);
@ -1067,6 +1119,91 @@ gst_xwindow_destroy (GstXWindow * window)
g_slice_free1 (sizeof (GstXWindow), window); g_slice_free1 (sizeof (GstXWindow), window);
} }
#ifdef HAVE_XI2
void
gst_xwindow_select_touch_events (GstXvContext * context, GstXWindow * window)
{
XIDeviceInfo *devices;
int ndevices, i, j, mask_len;
unsigned char *mask;
window->touch_devices = g_array_remove_range (window->touch_devices,
0, window->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 (window->context->disp, XIAllDevices, &ndevices);
/* Find suitable touch screen devices, and select touch events for each */
for (i = 0; i < ndevices; i++) {
XIEventMask mask_data;
GstXvTouchDevice 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 (context->disp,
"Abs Pressure", TRUE))
temp.abs_pressure = TRUE;
else if (val_info->label == XInternAtom (context->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 (GstXvTouchDevice, 1);
*device = temp;
device->name = g_strdup (device->name);
window->touch_devices =
g_array_append_vals (window->touch_devices, device, 1);
mask_data.deviceid = temp.id;
mask_data.mask_len = mask_len;
mask_data.mask = mask;
XISelectEvents (context->disp, window->win, &mask_data, 1);
}
}
g_free (mask);
XIFreeDeviceInfo (devices);
}
#endif
void void
gst_xwindow_set_event_handling (GstXWindow * window, gboolean handle_events) gst_xwindow_set_event_handling (GstXWindow * window, gboolean handle_events)
{ {
@ -1087,6 +1224,23 @@ gst_xwindow_set_event_handling (GstXWindow * window, gboolean handle_events)
ExposureMask | StructureNotifyMask | PointerMotionMask | ExposureMask | StructureNotifyMask | PointerMotionMask |
KeyPressMask | KeyReleaseMask); KeyPressMask | KeyReleaseMask);
} }
#ifdef HAVE_XI2
if (context->use_xi2) {
XIEventMask mask_data;
unsigned char mask[2];
gst_xwindow_select_touch_events (context, window);
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 (context->disp, window->win, &mask_data, 1);
}
#endif
} else { } else {
XSelectInput (context->disp, window->win, 0); XSelectInput (context->disp, window->win, 0);
} }

View file

@ -47,6 +47,7 @@ G_BEGIN_DECLS
typedef struct _GstXvContextConfig GstXvContextConfig; typedef struct _GstXvContextConfig GstXvContextConfig;
typedef struct _GstXvImageFormat GstXvImageFormat; typedef struct _GstXvImageFormat GstXvImageFormat;
typedef struct _GstXvContext GstXvContext; typedef struct _GstXvContext GstXvContext;
typedef struct _GstXvTouchDevice GstXvTouchDevice;
/** /**
* GstXvContextConfig: * GstXvContextConfig:
@ -112,6 +113,9 @@ struct _GstXvImageFormat
* if the Extension is present * if the Extension is present
* @use_xkb: used to known wether of not Xkb extension is usable or not even * @use_xkb: used to known wether of not Xkb extension is usable or not even
* if the Extension is present * if the Extension is present
* @use_xi2: used to known wether of not XInput extension is usable or not even
* if the Extension is present
* @xi_opcode: XInput opcode
* @xv_port_id: the XVideo port ID * @xv_port_id: the XVideo port ID
* @im_format: used to store at least a valid format for XShm calls checks * @im_format: used to store at least a valid format for XShm calls checks
* @formats_list: list of supported image formats on @xv_port_id * @formats_list: list of supported image formats on @xv_port_id
@ -148,6 +152,8 @@ struct _GstXvContext
gboolean use_xshm; gboolean use_xshm;
gboolean use_xkb; gboolean use_xkb;
gboolean use_xi2;
int xi_opcode;
XvPortID xv_port_id; XvPortID xv_port_id;
guint nb_adaptors; guint nb_adaptors;
@ -217,6 +223,8 @@ typedef struct _GstXWindow GstXWindow;
* @internal: used to remember if Window @win was created internally or passed * @internal: used to remember if Window @win was created internally or passed
* through the #GstVideoOverlay interface * through the #GstVideoOverlay interface
* @gc: the Graphical Context of Window @win * @gc: the Graphical Context of Window @win
* @last_touch: timestamp of the last received touch event
* @touch_devices: list of known touchscreen devices
* *
* Structure used to store information about a Window. * Structure used to store information about a Window.
*/ */
@ -230,6 +238,30 @@ struct _GstXWindow
GstVideoRectangle render_rect; GstVideoRectangle render_rect;
gboolean internal; gboolean internal;
GC gc; GC gc;
#ifdef HAVE_XI2
Time last_touch;
GArray *touch_devices;
#endif
};
/*
* GstXvTouchDevice:
* @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 _GstXvTouchDevice {
gchar *name;
gint id, pressure_valuator;
gboolean abs_pressure;
gdouble current_pressure, pressure_min, pressure_max;
}; };
G_END_DECLS G_END_DECLS
@ -240,6 +272,10 @@ GstXWindow * gst_xvcontext_create_xwindow_from_xid (GstXvContext * context, XI
void gst_xwindow_destroy (GstXWindow * window); void gst_xwindow_destroy (GstXWindow * window);
#ifdef HAVE_XI2
void gst_xwindow_select_touch_events (GstXvContext * context, GstXWindow * window);
#endif
void gst_xwindow_set_event_handling (GstXWindow * window, gboolean handle_events); void gst_xwindow_set_event_handling (GstXWindow * window, gboolean handle_events);
void gst_xwindow_set_title (GstXWindow * window, const gchar * title); void gst_xwindow_set_title (GstXWindow * window, const gchar * title);

View file

@ -132,6 +132,11 @@
/* for XkbKeycodeToKeysym */ /* for XkbKeycodeToKeysym */
#include <X11/XKBlib.h> #include <X11/XKBlib.h>
/* for touchscreen events */
#ifdef HAVE_XI2
#include <X11/extensions/XInput2.h>
#endif
GST_DEBUG_CATEGORY_EXTERN (gst_debug_xv_context); GST_DEBUG_CATEGORY_EXTERN (gst_debug_xv_context);
GST_DEBUG_CATEGORY_EXTERN (gst_debug_xv_image_pool); GST_DEBUG_CATEGORY_EXTERN (gst_debug_xv_image_pool);
GST_DEBUG_CATEGORY (gst_debug_xv_image_sink); GST_DEBUG_CATEGORY (gst_debug_xv_image_sink);
@ -418,7 +423,7 @@ gst_xv_image_sink_handle_xevents (GstXvImageSink * xvimagesink)
{ {
XEvent e; XEvent e;
gint pointer_x = 0, pointer_y = 0; gint pointer_x = 0, pointer_y = 0;
gboolean pointer_moved = FALSE; gboolean pointer_moved = FALSE, touch_frame_open = FALSE;
gboolean exposed = FALSE, configured = FALSE; gboolean exposed = FALSE, configured = FALSE;
g_return_if_fail (GST_IS_XV_IMAGE_SINK (xvimagesink)); g_return_if_fail (GST_IS_XV_IMAGE_SINK (xvimagesink));
@ -557,11 +562,11 @@ gst_xv_image_sink_handle_xevents (GstXvImageSink * xvimagesink)
g_mutex_lock (&xvimagesink->context->lock); g_mutex_lock (&xvimagesink->context->lock);
} }
/* Handle Display events */
while (XPending (xvimagesink->context->disp)) { while (XPending (xvimagesink->context->disp)) {
XNextEvent (xvimagesink->context->disp, &e); XNextEvent (xvimagesink->context->disp, &e);
switch (e.type) { switch (e.type) {
/* Handle Display events */
case ClientMessage:{ case ClientMessage:{
Atom wm_delete; Atom wm_delete;
@ -579,13 +584,139 @@ gst_xv_image_sink_handle_xevents (GstXvImageSink * xvimagesink)
} }
break; break;
} }
default:{
#ifdef HAVE_XI2
/* Handle XInput2 touch screen events */
if (xvimagesink->context->use_xi2
&& XGetEventData (xvimagesink->context->disp,
(XGenericEventCookie *) & e)
&& e.xcookie.extension == xvimagesink->context->xi_opcode) {
XGenericEventCookie *cookie;
XIDeviceEvent *touch;
GstXvTouchDevice device;
GstXWindow *xwindow;
GstEvent *nav;
gdouble pressure;
gboolean device_found;
unsigned int ev_id, i;
cookie = &e.xcookie;
xwindow = xvimagesink->xwindow;
nav = NULL;
if (cookie->evtype == XI_HierarchyChanged) {
GST_DEBUG ("ximagesink devices changed, searching for "
"touch devices again");
gst_xwindow_select_touch_events (xvimagesink->context, 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 < xwindow->touch_devices->len; i++) {
device = g_array_index (xwindow->touch_devices,
GstXvTouchDevice, 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 (&xvimagesink->context->lock);
g_mutex_unlock (&xvimagesink->flow_lock);
/* assume the event queue is ordered chronologically, and end */
/* the previous touch event frame when the timestamp increases */
if (touch->time != xwindow->last_touch && touch_frame_open) {
if (touch->time < xwindow->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",
xwindow->last_touch);
gst_navigation_send_event_simple (GST_NAVIGATION (xvimagesink),
gst_navigation_event_new_touch_frame ());
}
}
switch (cookie->evtype) {
case XI_TouchBegin:{
GST_DEBUG ("xvimagesink 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 ("xvimagesink 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 ("xvimagesink 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: default:
break; break;
} }
gst_navigation_send_event_simple (GST_NAVIGATION (xvimagesink), nav);
xvimagesink->xwindow->last_touch = touch->time;
touch_frame_open = TRUE;
g_mutex_lock (&xvimagesink->flow_lock);
g_mutex_lock (&xvimagesink->context->lock);
XFreeEventData (xvimagesink->context->disp, cookie);
}
#endif
break;
}
}
} }
g_mutex_unlock (&xvimagesink->context->lock); g_mutex_unlock (&xvimagesink->context->lock);
g_mutex_unlock (&xvimagesink->flow_lock); g_mutex_unlock (&xvimagesink->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 ("xvimagesink ending touch frame for %lu",
xvimagesink->xwindow->last_touch);
gst_navigation_send_event_simple (GST_NAVIGATION (xvimagesink),
gst_navigation_event_new_touch_frame ());
}
#endif
} }
static gpointer static gpointer