Colemak


I switched to Colemak a couple of years ago. I love it. Never going back. Not really any faster than QWERTY, but so much more comfortable.

One issue though is whenever I jump on someone else’s machine or remote into some server. I want to switch the layout but I don’t really want to install anything.

Here’s a little seventy-line F# app that will intercept all keys on the system on which it’s running and remap to Colemak:

open System
open System.Diagnostics
open System.Runtime.InteropServices
open System.Windows.Forms

[<Literal>]
let WH_KEYBOARD_LL = 13

[<StructLayout(LayoutKind.Sequential)>]
type KBDLLHOOKSTRUCT =
val vkCode : uint32
val scanCode : uint32
val flags : uint32
val time : uint32
val dwExtraInfo : nativeint

type LowLevelKeyboardProc = delegate of int * nativeint * KBDLLHOOKSTRUCT -> nativeint

[<DllImport("kernel32.dll", SetLastError = true)>]
extern nativeint GetModuleHandle(string lpModuleName)

[<DllImport("user32.dll", SetLastError = true)>]
extern bool UnhookWindowsHookEx(nativeint hhk)

[<DllImport("user32.dll", SetLastError = true)>]
extern nativeint SetWindowsHookEx(int idhook, LowLevelKeyboardProc proc, nativeint hMod, uint32 threadId)

[<DllImport("user32.dll", SetLastError = true)>]
extern nativeint CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, KBDLLHOOKSTRUCT lParam)

let SetHook (proc: LowLevelKeyboardProc) =
use curProc = Process.GetCurrentProcess ()
use curMod = curProc.MainModule
SetWindowsHookEx(WH_KEYBOARD_LL, proc, GetModuleHandle(curMod.ModuleName), 0u)

type App(handler) as x =
inherit ApplicationContext()
let rec callback (code : int) (wparam : nativeint) (lparam : KBDLLHOOKSTRUCT) : nativeint =
match handler code wparam lparam with
| Some keys -> SendKeys.Send keys; nativeint 1
| None -> CallNextHookEx(hook, code, wparam, lparam)
and proc = new LowLevelKeyboardProc(callback)
and hook = SetHook proc
override x.ExitThreadCore() =
UnhookWindowsHookEx(hook) |> ignore
base.ExitThreadCore()

printfn "Welcome to Colemak!"

Application.Run(new App(fun code wparam lparam ->
let key = int >> enum
if lparam.flags &&& 0b10010000u = 0u then // key down and not injected
match lparam.vkCode |> key with
| Keys.D -> Some "s"
| Keys.E -> Some "f"
| Keys.F -> Some "t"
| Keys.G -> Some "d"
| Keys.I -> Some "u"
| Keys.J -> Some "n"
| Keys.K -> Some "e"
| Keys.L -> Some "i"
| Keys.N -> Some "k"
| Keys.O -> Some "y"
| Keys.P -> Some ";"
| Keys.R -> Some "p"
| Keys.S -> Some "r"
| Keys.T -> Some "g"
| Keys.U -> Some "l"
| Keys.Y -> Some "j"
| Keys.OemSemicolon -> Some "o"
| _ -> None
else None))
Comments (6)

  1. Max Battcher says:

    Best solution would be to get Colemak added to the list of keyboard layouts installed by default in Windows. At this point Windows is the last major OS left that doesn't include Colemak "in the box". (Mac OS X and Unix do now.)

  2. @Max, absolutely! I wish I knew right person to talk to and make that happen…

  3. @Ashley, Maybe Michael Kaplan? He'd at least know who to get a hold of. blogs.msdn.com/…/michkap

  4. @kettch I had a little email thread with Michael today, he says it’s not being considered for the upcoming version, but it is on the radar.

  5. RB says:

    I have a mechanical keyboard that has hardware switches to change the layout, no worrying about having the layout set correctly/available!

    But right now I have a choice between Dvorak and Colemak. Looks like Colemak is the better choice based on your usage. But I'm curious, how did you come up with those heatmaps?