2024-03-26 09:03:05 -07:00
|
|
|
// Copyright (c) Microsoft Corporation.
|
|
|
|
|
// Licensed under the MIT license.
|
|
|
|
|
|
|
|
|
|
#include "pch.h"
|
|
|
|
|
#include "TerminalPaneContent.h"
|
2024-03-29 12:48:58 +01:00
|
|
|
|
|
|
|
|
#include <mmsystem.h>
|
|
|
|
|
|
2025-08-19 09:35:27 -05:00
|
|
|
#include "TerminalSettingsCache.h"
|
2024-03-29 12:48:58 +01:00
|
|
|
#include "../../types/inc/utils.hpp"
|
2024-03-26 09:03:05 -07:00
|
|
|
|
|
|
|
|
#include "BellEventArgs.g.cpp"
|
2024-03-29 12:48:58 +01:00
|
|
|
#include "TerminalPaneContent.g.cpp"
|
2024-03-26 09:03:05 -07:00
|
|
|
|
|
|
|
|
using namespace winrt::Windows::Foundation;
|
|
|
|
|
using namespace winrt::Windows::UI::Xaml;
|
|
|
|
|
using namespace winrt::Microsoft::Terminal::Settings::Model;
|
|
|
|
|
using namespace winrt::Microsoft::Terminal::Control;
|
|
|
|
|
using namespace winrt::Microsoft::Terminal::TerminalConnection;
|
|
|
|
|
|
|
|
|
|
namespace winrt::TerminalApp::implementation
|
|
|
|
|
{
|
|
|
|
|
TerminalPaneContent::TerminalPaneContent(const winrt::Microsoft::Terminal::Settings::Model::Profile& profile,
|
2025-08-19 09:35:27 -05:00
|
|
|
const std::shared_ptr<TerminalSettingsCache>& cache,
|
2024-03-26 09:03:05 -07:00
|
|
|
const winrt::Microsoft::Terminal::Control::TermControl& control) :
|
|
|
|
|
_control{ control },
|
2024-04-05 07:45:21 -07:00
|
|
|
_cache{ cache },
|
2024-03-26 09:03:05 -07:00
|
|
|
_profile{ profile }
|
|
|
|
|
{
|
|
|
|
|
_setupControlEvents();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TerminalPaneContent::_setupControlEvents()
|
|
|
|
|
{
|
|
|
|
|
_controlEvents._ConnectionStateChanged = _control.ConnectionStateChanged(winrt::auto_revoke, { this, &TerminalPaneContent::_controlConnectionStateChangedHandler });
|
|
|
|
|
_controlEvents._WarningBell = _control.WarningBell(winrt::auto_revoke, { get_weak(), &TerminalPaneContent::_controlWarningBellHandler });
|
|
|
|
|
_controlEvents._CloseTerminalRequested = _control.CloseTerminalRequested(winrt::auto_revoke, { get_weak(), &TerminalPaneContent::_closeTerminalRequestedHandler });
|
|
|
|
|
_controlEvents._RestartTerminalRequested = _control.RestartTerminalRequested(winrt::auto_revoke, { get_weak(), &TerminalPaneContent::_restartTerminalRequestedHandler });
|
|
|
|
|
|
|
|
|
|
_controlEvents._TitleChanged = _control.TitleChanged(winrt::auto_revoke, { get_weak(), &TerminalPaneContent::_controlTitleChanged });
|
|
|
|
|
_controlEvents._TabColorChanged = _control.TabColorChanged(winrt::auto_revoke, { get_weak(), &TerminalPaneContent::_controlTabColorChanged });
|
|
|
|
|
_controlEvents._SetTaskbarProgress = _control.SetTaskbarProgress(winrt::auto_revoke, { get_weak(), &TerminalPaneContent::_controlSetTaskbarProgress });
|
|
|
|
|
_controlEvents._ReadOnlyChanged = _control.ReadOnlyChanged(winrt::auto_revoke, { get_weak(), &TerminalPaneContent::_controlReadOnlyChanged });
|
|
|
|
|
_controlEvents._FocusFollowMouseRequested = _control.FocusFollowMouseRequested(winrt::auto_revoke, { get_weak(), &TerminalPaneContent::_controlFocusFollowMouseRequested });
|
|
|
|
|
}
|
|
|
|
|
void TerminalPaneContent::_removeControlEvents()
|
|
|
|
|
{
|
|
|
|
|
_controlEvents = {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
winrt::Windows::UI::Xaml::FrameworkElement TerminalPaneContent::GetRoot()
|
|
|
|
|
{
|
|
|
|
|
return _control;
|
|
|
|
|
}
|
|
|
|
|
winrt::Microsoft::Terminal::Control::TermControl TerminalPaneContent::GetTermControl()
|
|
|
|
|
{
|
|
|
|
|
return _control;
|
|
|
|
|
}
|
|
|
|
|
winrt::Windows::Foundation::Size TerminalPaneContent::MinimumSize()
|
|
|
|
|
{
|
|
|
|
|
return _control.MinimumSize();
|
|
|
|
|
}
|
|
|
|
|
void TerminalPaneContent::Focus(winrt::Windows::UI::Xaml::FocusState reason)
|
|
|
|
|
{
|
|
|
|
|
_control.Focus(reason);
|
|
|
|
|
}
|
|
|
|
|
void TerminalPaneContent::Close()
|
|
|
|
|
{
|
2025-12-17 19:19:15 +01:00
|
|
|
// We deliberately remove the event handlers before closing the control.
|
|
|
|
|
// This is to prevent reentrancy issues, pointless callbacks, etc.
|
2024-03-26 09:03:05 -07:00
|
|
|
_removeControlEvents();
|
|
|
|
|
|
2025-12-17 19:19:15 +01:00
|
|
|
_control.Close();
|
2024-03-26 09:03:05 -07:00
|
|
|
}
|
|
|
|
|
|
2024-04-03 08:31:13 -07:00
|
|
|
winrt::hstring TerminalPaneContent::Icon() const
|
|
|
|
|
{
|
Rewrite media resource handling (relative path icons, web URLs) (#19143)
This pull request broadly rewrites how we handle all media resources in
the Terminal settings model.
## What is a media resource?
A media resource is any JSON property that refers to a file on disk,
including:
- `icon` on profile
- `backgroundImage` on profile (appearance)
- `pixelShaderPath` and `pixelShaderImagePath` on profile (appearance)
- `icon` on command and the new tab menu entries
The last two bullet points were newly discovered during the course of
this work.
## Description of Changes
In every place the settings model used to store a string for a media
path, it now stores an `IMediaResource`.
A media resource must be _resolved_ before it's used. When resolved, it
can report whether it is `Ok` (found, valid) and what the final
normalized path was.
This allows the settings model to apply some new behaviors.
One of those new behaviors is resolving media paths _relative to the
JSON file that referred to them._ This means fragments and user settings
can now contain _local_ images, pixel shaders and more and refer to them
by filename.
Relative path support requires us to track the path from which every
media resource "container" was read[^2]. For "big" objects like Profile,
we track it directly in the object and for each layer. This means that
fragments **updating** a profile pass their relative base path into the
mix. For some of the entries such as those in `newTabMenu`, we just wing
it (#19191). For everything that is recursively owned by a parent that
has a path (say each Command inside an ActionMap), we pass it in from
the parent during media resolution.
During resolution, we now track _exactly which layer_ an icon,
background image, or pixel shader path came from and read the "base
path" from only that layer. The base path is not inherited.
Another new behavior is in the handling of web and other URLs.
Canonical and a few other WSL distributors had to resort to web URLs for
icons because we did not support loading them from the package. Julia
tried to use `ms-appx://JuliaPackageNameHere/path/to/icon` for the same
reason. Neither was intended, and of the two the second _should_ have
worked but never could[^1].
For both `http(s?)` URLs and `ms-appx://` URLs which specify a package
name, we now strip everything except the filename. As an example...
If my fragment specifies `https://example.net/assets/foo.ico`, and my
fragment was loaded from `C:\Fragments`, Terminal will look *only* at
`C:\Fragments\foo.ico`.
This works today for Julia (they put their icon in the fragment folder
hoping that one day we would support this.) It will require some work
from existing WSL distributors.
I'm told that this is similar to how XML schema documents work.
Now, icons are special. They support _Emoji_ and _Segoe Icons_. This PR
adds an early pass to avoid resolving anything that looks like an
emoji.
This PR intentionally expands the heuristic definition of an emoji. It
used to only cover 1-2 code unit emoji, which prevented the use of any
emoji more complicated than "man in business suite levitating."
An icon path will now be considered an emoji or symbol icon if it is
composed of a single grapheme cluster (as measured by ICU.)
This is not perfect, as it errs on the side of allowing too many
things... but each of those things is technically a single grapheme
cluster and is a perfectly legal FontIcon ;)
Profile icons are _even more special_ than icons. They have an
additional fallback behavior which we had to preserve. When a profile
icon fails validation, or is expressly set to `null`, we fall back to
the EXE specified in the command line.
Because we do this fallback during resolution, _and the icon may be
inherited by any higher profile,_ we can only resolve it against the
commandline at the same level as the failed or nulled icon.
Therefore, if you specify `icon: null` in your `defaults` profile, it
will only ever resolve to `cmd.exe` for any profile that inherits it
(unless you change `defaults.commandline`).
This change expands support for the magic keywords `desktopWallpaper`
and `none` to all media paths (yes, even `pixelShaderPath`... but also,
`pixelShaderImagePath`!) It also expands support for _environment
variables_ to all of those places. Yes, we had like forty different
handlers for different types of string path. They are now uniform.
## Resource Validation
Media resources which are not found are "rejected". If a rejected
resource lives in _user_ settings, we will generate a warning and
display it.
In the future, we could detect this in the Settings UI and display a
warning inline.
## Surprises
I learned that `Windows.Foundation.Uri` parses file paths into `file://`
URIs, but does not offer you a way to get the original file path back
out. If you pass `C:\hello world`, _`Uri.Path`_ will return
`/C:/hello%20world`. I kid you not.
As a workaround, we bail out of URL handling if the `:` is too close to
the start (indicating an absolute file path).
## Testing
I added a narow test hook in the media resource resolver, which is
removed completely by link-time code generation. It is a real joy.
The test cases are all new and hopefully comprehensive.
Closes #19075
Closes #16295
Closes #10359 (except it doesn't support fonts)
Supersedes #16949 somewhat (`WT_SETTINGS_DIR`)
Refs #18679
Refs #19215 (future work)
Refs #19201 (future work)
Refs #19191 (future work)
[^1]: Handling a `ms-appx` path requires us to _add their package to our
dependency graph_ for the entire duration during which the resource will
be used. For us, that could be any time (like opening the command
palette for the first time!)
[^2]: We don't bother tracking where the defaults came from, because we
control everything about them.
2025-08-05 15:47:50 -05:00
|
|
|
return _profile.Icon().Resolved();
|
2024-04-03 08:31:13 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Windows::Foundation::IReference<winrt::Windows::UI::Color> TerminalPaneContent::TabColor() const noexcept
|
|
|
|
|
{
|
|
|
|
|
return _control.TabColor();
|
|
|
|
|
}
|
|
|
|
|
|
Add support for restoring non-terminal panes, and opening them with `splitPane`, `newTab` (#16914)
This changes `NewTabArgs`, `SplitPaneArgs`, and `NewWindowArgs` to
accept a `INewContentArgs`, rather than just a `NewTerminalArgs`. This
allows a couple things:
* Users can open arbitrary types of panes with the existing `splitPane`,
`newWindow` actions, just by passing `"type": "scartchpad"` (for
example). This is a lot more flexible than re-defining different
`"openScratchpad"`, `"openTasksPane"`, etc, etc actions for every kind
of pane.
* This allows us to use the existing machinery of session restore to
also restore non-terminal panes.
The `type` property was added to `newTab`, `splitPane`, `newWindow`.
When omitted, we still just treat the json as a blob of NewTerminalArgs.
There's not actually any other kinds of `INewContentArgs` in this PR
(other than the placeholder `GenericContentArgs`). In
[`dev/migrie/fhl/md-pane`](https://github.com/microsoft/terminal/compare/dev/migrie/f/tasks-pane...dev/migrie/fhl/md-pane),
I have a type of pane that would LOVE to add some args here. So that's
forward-thinking.
There's really just two stealth types of pane for now: `settings`, and
`scratchpad`. Those I DON'T have as constants or anything in this PR.
They probably should be? Though, I suspect around the time of the tasks
& MD panes, I'll come up with whatever structure I actually want them to
take.
### future considerations here
* In the future, this should allow extensions to say "I know how to host
`foo` content", for 3p content.
* The `wt` CLI args were not yet updated to also accept `--type` yet.
There's no reason we couldn't easily do that.
* I considered adding `ICanHasCommandline` to allow arbitrary content to
generate a `wt` commandline-serializable string. Punted on that for now.
## other PRs
* #16170
* #16171
* #16172
* #16895
* #16914 <-- you are here
Closes #17014
2024-04-05 12:29:27 -07:00
|
|
|
INewContentArgs TerminalPaneContent::GetNewTerminalArgs(const BuildStartupKind kind) const
|
2024-03-26 09:03:05 -07:00
|
|
|
{
|
|
|
|
|
NewTerminalArgs args{};
|
|
|
|
|
const auto& controlSettings = _control.Settings();
|
|
|
|
|
|
2025-07-09 15:42:23 -05:00
|
|
|
args.Profile(::Microsoft::Console::Utils::GuidToString(_profile.Guid()));
|
2024-03-26 09:03:05 -07:00
|
|
|
// If we know the user's working directory use it instead of the profile.
|
2026-03-30 20:10:26 +02:00
|
|
|
if (const auto dir = _control.WorkingDirectory(); ::Microsoft::Console::Utils::IsValidDirectory(dir.c_str()))
|
2024-03-26 09:03:05 -07:00
|
|
|
{
|
|
|
|
|
args.StartingDirectory(dir);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
args.StartingDirectory(controlSettings.StartingDirectory());
|
|
|
|
|
}
|
|
|
|
|
args.TabTitle(controlSettings.StartingTitle());
|
|
|
|
|
args.Commandline(controlSettings.Commandline());
|
|
|
|
|
args.SuppressApplicationTitle(controlSettings.SuppressApplicationTitle());
|
|
|
|
|
if (controlSettings.TabColor() || controlSettings.StartingTabColor())
|
|
|
|
|
{
|
|
|
|
|
til::color c;
|
|
|
|
|
// StartingTabColor is prioritized over other colors
|
|
|
|
|
if (const auto color = controlSettings.StartingTabColor())
|
|
|
|
|
{
|
|
|
|
|
c = til::color(color.Value());
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
c = til::color(controlSettings.TabColor().Value());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
args.TabColor(winrt::Windows::Foundation::IReference<winrt::Windows::UI::Color>{ static_cast<winrt::Windows::UI::Color>(c) });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO:GH#9800 - we used to be able to persist the color scheme that a
|
|
|
|
|
// TermControl was initialized with, by name. With the change to having the
|
Remove TerminalSettings from the TerminalSettingsModel project (#19262)
The idea with IControlSettings (and friends) was always that a consumer
of the terminal control could implement it in whatever way they pleased.
Windows Terminal (the application) was intended to be only one
consumer. It has a whole JSON settings model. Nobody wants to think
about JSON at the Terminal Control level. We could have an "adapter" in
TerminalApp, which spoke Terminal JSON Settings on one side and Terminal
Control on the other side.
That worked until we added the settings editor. The settings editor
needed to display a control, and that control's settings needed to be
based on the JSON settings. Oops. We took the expedient route of moving
the adapter into TerminalSettingsModel itself, and poking a bunch of
holes in it so that TerminalApp and TerminalSettingsEditor could tweak
it as needed.
Later, we doubled down on the control settings interface by having every
Terminal Control _make its own ControlSettings_ when we were going to do
the multi-process model. This reduced the number of IPC round trips for
every settings query to 0. Later we built color scheme previewing on top
of that--adding structs to carry color schemes and stuff which was
already in the Appearance config. Sheesh. Layers and layers and layers.
This pull request moves it back into its own library and strips it from
the surface of TerminalSettingsModel. It also deletes `ControlSettings`
and `struct CoreScheme`. That library is called
`TerminalSettingsAppAdapterLib`, and it contains a hidden WinRT
_implements_ type rather than a full-fledged activatable `runtimeclass`.
It also implements one-level inheritance on its own rather than using
IInheritable.
It adheres to the following principles:
- The control will never modify its settings in a way that is visible to
the control's consumer; therefore, none of the properties have setters
- The settings should never contain things of interest only to the
Application that the Application uses to communicate data _back to
itself_ (see `ProfileName`, removed in 68b723c and `KeyBindings`,
removed in fa09141). This generalizes to "we should never store stuff
in an unrelated object passed between layers solely for the purpose of
getting it back".
I made a few changes to the settings interface, including introducing a
new `ICoreScheme` interface that _only_ contains color scheme info. This
is designed to support the Preview/Set color scheme actions, which no
longer work by _app backing up the scheme and restoring it later._ All
of that machinery lives inside TermControl/ControlCore now.
`ICoreScheme` no longer supports `GetColorAtIndex`; you must read all 16
colors at the same time. I am not sorry. Every consumer did that
already, so now we have 15 fewer COM calls for every color scheme.
The new TerminalSettings is mostly consumed via
`com_ptr<TerminalSettings>`, so a bunch of `.` (projected) accesses had
to turn into `->` (com_ptr dereferencing) accesses.
I also realized, in the course of this work, that the old
TerminalSettings contained a partial hand-written reimplementation of
_every setting_ in `ControlProperties`. Every contributor had to add
every new setting to both places--why? I can't figure it out. I'm using
ControlProperties comprehensively now. I propagated any setting whose
default value was different from that in ControlProperties back to
ControlProperties.
This is part X in a series of pull requests that will remove all mention
of Microsoft.Terminal.Control and Microsoft.Terminal.Core from the
settings model. Once that is done, the settings model can consume _only_
the base WinRT types and build very early and test more easily.
Previewing is fun. I introduced a new place to stash an entire color
table on ControlCore, which we use to save the "active" colors while we
temporarily overwrite them. SetColorScheme is _also_ fun. We now have a
slot for overriding only the focused color scheme on ControlCore. It's
fine. It's clearer than "back up the focused appearance, overwrite the
focused appearance, create a child of the user's settings and apply the
color scheme to it, etc.".
There is a bug/design choice in color scheme overriding, which may or
may not matter: overlaying a color scheme on a terminal with an
unfocused appearance which _does not_ have its own color scheme will
result in the previously-deleted overridden focused color scheme peeking
through when the terminal is not focused.
I also got rid of our only in-product use of
`Terminal::CreateFromSettings` which required us to set `InitialRows`
and `InitialCols` on the incoming settings object (see core tenet 2).
Refs #19261
Refs #19314
Refs #19254
2025-09-03 14:01:36 -05:00
|
|
|
// control own its own copy of its settings, this wasn't possible anymore.
|
|
|
|
|
// It probably is once again possible, but Dustin doesn't know how to undo
|
|
|
|
|
// the damage done in the ControlSettings migration.
|
2024-03-26 09:03:05 -07:00
|
|
|
|
2024-03-29 12:48:58 +01:00
|
|
|
switch (kind)
|
2024-03-26 09:03:05 -07:00
|
|
|
{
|
2024-03-29 12:48:58 +01:00
|
|
|
case BuildStartupKind::Content:
|
|
|
|
|
case BuildStartupKind::MovePane:
|
|
|
|
|
// Only fill in the ContentId if absolutely needed. If you fill in a number
|
|
|
|
|
// here (even 0), we'll serialize that number, AND treat that action as an
|
|
|
|
|
// "attach existing" rather than a "create"
|
2024-03-26 09:03:05 -07:00
|
|
|
args.ContentId(_control.ContentId());
|
2024-03-29 12:48:58 +01:00
|
|
|
break;
|
2025-11-20 20:49:14 +01:00
|
|
|
case BuildStartupKind::Persist:
|
2024-03-29 12:48:58 +01:00
|
|
|
{
|
|
|
|
|
const auto connection = _control.Connection();
|
|
|
|
|
const auto id = connection ? connection.SessionId() : winrt::guid{};
|
|
|
|
|
if (id != winrt::guid{})
|
|
|
|
|
{
|
|
|
|
|
args.SessionId(id);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
break;
|
2024-03-26 09:03:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return args;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TerminalPaneContent::_controlTitleChanged(const IInspectable&, const IInspectable&)
|
|
|
|
|
{
|
|
|
|
|
TitleChanged.raise(*this, nullptr);
|
|
|
|
|
}
|
|
|
|
|
void TerminalPaneContent::_controlTabColorChanged(const IInspectable&, const IInspectable&)
|
|
|
|
|
{
|
|
|
|
|
TabColorChanged.raise(*this, nullptr);
|
|
|
|
|
}
|
|
|
|
|
void TerminalPaneContent::_controlSetTaskbarProgress(const IInspectable&, const IInspectable&)
|
|
|
|
|
{
|
|
|
|
|
TaskbarProgressChanged.raise(*this, nullptr);
|
|
|
|
|
}
|
|
|
|
|
void TerminalPaneContent::_controlReadOnlyChanged(const IInspectable&, const IInspectable&)
|
|
|
|
|
{
|
|
|
|
|
ReadOnlyChanged.raise(*this, nullptr);
|
|
|
|
|
}
|
|
|
|
|
void TerminalPaneContent::_controlFocusFollowMouseRequested(const IInspectable&, const IInspectable&)
|
|
|
|
|
{
|
|
|
|
|
FocusRequested.raise(*this, nullptr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Method Description:
|
|
|
|
|
// - Called when our attached control is closed. Triggers listeners to our close
|
|
|
|
|
// event, if we're a leaf pane.
|
|
|
|
|
// - If this was called, and we became a parent pane (due to work on another
|
|
|
|
|
// thread), this function will do nothing (allowing the control's new parent
|
|
|
|
|
// to handle the event instead).
|
|
|
|
|
// Arguments:
|
|
|
|
|
// - <none>
|
|
|
|
|
// Return Value:
|
|
|
|
|
// - <none>
|
2024-08-23 21:19:42 +02:00
|
|
|
safe_void_coroutine TerminalPaneContent::_controlConnectionStateChangedHandler(const winrt::Windows::Foundation::IInspectable& sender,
|
|
|
|
|
const winrt::Windows::Foundation::IInspectable& args)
|
2024-03-26 09:03:05 -07:00
|
|
|
{
|
|
|
|
|
ConnectionStateChanged.raise(sender, args);
|
|
|
|
|
auto newConnectionState = ConnectionState::Closed;
|
|
|
|
|
if (const auto coreState = sender.try_as<ICoreState>())
|
|
|
|
|
{
|
|
|
|
|
newConnectionState = coreState.ConnectionState();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const auto previousConnectionState = std::exchange(_connectionState, newConnectionState);
|
|
|
|
|
if (newConnectionState < ConnectionState::Closed)
|
|
|
|
|
{
|
|
|
|
|
// Pane doesn't care if the connection isn't entering a terminal state.
|
|
|
|
|
co_return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const auto weakThis = get_weak();
|
|
|
|
|
co_await wil::resume_foreground(_control.Dispatcher());
|
|
|
|
|
const auto strongThis = weakThis.get();
|
|
|
|
|
if (!strongThis)
|
|
|
|
|
{
|
|
|
|
|
co_return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// It's possible that this event handler started being executed, scheduled
|
|
|
|
|
// on the UI thread, another child got created. So our control is
|
|
|
|
|
// actually no longer _our_ control, and instead could be a descendant.
|
|
|
|
|
//
|
|
|
|
|
// When the control's new Pane takes ownership of the control, the new
|
|
|
|
|
// parent will register its own event handler. That event handler will get
|
|
|
|
|
// fired after this handler returns, and will properly cleanup state.
|
|
|
|
|
|
|
|
|
|
if (previousConnectionState < ConnectionState::Connected && newConnectionState >= ConnectionState::Failed)
|
|
|
|
|
{
|
|
|
|
|
// A failure to complete the connection (before it has _connected_) is not covered by "closeOnExit".
|
|
|
|
|
// This is to prevent a misconfiguration (closeOnExit: always, startingDirectory: garbage) resulting
|
|
|
|
|
// in Terminal flashing open and immediately closed.
|
|
|
|
|
co_return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (_profile)
|
|
|
|
|
{
|
|
|
|
|
const auto mode = _profile.CloseOnExit();
|
Fix two panes being closed when just one is (#17358)
#17333 introduced a regression: While it fixes a recursion *into*
`Pane::Close()` that doesn't fix the recursion outside of it.
In this case, `Close()` raises the `Closed` event which results
in another tab being closed because it's bound to `_RemoveTab`.
The recursion is now obvious, because I made the entire process
synchronous. Previously, it would (hopefully) just be scheduled
after the pane and its content are already gone.
The issue can be fixed by moving the recursion check from
`Pane::Close()` to `TerminalTab::Shutdown()` but I felt like
it would better to fix the issue a bit more thoroughly.
`IPaneContent` can raise a `CloseRequested` event to indicate it wants
to be closed. However, that also contained recursion, because the
content would call its own `Close()` to raise the event, which the
tab catches, calls `Close()` on the `Pane` which calls `Close()` on
the content which raises the event again and so on. That's what was
fixed in #17333 among others. We can do this better by not raising
the event from `IPaneContent::Close()`. Instead, that method will now
be exclusively called by `Pane`. The `CloseRequested` event will now
truly be just a request and nothing more. Furthermore, the ownership
of the event handling was moved from the `TerminalTab` to the `Pane`.
To make all of this a bit simpler and more robust, two new methods
were added to `Pane`: `_takePaneContent` and `_setPaneContent`.
These methods ensure that `Close()` is called on the content,
that the event handlers are always added and revoked
and that the ownership transfers cleanly between panes.
## Validation Steps Performed
* Open 3 tabs, close the middle one ✅
* Open 3 vertical panes, close the middle one ✅
* Drag tabs with multiple panes between windows ✅
2024-06-04 20:58:37 +02:00
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
// This one is obvious: If the user asked for "always" we do just that.
|
|
|
|
|
(mode == CloseOnExitMode::Always) ||
|
|
|
|
|
// Otherwise, and unless the user asked for the opposite of "always",
|
|
|
|
|
// close the pane when the connection closed gracefully (not failed).
|
|
|
|
|
(mode != CloseOnExitMode::Never && newConnectionState == ConnectionState::Closed) ||
|
|
|
|
|
// However, defterm handoff can result in Windows Terminal randomly opening which may be annoying,
|
|
|
|
|
// so by default we should at least always close the pane, even if the command failed.
|
|
|
|
|
// See GH #13325 for discussion.
|
|
|
|
|
(mode == CloseOnExitMode::Automatic && _isDefTermSession))
|
2024-03-26 09:03:05 -07:00
|
|
|
{
|
Fix two panes being closed when just one is (#17358)
#17333 introduced a regression: While it fixes a recursion *into*
`Pane::Close()` that doesn't fix the recursion outside of it.
In this case, `Close()` raises the `Closed` event which results
in another tab being closed because it's bound to `_RemoveTab`.
The recursion is now obvious, because I made the entire process
synchronous. Previously, it would (hopefully) just be scheduled
after the pane and its content are already gone.
The issue can be fixed by moving the recursion check from
`Pane::Close()` to `TerminalTab::Shutdown()` but I felt like
it would better to fix the issue a bit more thoroughly.
`IPaneContent` can raise a `CloseRequested` event to indicate it wants
to be closed. However, that also contained recursion, because the
content would call its own `Close()` to raise the event, which the
tab catches, calls `Close()` on the `Pane` which calls `Close()` on
the content which raises the event again and so on. That's what was
fixed in #17333 among others. We can do this better by not raising
the event from `IPaneContent::Close()`. Instead, that method will now
be exclusively called by `Pane`. The `CloseRequested` event will now
truly be just a request and nothing more. Furthermore, the ownership
of the event handling was moved from the `TerminalTab` to the `Pane`.
To make all of this a bit simpler and more robust, two new methods
were added to `Pane`: `_takePaneContent` and `_setPaneContent`.
These methods ensure that `Close()` is called on the content,
that the event handlers are always added and revoked
and that the ownership transfers cleanly between panes.
## Validation Steps Performed
* Open 3 tabs, close the middle one ✅
* Open 3 vertical panes, close the middle one ✅
* Drag tabs with multiple panes between windows ✅
2024-06-04 20:58:37 +02:00
|
|
|
CloseRequested.raise(nullptr, nullptr);
|
2024-03-26 09:03:05 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Method Description:
|
|
|
|
|
// - Plays a warning note when triggered by the BEL control character,
|
|
|
|
|
// using the sound configured for the "Critical Stop" system event.`
|
|
|
|
|
// This matches the behavior of the Windows Console host.
|
|
|
|
|
// - Will also flash the taskbar if the bellStyle setting for this profile
|
|
|
|
|
// has the 'visual' flag set
|
|
|
|
|
// Arguments:
|
|
|
|
|
// - <unused>
|
|
|
|
|
void TerminalPaneContent::_controlWarningBellHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/,
|
|
|
|
|
const winrt::Windows::Foundation::IInspectable& /*eventArgs*/)
|
|
|
|
|
{
|
|
|
|
|
if (_profile)
|
|
|
|
|
{
|
|
|
|
|
// We don't want to do anything if nothing is set, so check for that first
|
|
|
|
|
if (static_cast<int>(_profile.BellStyle()) != 0)
|
|
|
|
|
{
|
|
|
|
|
if (WI_IsFlagSet(_profile.BellStyle(), winrt::Microsoft::Terminal::Settings::Model::BellStyle::Audible))
|
|
|
|
|
{
|
|
|
|
|
// Audible is set, play the sound
|
|
|
|
|
auto sounds{ _profile.BellSound() };
|
|
|
|
|
if (sounds && sounds.Size() > 0)
|
|
|
|
|
{
|
2026-05-16 00:21:01 +05:30
|
|
|
// Sound paths are resolved and validated by CascadiaSettings
|
|
|
|
|
// before we reach this point.
|
|
|
|
|
auto soundPath{ sounds.GetAt(rand() % sounds.Size()).Resolved() };
|
|
|
|
|
PlaySoundW(soundPath.c_str(), nullptr, SND_FILENAME | SND_ASYNC | SND_SENTRY | SND_NODEFAULT);
|
2024-03-26 09:03:05 -07:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2026-05-16 00:21:01 +05:30
|
|
|
const auto soundAlias = reinterpret_cast<LPCWSTR>(SND_ALIAS_SYSTEMHAND);
|
|
|
|
|
PlaySoundW(soundAlias, nullptr, SND_ALIAS_ID | SND_ASYNC | SND_SENTRY);
|
2024-03-26 09:03:05 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (WI_IsFlagSet(_profile.BellStyle(), winrt::Microsoft::Terminal::Settings::Model::BellStyle::Window))
|
|
|
|
|
{
|
|
|
|
|
_control.BellLightOn();
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-04 10:38:17 -07:00
|
|
|
// raise the event with the bool values corresponding to the taskbar and notification flags
|
2024-03-26 09:03:05 -07:00
|
|
|
BellRequested.raise(*this,
|
2026-05-04 10:38:17 -07:00
|
|
|
*winrt::make_self<TerminalApp::implementation::BellEventArgs>(
|
|
|
|
|
WI_IsFlagSet(_profile.BellStyle(), BellStyle::Taskbar),
|
|
|
|
|
WI_IsFlagSet(_profile.BellStyle(), BellStyle::Notification)));
|
2024-03-26 09:03:05 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TerminalPaneContent::_closeTerminalRequestedHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/,
|
|
|
|
|
const winrt::Windows::Foundation::IInspectable& /*args*/)
|
|
|
|
|
{
|
Fix two panes being closed when just one is (#17358)
#17333 introduced a regression: While it fixes a recursion *into*
`Pane::Close()` that doesn't fix the recursion outside of it.
In this case, `Close()` raises the `Closed` event which results
in another tab being closed because it's bound to `_RemoveTab`.
The recursion is now obvious, because I made the entire process
synchronous. Previously, it would (hopefully) just be scheduled
after the pane and its content are already gone.
The issue can be fixed by moving the recursion check from
`Pane::Close()` to `TerminalTab::Shutdown()` but I felt like
it would better to fix the issue a bit more thoroughly.
`IPaneContent` can raise a `CloseRequested` event to indicate it wants
to be closed. However, that also contained recursion, because the
content would call its own `Close()` to raise the event, which the
tab catches, calls `Close()` on the `Pane` which calls `Close()` on
the content which raises the event again and so on. That's what was
fixed in #17333 among others. We can do this better by not raising
the event from `IPaneContent::Close()`. Instead, that method will now
be exclusively called by `Pane`. The `CloseRequested` event will now
truly be just a request and nothing more. Furthermore, the ownership
of the event handling was moved from the `TerminalTab` to the `Pane`.
To make all of this a bit simpler and more robust, two new methods
were added to `Pane`: `_takePaneContent` and `_setPaneContent`.
These methods ensure that `Close()` is called on the content,
that the event handlers are always added and revoked
and that the ownership transfers cleanly between panes.
## Validation Steps Performed
* Open 3 tabs, close the middle one ✅
* Open 3 vertical panes, close the middle one ✅
* Drag tabs with multiple panes between windows ✅
2024-06-04 20:58:37 +02:00
|
|
|
CloseRequested.raise(nullptr, nullptr);
|
2024-03-26 09:03:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TerminalPaneContent::_restartTerminalRequestedHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/,
|
|
|
|
|
const winrt::Windows::Foundation::IInspectable& /*args*/)
|
|
|
|
|
{
|
|
|
|
|
RestartTerminalRequested.raise(*this, nullptr);
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-05 16:18:43 -05:00
|
|
|
void TerminalPaneContent::UpdateSettings(const CascadiaSettings& settings)
|
2024-04-03 08:31:13 -07:00
|
|
|
{
|
2025-08-05 16:18:43 -05:00
|
|
|
// Reload our profile from the settings model to propagate bell mode, icon, and close on exit mode (anything that uses _profile).
|
|
|
|
|
const auto profile{ settings.FindProfile(_profile.Guid()) };
|
|
|
|
|
_profile = profile ? profile : settings.ProfileDefaults();
|
|
|
|
|
|
Remove TerminalSettings from the TerminalSettingsModel project (#19262)
The idea with IControlSettings (and friends) was always that a consumer
of the terminal control could implement it in whatever way they pleased.
Windows Terminal (the application) was intended to be only one
consumer. It has a whole JSON settings model. Nobody wants to think
about JSON at the Terminal Control level. We could have an "adapter" in
TerminalApp, which spoke Terminal JSON Settings on one side and Terminal
Control on the other side.
That worked until we added the settings editor. The settings editor
needed to display a control, and that control's settings needed to be
based on the JSON settings. Oops. We took the expedient route of moving
the adapter into TerminalSettingsModel itself, and poking a bunch of
holes in it so that TerminalApp and TerminalSettingsEditor could tweak
it as needed.
Later, we doubled down on the control settings interface by having every
Terminal Control _make its own ControlSettings_ when we were going to do
the multi-process model. This reduced the number of IPC round trips for
every settings query to 0. Later we built color scheme previewing on top
of that--adding structs to carry color schemes and stuff which was
already in the Appearance config. Sheesh. Layers and layers and layers.
This pull request moves it back into its own library and strips it from
the surface of TerminalSettingsModel. It also deletes `ControlSettings`
and `struct CoreScheme`. That library is called
`TerminalSettingsAppAdapterLib`, and it contains a hidden WinRT
_implements_ type rather than a full-fledged activatable `runtimeclass`.
It also implements one-level inheritance on its own rather than using
IInheritable.
It adheres to the following principles:
- The control will never modify its settings in a way that is visible to
the control's consumer; therefore, none of the properties have setters
- The settings should never contain things of interest only to the
Application that the Application uses to communicate data _back to
itself_ (see `ProfileName`, removed in 68b723c and `KeyBindings`,
removed in fa09141). This generalizes to "we should never store stuff
in an unrelated object passed between layers solely for the purpose of
getting it back".
I made a few changes to the settings interface, including introducing a
new `ICoreScheme` interface that _only_ contains color scheme info. This
is designed to support the Preview/Set color scheme actions, which no
longer work by _app backing up the scheme and restoring it later._ All
of that machinery lives inside TermControl/ControlCore now.
`ICoreScheme` no longer supports `GetColorAtIndex`; you must read all 16
colors at the same time. I am not sorry. Every consumer did that
already, so now we have 15 fewer COM calls for every color scheme.
The new TerminalSettings is mostly consumed via
`com_ptr<TerminalSettings>`, so a bunch of `.` (projected) accesses had
to turn into `->` (com_ptr dereferencing) accesses.
I also realized, in the course of this work, that the old
TerminalSettings contained a partial hand-written reimplementation of
_every setting_ in `ControlProperties`. Every contributor had to add
every new setting to both places--why? I can't figure it out. I'm using
ControlProperties comprehensively now. I propagated any setting whose
default value was different from that in ControlProperties back to
ControlProperties.
This is part X in a series of pull requests that will remove all mention
of Microsoft.Terminal.Control and Microsoft.Terminal.Core from the
settings model. Once that is done, the settings model can consume _only_
the base WinRT types and build very early and test more easily.
Previewing is fun. I introduced a new place to stash an entire color
table on ControlCore, which we use to save the "active" colors while we
temporarily overwrite them. SetColorScheme is _also_ fun. We now have a
slot for overriding only the focused color scheme on ControlCore. It's
fine. It's clearer than "back up the focused appearance, overwrite the
focused appearance, create a child of the user's settings and apply the
color scheme to it, etc.".
There is a bug/design choice in color scheme overriding, which may or
may not matter: overlaying a color scheme on a terminal with an
unfocused appearance which _does not_ have its own color scheme will
result in the previously-deleted overridden focused color scheme peeking
through when the terminal is not focused.
I also got rid of our only in-product use of
`Terminal::CreateFromSettings` which required us to set `InitialRows`
and `InitialCols` on the incoming settings object (see core tenet 2).
Refs #19261
Refs #19314
Refs #19254
2025-09-03 14:01:36 -05:00
|
|
|
if (const auto settings{ _cache->TryLookup(_profile) })
|
2024-04-03 08:31:13 -07:00
|
|
|
{
|
Remove TerminalSettings from the TerminalSettingsModel project (#19262)
The idea with IControlSettings (and friends) was always that a consumer
of the terminal control could implement it in whatever way they pleased.
Windows Terminal (the application) was intended to be only one
consumer. It has a whole JSON settings model. Nobody wants to think
about JSON at the Terminal Control level. We could have an "adapter" in
TerminalApp, which spoke Terminal JSON Settings on one side and Terminal
Control on the other side.
That worked until we added the settings editor. The settings editor
needed to display a control, and that control's settings needed to be
based on the JSON settings. Oops. We took the expedient route of moving
the adapter into TerminalSettingsModel itself, and poking a bunch of
holes in it so that TerminalApp and TerminalSettingsEditor could tweak
it as needed.
Later, we doubled down on the control settings interface by having every
Terminal Control _make its own ControlSettings_ when we were going to do
the multi-process model. This reduced the number of IPC round trips for
every settings query to 0. Later we built color scheme previewing on top
of that--adding structs to carry color schemes and stuff which was
already in the Appearance config. Sheesh. Layers and layers and layers.
This pull request moves it back into its own library and strips it from
the surface of TerminalSettingsModel. It also deletes `ControlSettings`
and `struct CoreScheme`. That library is called
`TerminalSettingsAppAdapterLib`, and it contains a hidden WinRT
_implements_ type rather than a full-fledged activatable `runtimeclass`.
It also implements one-level inheritance on its own rather than using
IInheritable.
It adheres to the following principles:
- The control will never modify its settings in a way that is visible to
the control's consumer; therefore, none of the properties have setters
- The settings should never contain things of interest only to the
Application that the Application uses to communicate data _back to
itself_ (see `ProfileName`, removed in 68b723c and `KeyBindings`,
removed in fa09141). This generalizes to "we should never store stuff
in an unrelated object passed between layers solely for the purpose of
getting it back".
I made a few changes to the settings interface, including introducing a
new `ICoreScheme` interface that _only_ contains color scheme info. This
is designed to support the Preview/Set color scheme actions, which no
longer work by _app backing up the scheme and restoring it later._ All
of that machinery lives inside TermControl/ControlCore now.
`ICoreScheme` no longer supports `GetColorAtIndex`; you must read all 16
colors at the same time. I am not sorry. Every consumer did that
already, so now we have 15 fewer COM calls for every color scheme.
The new TerminalSettings is mostly consumed via
`com_ptr<TerminalSettings>`, so a bunch of `.` (projected) accesses had
to turn into `->` (com_ptr dereferencing) accesses.
I also realized, in the course of this work, that the old
TerminalSettings contained a partial hand-written reimplementation of
_every setting_ in `ControlProperties`. Every contributor had to add
every new setting to both places--why? I can't figure it out. I'm using
ControlProperties comprehensively now. I propagated any setting whose
default value was different from that in ControlProperties back to
ControlProperties.
This is part X in a series of pull requests that will remove all mention
of Microsoft.Terminal.Control and Microsoft.Terminal.Core from the
settings model. Once that is done, the settings model can consume _only_
the base WinRT types and build very early and test more easily.
Previewing is fun. I introduced a new place to stash an entire color
table on ControlCore, which we use to save the "active" colors while we
temporarily overwrite them. SetColorScheme is _also_ fun. We now have a
slot for overriding only the focused color scheme on ControlCore. It's
fine. It's clearer than "back up the focused appearance, overwrite the
focused appearance, create a child of the user's settings and apply the
color scheme to it, etc.".
There is a bug/design choice in color scheme overriding, which may or
may not matter: overlaying a color scheme on a terminal with an
unfocused appearance which _does not_ have its own color scheme will
result in the previously-deleted overridden focused color scheme peeking
through when the terminal is not focused.
I also got rid of our only in-product use of
`Terminal::CreateFromSettings` which required us to set `InitialRows`
and `InitialCols` on the incoming settings object (see core tenet 2).
Refs #19261
Refs #19314
Refs #19254
2025-09-03 14:01:36 -05:00
|
|
|
_control.UpdateControlSettings(settings->DefaultSettings(), settings->UnfocusedSettings());
|
2024-04-03 08:31:13 -07:00
|
|
|
}
|
2024-03-26 09:03:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Method Description:
|
|
|
|
|
// - Should be called when this pane is created via a default terminal handoff
|
|
|
|
|
// - Finalizes our configuration given the information that we have been
|
|
|
|
|
// created via default handoff
|
|
|
|
|
void TerminalPaneContent::MarkAsDefterm()
|
|
|
|
|
{
|
|
|
|
|
_isDefTermSession = true;
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-03 08:31:13 -07:00
|
|
|
winrt::Windows::UI::Xaml::Media::Brush TerminalPaneContent::BackgroundBrush()
|
|
|
|
|
{
|
|
|
|
|
return _control.BackgroundBrush();
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-26 09:03:05 -07:00
|
|
|
float TerminalPaneContent::SnapDownToGrid(const TerminalApp::PaneSnapDirection direction, const float sizeToSnap)
|
|
|
|
|
{
|
|
|
|
|
return _control.SnapDimensionToGrid(direction == PaneSnapDirection::Width, sizeToSnap);
|
|
|
|
|
}
|
|
|
|
|
Windows::Foundation::Size TerminalPaneContent::GridUnitSize()
|
|
|
|
|
{
|
|
|
|
|
return _control.CharacterDimensions();
|
|
|
|
|
}
|
|
|
|
|
}
|