首页
/ WezTerm ExecDomain 配置详解:用 `wezterm.exec_domain` 包装本地命令派发

WezTerm ExecDomain 配置详解:用 `wezterm.exec_domain` 包装本地命令派发

2026-09-10 12:26:44作者:毕习沙Eudora

ExecDomain 是 WezTerm(当前仓库 GitHub_Trending/we/wezterm)中用于定义"本地执行型多路复用域"(local-execution multiplexer domain)的核心配置对象。它的价值在于:让 WezTerm 不直接执行用户请求的程序,而是先把该命令交给一个 Lua fixup 函数改写(wrap),再由改写后的命令去启动真正的程序。读完本文,你将掌握 wezterm.exec_domain 的完整参数语义、fixuplabel 回调的编写方式,并能用两个完整范例(systemd scope 隔离、Docker 容器域)把这一机制落地到自己的 wezterm.lua 配置中。

ExecDomain 是什么

根据官方文档 docs/config/lua/ExecDomain.md,一个 ExecDomain 定义的是一个"本地执行的 multiplexer 域"(自版本 20220807-113146-c2fee766 起可用)。通俗地说:与其直接执行请求的程序,ExecDomain 允许你把该命令调用"传递"给其他进程来处理。

举个典型场景:如果你想更方便地在 Docker 容器里使用标签页(tab)与窗格(pane),可以定义一个 ExecDomain,让所有命令都经由 docker exec 来运行。诚然,你也可以手动让 WezTerm 显式 spawn 一条执行 docker exec 的命令,但那样你还得改动默认的"分屏键位"(如 SplitHorizontalSplitVertical 等 key assignment),让它们知道这个偏好。而使用 ExecDomain,这个偏好会与 pane 绑定在一起,分屏、新开标签页都能"天然"地继承该偏好,交互上更直观。

从源码结构看,ExecDomain 是配置层的一等公民。在 config/src/exec_domain.rs 中,它被定义为一个 Rust 结构体:

#[derive(Debug, Clone, FromDynamic, ToDynamic)]
pub struct ExecDomain {
    #[dynamic(validate = "validate_domain_name")]
    pub name: String,
    pub fixup_command: String,
    pub label: Option<ValueOrFunc>,
}
  • name:域的唯一标识,配置校验时会调用 validate_domain_name
  • fixup_command:Lua 回调(以字符串形式存储的 Lua 函数引用),用于改写命令;
  • label:可选,可以是静态字符串(Value)或 Lua 回调函数(Func),对应 ValueOrFunc 枚举。

config/src/config.rs 中,配置结构体通过 pub exec_domains: Vec<ExecDomain> 承接用户在 Lua 中 config.exec_domains = {...} 的赋值。

定义 ExecDomain

必须使用 wezterm.exec_domain 函数来定义域。它接受如下参数:

wezterm.exec_domain(NAME, FIXUP [, LABEL])
  • name:唯一标识该域。必须与其他任何 multiplexer 域(unix/ssh/wsl/tls 等)的名称都不冲突。配置加载时会统一做名称唯一性校验,见 config/src/config.rscheck_domain(&d.name, "exec domain") 的检查逻辑;
  • fixup:一个 Lua 函数,被调用来 fixup(改写)请求的命令,并返回修订后的命令;
  • label(可选):可以是一个字符串,作为 Launcher Menu(启动器菜单) 中显示的标签;也可以是一个返回标签的 Lua 函数。

fixup 函数

最简单的 fixup 函数长这样:

wezterm.exec_domain('myname', function(cmd)
  return cmd
end)

cmd 参数是一个 SpawnCommand 对象,包含将要执行的命令信息。这个 SpawnCommand 要么是用户在 key assignment 中配置的产物,要么是在响应"新开标签页 / 分屏"请求时生成的等价物。

预期行为是:fixup 函数调整传入命令的各个字段,然后把它返回。WezTerm 将执行这个被调整后的命令,以满足用户的 spawn 请求。

