The issue
Whilst working on Diodon I needed to implement a global hotkey. After browsing the web and some source code I came up with the following solution.
Get the code
Here is a sample class to easily bind a delegate with a global hotkey.
/**
* This class is in charge to grab keybindings on the X11 display
* and filter X11-events and passing on such events to the registed
* handler methods.
*
* @author Oliver Sauder
*/
public class KeybindingManager : GLib.Object
{
/**
* list of binded keybindings
*/
private Gee.List bindings = new Gee.ArrayList();
/**
* locked modifiers used to grab all keys whatever lock key
* is pressed.
*/
private static uint[] lock_modifiers = {
0,
Gdk.ModifierType.MOD2_MASK, // NUM_LOCK
Gdk.ModifierType.LOCK_MASK, // CAPS_LOCK
Gdk.ModifierType.MOD5_MASK, // SCROLL_LOCK
Gdk.ModifierType.MOD2_MASK|Gdk.ModifierType.LOCK_MASK,
Gdk.ModifierType.MOD2_MASK|Gdk.ModifierType.MOD5_MASK,
Gdk.ModifierType.LOCK_MASK|Gdk.ModifierType.MOD5_MASK,
Gdk.ModifierType.MOD2_MASK|Gdk.ModifierType.LOCK_MASK|Gdk.ModifierType.MOD5_MASK
};
/**
* Helper class to store keybinding
*/
private class Keybinding
{
public Keybinding(string accelerator, int keycode,
Gdk.ModifierType modifiers, KeybindingHandlerFunc handler)
{
this.accelerator = accelerator;
this.keycode = keycode;
this.modifiers = modifiers;
this.handler = handler;
}
public string accelerator { get; set; }
public int keycode { get; set; }
public Gdk.ModifierType modifiers { get; set; }
public KeybindingHandlerFunc handler { get; set; }
}
/**
* Keybinding func needed to bind key to handler
*
* @param event passing on gdk event
*/
public delegate void KeybindingHandlerFunc(Gdk.Event event);
public KeybindingManager()
{
// init filter to retrieve X.Events
Gdk.Window rootwin = Gdk.get_default_root_window();
if(rootwin != null) {
rootwin.add_filter(event_filter);
}
}
/**
* Bind accelerator to given handler
*
* @param accelerator accelerator parsable by Gtk.accelerator_parse
* @param handler handler called when given accelerator is pressed
*/
public void bind(string accelerator, KeybindingHandlerFunc handler)
{
debug("Binding key " + accelerator);
// convert accelerator
uint keysym;
Gdk.ModifierType modifiers;
Gtk.accelerator_parse(accelerator, out keysym, out modifiers);
Gdk.Window rootwin = Gdk.get_default_root_window();
X.Display display = Gdk.x11_drawable_get_xdisplay(rootwin);
X.ID xid = Gdk.x11_drawable_get_xid(rootwin);
int keycode = display.keysym_to_keycode(keysym);
if(keycode != 0) {
// trap XErrors to avoid closing of application
// even when grabing of key fails
Gdk.error_trap_push();
// grab key finally
// also grab all keys which are combined with a lock key such NumLock
foreach(uint lock_modifier in lock_modifiers) {
display.grab_key(keycode, modifiers|lock_modifier, xid, false,
X.GrabMode.Async, X.GrabMode.Async);
}
// wait until all X request have been processed
Gdk.flush();
// store binding
Keybinding binding = new Keybinding(accelerator, keycode, modifiers, handler);
bindings.add(binding);
debug("Successfully binded key " + accelerator);
}
}
/**
* Unbind given accelerator.
*
* @param accelerator accelerator parsable by Gtk.accelerator_parse
*/
public void unbind(string accelerator)
{
debug("Unbinding key " + accelerator);
Gdk.Window rootwin = Gdk.get_default_root_window();
X.Display display = Gdk.x11_drawable_get_xdisplay(rootwin);
X.ID xid = Gdk.x11_drawable_get_xid(rootwin);
// unbind all keys with given accelerator
Gee.List remove_bindings = new Gee.ArrayList();
foreach(Keybinding binding in bindings) {
if(str_equal(accelerator, binding.accelerator)) {
foreach(uint lock_modifier in lock_modifiers) {
display.ungrab_key(binding.keycode, binding.modifiers, xid);
}
remove_bindings.add(binding);
}
}
// remove unbinded keys
bindings.remove_all(remove_bindings);
}
/**
* Event filter method needed to fetch X.Events
*/
public Gdk.FilterReturn event_filter(Gdk.XEvent gdk_xevent, Gdk.Event gdk_event)
{
Gdk.FilterReturn filter_return = Gdk.FilterReturn.CONTINUE;
void* pointer = &gdk_xevent;
X.Event* xevent = (X.Event*) pointer;
if(xevent->type == X.EventType.KeyPress) {
foreach(Keybinding binding in bindings) {
// remove NumLock, CapsLock and ScrollLock from key state
uint event_mods = xevent.xkey.state & ~ (lock_modifiers[7]);
if(xevent->xkey.keycode == binding.keycode && event_mods == binding.modifiers) {
// call all handlers with pressed key and modifiers
binding.handler(gdk_event);
}
}
}
return filter_return;
}
}
How to use it
As some bindings needed to be adjusted you need at least version 0.11.3 of vala.
Furthermore are following packages needed:
- gtk+-2.0
- x11
- gdk-x11-2.0
- gee-1.0
Here is a sample how to compile this class:
valac --pkg gtk+-2.0 --pkg x11 --pkg gdk-x11-2.0 --pkg gee-1.0 keybinding-manager.vala
So and finally here some sample code how to bind a key:
public static int main (string[] args)
{
Gtk.init (ref args);
KeybindingManager manager = new KeybindingManager();
manager.bind("V", test);
Gtk.main ();
return 0;
}
private static void test()
{
debug("hotkey pressed");
}
Hope this sample class will be helpful for you. Happy coding ;).
Nice thx, this will help me in my Project. But i also want a windows version so im still thinling about how to bind vala with WIN32API function calls or make extern c calls, i hope i can still pack it in a “nice” class…
Oh one more think, i also found sth interresting which will maby make the whole XLib calls go away and maby also working on windows, (just read some hints in mailing list but did not try it out yet if it rly works) I’m speaking about a low level GDK add filter function which claims to be able to intercept all events (from specific window or with null from all but im not shure if that means system wide or Gtk apps wide …) before they reach the rest of Gtk. http://mail.gnome.org/archives/gtk-devel-list/2004-March/msg00246.html
What this sample code actually does is to bind a keybinding to a window so that that Gdk Window will be noticed of such X.Event.
See constructor (rootwin.add_filter(event_filter)) where the method which is mentioned in the mailinglist is used.
So therefore I currently do not see a way to register a global keybinding without using XLib.
However I would definitely be interested if you would be able to find one.