doc/cascadia/AddASetting.md
Adding a setting to Windows Terminal is fairly straightforward. This guide serves as a reference on how to add a setting.
The Terminal Settings Model (Microsoft.Terminal.Settings.Model) is responsible for (de)serializing and exposing settings.
INHERITABLE_SETTING macroThe INHERITABLE_SETTING macro can be used to implement inheritance for your new setting and store the setting in the settings model. It takes three parameters:
type: the type that the setting will be stored asname: the name of the variable for storagedefaultValue: the value to use if the user does not define the setting anywhereThis tutorial will add CloseOnExitMode CloseOnExit as a profile setting.
Profile.h, declare/define the setting:INHERITABLE_SETTING(CloseOnExitMode, CloseOnExit, CloseOnExitMode::Graceful)
Profile.idl, expose the setting via WinRT:Boolean HasCloseOnExit();
void ClearCloseOnExit();
CloseOnExitMode CloseOnExit;
Profile.cpp, add (de)serialization and copy logic:// 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);
TerminalSettingsSerializationHelpers.h add (de)serialization logic for the accepted values:// 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)
Follow the "adding a Profile setting" instructions above, but do it on the GlobalAppSettings files.
This tutorial will add the openSettings action.
KeyMapping.idl, declare the action:// Add the action to ShortcutAction
enum ShortcutAction
{
OpenSettings
}
ActionAndArgs.cpp, add serialization logic:// 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 },
// 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>
ActionArgs.idl, declare the arguments[default_interface] runtimeclass OpenSettingsArgs : IActionArgs
{
// this declares the "target" arg
SettingsTarget Target { get; };
};
ActionArgs.h, define the new runtime classstruct 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, define GenerateName(). This is used to automatically generate a name when it appears in the Command Palette.ActionAndArgs.cpp, add serialization logic:// ActionKeyNamesMap --> argParsers
{ ShortcutAction::OpenSettings, OpenSettingsArgs::FromJson },
Follow step 3 from the "adding an Action" instructions above, but modify the relevant ActionArgs files.
Now that the Terminal Settings Model is updated, Windows Terminal can read and write to the settings file. This section covers how to add functionality to your newly created setting.
App-level settings are settings that affect the frame of Windows Terminal. Generally, these tend to be global settings. The TerminalApp project is responsible for presenting the frame of Windows Terminal. A few files of interest include:
TerminalPage: XAML control responsible for the look and feel of Windows TerminalAppLogic: WinRT class responsible for window-related issues (i.e. the titlebar, focus mode, etc...)Both have access to a CascadiaSettings object, for you to read the loaded setting and update Windows Terminal appropriately.
Terminal-level settings are settings that affect a shell session. Generally, these tend to be profile settings. The TerminalApp project is responsible for packaging this settings from the Terminal Settings Model to the terminal instance. There are two kinds of settings here:
IControlSettings:
TerminalControl (a XAML control that hosts a shell session).TerminalControl project has access to these settings via a saved IControlSettings member.ICoreSettings:
TerminalCore (a lower level object that interacts with the text buffer).TerminalCore project has access to these settings via a saved ICoreSettings member.TerminalApp packages these settings into a TerminalSettings : IControlSettings, ICoreSettings object upon creating a new terminal instance. To do so, you must submit the following changes:
IControlSettings.idl or ICoreSettings.idl (whichever is relevant to your setting). If your setting is an enum setting, declare the enum here instead of in the TerminalSettingsModel project.TerminalSettings.h, declare/define the setting...// 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...
_ApplyProfileSettings for profile settings_ApplyGlobalSettings for global settingsbackgroundImageAlignment is stored as a ConvergedAlignment in the Terminal Settings Model, but converted into XAML's separate horizontal and vertical alignment enums for packaging.Actions are packaged as an ActionAndArgs object, then handled in TerminalApp. To add functionality for actions...
ShortcutActionDispatch files, dispatch an event when the action occurs...// 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 files, handle the event...// 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 vary based on the action you want to perform. A few useful helper functions include:
_GetFocusedTab(): retrieves the focused tab_GetActiveControl(): retrieves the active terminal control_GetTerminalTabImpl(): tries to cast the given tab as a TerminalTab (a tab that hosts a terminal instance)If the new setting supports enums, you need to expose a map of the enum and the respective value in the Terminal Settings Model's 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);
Find the page in the Settings UI that the new setting fits best in. In this example, we are adding LaunchMode.
Launch.idl, expose the bindable setting...// 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; };
Launch.h, declare the bindable enum setting...// 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);
Launch.cpp, populate these functions...// Constructor (after 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");
Resources.resw for Microsoft.Terminal.Settings.Editor, add the localized text to expose each enum value. Use the following format: <SettingGroup>_<SettingName><EnumValue>.ContentSettingGroup:
Globals for global settingsProfile for profile settingsSettingName:
LaunchMode for "launchMode")EnumValue:
Focus for "focus")Globals_LaunchModeFocus.ContentWhen adding a setting to the UI, make sure you follow the UWP design guidance.
Now, create a XAML control in the relevant XAML file. Use the following tips and tricks to style everything appropriately:
ContentPresenter adhering to the SettingContainerStyle styleSelectedItem to the relevant Current<Setting> (i.e. CurrentLaunchMode). Ensure it's a TwoWay bindingItemsSource to <Setting>List (i.e. LaunchModeList)Enum<ControlType>Template (i.e. EnumRadioButtonTemplate for radio buttons)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>
To add any localized text, add a x:Uid, and access the relevant property via the Resources.resw file. For example, Globals_LaunchMode.Header sets the header for this control. You can also set the tooltip text like this:
Globals_DefaultProfile.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip.
Continue to reference CommonResources.xaml for appropriate styling and wrap the control with a similar ContentPresenter. However, instead of binding to the Current<Setting> and <Setting>List, bind directly to the setting via the state. Binding a setting like altGrAliasing should look something like this:
<!--AltGr Aliasing-->
<ContentPresenter Style="{StaticResource SettingContainerStyle}">
<CheckBox x:Uid="Profile_AltGrAliasing"
IsChecked="{x:Bind State.Profile.AltGrAliasing, Mode=TwoWay}"
Style="{StaticResource CheckBoxSettingStyle}"/>
</ContentPresenter>
If you are specifically adding a Profile setting, in addition to the steps above, you need to make the setting observable by modifying the Profiles files...
// 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);
The ProfilePageNavigationState holds a ProfileViewModel, which wraps the Profile object from the Terminal Settings Model. The ProfileViewModel makes all of the profile settings observable.
Actions are not yet supported in the Settings UI.