XRecord does not receive Device events for touchscreen ButtonPress/ButtonRelease events
Hi,
I have written a program to capture ButtonPress and KeyPress events, to play sounds on KeyPress and ButtonPress, and vibrate a device (mobile phone) on ButtonPress. This works fine with a mouse and keyboard, but fails with a touchscreen: the ButtonPress events are never delivered.
The problem seems to be that the touchscreen ButtonPress events are delivered to record.c's RecordADeliveredEventOrError, where the normal device events (from mouse, keyboard) get delivered to RecordADeviceEvent.
So if X Record using program only asks for device events (which mine does), it doesn't receive the touchscreen ButtonPress events at all.
This seems like a bug to me, which can be worked around by asking for any other (non-device event), like so:
> ranges[0]->device_events.first = KeyPress;
> ranges[0]->device_events.last = KeyPress;
> ranges[1]->device_events.first = ButtonPress;
> ranges[1]->device_events.last = ButtonPress;
> #if WORKAROUND
> ranges[2]->errors.first = 0;
> ranges[2]->errors.last = 100;
> #endif
When these XRecordRanges are presented (as opposed to just the first two), then the ButtonPress events from touchscreen are also sent to the X Record callback. But this is not a good workound either: many other events (like MotionNotify from the touchscreen) are also sent along, since those are also erroneously (?) sent through RecordADeliveredEventOrError.
I don't have a lot of knowledge of the internals of X, but it seems that for events to end up at RecordADeviceEvent, they need to queued for the DeviceEventCallback (Xi/exevents.c), not for the EventCallback list.
From the backtrace from a touchscreen event [1], it looks like dix/events.c still has the right intention from function DeliverDeviceEvents (as implied by the name), but somewhere things go south: ultimately WriteEventsToClient just calls the EventCallback list, regardless of the event type. TryClientEvents doesn't seem to use the source input device for any logic other than repeated key press / release fixups.
Does anyone here have a clue what's up here, and what the right way to fix this would be? I'd like to contribute a fix, but I think I could use some guidance / feedback here.
The simple change to me seems to simply fix up record.c to check for device events in RecordADeliveredEventOrError and deliver (and filter) them properly, but maybe there is a bigger problem.
Also included a backtrace from a regular ButtonPress from a mouse [2],
and a test program I wrote [3] (compile with gcc xrecord-test.c -o xrecord-test $(pkg-config --cflags --libs x11 xext xtst
).
All backtraces are from a X server on current debian stable: 1.20.4
[1]
Thread 1 "Xorg" hit Breakpoint 2, RecordADeliveredEventOrError
(pcbl=<optimized out>, nulldata=<optimized out>, calldata=0x7ffe4d95aa10)
at ../../../../record/record.c:667
667 in ../../../../record/record.c
(gdb) bt
#0 0x0000561a0af74fc0 in RecordADeliveredEventOrError (pcbl=<optimized
out>, nulldata=<optimized out>, calldata=0x7ffe4d95aa10) at
../../../../record/record.c:667
#1 0x0000561a0aec4c94 in _CallCallbacks (pcbl=pcbl at entry=0x561a0b0cc698
<EventCallback>, call_data=call_data at entry=0x7ffe4d95aa10) at
../../../../dix/dixutils.c:737
#2 0x0000561a0aecad5f in CallCallbacks (call_data=0x7ffe4d95aa10,
pcbl=0x561a0b0cc698 <EventCallback>) at ../../../../include/callback.h:83
#3 0x0000561a0aecad5f in WriteEventsToClient
(pClient=pClient at entry=0x561a0c3471c0, count=count at entry=1,
events=events at entry=0x561a0c2dace0)
at ../../../../dix/events.c:5958
#4 0x0000561a0aecb084 in TryClientEvents
(filter=<optimized out>, grab=0x0, mask=<optimized out>, count=1,
pEvents=0x561a0c2dace0, dev=<optimized out>, client=0x561a0c3471c0)
at ../../../../dix/events.c:2021
#5 0x0000561a0aecb084 in TryClientEvents
(client=0x561a0c3471c0, dev=<optimized out>, pEvents=0x561a0c2dace0,
count=1, mask=<optimized out>, filter=<optimized out>, grab=0x0)
at ../../../../dix/events.c:1922
#6 0x0000561a0aecebe0 in DeliverToWindowOwner
(grab=<optimized out>, filter=<optimized out>, count=<optimized
out>, events=<optimized out>, win=<optimized out>, dev=<optimized out>)
at ../../../../dix/events.c:2091
#7 0x0000561a0aecebe0 in DeliverEventsToWindow
(pDev=pDev at entry=0x561a0c0b1860, pWin=pWin at entry=0x561a0c361680,
pEvents=pEvents at entry=0x561a0c2dace0, count=count at entry=1,
filter=filter at entry=4, grab=grab at entry=0x0) at ../../../../dix/events.c:2254
#8 0x0000561a0aecf00c in DeliverEvent (grab=0x0, child=0,
win=0x561a0c361680, count=1, xE=0x561a0c2dace0, dev=0x561a0c0b1860) at
../../../../dix/events.c:2649
#9 0x0000561a0aecf00c in DeliverOneEvent
(event=event at entry=0x7ffe4d95b8b0, dev=dev at entry=0x561a0c0b1860,
level=level at entry=CORE, win=win at entry=0x561a0c361680,
child=child at entry=0, grab=grab at entry=0x0)
at ../../../../dix/events.c:2681
#10 0x0000561a0aecf151 in DeliverDeviceEvents
(pWin=0x561a0c361680, event=event at entry=0x7ffe4d95b8b0,
grab=grab at entry=0x0, stopAt=stopAt at entry=0x561a0c361680,
dev=dev at entry=0x561a0c0b1860)
at ../../../../dix/events.c:2739
#11 0x0000561a0afa637a in DeliverTouchEmulatedEvent
(dev=dev at entry=0x561a0c0b1860, ti=ti at entry=0x561a0c3e85d0,
ev=ev at entry=0x7ffe4d95c6e0, win=win at entry=0x561a0c361680,
grab=grab at entry=0x0, xi2mask=<optimized out>, client=<optimized out>,
listener=<optimized out>, listener=<optimized out>) at
../../../../Xi/exevents.c:1437
#12 0x0000561a0afa6585 in DeliverTouchEmulatedEvent
(dev=dev at entry=0x561a0c0b1860, ti=ti at entry=0x561a0c3e85d0,
ev=ev at entry=0x7ffe4d95c6e0, win=win at entry=0x561a0c361680, grab=0x0,
xi2mask=<optimized out>, client=0x561a0c3471c0, listener=<optimized
out>, listener=<optimized out>) at ../../../../Xi/exevents.c:1382
#13 0x0000561a0afa5f12 in DeliverTouchBeginEvent
(xi2mask=<optimized out>, grab=0x0, win=0x561a0c361680,
client=0x561a0c3471c0, listener=0x561a0ce003e0, ev=0x7ffe4d95c6e0,
ti=0x561a0c3e85d0, dev=0x561a0c0b1860)
at ../../../../Xi/exevents.c:1890
#14 0x0000561a0afa5f12 in DeliverTouchEvent
(xi2mask=<optimized out>, grab=0x0, win=0x561a0c361680,
client=0x561a0c3471c0, listener=0x561a0ce003e0, ev=0x7ffe4d95c6e0,
ti=0x561a0c3e85d0, dev=0x561a0c0b1860)
at ../../../../Xi/exevents.c:2017
#15 0x0000561a0afa5f12 in DeliverTouchEvents
(dev=dev at entry=0x561a0c0b1860, ti=ti at entry=0x561a0c3e85d0,
ev=ev at entry=0x7ffe4d95c6e0, resource=0)
at ../../../../Xi/exevents.c:2072
#16 0x0000561a0afa8f64 in ProcessTouchEvent (dev=0x561a0c0b1860,
ev=0x7ffe4d95c6e0) at ../../../../Xi/exevents.c:1626
#17 0x0000561a0afa8f64 in ProcessOtherEvent (ev=0x7ffe4d95c6e0,
device=0x561a0c0b1860) at ../../../../Xi/exevents.c:1861
#18 0x0000561a0afcbc47 in ProcessPointerEvent (ev=0x7ffe4d95c6e0,
mouse=0x561a0c0b1860) at ../../../../xkb/xkbAccessX.c:756
#19 0x0000561a0affdf45 in mieqProcessDeviceEvent
(dev=dev at entry=0x561a0c4b3580, event=event at entry=0x7ffe4d95d340,
screen=screen at entry=0x561a0bff4e30)
at ../../../../mi/mieq.c:496
#20 0x0000561a0affe089 in mieqProcessInputEvents () at
../../../../mi/mieq.c:551
#21 0x0000561a0aefc559 in ProcessInputEvents () at
../../../../../../hw/xfree86/common/xf86Events.c:151
#22 0x0000561a0aebf738 in Dispatch () at ../../../../dix/dispatch.c:417
#23 0x0000561a0aec3986 in dix_main (argc=11, argv=0x7ffe4d95e158,
envp=<optimized out>) at ../../../../dix/main.c:276
#24 0x00007f85b324909b in __libc_start_main (main=
0x561a0aead640 <main>, argc=11, argv=0x7ffe4d95e158, init=<optimized
out>, fini=<optimized out>, rtld_fini=<optimized out>,
stack_end=0x7ffe4d95e148)
at ../csu/libc-start.c:308
#25 0x0000561a0aead67a in _start () at
../../../../../../hw/xfree86/common/xf86Events.c:626
[2]
(gdb) bt
#0 0x0000561a0af7567e in RecordADeviceEvent (pcbl=0x561a0b0d1700
<DeviceEventCallback>, nulldata=0x0, calldata=0x7ffe4d95b9f0) at
../../../../record/record.c:775
#1 0x0000561a0aec4c94 in _CallCallbacks (pcbl=0x561a0b0d1700
<DeviceEventCallback>, call_data=call_data at entry=0x7ffe4d95b9f0) at
../../../../dix/dixutils.c:737
#2 0x0000561a0afa8781 in CallCallbacks (call_data=0x7ffe4d95b9f0,
pcbl=<optimized out>) at ../../../../include/callback.h:83
#3 0x0000561a0afa8781 in ProcessDeviceEvent
(ev=ev at entry=0x7ffe4d95d340, device=device at entry=0x561a0c328430) at
../../../../Xi/exevents.c:1759
#4 0x0000561a0afa8e63 in ProcessOtherEvent (ev=0x7ffe4d95d340,
device=0x561a0c328430) at ../../../../Xi/exevents.c:1873
#5 0x0000561a0afd2875 in ProcessKeyboardEvent (ev=<optimized out>,
keybd=0x561a0c328430) at ../../../../xkb/xkbPrKeyEv.c:178
#6 0x0000561a0affdf2b in mieqProcessDeviceEvent
(dev=dev at entry=0x561a0c328430, event=event at entry=0x7ffe4d95d340,
screen=screen at entry=0x561a0bff4e30)
at ../../../../mi/mieq.c:491
#7 0x0000561a0affe089 in mieqProcessInputEvents () at
../../../../mi/mieq.c:551
#8 0x0000561a0aefc559 in ProcessInputEvents () at
../../../../../../hw/xfree86/common/xf86Events.c:151
#9 0x0000561a0aebf738 in Dispatch () at ../../../../dix/dispatch.c:417
#10 0x0000561a0aec3986 in dix_main (argc=11, argv=0x7ffe4d95e158,
envp=<optimized out>) at ../../../../dix/main.c:276
#11 0x00007f85b324909b in __libc_start_main (main=
0x561a0aead640 <main>, argc=11, argv=0x7ffe4d95e158, init=<optimized
out>, fini=<optimized out>, rtld_fini=<optimized out>,
stack_end=0x7ffe4d95e148)
at ../csu/libc-start.c:308
#12 0x0000561a0aead67a in _start () at
../../../../../../hw/xfree86/common/xf86Events.c:626
[3]
#include <stdlib.h>
#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/extensions/record.h>
static int verbose = 1;
#define LOG_ERROR(msg) fprintf(stderr, "%s:%u, %s(): " msg "\n",
__FILE__, __LINE__, __FUNCTION__);
#define LOG_ERROR1(fmt, ...) fprintf(stderr, "%s:%u, %s(): " fmt "\n",
__FILE__, __LINE__, __FUNCTION__, __VA_ARGS__);
#define LOG_VERBOSE(msg) \
{ \
if (verbose) { \
fprintf(stderr, "%s:%u, %s(): " msg "\n", __FILE__, __LINE__,
__FUNCTION__); \
} \
}
#define LOG_VERBOSE1(fmt, ...) \
{ \
if (verbose) { \
fprintf(stderr, "%s:%u, %s(): " fmt "\n", __FILE__, __LINE__,
__FUNCTION__, __VA_ARGS__); \
} \
}
int xerror_handler(Display * display, XErrorEvent * ev) {
(void)display;
(void)ev;
char buf[512];
XGetErrorText(display, ev->error_code, &buf, 512);
LOG_ERROR1("X11 error_handler fired: %s", buf);
return 0;
}
void xrec_data_cb(XPointer data, XRecordInterceptData * recdat) {
int diff_ms;
int keyev, val;
int device_state;
unsigned char *xrd = recdat->data;
if (!xrd) {
LOG_ERROR("xrd == NULL");
return;
}
LOG_VERBOSE("xrec_data_cb");
keyev = xrd[0];
val = xrd[1];
if (keyev == ButtonPress && verbose) {
LOG_VERBOSE1("X ButtonPress %d\n", val);
}
#if 0
if (keyev == MotionNotify && verbose) {
LOG_VERBOSE1("X MotionNotify %d\n", val);
}
#endif
if (keyev == KeyPress && verbose) {
LOG_VERBOSE1("X KeyPress %d\n", val);
}
XRecordFreeData(recdat);
return;
}
void *xrec_thread() {
Display *display;
XRecordContext recordcontext;
int major, minor;
#if 1
XRecordRange *ranges[3];
#else
XRecordRange *ranges[2];
#endif
XRecordClientSpec spec;
display = XOpenDisplay(NULL);
if (!display) {
fprintf(stderr, "xrec_thread failed to open display\n");
exit(EXIT_FAILURE);
}
XSetErrorHandler(xerror_handler);
if (!XRecordQueryVersion(display, &major, &minor)) {
LOG_ERROR("X Record Extension not available.");
exit(1);
}
LOG_VERBOSE1("X Record %d.%d is available\n", major, minor);
ranges[0] = XRecordAllocRange();
ranges[1] = XRecordAllocRange();
#if 1
ranges[2] = XRecordAllocRange();
#endif
#if 1
if (!ranges[0] || !ranges[1] || !ranges[2]) {
#else
if (!ranges[0] || !ranges[1]) {
#endif
LOG_ERROR("failed to allocate X Record Range");
}
ranges[0]->device_events.first = KeyPress;
ranges[0]->device_events.last = KeyPress;
ranges[1]->device_events.first = ButtonPress;
ranges[1]->device_events.last = ButtonPress;
#if 1
ranges[2]->errors.first = 0;
ranges[2]->errors.last = 100;
//ranges[2]->device_events.first = ButtonPress;
//ranges[2]->device_events.last = ButtonPress;
#endif
spec = XRecordAllClients;
#if 1
recordcontext = XRecordCreateContext(display, 0, &spec, 1, ranges, 3);
#else
recordcontext = XRecordCreateContext(display, 0, &spec, 1, ranges, 2);
#endif
if (!recordcontext) {
LOG_ERROR("failed to create X Record Context");
exit(1);
}
if (!XRecordEnableContext(display, recordcontext, xrec_data_cb, NULL)) {
LOG_ERROR("failed to enable async X record data transfers");
}
LOG_VERBOSE("event record finished");
XFree(ranges[0]);
XFree(ranges[1]);
#if 1
XFree(ranges[2]);
#endif
return NULL;
}
int main(int argc, char **argv) {
xrec_thread();
}