15

C-[ is equivalent to the escape key on US English keyboards, thus, any attempt at binding it will mess up M- behaviour.

Emacs seems to have no trouble telling <escape> and C-[ apart in GUI frames. The following works fine and bindings starting with M- remain to be working:

(global-set-key (kbd "<escape>") (lambda () (interactive) (message "<escape>")))

However, if I bind

(global-set-key (kbd "C-[") (lambda () (interactive) (message "C-[")))

suddenly emacs goes crazy and binding like M-x break. Moreover, pressing C-[ refuses to trigger the bound lambda. Interestingly enough, C-x @ c [ (apply modifier control to open bracket) still says C-[ is undefined.

Is there any way of binding something to C-[ without breaking emacs?

2 Answers 2

14

You cannot really change the C-[ binding at user-level maps, as you would do with global-set-key. However, you can change it as a keyboard event before it reaches those keymaps. You can say for example:

(define-key input-decode-map 
    (kbd "C-[") 
    [control-bracketleft])

and then use [control-bracketleft] in your keymaps. Pretty simple isn't it?

Director's Cut

Unfortunately, it is not that simple, and this solution requires some adjustments, which will appear to be very painful. You've been warned. But let's see first why user-level maps cannot answer the question. In the following, I refer to the Emacs Lisp manual for emacs 26.1 when I say "see something" without more precision.

C-[ is interpreted at a very early stage as the ASCII control character ESC (see 21.7.1 - Keyboard Events). This code is spread out all other the place as the prefix for longer sequences. There is a reason for that: ESC is actually the meta prefix (see meta-prefix-char), and all bindings that read M-something will translate to a sequence that starts with ESC. Thus, changing the global map won't be enough: you need first to change meta-prefix-char, then to remap ESC to your new meta-prefix-char in each and every map that uses M- before you can safely map C-[.

OK then, of course: let's use input-decode-map. There is a couple of similar maps we may get tempted to use (see sections 21.8.3 and 22.14), but let's stick to this one. And well... this works! You're done, aren't you?

Actually, no, the story does not end here. This works... as long as you're using a window system. If due to bad luck you're jailed in the linux console in an emergency state, you realize how dramatic the situation has become: arrow keys, Home, and of course M- bindings, are all garbage. Why? Because when the terminal says ESC (which it does when you type C-[), it really means ESC, and starts a sequence of the same kind it uses to transmit non-ASCII characters.

Observing the disaster, you may consider it wise to protect the above input-decode-map modification in such a way that it activates only in the case a window system is controlling the keyboard:

(let ((frame (framep (selected-frame))))
  (or (eq  t  frame)
      (eq 'pc frame)

      (define-key input-decode-map 
                  (kbd "C-[") 
                  [control-bracketleft])
     )))

Terminals then work as they used to.

Now, can we deal with C-[ on terminals? Actually, yes we can, on the linux console as well as on the other terminal emulators I can play with. But that makes the story a pretty long one, as new characters enter the scene. For it is no more emacs alone: the terminal has now the central role.

Let's lend an ear on what the linux console has to say. Type C-v before some key to hear it plain. C-[ is ESC; so is Esc. The up arrow sounds like ESC [ A, while M-a is ESC A. Hmm... Looks like this meta key circumvolutions in emacs isn't it? Anyway.

Unless we're ready to play some tricks based on the time elapsed between the character events (which by the way won't distinguish Esc from C-[), it seems we've got no choice but to tell the console what we actually don't mean ESC when we type C-[. Moreover, it appears pretty soon that C-[ is not the only problem with stock terminal codes: modifiers are most of time erased out the transmitted information. We need to customize the terminal for the very same reason we customize emacs: it would be so much more practical if we do.

At this point, you shall dare a deep look into your terminal's documentation eyes: man pages loadkeys(1) for the linux console, for xterm xterm(1) in section Custom Key Bindings, and whatever-i-dont-know for other terminals. In KDE konsole, you can define custom translations in Settings/Edit Current Profile... then Keyboard. Here is an excerpt from ~/.local/share/konsole/Test.keytab after playing with this latter dialog:

key [+Ctrl+AnyModifier : "\EO*["

Once you have the terminal send ESC O 5 [ for C-[ (as in the above configuration), you can go back to emacs. For of course, you're not done yet.

To instruct emacs what dialect a given terminal uses, you can adjust input-decode-map. Yes, this is fortuitously the very one we've modified at the beginning of this story, and this is the very one term/xterm.el touches when xterm is involved. A good place for the adjustment is tty-setup-hook (see section 40.1.3):

(add-hook 'tty-setup-hook 
   (lambda ()
    (let ((term (getenv "TERM")))
      (cond 
        (;; xterm-function-map not in doc, but in term/xterm.el
         (boundp 'xterm-function-map) 
         (map-my-term-codes xterm-function-map))

        ((equal term "linux")
         (map-my-term-codes input-decode-map))
        )
      )))

Be aware this hook only runs if you're in a terminal. Thus, you cannot insert here the code for the window system initialization. Here's the translation function by itself:

(defun map-my-term-codes (map)
      (define-key map (kbd "M-O 5 [") 
                      [control-bracketleft])
      )

And then you can take some rest: it's the end of the journey. Of course, if you don't care about terminals, it's quick as you'll skip all the painful part. But you'll admit it's also rather incomplete.

Two final notes:

  • I choose ESC O 5 [ to code C-[. This is just an example: I won't pretend it's a good choice. Only the 5 part, which means C-, seems to obey some kind of established convention

  • configuring the linux console leaves a bad taste: it does not seem possible to do the binding without using an intermediate existing symbol, and those I would need do not exist. I use symbols in the F21-F246 range as in most internet examples, but it is not very satisfying. It's OK for a few unrelated bindings, but it won't serve a systematic schema.

Edit

  • I've completed this with the Esc case - which has its own personality - in another post: How to remove bindings to the ESC prefix key
  • here is a fragment of a configuration to feed loadkeys with. I put this in /root/custom.kmap, and load it when I need to (which is rare). My actual config also maps arrows and different combinations of modifier, but it's rather long, choice of symbols and sequences is questionable, and I'm not sure the keycodes for my keyboard will match yours. So let's keep it at its due level: this is nothing but an illustration.

    keymaps 0-127
    
    # http://tldp.org/HOWTO/Keyboard-and-Console-HOWTO-15.html
    # web+man:keymaps
    # web+man:loadkeys
    
    # escape
    keycode  1  = F100
        alt keycode  1 = Escape # keep the Escape behavior somewhere          
    
    # keycode  26 = bracketleft
        control keycode 26 = F115 # Control_bracketleft does not exist          
    
    string F100     = "\033OO" # map this to [escape] in map-my-term-codes
    string F115     = "\033O5["
    
2
  • 1
    Thanks, that's a great answer. But surely even a great answer like this surely doesn't need to be bumped to the top of the front page 34 times. Each bump has a small cost which is shared by the community: checking for spam, looking if there's new interesting content, etc. Maybe you could group minor improvements together? Or just stick with what you have. Speaking from experience, there's no such thing as the perfect post, at some point you just have to move on. Aug 27, 2019 at 18:48
  • 1
    @Gilles Got it, and sorry for that. I was not aware there was some issue adjusting this at wish.
    – Champignac
    Aug 28, 2019 at 12:47
0

The following solution is a bit kludky, but seems to work:

Let ~/.xbindkeysrc contain the following:

"xvkbd -xsendevent -text '\[Control_L]\[F13]'"
  m:0x14 + c:34

"xvkbd -xsendevent -text '\[Control_L]\[F14]'"
  m:0x14 + c:35

Now xbindkeys will translate C-[ to C-<f13> and C-] to C-<f14>, so they can be bound in emacs freely. You will probably want to bind abort-recursive-edit to something other than C-], for example, C-S-g.

The downside is that now C-[ is broken in every application except Emacs, which could be fixed by adding some logic to test whether the key combination is being sent to emacs...

3
  • FWIW, I don't think there's anything special about C-].
    – Malabarba
    Jan 31, 2015 at 13:25
  • Yeah, me neither, but for some odd reason my C-] binding stopped working after I fired up Xbindkeys, so I rebound that one, too. Jan 31, 2015 at 13:28
  • When I install xbindkeys and use your code above, hitting "C-[" and "C-]" doesn't do anything. it detects my Ctrl click but is still waiting for input after I hit Ctrl. running Ubuntu 20.10 and Xfce desktop Mar 25, 2021 at 5:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.