New focus management interface

focus-in-hook and focus-out-hook don't accurately reflect actual
user-visible focus states.  Add a new focus interface and mark the old
one obsolete.

* doc/lispref/frames.texi (Input Focus): Document new focus
functions.  Remove references to the now-obsolete focus hooks.

* lisp/frame.el (frame-focus-state): New function.
(after-focus-change-function): New variable.
(focus-in-hook, focus-out-hook): Move to lisp from C;
mark obsolete.

* lisp/term/xterm.el (xterm-translate-focus-in)
(xterm-translate-focus-out): Track tty focus in `tty-focus-state'
terminal parameter; call `after-focus-change-function'.
(xterm--suspend-tty-function): New function.

* src/frame.c (Fhandle_switch_frame): Update docstring; don't call
focus hooks.
(focus-in-hook, focus-out-hook): Remove: moved to lisp.
(syms_of_frame): Remove unread_switch_frame; add
Vunread_switch_frame.

* src/keyboard.c:
(Finternal_handle_focus_in): New function.
(make_lispy_event): Always report focus events to lisp; don't
translate them to switch events sometimes.  Lisp can take care of
creating synthetic switch-frame events via
`internal-handle-focus-in'.

* src/w32term.c (x_focus_changed): Remove switch-avoidance logic:
just directly report focus changes to lisp.

* src/xterm.c (x_focus_changed): Remove switch-avoidance logic:
just directly report focus changes to lisp.
This commit is contained in:
Daniel Colascione 2018-06-11 14:58:09 -07:00
parent a20fe5a7e3
commit 2f6c682061
8 changed files with 219 additions and 124 deletions

View file

@ -2702,14 +2702,22 @@ This function returns the selected frame.
Some window systems and window managers direct keyboard input to the
window object that the mouse is in; others require explicit clicks or
commands to @dfn{shift the focus} to various window objects. Either
way, Emacs automatically keeps track of which frame has the focus. To
way, Emacs automatically keeps track of which frames have focus. To
explicitly switch to a different frame from a Lisp function, call
@code{select-frame-set-input-focus}.
Lisp programs can also switch frames temporarily by calling the
function @code{select-frame}. This does not alter the window system's
concept of focus; rather, it escapes from the window manager's control
until that control is somehow reasserted.
The plural ``frames'' in the previous paragraph is deliberate: while
Emacs itself has only one selected frame, Emacs can have frames on
many different terminals (recall that a connection to a window system
counts as a terminal), and each terminal has its own idea of which
frame has input focus. When you set the input focus to a frame, you
set the focus for that frame's terminal, but frames on other terminals
may still remain focused.
Lisp programs can switch frames temporarily by calling the function
@code{select-frame}. This does not alter the window system's concept
of focus; rather, it escapes from the window manager's control until
that control is somehow reasserted.
When using a text terminal, only one frame can be displayed at a time
on the terminal, so after a call to @code{select-frame}, the next
@ -2720,11 +2728,11 @@ before the buffer name (@pxref{Mode Line Variables}).
@defun select-frame-set-input-focus frame &optional norecord
This function selects @var{frame}, raises it (should it happen to be
obscured by other frames) and tries to give it the X server's focus.
On a text terminal, the next redisplay displays the new frame on the
entire terminal screen. The optional argument @var{norecord} has the
same meaning as for @code{select-frame} (see below). The return value
of this function is not significant.
obscured by other frames) and tries to give it the window system's
focus. On a text terminal, the next redisplay displays the new frame
on the entire terminal screen. The optional argument @var{norecord}
has the same meaning as for @code{select-frame} (see below).
The return value of this function is not significant.
@end defun
Ideally, the function described next should focus a frame without also
@ -2772,17 +2780,31 @@ could switch to a different terminal without switching back when
you're done.
@end deffn
Emacs cooperates with the window system by arranging to select frames as
the server and window manager request. It does so by generating a
special kind of input event, called a @dfn{focus} event, when
appropriate. The command loop handles a focus event by calling
@code{handle-switch-frame}. @xref{Focus Events}.
Emacs cooperates with the window system by arranging to select frames
as the server and window manager request. When a window system
informs Emacs that one of its frames has been selected, Emacs
internally generates a @dfn{focus-in} event. Focus events are
normally handled by @code{handle-focus-in}.
@deffn Command handle-focus-in event
This function handles focus-in events from window systems and
terminals that support explicit focus notifications. It updates the
per-frame focus flags that @code{frame-focus-state} queries and calls
@code{after-focus-change-function}. In addition, it generates a
@code{switch-frame} event in order to switch the Emacs notion of the
selected frame to the frame most recently focused in some terminal.
It's important to note that this switching of the Emacs selected frame
to the most recently focused frame does not mean that other frames do
not continue to have the focus in their respective terminals. Do not
invoke this function yourself: instead, attach logic to
@code{after-focus-change-function}.
@end deffn
@deffn Command handle-switch-frame frame
This function handles a focus event by selecting frame @var{frame}.
Focus events normally do their job by invoking this command.
Don't call it for any other reason.
This function handles a switch-frame event, which Emacs generates for
itself upon focus notification or under various other circumstances
involving an input event arriving at a different frame from the last
event. Do not invoke this function yourself.
@end deffn
@defun redirect-frame-focus frame &optional focus-frame
@ -2816,14 +2838,42 @@ The redirection lasts until @code{redirect-frame-focus} is called to
change it.
@end defun
@defvar focus-in-hook
This is a normal hook run when an Emacs frame gains input focus. The
frame gaining focus is selected when this hook is run.
@end defvar
@defun frame-focus-state frame
This function retrieves the last known focus state of @var{frame}.
@defvar focus-out-hook
This is a normal hook run when an Emacs frame has lost input focus and
no other Emacs frame has gained input focus instead.
It returns @code{nil} if the frame is known not to be focused,
@code{t} if the frame is known to be focused, or @code{unknown} if
Emacs does not know the focus state of the frame. (You may see this
last state in TTY frames running on terminals that do not support
explicit focus notifications.)
@end defun
@defvar after-focus-change-function
This function is an extension point that code can use to receive a
notification that focus has changed.
This function is called with no arguments when Emacs notices that the
set of focused frames may have changed. Code wanting to do something
when frame focus changes should use @code{add-function} to add a
function to this one, and in this added function, re-scan the set of
focused frames, calling @code{frame-focus-state} to retrieve the last
known focus state of each frame. Focus events are delivered
asynchronously, and frame input focus according to an external system
may not correspond to the notion of the Emacs selected frame.
Multiple frames may appear to have input focus simultaneously due to
focus event delivery differences, the presence of multiple Emacs
terminals, and other factors, and code should be robust in the face of
this situation.
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.
This function may be called in arbitrary contexts, including from
inside @code{read-event}, so take the same care as you might when
writing a process filter.
@end defvar
@defopt focus-follows-mouse

View file

@ -582,6 +582,12 @@ manual for more details.
* Lisp Changes in Emacs 27.1
+++
** New focus state inspection interface: `focus-in-hook' and
`focus-out-hook' are marked obsolete. Instead, attach to
`after-focus-change-function' using `add-function' and inspect the
focus state of each frame using `frame-focus-state'.
+++
** Emacs now requests and recognizes focus-change notifications from
terminals that support the feature, meaning that `focus-in-hook'

