Windows Terminal 添加设置完全指南:从 Terminal Settings Model 到 Settings UI 的完整链路
2026/9/7 23:25:49 网站建设 项目流程

Windows Terminal 添加设置完全指南:从 Terminal Settings Model 到 Settings UI 的完整链路

【免费下载链接】terminalThe new Windows Terminal and the original Windows console host, all in the same place!项目地址: https://gitcode.com/GitHub_Trending/term/terminal

本文为 Windows Terminal(OpenConsole 仓库)的开发者指南,系统讲解如何向 Terminal 添加一个新设置(Profile 设置、全局设置)或新 Action(含 Action 参数)。文档基于doc/cascadia/AddASetting.md的核心步骤展开,并结合当前仓库中src/cascadia/TerminalSettingsModel/项目的真实源码印证每一步的底层机制。读完本文,你将掌握:INHERITABLE_SETTING宏的继承与反序列化原理、设置如何从 JSON 流转到 XAML 控件、Action 事件的派发链路,以及设置 UI 中可绑定枚举的完整实现方式。

1. 总览:一个设置的三层实现结构

在 Windows Terminal 中,"添加一个设置"并不是改一个配置文件那么简单。设置系统被拆成三个职责清晰的项目:

  1. Terminal Settings ModelMicrosoft.Terminal.Settings.Model):负责设置的 (de)serialization(序列化/反序列化)与对外暴露,是整个设置系统的数据层核心;
  2. 功能层(Setting Functionality)TerminalApp等消费方项目读取模型中已加载的设置并驱动 UI 行为;
  3. Settings UITerminalSettingsEditor):设置编辑界面,将设置绑定为可交互控件并本地化。

添加设置的标准流程即依次完成这三层:先在 Model 中声明并支持序列化 → 再让功能代码读取生效 → 最后在设置 UI 中暴露

2. Terminal Settings Model:声明一个设置

2.1INHERITABLE_SETTING宏与继承机制

INHERITABLE_SETTING宏用于为新设置实现"继承"能力并在模型中存储该设置。它接收三个参数:

  • type:设置存储所用的类型;
  • name:存储用变量的名称;
  • defaultValue:用户在任何地方都未定义该设置时使用的值。

从源码看,该宏的真实定义位于 IInheritable.h,它展开后会自动生成一组方法,这正是设置继承机制的全部秘密:

