Игнорировать автоповтор в приложениях X11
Если вы нажимаете и удерживаете клавишу в X11, когда включен автоповтор, вы постоянно получаете события KeyPress и KeyRelease. Я знаю, что AutoRepeat можно отключить с помощью функции XAutoRepeatOff (), но это меняет настройку для всего X-сервера. Есть ли способ отключить AutoRepeat для одного приложения или игнорировать повторные нажатия клавиш?
То, что я ищу, - это одно событие KeyPress, когда клавиша нажата, и одно событие KeyRelease, когда клавиша отпущена, без вмешательства в параметр AutoRepeat X-сервера.
Вот минимальный пример для начала работы (в основном из учебника для начинающих Xlib):
#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);
}
6 ответов
Когда вы получаете отпускание клавиши, и следующее событие - это нажатие клавиши с той же комбинацией клавиш, то происходит автоматическое повторение, и клавиша не была выпущена полностью. Вы можете использовать этот код, чтобы посмотреть следующее событие
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 */
}
}
Вы можете использовать функцию XkbSetDetectableAutorepeat, чтобы сообщить X-серверу отправлять события KeyRelease только тогда, когда пользователь действительно отпустит ключ - когда вы не хотите, чтобы события автоповтора повторялись, вы отбрасываете любую KeyPress без соответствия KeyRelease.
Для справки вот рабочий минимальный пример, который удаляет автоматически повторяющиеся события KeyPress. Спасибо, кралык!
#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);
}
Вы можете установить таймер, когда клавиша нажата или отпущена, и игнорировать события KeyPress и KeyRelease, которые происходят в интервале повторения.
Вот решение, которое я придумал.
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:
...
}
}
Поэтому каждый раз, когда я получаю событие KeyRelease, я использую XQueryKeymap
который будет копировать в keys
биты нажатых клавиш (8 различных клавиш по char
). Для тех, кто не привык работать с побитовыми операциями и оператором сдвига, вот простое объяснение:
keys[event.xkey.keycode>>3]
поиск по индексу event.xkey.keycode / 8
использование "оператора сдвига вправо" (который допускает "целочисленное деление" на 2, 4, 8, 16 и т. д., без приведения типа к числу с плавающей или двойной и обратно к целому числу).
0x1 << (event.xkey.keycode % 8)
делает опозит. Это умножает значение 0x1
(== 1
) на 2 повышен до (event.xkey.keycode % 8)
&
побитовый оператор между keys[event.xkey.keycode>>3]
а также 0x1 << (event.xkey.keycode % 8)
будет сравниваться, если единственный бит, установленный в операнде справа, установлен в 1 внутри этого индекса массива. Если так, то клавиша нажимается.
Наконец, вы просто заключаете это в ()
с !
прямо перед тем, как результат станет правдой, вы больше не нажимаете эту клавишу.
И последнее замечание: чтобы использовать этот метод, вам нужно постоянно снабжать XServer событиями. Если нет, XQueryKeymap будет зависать до тех пор, пока это не произойдет (лучше использовать с потоками).
another approach. меня устраивает.
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;
...
}