Are you a macOS user occasionally dealing with Windows systems or trying to switch platforms? Are you a Windows user that believes that the Windows-native keyboard shortcuts are objectively bad? Are you annoyed by something as simple as copy/pasting text not working consistently across apps?

If so, this post will equip you with an AutoHotkey configuration file that brings macOS keyboard shortcuts to Windows. Read on.

Motivation

Up until last summer, I had been a macOS-only desktop user for about 15 years with the occasional peek at Windows 10 on an old PC. While I continue to prefer macOS as a desktop environment, I’m slightly concerned about the direction Apple is taking with the platform and I don’t like the thought of being locked in. Part of the reason behind me joining Microsoft last October was to familiarize myself with Windows, as was purchasing a Surface Go 2 last summer.

Upon joining the company, I tried to do all of my work from the Surface Laptop 3 that I was given, and… well, that was quite painful. Getting used to a new company, a new team, a new product, a new set of technologies… all from an OS I wasn’t fluent in was just too much. At that time, I enrolled my personal Mac Pro into the corporate network and did most of my day-to-day work from it. Clearing the productivity barrier was nice, but the situation had to change: the security software forced onto my Mac made it crawl; I still had to use Windows on a remote VM and on the laptop; and, well, I wasn’t fulfilling my plan of learning Windows.

To start addressing this, I set up an old Dell Optiplex 9020 as my desktop (which still works surprisingly well after a recent RAM upgrade), opted it into the corporate network, and I was full-time on Windows for work purposes. And it was hard again. You know, I do like Windows 10 and the platform has become neat for development with WSL, Windows Terminal, and VSCode, but I just could not get used to the keyboard shortcuts. The inconsistency across apps breaks “the flow”, and me still using my Mac Pro for personal use didn’t help.

After a couple of months, I had gotten used to the native shortcuts, but I was still not comfortable. I still felt sluggish doing my day-to-day work. I still made unavoidable mistakes, like pressing Ctrl+C in the terminal to copy text. So, for me, the broken keyboard shortcuts are kind of a big deal. They are the kind of a big deal that would be a deal breaker if you asked me to abandon macOS completely. Interestingly enough, I’m not the only one who thinks this way:

@migueldeicaza on June 1st, 2021

Apple’s real lock-in is not the AppStore, it is ⌘C, ⌘V and ⌘X working everywhere - that’s why I can never go back to Linux or use Windows.

1.5k likes · Go to Twitter thread

Something had to give. I could go back to my macOS-only setup… or I could try to fix the shortcuts on Windows to feel comfortable in the new environment.

Failed first attempts

My first attempt was via the Keyboard Manager in PowerToys. This was my first choice because this is a first-party tool. Unfortunately, in its current form, it’s excruciating to configure but I managed to get a reasonable set of keybindings to mimic the macOS shortcuts on Windows. It wasn’t great though: configuring different shortcuts for different apps wasn’t flexible enough and, if I recall correctly, the tool refused to run within a VM and on Windows Server 2019 (into which I have to remote).

Coincidentally, I saw kinto.sh pass by in Hacker News, which promises to painlessly offer macOS-like shortcuts on Windows. Sweet! I decided to give it a try and, indeed, it did the trick. But, once again, not without issues. I suffered from random stuck modifier keys here and there, and these were even more infuriating than the native Windows shortcuts. I tried to debug this issue and tweak the configuration to better fit my needs… but these only made the problem worse.

The thing is that kinto.sh tries to take advantage of the fact that most Ctrl+<Key> shortcuts on Windows directly translate to Cmd+<Key> on macOS. With this in mind, the tool swaps the modifier keys and then fixes up a few stragglers, giving you shortcuts that work most of the time with very little configuration. Neat trick, but it’s bound to fail: kinto.sh is based on AutoHotkey, and AutoHotkey’s own documentation says that remapping Alt+Tab is not going to work properly—which I experienced.

kinto.sh has various hacks to compensate for the problems described by AutoHotkey, but they don’t work perfectly. I wasted hours learning AutoHotkey’s configuration language and trying to fix the kinto.sh script, but the oddities and stuck keys remained for days, or even weeks.

AutoHotkey configuration

