wezterm `window-focus-changed` 事件完全指南:监听窗口焦点状态并联动 Lua 配置
2026/9/13 6:26:19 网站建设 项目流程

weztermwindow-focus-changed事件完全指南:监听窗口焦点状态并联动 Lua 配置

【免费下载链接】weztermA GPU-accelerated cross-platform terminal emulator and multiplexer written by @wez and implemented in Rust项目地址: https://gitcode.com/GitHub_Trending/we/wezterm

导读

window-focus-changed是 wezterm 在 GUI 窗口焦点状态发生变化时触发的一个 Lua 事件,它是窗口级事件(Window Event)家族的一员,通过 wezterm.on 注册回调即可捕获。本文基于 window-focus-changed.md 官方文档,结合 wezterm 源码(wezterm-gui/src/termwindow/mod.rs 与 wezterm-gui/src/scripting/guiwin.rs)深入讲解该事件的触发时机、参数结构、回调写法,并给出多个可直接复制的实战示例,帮助你实现"窗口失焦自动暂停任务、聚焦自动恢复主题、根据焦点状态切换配色"等场景。


一、事件概述:什么时候会触发?

window-focus-changed事件自版本20221119-145034-49b9839f起引入(见 docs/changelog.md 中与该版本同步发布的window:is_focused()方法)。该事件在窗口的焦点状态发生变化时发出,即:

  • 窗口从后台切换到前台(获得焦点);
  • 窗口从前台切换到后台(失去焦点);
  • 在多个 wezterm 窗口之间切换时,每个受影响窗口都会触发各自的事件。

注意:这里的"焦点"指 GUI 窗口级别的输入焦点,与某个具体 Pane 是否被选中不是同一概念。焦点变化会影响鼠标事件、光标闪烁等窗口级行为,而当前活动 Pane 的变化则与update-status等事件相关。

在源码层面,焦点变化由TermWindow::focus_changed()处理,wezterm-gui/src/termwindow/mod.rs 中可以看到完整调用链:

