One of the things that bugs me the most about keyboard bindings is how vim’s idiosyncratic hjkl
navigation keys have taken over the world - GMail was probably the first true mass-market UI that implemented them, then Google Reader, then Reeder, so it has become somewhat of a pain to not find them available everywhere.
One of those places is Lion’s excellent threaded message list view, which I find counter-intuitive to navigate with the default keyboard bindings.
I can navigate the message list itself just fine with the cursor keys (in fact, I’m used to navigating the whole of Mail.app using nothing but the keyboard), but after jumping to the thread list using Tab, I somehow find it confusing (and frustrating) that the up/down cursor keys scroll in minute increments and that the left/right ones actually move from one message to the other.
With my diving in to vim even further, and after the past evening’s hack, it was a trivial matter to build a Mail.app plugin that uses hjkl
to navigate the thread view alone.
Again, the source is small enough to publish:
from AppKit import *
from Foundation import *
from Quartz.CoreGraphics import * # CGEvent
import objc
def swizzle(*args):
""" Brilliant piece of coding from http://klep.name/programming/python/ """
cls, SEL = args
def decorator(func):
oldIMP = cls.instanceMethodForSelector_(SEL)
def wrapper(self, *args, **kwargs):
return func(self, oldIMP, *args, **kwargs)
newMethod = objc.selector(wrapper,
selector = oldIMP.selector,
signature = oldIMP.signature)
objc.classAddMethod(cls, SEL, newMethod)
return wrapper
return decorator
my_keymap = { # when sorting messages in decreasing date order
4: 115, # h maps to Fn + cursor left
37: 119, # l maps to Fn + cursor right
38: 124, # j maps to cursor left - next message
40: 123 # k maps to cursor right - previous message
}
@swizzle(MessagesTableView, 'keyDown:')
def keyDown_(self, original, event):
code = event.keyCode()
NSLog('Handling key %d' % code)
if code in my_keymap.keys():
NSLog("Changing key %d to %d" % (code, my_keymap[code]))
original(self, NSEvent.eventWithCGEvent_(CGEventCreateKeyboardEvent(None,my_keymap[code],True)));
original(self, event)
MVMailBundle = objc.lookUpClass('MVMailBundle')
class TestPlugin(MVMailBundle):
def initialize (cls):
MVMailBundle.registerBundle()
NSLog("TestPlugin registered with Mail")
initialize = classmethod(initialize)
Have, as they say, the appropriate amount of fun.