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
.