View file

@ -129,22 +129,104 @@ appended when the minibuffer frame is created."
;; Gildea@x.org says it is ok to ask questions before terminating.
(save-buffers-kill-emacs))))
(defun handle-focus-in (&optional _event)
"Handle a focus-in event.
Focus-in events are usually bound to this function.
Focus-in events occur when a frame has focus, but a switch-frame event
is not generated.
This function runs the hook `focus-in-hook'."
(interactive "e")
(run-hooks 'focus-in-hook))
(defun frame-focus-state (&optional frame)
"Return FRAME's last known focus state.
Return nil if the frame is definitely known not be focused, t if
the frame is known to be focused, and 'unknown if we don't know. If
FRAME is nil, query the selected frame."
(let* ((frame (or frame (selected-frame)))
(tty-top-frame (tty-top-frame frame)))
(if (not tty-top-frame)
(frame-parameter frame 'last-focus-update)
;; All tty frames are frame-visible-p if the terminal is
;; visible, so check whether the frame is the top tty frame
;; before checking visibility.
(cond ((not (eq tty-top-frame frame)) nil)
((not (frame-visible-p frame)) nil)
(t (let ((tty-focus-state
(terminal-parameter frame 'tty-focus-state)))
(cond ((eq tty-focus-state 'focused) t)
((eq tty-focus-state 'defocused) nil)
(t 'unknown))))))))
(defun handle-focus-out (&optional _event)
"Handle a focus-out event.
Focus-out events are usually bound to this function.
Focus-out events occur when no frame has focus.
This function runs the hook `focus-out-hook'."
(defvar after-focus-change-function #'ignore
"Function called after frame focus may have changed.
This function is called with no arguments when Emacs notices that
the set of focused frames may have changed. Code wanting to do
something when frame focus changes should use `add-function' to
add a function to this one, and in this added function, re-scan
the set of focused frames, calling `frame-focus-state' to
retrieve the last known focus state of each frame. Focus events
are delivered asynchronously, and frame input focus according to
an external system may not correspond to the notion of the Emacs
selected frame. Multiple frames may appear to have input focus
simultaneously due to focus event delivery differences, the
presence of multiple Emacs terminals, and other factors, and code
should be robust in the face of this situation.
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.
This function may be called in arbitrary contexts, including from
inside `read-event', so take the same care as you might when
writing a process filter.")
(defvar focus-in-hook nil
"Normal hook run when a frame gains focus.
The frame gaining focus is selected at the time this hook is run.
This hook is obsolete. Despite its name, this hook may be run in
situations other than when a frame obtains input focus: for
example, we also run this hook when switching the selected frame
internally to handle certain input events (like mouse wheel
scrolling) even when the user's notion of input focus
hasn't changed.
Prefer using `after-focus-change-function'.")
(make-obsolete-variable
'focus-in-hook "after-focus-change-function" "27.1" 'set)
(defvar focus-out-hook nil
"Normal hook run when all frames lost input focus.
This hook is obsolete; see `focus-in-hook'. Depending on timing,
this hook may be delivered when a frame does in fact have focus.
Prefer `after-focus-change-function'.")
(make-obsolete-variable
'focus-out-hook "after-focus-change-function" "27.1" 'set)
(defun handle-focus-in (event)
"Handle a focus-in event.
Focus-in events are bound to this function; do not change this
binding. Focus-in events occur when a frame receives focus from
the window system."
;; N.B. tty focus goes down a different path; see xterm.el.
(interactive "e")
(run-hooks 'focus-out-hook))
(unless (eq (car-safe event) 'focus-in)
(error "handle-focus-in should handle focus-in events"))
(internal-handle-focus-in event)
(let ((frame (nth 1 event)))
(setf (frame-parameter frame 'last-focus-update) t)
(run-hooks 'focus-in-hook)
(funcall after-focus-change-function)))
(defun handle-focus-out (event)
"Handle a focus-out event.
Focus-out events are bound to this function; do not change this
binding. Focus-out events occur when a frame loses focus, but
that's not the whole story: see `after-focus-change-function'."
;; N.B. tty focus goes down a different path; see xterm.el.
(interactive "e")
(unless (eq (car event) 'focus-out)
(error "handle-focus-out should handle focus-out events"))
(let ((frame (nth 1 event)))
(setf (frame-parameter frame 'last-focus-update) nil)
(run-hooks 'focus-out-hook)
(funcall after-focus-change-function)))
(defun handle-move-frame (event)
"Handle a move-frame event.

View file

@ -115,13 +115,20 @@ Return the pasted text as a string."
;; notifications) instead of read-event (which can't).
(defun xterm-translate-focus-in (_prompt)
(handle-focus-in)
(setf (terminal-parameter nil 'tty-focus-state) 'focused)
(funcall after-focus-change-function)
[])
(defun xterm-translate-focus-out (_prompt)
(handle-focus-out)
(setf (terminal-parameter nil 'tty-focus-state) 'defocused)
(funcall after-focus-change-function)
[])
(defun xterm--suspend-tty-function (_tty)
;; We can't know what happens to the tty after we're suspended
(setf (terminal-parameter nil 'tty-focus-state) nil)
(funcall after-focus-change-function))
;; Similarly, we want to transparently slurp the entirety of a
;; bracketed paste and encapsulate it into a single event. We used to
;; just slurp up the bracketed paste content in the event handler, but

View file

@ -1455,23 +1455,15 @@ This function returns FRAME, or nil if FRAME has been deleted. */)
DEFUN ("handle-switch-frame", Fhandle_switch_frame, Shandle_switch_frame, 1, 1, "^e",
doc: /* Handle a switch-frame event EVENT.
Switch-frame events are usually bound to this function.
A switch-frame event tells Emacs that the window manager has requested
that the user's events be directed to the frame mentioned in the event.
This function selects the selected window of the frame of EVENT.
If EVENT is frame object, handle it as if it were a switch-frame event
to that frame. */)
A switch-frame event is an event Emacs sends itself to
indicate that input is arriving in a new frame. It does not
necessarily represent user-visible input focus. */)
(Lisp_Object event)
{
Lisp_Object value;
/* Preserve prefix arg that the command loop just cleared. */
kset_prefix_arg (current_kboard, Vcurrent_prefix_arg);
run_hook (Qmouse_leave_buffer_hook);
/* `switch-frame' implies a focus in. */
value = do_switch_frame (event, 0, 0, Qnil);
call1 (intern ("handle-focus-in"), event);
return value;
return do_switch_frame (event, 0, 0, Qnil);
}
DEFUN ("selected-frame", Fselected_frame, Sselected_frame, 0, 0, 0,
@ -5888,15 +5880,6 @@ when the mouse is over clickable text. */);
The pointer becomes visible again when the mouse is moved. */);
Vmake_pointer_invisible = Qt;
DEFVAR_LISP ("focus-in-hook", Vfocus_in_hook,
doc: /* Normal hook run when a frame gains input focus.
The frame gaining focus is selected at the time this hook is run. */);
Vfocus_in_hook = Qnil;
DEFVAR_LISP ("focus-out-hook", Vfocus_out_hook,
doc: /* Normal hook run when all frames lost input focus. */);
Vfocus_out_hook = Qnil;
DEFVAR_LISP ("move-frame-functions", Vmove_frame_functions,
doc: /* Functions run after a frame was moved.
The functions are run with one arg, the frame that moved. */);

View file

@ -5331,45 +5331,10 @@ make_lispy_event (struct input_event *event)
}
case FOCUS_IN_EVENT:
{
/* Notification of a FocusIn event. The frame receiving the
focus is in event->frame_or_window. Generate a
switch-frame event if necessary. */
Lisp_Object frame = event->frame_or_window;
Lisp_Object focus = FRAME_FOCUS_FRAME (XFRAME (frame));
if (FRAMEP (focus))
frame = focus;
bool switching
= (
#ifdef HAVE_X11
! NILP (event->arg)
&&
#endif
!EQ (frame, internal_last_event_frame)
&& !EQ (frame, selected_frame));
internal_last_event_frame = frame;
return (switching ? make_lispy_switch_frame (frame)
: make_lispy_focus_in (frame));
}
return make_lispy_focus_in (event->frame_or_window);
case FOCUS_OUT_EVENT:
{
#ifdef HAVE_WINDOW_SYSTEM
Display_Info *di;
Lisp_Object frame = event->frame_or_window;
bool focused = false;
for (di = x_display_list; di && ! focused; di = di->next)
focused = di->x_highlight_frame != 0;
return focused ? Qnil
: make_lispy_focus_out (frame);
#endif /* HAVE_WINDOW_SYSTEM */
}
return make_lispy_focus_out (event->frame_or_window);
/* A simple keystroke. */
case ASCII_KEYSTROKE_EVENT:
@ -6637,6 +6602,31 @@ has the same base event type and all the specified modifiers. */)
error ("Invalid base event");
}
DEFUN ("internal-handle-focus-in", Finternal_handle_focus_in,
Sinternal_handle_focus_in, 1, 1, 0,
doc: /* Internally handle focus-in events, possibly generating
an artifical switch-frame event. */)
(Lisp_Object event)
{
Lisp_Object frame;
if (!EQ (CAR_SAFE (event), Qfocus_in) ||
!CONSP (XCDR (event)) ||
!FRAMEP ((frame = XCAR (XCDR (event)))))
error ("invalid focus-in event");
/* Conceptually, the concept of window manager focus on a particular
frame and the Emacs selected frame shouldn't be related, but for a
long time, we automatically switched the selected frame in response
to focus events, so let's keep doing that. */
bool switching = (!EQ (frame, internal_last_event_frame)
&& !EQ (frame, selected_frame));
internal_last_event_frame = frame;
if (switching || !NILP (unread_switch_frame))
unread_switch_frame = make_lispy_switch_frame (frame);
return Qnil;
}
/* Try to recognize SYMBOL as a modifier name.
Return the modifier flag bit, or 0 if not recognized. */
@ -11277,6 +11267,7 @@ syms_of_keyboard (void)
defsubr (&Scurrent_idle_time);
defsubr (&Sevent_symbol_parse_modifiers);
defsubr (&Sevent_convert_list);
defsubr (&Sinternal_handle_focus_in);
defsubr (&Sread_key_sequence);
defsubr (&Sread_key_sequence_vector);
defsubr (&Srecursive_edit);

View file

@ -2886,20 +2886,6 @@ x_focus_changed (int type, int state, struct w32_display_info *dpyinfo,
{
x_new_focus_frame (dpyinfo, frame);
dpyinfo->w32_focus_event_frame = frame;
/* Don't stop displaying the initial startup message
for a switch-frame event we don't need. */
if (NILP (Vterminal_frame)
&& CONSP (Vframe_list)
&& !NILP (XCDR (Vframe_list)))
{
bufp->arg = Qt;
}
else
{
bufp->arg = Qnil;
}
bufp->kind = FOCUS_IN_EVENT;
XSETFRAME (bufp->frame_or_window, frame);
}

View file

@ -4387,16 +4387,6 @@ x_focus_changed (int type, int state, struct x_display_info *dpyinfo, struct fra
{
x_new_focus_frame (dpyinfo, frame);
dpyinfo->x_focus_event_frame = frame;
/* Don't stop displaying the initial startup message
for a switch-frame event we don't need. */
/* When run as a daemon, Vterminal_frame is always NIL. */
bufp->arg = (((NILP (Vterminal_frame)
|| ! FRAME_X_P (XFRAME (Vterminal_frame))
|| EQ (Fdaemonp (), Qt))
&& CONSP (Vframe_list)
&& !NILP (XCDR (Vframe_list)))
? Qt : Qnil);
bufp->kind = FOCUS_IN_EVENT;
XSETFRAME (bufp->frame_or_window, frame);
}