32

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?

Drew
  • 75,699
  • 9
  • 109
  • 225
nispio
  • 8,175
  • 2
  • 35
  • 73
  • 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 '14 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 '14 at 23:46
  • sounds reasonable to me, but events might be better since this could also be applicable to mouse events. – b4hand Oct 23 '14 at 00: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 '14 at 01: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 '14 at 01:36

4 Answers4

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"))
jch
  • 5,680
  • 22
  • 39
  • 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 '14 at 00: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 '14 at 01: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 '14 at 02: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 '14 at 03: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: https://github.com/DarwinAwardWinner/ido-ubiquitous/blob/039944faf056215c2ae34c1bd2018563378a6e67/test/ido-ubiquitous-test.el#L38-L77 – Ryan C. Thompson Jan 06 '16 at 20:38
  • This doesn't work with single letter commands like double-quote – cjohansson Feb 12 '19 at 11:42
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)
shosti
  • 5,048
  • 26
  • 33
  • Evaluating the above and then pressing ``C-` `` gives me an error `apply: Wrong number of arguments: #[(ad--addoit-function ...`. – nispio Oct 23 '14 at 03:22
  • 1
    @nispio Not for me. That error looks like an advice. – Malabarba Oct 23 '14 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 '14 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 01 '15 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.

resueman
  • 401
  • 3
  • 10
  • @nispio: Actually, it does work, since the second argument is implicitly converted to a keyboard macro. – shosti Oct 23 '14 at 03: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 '14 at 03: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 '14 at 03: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 '14 at 03:45
  • Depending on your system, `(kbd "")` and `(kbd "ESC")` might mean different things--have you tried both? – shosti Oct 23 '14 at 03:47
  • Let us [continue this discussion in chat](http://chat.stackexchange.com/rooms/18092/discussion-between-shosti-and-nispio). – shosti Oct 23 '14 at 03:49
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.

nispio
  • 8,175
  • 2
  • 35
  • 73
  • 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 08 '20 at 12:55