An answer to another question suggests that supplying a timeout of 0.0 to read-event (as the optional SECONDS parameter) will always return nil immediately, and, if I'm reading the code in Emacs' keyboard.c correctly, even a (very) very small non-zero timeout could return nil without checking everywhere for an existing event.

Because input-pending-p can return false positives, calling read-event with a non-zero value for SECONDS to try to get that (nonexistant event) could cause a delay and still return nil. Practically, it seems like using a millisecond or two for SECONDS should be fine, but it does feel less tidy than if read-event returned any available events even with a timout of 0.0. Short of a locall Emacs build, is there a solution a particularly particular user might prefer?

The code @phils gave with his answer demonstrates this is not correct if "unread" events are available in any of 3 queues. I've added code that suggests yet another event-queue is checked by input-pending-p that is skipped by read-event when SECONDS is 0.0.

My polling loop below is more complicated than I'd like, but I wanted to record all the relative event times. The quit key (C-g) will terminate.

The code starts calling read-event with a SECONDS value of 0.0, increasing it after 5 seconds. It records when input-pending-p first returns true, and then the time and SECONDS value when read-event first returns non-nil.

For me, pressing a key before 5 seconds is up demonstrates that input-pending-p can be true, but until SECONDS is increased to at least a little above 0.0, read-event consistently returns nil.

(let* ((input-at) (read-at) (wait-to-try 0.0) (deadline (+ (float-time) 5.0)) (past-due))
  (while 't
    (setq past-due (- (float-time) deadline))
    (when (and (not input-at) (input-pending-p)) (setq input-at past-due))
    (when (not read-at)
      (when (> past-due 0.0) (setq wait-to-try (* 0.001 past-due)))
      (when (read-event nil nil wait-to-try) (setq read-at past-due)))
    (message "past: %f  in-p: %s  in-at: %s  wait: %s  read-at %s"
      past-due (input-pending-p) input-at wait-to-try read-at)
  • 1
    I suppose you could use (or (read-event nil nil 0) (and (input-pending-p) (read-event nil nil 0.1)))? Or maybe just the and form. But I guess that's what you were playing with to begin with, when discussing the possibility of false-positives incurring the full delay. Hopefully someone with more knowledge than me will chip in.
    – phils
    Sep 29, 2022 at 13:20

1 Answer 1


Edit: With the new information and test case provided in the question, it's clear that this answer is incorrect (or at least inadequate). Clearly there are indeed additional input event queue(s) which do not get the opportunity to feed event-read while elisp is being actively executed, until some kind of waiting occurs.

Original answer follows...

If the event is already existing/pending, I don't think you need to deal with timeouts at all?

(let ((unread-command-events '(42)))
=> 42

An answer to another question suggests that supplying a timeout of zero to read-event (as the optional SECONDS parameter) will always return nil immediately

That's certainly not what I'm seeing in a cursory test:

(let ((unread-command-events '(1))
      (unread-input-method-events '(2))
      (unread-post-input-method-events '(3)))
  (list (read-event nil nil 0)
        (read-event nil nil 0)
        (read-event nil nil 0)))

=> (3 1 2)
  • Do events normally flow to unread-command-events ? From the docs I thought this was a sort of "pushback stack", and events would only go on that list if a function read it from the standard queue, then decided it didn't want it.
    – user13929
    Sep 26, 2022 at 3:49
  • Beyond the fact that there are at least three different "unread events" lists, I'm not certain of the details; but I would have expected the result to be the same regardless -- if there's an event waiting in a queue and you call read-event I would expect the waiting event to be returned in all cases.
    – phils
    Sep 28, 2022 at 9:58
  • Yeah, that's what I expect, but I don't think it's the case. I ended up rewriting what I was doing to use a timer instead and add a little dynamics into the mix anyway, but I'd still like to understand...
    – user13929
    Sep 28, 2022 at 13:53
  • If you can provide an example of it working differently, that would no doubt help.
    – phils
    Sep 28, 2022 at 21:34
  • Ah! I missed your test after reading your earlier comment. Awesome! I’ll cook something up. Thanks!
    – user13929
    Sep 28, 2022 at 22:10

Your Answer

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