docwhat's avatardocwhat's blog

Aquamacs vs. Dvorak-Qwerty layout

I love Aquamacs Emacs. It totally rocks.

However, I use the Dvorak-Qwerty keyboard layout (AKA DQ) and Aquamacs doesn’t like the command key bindings that the DQ layout uses. DQ is dvorak except when you use the command key. Then it becomes QWERTY. This was invaluable when I first started using OS-X since it matched all the docs.

According to the FAQ Aquamacs cannot natively understand the command keys for this layout because it’s a Carbon application, not a Cocoa application.

Fortunately, there is a workaround…

According to davidswelt, this can be worked around by dropping in a custom osxkeys.el in ~/Library/Preferences/Aquamacs Emacs/.

A year ago, I did this customization by hand and pretty much forgot about.

Since then, Aquamacs has gone through several iterations.

The latest, 1.6, didn’t like my preference settings. Since I keep all my customizations (normally) in homedir I didn’t think twice about blowing away my ~/Library/Preferences/Aquamacs Emacs/ directory.

Whups! This broke all my command keys!

After spending a little time with Time Machine to recover the files and figure out what osxkeys.el was I decided to find a better solution.

And here it is; A horribly kludgy python script that essentially automates davidswelt’s workaround: based on a keymap hardcoded in the script it patches the osxkeys.el shipped with Aquamacs and then writes this patched version into ~/Library/Preferences/Aquamacs Emacs/.

Hopefully this will be useful for someone. If not, it’ll be useful for me when I forget about this and delete my preferences again.

Ciao!

#!/usr/bin/python -utWall
# -*- coding: utf-8 -*-

import os, sys, re, errno

class Modifier(object):
    rows = None
    filename = None
    is_changed = False

    regexp = re.compile('^(.*\(\s*define-key.*map.*\[\([^,]*,\s*osxkeys-command-key\s+)([^)]+)(\)\].*)$')

    def __init__(self, filename):
        self.filename = filename

        fd = file(filename, 'r')
        self.rows = [x.rstrip() for x in fd.readlines()]
        fd.close()

    def write(self, filename):
        if not self.is_changed:
            print "No changes to write."
            return

        # Backup
        if os.path.exists(filename):
            backname = filename + '.bak'
            try:
                os.unlink(backname)
            except OSError, err:
                if err.errno != errno.ENOENT:
                    raise
            os.link(filename, backname)

        # Write
        fd = file(filename, 'w')
        fd.writelines(["%s\n" % x for x in self.rows])
        fd.close()

    def check(self, map):
        keys = set(map.keys())
        values = set(map.values())
        value_not_a_key = values.difference(keys)
        key_not_a_value = keys.difference(values)
        diff = keys.symmetric_difference(values)
        if diff:
            print
            print "Key but not a value: %r" % key_not_a_value
            print "Value but not a key: %r" % value_not_a_key
            print
            raise ValueError("Unbalanced keys: %r" % diff)

    def fix(self, map):
        alreadydone = " ; @@CHANGED "
        rows = self.rows

        for i in range(len(rows)):
            line = rows[i]
            if alreadydone in line:
                continue

            match = self.regexp.match(line)
            if match:
                start, char, end = match.groups()
                char = char.strip()
                newchar = map.get(char, char)
                if char == newchar:
                    tail = "%s no change" % alreadydone
                else:
                    tail = "%s %s -> %s" % (alreadydone, char, newchar)

                rows[i] = "".join((start, newchar, end, tail))
                self.is_changed = True

def escape(c):
    mod = None
    parts = c.split(' ',1)
    if parts[0] == 'shift':
        mod = parts[0]
        c = parts[1]
    if len(c) == 1:
        if c in "?;`',<>][":
            c = '\\%s' % c
    if mod:
        return " ".join((mod,c))
    else:
        return c

if __name__ == '__main__':
    src = "/Applications/Aquamacs Emacs.app/Contents/Resources/site-lisp/macosx/osxkeys.el"
    m = Modifier(src)

    # Dvorak to QWERTY mappings
    # It was easier for me to type it this way...
    dvorak_to_qwerty = {
        #dv    qw        dv           qw
        "[":   '-',      '{':         '_',
        ']':   '=',      '}':         '+',

        "'":   'q',      '"':         'shift q',
        ',':   'w',      '< ':         'shift w',
        '.':   'e',      '>':         'shift e',
        'p':   'r',      'shift p':   'shift r',
        'y':   't',      'shift y':   'shift t',
        'f':   'y',      'shift f':   'shift y',
        'g':   'u',      'shift g':   'shift u',
        'c':   'i',      'shift c':   'shift i',
        'r':   'o',      'shift r':   'shift o',
        'l':   'p',      'shift l':   'shift p',
        '/':   '[',      '?':         '{',
        '=':   ']',      '+':         '}',
        '\\':  '\\',     '|':         '|',

        'a':   'a',      'shift a':   'shift a',
        'o':   's',      'shift o':   'shift s',
        'e':   'd',      'shift e':   'shift d',
        'u':   'f',      'shift u':   'shift f',
        'i':   'g',      'shift i':   'shift g',
        'd':   'h',      'shift d':   'shift h',
        'h':   'j',      'shift h':   'shift j',
        't':   'k',      'shift t':   'shift k',
        'n':   'l',      'shift n':   'shift l',
        's':   ';',      'shift s':   ':',
        '-':   "'",      '_':         '"',

        ';':   'z',      ':':          'shift z',
        'q':   'x',      'shift q':    'shift x',
        'j':   'c',      'shift j':    'shift c',
        'k':   'v',      'shift k':    'shift v',
        'x':   'b',      'shift x':    'shift b',
        'b':   'n',      'shift b':    'shift n',
        'm':   'm',      'shift m':    'shift m',
        'w':   ',',      'shift w':    '< ',
        'v':   '.',      'shift v':    '>',
        'z':   '/',      'shift z':    '?',

        }

    dvorak_to_qwerty = dict([(escape(x),escape(y)) for x,y in dvorak_to_qwerty.items()])

    m.check(dvorak_to_qwerty)

    # Flip it...
    qwerty_to_dvorak = dict([(y,x) for x,y in dvorak_to_qwerty.items()])
    m.fix(qwerty_to_dvorak)

    # Write out the changes.
    dst = os.path.expanduser("~/Library/Preferences/Aquamacs Emacs/osxkeys.el")
    if os.path.exists(dst):
        os.unlink(dst)
    m.write(dst)

# EOF
Edit on GitHub