mirror of
https://github.com/microsoft/terminal.git
synced 2025-12-10 00:48:23 -06:00
Implement ConnectionState and closeOnExit=graceful/always/never (#3623)
This pull request implements the new `ITerminalConnection::ConnectionState` interface (enum, event) and connects it through TerminalControl to Pane, Tab and App as specified in #2039. It does so to implement `closeOnExit` = `graceful` in addition to the other two normal CoE types. It also: * exposes the singleton `CascadiaSettings` through a function that looks it up by using the current Xaml application's `AppLogic`. * In so doing, we've broken up the weird runaround where App tells TerminalSettings to CloseOnExit and then later another part of App _asks TerminalControl_ to tell it what TerminalSettings said App told it earlier. `:crazy_eyes:` * wires up a bunch of connection state points to `AzureConnection`. This required moving the Azure connection's state machine to use another enum name (oops). * ships a helper class for managing connection state transitions. * contains a bunch of template magic. * introduces `WINRT_CALLBACK`, a oneshot callback like `TYPED_EVENT`. * replaces a bunch of disparate `_connecting` and `_closing` members with just one uberstate. * updates the JSON schema and defaults to prefer closeOnExit: graceful * updates all relevant documentation Specified in #2039 Fixes #2563 Co-authored-by: mcpiroman <38111589+mcpiroman@users.noreply.github.com>
This commit is contained in:
parent
983f9b5a47
commit
901a1e1a09
@ -28,7 +28,7 @@ Properties listed below are specific to each unique profile.
|
||||
| `backgroundImageAlignment` | Optional | String | `center` | Sets how the background image aligns to the boundaries of the window. Possible values: `"center"`, `"left"`, `"top"`, `"right"`, `"bottom"`, `"topLeft"`, `"topRight"`, `"bottomLeft"`, `"bottomRight"` |
|
||||
| `backgroundImageOpacity` | Optional | Number | `1.0` | Sets the transparency of the background image. Accepts floating point values from 0-1. |
|
||||
| `backgroundImageStretchMode` | Optional | String | `uniformToFill` | Sets how the background image is resized to fill the window. Possible values: `"none"`, `"fill"`, `"uniform"`, `"uniformToFill"` |
|
||||
| `closeOnExit` | Optional | Boolean | `true` | When set to `true`, the selected tab closes when `exit` is typed. When set to `false`, the tab will remain open when `exit` is typed. |
|
||||
| `closeOnExit` | Optional | String | `graceful` | Sets how the profile reacts to termination or failure to launch. Possible values: `"graceful"` (close when `exit` is typed or the process exits normally), `"always"` (always close) and `"never"` (never close). `true` and `false` are accepted as synonyms for `"graceful"` and `"never"` respectively. |
|
||||
| `colorScheme` | Optional | String | `Campbell` | Name of the terminal color scheme to use. Color schemes are defined under `schemes`. |
|
||||
| `colorTable` | Optional | Array[String] | | Array of colors used in the profile if `colorscheme` is not set. Array follows the format defined in `schemes`. |
|
||||
| `commandline` | Optional | String | | Executable used in the profile. |
|
||||
|
||||
@ -327,9 +327,21 @@
|
||||
"type": "string"
|
||||
},
|
||||
"closeOnExit": {
|
||||
"default": true,
|
||||
"description": "When set to true (default), the selected tab closes when the connected application exits. When set to false, the tab will remain open when the connected application exits.",
|
||||
"type": "boolean"
|
||||
"default": "graceful",
|
||||
"description": "Sets how the profile reacts to termination or failure to launch. Possible values: \"graceful\" (close when exit is typed or the process exits normally), \"always\" (always close) and \"never\" (never close). true and false are accepted as synonyms for \"graceful\" and \"never\" respectively.",
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": [
|
||||
"never",
|
||||
"graceful",
|
||||
"always"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "boolean"
|
||||
}
|
||||
]
|
||||
},
|
||||
"colorScheme": {
|
||||
"default": "Campbell",
|
||||
|
||||
@ -55,6 +55,9 @@ namespace TerminalAppLocalTests
|
||||
TEST_METHOD(TestProfileIconWithEnvVar);
|
||||
TEST_METHOD(TestProfileBackgroundImageWithEnvVar);
|
||||
|
||||
TEST_METHOD(TestCloseOnExitParsing);
|
||||
TEST_METHOD(TestCloseOnExitCompatibilityShim);
|
||||
|
||||
TEST_CLASS_SETUP(ClassSetup)
|
||||
{
|
||||
InitializeJsonReader();
|
||||
@ -1414,4 +1417,67 @@ namespace TerminalAppLocalTests
|
||||
auto terminalSettings = settings._profiles[0].CreateTerminalSettings(globalSettings.GetColorSchemes());
|
||||
VERIFY_ARE_EQUAL(expectedPath, terminalSettings.BackgroundImage());
|
||||
}
|
||||
void SettingsTests::TestCloseOnExitParsing()
|
||||
{
|
||||
const std::string settingsJson{ R"(
|
||||
{
|
||||
"profiles": [
|
||||
{
|
||||
"name": "profile0",
|
||||
"closeOnExit": "graceful"
|
||||
},
|
||||
{
|
||||
"name": "profile1",
|
||||
"closeOnExit": "always"
|
||||
},
|
||||
{
|
||||
"name": "profile2",
|
||||
"closeOnExit": "never"
|
||||
},
|
||||
{
|
||||
"name": "profile3",
|
||||
"closeOnExit": null
|
||||
},
|
||||
{
|
||||
"name": "profile4",
|
||||
"closeOnExit": { "clearly": "not a string" }
|
||||
}
|
||||
]
|
||||
})" };
|
||||
|
||||
VerifyParseSucceeded(settingsJson);
|
||||
CascadiaSettings settings{};
|
||||
settings._ParseJsonString(settingsJson, false);
|
||||
settings.LayerJson(settings._userSettings);
|
||||
VERIFY_ARE_EQUAL(CloseOnExitMode::Graceful, settings._profiles[0].GetCloseOnExitMode());
|
||||
VERIFY_ARE_EQUAL(CloseOnExitMode::Always, settings._profiles[1].GetCloseOnExitMode());
|
||||
VERIFY_ARE_EQUAL(CloseOnExitMode::Never, settings._profiles[2].GetCloseOnExitMode());
|
||||
|
||||
// Unknown modes parse as "Graceful"
|
||||
VERIFY_ARE_EQUAL(CloseOnExitMode::Graceful, settings._profiles[3].GetCloseOnExitMode());
|
||||
VERIFY_ARE_EQUAL(CloseOnExitMode::Graceful, settings._profiles[4].GetCloseOnExitMode());
|
||||
}
|
||||
void SettingsTests::TestCloseOnExitCompatibilityShim()
|
||||
{
|
||||
const std::string settingsJson{ R"(
|
||||
{
|
||||
"profiles": [
|
||||
{
|
||||
"name": "profile0",
|
||||
"closeOnExit": true
|
||||
},
|
||||
{
|
||||
"name": "profile1",
|
||||
"closeOnExit": false
|
||||
}
|
||||
]
|
||||
})" };
|
||||
|
||||
VerifyParseSucceeded(settingsJson);
|
||||
CascadiaSettings settings{};
|
||||
settings._ParseJsonString(settingsJson, false);
|
||||
settings.LayerJson(settings._userSettings);
|
||||
VERIFY_ARE_EQUAL(CloseOnExitMode::Graceful, settings._profiles[0].GetCloseOnExitMode());
|
||||
VERIFY_ARE_EQUAL(CloseOnExitMode::Never, settings._profiles[1].GetCloseOnExitMode());
|
||||
}
|
||||
}
|
||||
|
||||
@ -592,6 +592,13 @@ namespace winrt::TerminalApp::implementation
|
||||
});
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Returns a pointer to the global shared settings.
|
||||
[[nodiscard]] std::shared_ptr<::TerminalApp::CascadiaSettings> AppLogic::GetSettings() const noexcept
|
||||
{
|
||||
return _settings;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Update the current theme of the application. This will trigger our
|
||||
// RequestedThemeChanged event, to have our host change the theme of the
|
||||
|
||||
@ -29,6 +29,7 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
void Create();
|
||||
void LoadSettings();
|
||||
[[nodiscard]] std::shared_ptr<::TerminalApp::CascadiaSettings> GetSettings() const noexcept;
|
||||
|
||||
Windows::Foundation::Point GetLaunchDimensions(uint32_t dpi);
|
||||
winrt::Windows::Foundation::Point GetLaunchInitialPositions(int32_t defaultInitialX, int32_t defaultInitialY);
|
||||
|
||||
@ -39,7 +39,6 @@ std::vector<TerminalApp::Profile> AzureCloudShellGenerator::GenerateProfiles()
|
||||
azureCloudShellProfile.SetColorScheme({ L"Vintage" });
|
||||
azureCloudShellProfile.SetAcrylicOpacity(0.6);
|
||||
azureCloudShellProfile.SetUseAcrylic(true);
|
||||
azureCloudShellProfile.SetCloseOnExit(false);
|
||||
azureCloudShellProfile.SetConnectionType(AzureConnectionType);
|
||||
profiles.emplace_back(azureCloudShellProfile);
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
#include "CascadiaSettings.h"
|
||||
#include "../../types/inc/utils.hpp"
|
||||
#include "../../inc/DefaultSettings.h"
|
||||
#include "AppLogic.h"
|
||||
#include "Utils.h"
|
||||
|
||||
#include "PowershellCoreProfileGenerator.h"
|
||||
@ -26,6 +27,21 @@ static constexpr std::wstring_view PACKAGED_PROFILE_ICON_PATH{ L"ms-appx:///Prof
|
||||
static constexpr std::wstring_view PACKAGED_PROFILE_ICON_EXTENSION{ L".png" };
|
||||
static constexpr std::wstring_view DEFAULT_LINUX_ICON_GUID{ L"{9acb9455-ca41-5af7-950f-6bca1bc9722f}" };
|
||||
|
||||
// Method Description:
|
||||
// - Returns the settings currently in use by the entire Terminal application.
|
||||
// Throws:
|
||||
// - HR E_INVALIDARG if the app isn't up and running.
|
||||
const CascadiaSettings& CascadiaSettings::GetCurrentAppSettings()
|
||||
{
|
||||
auto currentXamlApp{ winrt::Windows::UI::Xaml::Application::Current().as<winrt::TerminalApp::App>() };
|
||||
THROW_HR_IF_NULL(E_INVALIDARG, currentXamlApp);
|
||||
|
||||
auto appLogic = winrt::get_self<winrt::TerminalApp::implementation::AppLogic>(currentXamlApp.Logic());
|
||||
THROW_HR_IF_NULL(E_INVALIDARG, appLogic);
|
||||
|
||||
return *(appLogic->GetSettings());
|
||||
}
|
||||
|
||||
CascadiaSettings::CascadiaSettings() :
|
||||
CascadiaSettings(true)
|
||||
{
|
||||
|
||||
@ -49,6 +49,8 @@ public:
|
||||
static std::unique_ptr<CascadiaSettings> LoadDefaults();
|
||||
static std::unique_ptr<CascadiaSettings> LoadAll();
|
||||
|
||||
static const CascadiaSettings& GetCurrentAppSettings();
|
||||
|
||||
winrt::Microsoft::Terminal::Settings::TerminalSettings MakeSettings(std::optional<GUID> profileGuid) const;
|
||||
|
||||
GlobalAppSettings& GlobalSettings();
|
||||
|
||||
@ -3,6 +3,10 @@
|
||||
|
||||
#include "pch.h"
|
||||
#include "Pane.h"
|
||||
#include "Profile.h"
|
||||
#include "CascadiaSettings.h"
|
||||
|
||||
#include "winrt/Microsoft.Terminal.TerminalConnection.h"
|
||||
|
||||
using namespace winrt::Windows::Foundation;
|
||||
using namespace winrt::Windows::UI;
|
||||
@ -11,7 +15,9 @@ using namespace winrt::Windows::UI::Core;
|
||||
using namespace winrt::Windows::UI::Xaml::Media;
|
||||
using namespace winrt::Microsoft::Terminal::Settings;
|
||||
using namespace winrt::Microsoft::Terminal::TerminalControl;
|
||||
using namespace winrt::Microsoft::Terminal::TerminalConnection;
|
||||
using namespace winrt::TerminalApp;
|
||||
using namespace TerminalApp;
|
||||
|
||||
static const int PaneBorderSize = 2;
|
||||
static const int CombinedPaneBorderSize = 2 * PaneBorderSize;
|
||||
@ -28,7 +34,7 @@ Pane::Pane(const GUID& profile, const TermControl& control, const bool lastFocus
|
||||
_root.Children().Append(_border);
|
||||
_border.Child(_control);
|
||||
|
||||
_connectionClosedToken = _control.ConnectionClosed({ this, &Pane::_ControlClosedHandler });
|
||||
_connectionStateChangedToken = _control.ConnectionStateChanged({ this, &Pane::_ControlConnectionStateChangedHandler });
|
||||
|
||||
// On the first Pane's creation, lookup resources we'll use to theme the
|
||||
// Pane, including the brushed to use for the focused/unfocused border
|
||||
@ -302,7 +308,7 @@ bool Pane::NavigateFocus(const Direction& direction)
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Pane::_ControlClosedHandler()
|
||||
void Pane::_ControlConnectionStateChangedHandler(const TermControl& /*sender*/, const winrt::Windows::Foundation::IInspectable& /*args*/)
|
||||
{
|
||||
std::unique_lock lock{ _createCloseLock };
|
||||
// It's possible that this event handler started being executed, then before
|
||||
@ -317,10 +323,24 @@ void Pane::_ControlClosedHandler()
|
||||
return;
|
||||
}
|
||||
|
||||
if (_control.ShouldCloseOnExit())
|
||||
const auto newConnectionState = _control.ConnectionState();
|
||||
|
||||
if (newConnectionState < ConnectionState::Closed)
|
||||
{
|
||||
// Fire our Closed event to tell our parent that we should be removed.
|
||||
_closedHandlers();
|
||||
// Pane doesn't care if the connection isn't entering a terminal state.
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& settings = CascadiaSettings::GetCurrentAppSettings();
|
||||
auto paneProfile = settings.FindProfile(_profile.value());
|
||||
if (paneProfile)
|
||||
{
|
||||
auto mode = paneProfile->GetCloseOnExitMode();
|
||||
if ((mode == CloseOnExitMode::Always) ||
|
||||
(mode == CloseOnExitMode::Graceful && newConnectionState == ConnectionState::Closed))
|
||||
{
|
||||
_ClosedHandlers(nullptr, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -333,7 +353,7 @@ void Pane::_ControlClosedHandler()
|
||||
void Pane::Close()
|
||||
{
|
||||
// Fire our Closed event to tell our parent that we should be removed.
|
||||
_closedHandlers();
|
||||
_ClosedHandlers(nullptr, nullptr);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@ -570,7 +590,7 @@ void Pane::_CloseChild(const bool closeFirst)
|
||||
_profile = remainingChild->_profile;
|
||||
|
||||
// Add our new event handler before revoking the old one.
|
||||
_connectionClosedToken = _control.ConnectionClosed({ this, &Pane::_ControlClosedHandler });
|
||||
_connectionStateChangedToken = _control.ConnectionStateChanged({ this, &Pane::_ControlConnectionStateChangedHandler });
|
||||
|
||||
// Revoke the old event handlers. Remove both the handlers for the panes
|
||||
// themselves closing, and remove their handlers for their controls
|
||||
@ -578,8 +598,8 @@ void Pane::_CloseChild(const bool closeFirst)
|
||||
// they'll trigger only our event handler for the control's close.
|
||||
_firstChild->Closed(_firstClosedToken);
|
||||
_secondChild->Closed(_secondClosedToken);
|
||||
closedChild->_control.ConnectionClosed(closedChild->_connectionClosedToken);
|
||||
remainingChild->_control.ConnectionClosed(remainingChild->_connectionClosedToken);
|
||||
closedChild->_control.ConnectionStateChanged(closedChild->_connectionStateChangedToken);
|
||||
remainingChild->_control.ConnectionStateChanged(remainingChild->_connectionStateChangedToken);
|
||||
|
||||
// If either of our children was focused, we want to take that focus from
|
||||
// them.
|
||||
@ -659,7 +679,7 @@ void Pane::_CloseChild(const bool closeFirst)
|
||||
// Revoke event handlers on old panes and controls
|
||||
oldFirst->Closed(oldFirstToken);
|
||||
oldSecond->Closed(oldSecondToken);
|
||||
closedChild->_control.ConnectionClosed(closedChild->_connectionClosedToken);
|
||||
closedChild->_control.ConnectionStateChanged(closedChild->_connectionStateChangedToken);
|
||||
|
||||
// Reset our UI:
|
||||
_root.Children().Clear();
|
||||
@ -720,13 +740,13 @@ void Pane::_CloseChild(const bool closeFirst)
|
||||
// - <none>
|
||||
void Pane::_SetupChildCloseHandlers()
|
||||
{
|
||||
_firstClosedToken = _firstChild->Closed([this]() {
|
||||
_firstClosedToken = _firstChild->Closed([this](auto&& /*s*/, auto&& /*e*/) {
|
||||
_root.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [=]() {
|
||||
_CloseChild(true);
|
||||
});
|
||||
});
|
||||
|
||||
_secondClosedToken = _secondChild->Closed([this]() {
|
||||
_secondClosedToken = _secondChild->Closed([this](auto&& /*s*/, auto&& /*e*/) {
|
||||
_root.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [=]() {
|
||||
_CloseChild(false);
|
||||
});
|
||||
@ -965,8 +985,8 @@ std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Pane::_Split(SplitState
|
||||
std::unique_lock lock{ _createCloseLock };
|
||||
|
||||
// revoke our handler - the child will take care of the control now.
|
||||
_control.ConnectionClosed(_connectionClosedToken);
|
||||
_connectionClosedToken.value = 0;
|
||||
_control.ConnectionStateChanged(_connectionStateChangedToken);
|
||||
_connectionStateChangedToken.value = 0;
|
||||
|
||||
// Remove our old GotFocus handler from the control. We don't what the
|
||||
// control telling us that it's now focused, we want it telling its new
|
||||
@ -1124,5 +1144,4 @@ void Pane::_SetupResources()
|
||||
}
|
||||
}
|
||||
|
||||
DEFINE_EVENT(Pane, Closed, _closedHandlers, ConnectionClosedEventArgs);
|
||||
DEFINE_EVENT(Pane, GotFocus, _GotFocusHandlers, winrt::delegate<std::shared_ptr<Pane>>);
|
||||
|
||||
@ -71,7 +71,7 @@ public:
|
||||
|
||||
void Close();
|
||||
|
||||
DECLARE_EVENT(Closed, _closedHandlers, winrt::Microsoft::Terminal::TerminalControl::ConnectionClosedEventArgs);
|
||||
WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler<winrt::Windows::Foundation::IInspectable>);
|
||||
DECLARE_EVENT(GotFocus, _GotFocusHandlers, winrt::delegate<std::shared_ptr<Pane>>);
|
||||
|
||||
private:
|
||||
@ -89,7 +89,7 @@ private:
|
||||
|
||||
bool _lastActive{ false };
|
||||
std::optional<GUID> _profile{ std::nullopt };
|
||||
winrt::event_token _connectionClosedToken{ 0 };
|
||||
winrt::event_token _connectionStateChangedToken{ 0 };
|
||||
winrt::event_token _firstClosedToken{ 0 };
|
||||
winrt::event_token _secondClosedToken{ 0 };
|
||||
|
||||
@ -119,7 +119,7 @@ private:
|
||||
void _CloseChild(const bool closeFirst);
|
||||
|
||||
void _FocusFirstChild();
|
||||
void _ControlClosedHandler();
|
||||
void _ControlConnectionStateChangedHandler(const winrt::Microsoft::Terminal::TerminalControl::TermControl& sender, const winrt::Windows::Foundation::IInspectable& /*args*/);
|
||||
|
||||
std::pair<float, float> _GetPaneSizes(const float& fullSize);
|
||||
|
||||
|
||||
@ -50,6 +50,11 @@ static constexpr std::string_view BackgroundImageOpacityKey{ "backgroundImageOpa
|
||||
static constexpr std::string_view BackgroundImageStretchModeKey{ "backgroundImageStretchMode" };
|
||||
static constexpr std::string_view BackgroundImageAlignmentKey{ "backgroundImageAlignment" };
|
||||
|
||||
// Possible values for closeOnExit
|
||||
static constexpr std::string_view CloseOnExitAlways{ "always" };
|
||||
static constexpr std::string_view CloseOnExitGraceful{ "graceful" };
|
||||
static constexpr std::string_view CloseOnExitNever{ "never" };
|
||||
|
||||
// Possible values for Scrollbar state
|
||||
static constexpr std::wstring_view AlwaysVisible{ L"visible" };
|
||||
static constexpr std::wstring_view AlwaysHide{ L"hidden" };
|
||||
@ -109,7 +114,7 @@ Profile::Profile(const std::optional<GUID>& guid) :
|
||||
_acrylicTransparency{ 0.5 },
|
||||
_useAcrylic{ false },
|
||||
_scrollbarState{},
|
||||
_closeOnExit{ true },
|
||||
_closeOnExitMode{ CloseOnExitMode::Graceful },
|
||||
_padding{ DEFAULT_PADDING },
|
||||
_icon{},
|
||||
_backgroundImage{},
|
||||
@ -170,7 +175,6 @@ TerminalSettings Profile::CreateTerminalSettings(const std::unordered_map<std::w
|
||||
|
||||
// Fill in the remaining properties from the profile
|
||||
terminalSettings.UseAcrylic(_useAcrylic);
|
||||
terminalSettings.CloseOnExit(_closeOnExit);
|
||||
terminalSettings.TintOpacity(_acrylicTransparency);
|
||||
|
||||
terminalSettings.FontFace(_fontFace);
|
||||
@ -299,13 +303,11 @@ Json::Value Profile::ToJson() const
|
||||
}
|
||||
root[JsonKey(CursorShapeKey)] = winrt::to_string(_SerializeCursorStyle(_cursorShape));
|
||||
|
||||
///// Control Settings /////
|
||||
root[JsonKey(CommandlineKey)] = winrt::to_string(_commandline);
|
||||
root[JsonKey(FontFaceKey)] = winrt::to_string(_fontFace);
|
||||
root[JsonKey(FontSizeKey)] = _fontSize;
|
||||
root[JsonKey(AcrylicTransparencyKey)] = _acrylicTransparency;
|
||||
root[JsonKey(UseAcrylicKey)] = _useAcrylic;
|
||||
root[JsonKey(CloseOnExitKey)] = _closeOnExit;
|
||||
root[JsonKey(PaddingKey)] = winrt::to_string(_padding);
|
||||
|
||||
if (_connectionType)
|
||||
@ -359,6 +361,8 @@ Json::Value Profile::ToJson() const
|
||||
root[JsonKey(BackgroundImageAlignmentKey)] = SerializeImageAlignment(_backgroundImageAlignment.value()).data();
|
||||
}
|
||||
|
||||
root[JsonKey(CloseOnExitKey)] = _SerializeCloseOnExitMode(_closeOnExitMode).data();
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
@ -689,7 +693,7 @@ void Profile::LayerJson(const Json::Value& json)
|
||||
if (json.isMember(JsonKey(CloseOnExitKey)))
|
||||
{
|
||||
auto closeOnExit{ json[JsonKey(CloseOnExitKey)] };
|
||||
_closeOnExit = closeOnExit.asBool();
|
||||
_closeOnExitMode = ParseCloseOnExitMode(closeOnExit);
|
||||
}
|
||||
if (json.isMember(JsonKey(PaddingKey)))
|
||||
{
|
||||
@ -767,9 +771,9 @@ void Profile::SetSelectionBackground(COLORREF selectionBackground) noexcept
|
||||
_selectionBackground = selectionBackground;
|
||||
}
|
||||
|
||||
void Profile::SetCloseOnExit(bool defaultClose) noexcept
|
||||
void Profile::SetCloseOnExitMode(CloseOnExitMode mode) noexcept
|
||||
{
|
||||
_closeOnExit = defaultClose;
|
||||
_closeOnExitMode = mode;
|
||||
}
|
||||
|
||||
void Profile::SetConnectionType(GUID connectionType) noexcept
|
||||
@ -876,9 +880,9 @@ GUID Profile::GetConnectionType() const noexcept
|
||||
_GUID{};
|
||||
}
|
||||
|
||||
bool Profile::GetCloseOnExit() const noexcept
|
||||
CloseOnExitMode Profile::GetCloseOnExitMode() const noexcept
|
||||
{
|
||||
return _closeOnExit;
|
||||
return _closeOnExitMode;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@ -925,6 +929,60 @@ std::wstring Profile::EvaluateStartingDirectory(const std::wstring& directory)
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Helper function for converting a user-specified closeOnExit value to its corresponding enum
|
||||
// Arguments:
|
||||
// - The value from the profiles.json file
|
||||
// Return Value:
|
||||
// - The corresponding enum value which maps to the string provided by the user
|
||||
CloseOnExitMode Profile::ParseCloseOnExitMode(const Json::Value& json)
|
||||
{
|
||||
if (json.isBool())
|
||||
{
|
||||
return json.asBool() ? CloseOnExitMode::Graceful : CloseOnExitMode::Never;
|
||||
}
|
||||
|
||||
if (json.isString())
|
||||
{
|
||||
auto closeOnExit = json.asString();
|
||||
if (closeOnExit == CloseOnExitAlways)
|
||||
{
|
||||
return CloseOnExitMode::Always;
|
||||
}
|
||||
else if (closeOnExit == CloseOnExitGraceful)
|
||||
{
|
||||
return CloseOnExitMode::Graceful;
|
||||
}
|
||||
else if (closeOnExit == CloseOnExitNever)
|
||||
{
|
||||
return CloseOnExitMode::Never;
|
||||
}
|
||||
}
|
||||
|
||||
return CloseOnExitMode::Graceful;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Helper function for converting a CloseOnExitMode to its corresponding string
|
||||
// value.
|
||||
// Arguments:
|
||||
// - closeOnExitMode: The enum value to convert to a string.
|
||||
// Return Value:
|
||||
// - The string value for the given CloseOnExitMode
|
||||
std::string_view Profile::_SerializeCloseOnExitMode(const CloseOnExitMode closeOnExitMode)
|
||||
{
|
||||
switch (closeOnExitMode)
|
||||
{
|
||||
case CloseOnExitMode::Always:
|
||||
return CloseOnExitAlways;
|
||||
case CloseOnExitMode::Never:
|
||||
return CloseOnExitNever;
|
||||
case CloseOnExitMode::Graceful:
|
||||
default:
|
||||
return CloseOnExitGraceful;
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Helper function for converting a user-specified scrollbar state to its corresponding enum
|
||||
// Arguments:
|
||||
|
||||
@ -35,6 +35,13 @@ constexpr GUID RUNTIME_GENERATED_PROFILE_NAMESPACE_GUID = { 0xf65ddb7e, 0x706b,
|
||||
namespace TerminalApp
|
||||
{
|
||||
class Profile;
|
||||
|
||||
enum class CloseOnExitMode
|
||||
{
|
||||
Never = 0,
|
||||
Graceful,
|
||||
Always
|
||||
};
|
||||
};
|
||||
|
||||
class TerminalApp::Profile final
|
||||
@ -76,7 +83,7 @@ public:
|
||||
void SetDefaultForeground(COLORREF defaultForeground) noexcept;
|
||||
void SetDefaultBackground(COLORREF defaultBackground) noexcept;
|
||||
void SetSelectionBackground(COLORREF selectionBackground) noexcept;
|
||||
void SetCloseOnExit(bool defaultClose) noexcept;
|
||||
void SetCloseOnExitMode(CloseOnExitMode mode) noexcept;
|
||||
void SetConnectionType(GUID connectionType) noexcept;
|
||||
|
||||
bool HasIcon() const noexcept;
|
||||
@ -86,7 +93,7 @@ public:
|
||||
bool HasBackgroundImage() const noexcept;
|
||||
winrt::hstring GetExpandedBackgroundImagePath() const;
|
||||
|
||||
bool GetCloseOnExit() const noexcept;
|
||||
CloseOnExitMode GetCloseOnExitMode() const noexcept;
|
||||
bool GetSuppressApplicationTitle() const noexcept;
|
||||
bool IsHidden() const noexcept;
|
||||
|
||||
@ -104,6 +111,9 @@ private:
|
||||
static std::tuple<winrt::Windows::UI::Xaml::HorizontalAlignment, winrt::Windows::UI::Xaml::VerticalAlignment> ParseImageAlignment(const std::string_view imageAlignment);
|
||||
static std::tuple<winrt::Windows::UI::Xaml::HorizontalAlignment, winrt::Windows::UI::Xaml::VerticalAlignment> _ConvertJsonToAlignment(const Json::Value& json);
|
||||
|
||||
static CloseOnExitMode ParseCloseOnExitMode(const Json::Value& json);
|
||||
static std::string_view _SerializeCloseOnExitMode(const CloseOnExitMode closeOnExitMode);
|
||||
|
||||
static std::string_view SerializeImageAlignment(const std::tuple<winrt::Windows::UI::Xaml::HorizontalAlignment, winrt::Windows::UI::Xaml::VerticalAlignment> imageAlignment);
|
||||
static winrt::Microsoft::Terminal::Settings::CursorStyle _ParseCursorShape(const std::wstring& cursorShapeString);
|
||||
static std::wstring_view _SerializeCursorStyle(const winrt::Microsoft::Terminal::Settings::CursorStyle cursorShape);
|
||||
@ -144,7 +154,7 @@ private:
|
||||
std::optional<std::tuple<winrt::Windows::UI::Xaml::HorizontalAlignment, winrt::Windows::UI::Xaml::VerticalAlignment>> _backgroundImageAlignment;
|
||||
|
||||
std::optional<std::wstring> _scrollbarState;
|
||||
bool _closeOnExit;
|
||||
CloseOnExitMode _closeOnExitMode;
|
||||
std::wstring _padding;
|
||||
|
||||
std::optional<std::wstring> _icon;
|
||||
|
||||
@ -19,8 +19,8 @@ Tab::Tab(const GUID& profile, const TermControl& control)
|
||||
{
|
||||
_rootPane = std::make_shared<Pane>(profile, control, true);
|
||||
|
||||
_rootPane->Closed([=]() {
|
||||
_closedHandlers();
|
||||
_rootPane->Closed([=](auto&& /*s*/, auto&& /*e*/) {
|
||||
_ClosedHandlers(nullptr, nullptr);
|
||||
});
|
||||
|
||||
_activePane = _rootPane;
|
||||
@ -335,5 +335,4 @@ void Tab::_AttachEventHandlersToPane(std::shared_ptr<Pane> pane)
|
||||
});
|
||||
}
|
||||
|
||||
DEFINE_EVENT(Tab, Closed, _closedHandlers, ConnectionClosedEventArgs);
|
||||
DEFINE_EVENT(Tab, ActivePaneChanged, _ActivePaneChangedHandlers, winrt::delegate<>);
|
||||
|
||||
@ -35,7 +35,7 @@ public:
|
||||
|
||||
void ClosePane();
|
||||
|
||||
DECLARE_EVENT(Closed, _closedHandlers, winrt::Microsoft::Terminal::TerminalControl::ConnectionClosedEventArgs);
|
||||
WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler<winrt::Windows::Foundation::IInspectable>);
|
||||
DECLARE_EVENT(ActivePaneChanged, _ActivePaneChangedHandlers, winrt::delegate<>);
|
||||
|
||||
private:
|
||||
|
||||
@ -464,7 +464,7 @@ namespace winrt::TerminalApp::implementation
|
||||
tabViewItem.PointerPressed({ this, &TerminalPage::_OnTabClick });
|
||||
|
||||
// When the tab is closed, remove it from our list of tabs.
|
||||
newTab->Closed([tabViewItem, this]() {
|
||||
newTab->Closed([tabViewItem, this](auto&& /*s*/, auto&& /*e*/) {
|
||||
_tabView.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [tabViewItem, this]() {
|
||||
_RemoveTabViewItem(tabViewItem);
|
||||
});
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
"commandline": "powershell.exe",
|
||||
"hidden": false,
|
||||
"startingDirectory": "%USERPROFILE%",
|
||||
"closeOnExit": true,
|
||||
"closeOnExit": "graceful",
|
||||
"colorScheme": "Campbell Powershell",
|
||||
"cursorColor": "#FFFFFF",
|
||||
"cursorShape": "bar",
|
||||
@ -35,7 +35,7 @@
|
||||
"commandline": "cmd.exe",
|
||||
"hidden": false,
|
||||
"startingDirectory": "%USERPROFILE%",
|
||||
"closeOnExit": true,
|
||||
"closeOnExit": "graceful",
|
||||
"colorScheme": "Campbell",
|
||||
"cursorColor": "#FFFFFF",
|
||||
"cursorShape": "bar",
|
||||
|
||||
@ -49,51 +49,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
// - str: the string to write.
|
||||
void AzureConnection::_WriteStringWithNewline(const winrt::hstring& str)
|
||||
{
|
||||
_outputHandlers(str + L"\r\n");
|
||||
}
|
||||
|
||||
// Method description:
|
||||
// - ascribes to the ITerminalConnection interface
|
||||
// - registers an output event handler
|
||||
// Arguments:
|
||||
// - the handler
|
||||
// Return value:
|
||||
// - the event token for the handler
|
||||
winrt::event_token AzureConnection::TerminalOutput(Microsoft::Terminal::TerminalConnection::TerminalOutputEventArgs const& handler)
|
||||
{
|
||||
return _outputHandlers.add(handler);
|
||||
}
|
||||
|
||||
// Method description:
|
||||
// - ascribes to the ITerminalConnection interface
|
||||
// - revokes an output event handler
|
||||
// Arguments:
|
||||
// - the event token for the handler
|
||||
void AzureConnection::TerminalOutput(winrt::event_token const& token) noexcept
|
||||
{
|
||||
_outputHandlers.remove(token);
|
||||
}
|
||||
|
||||
// Method description:
|
||||
// - ascribes to the ITerminalConnection interface
|
||||
// - registers a terminal-disconnected event handler
|
||||
// Arguments:
|
||||
// - the handler
|
||||
// Return value:
|
||||
// - the event token for the handler
|
||||
winrt::event_token AzureConnection::TerminalDisconnected(Microsoft::Terminal::TerminalConnection::TerminalDisconnectedEventArgs const& handler)
|
||||
{
|
||||
return _disconnectHandlers.add(handler);
|
||||
}
|
||||
|
||||
// Method description:
|
||||
// - ascribes to the ITerminalConnection interface
|
||||
// - revokes a terminal-disconnected event handler
|
||||
// Arguments:
|
||||
// - the event token for the handler
|
||||
void AzureConnection::TerminalDisconnected(winrt::event_token const& token) noexcept
|
||||
{
|
||||
_disconnectHandlers.remove(token);
|
||||
_TerminalOutputHandlers(str + L"\r\n");
|
||||
}
|
||||
|
||||
// Method description:
|
||||
@ -112,7 +68,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
|
||||
THROW_LAST_ERROR_IF_NULL(_hOutputThread);
|
||||
|
||||
_connected = true;
|
||||
_transitionToState(ConnectionState::Connecting);
|
||||
}
|
||||
|
||||
// Method description:
|
||||
@ -122,7 +78,8 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
// the user's input
|
||||
void AzureConnection::WriteInput(hstring const& data)
|
||||
{
|
||||
if (!_connected || _closing.load())
|
||||
// We read input while connected AND connecting.
|
||||
if (!_isStateOneOf(ConnectionState::Connected, ConnectionState::Connecting))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -131,7 +88,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
switch (_state)
|
||||
{
|
||||
// The user has stored connection settings, let them choose one of them, create a new one or remove all stored ones
|
||||
case State::AccessStored:
|
||||
case AzureState::AccessStored:
|
||||
{
|
||||
const auto s = winrt::to_string(data);
|
||||
int storeNum = -1;
|
||||
@ -172,7 +129,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
return;
|
||||
}
|
||||
// The user has multiple tenants in their Azure account, let them choose one of them
|
||||
case State::TenantChoice:
|
||||
case AzureState::TenantChoice:
|
||||
{
|
||||
int tenantNum = -1;
|
||||
try
|
||||
@ -195,7 +152,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
return;
|
||||
}
|
||||
// User has the option to save their connection settings for future logins
|
||||
case State::StoreTokens:
|
||||
case AzureState::StoreTokens:
|
||||
{
|
||||
std::lock_guard<std::mutex> lg{ _commonMutex };
|
||||
if (data == RS_(L"AzureUserEntry_Yes"))
|
||||
@ -218,7 +175,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
return;
|
||||
}
|
||||
// We are connected, send user's input over the websocket
|
||||
case State::TermConnected:
|
||||
case AzureState::TermConnected:
|
||||
{
|
||||
websocket_outgoing_message msg;
|
||||
const auto str = winrt::to_string(data);
|
||||
@ -238,12 +195,12 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
// - the new rows/cols values
|
||||
void AzureConnection::Resize(uint32_t rows, uint32_t columns)
|
||||
{
|
||||
if (!_connected || !(_state == State::TermConnected))
|
||||
if (!_isConnected())
|
||||
{
|
||||
_initialRows = rows;
|
||||
_initialCols = columns;
|
||||
}
|
||||
else if (!_closing.load())
|
||||
else // We only transition to Connected when we've established the websocket.
|
||||
{
|
||||
// Initialize client
|
||||
http_client terminalClient(_cloudShellUri);
|
||||
@ -264,24 +221,24 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
// - closes the websocket connection and the output thread
|
||||
void AzureConnection::Close()
|
||||
{
|
||||
if (!_connected)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_closing.exchange(true))
|
||||
if (_transitionToState(ConnectionState::Closing))
|
||||
{
|
||||
_canProceed.notify_all();
|
||||
if (_state == State::TermConnected)
|
||||
if (_state == AzureState::TermConnected)
|
||||
{
|
||||
// Close the websocket connection
|
||||
auto closedTask = _cloudShellSocket.close();
|
||||
closedTask.wait();
|
||||
}
|
||||
|
||||
// Tear down our output thread
|
||||
WaitForSingleObject(_hOutputThread.get(), INFINITE);
|
||||
_hOutputThread.reset();
|
||||
if (_hOutputThread)
|
||||
{
|
||||
// Tear down our output thread
|
||||
WaitForSingleObject(_hOutputThread.get(), INFINITE);
|
||||
_hOutputThread.reset();
|
||||
}
|
||||
|
||||
_transitionToState(ConnectionState::Closed);
|
||||
}
|
||||
}
|
||||
|
||||
@ -320,45 +277,52 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (_isStateAtOrBeyond(ConnectionState::Closing))
|
||||
{
|
||||
// If we enter a new state while closing, just bail.
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
switch (_state)
|
||||
{
|
||||
// Initial state, check if the user has any stored connection settings and allow them to login with those
|
||||
// or allow them to login with a different account or allow them to remove the saved settings
|
||||
case State::AccessStored:
|
||||
case AzureState::AccessStored:
|
||||
{
|
||||
RETURN_IF_FAILED(_AccessHelper());
|
||||
break;
|
||||
}
|
||||
// User has no saved connection settings or has opted to login with a different account
|
||||
// Azure authentication happens here
|
||||
case State::DeviceFlow:
|
||||
case AzureState::DeviceFlow:
|
||||
{
|
||||
RETURN_IF_FAILED(_DeviceFlowHelper());
|
||||
break;
|
||||
}
|
||||
// User has multiple tenants in their Azure account, they need to choose which one to connect to
|
||||
case State::TenantChoice:
|
||||
case AzureState::TenantChoice:
|
||||
{
|
||||
RETURN_IF_FAILED(_TenantChoiceHelper());
|
||||
break;
|
||||
}
|
||||
// Ask the user if they want to save these connection settings for future logins
|
||||
case State::StoreTokens:
|
||||
case AzureState::StoreTokens:
|
||||
{
|
||||
RETURN_IF_FAILED(_StoreHelper());
|
||||
break;
|
||||
}
|
||||
// Connect to Azure, we only get here once we have everything we need (tenantID, accessToken, refreshToken)
|
||||
case State::TermConnecting:
|
||||
case AzureState::TermConnecting:
|
||||
{
|
||||
RETURN_IF_FAILED(_ConnectHelper());
|
||||
break;
|
||||
}
|
||||
// We are connected, continuously read from the websocket until its closed
|
||||
case State::TermConnected:
|
||||
case AzureState::TermConnected:
|
||||
{
|
||||
_transitionToState(ConnectionState::Connected);
|
||||
while (true)
|
||||
{
|
||||
// Read from websocket
|
||||
@ -370,15 +334,15 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// Websocket has been closed
|
||||
if (!_closing.load())
|
||||
// Websocket has been closed; consider it a graceful exit?
|
||||
// This should result in our termination.
|
||||
if (_transitionToState(ConnectionState::Closed))
|
||||
{
|
||||
_state = State::NoConnect;
|
||||
_disconnectHandlers();
|
||||
// End the output thread.
|
||||
return S_FALSE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
auto msg = msgT.get();
|
||||
auto msgStringTask = msg.extract_string();
|
||||
auto msgString = msgStringTask.get();
|
||||
@ -387,21 +351,21 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
const auto hstr = winrt::to_hstring(msgString);
|
||||
|
||||
// Pass the output to our registered event handlers
|
||||
_outputHandlers(hstr);
|
||||
_TerminalOutputHandlers(hstr);
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
case State::NoConnect:
|
||||
case AzureState::NoConnect:
|
||||
{
|
||||
_WriteStringWithNewline(RS_(L"AzureInternetOrServerIssue"));
|
||||
_disconnectHandlers();
|
||||
_transitionToState(ConnectionState::Failed);
|
||||
return E_FAIL;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
_state = State::NoConnect;
|
||||
_state = AzureState::NoConnect;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -425,7 +389,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
catch (...)
|
||||
{
|
||||
// No credentials are stored, so start the device flow
|
||||
_state = State::DeviceFlow;
|
||||
_state = AzureState::DeviceFlow;
|
||||
return S_FALSE;
|
||||
}
|
||||
_maxStored = 0;
|
||||
@ -458,7 +422,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
_WriteStringWithNewline(RS_(L"AzureOldCredentialsFlushedMessage"));
|
||||
}
|
||||
// No valid up-to-date credentials were found, so start the device flow
|
||||
_state = State::DeviceFlow;
|
||||
_state = AzureState::DeviceFlow;
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
@ -468,10 +432,10 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
|
||||
std::unique_lock<std::mutex> storedLock{ _commonMutex };
|
||||
_canProceed.wait(storedLock, [=]() {
|
||||
return (_storedNumber >= 0 && _storedNumber < _maxStored) || _removeOrNew.has_value() || _closing.load();
|
||||
return (_storedNumber >= 0 && _storedNumber < _maxStored) || _removeOrNew.has_value() || _isStateAtOrBeyond(ConnectionState::Closing);
|
||||
});
|
||||
// User might have closed the tab while we waited for input
|
||||
if (_closing.load())
|
||||
if (_isStateAtOrBeyond(ConnectionState::Closing))
|
||||
{
|
||||
return E_FAIL;
|
||||
}
|
||||
@ -479,13 +443,13 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
{
|
||||
// User wants to remove the stored settings
|
||||
_RemoveCredentials();
|
||||
_state = State::DeviceFlow;
|
||||
_state = AzureState::DeviceFlow;
|
||||
return S_OK;
|
||||
}
|
||||
else if (_removeOrNew.has_value() && !_removeOrNew.value())
|
||||
{
|
||||
// User wants to login with a different account
|
||||
_state = State::DeviceFlow;
|
||||
_state = AzureState::DeviceFlow;
|
||||
return S_OK;
|
||||
}
|
||||
// User wants to login with one of the saved connection settings
|
||||
@ -514,7 +478,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
}
|
||||
|
||||
// We have everything we need, so go ahead and connect
|
||||
_state = State::TermConnecting;
|
||||
_state = AzureState::TermConnecting;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
@ -558,6 +522,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
if (tenantListAsArray.size() == 0)
|
||||
{
|
||||
_WriteStringWithNewline(RS_(L"AzureNoTenants"));
|
||||
_transitionToState(ConnectionState::Failed);
|
||||
return E_FAIL;
|
||||
}
|
||||
else if (_tenantList.size() == 1)
|
||||
@ -571,11 +536,11 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
_refreshToken = refreshResponse.at(L"refresh_token").as_string();
|
||||
_expiry = std::stoi(refreshResponse.at(L"expires_on").as_string());
|
||||
|
||||
_state = State::StoreTokens;
|
||||
_state = AzureState::StoreTokens;
|
||||
}
|
||||
else
|
||||
{
|
||||
_state = State::TenantChoice;
|
||||
_state = AzureState::TenantChoice;
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
@ -602,10 +567,10 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
// Use a lock to wait for the user to input a valid number
|
||||
std::unique_lock<std::mutex> tenantNumberLock{ _commonMutex };
|
||||
_canProceed.wait(tenantNumberLock, [=]() {
|
||||
return (_tenantNumber >= 0 && _tenantNumber < _maxSize) || _closing.load();
|
||||
return (_tenantNumber >= 0 && _tenantNumber < _maxSize) || _isStateAtOrBeyond(ConnectionState::Closing);
|
||||
});
|
||||
// User might have closed the tab while we waited for input
|
||||
if (_closing.load())
|
||||
if (_isStateAtOrBeyond(ConnectionState::Closing))
|
||||
{
|
||||
return E_FAIL;
|
||||
}
|
||||
@ -619,7 +584,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
_refreshToken = refreshResponse.at(L"refresh_token").as_string();
|
||||
_expiry = std::stoi(refreshResponse.at(L"expires_on").as_string());
|
||||
|
||||
_state = State::StoreTokens;
|
||||
_state = AzureState::StoreTokens;
|
||||
return S_OK;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
@ -636,10 +601,10 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
// Wait for user input
|
||||
std::unique_lock<std::mutex> storeLock{ _commonMutex };
|
||||
_canProceed.wait(storeLock, [=]() {
|
||||
return _store.has_value() || _closing.load();
|
||||
return _store.has_value() || _isStateAtOrBeyond(ConnectionState::Closing);
|
||||
});
|
||||
// User might have closed the tab while we waited for input
|
||||
if (_closing.load())
|
||||
if (_isStateAtOrBeyond(ConnectionState::Closing))
|
||||
{
|
||||
return E_FAIL;
|
||||
}
|
||||
@ -651,7 +616,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
_WriteStringWithNewline(RS_(L"AzureTokensStored"));
|
||||
}
|
||||
|
||||
_state = State::TermConnecting;
|
||||
_state = AzureState::TermConnecting;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
@ -667,6 +632,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
if (settingsResponse.has_field(L"error"))
|
||||
{
|
||||
_WriteStringWithNewline(RS_(L"AzureNoCloudAccount"));
|
||||
_transitionToState(ConnectionState::Failed);
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
@ -683,13 +649,13 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
const auto shellType = L"bash";
|
||||
_WriteStringWithNewline(RS_(L"AzureRequestingTerminal"));
|
||||
const auto socketUri = _GetTerminal(shellType);
|
||||
_outputHandlers(L"\r\n");
|
||||
_TerminalOutputHandlers(L"\r\n");
|
||||
|
||||
// Step 8: connecting to said terminal
|
||||
const auto connReqTask = _cloudShellSocket.connect(socketUri);
|
||||
connReqTask.wait();
|
||||
|
||||
_state = State::TermConnected;
|
||||
_state = AzureState::TermConnected;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
@ -759,7 +725,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
for (int count = 0; count < expiresIn / pollInterval; count++)
|
||||
{
|
||||
// User might close the tab while we wait for them to authenticate, this case handles that
|
||||
if (_closing.load())
|
||||
if (_isStateAtOrBeyond(ConnectionState::Closing))
|
||||
{
|
||||
throw "Tab closed.";
|
||||
}
|
||||
|
||||
@ -11,26 +11,24 @@
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
|
||||
#include "../cascadia/inc/cppwinrt_utils.h"
|
||||
#include "ConnectionStateHolder.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
{
|
||||
struct AzureConnection : AzureConnectionT<AzureConnection>
|
||||
struct AzureConnection : AzureConnectionT<AzureConnection>, ConnectionStateHolder<AzureConnection>
|
||||
{
|
||||
static bool IsAzureConnectionAvailable();
|
||||
AzureConnection(const uint32_t rows, const uint32_t cols);
|
||||
|
||||
winrt::event_token TerminalOutput(TerminalConnection::TerminalOutputEventArgs const& handler);
|
||||
void TerminalOutput(winrt::event_token const& token) noexcept;
|
||||
winrt::event_token TerminalDisconnected(TerminalConnection::TerminalDisconnectedEventArgs const& handler);
|
||||
void TerminalDisconnected(winrt::event_token const& token) noexcept;
|
||||
void Start();
|
||||
void WriteInput(hstring const& data);
|
||||
void Resize(uint32_t rows, uint32_t columns);
|
||||
void Close();
|
||||
|
||||
private:
|
||||
winrt::event<TerminalConnection::TerminalOutputEventArgs> _outputHandlers;
|
||||
winrt::event<TerminalConnection::TerminalDisconnectedEventArgs> _disconnectHandlers;
|
||||
WINRT_CALLBACK(TerminalOutput, TerminalOutputHandler);
|
||||
|
||||
private:
|
||||
uint32_t _initialRows{};
|
||||
uint32_t _initialCols{};
|
||||
int _storedNumber{ -1 };
|
||||
@ -40,7 +38,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
std::condition_variable _canProceed;
|
||||
std::mutex _commonMutex;
|
||||
|
||||
enum class State
|
||||
enum class AzureState
|
||||
{
|
||||
AccessStored,
|
||||
DeviceFlow,
|
||||
@ -51,14 +49,11 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
NoConnect
|
||||
};
|
||||
|
||||
State _state{ State::AccessStored };
|
||||
AzureState _state{ AzureState::AccessStored };
|
||||
|
||||
std::optional<bool> _store;
|
||||
std::optional<bool> _removeOrNew;
|
||||
|
||||
bool _connected{};
|
||||
std::atomic<bool> _closing{ false };
|
||||
|
||||
wil::unique_handle _hOutputThread;
|
||||
|
||||
static DWORD WINAPI StaticOutputThreadProc(LPVOID lpParameter);
|
||||
|
||||
85
src/cascadia/TerminalConnection/ConnectionStateHolder.h
Normal file
85
src/cascadia/TerminalConnection/ConnectionStateHolder.h
Normal file
@ -0,0 +1,85 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "../inc/cppwinrt_utils.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
{
|
||||
template<typename T>
|
||||
struct ConnectionStateHolder
|
||||
{
|
||||
public:
|
||||
ConnectionState State() const noexcept { return _connectionState; }
|
||||
TYPED_EVENT(StateChanged, ITerminalConnection, winrt::Windows::Foundation::IInspectable);
|
||||
|
||||
protected:
|
||||
// Method Description:
|
||||
// - Attempt to transition to and signal the specified connection state.
|
||||
// The transition will only be effected if the state is "beyond" the current state.
|
||||
// Arguments:
|
||||
// - state: the new state
|
||||
// Return Value:
|
||||
// Whether we've successfully transitioned to the new state.
|
||||
bool _transitionToState(const ConnectionState state) noexcept
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> stateLock{ _stateMutex };
|
||||
// only allow movement up the state gradient
|
||||
if (state < _connectionState)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
_connectionState = state;
|
||||
}
|
||||
// Dispatch the event outside of lock.
|
||||
_StateChangedHandlers(*static_cast<T*>(this), nullptr);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Returns whether the state is one of the N specified states.
|
||||
// Arguments:
|
||||
// - "...": the states
|
||||
// Return Value:
|
||||
// Whether we're in one of the states.
|
||||
template<typename... Args>
|
||||
bool _isStateOneOf(Args&&... args) const noexcept
|
||||
{
|
||||
// Dark magic! This function uses C++17 fold expressions. These fold expressions roughly expand as follows:
|
||||
// (... OP (expression_using_args))
|
||||
// into ->
|
||||
// expression_using_args[0] OP expression_using_args[1] OP expression_using_args[2] OP (etc.)
|
||||
// We use that to first check that All Args types are ConnectionState (type[0] == ConnectionState && type[1] == ConnectionState && etc.)
|
||||
// and then later to check that the current state is one of the passed-in ones:
|
||||
// (_state == args[0] || _state == args[1] || etc.)
|
||||
static_assert((... && std::is_same<Args, ConnectionState>::value), "all queried connection states must be from the ConnectionState enum");
|
||||
std::lock_guard<std::mutex> stateLock{ _stateMutex };
|
||||
return (... || (_connectionState == args));
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Returns whether the state has reached or surpassed the specified state.
|
||||
// Arguments:
|
||||
// - state; the state to check against
|
||||
// Return Value:
|
||||
// Whether we're at or beyond the specified state
|
||||
bool _isStateAtOrBeyond(const ConnectionState state) const noexcept
|
||||
{
|
||||
std::lock_guard<std::mutex> stateLock{ _stateMutex };
|
||||
return _connectionState >= state;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - (Convenience:) Returns whether we're "connected".
|
||||
// Return Value:
|
||||
// Whether we're "connected"
|
||||
bool _isConnected() const noexcept
|
||||
{
|
||||
return _isStateOneOf(ConnectionState::Connected);
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic<ConnectionState> _connectionState{ ConnectionState::NotConnected };
|
||||
mutable std::mutex _stateMutex;
|
||||
};
|
||||
}
|
||||
@ -11,9 +11,25 @@
|
||||
#include "../../types/inc/Utils.hpp"
|
||||
#include "../../types/inc/Environment.hpp"
|
||||
#include "../../types/inc/UTF8OutPipeReader.hpp"
|
||||
#include "LibraryResources.h"
|
||||
|
||||
using namespace ::Microsoft::Console;
|
||||
|
||||
// Notes:
|
||||
// There is a number of ways that the Conpty connection can be terminated (voluntarily or not):
|
||||
// 1. The connection is Close()d
|
||||
// 2. The pseudoconsole or process cannot be spawned during Start()
|
||||
// 3. The client process exits with a code.
|
||||
// (Successful (0) or any other code)
|
||||
// 4. The read handle is terminated.
|
||||
// (This usually happens when the pseudoconsole host crashes.)
|
||||
// In each of these termination scenarios, we need to be mindful of tripping the others.
|
||||
// Closing the pseudoconsole in response to the client exiting (3) can trigger (4).
|
||||
// Close() (1) will cause the automatic triggering of (3) and (4).
|
||||
// In a lot of cases, we use the connection state to stop "flapping."
|
||||
//
|
||||
// To figure out where we handle these, search for comments containing "EXIT POINT"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
{
|
||||
// Function Description:
|
||||
@ -150,26 +166,6 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
return _guid;
|
||||
}
|
||||
|
||||
winrt::event_token ConptyConnection::TerminalOutput(Microsoft::Terminal::TerminalConnection::TerminalOutputEventArgs const& handler)
|
||||
{
|
||||
return _outputHandlers.add(handler);
|
||||
}
|
||||
|
||||
void ConptyConnection::TerminalOutput(winrt::event_token const& token) noexcept
|
||||
{
|
||||
_outputHandlers.remove(token);
|
||||
}
|
||||
|
||||
winrt::event_token ConptyConnection::TerminalDisconnected(Microsoft::Terminal::TerminalConnection::TerminalDisconnectedEventArgs const& handler)
|
||||
{
|
||||
return _disconnectHandlers.add(handler);
|
||||
}
|
||||
|
||||
void ConptyConnection::TerminalDisconnected(winrt::event_token const& token) noexcept
|
||||
{
|
||||
_disconnectHandlers.remove(token);
|
||||
}
|
||||
|
||||
void ConptyConnection::Start()
|
||||
try
|
||||
{
|
||||
@ -205,32 +201,70 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
|
||||
SetThreadpoolWait(_clientExitWait.get(), _piClient.hProcess, nullptr);
|
||||
|
||||
_connected = true;
|
||||
_transitionToState(ConnectionState::Connected);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// EXIT POINT
|
||||
const auto hr = wil::ResultFromCaughtException();
|
||||
// TODO GH#2563 - signal a transition into failed state here!
|
||||
LOG_HR(hr);
|
||||
|
||||
_disconnectHandlers();
|
||||
winrt::hstring failureText{ wil::str_printf<std::wstring>(RS_(L"ProcessFailedToLaunch").c_str(), static_cast<unsigned int>(hr), _commandline.c_str()) };
|
||||
_TerminalOutputHandlers(failureText);
|
||||
_transitionToState(ConnectionState::Failed);
|
||||
|
||||
// Tear down any state we may have accumulated.
|
||||
_hPC.reset();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - prints out the "process exited" message formatted with the exit code
|
||||
// Arguments:
|
||||
// - status: the exit code.
|
||||
void ConptyConnection::_indicateExitWithStatus(unsigned int status) noexcept
|
||||
{
|
||||
try
|
||||
{
|
||||
winrt::hstring exitText{ wil::str_printf<std::wstring>(RS_(L"ProcessExited").c_str(), (unsigned int)status) };
|
||||
_TerminalOutputHandlers(L"\r\n");
|
||||
_TerminalOutputHandlers(exitText);
|
||||
}
|
||||
CATCH_LOG();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - called when the client application (not necessarily its pty) exits for any reason
|
||||
void ConptyConnection::_ClientTerminated() noexcept
|
||||
{
|
||||
if (_closing.load())
|
||||
if (_isStateAtOrBeyond(ConnectionState::Closing))
|
||||
{
|
||||
// This is okay, break out to kill the thread
|
||||
// This termination was expected.
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO GH#2563 - get the exit code from the process and see whether it was a failing one.
|
||||
_disconnectHandlers();
|
||||
// EXIT POINT
|
||||
DWORD exitCode{ 0 };
|
||||
GetExitCodeProcess(_piClient.hProcess, &exitCode);
|
||||
|
||||
// Signal the closing or failure of the process.
|
||||
// Load bearing. Terminating the pseudoconsole will make the output thread exit unexpectedly,
|
||||
// so we need to signal entry into the correct closing state before we do that.
|
||||
_transitionToState(exitCode == 0 ? ConnectionState::Closed : ConnectionState::Failed);
|
||||
|
||||
// Close the pseudoconsole and wait for all output to drain.
|
||||
_hPC.reset();
|
||||
if (auto localOutputThreadHandle = std::move(_hOutputThread))
|
||||
{
|
||||
LOG_LAST_ERROR_IF(WAIT_FAILED == WaitForSingleObject(localOutputThreadHandle.get(), INFINITE));
|
||||
}
|
||||
|
||||
_indicateExitWithStatus(exitCode);
|
||||
|
||||
_piClient.reset();
|
||||
}
|
||||
|
||||
void ConptyConnection::WriteInput(hstring const& data)
|
||||
{
|
||||
if (!_connected || _closing.load())
|
||||
if (!_isConnected())
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -248,7 +282,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
_initialRows = rows;
|
||||
_initialCols = columns;
|
||||
}
|
||||
else if (!_closing.load())
|
||||
else if (_isConnected())
|
||||
{
|
||||
THROW_IF_FAILED(ConptyResizePseudoConsole(_hPC.get(), { Utils::ClampToShortMax(columns, 1), Utils::ClampToShortMax(rows, 1) }));
|
||||
}
|
||||
@ -256,28 +290,32 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
|
||||
void ConptyConnection::Close()
|
||||
{
|
||||
if (!_connected)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_closing.exchange(true))
|
||||
if (_transitionToState(ConnectionState::Closing))
|
||||
{
|
||||
// EXIT POINT
|
||||
_clientExitWait.reset(); // immediately stop waiting for the client to exit.
|
||||
|
||||
_hPC.reset();
|
||||
_hPC.reset(); // tear down the pseudoconsole (this is like clicking X on a console window)
|
||||
|
||||
_inPipe.reset();
|
||||
_inPipe.reset(); // break the pipes
|
||||
_outPipe.reset();
|
||||
|
||||
// Tear down our output thread -- now that the output pipe was closed on the
|
||||
// far side, we can run down our local reader.
|
||||
LOG_LAST_ERROR_IF(WAIT_FAILED == WaitForSingleObject(_hOutputThread.get(), INFINITE));
|
||||
_hOutputThread.reset();
|
||||
if (_hOutputThread)
|
||||
{
|
||||
// Tear down our output thread -- now that the output pipe was closed on the
|
||||
// far side, we can run down our local reader.
|
||||
LOG_LAST_ERROR_IF(WAIT_FAILED == WaitForSingleObject(_hOutputThread.get(), INFINITE));
|
||||
_hOutputThread.reset();
|
||||
}
|
||||
|
||||
// Wait for the client to terminate.
|
||||
LOG_LAST_ERROR_IF(WAIT_FAILED == WaitForSingleObject(_piClient.hProcess, INFINITE));
|
||||
_piClient.reset();
|
||||
if (_piClient.hProcess)
|
||||
{
|
||||
// Wait for the client to terminate (which it should do successfully)
|
||||
LOG_LAST_ERROR_IF(WAIT_FAILED == WaitForSingleObject(_piClient.hProcess, INFINITE));
|
||||
_piClient.reset();
|
||||
}
|
||||
|
||||
_transitionToState(ConnectionState::Closed);
|
||||
}
|
||||
}
|
||||
|
||||
@ -292,13 +330,15 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
HRESULT result = pipeReader.Read(strView);
|
||||
if (FAILED(result) || result == S_FALSE)
|
||||
{
|
||||
if (_closing.load())
|
||||
if (_isStateAtOrBeyond(ConnectionState::Closing))
|
||||
{
|
||||
// This is okay, break out to kill the thread
|
||||
// This termination was expected.
|
||||
return 0;
|
||||
}
|
||||
|
||||
_disconnectHandlers();
|
||||
// EXIT POINT
|
||||
_indicateExitWithStatus(result); // print a message
|
||||
_transitionToState(ConnectionState::Failed);
|
||||
return (DWORD)-1;
|
||||
}
|
||||
|
||||
@ -326,7 +366,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
auto hstr{ winrt::to_hstring(strView) };
|
||||
|
||||
// Pass the output to our registered event handlers
|
||||
_outputHandlers(hstr);
|
||||
_TerminalOutputHandlers(hstr);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ConptyConnection.g.h"
|
||||
#include "ConnectionStateHolder.h"
|
||||
#include "../inc/cppwinrt_utils.h"
|
||||
|
||||
#include <conpty-static.h>
|
||||
|
||||
namespace wil
|
||||
@ -14,14 +17,10 @@ namespace wil
|
||||
|
||||
namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
{
|
||||
struct ConptyConnection : ConptyConnectionT<ConptyConnection>
|
||||
struct ConptyConnection : ConptyConnectionT<ConptyConnection>, ConnectionStateHolder<ConptyConnection>
|
||||
{
|
||||
ConptyConnection(const hstring& cmdline, const hstring& startingDirectory, const hstring& startingTitle, const uint32_t rows, const uint32_t cols, const guid& guid);
|
||||
|
||||
winrt::event_token TerminalOutput(TerminalConnection::TerminalOutputEventArgs const& handler);
|
||||
void TerminalOutput(winrt::event_token const& token) noexcept;
|
||||
winrt::event_token TerminalDisconnected(TerminalConnection::TerminalDisconnectedEventArgs const& handler);
|
||||
void TerminalDisconnected(winrt::event_token const& token) noexcept;
|
||||
void Start();
|
||||
void WriteInput(hstring const& data);
|
||||
void Resize(uint32_t rows, uint32_t columns);
|
||||
@ -29,13 +28,13 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
|
||||
winrt::guid Guid() const noexcept;
|
||||
|
||||
WINRT_CALLBACK(TerminalOutput, TerminalOutputHandler);
|
||||
|
||||
private:
|
||||
HRESULT _LaunchAttachedClient() noexcept;
|
||||
void _indicateExitWithStatus(unsigned int status) noexcept;
|
||||
void _ClientTerminated() noexcept;
|
||||
|
||||
winrt::event<TerminalConnection::TerminalOutputEventArgs> _outputHandlers;
|
||||
winrt::event<TerminalConnection::TerminalDisconnectedEventArgs> _disconnectHandlers;
|
||||
|
||||
uint32_t _initialRows{};
|
||||
uint32_t _initialCols{};
|
||||
hstring _commandline;
|
||||
@ -43,8 +42,6 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
hstring _startingTitle;
|
||||
guid _guid{}; // A unique session identifier for connected client
|
||||
|
||||
bool _connected{};
|
||||
std::atomic<bool> _closing{ false };
|
||||
bool _recievedFirstByte{ false };
|
||||
std::chrono::high_resolution_clock::time_point _startTime{};
|
||||
|
||||
|
||||
@ -13,27 +13,6 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
{
|
||||
}
|
||||
|
||||
winrt::event_token EchoConnection::TerminalOutput(TerminalConnection::TerminalOutputEventArgs const& handler)
|
||||
{
|
||||
return _outputHandlers.add(handler);
|
||||
}
|
||||
|
||||
void EchoConnection::TerminalOutput(winrt::event_token const& token) noexcept
|
||||
{
|
||||
_outputHandlers.remove(token);
|
||||
}
|
||||
|
||||
winrt::event_token EchoConnection::TerminalDisconnected(TerminalConnection::TerminalDisconnectedEventArgs const& handler)
|
||||
{
|
||||
handler;
|
||||
throw hresult_not_implemented();
|
||||
}
|
||||
|
||||
void EchoConnection::TerminalDisconnected(winrt::event_token const& token) noexcept
|
||||
{
|
||||
token;
|
||||
}
|
||||
|
||||
void EchoConnection::Start()
|
||||
{
|
||||
}
|
||||
@ -56,7 +35,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
prettyPrint << wch;
|
||||
}
|
||||
}
|
||||
_outputHandlers(prettyPrint.str());
|
||||
_TerminalOutputHandlers(prettyPrint.str());
|
||||
}
|
||||
|
||||
void EchoConnection::Resize(uint32_t rows, uint32_t columns)
|
||||
|
||||
@ -5,23 +5,23 @@
|
||||
|
||||
#include "EchoConnection.g.h"
|
||||
|
||||
#include "../cascadia/inc/cppwinrt_utils.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
{
|
||||
struct EchoConnection : EchoConnectionT<EchoConnection>
|
||||
{
|
||||
EchoConnection();
|
||||
|
||||
winrt::event_token TerminalOutput(TerminalConnection::TerminalOutputEventArgs const& handler);
|
||||
void TerminalOutput(winrt::event_token const& token) noexcept;
|
||||
winrt::event_token TerminalDisconnected(TerminalConnection::TerminalDisconnectedEventArgs const& handler);
|
||||
void TerminalDisconnected(winrt::event_token const& token) noexcept;
|
||||
void Start();
|
||||
void WriteInput(hstring const& data);
|
||||
void Resize(uint32_t rows, uint32_t columns);
|
||||
void Close();
|
||||
|
||||
private:
|
||||
winrt::event<TerminalConnection::TerminalOutputEventArgs> _outputHandlers;
|
||||
ConnectionState State() const noexcept { return ConnectionState::Connected; }
|
||||
|
||||
WINRT_CALLBACK(TerminalOutput, TerminalOutputHandler);
|
||||
TYPED_EVENT(StateChanged, ITerminalConnection, IInspectable);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -3,18 +3,28 @@
|
||||
|
||||
namespace Microsoft.Terminal.TerminalConnection
|
||||
{
|
||||
delegate void TerminalOutputEventArgs(String output);
|
||||
delegate void TerminalDisconnectedEventArgs();
|
||||
enum ConnectionState
|
||||
{
|
||||
NotConnected = 0,
|
||||
Connecting,
|
||||
Connected,
|
||||
Closing,
|
||||
Closed,
|
||||
Failed
|
||||
};
|
||||
|
||||
delegate void TerminalOutputHandler(String output);
|
||||
|
||||
interface ITerminalConnection
|
||||
{
|
||||
event TerminalOutputEventArgs TerminalOutput;
|
||||
event TerminalDisconnectedEventArgs TerminalDisconnected;
|
||||
|
||||
void Start();
|
||||
void WriteInput(String data);
|
||||
void Resize(UInt32 rows, UInt32 columns);
|
||||
void Close();
|
||||
};
|
||||
|
||||
event TerminalOutputHandler TerminalOutput;
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<ITerminalConnection, Object> StateChanged;
|
||||
ConnectionState State { get; };
|
||||
};
|
||||
}
|
||||
|
||||
@ -205,4 +205,13 @@
|
||||
<value>n</value>
|
||||
<comment>The user must enter this character to signify NO</comment>
|
||||
</data>
|
||||
<data name="ProcessExited" xml:space="preserve">
|
||||
<value>[process exited with code %u]</value>
|
||||
<comment>The first argument (%u) is the (positive) error code of the process. 0 is success.</comment>
|
||||
</data>
|
||||
<data name="ProcessFailedToLaunch" xml:space="preserve">
|
||||
<value>[error 0x%8.08x when launching `%ls']</value>
|
||||
<comment>The first argument is the hexadecimal error code. The second is the process name.
|
||||
If this resource spans multiple lines, it will not be displayed properly. Yeah.</comment>
|
||||
</data>
|
||||
</root>
|
||||
@ -156,8 +156,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// DON'T CALL _InitializeTerminal here - wait until the swap chain is loaded to do that.
|
||||
|
||||
// Subscribe to the connection's disconnected event and call our connection closed handlers.
|
||||
_connection.TerminalDisconnected([=]() {
|
||||
_connectionClosedHandlers();
|
||||
_connectionStateChangedRevoker = _connection.StateChanged(winrt::auto_revoke, [weakThis = get_weak()](auto&& /*s*/, auto&& /*v*/) {
|
||||
if (auto strongThis{ weakThis.get() })
|
||||
{
|
||||
strongThis->_ConnectionStateChangedHandlers(*strongThis, nullptr);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -421,6 +424,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
return _swapChainPanel.Margin();
|
||||
}
|
||||
|
||||
TerminalConnection::ConnectionState TermControl::ConnectionState() const
|
||||
{
|
||||
return _connection.State();
|
||||
}
|
||||
|
||||
void TermControl::SwapChainChanged()
|
||||
{
|
||||
if (!_initializedTerminal)
|
||||
@ -1564,8 +1572,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
{
|
||||
if (!_closing.exchange(true))
|
||||
{
|
||||
// Stop accepting new output before we disconnect everything.
|
||||
// Stop accepting new output and state changes before we disconnect everything.
|
||||
_connection.TerminalOutput(_connectionOutputEventToken);
|
||||
_connectionStateChangedRevoker.revoke();
|
||||
|
||||
// Clear out the cursor timer, so it doesn't trigger again on us once we're destructed.
|
||||
if (auto localCursorTimer{ std::exchange(_cursorTimer, std::nullopt) })
|
||||
@ -1858,17 +1867,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
return flags;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Returns true if this control should close when its connection is closed.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - true iff the control should close when the connection is closed.
|
||||
bool TermControl::ShouldCloseOnExit() const noexcept
|
||||
{
|
||||
return _settings.CloseOnExit();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Gets the corresponding viewport terminal position for the cursor
|
||||
// by excluding the padding and normalizing with the font size.
|
||||
@ -1980,7 +1978,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// Winrt events need a method for adding a callback to the event and removing the callback.
|
||||
// These macros will define them both for you.
|
||||
DEFINE_EVENT(TermControl, TitleChanged, _titleChangedHandlers, TerminalControl::TitleChangedEventArgs);
|
||||
DEFINE_EVENT(TermControl, ConnectionClosed, _connectionClosedHandlers, TerminalControl::ConnectionClosedEventArgs);
|
||||
DEFINE_EVENT(TermControl, ScrollPositionChanged, _scrollPositionChangedHandlers, TerminalControl::ScrollPositionChangedEventArgs);
|
||||
|
||||
DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(TermControl, PasteFromClipboard, _clipboardPasteHandlers, TerminalControl::TermControl, TerminalControl::PasteFromClipboardEventArgs);
|
||||
|
||||
@ -62,7 +62,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
bool CopySelectionToClipboard(bool trimTrailingWhitespace);
|
||||
void PasteTextFromClipboard();
|
||||
void Close();
|
||||
bool ShouldCloseOnExit() const noexcept;
|
||||
Windows::Foundation::Size CharacterDimensions() const;
|
||||
Windows::Foundation::Size MinimumSize() const;
|
||||
|
||||
@ -82,16 +81,19 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
const FontInfo GetActualFont() const;
|
||||
const Windows::UI::Xaml::Thickness GetPadding() const;
|
||||
|
||||
TerminalConnection::ConnectionState ConnectionState() const;
|
||||
|
||||
static Windows::Foundation::Point GetProposedDimensions(Microsoft::Terminal::Settings::IControlSettings const& settings, const uint32_t dpi);
|
||||
|
||||
// clang-format off
|
||||
// -------------------------------- WinRT Events ---------------------------------
|
||||
DECLARE_EVENT(TitleChanged, _titleChangedHandlers, TerminalControl::TitleChangedEventArgs);
|
||||
DECLARE_EVENT(ConnectionClosed, _connectionClosedHandlers, TerminalControl::ConnectionClosedEventArgs);
|
||||
DECLARE_EVENT(ScrollPositionChanged, _scrollPositionChangedHandlers, TerminalControl::ScrollPositionChangedEventArgs);
|
||||
|
||||
DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(PasteFromClipboard, _clipboardPasteHandlers, TerminalControl::TermControl, TerminalControl::PasteFromClipboardEventArgs);
|
||||
DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(CopyToClipboard, _clipboardCopyHandlers, TerminalControl::TermControl, TerminalControl::CopyToClipboardEventArgs);
|
||||
|
||||
TYPED_EVENT(ConnectionStateChanged, TerminalControl::TermControl, IInspectable);
|
||||
// clang-format on
|
||||
|
||||
private:
|
||||
@ -105,6 +107,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
TSFInputControl _tsfInputControl;
|
||||
|
||||
event_token _connectionOutputEventToken;
|
||||
TerminalConnection::ITerminalConnection::StateChanged_revoker _connectionStateChangedRevoker;
|
||||
|
||||
std::unique_ptr<::Microsoft::Terminal::Core::Terminal> _terminal;
|
||||
|
||||
|
||||
@ -4,7 +4,6 @@
|
||||
namespace Microsoft.Terminal.TerminalControl
|
||||
{
|
||||
delegate void TitleChangedEventArgs(String newTitle);
|
||||
delegate void ConnectionClosedEventArgs();
|
||||
delegate void ScrollPositionChangedEventArgs(Int32 viewTop, Int32 viewHeight, Int32 bufferLength);
|
||||
|
||||
runtimeclass CopyToClipboardEventArgs
|
||||
@ -29,16 +28,19 @@ namespace Microsoft.Terminal.TerminalControl
|
||||
void UpdateSettings(Microsoft.Terminal.Settings.IControlSettings newSettings);
|
||||
|
||||
event TitleChangedEventArgs TitleChanged;
|
||||
event ConnectionClosedEventArgs ConnectionClosed;
|
||||
event Windows.Foundation.TypedEventHandler<TermControl, CopyToClipboardEventArgs> CopyToClipboard;
|
||||
event Windows.Foundation.TypedEventHandler<TermControl, PasteFromClipboardEventArgs> PasteFromClipboard;
|
||||
|
||||
// This is an event handler forwarder for the underlying connection.
|
||||
// We expose this and ConnectionState here so that it might eventually be data bound.
|
||||
event Windows.Foundation.TypedEventHandler<TermControl, IInspectable> ConnectionStateChanged;
|
||||
Microsoft.Terminal.TerminalConnection.ConnectionState ConnectionState { get; };
|
||||
|
||||
String Title { get; };
|
||||
|
||||
Boolean CopySelectionToClipboard(Boolean trimTrailingWhitespace);
|
||||
void PasteTextFromClipboard();
|
||||
void Close();
|
||||
Boolean ShouldCloseOnExit { get; };
|
||||
Windows.Foundation.Size CharacterDimensions { get; };
|
||||
Windows.Foundation.Size MinimumSize { get; };
|
||||
|
||||
|
||||
@ -20,7 +20,6 @@ namespace Microsoft.Terminal.Settings
|
||||
interface IControlSettings requires Microsoft.Terminal.Settings.ICoreSettings
|
||||
{
|
||||
Boolean UseAcrylic;
|
||||
Boolean CloseOnExit;
|
||||
Double TintOpacity;
|
||||
ScrollbarState ScrollState;
|
||||
|
||||
|
||||
@ -24,7 +24,6 @@ namespace winrt::Microsoft::Terminal::Settings::implementation
|
||||
_wordDelimiters{ DEFAULT_WORD_DELIMITERS },
|
||||
_copyOnSelect{ false },
|
||||
_useAcrylic{ false },
|
||||
_closeOnExit{ true },
|
||||
_tintOpacity{ 0.5 },
|
||||
_padding{ DEFAULT_PADDING },
|
||||
_fontFace{ DEFAULT_FONT_FACE },
|
||||
@ -181,16 +180,6 @@ namespace winrt::Microsoft::Terminal::Settings::implementation
|
||||
_useAcrylic = value;
|
||||
}
|
||||
|
||||
bool TerminalSettings::CloseOnExit()
|
||||
{
|
||||
return _closeOnExit;
|
||||
}
|
||||
|
||||
void TerminalSettings::CloseOnExit(bool value)
|
||||
{
|
||||
_closeOnExit = value;
|
||||
}
|
||||
|
||||
double TerminalSettings::TintOpacity()
|
||||
{
|
||||
return _tintOpacity;
|
||||
|
||||
@ -55,8 +55,6 @@ namespace winrt::Microsoft::Terminal::Settings::implementation
|
||||
|
||||
bool UseAcrylic();
|
||||
void UseAcrylic(bool value);
|
||||
bool CloseOnExit();
|
||||
void CloseOnExit(bool value);
|
||||
double TintOpacity();
|
||||
void TintOpacity(double value);
|
||||
hstring Padding();
|
||||
@ -114,7 +112,6 @@ namespace winrt::Microsoft::Terminal::Settings::implementation
|
||||
hstring _wordDelimiters;
|
||||
|
||||
bool _useAcrylic;
|
||||
bool _closeOnExit;
|
||||
double _tintOpacity;
|
||||
hstring _fontFace;
|
||||
int32_t _fontSize;
|
||||
|
||||
@ -71,6 +71,20 @@ public:
|
||||
private: \
|
||||
winrt::event<winrt::Windows::Foundation::TypedEventHandler<sender, args>> _##name##Handlers;
|
||||
|
||||
// This is a helper macro for both declaring the signature of a callback (nee event) and
|
||||
// defining the body. Winrt callbacks need a method for adding a delegate to the
|
||||
// callback and removing the delegate. This macro will both declare the method
|
||||
// signatures and define them both for you, because they don't really vary from
|
||||
// event to event.
|
||||
// Use this in a class's header if you have a "delegate" type in your IDL.
|
||||
#define WINRT_CALLBACK(name, args) \
|
||||
public: \
|
||||
winrt::event_token name(args const& handler) { return _##name##Handlers.add(handler); } \
|
||||
void name(winrt::event_token const& token) noexcept { _##name##Handlers.remove(token); } \
|
||||
\
|
||||
private: \
|
||||
winrt::event<args> _##name##Handlers;
|
||||
|
||||
// This is a helper macro for both declaring the signature and body of an event
|
||||
// which is exposed by one class, but actually handled entirely by one of the
|
||||
// class's members. This type of event could be considered "forwarded" or
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user