Глобальная горячая клавиша с X11/Xlib
Моя цель - иметь программу, которая спит в фоновом режиме, но может быть активирована пользователем через некоторую "горячую клавишу". Из копания вокруг руководства Xlib и руководства Xlib O'reilly я понимаю, что правильный путь к этому - с XGrabKey. Однако мое понимание процесса неверно, поскольку простое доказательство концепции не работает.
Я понимаю, что если я вызову XGrabKey с корневым окном как grab_window, а owner_events false, то всякий раз, когда моя горячая клавиша будет нажата, событие будет отправлено только в корневое окно. Если я затем выбираю события KeyPress из корневого окна, а затем слушаю X-события, я должен получить событие нажатия клавиши, когда горячая клавиша нажата. Я вставил минимальный пример ниже.
Я ожидаю, что при запуске программы, независимо от того, какое окно имеет фокус, если нажать Ctrl + Shift + K, моя программа должна вывести "Горячая клавиша нажата!". в консоли, а затем завершите.
Кроме того, я понимаю, что если XGrabKey терпит неудачу, обработчик ошибок по умолчанию будет отображать сообщение, и, поскольку он не является, я предполагаю, что вызов преуспевает.
Очевидно, мое понимание как-то испорчено. Может кто-то указать мне верное направление?
#include <iostream>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
using namespace std;
int main()
{
Display* dpy = XOpenDisplay(0);
Window root = DefaultRootWindow(dpy);
XEvent ev;
unsigned int modifiers = ControlMask | ShiftMask;
int keycode = XKeysymToKeycode(dpy,XK_Y);
Window grab_window = root;
Bool owner_events = False;
int pointer_mode = GrabModeAsync;
int keyboard_mode = GrabModeAsync;
XGrabKey(dpy, keycode, modifiers, grab_window, owner_events, pointer_mode,
keyboard_mode);
XSelectInput(dpy, root, KeyPressMask );
while(true)
{
bool shouldQuit = false;
XNextEvent(dpy, &ev);
switch(ev.type)
{
case KeyPress:
cout << "Hot key pressed!" << endl;
XUngrabKey(dpy,keycode,modifiers,grab_window);
shouldQuit = true;
default:
break;
}
if(shouldQuit)
break;
}
XCloseDisplay(dpy);
return 0;
}
Ответы
Ответ 1
Ваша программа работает здесь. Я предполагаю, что у вас есть еще один модификатор, например NumLock. GrabKey работает только с маской модификатора.
Например, вот некоторый (GPL) код из диспетчера окон metacity
/* Grab/ungrab, ignoring all annoying modifiers like NumLock etc. */
static void
meta_change_keygrab (MetaDisplay *display,
Window xwindow,
gboolean grab,
int keysym,
unsigned int keycode,
int modmask)
{
unsigned int ignored_mask;
/* Grab keycode/modmask, together with
* all combinations of ignored modifiers.
* X provides no better way to do this.
*/
meta_topic (META_DEBUG_KEYBINDINGS,
"%s keybinding %s keycode %d mask 0x%x on 0x%lx\n",
grab ? "Grabbing" : "Ungrabbing",
keysym_name (keysym), keycode,
modmask, xwindow);
/* efficiency, avoid so many XSync() */
meta_error_trap_push (display);
ignored_mask = 0;
while (ignored_mask <= display->ignored_modifier_mask)
{
if (ignored_mask & ~(display->ignored_modifier_mask))
{
/* Not a combination of ignored modifiers
* (it contains some non-ignored modifiers)
*/
++ignored_mask;
continue;
}
if (meta_is_debugging ())
meta_error_trap_push_with_return (display);
if (grab)
XGrabKey (display->xdisplay, keycode,
modmask | ignored_mask,
xwindow,
True,
GrabModeAsync, GrabModeSync);
else
XUngrabKey (display->xdisplay, keycode,
modmask | ignored_mask,
xwindow);
if (meta_is_debugging ())
{
int result;
result = meta_error_trap_pop_with_return (display, FALSE);
if (grab && result != Success)
{
if (result == BadAccess)
meta_warning (_("Some other program is already using the key %s with modifiers %x as a binding\n"), keysym_name (keysym), modmask | ignored_mask);
else
meta_topic (META_DEBUG_KEYBINDINGS,
"Failed to grab key %s with modifiers %x\n",
keysym_name (keysym), modmask | ignored_mask);
}
}
++ignored_mask;
}
meta_error_trap_pop (display, FALSE);
}
Ответ 2
Если вы используете/ориентируете gtk на X11, есть библиотека C с гораздо более простым интерфейсом:
https://github.com/engla/keybinder
Включает привязки Python, Lua и Vala. (Также упоминается здесь.)
Ответ 3
С вашей маской ControlMask | ShiftMask
вы не получите ключ, если удерживается другой ключ-модификатор. В первую очередь это звучит хорошо, но есть ловушка: NumLock, CapsLock и все они также рассматриваются как модификаторы.
У вас есть два варианта:
- Вы вызываете
XGrabKey()
несколько раз, один раз для каждой явной комбинации, которая вас интересует.
- Вы вызываете
XGrabKey()
с помощью AnyModifier
и используйте event.xkey.state
для проверки того, насколько модификаторы ожидаются.
Заголовочный файл <X.h>
определяет ShiftMask
, LockMask
, ControlMask
, Mod1Mask
, Mod2Mask
, Mod3Mask
, Mod4Mask
, Mod5Mask
и AnyModifier
.
Ключами являются:
Mask | Value | Key
------------+-------+------------
ShiftMask | 1 | Shift
LockMask | 2 | Caps Lock
ControlMask | 4 | Ctrl
Mod1Mask | 8 | Alt
Mod2Mask | 16 | Num Lock
Mod3Mask | 32 | Scroll Lock
Mod4Mask | 64 | Windows
Mod5Mask | 128 | ???
Предупреждение Я узнал о клавишах ModNMask
, пытаясь, и я не знаю, действительно ли это относится ко всем машинам/конфигурациям/версиям/операционным системам.
В вашем случае вы, вероятно, захотите убедиться, что ShiftMask | CtrlMask
установлен, Mod1Mask | Mod4Mask
понятны, а остальные должны игнорироваться.
Я бы сделал это, чтобы настроить захват ключа:
XGrabKey(dpy, keycode, AnyModifier, grab_window, owner_events, pointer_mode, keyboard_mode);
И это, чтобы проверить, установлены ли правильные модификаторы:
switch (ev.type) {
case KeyPress:
if ((ev.xkey.state & (ShiftMask | CtrlMask | Mod1Mask | Mod4Mask)) == (ShiftMask | CtrlMask))
// ...
}