fn focus_changed(&mut self, focused: bool, window: &Window) { log::trace!("Setting focus to {:?}", focused); self.focused = if focused { Some(Instant::now()) } else { None }; self.quad_generation += 1; self.load_os_parameters(); if self.focused.is_none() { // 失焦时清理鼠标状态 self.last_mouse_click = None; self.current_mouse_buttons.clear(); self.current_mouse_capture = None; self.is_click_to_focus_window = false; } // 重置光标闪烁相位,强制重绘光标 self.prev_cursor.bump(); window.invalidate(); // 通知活动 pane 焦点变化 if let Some(pane) = self.get_active_pane_or_overlay() { pane.focus_changed(focused); } self.update_title(); self.emit_window_event("window-focus-changed", None); }

从这段实现可以看出几个值得注意的细节:

  • 窗口内部的focused字段被记录为Option<Instant>(wezterm-gui/src/termwindow/mod.rs),window:is_focused()正是通过判断该字段是否为Some来返回布尔值(见 wezterm-gui/src/scripting/guiwin.rs);
  • 失焦时 wezterm 会主动清理鼠标点击、按键捕获、鼠标终端坐标等状态,避免失焦后残留输入状态;
  • 无论聚焦还是失焦,都会触发一次window-focus-changed事件,回调中可以通过window:is_focused()区分是哪种状态变化。

二、事件参数:window对象与pane对象

与大多数窗口级事件(如 window-resized.md、window-config-reloaded.md)一致,window-focus-changed的回调携带两个参数:

参数类型含义
windowwindow 对象代表当前触发事件的 GUI 窗口句柄
panepane 对象该窗口中当前活动的 pane

其中window对象本质上是源码中GuiWin结构体(mux_window_id+ 底层::window::Window)在 Lua 层的封装,wezterm-gui/src/scripting/guiwin.rs 中定义了它的构造方式。它不能由 Lua 代码主动创建,只能通过事件回调传入。

pane对象是MuxPane类型(mux::pane::PaneId的包装),代表当前活动窗格。注意:事件触发时若窗口存在 overlay(例如复制模式、快速选择、确认关闭对话框等叠加层),get_active_pane_or_overlay()返回的可能是 overlay 对应的 pane,这在 wezterm-gui/src/termwindow/mod.rs 的schedule_window_event中体现。


三、基础用法:官方最小示例

官方文档给出的最小示例(window-focus-changed.md)如下:

local wezterm = require 'wezterm' wezterm.on('window-focus-changed', function(window, pane) wezterm.log_info( 'the focus state of ', window:window_id(), ' changed to ', window:is_focused() ) end)

将这段代码放进你的~/.wezterm.lua(或wezterm.lua)配置文件的顶部即可生效。它做了三件事:

  1. 通过wezterm.on('window-focus-changed', ...)注册回调;
  2. 调用window:window_id()获取该窗口在 mux 中的唯一 ID(源码见 wezterm-gui/src/scripting/guiwin.rs:methods.add_method("window_id", ...)直接返回mux_window_id);
  3. 调用window:is_focused()判断当前是否持有焦点。

wezterm.log_info会输出到 wezterm 的日志层,可在运行 wezterm 的终端 stdout 中看到,也可以通过ShowDebugOverlay动作(Shift+Ctrl+Space默认绑定)在调试面板中查看。log_info20210814-124438-54e29167起支持多参数、任意类型(见 wezterm/log_info.md)。


四、事件分发机制:fire-and-forget 与并发保护

4.1 为什么说是 "fire-and-forget"

官方文档明确说明该事件是"fire-and-forget"(发出即忘)语义:wezterm 只负责在焦点变化时把事件发出去通知你,不会等待回调结果,也不依赖回调的返回值做任何后续处理。因此你不能在回调里通过返回值来"阻止"或"修改"这次焦点变化本身——它只是通知。

这一点在emit_window_event中体现得很清楚:焦点变化处理完毕后,self.emit_window_event("window-focus-changed", None)只是把事件名投递给调度器(wezterm-gui/src/termwindow/mod.rs),随后事件在独立异步任务中执行 Lua 回调。

4.2 事件执行的并发控制

wezterm 对同一窗口的窗口级事件做了单飞(single-flight)+ 排队(queued)保护,避免同一事件重入造成状态混乱(wezterm-gui/src/termwindow/mod.rs):

  • 每个事件名对应一个EventStateNone/InProgress/InProgressWithQueued);
  • 事件正在执行时又发生新触发,不会立刻并发执行,而是标记为"有一个待处理项";
  • 当前回调执行完毕后,finish_window_event会检查是否存在排队项,若有则立即调度下一个(wezterm-gui/src/termwindow/mod.rs)。

这意味着即使焦点在极短时间内快速切换多次,你的回调也不会被并发重入,最多保持"1 个在执行 + 1 个排队"的状态,逻辑上可以安全地操作共享状态。

4.3 事件如何到达 Lua

schedule_window_event会在主线程上取回 Lua 配置(config::with_lua_config_on_main_thread),构造GuiWinMuxPane参数,然后调用config::lua::emit_event分发(wezterm-gui/src/termwindow/mod.rs)。分发逻辑位于 config/src/lua.rs:wezterm 会把同名事件的所有处理器按注册顺序依次调用,如果某个回调返回false,则视为"阻止默认行为"并停止后续回调;返回true或非布尔值时继续。不过如前所述,对window-focus-changed而言这个返回值不会被 wezterm 采纳。


五、实战示例

5.1 失焦时切换配色方案,聚焦时恢复

这个示例结合了window:is_focused()window:set_config_overrides()/window:get_config_overrides()(源码见 wezterm-gui/src/scripting/guiwin.rs),实现"窗口失焦时自动变暗、聚焦时恢复明亮配色",方便在一屏多窗口时快速定位当前终端:

local wezterm = require 'wezterm' wezterm.on('window-focus-changed', function(window, pane) local overrides = window:get_config_overrides() or {} if window:is_focused() then -- 获得焦点:恢复明亮主题 overrides.color_scheme = 'nordfox' else -- 失去焦点:切换为暗色主题 overrides.color_scheme = 'nightfox' end window:set_config_overrides(overrides) end)

要点:

  • get_config_overrides()返回当前窗口的配置覆盖表,set_config_overrides()将其应用到该窗口(不会影响其他窗口和其他配置文件的用户);
  • color_scheme取值必须是 wezterm 内置 scheme 或你的自定义 scheme 名称;
  • 由于每次焦点切换都会触发事件,set_config_overrides在值未变化时应尽量避免调用(可先比较再赋值),这与 window-config-reloaded.md 中关于避免递归触发window-config-reloaded的提醒同理。

5.2 失焦时让 pane 暂停输出(配合pane:inject_output之外的机制)

如果你希望在窗口失焦时自动暂停任务,可以使用mux域 API 在失焦时对窗口内的 pane 发送控制字符。例如失焦时向活动 pane 发送Ctrl+S(XON/XOFF 流控的暂停符),聚焦时发送Ctrl+Q恢复:

local wezterm = require 'wezterm' wezterm.on('window-focus-changed', function(window, pane) if window:is_focused() then pane:send_text('\x11') -- Ctrl+Q:恢复输出 else pane:send_text('\x13') -- Ctrl+S:暂停输出 end end)

pane:send_text()是 pane 对象 提供的方法,会向该 pane 的终端输入流注入文本(其底层对应pane:inject_output/终端输入通路,详见 pane/send_text.md)。注意这只适合可以响应流控的交互式程序,对忽略 XON/XOFF 的程序无效。

5.3 多窗口环境下只对特定窗口生效

因为回调第一个参数带有window:window_id(),你可以只关心某个特定窗口的焦点变化:

local wezterm = require 'wezterm' local target_window_id = nil wezterm.on('window-focus-changed', function(window, pane) local wid = window:window_id() -- 首次触发时记住当前窗口,之后只响应它 if target_window_id == nil then target_window_id = wid end if wid == target_window_id then wezterm.log_info('focus of main window changed to ' .. tostring(window:is_focused())) end end)

5.4 组合update-status实现状态栏焦点指示

wezterm/window/is_focused.md 中展示了另一种思路:不通过window-focus-changed,而是利用update-status事件(它在焦点变化时同样会被触发)刷新状态栏。两者可以组合:用window-focus-changed做"一次性动作"(如改配色、暂停任务),用update-status做"持续显示"(如状态栏图标):

local wezterm = require 'wezterm' wezterm.on('update-status', function(window, pane) local overrides = window:get_config_overrides() or {} if window:is_focused() then overrides.color_scheme = 'nordfox' else overrides.color_scheme = 'nightfox' end window:set_config_overrides(overrides) end) return {}

两种方式的差别在于:window-focus-changed只在焦点状态翻转的瞬间触发一次;update-status会被更频繁地调用(如每 1 秒刷新一次),适合需要持续反映状态、但不想维护额外触发源的场景。若在window-focus-changed回调里做较重的工作,建议避免在回调内再触发会连锁更新状态栏的写操作,防止事件风暴。


六、与其他窗口事件的关联与区分

window-focus-changed属于 窗口事件(Window Events) 一族,与以下事件共享相同的window/pane双参数结构和相同的并发调度机制:

事件触发时机典型用途
window-focus-changed窗口焦点状态变化焦点感知的配色、暂停/恢复任务
window-resized窗口尺寸变化、全屏切换动态调整窗口内边距(见 window-resized.md)
window-config-reloaded配置重载(自动重载、ReloadConfigurationset_config_overrides应用配置覆盖、联动重算
update-status周期性刷新(默认每 1 秒)及焦点等状态变化更新左右状态栏

需要区分的是:

  • window-focus-changed反映的是窗口级输入焦点
  • 状态栏(tab bar)刷新与活动 pane 变更更多依赖update-status
  • 若你在window-config-reloaded中调用set_config_overrides,会再次触发window-config-reloaded,注意避免死循环(window-config-reloaded.md);window-focus-changed本身没有这种自触发链路,回调中可以相对放心地修改配置覆盖。

七、调试与注意事项

  1. 确认版本window-focus-changed需要 wezterm20221119-145034-49b9839f或更新版本。可以在配置中通过wezterm.version(wezterm/version 相关文档)打印当前版本核对。
  2. 查看日志:回调中的wezterm.log_info/wezterm.log_warn/wezterm.log_error输出可通过ShowDebugOverlay调试面板查看;若 wezterm 由终端启动,也会打印到该终端 stdout。
  3. 避免重复注册wezterm.on同一个事件名可以注册多个处理器,它们按注册顺序依次执行(见 config/src/lua.rs)。不要在每个回调里再次require 'wezterm'或重复wezterm.on,这会导致处理器越积越多。
  4. 异步开销:事件回调运行在独立异步任务中,不要在回调里做阻塞主线程的重操作(如长时间os.execute、网络请求);如需与窗口交互,优先使用window对象提供的异步方法(如window:get_dimensions())。
  5. 多窗口/多进程window-focus-changed只在 GUI 前端进程(wezterm-gui)中触发;通过wezterm connect连接的远端 mux 会话中,焦点事件仍由本地 GUI 进程负责感知与分发。

总结

window-focus-changed是 wezterm 面向窗口焦点场景提供的精确、低噪的事件接口:触发时机由TermWindow::focus_changed在每次焦点翻转时统一驱动(wezterm-gui/src/termwindow/mod.rs),回调携带windowpane两个对象,配合window:is_focused()window:set_config_overrides()等方法可以完成从"日志记录"到"焦点感知配色""任务暂停恢复"等丰富联动。理解其 fire-and-forget 语义与单飞/排队并发机制,有助于写出健壮、不产生事件风暴的配置代码。

【免费下载链接】weztermA GPU-accelerated cross-platform terminal emulator and multiplexer written by @wez and implemented in Rust项目地址: https://gitcode.com/GitHub_Trending/we/wezterm

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询