I'm on Emacs 27.1, Fedora 33, GNOME 3.38.2.

In the latest NEWS it is reported that

The hooks focus-in-hook and focus-out-hook are now obsolete. Instead, attach to after-focus-change-function using add-function and inspect the focus state of each frame using frame-focus-state.

The documentation for after-focus-change-function says

Depending on window system, focus events may also be delivered repeatedly and with different focus states before settling to the expected values. Code relying on focus notifications should "debounce" any user-visible updates arising from focus changes, perhaps by deferring work until redisplay.

Indeed, after evaluating this code,

(defun focus-test ()
  (message "ffs: %s" (frame-focus-state)))
(add-function :after after-focus-change-function #'focus-test)

every time I switch buffer by selecting one with the mouse my message buffer shows

ffs: nil
ffs: t
ffs: nil
ffs: t

That is, Emacs sees two back and forth focus changes where I did none, and most importantly it has hallucinations of focus-out events which defeat the usefulness of the after-focus-change-function+frame-focus-state method for triggering functions based on focus events.

My question is, how do I "'debounce' any user-visible updates arising from focus changes"? (I made a naive attempt with

(defun focus-test ()
  (sit-for 0)
  (message "ffs: %s" (frame-focus-state)))

but it didn't work.)


1 Answer 1


I've settled with these home-made remakes of the obsoleted hooks:

;; Set up hooks to be run on focus in and focus out

;; Mutter sends spurious focus events, including sending focus out
;; events when Emacs never really was unfocused.
;; The timer filters out these false focus out events, while
;; checking the ‘last-focus-state’ allows ignoring repeated events
;; of the same kind.
;; REVIEW: false focus out events are still received sometimes when
;; using the menu bar.
;; Based on a suggestion by DasEwigeLicht on
;; https://www.reddit.com/r/emacs/comments/kxsgtn/ignore_spurious_focus_events_for/

(defvar focus-events-timer nil)

(defvar last-focus-state (frame-focus-state))

(defvar my-focus-in-hook nil
  "Normal hook run when all frames lose input focus.")

(defvar my-focus-out-hook nil
  "Normal hook run when a frame gains focus.")

(defun change-of-focus-functions ()
  (unless (equal last-focus-state (frame-focus-state))
    (if (frame-focus-state)
        (run-hooks 'my-focus-in-hook)
      (run-hooks 'my-focus-out-hook)))
  (setq focus-events-timer nil)
  (setq last-focus-state (frame-focus-state)))

(defun run-change-of-focus-functions-with-timer ()
  (unless (timerp focus-events-timer)
    (setq focus-events-timer
          (run-at-time "0.05 sec" nil ; Delay chosen by trial and error. Test it if you change it.

(add-function :after after-focus-change-function #'run-change-of-focus-functions-with-timer)

;; Test

(defun focus-in-test ()
  (message "focus-in"))

(defun focus-out-test ()
  (message "focus-out"))

(add-hook 'my-focus-in-hook #'focus-in-test)
(add-hook 'my-focus-out-hook #'focus-out-test)

;; Actual use

(add-hook 'my-focus-out-hook #'do-auto-save)

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.