可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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).