Extras

The macOS shortcuts Serenity's firmware expects, config for the apps around it, and a guide to Vim-style editing without Vim.

macOS setup

Most keys on the Serenity QMK OS, Hardware and Action layers send shortcuts macOS already knows. The ones below have no default, so they do nothing until you set them. Tick them off as you go; the ticks stay in this browser.

0 of 13 done

Application config

Ghostty

Add this to your Ghostty config, at ~/.config/ghostty/config by default.

~/.config/ghostty/config
# sends ESC d, which Zsh/Bash typically interpret as kill-word (delete next word).
keybind = alt+delete=esc:d

iTerm2

Key mappings for moving with the arrow keys and modifiers. Import the preset under Settings → Keys → Key Bindings → Presets… → Import…

iTerm2 key bindings preset
{"Key Mappings":{"0xf702-0x320000-0x7b":{"Version":0,"Action":47,"Text":"2","Label":""},"0xf703-0x280000-0x0":{"Version":0,"Action":10,"Text":"f","Label":""},"0x7a-0x100000-0x6":{"Version":0,"Action":11,"Text":"0x1f","Label":""},"0xf703-0x2a0000-0x7c":{"Version":0,"Action":46,"Text":"1","Label":""},"0x5a-0x120000-0x6":{"Version":0,"Action":11,"Text":"0x18 0x1f","Label":""},"0xf700-0x300000-0x0":{"Version":0,"Action":7,"Text":"","Label":""},"0xf702-0x2a0000-0x7b":{"Version":0,"Action":47,"Text":"1","Label":""},"0x9-0x40000-0x0":{"Version":0,"Action":25,"Text":"Select Next Tab
Select Next Tab","Label":""},"0x7f-0x100000-0x33":{"Version":0,"Action":11,"Text":"0x15","Label":""},"0xf72d-0x100000-0x0":{"Version":0,"Action":8,"Text":"","Label":""},"0xf702-0x280000-0x0":{"Version":0,"Action":10,"Text":"b","Label":""},"0xf72b-0x100000-0x0":{"Version":0,"Action":4,"Text":"","Label":""},"0x74-0x100000-0x0":{"Version":0,"Action":25,"Text":"New Tab with Current Profile","Label":""},"0x54-0x120000-0x0":{"Version":0,"Action":25,"Text":"Split Vertically with Current Profile
Split Vertically with Current Profile","Label":""},"0xf703-0x300000-0x0":{"Version":0,"Action":11,"Text":"0x05","Label":""},"0x19-0x60000-0x0":{"Version":0,"Action":25,"Text":"Select Previous Tab
Select Previous Tab","Label":""},"0xf703-0x320000-0x0":{"Version":0,"Action":46,"Text":"2","Label":""},"0xf72c-0x20000-0x0":{"Version":0,"Action":9,"Text":"","Label":""},"0xf72d-0x20000-0x0":{"Version":0,"Action":8,"Text":"","Label":""},"0xf703-0x220000-0x7c":{"Version":0,"Action":46,"Text":"0","Label":""},"0x7f-0x80000-0x33":{"Version":0,"Action":11,"Text":"0x17","Label":""},"0xf729-0x100000-0x0":{"Version":0,"Action":5,"Text":"","Label":""},"0xf72c-0x100000-0x0":{"Version":0,"Action":9,"Text":"","Label":""},"0xf702-0x300000-0x7b":{"Version":0,"Action":11,"Text":"0x01","Label":""},"0xf701-0x300000-0x0":{"Version":0,"Action":6,"Text":"","Label":""},"0xf728-0x80000-0x75":{"Version":0,"Action":11,"Text":"0x1b 0x64","Label":""},"0xf702-0x220000-0x7b":{"Version":0,"Action":47,"Text":"0","Label":""}},"Touch Bar Items":{}}

Hammerspoon

Lets Karabiner-Elements know when the screen is locked. Add it to your Hammerspoon config, at ~/.hammerspoon/init.lua by default.

~/.hammerspoon/init.lua
local karabinerLog = hs.logger.new("karabinerSync", "info")

local KARABINER_CLI = "/Library/Application Support/org.pqrs/Karabiner-Elements/bin/karabiner_cli"

-- Push a variable into Karabiner-Elements so complex modification rules
-- can react to it via variable_if / variable_unless conditions.
local function setKarabinerVariable(name, value)
  local json = string.format('{"%s": %d}', name, value)

  local task = hs.task.new(KARABINER_CLI, function(exitCode, _, stdErr)
    if exitCode ~= 0 then
      karabinerLog.ef("karabiner_cli failed (%d): %s", exitCode, stdErr)
    end
  end, { "--set-variables-from-stdin" })

  task:setInput(json)
  task:start()
end

-- Keep Karabiner's `is_lock_screen` variable in sync with the real lock state,
-- since Karabiner has no native way to detect this itself.
local screenWatcher = hs.caffeinate.watcher.new(function(eventType)
  if eventType == hs.caffeinate.watcher.screensDidLock then
    setKarabinerVariable("is_lock_screen", 1)
  elseif eventType == hs.caffeinate.watcher.screensDidUnlock then
    setKarabinerVariable("is_lock_screen", 0)
  end
end)

screenWatcher:start()

Automator

An AppleScript Quick Action that toggles Dark Mode, if you'd rather not use the Shortcuts app version in macOS setup above. Create it in Automator, then give it a shortcut in System Settings → Keyboard → Keyboard Shortcuts… →Services: ⌃⌥⇧⌘D matches the Serenity QMK Hardware layer.

Quick Action
on run {input, parameters}
	tell application "System Events"
		tell appearance preferences
			set dark mode to not dark mode
		end tell
	end tell

	return input
end run

Vim-less

Thinking of quitting Vim? These are the common Vim motions and commands done with standard Mac shortcuts. Most work in any app; the ones tagged code editors are VS Code's defaults, and other editors have close equivalents. They're comfortable on Serenity because the firmware puts modifiers and arrows on the home row, with paging and clipboard keys right below them. They won't replace a modal editor for everything, but they'll keep you in Insert mode far more often if you can't quit cold turkey.

Move

h, j, k, l
Move
Press: ← ↓ ↑ →
b
Move to start of word
Press: ⌥ ←
e
Move to end of word
Press: ⌥ →
^
Move to first character of linecode editors
Press: ⌘ ← or Home
0
Move to start of line
Press: ⌘ ← (twice in code editors, or Home twice)
$
Move to end of line
Press: ⌘ → (or End in code editors)
gg
Jump to beginning of document
Press: ⌘ ↑
G
Jump to end of document
Press: ⌘ ↓
⌃ b
Page up
Press: Page Up key (Num/Nav layer)
⌃ f
Page down
Press: Page Down key (Num/Nav layer)
:42
Go to linecode editors
Press: ⌃ G
%
Jump to matching bracketcode editors
Press: ⇧ ⌘ \

Delete

X
Delete previous character
Press: ⌫
x
Delete character
Press: ⌦
db
Delete to start of word
Press: ⌥ ⌫
de
Delete to end of word
Press: ⌥ ⌦
d0
Delete to start of line
Press: ⌘ ⌫
D
Delete to end of line
Press: ⌘ ⌦ or ⌃ K
xp
Swap two characters
Press: ⌃ T

Clipboard and undo

u
Undo
Press: Undo key (Num/Nav layer)
⌃ r
Redo
Press: Redo key (Num/Nav layer)
d
Cut
Press: Cut key (Num/Nav layer)
y
Copy
Press: Copy key (Num/Nav layer)
p
Paste
Press: Paste key (Num/Nav layer)
dd, yy
Cut or copy linecode editors
Press: Cut or Copy key with nothing selected

Select

v
Selection (Visual mode)
Press: ⇧ with any movement above, to grow or shrink the selection
viw
Select cursor word
Press: Word Select key (Num/Nav layer); tap again to add the next word
V
Select cursor line
Press: Word Select key with Shift; tap again to add the next line
gn
Add the next match to the selectioncode editors
Press: ⌘ D

Lines

>>
Indentcode editors
Press: ⌘ ], or ⇥ (with lines selected)
<<
Un-indentcode editors
Press: ⌘ [ or ⇤
o
Open line belowcode editors
Press: ⌘ ↵
O
Open line abovecode editors
Press: ⇧ ⌘ ↵
ddp
Move line downcode editors
Press: ⌥ ↓
ddkP
Move line upcode editors
Press: ⌥ ↑
yyp
Duplicate linecode editors
Press: ⇧ ⌥ ↓
J
Join linescode editors
Press: ⌃ J

Search

/
Find
Press: ⌘ F
n
Next match
Press: ⌘ G
N
Previous match
Press: ⇧ ⌘ G
*
Find word under cursor
Press: Word Select key, then ⌘ E, then ⌘ G
:%s
Find and replacecode editors
Press: ⌥ ⌘ F

Files

:w
Save
Press: ⌘ S
:q
Close, or quit the app
Press: ⌘ W, ⌘ Q

Native Mac text fields also take Emacs-style keys: ⌃ A and ⌃ E for the start and end of the line, ⌃ K to cut to the end of the line, and ⌃ Y to paste it back.

What's left, like text objects (ciw), the dot repeat and macros, is where a modal editor still earns its keep, though a modern editor covers a lot of it with extensions or built-in commands. These are two VS Code extensions I maintain to fill such gaps.