My goal is to have a program that sleeps in the background but can be activated by the user via some "hotkey". From digging around the Xlib manual and the Xlib O'reilly manual, I gather that the correct way to to this is with XGrabKey. However my understanding of the process is incorrect as a simple proof of concept does not work.
My understanding is that if I call XGrabKey with the root window as the grab_window, and owner_events false, then whenever my hotkey is pressed the event will be sent only to the root window. If I then select KeyPress events from the root window, and then listen for X events, I should get a key press event when the hotkey is pressed. I've pasted a minimal example below.
What I expect is that when the program is run, regardless of what window has focus, if Ctrl+Shift+K is pressed, my program should output "Hot key pressed!" in the console, and then terminate.
Furthermore, it is my understanding that if the XGrabKey fails, the default error handler will display a message, and since it does not I am assuming that the call succeeds.
Obviously, my understanding is flawed somehow. Can anyone point me in the right direction?
#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;
}
Your program works here. My guess is you have another modifier active, such as NumLock. GrabKey only works on the exact modifier mask.
For example here is some (GPL) code from metacity window manager
/* 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);
}
With your mask ControlMask | ShiftMask
you will not get the key if another modifier key is held. This sounds okay in the first place, but there's a pitfall: NumLock, CapsLock and alike all are treated as modifiers, too.
You have two options:
XGrabKey()
multiple times, once for each explicit combination that you're interested in.XGrabKey()
with AnyModifier
and use event.xkey.state
to check whether the modifiers are as you expected.The header file <X.h>
defines ShiftMask
, LockMask
, ControlMask
, Mod1Mask
, Mod2Mask
, Mod3Mask
, Mod4Mask
, Mod5Mask
and AnyModifier
.
The keys are:
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 | ???
Warning I found out about the ModNMask
keys by trying and I do not know if this is valid on all machines / configurations / versions / operating systems.
In your case, you probably want to make sure that ShiftMask | CtrlMask
is set, Mod1Mask | Mod4Mask
are clear, and the others to be ignored.
I'd do this to setup the key grab:
XGrabKey(dpy, keycode, AnyModifier, grab_window, owner_events, pointer_mode, keyboard_mode);
And this to check whether the right modifiers are set:
switch (ev.type) {
case KeyPress:
if ((ev.xkey.state & (ShiftMask | CtrlMask | Mod1Mask | Mod4Mask)) == (ShiftMask | CtrlMask))
// ...
}
If you're using/targeting gtk on X11, there's a C library with a much simpler interface:
https://github.com/engla/keybinder
Includes Python, Lua and Vala bindings. (Also mentioned here.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With