首页
/ Windows Terminal `unfocusedAppearance` 深度解析:用外观配置对象实现窗格焦点状态区分

Windows Terminal `unfocusedAppearance` 深度解析:用外观配置对象实现窗格焦点状态区分

2026-09-04 16:17:33作者:廉彬冶Miranda

本文基于 Windows Terminal 仓库中的设计规范文档 doc/specs/#3062 - Appearance configuration object for profiles.md,完整讲解 profiles 中 unfocusedAppearance 配置对象的设计动机、允许参数、"整体继承"模型与 JSON 配置方式,并结合当前仓库的 TerminalSettingsModelTerminalControl 源码,剖析焦点切换时外观对象如何被选中、下发到渲染层,帮助读者既会用这个配置,也理解其底层实现链路。

一、背景:为什么需要"失焦外观"配置

规范文档的出发点很直接:当多个窗格(pane)并排显示时,用户希望有一个比默认样式更强的视觉指示,来区分当前聚焦的窗格与未聚焦的窗格(该需求对应 issue #3062)。方案是让控制对象(TermControl)能够根据自身的状态——聚焦或失焦——渲染出不同的外观,而承载这种"状态化外观"的机制,就是 profile 里的 appearance configuration object,即 unfocusedAppearance

规范摘要(Abstract)给出的核心命题是:在 profiles 中支持"配置对象",使控制对象可以根据自身状态的不同而渲染出不同外观,例如聚焦时与非聚焦时使用不同的配色、光标样式。

二、方案设计:复用 TerminalSettings 的继承树

规范"Solution Design"一节指出,该功能的实现建立在 TerminalSettings 类引入的继承机制之上——不同的 TerminalSettings 对象可以互相继承。这一机制的原始目的,是不希望 settings reload 时抹掉 TermControl 在运行期对设置做过的覆写(override):由于传给控制对象的是 TerminalSettings子对象(child),重载时只需更换子对象的父节点,子对象里保存的运行时覆写就得以保留。

unfocusedAppearance 的设计思路与它一致:再传一个 TerminalSettings 对象给控制对象,这个对象只是一个"已经预置了若干覆写"的子对象。当控制对象获得或失去焦点时,只需要在两个设置对象之间做切换即可。

这一设计在当前源码中可以直接印证。ControlCore 构造时就持有两份外观对象:

// src/cascadia/TerminalControl/ControlCore.cpp
_settings = settings;
_hasUnfocusedAppearance = static_cast<bool>(unfocusedAppearance);
_unfocusedAppearance = _hasUnfocusedAppearance ? unfocusedAppearance : settings;

ControlCore.cpp。如果没有提供失焦外观,_unfocusedAppearance 就退化为普通设置本身,从而保证未配置该功能的用户行为完全不变。

三、允许在对象中定义哪些参数

规范明确:这些状态在最初是**纯外观(entirely appearance-based)**的,因此 Profile 里并非所有参数都能出现在该对象中(例如不希望出现会导致窗口尺寸变化的参数)。规范给出的初始允许清单为:

  • 一切与颜色相关的参数:colorSchemeforegroundbackgroundcursorColor 等;
  • 一切与背景图片相关的参数:pathopacityalignmentstretchMode
  • cursorShape

规范同时声明,未来可能放行更多参数(如 bellStyle),属于超出本规范范围的议题。

当前仓库的实现已经比规范当时的清单更宽:unfocusedAppearance 在 JSON Schema 中被声明为完整引用 AppearanceConfig 定义(类型可为对象或 null),见 profiles.schema.json

"unfocusedAppearance": {
  "$ref": "#/$defs/AppearanceConfig",
  "description": "Sets the appearance of the terminal when it is unfocused.",
  "type": [ "object", "null" ]
}

也就是说,凡属于外观类(AppearanceConfig)的设置都可以出现在其中,包括规范点名的颜色、背景图片与光标形状,以及 opacityuseAcrylicintenseTextStyleadjustIndistinguishableColors 等参数。完整的可继承外观参数列表定义在 AppearanceConfig.hMTSMSettings.h 中,例如:

参数 JSON 键 默认值(源码可见部分)
光标形状 cursorShape Bar
光标高度 cursorHeight DEFAULT_CURSOR_HEIGHT
背景图路径 backgroundImage 空(MediaResource::Empty()
背景图透明度 backgroundImageOpacity 1.0
背景图对齐 backgroundImageAlignment 水平居中 + 垂直居中
背景图拉伸模式 backgroundImageStretchMode UniformToFill
窗口不透明度 opacity 1.0
是否使用亚克力 useAcrylic false
高亮文本风格 intenseTextStyle Bright
明暗两套配色方案名 darkColorSchemeName / lightColorSchemeName Campbell
前景/背景/选中/光标色 foreground / background / selectionBackground / cursorColor 未设置(可为 null,继承)

其中四个颜色项与 opacity 因解析方式特殊,以 INHERITABLE_NULLABLE_SETTING 宏单独声明(见 AppearanceConfig.h),未显式设置时为 null、走继承。

四、继承模型:"整体继承"(all-or-nothing)

这是规范中最值得细读的设计决策之一。unfocusedAppearance 对象被视为一个单一设置项(a single setting),而不是"一个内含多个设置、各自分别向上找父节点的对象"。规范解释选择这一模型的原因是:它比"对象内每个设置各自带一个父节点"的备选方案更干净、更易理解

Profile 类的头文件注释里,作者直接画出了失焦设置的继承树(DAG),见 Profile.h

                +-------------------+
                |Profile.defaults |
                |DefaultAppearance |
                |UnfocusedAppearance|
                +-------------------+
                   ^             ^
                   |             |
+------------------+           +------------------+
|MyProfile          |           |Profile.defaults   |
|DefaultAppearance  |           |UnfocusedAppearance|
+-------------------+           +-------------------+
                   ^
                   |
+------------------+
|MyProfile          |
|UnfocusedAppearance|
+-------------------+

即:profile 自身的 unfocusedAppearance 的父节点是 profile 的默认外观(DefaultAppearance),而后者再向上追溯到全局 profileDefaults 中的对应外观。规范中给出的未定义参数取值顺序与此一致:

  1. profile(或 globals/profileDefaults)中定义的 unfocused config;
  2. 终端控制对象(control)在运行期做出的覆写;
  3. 父 profile。

源码中 Profile::CreateUnfocusedAppearance 精确实现了"把 profile 默认外观加为最低优先级父节点"这一行为:

// src/cascadia/TerminalSettingsModel/Profile.cpp
void Profile::CreateUnfocusedAppearance()
{
    if (!_UnfocusedAppearance)
    {
        auto unfocusedAppearance{ winrt::make_self<implementation::AppearanceConfig>(weak_ref<Model::Profile>(*this)) };

        // If an unfocused appearance is defined in this profile, any undefined parameters are
        // taken from this profile's default appearance, so add it as a parent
        com_ptr<AppearanceConfig> parentCom;
        parentCom.copy_from(winrt::get_self<implementation::AppearanceConfig>(_DefaultAppearance));
        unfocusedAppearance->AddLeastImportantParent(parentCom);

        _UnfocusedAppearance = *unfocusedAppearance;
    }
}

Profile.cppProfile 类里该字段本身就是以可继承设置声明的:INHERITABLE_SETTING(Model::Profile, Model::IAppearanceConfig, UnfocusedAppearance, nullptr),见 Profile.h

"整体继承"在复制 profile 的逻辑里同样体现:当 CascadiaSettings 复制一个 profile 时,注释明确写道 UnfocusedAppearance is treated as a single setting,整个对象作为一个整体被复制,并把"被复制 profile 的默认外观"重新挂为副本中 UnfocusedAppearance 的父节点,见 CascadiaSettings.cppProfile::CopySettings 中克隆时也调用 AddLeastImportantParent(defaultAppearance) 重建这条父子链,见 Profile.cpp

五、配置实战:如何在 settings.json 中书写 unfocusedAppearance

规范"UI/UX Design"一节给出的用户侧配置形态如下(可直接放入 profile 中):

"unfocusedAppearance":
{
    "colorScheme": "Campbell",
    "cursorColor": "#888",
    "cursorShape": "emptyBox",
    "foreground": "#C0C0C0",
    "background": "#000000"
}

效果:当该窗格失焦时,光标变为灰色空盒、文字变为浅灰、背景变为纯黑;聚焦时恢复 profile 的常规外观。由于未列出的参数(如 selectionBackgroundopacity、字体等)会沿第四节描述的继承链向上取值,因此只需要写"你希望和聚焦态不同的那几个字段"

JSON 解析入口在 Profile::FromJson / LayerJson 中,键名常量即 "unfocusedAppearance"(见 Profile.cpp),解析逻辑是:若 JSON 中存在该键,则创建一个 AppearanceConfig 并用 LayerJson 合并后赋给 _UnfocusedAppearance;序列化时再经 ToJson 写回。

一个值得注意的行为细节(规范"UI/UX Design"末尾明确说明):当某些外观设置被 OSC 转义序列在运行期修改(例如 OSC 10/11 改前景色/背景色)时,只有聚焦/常规外观会变化,失焦外观本身保持不变;但由于失焦对象继承自常规对象,只要它没有为该设置定义自己的值,该变化仍会透过继承反映到失焦渲染上。这与 ControlCore 中运行期配色覆写只作用在 _focusedColorSchemeOverride 上的处理方式相互印证:

// src/cascadia/TerminalControl/ControlCore.cpp (ApplyAppearance)
const IControlAppearance newAppearance{ focused ? _settings : _unfocusedAppearance };
_terminal->UpdateAppearance(newAppearance);
if ((focused || !_hasUnfocusedAppearance) && _focusedColorSchemeOverride)
{
    _terminal->UpdateColorScheme(_focusedColorSchemeOverride);
}

ControlCore.cpp

六、焦点切换的运行时链路

从"焦点变化"到"重新着色"的完整调用链,在当前仓库中可以完整追溯:

  1. TermControl 层(UI 线程)UpdateControlSettings 接收新的控制设置后,根据当前是否聚焦选择下发哪份外观对象:

    // src/cascadia/TerminalControl/TermControl.cpp
    void TermControl::UpdateControlSettings(IControlSettings settings, IControlAppearance unfocusedAppearance)
    {
        _core.UpdateSettings(settings, unfocusedAppearance);
        _UpdateSettingsFromUIThread();
        _UpdateAppearanceFromUIThread(_focused ? _core.FocusedAppearance() : _core.UnfocusedAppearance());
    }
    

    TermControl.cpp

  2. ControlCore 层UpdateSettings 更新 _hasUnfocusedAppearance_unfocusedAppearance(若新外观为空则回退为普通设置,见 ControlCore.cpp);ApplyAppearance(focused) 则完成真正的对象切换——按 focused 选择 _settings_unfocusedAppearance,调用 _terminal->UpdateAppearance,并同步更新渲染引擎的不透明度、亚克力、像素着色器等外观相关状态,最后 TriggerRedrawAll 触发全量重绘(见 ControlCore.cpp)。

  3. 对外接口ControlCore 通过 WinRT 接口暴露 FocusedAppearance()UnfocusedAppearance()HasUnfocusedAppearance(),供宿主(如 TerminalApp 的 ContentManager)判断与查询,见 ControlCore.idl

从源码结构看,焦点切换并不产生新对象,只是在两份已构造好的外观对象间做选择,这与规范"它 simply switches between the two settings objects"的描述一致,也解释了性能章节的结论:窗格间常规切换的开销是可控的,只有短时间内高频切换大量窗格时,连续的多次外观变更才可能对性能产生可感知影响

七、能力影响面:规范原文的五项评估

规范"Capabilities"一节对该功能的副作用评估值得原样保留,这是理解功能边界的关键:

  • 可访问性(Accessibility):不影响。
  • 安全性(Security):不影响。
  • 可靠性(Reliability):这是设置解析/加载可能失败的新位置,但任何新增设置都有此风险,规范认为这是该功能合理的代价。
  • 兼容性(Compatibility):不应产生影响。
  • 性能、功耗与效率(Performance, Power, and Efficiency):短时间内快速切换大量窗格、引发连续多次外观变更时可能影响性能;常规的合理窗格切换不应有明显影响。

八、潜在问题与未来演进方向

规范"Potential Issues"一节指出了一个真实工程风险:非活动(inactive)标签页在后台会按 UnfocusedRenderingParams 渲染,需要确保切换到某个非活动标签页、从而让渲染器用"常规参数"刷新时,不会导致窗口闪现或显示出突兀的渲染值变化指示。这是实现该功能时必须重点回归测试的场景。

"Future considerations"一节还保留了两条尚未落地的路线:

  1. 设置 UI(Settings UI)中的呈现方式:当时未定。从当前仓库看,设置编辑器侧已存在对 UnfocusedAppearance 的支持代码(如 Profiles_Appearance.cppProfileViewModel.cpp),该议题在实现中已有着落。
  2. 更多状态(如 elevated:规范记录了团队的讨论结论——当多个状态可能同时生效(如"失焦 + 提权")时,找不到合适的分层方案;加之状态数量不确定,决定当前只支持 unfocused 一种状态,未来若确有新增状态,可以扩展实现(extension)而非内建支持;若最终只有 unfocused 与 elevated 两种,也可以允许组合出"unfocused elevated"状态。

九、如何自行验证

结合仓库内的可验证材料,读者可以按以下路径核对本文各结论:

  • 设计动机与完整决策过程:[规范原文](https://gitcode.com/GitHub_Trending/term/terminal/blob/20588130d8ef2ba40eb56bdae88e04cce7fc5b5d/doc/specs/?utm_source=gitcode_repo_files#3062 - Appearance configuration object for profiles.md)(作者 Pankaj Bhojwani,创建 2020-11-20,issue #8345);
  • 设置模型:Profile 的字段声明、创建/删除方法与 JSON 序列化(Profile.hProfile.cpp),AppearanceConfig 的参数定义(AppearanceConfig.h);
  • 运行层切换逻辑:ControlCore.cppTermControl.cpp
  • JSON 校验:profiles.schema.jsonunfocusedAppearance 的类型为 object | null,即 profile 中可以整体省略该字段;
  • 单元测试:src/cascadia/UnitTests_SettingsModel/ 目录下的 ColorSchemeTests.cppMediaResourceTests.cpp 等测试文件中存在对 UnfocusedAppearance 的引用,覆盖配色方案与媒体资源在该对象上的行为,可作为回归验证的起点。

小结

unfocusedAppearance 是 Windows Terminal 将"状态化渲染"做进设置模型的代表性设计:它没有为失焦态单独发明一套机制,而是复用 TerminalSettings 的继承树,用一个"预置覆写的子对象 + 整体继承"的模型,让控制对象在聚焦/失焦间切换两份外观即可。配置侧只需在 profile 中写差异字段,未写字段沿"profile 的失焦配置 → 控制对象运行期覆写 → 父 profile 默认外观 → 全局 profileDefaults"一路继承;运行侧由 ControlCore::ApplyAppearance 在每次焦点变化时完成对象切换与重绘。理解这一条链路,既能正确配置窗格焦点视觉区分,也能在扩展或排查相关渲染问题时快速定位到实现层。

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

项目优选

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