Ignore auto repeat in X11 applications

2019-01-22 19:43发布

问题:

If you press and hold a key in X11 while AutoRepeat is enabled, you continuously receive KeyPress and KeyRelease events. I know that AutoRepeat can be disabled using the function XAutoRepeatOff(), but this changes the setting for the whole X server. Is there a way to either disable AutoRepeat for a single application or to ignore repeated keystrokes?

What I'm looking for is a single KeyPress event when a key is pressed and a single KeyRelease event when a key is released, without interfering with the X server's AutoRepeat setting.

Here's a minimal example to get you going (mostly from the Beginner Xlib Tutorial):

#include <stdio.h>
#include <stdlib.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xos.h>
#include <X11/Xatom.h>
#include <X11/keysym.h>

Display *dis;
Window win;
XEvent report;

int main ()
{
  dis = XOpenDisplay (NULL);
  // XAutoRepeatOn(dis);
  win = XCreateSimpleWindow (dis, RootWindow (dis, 0), 1, 1, 500, 500,
        0, BlackPixel (dis, 0), BlackPixel (dis, 0));
  XSelectInput (dis, win, KeyPressMask | KeyReleaseMask);
  XMapWindow (dis, win);
  XFlush (dis);

  while (1)
    {
      XNextEvent (dis, &report);
      switch (report.type)
 {
 case KeyPress:
   fprintf (stdout, "key #%ld was pressed.\n",
     (long) XLookupKeysym (&report.xkey, 0));
   break;
 case KeyRelease:
   fprintf (stdout, "key #%ld was released.\n",
     (long) XLookupKeysym (&report.xkey, 0));
   break;
 }
    }

  return (0);
}

回答1:

When you receive a key release and the next event is a key press of the same key combination, then it's auto-repeat and the key wasn't acutally released. You can use code like this to peek next event

if (event->type == KeyRelease && XEventsQueued(disp, QueuedAfterReading))
{
  XEvent nev;
  XPeekEvent(disp, &nev);

  if (nev.type == KeyPress && nev.xkey.time == event->xkey.time &&
      nev.xkey.keycode == event->xkey.keycode)
  {
    /* Key wasn’t actually released */
  }
}


回答2:

You can use the XkbSetDetectableAutorepeat function to tell the X server to only send KeyRelease events when the user actually releases the key - when you don't want autorepeat events, then you discard any KeyPress without matching KeyRelease.



回答3:

For your reference, here's a working minimal example that deletes auto-repeated KeyPress events. Thank you, kralyk!

#include <stdio.h>
#include <stdlib.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xos.h>
#include <X11/Xatom.h>
#include <X11/keysym.h>

Display *dis;
Window win;
XEvent report;

int main ()
{
  dis = XOpenDisplay (NULL);
  // XAutoRepeatOn(dis);
  win = XCreateSimpleWindow (dis, RootWindow (dis, 0), 1, 1, 500, 500,
        0, BlackPixel (dis, 0), BlackPixel (dis, 0));
  XSelectInput (dis, win, KeyPressMask | KeyReleaseMask);
  XMapWindow (dis, win);
  XFlush (dis);

  while (1)
    {
      XNextEvent (dis, &report);
      switch (report.type)
 {
 case KeyPress:
   fprintf (stdout, "key #%ld was pressed.\n",
     (long) XLookupKeysym (&report.xkey, 0));
   break;
 case KeyRelease:
   {
     unsigned short is_retriggered = 0;

     if (XEventsQueued(dis, QueuedAfterReading))
       {
         XEvent nev;
         XPeekEvent(dis, &nev);

         if (nev.type == KeyPress && nev.xkey.time == report.xkey.time &&
             nev.xkey.keycode == report.xkey.keycode)
           {
             fprintf (stdout, "key #%ld was retriggered.\n",
               (long) XLookupKeysym (&nev.xkey, 0));

             // delete retriggered KeyPress event
             XNextEvent (dis, &report);
             is_retriggered = 1;
           }
       }

     if (!is_retriggered)
       fprintf (stdout, "key #%ld was released.\n",
         (long) XLookupKeysym (&report.xkey, 0));
   }
   break;
 }
    }

  return (0);
}


回答4:

You could set a timer when a key is pressed or released and ignore KeyPress and KeyRelease events that occur within the repetition interval.



回答5:

another approach. it works for me.

char keyz[1024] = {0};
bool physical;
XEvent event, nev;

while (!quit) {
    XNextEvent(display, &event);
    ...
    switch(event.type) {
        case KeyPress:
            physical = (keyz[event.xkey.keycode] == 0);
            keyz[event.xkey.keycode] = 1;
            keyboard(event.xkey.window, true, event.xkey.keycode, physical);
            break;
        case KeyRelease:
            physical = true;
            if (XPending(display)) {
                XPeekEvent(display, &nev);
                if (nev.type == KeyPress && nev.xkey.time == event.xkey.time 
                && nev.xkey.keycode == event.xkey.keycode) physical = false;
            }
            if (physical) keyz[event.xkey.keycode] = 0;
            keyboard(event.xkey.window, false, event.xkey.keycode, physical);
            break;
    ...
    }


回答6:

Here's the solution I came up with.

XEvent event;

while(1)
{
    XNextEvent(display, &event);

    switch(event.type)
    {
        // Other cases
        case ...:
            ...
            break;
        ...

        // On KeyRelease
        case KeyRelease:
        {
            char keys[32];
            XQueryKeymap(display, keys);

            if(!(keys[event.xkey.keycode>>3] & (0x1 << (event.xkey.keycode % 8))))
            {
                // Stuff to do on KeyRelease
                ...
            }
        }
        break;

        // On KeyPress
        case KeyPress:
            // Stuff to do on KeyPress
            ...
            break;
        default:
            ...
    }
}

So, everytime I get a KeyRelease event, I use XQueryKeymap which will copy to keys the bits of pressed keys (8 different keys by char). For the ones who are not used to work with bitwise operatos and shift operator, here's a simple explanation:

keys[event.xkey.keycode>>3] search for index event.xkey.keycode / 8 using "right shift operator" (that allows for "integer division" by 2, 4, 8, 16 and so on, without type cast to float or double and back to integer).

0x1 << (event.xkey.keycode % 8) does the oposite. It multiplies the value of 0x1 (== 1) by 2 raised to (event.xkey.keycode % 8)

The & bitwise operator between keys[event.xkey.keycode>>3] and 0x1 << (event.xkey.keycode % 8) will compare if the only bit set in the right side operand is set to 1 inside this array index. If so, the key is being pressed.

Finally you just enclose it in (), with a ! right before and if the result becomes true, you're not pressing that key anymore.

One final Note: To use this method, you need to keep feeding the XServer with events. If not, XQueryKeymap will freze until it does (better use with threads).