Skip to Content

In Praise of AutoHotKey

People think it’s weird that I do all my development on a Windows machine. It’s definitely a second-class citizen experience in the wider development world, and Windows has a lot of really frustrating issues, but it’s still my favorite operating system. This is for exactly one reason: AutoHotKey.

AHK is an engine for mapping keystrokes to scripts. I wouldn’t call it particularly elegant, and it’s filled with tons of redundancy and quirks. Even its fans admit how nasty the language can be. But it hooks into the whole Windows system and makes it easy to augment my workflow. It’s given me a far greater degree of control over my computer than I ever managed to achieve with another OS.

Here’s a short list of some of the scripts I’ve written, in roughly ascending order of complexity.1

Mouse shortcuts

Let’s start with a simple one. I use Dragon voice to text for writing as I can speak prose a lot faster than I can type. My microphone is very sensitive and often picks up the sounds of the keyboard, so I need a way to toggle dictation on and off. I wanted to map the toggle button to tapping my mouse wheel left, but Dragon only supports keyboard shortcuts. So I glued the two together with AutoHotKey:

WheelLeft::Send, {NumpadAdd}

The WheelLeft is the shortcut itself. On the right of the :: is the actual command. In this case all it does is send a Numpad + keystroke, which happens to be the “toggle dictation” hotkey in Dragon. It’s a small thing that only took me about a minute to write and makes using Dragon more pleasant. Most my AutoHotKey uses are like this: tacking on a crude API on to something that doesn’t have one.

Hotstrings for math

I write a lot of mathematical notation, such as ∀x ∈ S: P(x) ⇒ Q(x), for work. Historically people wrote these characters by using a rendering engine like LaTeX or Mathjax, which are cumbersome and not portable. I can’t put arbitrary LaTeX into an email. The alternative to those is writing Unicode. But Unicode symbols are not on standard keyboards and there are a lot of characters to write. Most of the time I’ve seen people just copy and paste the characters they want, one character at a time.

AutoHotKey lets you define hotstrings, which trigger when certain strings are typed. While this can be any kind of command, it’s most often used to replace a short string with a much longer one.


I can also use it to map strings to complex Unicode characters. Here’s a sample of my current hotstrings list:2

; etc

Now if I type ;a x ;in S: ;diamond(f[x] ;geq 0), I get ∀x ∈ S: ◇(f[x] ≥ 0). This is a hell of a lot more convenient than copy and paste.

This blog is in Markdown. The syntax for a markdown link is [title](url). It’s really annoying if I have to copy both the title and the URL from somewhere else. Then I need to do two trips between the source and the markdown text, one to copy each part. Enter AutoHotKey:

#!c:: ; win+alt+c
ctmp := clipboard ; what's currently on the clipboard
clipboard := ""
Send ^c ; copy to clipboard
ClipWait, 2 ; wait for the clipboard to change
clipboard := "[" . clipboard . "](" . ctmp . ")"
Return ; ends a multiline command

If url is on the clipboard and I press win+alt+c while title is the selected text, then the clipboard is replaced with [title](url). This might seem like a small affordances but it makes adding links so, so much nicer.

Fast Window Switching

alt+tab is annoying because you have to visually confirm you’re on the right app. You can’t use muscle memory here because the stack depends on your recent windows. As a hard-core Vim user, anything that gets in the way of muscle memory is bad and should be avoided. Let’s write a better window switcher in AutoHotKey.

AHK lets you create window groups based on window titles, .exe name, or process ID. I can create a separate window group for each app, and then make a hotkey that specifically cycles through that window group:

GroupAdd, firefox, ahk_exe firefox.exe 

; right alt + 1
>!1:: GroupActivate, firefox, R

Now no matter where I am, pressing rightalt+1 will immediately switch to Firefox. If I’m already in Firefox, it cycles through the active windows. A window can belong to multiple groups, so I can have a special “workshops” key to cycle between lecture notes, slides, and the IDE. I have a separate window group for each of my most commonly used apps, including Neovim, the TLA+ toolbox, and the terminal.

Speaking of commonly used apps:

Remapping the calculator button

  1. Nobody likes the default Windows calculator
  2. If you press the calculator button when the calculator is already open, it just opens a 2nd calculator. Why would anyone want that?

So instead I have it open up Frink. If Frink is already open, it activates it. If it’s already active, it minimizes it. Much better UX.3

toggle_app(app, location) 
    if WinExist(app)   
        if !WinActive(app)
    else if location != ""
        Run, %location%

Launch_App2::toggle_app("Frink", "\Path\to\frink.jar")

I have the same thing set up for Spotify.


sci-hub is a service that lets you access research papers for free. It’s not quuiiiiiiiiite legal, but buying papers is super expensive, like fifty dollars per paper. Not something I can afford, and I think the entire industry is evil anyway.

Normally to use sci-hub you have to copy the blocked paper url, go to sci-hub, paste the link into a form on the website, and click go. It’s a lot more convenient than paying 50 bucks for a paper, but it’s still pretty annoying. If I’m trying to skim a dozen papers, doing that every single time breaks my flow.

Fortunately sci-hub uses a standard format for its links, so I can automate the entire process. This script reopens the current paper in sci-hub:

; Only enable hotkey in Firefox
#IfWinActive ahk_exe firefox.exe

; right alt + right ctrl + s
; wait for both control keys to be lifted
Keywait, RControl
Keywait, RAlt
; go to url bar
SendEvent, ^l

; add prefix, go
SendInput, {left}{enter}

I found that after adding this I’ve read a lot more science papers. There’s something about the difference between “a single hotkey” and “four steps” that makes me more likely to bother.

I have something set up similar for the Internet Archive, where I can just press a hotkey to reload the current page in the archive. This is handy for doing any kind of historical research.

I don’t think AutoHotKey or Windows is special in this regard. I could have gotten something similar working on Mac or Linux. But when I tried, I found it much harder. I had to dive into about how the OS worked. I remember trying for two days to get the sign thing working on Linux and eventually had to read up on how the scan codes worked. In the end I decided that the added convenience was just not worth the extra effort. And that was just one system. If I wanted to add my window switcher, I’d have to learn how the window management works.

The real draw is that AutoHotKey makes it all convenient. Each of these scripts takes me just a few minutes to write, no deep dives into Windows internals needed. When adding affordances to my computer is easy, I’m more likely to add them. Using AutoHotKey makes me feel like I can customize my machine. My scripts bring the computer closer to what I need.

The downside is that it’s pretty fragile. I have to tweak delays and sometimes reorganize my computer just to keep my scripts from breaking. I disabled every animation I can to minimize nondeterminism. I wish it was easier to use AutoHotKey if you weren’t a technical expert, so I could give scripts to my friends to help them too. But even with these issues it’s still one of my favorite programs, and still the reason I prefer working on Windows to any other operating system.

Thanks to Lito Nicolai for feedback.

  1. Disclaimer, I have not put all that much time into learning AHK best practices and am sure that all of my scripts can be made much better. [return]
  2. the O means that we do not have the space after the hot string. It’s nice for things like temporal logic, where you don’t have a space in □P. [return]
  3. You can get the same thing by fiddling with the registry keys, but then I’d have to fiddle with registry keys. And that’s not portable; I can copy this script to a new machine and instantly have it work right. [return]