33

Is it possible to simulate an arbitrary key event from elisp? I am aware of ways that I can find the binding for a given key, and then call that command interactively, but what if that key event is not bound to a command?

As one example, what if I wanted to bind C-` to behave the same as the ESC key in all contexts?

5
  • It seems like key-bindings is the wrong tag if you aren't trying to alias a key binding. Also, maybe you should change your example to something else so that it doesn't get confused.
    – b4hand
    Oct 22, 2014 at 23:45
  • @b4hand I'm open to suggestions for better tags. There is no key-events tag. Should I make one?
    – nispio
    Oct 22, 2014 at 23:46
  • sounds reasonable to me, but events might be better since this could also be applicable to mouse events.
    – b4hand
    Oct 23, 2014 at 0:43
  • 2
    I'm still confused as to whether you want to simulate a key event in elisp, or you specifically want the ability to make a key act as if it were another key? The likes of key-translation-map facilitate the latter, so if that's all you want, I would suggest using it rather than doing anything more manual.
    – phils
    Oct 23, 2014 at 1:01
  • ...and if key translation really is what you want here, I think that's a different question, and that you should ask that separately; and then re-word your example for this question to be more appropriate to the more general problem of "how do I simulate a key event in elisp?"
    – phils
    Oct 23, 2014 at 1:36

4 Answers 4

28

You can feed arbitrary events (keystrokes, mouse clicks, etc.) to the command loop by putting them onto unread-command-events. For example, the following will cause the command loop to execute a break the next time it is run:

(setq unread-command-events (listify-key-sequence "\C-g"))

Note that this only feeds events to the command loop, so it will do nothing interesting if you're looping in your own code.

A different approach, which you seem to be aware of, is to find the function a given key is bound to, and execute it yourself:

(funcall (global-key-binding "\C-g"))

This will execute the command immediately. Beware, however, that some commands have different behaviour depending on whether they are called interactively, such as defaulting arguments. You'll want to compensate for that by using call-interactively:

(call-interactively (global-key-binding "\C-g"))
6
  • I read about unread-command-events but I haven't been able to figure out how to use it. Setting it has had no effect for me. Are there any good example of how it is used?
    – nispio
    Oct 23, 2014 at 0:55
  • I've seen it used when asking the user to press space to continue — if the user presses anything else, it goes onto unread-command-events.
    – jch
    Oct 23, 2014 at 1:10
  • @nispio: unread-command-events is just what its name says. You can examine an event and then, depending on what it is, conditionally push it back onto u-c-e so that will then be processed normally. There are lots of examples of its use in the Emacs source code - grep is your friend.
    – Drew
    Oct 23, 2014 at 2:42
  • 1
    I was able to get unread-command-events to work. The piece I was missing before was the listify-key-sequence function. I had just been using the raw key vector.
    – nispio
    Oct 23, 2014 at 3:29
  • 1
    Thanks for this answer. I wanted to implement non-interactive tests of my completion system, so I used this idea to implement a with-simulated-input macro that evaluates any expression with unread-command-events let-bound to a specified key sequence: github.com/DarwinAwardWinner/ido-ubiquitous/blob/… Jan 6, 2016 at 20:38
14

The simplest way I know of is just to use execute-kbd-macro:

(defun foo () (interactive) (execute-kbd-macro (kbd "<escape>")))
(global-set-key (kbd "C-`") 'foo)
4
  • Evaluating the above and then pressing C-` gives me an error apply: Wrong number of arguments: #[(ad--addoit-function ....
    – nispio
    Oct 23, 2014 at 3:22
  • 1
    @nispio Not for me. That error looks like an advice.
    – Malabarba
    Oct 23, 2014 at 10:16
  • @Malabarba I think you are right. After starting fresh with emacs -Q that error is not present. I still get this error though: After 0 kbd macro iterations: foo: Lisp nesting exceeds `max-lisp-eval-depth'
    – nispio
    Oct 23, 2014 at 13:13
  • This is actually what I was looking for. For some strange reason (probably some interaction details with evil), directly calling the desired function had an unexpected effect in my case(evilmi-jump-items), and I had to use (execute-kbd-macro (kbd "%"))
    – xji
    Jul 1, 2015 at 10:49
4

Taken from this answer, you can use global-set-key like this

(global-set-key (kbd "C-`") (kbd "<escape>"))

Which will treat C-` as escape

This does seem to have some problems though if the second combination doesn't execute a function. So if escape is being used like Meta, then it doesn't work correctly. But it seems to work for commands bound to functions.

6
  • @nispio: Actually, it does work, since the second argument is implicitly converted to a keyboard macro.
    – shosti
    Oct 23, 2014 at 3:16
  • 1
    @shosti Evaluating the above and then pressing C-` gives me an error: After 0 kbd macro iterations: command-execute: Lisp nesting exceeds `max-lisp-eval-depth'.
    – nispio
    Oct 23, 2014 at 3:24
  • @nispio: You probably already have C- ` bound to ESC by some other method, so it's going into an infinite loop.
    – shosti
    Oct 23, 2014 at 3:42
  • @shosti You were right. Too many eval-sexp going on in one session. :-) But trying again with emacs -Q causes C-` to simply do nothing.
    – nispio
    Oct 23, 2014 at 3:45
  • Depending on your system, (kbd "<escape>") and (kbd "ESC") might mean different things--have you tried both?
    – shosti
    Oct 23, 2014 at 3:47
2

After reading the suggestion from jch to use unread-command-events, I was able to hack together a solution that will do some of the things that I am looking for.

(defun my-simulate-key-event (event &optional N)
  "Simulate an arbitrary keypress event.

This function sets the `unread-command-events' variable in order to simulate a
series of key events given by EVENT. Can also For negative N, simulate the
specified key EVENT directly.  For positive N, removes the last N elements from
the list of key events in `this-command-keys' and then appends EVENT.  For N nil,
treat as N=1."
  (let ((prefix (listify-key-sequence (this-command-keys)))
         (key (listify-key-sequence event))
         (n (prefix-numeric-value N)))
     (if (< n 0)
         (setq prefix key)
       (nbutlast prefix n)
       (nconc prefix key))
       (setq unread-command-events prefix)))

There are still a number of kinks to work out. Namely, I don't get the correct result if I call this function twice in a row within a single defun.


Side Note:

After checking out phils' suggestion to use key-translation-map I was able to find local-function-key-map which is also very helpful in achieving some of my broader goals.

1
  • This won't work in succession because you're overwriting the unread command events, not adding a new event. You probably want (setq unread-command-events (cons prefix unread-command-events))
    – JCC
    Apr 8, 2020 at 12:55

Your Answer

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

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