Global hotkeys with vala

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 ;).

Easen use of svn switch with bash prompt

The issue

svn switch can safe a lot of time when working in different branches as not all source code has to be checked out again.
But well you know it: How fast does it happen that a file gets committed in the wrong branch?
To prevent you from always have to call svn info before committing, here comes the script which includes the current branch in the bash prompt so you may never commit code in the wrong branch again ;).

Get the script

#!/usr/bin/perl

use Cwd;
my $dir = getcwd;
unless( -r $dir."/.svn" )
{
  exit;
}
$ret="svn";
$dir=`svn info | grep URL`;
$dir =~ s/n//;
if($dir =~ m//trunk/)
{
  $ret="trunk";
}
if($dir =~ m/branches/|tags//)
{
  $dir=$dir."/";
  $dir =~ s/.*(b|t)(ranches|ags)/(.*?)/.*/$1:$3/;
  $ret=$dir;
}
print " [$ret]";

What needs to be installed

Subversion of course and perl to run the script, which is usually installed in most distributions.

How to use it

Let’s store the script to svn-ps1.pl somewhere let’s say $HOME/bin.
Now open the bashrc file with vi ~/.bashrc and adjust the export of the PS1 variable:

PS1='${debian_chroot:+($debian_chroot)}[33[01;32m]u@h[33[00m]:[33[01;34m]w[33[00m]$ '

to the following:

PS1='${debian_chroot:+($debian_chroot)}[33[01;32m]u@h[33[00m]:[33[01;34m]w[33[00m]$(~/bin/svn-ps1.pl)$ '

Now simply change to some source code checked out with subversion and you will see that the prompt will look like this:
user@laptop:~/src/svn/some-code [b:branch-name]$

Cheers!

PPA für Backports

Es kommt doch noch häufiger vor als einem lieb ist, das man eine neue Version einer Library oder Applikation benötigt, die erst im Ubuntu Entwicklungszweig vorhanden sind, aber nicht in der aktuellen Version.
Von solchen Versionen mache ich dann oft doch noch einen Backport um diese auf meinem System zu testen. Um diese Backports auch der Öffentlichkeit zu Verfügung zu stellen, habe ich ein Backport PPA eröffnet. Von Zeit zu Zeit füge ich diesem PPA Backports aus verschiedenen Bereichen hinzu. Was alle Backports aber gleich haben, ist, dass diese aus einem offiziellen Ubuntu Repository erstellt wurden und daher weniger Probleme verursachen sollte.
Aber Gewähr dafür kann ich natürlich nicht geben.

Um Pakete aus dem Backport Repositroy zu installieren, muss folgender Befehl ausgeführt werden:
sudo add-apt-repository ppa:sao/backports

Jetzt fehlt nur noch eine apt-get update und es kann los gehen ;).