Before giving up, I decided to start with a fresh AutoHotkey configuration and attempted a manual remapping of all the macOS shortcuts I care about, one by one, without swapping the modifier keys. If you think about it, there are not that many: clipboard access, file manipulation, text editing, tab and window manipulation… and that’s about it. Yes, patching these one by one was kind of a whack-a-mole game for a bit, but I eventually reached a pretty stable configuration that I haven’t touched for months now.

Without further ado, here it is, the AutoHotkey configuration to set up macOS keyboard shortcuts on Windows:

; Sets up macOS-like keybindings on Windows via AutoHotkey.
;
; Last updated on 2021-07-14.

#SingleInstance force
#NoEnv
#Persistent
#InstallKeybdHook

Menu, Tray, Standard

GroupAdd, terminals, ahk_exe powershell.exe
GroupAdd, terminals, ahk_exe WindowsTerminal.exe
GroupAdd, terminals, ahk_exe Cmd.exe
GroupAdd, terminals, ahk_exe mstsc.exe ; Remote desktop.

GroupAdd, posix, ahk_exe powershell.exe
GroupAdd, posix, ahk_exe WindowsTerminal.exe
GroupAdd, posix, ahk_exe Cmd.exe
GroupAdd, posix, ahk_exe gvim.exe
GroupAdd, posix, ahk_exe mstsc.exe ; Remote desktop.

GroupAdd, vscode, ahk_exe VSCodium.exe
GroupAdd, vscode, ahk_exe Code.exe

; Emergency clear.
*PrintScreen::
Reload
Goto ReleaseModifiers
return

; CHEATSHEET
; # Win    ! Alt    ^ Ctrl    + Shift
;
; These modifiers work *after* remapping the modifiers.
; It doesn't matter where the keys are remapped; order is irrelevant.

;$AppsKey::RCtrl  ; Surface Laptop.
$AppsKey::RWin  ; Sculpt keyboard.

; Program launchers.
$#e::Run explorer
$#n::Run notepad
$#t::Run wt

; Window manipulation.
$!q::Send !{F4}

; Workspace movement.
$^Left::Send ^#{Left}
$^Right::Send ^#{Right}

; Screenshots.
;$!+3::Send {PrintScreen}
;$!+4::Send #+{S}

; Special characters.
$^+c::Send {�}
$^+n::Send {�}
$!-::Send {�}
$!+-::Send {�}

; Hide all instances of active program.
!h::
WinGetClass, class, A
SetTitleMatchMode, 2
WinGet, AllWindows, List
loop %AllWindows% {
    WinGetClass, WinClass, % "ahk_id " AllWindows%A_Index%
    if(InStr(WinClass,class)){
        WinMinimize, % "ahk_id " AllWindows%A_Index%
    }
}
return