从实现上看,WezTerm 在 mux/src/domain.rsLocalDomain::fixup_command 中完成了"执行请求 → 构造 SpawnCommand → 调用 Lua fixup → 重组命令"的完整链路:

  1. 先通过 resolve_exec_domain()config::configuration().exec_domains 中按 name 查找匹配的域(mux/src/domain.rs);
  2. CommandBuilder 中的 argv、完整环境变量、cwd 提取出来,构造一个 SpawnCommand,其中 domain 字段被设为 SpawnTabDomain::DomainName(ed.name.clone())
  3. 在主线程上以异步回调方式执行 Lua 的 fixup_command,并把返回结果反序列化回 SpawnCommand
  4. 最后用新的 argsset_environment_variablescwd 重新填充 CommandBuilder

也就是说,fixup 函数拥有对 argv、环境变量、工作目录的完全控制权——这正是它能做"命令包装"的根本原因。

label

标签会显示在 Launcher Menu 中。你可以设置为静态字符串,也可以设置为 Lua 回调。默认行为等价于下面这个回调:

-- domain_name 与调用 wezterm.exec_domain() 时的第一个参数相同
wezterm.exec_domains(domain_name, fixup_func, function(domain_name)
  return domain_name
end)

(注意:这是文档中表达默认语义的等价示意代码,实际配置中第三参如果省略即取此默认行为。)

使用回调函数可以让你在 Launcher Menu 渲染前的最后一刻生成修订后的标签。这很适合把标签做成某种"状态信息"——例如为 Docker 容器或虚拟机定义 ExecDomain 时,让标签反映它当前是否在运行。

静态字符串与回调生成的字符串都可以包含影响文本样式的转义序列。建议使用 wezterm.format() 来管理这些样式。

从实现上看,domain_label() 位于 mux/src/domain.rs

  • labelValueOrFunc::Value(Value::String(s)),直接返回该静态字符串;
  • labelValueOrFunc::Func(label_func),则在主线程异步调用该 Lua 函数,把 domain_name 作为参数传入,并将其返回值解释为字符串;若回调出错,会记录 log::error! 并回退到域名称本身;
  • 若没有 label,回退到 self.name

示例一:让每条命令运行在独立的 systemd scope 中

完整示例来自 docs/config/lua/ExecDomain.md

local wezterm = require 'wezterm'
local config = {}

-- Equivalent to POSIX basename(3)
-- Given "/foo/bar" returns "bar"
-- Given "c:\\foo\\bar" returns "bar"
local function basename(s)
  return string.gsub(s, '(.*[/\\])(.*)', '%2')
end

config.exec_domains = {
  -- Defines a domain called "scoped" that will run the requested
  -- command inside its own individual systemd scope.
  -- This defines a strong boundary for resource control and can
  -- help to avoid OOMs in one pane causing other panes to be
  -- killed.
  wezterm.exec_domain('scoped', function(cmd)
    -- The "cmd" parameter is a SpawnCommand object.
    -- You can log it to see what's inside:
    wezterm.log_info(cmd)

    -- Synthesize a human understandable scope name that is
    -- (reasonably) unique. WEZTERM_PANE is the pane id that
    -- will be used for the newly spawned pane.
    -- WEZTERM_UNIX_SOCKET is associated with the wezterm
    -- process id.
    local env = cmd.set_environment_variables
    local ident = 'wezterm-pane-'
      .. env.WEZTERM_PANE
      .. '-on-'
      .. basename(env.WEZTERM_UNIX_SOCKET)

    -- Generate a new argument array that will launch a
    -- program via systemd-run
    local wrapped = {
      '/usr/bin/systemd-run',
      '--user',
      '--scope',
      '--description=Shell started by wezterm',
      '--same-dir',
      '--collect',
      '--unit=' .. ident,
    }

    -- Append the requested command
    -- Note that cmd.args may be nil; that indicates that the
    -- default program should be used. Here we're using the
    -- shell defined by the SHELL environment variable.
    for _, arg in ipairs(cmd.args or { os.getenv 'SHELL' }) do
      table.insert(wrapped, arg)
    end

    -- replace the requested argument array with our new one
    cmd.args = wrapped

    -- and return the SpawnCommand that we want to execute
    return cmd
  end),
}

-- Making the domain the default means that every pane/tab/window
-- spawned by wezterm will have its own scope
config.default_domain = 'scoped'