宏展开产物职责
Has<name>()判断用户是否显式设置了该值(返回_name.has_value()
<name>OverrideSource()返回实际提供该解析值的父对象(用于 UI 展示"继承自哪里")
Clear<name>()清除用户设置值,使该设置回落到继承值
<name>()getter返回解析后的值,回退顺序为:用户设置值 → 继承值 → 系统默认值
<name>(value)setter直接覆写用户设置值

内部存储为std::optional<type>(见 _BASE_INHERITABLE_SETTING 宏),nullopt即表示"需从父对象继承"。解析时沿_parents向量依次向上查找第一个有值的祖先,找不到才返回默认值。这套机制支撑了 Windows Terminal 设置中"默认配置 → 配置文件 → 具体 Profile(含"parent"继承链)"的多层叠加模型。

当前仓库中还有两个变体值得注意:

  • INHERITABLE_SETTING_WITH_LOGGING(IInheritable.h#L211):setter 时记录设置变更日志,Profile 设置目前统一经由它初始化;
  • INHERITABLE_NULLABLE_SETTING(IInheritable.h#L236):用于"null 本身是合法值"的可选设置(如Profile.Foreground),以IReference形式对外暴露,区别于"nullopt 表示继承"的语义。

2.2 添加一个 Profile 设置(以CloseOnExit为例)

以下以教程中的示例——为 Profile 添加CloseOnExitMode CloseOnExit设置——走一遍完整步骤。

第 1 步:在Profile.h中声明/定义设置。

INHERITABLE_SETTING(CloseOnExitMode, CloseOnExit, CloseOnExitMode::Graceful)

当前仓库的演进:在最新代码中,Profile 设置的注册已收敛到 X-macro 列表。MTSMSettings.h 中定义了MTSM_PROFILE_SETTINGS(X),每个条目按(type, name, jsonKey, defaultArgs)格式声明,例如:

X(CloseOnExitMode, CloseOnExit, "closeOnExit", CloseOnExitMode::Automatic)

随后 Profile.h 通过PROFILE_SETTINGS_INITIALIZE将该列表展开为INHERITABLE_SETTING_WITH_LOGGING实例。相比文档中逐行手写,这种集中式注册把"新增设置"压缩为在列表中加一行,同时 jsonKey 与默认值都显式出现在同一处。

第 2 步:在Profile.idl中通过 WinRT 暴露设置

Boolean HasCloseOnExit(); void ClearCloseOnExit(); CloseOnExitMode CloseOnExit;

第 3 步:在Profile.cpp中添加 (de)serialization 与复制逻辑

// Top of file: // - Add the serialization key static constexpr std::string_view CloseOnExitKey{ "closeOnExit" }; // CopySettings() or Copy(): // - The setting is exposed in the Settings UI profile->_CloseOnExit = source->_CloseOnExit; // LayerJson(): // - get the value from the JSON JsonUtils::GetValueForKey(json, CloseOnExitKey, _CloseOnExit); // ToJson(): // - write the value to the JSON JsonUtils::SetValueForKey(json, CloseOnExitKey, _CloseOnExit);
  • 若设置不是基础类型(primitive type),还需在TerminalSettingsSerializationHelpers.h中为它添加枚举/枚举标志的 (de)serialization 逻辑:
// For enum values... JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Settings::Model::CloseOnExitMode) { JSON_MAPPINGS(3) = { pair_type{ "always", ValueType::Always }, pair_type{ "graceful", ValueType::Graceful }, pair_type{ "never", ValueType::Never }, }; }; // For enum flag values... JSON_FLAG_MAPPER(::winrt::Microsoft::Terminal::TerminalControl::CopyFormat) { JSON_MAPPINGS(5) = { pair_type{ "none", AllClear }, pair_type{ "html", ValueType::HTML }, pair_type{ "rtf", ValueType::RTF }, pair_type{ "all", AllSet }, }; }; // NOTE: This is also where you can add functionality for... // - overloaded type support (i.e. accept a bool and an enum) // - custom (de)serialization logic (i.e. coordinates)

当前仓库的实证:TerminalSettingsSerializationHelpers.h#L169-L194 中的CloseOnExitModemapper 正好演示了注释提到的"重载类型支持":FromJson被覆写以同时接受布尔值(trueGracefulfalseNever,兼容旧版布尔写法),并且映射表已扩展到 4 个值——新增了"automatic"。默认值也随之更新:MTSMSettings.h#L105 与 defaults.json 中均为"closeOnExit": "automatic"

2.3 添加一个 Global 设置

步骤与 Profile 设置完全相同,只是把修改对象换成GlobalAppSettings相关文件(GlobalAppSettings.idl/.h/.cpp,位于src/cascadia/TerminalSettingsModel/)。当前仓库中全局设置同样通过 MTSMSettings.h 的 X-macro 集中注册,且区分了两类:

  • MTSM_GLOBAL_ONLY_SETTINGS:真正全局的设置(如languagefirstWindowPreferencealwaysShowNotificationIcon),不随窗口变化;
  • MTSM_WINDOW_SETTINGS:按窗口变化的设置(如initialRowscopyOnSelectlaunchMode)。

二者并集构成MTSM_GLOBAL_SETTINGS,由GlobalAppSettings统一承载。

2.4 添加一个 Action

以下以教程中的openSettingsaction 为例。

第 1 步:在KeyMapping.idl中声明该 action

// Add the action to ShortcutAction enum ShortcutAction { OpenSettings }

第 2 步:在ActionAndArgs.cpp中添加序列化逻辑

// Top of file: // - Add the serialization key static constexpr std::string_view OpenSettingsKey{ "openSettings" }; // ActionKeyNamesMap: // - map the new enum to the json key { OpenSettingsKey, ShortcutAction::OpenSettings },

第 3 步(可选):让 Action 在命令面板(Command Palette)中出现时自动生成名称

// In ActionAndArgs.cpp GenerateName() --> GeneratedActionNames { ShortcutAction::OpenSettings, RS_(L"OpenSettingsCommandKey") }, // In Resources.resw for Microsoft.Terminal.Settings.Model.Lib, // add the generated name // NOTE: Visual Studio presents the resw file as a table. // If you choose to edit the file with a text editor, // the code should look something like this... <data name="OpenSettingsCommandKey" xml:space="preserve"> <value>Open settings file</value> </data>

第 4 步(可选):如果该 Action 支持参数

  • ActionArgs.idl中声明参数:
[default_interface] runtimeclass OpenSettingsArgs : IActionArgs { // this declares the "target" arg SettingsTarget Target { get; }; };
  • ActionArgs.h中定义新的 runtime class:
struct OpenSettingsArgs : public OpenSettingsArgsT<OpenSettingsArgs> { OpenSettingsArgs() = default; // adds a getter/setter for your argument, and defines the json key WINRT_PROPERTY(SettingsTarget, Target, SettingsTarget::SettingsFile); static constexpr std::string_view TargetKey{ "target" }; public: hstring GenerateName() const; bool Equals(const IActionArgs& other) { auto otherAsUs = other.try_as<OpenSettingsArgs>(); if (otherAsUs) { return otherAsUs->_Target == _Target; } return false; }; static FromJsonResult FromJson(const Json::Value& json) { // LOAD BEARING: Not using make_self here _will_ break you in the future! auto args = winrt::make_self<OpenSettingsArgs>(); JsonUtils::GetValueForKey(json, TargetKey, args->_Target); return { *args, {} }; } IActionArgs Copy() const { auto copy{ winrt::make_self<OpenSettingsArgs>() }; copy->_Target = _Target; return *copy; } };
  • ActionArgs.cpp中定义GenerateName()(用于命令面板中的自动命名);
  • ActionAndArgs.cppActionKeyNamesMap --> argParsers中注册参数解析器:
{ ShortcutAction::OpenSettings, OpenSettingsArgs::FromJson },

注意FromJson中的强调注释:必须使用winrt::make_self创建实例,否则会在未来踩坑——WinRT 类型需要走工厂路径以正确初始化引用计数与 COM 状态。

2.5 添加一个 Action Argument

步骤同"添加 Action"的第 3 步,但修改对象是相关的ActionArgs文件(在已有 Args 类中新增WINRT_PROPERTY成员及对应 jsonKey 即可)。

3. Setting Functionality:让设置真正生效

Terminal Settings Model 更新完成后,Windows Terminal 已经能够读写设置文件。本节说明如何为新设置添加功能。

3.1 应用级(App-level)设置

应用级设置影响 Windows Terminal 的"帧"(frame),通常即全局设置。TerminalApp项目负责呈现 Terminal 的帧,其中两个关键文件:

  • TerminalPage:负责 Windows Terminal 外观与交互的 XAML 控件;
  • AppLogic:负责窗口相关事务的 WinRT 类(如标题栏、Focus mode 等)。

二者都可以访问一个CascadiaSettings对象(模型侧实现见 CascadiaSettings.h),供你读取已加载的设置并相应更新 Terminal 行为。

3.2 终端级(Terminal-level)设置

终端级设置影响某个 shell 会话,通常是 Profile 设置。TerminalApp负责把 Terminal Settings Model 的设置打包进终端实例,分两类接口:

  • IControlSettings:影响TerminalControl(承载 shell 会话的 XAML 控件)。例如背景图定制、交互行为(如选择)、acrylic 与字体定制。TerminalControl项目通过一个保存的IControlSettings成员访问它们。
  • ICoreSettings:影响TerminalCore(与文本缓冲区交互的底层对象)。例如初始尺寸、历史缓冲区大小、光标定制。TerminalCore项目通过保存的ICoreSettings成员访问它们。

创建新的终端实例时,TerminalApp会把这些设置打包进TerminalSettings : IControlSettings, ICoreSettings对象。为此需要提交以下改动:

  • IControlSettings.idlICoreSettings.idl(依设置归属选择)中声明该设置。若设置是枚举类型,枚举应声明在这里,而不是在TerminalSettingsModel项目中
  • TerminalSettings.h中声明/定义设置:
// The WINRT_PROPERTY macro declares/defines a getter setter for the setting. // Like INHERITABLE_SETTING, it takes in a type, name, and defaultValue. WINRT_PROPERTY(bool, UseAcrylic, false);
  • TerminalSettings.cpp中:
    • Profile 设置更新_ApplyProfileSettings
    • 全局设置更新_ApplyGlobalSettings
    • 如需额外处理,在此完成。例如backgroundImageAlignment在 Terminal Settings Model 中以ConvergedAlignment存储(可对照 MTSMSettings.h#L154 的 X-macro 条目),但打包进 XAML 时会被拆分为水平与垂直两个对齐枚举。

3.3 Actions 的功能实现

Actions 被打包为ActionAndArgs对象,然后在TerminalApp中处理。为 Action 添加功能:

  • ShortcutActionDispatch文件中,当 action 发生时派发事件:
// ShortcutActionDispatch.idl event Windows.Foundation.TypedEventHandler<ShortcutActionDispatch, Microsoft.Terminal.Settings.Model.ActionEventArgs> OpenSettings; // ShortcutActionDispatch.h TYPED_EVENT(OpenSettings, TerminalApp::ShortcutActionDispatch, Microsoft::Terminal::Settings::Model::ActionEventArgs); // ShortcutActionDispatch.cpp --> DoAction() // - dispatch the appropriate event case ShortcutAction::OpenSettings: { _OpenSettingsHandlers(*this, eventArgs); break; }
  • TerminalPage文件中处理该事件:
// TerminalPage.h // - declare the handler void _HandleOpenSettings(const IInspectable& sender, const Microsoft::Terminal::Settings::Model::ActionEventArgs& args); // TerminalPage.cpp --> _RegisterActionCallbacks() // - register the handler _actionDispatch->OpenSettings({ this, &TerminalPage::_HandleOpenSettings }); // AppActionHandlers.cpp // - direct the function to the right place and call a helper function void TerminalPage::_HandleOpenSettings(const IInspectable& /*sender*/, const ActionEventArgs& args) { // NOTE: this if-statement can be omitted if the action does not support arguments if (const auto& realArgs = args.ActionArgs().try_as<OpenSettingsArgs>()) { _LaunchSettings(realArgs.Target()); args.Handled(true); } }

AppActionHandlers因 action 而异,几个常用辅助函数:

  • _GetFocusedTab():获取当前聚焦的 tab;
  • _GetActiveControl():获取处于活动状态的终端控件;
  • _GetTerminalTabImpl():尝试把给定 tab 向下转型为TerminalTab(承载终端实例的 tab)。

4. Settings UI:在设置界面中暴露设置

4.1 暴露枚举设置(EnumMappings)

如果新设置支持枚举,需要在 Terminal Settings Model 的EnumMappings中暴露"枚举 → 值"的映射表:

// EnumMappings.idl static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Settings.Model.CloseOnExitMode> CloseOnExitMode { get; }; // EnumMappings.h static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, CloseOnExitMode> CloseOnExitMode(); // EnumMappings.cpp // - this macro leverages the json enum mapper in TerminalSettingsSerializationHelper to expose // the mapped values across project boundaries DEFINE_ENUM_MAP(Model::CloseOnExitMode, CloseOnExitMode);

当前仓库的实证:EnumMappings.cpp#L16-L28 中DEFINE_ENUM_MAP宏的实现印证了注释所说的"跨项目边界复用"——它直接遍历JsonUtils::ConversionTrait<type>::mappings(即TerminalSettingsSerializationHelpers.h中的 JSON 枚举 mapper),把同一份映射表转成IMap<hstring, type>供 Settings Editor 使用。EnumMappings.cpp#L49 正是CloseOnExitMode的注册行。这样 JSON 解析与 UI 下拉列表永远不会出现两份不一致的枚举定义。

4.2 绑定并本地化枚举设置

先在 Settings UI 中找到该设置最适合的页面。以添加LaunchMode为例:

第 1 步:在Launch.idl中暴露可绑定属性

// Expose the current value for the setting IInspectable CurrentLaunchMode; // Expose the list of possible values Windows.Foundation.Collections.IObservableVector<Microsoft.Terminal.Settings.Editor.EnumEntry> LaunchModeList { get; };

第 2 步:在Launch.h中声明可绑定枚举设置

// the GETSET_BINDABLE_ENUM_SETTING macro accepts... // - name: the name of the setting // - enumType: the type of the setting // - settingsModelName: how to retrieve the setting (use State() to get access to the settings model) // - settingNameInModel: the name of the setting in the terminal settings model GETSET_BINDABLE_ENUM_SETTING(LaunchMode, Model::LaunchMode, State().Settings().GlobalSettings, LaunchMode);

第 3 步:在Launch.cpp的构造函数中(InitializeComponent()之后)初始化

// the INITIALIZE_BINDABLE_ENUM_SETTING macro accepts... // - name: the name of the setting // - enumMappingsName: the name from the TerminalSettingsModel's EnumMappings // - enumType: the type for the enum // - resourceSectionAndType: prefix for the localization // - resourceProperty: postfix for the localization INITIALIZE_BINDABLE_ENUM_SETTING(LaunchMode, LaunchMode, LaunchMode, L"Globals_LaunchMode", L"Content");

第 4 步:在 Microsoft.Terminal.Settings.Editor 的Resources.resw中为每个枚举值添加本地化文本。键名格式为<SettingGroup>_<SettingName><EnumValue>.Content

  • SettingGroup:全局设置用Globals,Profile 设置用Profile
  • SettingName:设置类型的 Pascal-case 形式(如"launchMode"LaunchMode);
  • EnumValue:该值在 JSON 中的键、首字母大写(如"focus"Focus);
  • 最终键名形如Globals_LaunchModeFocus.Content,这就是控件中实际显示的文本。

4.3 更新 XAML:枚举设置

创建 XAML 控件时遵循 UWP 设计指南,并掌握以下技巧:

  • 用符合SettingContainerStyle样式的ContentPresenter包裹控件;
  • SelectedItem绑定到对应的Current<Setting>(如CurrentLaunchMode),必须是TwoWay绑定;
  • ItemsSource绑定到<Setting>List(如LaunchModeList);
  • ItemTemplate设为Enum<ControlType>Template(如单选按钮用EnumRadioButtonTemplate);
  • 样式取自CommonResources.xaml中的合适样式。
<!--Launch Mode--> <ContentPresenter Style="{StaticResource SettingContainerStyle}"> <muxc:RadioButtons x:Uid="Globals_LaunchMode" SelectedItem="{x:Bind CurrentLaunchMode, Mode="TwoWay"}" ItemsSource="{x:Bind LaunchModeList}" ItemTemplate="{StaticResource EnumRadioButtonTemplate}" Style="{StaticResource RadioButtonsSettingStyle}"/> </ContentPresenter>

添加本地化文本时加上x:Uid,然后通过Resources.resw访问对应属性。例如Globals_LaunchMode.Header设置该控件的标题;同理可以设置 tooltip:

Globals_DefaultProfile.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip

4.4 非枚举设置的 XAML 绑定

同样参考CommonResources.xaml的样式、用类似的ContentPresenter包裹,但不经过Current<Setting><Setting>List,而是直接经由 state 绑定到设置本身。例如绑定altGrAliasing

<!--AltGr Aliasing--> <ContentPresenter Style="{StaticResource SettingContainerStyle}"> <CheckBox x:Uid="Profile_AltGrAliasing" IsChecked="{x:Bind State.Profile.AltGrAliasing, Mode=TwoWay}" Style="{StaticResource CheckBoxSettingStyle}"/> </ContentPresenter>

4.5 Profile 设置的"可观察化"

如果添加的是 Profile 设置,除上述步骤外还需修改Profiles相关文件,使设置变成可观察(observable)的:

// Profiles.idl --> ProfileViewModel // - this declares the setting as observable using the type and the name of the setting OBSERVABLE_PROJECTED_SETTING(Microsoft.Terminal.Settings.Model.CloseOnExitMode, CloseOnExit); // Profiles.h --> ProfileViewModel // - this defines the setting as observable off of the _profile object OBSERVABLE_PROJECTED_SETTING(_profile, CloseOnExit); // Profiles.h --> ProfileViewModel // - if the setting cannot be inherited by another profile (aka missing the Clear() function), use the following macro instead: PERMANENT_OBSERVABLE_PROJECTED_SETTING(_profile, Guid);

ProfilePageNavigationState持有一个ProfileViewModel,它包装了 Terminal Settings Model 中的Profile对象,ProfileViewModel的作用就是把所有 Profile 设置变为可观察属性,供 XAML 的x:Bind使用。

4.6 Action 与设置 UI

目前 Action 尚不支持在 Settings UI 中直接编辑(按键与 Action 的映射通过 JSON 设置文件中的actions段完成)。

5. 小结:新增设置的全链路检查清单

综合上述三层,向 Windows Terminal 添加一个完整的新设置需要触碰以下文件(以 Profile 枚举设置为参照):

层次文件关键动作
模型注册src/cascadia/TerminalSettingsModel/MTSMSettings.hProfile.idlX-macro 一行注册 + WinRT 暴露
序列化Profile.cppTerminalSettingsSerializationHelpers.hLayerJson/ToJson+JSON_ENUM_MAPPER
枚举共享EnumMappings.idl/.h/.cppDEFINE_ENUM_MAP跨项目暴露映射
功能生效TerminalSettings.h/.cppTerminalAppWINRT_PROPERTY+_ApplyProfileSettings
Action(可选)KeyMapping.idlActionAndArgs.cppActionArgs.idl/.h/.cppShortcutActionDispatch.*枚举、键名映射、参数类、事件派发
设置 UISettings Editor 的页面.idl/.h/.cppResources.resw、XAML可绑定枚举/直连绑定 +x:Uid本地化

理解这一链路后,再结合 doc/cascadia/Json-Utility-API.md 中JsonUtils的 API 细节,即可独立为 Terminal 扩展任意新设置或新 Action。

【免费下载链接】terminalThe new Windows Terminal and the original Windows console host, all in the same place!项目地址: https://gitcode.com/GitHub_Trending/term/terminal

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

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

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

立即咨询