diff options
-rw-r--r-- | doc/lispref/frames.texi | 102 | ||||
-rw-r--r-- | etc/NEWS | 6 | ||||
-rw-r--r-- | lisp/frame.el | 106 | ||||
-rw-r--r-- | lisp/term/xterm.el | 11 | ||||
-rw-r--r-- | src/frame.c | 25 | ||||
-rw-r--r-- | src/keyboard.c | 65 | ||||
-rw-r--r-- | src/w32term.c | 14 | ||||
-rw-r--r-- | src/xterm.c | 10 |
8 files changed, 217 insertions, 122 deletions
diff --git a/doc/lispref/frames.texi b/doc/lispref/frames.texi index 459f05cb1c9..3a97ec01384 100644 --- a/doc/lispref/frames.texi +++ b/doc/lispref/frames.texi @@ -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}. + +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 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. +@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 @@ -583,6 +583,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' and `focus-out-hook' may run for TTY frames. diff --git a/lisp/frame.el b/lisp/frame.el index c3daff44406..2a2391e8a53 100644 --- a/lisp/frame.el +++ b/lisp/frame.el @@ -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) +(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)))))))) + +(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 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'." +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-in-hook)) - -(defun handle-focus-out (&optional _event) + (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 usually bound to this function. -Focus-out events occur when no frame has focus. -This function runs the hook `focus-out-hook'." +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") - (run-hooks 'focus-out-hook)) + (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. diff --git a/lisp/term/xterm.el b/lisp/term/xterm.el index b3b7a216352..ce4e18efff8 100644 --- a/lisp/term/xterm.el +++ b/lisp/term/xterm.el @@ -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 diff --git a/src/frame.c b/src/frame.c index da82621b8a0..bf0269292d6 100644 --- a/src/frame.c +++ b/src/frame.c @@ -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. */); diff --git a/src/keyboard.c b/src/keyboard.c index c9e069c8653..84acb247228 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -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); diff --git a/src/w32term.c b/src/w32term.c index 24950dd25ec..ff0d2bf5ddb 100644 --- a/src/w32term.c +++ b/src/w32term.c @@ -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); } diff --git a/src/xterm.c b/src/xterm.c index eb299c36759..decaa33670b 100644 --- a/src/xterm.c +++ b/src/xterm.c @@ -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); } |