; Cycle between same-app windows.
!`::
WinGet, ActiveProcess, ProcessName, A
WinGet, OpenWindowsAmount, Count, ahk_exe %ActiveProcess%
if (OpenWindowsAmount > 1) {
    WinGetTitle, FullTitle, A
    AppTitle := SubStr(FullTitle, InStr(FullTitle, " ", false, -1) + 1)

    SetTitleMatchMode, 2
    WinGet, WindowsWithSameTitleList, List, %AppTitle%

    if (WindowsWithSameTitleList > 1) {
        WinActivate, % "ahk_id " WindowsWithSameTitleList%WindowsWithSameTitleList%
    }
}
return


; Lock screen and turn off monitor.
$!^q::
    Sleep, 200
    DllCall("LockWorkStation")
    Sleep, 200
    SendMessage,0x112,0xF170,2,,Program Manager
    return

#IfWinNotActive ahk_group posix
    $!a::Send ^a ; Select all.
    $!f::Send ^f ; Find.
    $!l::Send ^l ; Location bar.
    $!r::Send {F5} ; Refresh.
    $!z::Send ^z ; Undo.
    $!+z::Send ^y ; Redo.
    $^!Space::Send #; ; Emoji selector.

    ; File manipulation.
    $!o::Send ^o
    $!s::Send ^s

    ; Line edits.
    $^k::SendInput +{End}{Delete}
    $^o::SendInput {Enter}{Up}
    $!/::Send ^/ ; Comment line.

    ; Word edits.
    $#Backspace::Send ^{Backspace}
    $!Backspace::Send ^{Backspace}

    ; Cursor movement.
    $^a::Send {Home}
    $^e::Send {End}
    $^p::SendInput {Up}
    $^n::SendInput {Down}
    $^b::SendInput {Left}
    $^f::SendInput {Right}
    $#b::SendInput ^{Left}
    $#f::SendInput ^{Right}

    $!Left::Send {Home}
    $!Right::Send {End}
    $!+Left::Send +{Home}
    $!+Right::Send +{End}

    ; Formatting.
    $!b::Send ^b ; Bold.
    $!i::Send ^i ; Italic.
    $!u::Send ^u ; Underline.
    $!k::Send ^k ; Insert link.
#If

#IfWinActive ahk_group vscode
    $!+p::Send ^+p ; File bar.
    $!p::Send ^p ; File bar.
    $!,::Send ^, ; Settings.
#If

#IfWinNotActive ahk_group terminals
    ; Clipboard.
    $!c::Send ^c
    $!v::Send ^v
    $!+v::Send ^+v
    $!x::Send ^x

    ; Tabs.
    $!w::Send ^w
    $!n::Send ^n
    $!+n::Send ^+n
    $!t::Send ^t
    $!+t::Send ^+t
    $!+{::send ^{PgUp}
    $!+}::send ^{PgDn}
    $!0::Send ^0
    $!1::Send ^1
    $!2::Send ^2
    $!3::Send ^3
    $!4::Send ^4
    $!5::Send ^5
    $!6::Send ^6
    $!7::Send ^7
    $!8::Send ^8
    $!9::Send ^9

    $^d::SendInput {Delete} ; Delete character.
    $!,::Send ^, ; Settings.
#If
#IfWinActive ahk_group terminals
    ; Clipboard.
    $!c::Send ^+c
    $!v::Send ^+v

    ; Tabs.
    $!w::Send ^+w
    $!n::Send ^+n
    $!t::Send ^+t
    $!+{::send ^+{Tab}
    $!+}::send ^{Tab}
    $!0::Send ^!0
    $!1::Send ^!1
    $!2::Send ^!2
    $!3::Send ^!3
    $!4::Send ^!4
    $!5::Send ^!5
    $!6::Send ^!6
    $!7::Send ^!7
    $!8::Send ^!8
    $!9::Send ^!9
    $!+0::Send ^+0
    $!+1::Send ^+1
    $!+2::Send ^+2
    $!+3::Send ^+3
    $!+4::Send ^+4
    $!+5::Send ^+5
    $!+6::Send ^+6
    $!+7::Send ^+7
    $!+8::Send ^+8
    $!+9::Send ^+9

    $!,::Send ^, ; Settings.
#If

ReleaseModifiers:
Send {RCtrl up}
Send {LCtrl up}
Send {RAlt up}
Send {LAlt up}
Send {RWin up}
Send {LWin up}
Send {RShift up}
Send {LShift up}
return

I can’t promise this will work great with the apps you use, but my workflow is mostly around Windows Terminal, VSCode, Edge, and Word—and it does the trick just fine with these. I do get most macOS shortcuts, and I even get Emacs-like editing features in almost any text field (just like on a Mac!).

I store this file in the Git repository I use to share configurations across computers, and I have a script like the following to install the above at startup time:

$startup = "$Home\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup"
if (!(Test-Path "$startup")) {
    mkdir -Path "$startup"
}
$WshShell = New-Object -comObject WScript.Shell
$Shortcut = $WshShell.CreateShortcut("$startup\macos.ahk.lnk")
$Shortcut.TargetPath = "$Home\config\macos.ahk"
$Shortcut.Save()

Using this AutoHotkey configuration has massively changed my Windows experience for the better. Best of all: it works on my desktop; it works on my laptop; and it even works on the local VMs and remote machines I have to connect to. In fact, this is such a game changer that I’m at a point where I wonder if, when Apple stops supporting the Mac Pro 2013 I have, I’ll choose a Mac as my next machine or not.

Hope this helps!