summaryrefslogtreecommitdiffstats
path: root/driver/xinput.c
diff options
context:
space:
mode:
Diffstat (limited to 'driver/xinput.c')
-rw-r--r--driver/xinput.c186
1 files changed, 117 insertions, 69 deletions
diff --git a/driver/xinput.c b/driver/xinput.c
index 3b21db0..f865f42 100644
--- a/driver/xinput.c
+++ b/driver/xinput.c
@@ -1,4 +1,4 @@
-/* xscreensaver, Copyright © 1991-2021 Jamie Zawinski <jwz@jwz.org>
+/* xscreensaver, Copyright © 1991-2022 Jamie Zawinski <jwz@jwz.org>
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
@@ -9,6 +9,49 @@
* implied warranty.
*/
+/* For our purposes, here are the salient facts about the XInput2 extension:
+
+ - Available by default as of X11R7 in 2009.
+
+ - We receive events from the XIRawEvent family while other processes
+ have the keyboard and mouse grabbed (XI_RawKeyPress, etc.)
+ The entire xscreensaver-auth security model hinges on this fact.
+
+ - We cannot receive events from the XIDeviceEvent family (XI_KeyPress,
+ etc.) so while XIDeviceEvents contain many fields that we would like
+ to have, don't be fooled, those are not available. XIRawEvents such
+ as XI_RawKeyPress cannot be cast to XIDeviceEvent.
+
+ - XI_RawButtonPress, XI_RawButtonRelease and XI_RawMotion contain no
+ usable position information. The non-raw versions do, but we don't
+ receive those, so we have to read that info via XQueryPointer.
+
+ - XI_RawKeyPress and XI_RawKeyRelease contain the keycode (in 'detail')
+ but no modifier info, so, again, to be able to tell "a" from "A" we
+ need to call XQueryPointer.
+
+ - XI_RawKeyPress does not auto-repeat the way Xlib KeyPress does.
+
+ - Raw events have no window, subwindow, etc.
+
+ - Event serial numbers are not unique.
+
+ - If *this* process has the keyboard and mouse grabbed, it receives:
+
+ - KeyPress, KeyRelease
+ - ButtonPress, ButtonRelease, MotionNotify
+ - XI_RawButtonPress, XI_RawButtonRelease, XI_RawMotion (doubly reported)
+
+ - If this process *does not* have keyboard and mouse grabbed, it receives:
+
+ - XI_RawKeyPress, XI_RawKeyRelease
+ - XI_RawButtonPress, XI_RawButtonRelease, XI_RawMotion
+
+ The closest thing to actual documentation on XInput2 seems to be a series
+ of blog posts by Peter Hutterer. There's basically nothing about it on
+ www.x.org. https://who-t.blogspot.com/2009/07/xi2-recipes-part-4.html
+ */
+
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
@@ -35,7 +78,6 @@ extern Bool debug_p;
Bool
init_xinput (Display *dpy, int *opcode_ret)
{
- int nscreens = ScreenCount (dpy);
XIEventMask evmasks[1];
unsigned char mask1[(XI_LASTEVENT + 7)/8];
int major, minor;
@@ -64,6 +106,10 @@ init_xinput (Display *dpy, int *opcode_ret)
memset (mask1, 0, sizeof(mask1));
+ /* The XIRawEvent family of events are delivered to us while someone else
+ holds a grab, but the non-raw versions are not. In fact, attempting to
+ select XI_KeyPress, etc. results in a BadAccess X error.
+ */
XISetMask (mask1, XI_RawMotion);
XISetMask (mask1, XI_RawKeyPress);
XISetMask (mask1, XI_RawKeyRelease);
@@ -73,19 +119,18 @@ init_xinput (Display *dpy, int *opcode_ret)
XISetMask (mask1, XI_RawTouchUpdate);
XISetMask (mask1, XI_RawTouchEnd);
- /* If we use XIAllDevices instead, we get double events. */
+ /* If we use XIAllDevices instead, we get duplicate events. */
evmasks[0].deviceid = XIAllMasterDevices;
evmasks[0].mask_len = sizeof(mask1);
evmasks[0].mask = mask1;
- for (i = 0; i < nscreens; i++)
+ /* Only select events on screen 0 -- if we select on each screen,
+ we get duplicate events. */
+ if (XISelectEvents (dpy, RootWindow (dpy, 0), evmasks, countof(evmasks))
+ != Success)
{
- Window root = RootWindow (dpy, i);
- if (XISelectEvents (dpy, root, evmasks, countof(evmasks)) != Success)
- {
- fprintf (stderr, "%s: XISelectEvents failed\n", blurb());
- return False;
- }
+ fprintf (stderr, "%s: XISelectEvents failed\n", blurb());
+ return False;
}
XFlush (dpy);
@@ -127,33 +172,10 @@ xinput_event_to_xlib (int evtype, XIDeviceEvent *in, XEvent *out)
Bool ok = False;
int root_x = 0, root_y = 0;
+ int win_x = 0, win_y = 0;
unsigned int mods = 0;
+ Window subw = 0;
- /* The closest thing to actual documentation on XInput2 seems to be a series
- of blog posts by Peter Hutterer. There's basically nothing about it on
- www.x.org. In http://who-t.blogspot.com/2009/07/xi2-recipes-part-4.html
- he says:
-
- "XIDeviceEvent [...] contains the state of the modifier keys [...]
- The base modifiers are the ones currently pressed, latched the ones
- pressed until a key is pressed that's configured to unlatch it (e.g.
- some shift-capslock interactions have this behaviour) and finally
- locked modifiers are the ones permanently active until unlocked
- (default capslock behaviour in the US layout). The effective modifiers
- are a bitwise OR of the three above - which is essentially equivalent
- to the modifiers state supplied in the core protocol events."
-
- However, I'm seeing random noise in the various XIDeviceEvent.mods fields.
- Nonsensical values like base = 0x6045FB3D. So, let's poll the actual
- modifiers from XQueryPointer. This can race: maybe the modifier state
- changed between when the server generated the keyboard event, and when
- we receive it and poll. However, if an actual human is typing and
- releasing their modifier keys on such a tight timeframe... that's
- probably already not going well.
-
- I'm also seeing random noise in the event_xy and root_xy fields in
- motion events. So just always use XQueryPointer.
- */
switch (evtype) {
case XI_RawKeyPress:
case XI_RawKeyRelease:
@@ -161,12 +183,12 @@ xinput_event_to_xlib (int evtype, XIDeviceEvent *in, XEvent *out)
case XI_RawButtonRelease:
case XI_RawMotion:
{
- Window root_ret, child_ret;
+ Window root_ret;
int win_x, win_y;
int i;
for (i = 0; i < ScreenCount (dpy); i++) /* query on correct screen */
if (XQueryPointer (dpy, RootWindow (dpy, i),
- &root_ret, &child_ret, &root_x, &root_y,
+ &root_ret, &subw, &root_x, &root_y,
&win_x, &win_y, &mods))
break;
}
@@ -178,12 +200,12 @@ xinput_event_to_xlib (int evtype, XIDeviceEvent *in, XEvent *out)
case XI_RawKeyRelease:
out->xkey.type = (evtype == XI_RawKeyPress ? KeyPress : KeyRelease);
out->xkey.display = in->display;
- out->xkey.window = in->event;
out->xkey.root = in->root;
- out->xkey.subwindow = in->child;
+ out->xkey.window = subw;
+ out->xkey.subwindow = subw;
out->xkey.time = in->time;
- out->xkey.x = root_x;
- out->xkey.y = root_y;
+ out->xkey.x = win_x;
+ out->xkey.y = win_y;
out->xkey.x_root = root_x;
out->xkey.y_root = root_y;
out->xkey.state = mods;
@@ -195,12 +217,12 @@ xinput_event_to_xlib (int evtype, XIDeviceEvent *in, XEvent *out)
out->xbutton.type = (evtype == XI_RawButtonPress
? ButtonPress : ButtonRelease);
out->xbutton.display = in->display;
- out->xbutton.window = in->event;
out->xbutton.root = in->root;
- out->xbutton.subwindow = in->child;
+ out->xbutton.window = subw;
+ out->xbutton.subwindow = subw;
out->xbutton.time = in->time;
- out->xbutton.x = root_x;
- out->xbutton.y = root_y;
+ out->xbutton.x = win_x;
+ out->xbutton.y = win_y;
out->xbutton.x_root = root_x;
out->xbutton.y_root = root_y;
out->xbutton.state = mods;
@@ -210,17 +232,34 @@ xinput_event_to_xlib (int evtype, XIDeviceEvent *in, XEvent *out)
case XI_RawMotion:
out->xmotion.type = MotionNotify;
out->xmotion.display = in->display;
- out->xmotion.window = in->event;
out->xmotion.root = in->root;
- out->xmotion.subwindow = in->child;
+ out->xmotion.window = subw;
+ out->xmotion.subwindow = subw;
out->xmotion.time = in->time;
- out->xmotion.x = root_x;
- out->xmotion.y = root_y;
+ out->xmotion.x = win_x;
+ out->xmotion.y = win_y;
out->xmotion.x_root = root_x;
out->xmotion.y_root = root_y;
out->xmotion.state = mods;
ok = True;
break;
+ case XI_RawTouchBegin:
+ case XI_RawTouchEnd:
+ out->xbutton.type = (evtype == XI_RawTouchBegin
+ ? ButtonPress : ButtonRelease);
+ out->xbutton.display = in->display;
+ out->xbutton.root = in->root;
+ out->xbutton.window = subw;
+ out->xbutton.subwindow = subw;
+ out->xbutton.time = in->time;
+ out->xbutton.x = win_x;
+ out->xbutton.y = win_y;
+ out->xbutton.x_root = root_x;
+ out->xbutton.y_root = root_y;
+ out->xbutton.state = mods;
+ out->xbutton.button = in->detail;
+ ok = True;
+ break;
default:
break;
}
@@ -230,14 +269,14 @@ xinput_event_to_xlib (int evtype, XIDeviceEvent *in, XEvent *out)
static void
-print_kbd_event (XEvent *xev, XComposeStatus *compose, Bool x11_p)
+print_kbd_event (XKeyEvent *xkey, XComposeStatus *compose, Bool x11_p)
{
if (debug_p) /* Passwords show up in plaintext! */
{
KeySym keysym = 0;
char c[100];
char M[100], *mods = M;
- int n = XLookupString (&xev->xkey, c, sizeof(c)-1, &keysym, compose);
+ int n = XLookupString (xkey, c, sizeof(c)-1, &keysym, compose);
const char *ks = keysym ? XKeysymToString (keysym) : "NULL";
c[n] = 0;
if (*c == '\n') strcpy (c, "\\n");
@@ -245,31 +284,31 @@ print_kbd_event (XEvent *xev, XComposeStatus *compose, Bool x11_p)
else if (*c == '\t') strcpy (c, "\\t");
*mods = 0;
- if (xev->xkey.state & ShiftMask) strcat (mods, "-Sh");
- if (xev->xkey.state & LockMask) strcat (mods, "-Lk");
- if (xev->xkey.state & ControlMask) strcat (mods, "-C");
- if (xev->xkey.state & Mod1Mask) strcat (mods, "-M1");
- if (xev->xkey.state & Mod2Mask) strcat (mods, "-M2");
- if (xev->xkey.state & Mod3Mask) strcat (mods, "-M3");
- if (xev->xkey.state & Mod4Mask) strcat (mods, "-M4");
- if (xev->xkey.state & Mod5Mask) strcat (mods, "-M5");
+ if (xkey->state & ShiftMask) strcat (mods, "-Sh");
+ if (xkey->state & LockMask) strcat (mods, "-Lk");
+ if (xkey->state & ControlMask) strcat (mods, "-C");
+ if (xkey->state & Mod1Mask) strcat (mods, "-M1");
+ if (xkey->state & Mod2Mask) strcat (mods, "-M2");
+ if (xkey->state & Mod3Mask) strcat (mods, "-M3");
+ if (xkey->state & Mod4Mask) strcat (mods, "-M4");
+ if (xkey->state & Mod5Mask) strcat (mods, "-M5");
if (*mods) mods++;
if (!*mods) strcat (mods, "0");
fprintf (stderr, "%s: %s 0x%02X %s %s \"%s\"\n", blurb(),
(x11_p
- ? (xev->xkey.type == KeyPress
+ ? (xkey->type == KeyPress
? "X11 KeyPress "
: "X11 KeyRelease ")
- : (xev->xkey.type == KeyPress
+ : (xkey->type == KeyPress
? "XI_RawKeyPress "
: "XI_RawKeyRelease")),
- xev->xkey.keycode, mods, ks, c);
+ xkey->keycode, mods, ks, c);
}
else /* Log only that the KeyPress happened. */
{
fprintf (stderr, "%s: X11 Key%s\n", blurb(),
- (xev->xkey.type == KeyPress ? "Press " : "Release"));
+ (xkey->type == KeyPress ? "Press " : "Release"));
}
}
@@ -284,7 +323,7 @@ print_xinput_event (Display *dpy, XEvent *xev, const char *desc)
case KeyRelease:
{
static XComposeStatus compose = { 0, };
- print_kbd_event (xev, &compose, True);
+ print_kbd_event (&xev->xkey, &compose, True);
}
break;
@@ -321,7 +360,7 @@ print_xinput_event (Display *dpy, XEvent *xev, const char *desc)
{
/* Fake up an XKeyEvent in order to call XKeysymToString(). */
XEvent ev2;
- Bool ok = xinput_event_to_xlib (xev->xcookie.evtype,
+ Bool ok = xinput_event_to_xlib (re->evtype,
(XIDeviceEvent *) re,
&ev2);
if (!ok)
@@ -329,7 +368,7 @@ print_xinput_event (Display *dpy, XEvent *xev, const char *desc)
else
{
static XComposeStatus compose = { 0, };
- print_kbd_event (&ev2, &compose, False);
+ print_kbd_event (&ev2.xkey, &compose, False);
}
break;
}
@@ -340,9 +379,18 @@ print_xinput_event (Display *dpy, XEvent *xev, const char *desc)
case XI_RawButtonPress:
case XI_RawButtonRelease:
- fprintf (stderr, "%s: XI RawButton%s %d\n", blurb(),
- (re->evtype == XI_RawButtonPress ? "Press " : "Release"),
- re->detail);
+ {
+ Window root_ret, child_ret;
+ int root_x, root_y;
+ int win_x, win_y;
+ unsigned int mask;
+ XQueryPointer (dpy, DefaultRootWindow (dpy),
+ &root_ret, &child_ret, &root_x, &root_y,
+ &win_x, &win_y, &mask);
+ fprintf (stderr, "%s: XI _RawButton%s %4d, %-4d\n", blurb(),
+ (re->evtype == XI_RawButtonPress ? "Press " : "Release"),
+ root_x, root_y);
+ }
break;
case XI_RawMotion: