30.10 输入焦点

任何时刻,Emacs 中都有一个框架是选中框架(selected frame)。 选中窗口(see 选中窗口)始终位于选中框架上。

当 Emacs 在多个终端上显示框架时(see 多终端),每个终端都有自己的选中框架。 但其中只有一个是**全局**选中框架:即最近一次输入来源终端所属的框架。 也就是说,Emacs 执行来自某终端的命令时,选中框架就是该终端的框架。 由于 Emacs 同一时间只运行一个命令,因此同一时间只需考虑一个选中框架; 本文档中将其称为选中框架(the selected frame)。 显示选中框架的显示器称为选中框架显示器(selected frame’s display)

Function: selected-frame

该函数返回选中框架。

部分窗口系统和窗口管理器将键盘输入导向鼠标所在的窗口; 另一些则需要显式点击或命令才能将焦点切换(shift the focus)到不同窗口。 无论哪种方式,Emacs 都会自动跟踪哪个框架拥有焦点。 若要在 Lisp 函数中显式切换到另一框架,可调用 select-frame-set-input-focus

上一段中使用复数“框架”是有意的:虽然 Emacs 自身只有一个选中框架, 但它可以在多个不同终端上拥有框架(注意:与窗口系统的连接也算作一个终端), 每个终端各自判断哪个框架拥有输入焦点。 在 X Window System 中,用户输入按独立“seat(输入席位)”组织,每个 seat 可以有自己的输入焦点。 将焦点设置到某个框架时,是为该框架所在终端、最后一次与 Emacs 交互的 seat 设置焦点, 但其他终端和 seat 上的框架仍可能保持聚焦状态。

如果在指定终端上尚未发生任何用户交互时就设置焦点,X 服务会选择一个随机 seat(通常编号最小的)并在那里设置焦点。

Lisp 程序可以调用 select-frame 临时切换框架。 这不会改变窗口系统的焦点状态,而是暂时脱离窗口管理器控制,直到某种方式重新恢复管理。

在文本终端上,同一时间只能显示一个框架,因此调用 select-frame 后, 下一次重绘会真正显示新选中的框架。该框架保持选中,直到下一次调用 select-frame。 文本终端上的每个框架都有一个编号,显示在模式行中缓冲区名称之前(see 模式行中使用的变量)。

Function: select-frame-set-input-focus frame &optional norecord

该函数选中 frame,将其置顶(若被其他框架遮挡),并尝试让它获得窗口系统焦点。 在文本终端上,下一次重绘会在整个终端屏幕显示新框架。 可选参数 norecord 的含义与 select-frame 相同(见下文)。 该函数的返回值无实际意义。

理想情况下,下面这个函数应能只聚焦框架而不将其置顶。 遗憾的是,许多窗口系统或窗口管理器不会遵守这一点。

Function: x-focus-frame frame &optional noactivate

该函数让 frame 获得 X 服务焦点,但不一定置顶。 framenil 表示使用选中框架。 在 X 下,可选参数 noactivate 非空时,会尽量避免将该框架设为窗口系统的“活动窗口”,从而更坚持不置顶。

在 MS-Windows 上,noactivate 无效。 但如果 frame 是子框架(see 子框架),该函数通常可以聚焦而不置顶。

若无窗口系统支持,此函数不执行任何操作。

Command: select-frame frame &optional norecord

该函数选中框架 frame,暂时忽略 X 服务的焦点状态。 对 frame 的选中会持续到用户下次操作选择其他框架,或再次调用该函数。 (若使用窗口系统,返回命令循环后,原先选中的框架可能因仍持有窗口系统焦点而恢复为选中框架。)

指定的 frame 成为选中框架,其终端成为选中终端。 该函数随后会调用 select-window,以框架内当前选中窗口为第一个参数,norecord 为第二个参数。 因此,若 norecord 非空,可避免改变最近选中窗口和缓冲区列表的顺序。See 选中窗口

函数返回 frame,若框架已删除则返回 nil

通常,绝不应该在使用 select-frame 切换到其他终端后,不切回原终端就结束操作。

Emacs 会配合窗口系统,按服务和窗口管理器的要求选中框架。 当窗口系统通知 Emacs 某个框架被选中时,Emacs 内部会生成一个focus-in 事件。 如果 Emacs 框架运行在支持焦点变化通知的文本终端模拟器(如 xterm)中, 即使是文本模式框架也会产生 focus-in 和 focus-out 事件。 焦点事件通常由 handle-focus-in 处理。

Command: handle-focus-in event

该函数处理来自窗口系统和支持显式焦点通知的终端的 focus-in 事件。 它更新 frame-focus-state 查询的 per-frame 焦点标记,并调用 after-focus-change-function。 此外,它会生成 switch-frame 事件,将 Emacs 意义上的选中框架切换到某个终端上最近聚焦的框架。

需要注意的是:将 Emacs 选中框架切换为最近聚焦框架, 并不意味着其他框架在各自终端上不再拥有焦点。 不要自行调用该函数;而应将逻辑挂到 after-focus-change-function

Command: handle-switch-frame frame

该函数处理 switch-frame 事件,Emacs 在收到焦点通知, 或输入事件到达与上一次不同的框架等情况下会自动生成该事件。不要自行调用。

Function: redirect-frame-focus frame &optional focus-frame

该函数将焦点从 frame 重定向到 focus-frame。 这意味着原本发给 frame 的后续按键和事件会被 focus-frame 接收。 此类事件发生后,last-event-frame 的值将为 focus-frame。 同时,指定 frame 的 switch-frame 事件也会转而选中 focus-frame

若省略 focus-frame 或为 nil,则取消对 frame 的现有重定向, 使其重新接收自己的事件。

焦点重定向的用途之一是处理不带迷你缓冲区的框架。 这类框架使用其他框架的迷你缓冲区。 在另一框架上激活迷你缓冲区时,会将焦点重定向到该框架, 使焦点正确落在迷你缓冲区所在框架,即使鼠标仍停留在激活迷你缓冲区的原框架上。

选中框架也会改变焦点重定向关系。 若原先选中框架 foo,之后选中 bar, 则所有指向 foo 的重定向会改为指向 bar。 这使得用户通过 select-window 切换框架时,焦点重定向仍能正常工作。

这意味着焦点重定向到自身的框架,与未重定向的框架处理方式不同。 select-frame 会影响前者,而不影响后者。

重定向效果持续到再次调用 redirect-frame-focus 修改为止。

Function: frame-focus-state frame

该函数获取 frame 最近已知的焦点状态。

若确定框架未聚焦,返回 nil; 确定已聚焦,返回 t; Emacs 无法判断焦点状态时返回 unknown。 (在不支持显式焦点通知的终端上运行的 TTY 框架可能出现此状态。)

Variable: after-focus-change-function

当 Emacs 检测到某个框架可能获得或失去焦点时,会无参调用该函数。 焦点事件是异步投递的,顺序可能不符合预期, 因此依赖框架焦点状态的代码需要遍历所有框架并逐一检查。

例如,下面是一个简单示例函数,根据框架是否聚焦设置背景色:

(add-function :after after-focus-change-function
              #'my-change-background)
(defun my-change-background ()
  (dolist (frame (frame-list))
    (pcase (frame-focus-state frame)
      (`t (set-face-background 'default "black" frame))
      (`nil (set-face-background 'default "#404040" frame)))))

由于焦点事件投递延迟、存在多个 Emacs 终端等原因, 多个框架可能同时看似拥有输入焦点,代码应能稳健处理这种情况。

根据窗口系统不同,焦点事件可能反复投递且状态多变,最终才稳定到预期值。 依赖焦点通知的代码应对用户可见的更新做“防抖”处理, 例如推迟到重绘时再执行。

该函数可能在任意上下文被调用,包括在 read-event 内部, 因此编写时需像编写进程过滤器一样谨慎。

User Option: focus-follows-mouse

该选项告知 Emacs,窗口管理器在鼠标移入框架时是否及如何转移焦点。 有效值有三种:

nil

默认值 nil 适用于采用 “点击聚焦(click-to-focus)” 策略的窗口管理器, 必须在框架内点击鼠标才能获得焦点。

t

t 适用于焦点自动跟随鼠标,但获得焦点的框架不会自动置顶、 甚至可能仍被其他窗口遮挡的窗口管理器。

auto-raise

auto-raise 适用于焦点自动跟随鼠标, 且获得焦点的框架会自动置顶的窗口管理器。

若该选项非空,Emacs 会将鼠标指针移到 select-frame-set-input-focus 选中的框架上。 许多命令(如 other-framepop-to-buffer)都会使用该函数。

对 “普通(normal)” 框架而言,tauto-raise 的区别并不重要, 因为窗口管理器通常会负责置顶。 该区别在通过 mouse-autoselect-window 自动置顶子框架时有用(see 鼠标窗口自动选择)。

注意:该选项不区分 “松散焦点(sloppy focus)” (鼠标离开后原框架仍保持焦点,直到进入其他窗口) 和 “严格焦点(strict focus)”(鼠标离开后框架立即失去焦点)。 它也不识别窗口管理器是否支持延迟聚焦或延迟置顶。

可以通过自定义变量 mouse-autoselect-window, 为单个 Emacs 窗口设置 “焦点跟随鼠标(focus follows mouse)” 策略(see 鼠标窗口自动选择)。