Macs, Control Characters, and iTerm2
I always forget how to do this. And I always lose Brad Erickson’s excellent article that taught me how to fix it. So here it is, for posterity.
Whenever I switch to a new Mac, be that through replacement of machine or job, I tend to wrestle with my laptop for a while. Mostly, this is due to Mac systems using the Command (⌘) key for shortcuts (copy, paste, etc.) while my favoured terminal emulator, iTerm2, sensibly uses Ctrl (^) as the modifier key for sending control characters (^C, etc).
I’m also a stubborn critic of modern laptop keyboard layouts in general. I don’t want the bottom-left key on my laptop to be Fn. I want it to be Ctrl, like how it was when I grew up, dammit! So of course, I use an external keyboard which has everything laid out the way I like it.
So, in summary, I want my keyboard to do the following, for example:
- Use Ctrl + c to copy in most applications.
- Use Ctrl + c to send control characters in iTerm2.
This presents a small challenge, because these are different things. One uses by default, Command (⌘). The other uses Ctrl (^).
Fix one thing, break another
When I first connect my external keyboard to a new Mac, copying, pasting, etc. requires that I use Super (“Windows”) + c for copy, for example. But iTerm2 works perfectly as I expect it to. No good.
I know what you’re thinking. Just go to System Preferences → Keyboard → Modifier Keys… and make sure you select the correct keyboard from the drop-down box, then swap the keys around like this:
This fixes my first requirement. I can now copy, paste, etc. with what my keyboard considers Ctrl. But it’s broken the second one! Now I need to use Super (“Windows”) + c to send a control character via iTerm2. Oh no!
Is there a setting for that?
How about the iTerm2 → Preferences → Keys → Remap Modifiers menu?
As soon as you start playing with this, iTerm2 asks for Accessibility Access (Events) privileges on your laptop.
If you grant this, you’re presented with a menu that looks suspiciously familiar to the one that we messed around with earlier:
For one, this is a bit of a mind-melter for me. What if I’ve adjusted the System Preferences menu from earlier? Does this override those? Perform another remap on top? Something else? My brain hurts just thinking about it.
For two, no matter what I adjust here, it doesn’t actually do anything to my external keyboard. I’m left to assume that this menu is hard-coded to adjust settings for the laptop’s built-in keyboard, as it seems to be lacking the Select keyboard drop-down box from the earlier menu.
Let’s not mess around with this, and let’s find another solution.
iTerm2 Key Bindings to the rescue!
Brad Erickson seems to be the only other person on the entire Internet that this behaviour frustrates, and he wrote a great article on how to fix it using iTerm2‘s Key Bindings configuration.
Here’s how I got it working to my liking:
Hit up iTerm2 → Preferences → Keys. The default tab in this window is Key Bindings. In the bottom left corner, press the small plus sign (+) to add a new binding. That’ll bring up a menu asking you for a shortcut and an action.
Here’s where I press Ctrl + c, which now shows up as Command + c (⌘c) after my previous adventures in System Preferences. As for an action, I want to Send Hex Code. Then, I can use Wikipedia’s handy ASCII Control Characters table to select the hex code that represents the control character that I want to send. I want ^C, which has a hex code of 03, formatted properly as 0x3 as far as iTerm2 is concerned. Turns out this control character is actually called the End-of-Text character. TIL.
As soon as the OK button is pressed, my shell’s behaviour is as I expect.
How about other control characters?
You can use the same mechanism to add the control characters that you send most often. Over a year and half, I’ve found that I only commonly use these:
- ^C (0x3): Ctrl + c (End-of-Text, or “send interrupt”)
- ^D (0x4): Ctrl + d (End-of-Transmission, or “logout”)
- ^Z (0x1a): Ctrl + z (Substitute, or “background job”)
- ^] (0x1d): Ctrl + ] (Group Separator, or “exit telnet”)
I’ll be the first to admit that setting these up manually isn’t ideal, but given the infrequency with which I move machines, I’m happy to take the hit since it’s truly a “set and forget” solution.
Hopefully this will help someone out there. And hopefully now you can control your terminal properly and escape any stress that this situation was putting you under!