Linux experience in macOS
I am often switching between a MacBook and a Linux desktop. From time to time I invest a bit of energy to nudge MacBook UX to be more like the UX I have on my Arch Linux installation. I mostly use command line tools so common UX is not that far of a stretch.
This article will overview various tools that I have found useful for this task.
Initial keyboard remap
First of all MacBook keyboard keys need a remap in a way which leaves as much as possible default keybindings working as one would expect in Linux.
For example, to open a new tab in the browser, one would use Cmd + t. Therefore, at least for browsers (and quite a few other GUI apps) the fix is quite simple: swap Fn and Cmd keys.
I use Karabiner-Elements for key remaps.
It has a simple UI for simple modifications. But one can always drop to the JSON configuration files to do complex remaps.
Having configuration in JSON files enables easy tracking for configuration changes. More on that later.
Making terminal work with cmd key
This quick remap works great for quite a few apps, but unfortunately it does not for terminal applications. Both, my terminal of choice alacritty, and VS Code terminal uses Ctrl key for usual terminal commands like Ctrl + c, Ctrl + w, etc on macOS.
To fix this, I needed to re-create bindings that work with Cmd as well as with Ctrl keys. Luckily, alacritty supports creating these kinds of bindings in its configuration file:
# Bind control characters to Command+... combination
# https://en.wikipedia.org/wiki/ASCII#ASCII_control_characters
- { key: A, mods: Command, chars: "\x01" }
- { key: C, mods: Command, chars: "\x03" }
- { key: D, mods: Command, chars: "\x04" }
- { key: E, mods: Command, chars: "\x05" }
- { key: U, mods: Command, chars: "\x15" }
- { key: W, mods: Command, chars: "\x17" }
VS Code also has support for that:
{
"key": "cmd+a",
"when": "terminalFocus",
"command": "workbench.action.terminal.sendSequence",
"args": {
"text": "\u0001"
}
},
{
"key": "cmd+c",
"when": "terminalFocus",
"command": "workbench.action.terminal.sendSequence",
"args": {
"text": "\u0003"
}
},
...
Text editing bindings
I am also used to moving around editable text with Ctrl + Arrow key. Also possibly selecting part of the text while holding the Shift key.
I found quite an old article that explained how to enable these key bindings in macOS: http://heisencoder.net/2008/04/fixing-up-mac-key-bindings-for-windows.html
I was happy to find that after 12 years, this solution still works. This is the config file, that did the trick:
{
"\UF729" = "moveToBeginningOfLine:"; /* Home */
"@\UF729" = "moveToBeginningOfDocument:"; /* Cmd + Home */
"$\UF729" = "moveToBeginningOfLineAndModifySelection:"; /* Shift + Home */
"@$\UF729" = "moveToBeginningOfDocumentAndModifySelection:"; /* Shift + Cmd + Home */
"\UF72B" = "moveToEndOfLine:"; /* End */
"@\UF72B" = "moveToEndOfDocument:"; /* Cmd + End */
"$\UF72B" = "moveToEndOfLineAndModifySelection:"; /* Shift + End */
"@$\UF72B" = "moveToEndOfDocumentAndModifySelection:"; /* Shift + Cmd + End */
"\UF72C" = "pageUp:"; /* PageUp */
"\UF72D" = "pageDown:"; /* PageDown */
"$\UF728" = "cut:"; /* Shift + Del */
"$\UF727" = "paste:"; /* Shift + Ins */
"@\UF727" = "copy:"; /* Cmd + Ins */
"$\UF746" = "paste:"; /* Shift + Help */
"@\UF746" = "copy:"; /* Cmd + Help (Ins) */
"@\UF702" = "moveWordBackward:"; /* Cmd + LeftArrow */
"@\UF703" = "moveWordForward:"; /* Cmd + RightArrow */
"@$\UF702" = "moveWordBackwardAndModifySelection:"; /* Shift + Cmd + Leftarrow */
"@$\UF703" = "moveWordForwardAndModifySelection:"; /* Shift + Cmd + Rightarrow */
}
Saved to ~/Library/KeyBindings/DefaultKeyBinding.Dict
file, it brought most of the familiar keybindings while moving around text back.
Bringing back tilde
For some reason the Tilde key was not printing ~ or `. Instead it would print ± or §.
Again, Karabiner-Elements was brought to the rescue, albeit with a strangely named configuration option:
Turning off sticky grave
With grave (`) symbol back in working order, there was another strange behaviour of this symbol and 🇱🇹 Lithuanian 🇱🇹 keyboard layout.
In the default Lithuanian layout there is some sticky mode being enabled by pressing grave once. If, after pressing grave, one would press a Lithuanian letter, like ą, then it would print 1 instead.
Maybe a nice feature, but it does not print the initial grave until second key is pressed. This is a bit confusing, especially since grave symbol is used quite a lot when writing about code.
To solve this, keyboard layout editor software Ukelele came to the rescue.
With this tool it became obvious that with the default Lithuanian layout grave key enters a different keyboard state. After removing this custom state, grave button went back to the expected function.
Moving windows
I am used to a tiling window manager when on Linux. However most often I have only a couple windows open on the screen. So having shortcuts for splitting windows into halves or thirds was enough of the functionality for my needs in macOS.
For that I use Rectangle.
There are other, more powerful tools in this scene, like ianyh/amethyst and koekeishiya/yabai, but currently a couple of features from Rectangle seems to do everything I need.
Launching applications
One of the first things to do, when Linux (or macOS) system boots up, is to open a terminal. 😉 This is a very frequent action and I usually have it bound to Meta + Enter on Linux. After all of the key swaps, it should end up on Ctrl + Enter on my macOS box.
I used koekeishiya/skhd tool to bind custom key combinations to launching GUI applications. It also is configured by editing a configuration file, which is always great.
# open terminal
ctrl - return : /MacOS/alacritty -e tmux-attach-or-new
shift + ctrl - return : /MacOS/alacritty -e fish
The following configuration opens a new terminal window with tmux when pressing Ctrl + Return. There is also an option to open a new terminal without tmux by additionally pressing Shift.
One more useful binding managed by skhd is to get GPG passphrase from LastPass and put it to clipboard:
# copy gpg passphrase to clipboard
ctrl - g : /MacOS/alacritty -e lpass show --sync=no --notes gpg/master --clip
This works great thanks to the lastpass/lastpass-cli utility that allows quick and easy access to LastPass vault from the terminal.
Tracking configuration
There is little point to custom scripts scattered all over the system, if there is no way of easily tracking changes in configuration files. twpayne/chezmoi does a really good job here.
I have been using chezmoi (French for home) for quite some time now. I originally used it to keep dotfiles on my Linux PC and two Linux laptops in sync.
It uses git for the actual change tracking. It integrates well with lpass for secret management and has template support if you want to have different contents in a file depending on a system.
For example, here is a template in alacritty configuration that resolves the path to the default shell according to the hostname of the system:
shell:
program:
{{- with (index (index .vars .chezmoi.hostname) "shell") }} {{ . -}} {{- end }}
args:
- -c
- tmux-attach-or-new
Then the remaining part is to list all of the different paths to the shell grouped by the hostname:
[data.vars.laguna]
shell = "/usr/local/bin/fish"
...
[data.vars.fortuna]
shell = "/usr/bin/fish"
...
I keep all of my Linux and macOS dotfiles in a single publicly accessible repository. Do not hesitate to take a look: https://github.com/2m/dotfiles