return config

这个范例的几个关键点值得展开:

  1. 身份合成WEZTERM_PANE 是新 pane 的 id,WEZTERM_UNIX_SOCKET 与 wezterm 进程 id 相关,两者拼出的 ident 使 scope 名"合理唯一"。basename 从 socket 路径中取出最后一段,避免路径中的 / 破坏 systemd unit 名的合法性。
  2. 默认程序兜底cmd.args 可能为 nil,表示应使用默认程序。这里用 cmd.args or { os.getenv 'SHELL' } 显式把 $SHELL 作为兜底。
  3. 资源隔离语义:systemd scope 为命令建立了强资源控制边界,可避免某个 pane 触发 OOM 时殃及其他 pane。这也是把 default_domain 设为 'scoped' 的动机——让 WezTerm 派生的每个 pane/tab/window 都拥有独立的 scope。
  4. 调试辅助:fixup 里调用 wezterm.log_info(cmd) 可以把 SpawnCommand 的字段打印到日志中,方便你确认对象结构。

示例二:把每个运行中的 Docker 容器变成一个域

第二个完整示例(同样来自 docs/config/lua/ExecDomain.md)展示了如何动态地把每个运行中的 Docker 容器注册为域,从而直接向容器内 spawn shell、或对容器进行分屏:

local wezterm = require 'wezterm'
local config = wezterm.config_builder()

function docker_list()
  local docker_list = {}
  local success, stdout, stderr = wezterm.run_child_process {
    'docker',
    'container',
    'ls',
    '--format',
    '{{.ID}}:{{.Names}}',
  }
  for _, line in ipairs(wezterm.split_by_newlines(stdout)) do
    local id, name = line:match '(.-):(.+)'
    if id and name then
      docker_list[id] = name
    end
  end
  return docker_list
end

function make_docker_label_func(id)
  return function(name)
    local success, stdout, stderr = wezterm.run_child_process {
      'docker',
      'inspect',
      '--format',
      '{{.State.Running}}',
      id,
    }
    local running = stdout == 'true\n'
    local color = running and 'Green' or 'Red'
    return wezterm.format {
      { Foreground = { AnsiColor = color } },
      { Text = 'docker container named ' .. name },
    }
  end
end

function make_docker_fixup_func(id)
  return function(cmd)
    cmd.args = cmd.args or { '/bin/sh' }
    local wrapped = {
      'docker',
      'exec',
      '-it',
      id,
    }
    for _, arg in ipairs(cmd.args) do
      table.insert(wrapped, arg)
    end

    cmd.args = wrapped
    return cmd
  end
end

function compute_exec_domains()
  local exec_domains = {}
  for id, name in pairs(docker_list()) do
    table.insert(
      exec_domains,
      wezterm.exec_domain(
        'docker:' .. name,
        make_docker_fixup_func(id),
        make_docker_label_func(id)
      )
    )
  end
  return exec_domains
end

config.exec_domains = compute_exec_domains()

return config

使用以上配置后,每次配置被重新加载(reload),可用域列表都会随之更新。打开 Launcher Menu 就能看到各个容器及其运行状态,并可直接在这些容器内启动程序。

该示例值得关注的工程细节:

  • 动态域 + 即时标签make_docker_label_func(id) 返回的闭包每次渲染菜单时都会执行一次 docker inspect,把 {{.State.Running}} 映射为 Green/Red 前景色,并通过 wezterm.format() 生成带样式的文本——这正是"用回调生成 label 以反映状态"的最佳实践。颜色常量对应 AnsiColor,其语义与 color 模块一致。
  • 闭包捕获 idmake_docker_fixup_func(id)make_docker_label_func(id) 均以闭包捕获容器的真实 id,而域 name 使用可读性更好的 docker:<容器名>,二者解耦。
  • wezterm.config_builder():返回一个可写配置对象,最终 return config 生效;config.exec_domains 支持表(数组)形式的多个域。

ExecDomain 的底层实现原理

