2011-09-16 : Amundsen-Scott South Pole Station

A South Pole Door Handle

So I’ve been at the South Pole for a while. In fact, it has almost been a year now. It has been a challenging winter in some ways and I’m feeling more than a little “toasty”. I’m not sure what else to say about it, so I’m going to write about some other stuff…

I’m switching from Fraise to MacVim. I’m also changing shells from bash to zsh. I’m changing from MacFuse to OSXFuse. I’m sticking to Firefox for most of my browsing (NoScript and AdBlock Plus are entirely indispensable). I’m giving GPGTools a try. Boxee has been really nice to have in my berthing room. I’ve never been a big fan of GNU screen, but I’m interested in tmux.

Rui Carmo posted his PyObjC code that uses latent semantic mapping to sort email in Mail.app and his code to rebind the keys in Mail.app’s threaded message view (both OS X 10.7). I am utterly impressed these posts. I’ve seen Objective-C method swizzling before – from a distance, but I’ve never gotten my hands into it. So I decided to take a crack at it. I extended his key binding example so that it also rebinds keys in the main message list, and to support remapping one key to a sequence of keys with optional modifiers applied. Now Mail.app supports the vim navigation bindings (h,j,k,l) and a few other keys: d - delete, f - forward, r - reply, R - reply all. I love working in Python and I enjoy Objective-C, so doing this project in PyObjC has been really fun and having a more customized tool has been very useful. Here’s my fork of his code snippet:

#!/usr/bin/python
from AppKit import *
from Foundation import *
from Quartz.CoreGraphics import * # CGEvent
import objc

# Based on work by Rui Carmo
# http://the.taoofmac.com/space/blog/2011/08/13/2110

shift = kCGEventFlagMaskShift
command = kCGEventFlagMaskCommand
option = kCGEventFlagMaskAlternate
modifier_mask = NSDeviceIndependentModifierFlagsMask

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


# So the keys are numeric tuples (keycode, modifier mask)
# The values are lists of tuples, which define key sequences
# [(keycode, flag mask), ...]

@swizzle(MessagesTableView, 'keyDown:')
def keyDown_(self, original, event):
    my_keymap = { # when sorting messages in decreasing date order
        # Navigation
        (4,  0): [(115, 0)], # h maps to Fn + cursor left
        (37, 0): [(119, 0)], # l maps to Fn + cursor right
        (38, 0): [(124, 0)], # j maps to cursor right  - next message 
        (40, 0): [(123, 0)], # k maps to cursor left - previous message
        # Operations
        (2,  0):     [(51, 0)],                # d -> delete, down arrow
        (15, 0):     [(15, command)],          # r -> cmd-r
        (15, shift): [(15, command | shift)],  # R -> cmd-shift-r
        (3,  0):     [(3,  command | shift)],  # f -> cmd-shift-f
        (44, 0):     [(3,  command | option)], # / -> cmd-opt-f

    }
    remap_event(self, original, event, my_keymap)


@swizzle(MailTableView, 'keyDown:')
def keyDown_(self, original, event):
    my_keymap = {
        # Navigation
        (4,  0): [(123, 0)], # h -> cursor left
        (37, 0): [(124, 0)], # l -> cursor right
        (38, 0): [(125, 0)], # j -> cursor down
        (40, 0): [(126, 0)], # k -> cursor up
        # Operations
        (2,  0):     [(51, 0), (125, 0)],      # d -> delete, down arrow
        (15, 0):     [(15, command)],          # r -> cmd-r
        (15, shift): [(15, command | shift)],  # R -> cmd-shift-r
        (3,  0):     [(3,  command | shift)],  # f -> cmd-shift-f
        (44, 0):     [(3,  command | option)], # / -> cmd-opt-f
    }
    remap_event(self, original, event, my_keymap)


def remap_event(self, original, event, keymap):
    debug = True
    pressed_key = (event.keyCode(), event.modifierFlags() & modifier_mask)

    if debug:
        NSLog('Handling key %s' % pressed_key.__str__())

    if pressed_key in keymap.keys():
        replacement_sequence = keymap[pressed_key]
        if debug:
            NSLog("Changing key %s to %s" %
                  (pressed_key.__str__(), replacement_sequence.__str__()))

        for (keycode, modifiers) in replacement_sequence:
            if debug:
                NSLog("Pressing modified key %s" % (keycode.__str__()))

            cge = CGEventCreateKeyboardEvent(None, keycode, True)
            if modifiers is not 0:
                CGEventSetFlags(cge, modifiers);
            original(self, NSEvent.eventWithCGEvent_(cge))
    else:
        original(self, event)


MVMailBundle = objc.lookUpClass('MVMailBundle')
class RebindMailKeys(MVMailBundle):
    def initialize (cls):
        MVMailBundle.registerBundle()
        NSLog("RebindMailKeys registered with Mail")
    initialize = classmethod(initialize)

I’ve really enjoyed OS X Lion too. I’m able to ignore a couple of annoying bits (cartoon interfaces on iCal and Address Book) and really take advantage of the many improvements.

So I installed cygwin’s SSH server on my windows machine. Since then, I’ve been able to take advantage of the many useful command line tools on that machine, from my mac. For example, with a shell alias and some careful keychain planning, this command works nicely: win net user myusername /Domain. Throw in PsTools and this kind of thing becomes so useful, that I can’t help wrapping some of them together into a quick GUI on the mac. I just need an I/O delegate program on the windows machine (to reduce the number of needed SSH connections) and I’m in business. So that means that I need to learn Core Data (because caching will be important, even with a delegate). Or maybe I’ll keep the program light-weight. PyObjC might be a good choice for that.