mux/src/domain.rs 的源码可以确认以下实现事实:

  • ExecDomain 基于 LocalDomainLocalDomain::new_exec_domain(exec_domain)mux/src/domain.rs)只是用域名称创建一个本地域,执行仍然走本机 pty 通道;resolve_exec_domain() 按名称反查配置中的 exec_domains,未命中时返回 None
  • 命令改写发生在 spawn 之前fixup_commandmux/src/domain.rs)依次处理 WSL 域(wsl.exe 包装)、ExecDomain(Lua fixup 改写)与 Flatpak 沙箱(flatpak-spawn --host),三者按优先级串联。对 ExecDomain 而言,整个 Lua 回调通过 config::lua::emit_async_callback 在配置主线程上以异步方式执行,返回值必须能被解释为 SpawnCommand,否则报错并带上下文信息 calling ExecDomain {name} function
  • label 渲染同样经过 Lua 回调domain_label()mux/src/domain.rs)把域名称作为参数传给 label 函数;回调抛错时不会中断渲染,而是记录错误并回退到域名。
  • 名称全局唯一性校验:配置校验阶段会对 unix_domainsssh_domainsexec_domainswsl_domainstls_clients 逐一执行 check_domainconfig/src/config.rs),因此 exec domain 的名称不能与任何其他类型的域重名。

结合 SpawnCommand 理解 fixup 能改什么

fixup 收到的 cmdSpawnCommand 对象,其字段在文档与实现中保持一致,均可省略:

wezterm.action.SpawnCommandInNewWindow {
  -- 可选标签;仅当该 SpawnCommand 出现在 launch_menu 中时才使用
  label = 'List all the files!',

  -- 命令及其参数的数组;省略时使用目标域的默认程序
  args = { 'ls', '-al' },

  -- 命令的工作目录;省略时 wezterm 根据触发时刻的活动 pane 推断,
  -- 若活动 pane 的域与本次 SpawnCommand 指定的域一致,则沿用其 cwd,
  -- 无法推断时通常回退到当前用户主目录
  cwd = '/some/path',

  -- 为本命令追加设置的环境变量
  set_environment_variables = {
    SOMETHING = 'a value',
  },

  -- 使用当前活动 pane 的 multiplexer 域(默认行为)
  domain = 'CurrentPaneDomain',
  -- 或使用默认域(通常为 "local",除非用 wezterm connect/serial 启动)
  -- domain = 'DefaultDomain',
  -- 或显式指定命名域
  -- domain = { DomainName = 'my.server' },
}

对照 mux/src/domain.rs 的实现,fixup 中真正会被采纳的字段是:args(重写 argv)、set_environment_variables(整体替换环境变量)、cwd(替换工作目录);其余字段主要用于传递上下文。这也解释了为何两个示例都集中在"重写 cmd.args"上——argv 是命令包装的核心载体。

总结与适用范围

ExecDomain 最适合以下场景:

  1. 命令包装:如通过 docker execsystemd-runsshsudoflatpak-spawn 等包装器执行目标程序;
  2. 默认域注入:把 config.default_domain 设为某个 ExecDomain 名称,使所有 pane/tab/window 都套用同一种包装策略;
  3. 动态域名:如示例二那样,在配置加载时扫描外部资源(容器、VM、连接)并批量生成域,配合 label 回调呈现实时状态;
  4. 键位联动:由于偏好与 pane 关联,默认的分屏/新标签键位会自动把命令交给正确的域,无需逐个改 key assignment。

需要留意的是:ExecDomain 是"本地执行"型域,与远程多路复用域(SSH/TLS/Unix domain)定位不同;它改写的是命令本身,而不是建立跨主机的会话连接。相关对比可参考 docs/config/lua/SshDomain.mddocs/config/lua/WslDomain.md(WSL 域在 mux/src/domain.rs 中同样走 fixup_command 链路,用 wsl.exe --distribution ... --exec 包装命令,是理解"本地包装"思路的另一个实例)。

配置好 config.exec_domains 后,重启或 reload WezTerm,即可在 Launcher Menu(Ctrl+Shift+Space 等默认键位打开)中看到每个域的标签,并在其中启动程序。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
docsdocs
暂无描述
Markdown
900
5.83 K
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.76 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
860
1.35 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
927
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.94 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
533
603
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
396
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.04 K
527