mirror of
https://github.com/microsoft/terminal.git
synced 2025-12-11 04:38:24 -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"` |
|
| `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. |
|
| `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"` |
|
| `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`. |
|
| `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`. |
|
| `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. |
|
| `commandline` | Optional | String | | Executable used in the profile. |
|
||||||
|
|||||||
@ -327,9 +327,21 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"closeOnExit": {
|
"closeOnExit": {
|
||||||
"default": true,
|
"default": "graceful",
|
||||||
"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.",
|
"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.",
|
||||||
"type": "boolean"
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"enum": [
|
||||||
|
"never",
|
||||||
|
"graceful",
|
||||||
|
"always"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"colorScheme": {
|
"colorScheme": {
|
||||||
"default": "Campbell",
|
"default": "Campbell",
|
||||||
|
|||||||
@ -55,6 +55,9 @@ namespace TerminalAppLocalTests
|
|||||||
TEST_METHOD(TestProfileIconWithEnvVar);
|
TEST_METHOD(TestProfileIconWithEnvVar);
|
||||||
TEST_METHOD(TestProfileBackgroundImageWithEnvVar);
|
TEST_METHOD(TestProfileBackgroundImageWithEnvVar);
|
||||||
|
|
||||||
|
TEST_METHOD(TestCloseOnExitParsing);
|
||||||
|
TEST_METHOD(TestCloseOnExitCompatibilityShim);
|
||||||
|
|
||||||
TEST_CLASS_SETUP(ClassSetup)
|
TEST_CLASS_SETUP(ClassSetup)
|
||||||
{
|
{
|
||||||
InitializeJsonReader();
|
InitializeJsonReader();
|
||||||
@ -1414,4 +1417,67 @@ namespace TerminalAppLocalTests
|
|||||||
auto terminalSettings = settings._profiles[0].CreateTerminalSettings(globalSettings.GetColorSchemes());
|
auto terminalSettings = settings._profiles[0].CreateTerminalSettings(globalSettings.GetColorSchemes());
|
||||||
VERIFY_ARE_EQUAL(expectedPath, terminalSettings.BackgroundImage());
|
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:
|
// Method Description:
|
||||||
// - Update the current theme of the application. This will trigger our
|
// - Update the current theme of the application. This will trigger our
|
||||||
// RequestedThemeChanged event, to have our host change the theme of the
|
// RequestedThemeChanged event, to have our host change the theme of the
|
||||||
|
|||||||
@ -29,6 +29,7 @@ namespace winrt::TerminalApp::implementation
|
|||||||
|
|
||||||
void Create();
|
void Create();
|
||||||
void LoadSettings();
|
void LoadSettings();
|
||||||
|
[[nodiscard]] std::shared_ptr<::TerminalApp::CascadiaSettings> GetSettings() const noexcept;
|
||||||
|
|
||||||
Windows::Foundation::Point GetLaunchDimensions(uint32_t dpi);
|
Windows::Foundation::Point GetLaunchDimensions(uint32_t dpi);
|
||||||
winrt::Windows::Foundation::Point GetLaunchInitialPositions(int32_t defaultInitialX, int32_t defaultInitialY);
|
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.SetColorScheme({ L"Vintage" });
|
||||||
azureCloudShellProfile.SetAcrylicOpacity(0.6);
|
azureCloudShellProfile.SetAcrylicOpacity(0.6);
|
||||||
azureCloudShellProfile.SetUseAcrylic(true);
|
azureCloudShellProfile.SetUseAcrylic(true);
|
||||||
azureCloudShellProfile.SetCloseOnExit(false);
|
|
||||||
azureCloudShellProfile.SetConnectionType(AzureConnectionType);
|
azureCloudShellProfile.SetConnectionType(AzureConnectionType);
|
||||||
profiles.emplace_back(azureCloudShellProfile);
|
profiles.emplace_back(azureCloudShellProfile);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,7 @@
|
|||||||
#include "CascadiaSettings.h"
|
#include "CascadiaSettings.h"
|
||||||
#include "../../types/inc/utils.hpp"
|
#include "../../types/inc/utils.hpp"
|
||||||
#include "../../inc/DefaultSettings.h"
|
#include "../../inc/DefaultSettings.h"
|
||||||
|
#include "AppLogic.h"
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
|
|
||||||
#include "PowershellCoreProfileGenerator.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 PACKAGED_PROFILE_ICON_EXTENSION{ L".png" };
|
||||||
static constexpr std::wstring_view DEFAULT_LINUX_ICON_GUID{ L"{9acb9455-ca41-5af7-950f-6bca1bc9722f}" };
|
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::CascadiaSettings() :
|
||||||
CascadiaSettings(true)
|
CascadiaSettings(true)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -49,6 +49,8 @@ public:
|
|||||||
static std::unique_ptr<CascadiaSettings> LoadDefaults();
|
static std::unique_ptr<CascadiaSettings> LoadDefaults();
|
||||||
static std::unique_ptr<CascadiaSettings> LoadAll();
|
static std::unique_ptr<CascadiaSettings> LoadAll();
|
||||||
|
|
||||||
|
static const CascadiaSettings& GetCurrentAppSettings();
|
||||||
|
|
||||||
winrt::Microsoft::Terminal::Settings::TerminalSettings MakeSettings(std::optional<GUID> profileGuid) const;
|
winrt::Microsoft::Terminal::Settings::TerminalSettings MakeSettings(std::optional<GUID> profileGuid) const;
|
||||||
|
|
||||||
GlobalAppSettings& GlobalSettings();
|
GlobalAppSettings& GlobalSettings();
|
||||||
|
|||||||
@ -3,6 +3,10 @@
|
|||||||
|
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
#include "Pane.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::Foundation;
|
||||||
using namespace winrt::Windows::UI;
|
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::Windows::UI::Xaml::Media;
|
||||||
using namespace winrt::Microsoft::Terminal::Settings;
|
using namespace winrt::Microsoft::Terminal::Settings;
|
||||||
using namespace winrt::Microsoft::Terminal::TerminalControl;
|
using namespace winrt::Microsoft::Terminal::TerminalControl;
|
||||||
|
using namespace winrt::Microsoft::Terminal::TerminalConnection;
|
||||||
using namespace winrt::TerminalApp;
|
using namespace winrt::TerminalApp;
|
||||||
|
using namespace TerminalApp;
|
||||||
|
|
||||||
static const int PaneBorderSize = 2;
|
static const int PaneBorderSize = 2;
|
||||||
static const int CombinedPaneBorderSize = 2 * PaneBorderSize;
|
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);
|
_root.Children().Append(_border);
|
||||||
_border.Child(_control);
|
_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
|
// 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
|
// Pane, including the brushed to use for the focused/unfocused border
|
||||||
@ -302,7 +308,7 @@ bool Pane::NavigateFocus(const Direction& direction)
|
|||||||
// - <none>
|
// - <none>
|
||||||
// Return Value:
|
// Return Value:
|
||||||
// - <none>
|
// - <none>
|
||||||
void Pane::_ControlClosedHandler()
|
void Pane::_ControlConnectionStateChangedHandler(const TermControl& /*sender*/, const winrt::Windows::Foundation::IInspectable& /*args*/)
|
||||||
{
|
{
|
||||||
std::unique_lock lock{ _createCloseLock };
|
std::unique_lock lock{ _createCloseLock };
|
||||||
// It's possible that this event handler started being executed, then before
|
// It's possible that this event handler started being executed, then before
|
||||||
@ -317,10 +323,24 @@ void Pane::_ControlClosedHandler()
|
|||||||
return;
|
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.
|
// Pane doesn't care if the connection isn't entering a terminal state.
|
||||||
_closedHandlers();
|
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()
|
void Pane::Close()
|
||||||
{
|
{
|
||||||
// Fire our Closed event to tell our parent that we should be removed.
|
// Fire our Closed event to tell our parent that we should be removed.
|
||||||
_closedHandlers();
|
_ClosedHandlers(nullptr, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Method Description:
|
// Method Description:
|
||||||
@ -570,7 +590,7 @@ void Pane::_CloseChild(const bool closeFirst)
|
|||||||
_profile = remainingChild->_profile;
|
_profile = remainingChild->_profile;
|
||||||
|
|
||||||
// Add our new event handler before revoking the old one.
|
// 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
|
// Revoke the old event handlers. Remove both the handlers for the panes
|
||||||
// themselves closing, and remove their handlers for their controls
|
// 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.
|
// they'll trigger only our event handler for the control's close.
|
||||||
_firstChild->Closed(_firstClosedToken);
|
_firstChild->Closed(_firstClosedToken);
|
||||||
_secondChild->Closed(_secondClosedToken);
|
_secondChild->Closed(_secondClosedToken);
|
||||||
closedChild->_control.ConnectionClosed(closedChild->_connectionClosedToken);
|
closedChild->_control.ConnectionStateChanged(closedChild->_connectionStateChangedToken);
|
||||||
remainingChild->_control.ConnectionClosed(remainingChild->_connectionClosedToken);
|
remainingChild->_control.ConnectionStateChanged(remainingChild->_connectionStateChangedToken);
|
||||||
|
|
||||||
// If either of our children was focused, we want to take that focus from
|
// If either of our children was focused, we want to take that focus from
|
||||||
// them.
|
// them.
|
||||||
@ -659,7 +679,7 @@ void Pane::_CloseChild(const bool closeFirst)
|
|||||||
// Revoke event handlers on old panes and controls
|
// Revoke event handlers on old panes and controls
|
||||||
oldFirst->Closed(oldFirstToken);
|
oldFirst->Closed(oldFirstToken);
|
||||||
oldSecond->Closed(oldSecondToken);
|
oldSecond->Closed(oldSecondToken);
|
||||||
closedChild->_control.ConnectionClosed(closedChild->_connectionClosedToken);
|
closedChild->_control.ConnectionStateChanged(closedChild->_connectionStateChangedToken);
|
||||||
|
|
||||||
// Reset our UI:
|
// Reset our UI:
|
||||||
_root.Children().Clear();
|
_root.Children().Clear();
|
||||||
@ -720,13 +740,13 @@ void Pane::_CloseChild(const bool closeFirst)
|
|||||||
// - <none>
|
// - <none>
|
||||||
void Pane::_SetupChildCloseHandlers()
|
void Pane::_SetupChildCloseHandlers()
|
||||||
{
|
{
|
||||||
_firstClosedToken = _firstChild->Closed([this]() {
|
_firstClosedToken = _firstChild->Closed([this](auto&& /*s*/, auto&& /*e*/) {
|
||||||
_root.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [=]() {
|
_root.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [=]() {
|
||||||
_CloseChild(true);
|
_CloseChild(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
_secondClosedToken = _secondChild->Closed([this]() {
|
_secondClosedToken = _secondChild->Closed([this](auto&& /*s*/, auto&& /*e*/) {
|
||||||
_root.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [=]() {
|
_root.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [=]() {
|
||||||
_CloseChild(false);
|
_CloseChild(false);
|
||||||
});
|
});
|
||||||
@ -965,8 +985,8 @@ std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Pane::_Split(SplitState
|
|||||||
std::unique_lock lock{ _createCloseLock };
|
std::unique_lock lock{ _createCloseLock };
|
||||||
|
|
||||||
// revoke our handler - the child will take care of the control now.
|
// revoke our handler - the child will take care of the control now.
|
||||||
_control.ConnectionClosed(_connectionClosedToken);
|
_control.ConnectionStateChanged(_connectionStateChangedToken);
|
||||||
_connectionClosedToken.value = 0;
|
_connectionStateChangedToken.value = 0;
|
||||||
|
|
||||||
// Remove our old GotFocus handler from the control. We don't what the
|
// 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
|
// 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>>);
|
DEFINE_EVENT(Pane, GotFocus, _GotFocusHandlers, winrt::delegate<std::shared_ptr<Pane>>);
|
||||||
|
|||||||
@ -71,7 +71,7 @@ public:
|
|||||||
|
|
||||||
void Close();
|
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>>);
|
DECLARE_EVENT(GotFocus, _GotFocusHandlers, winrt::delegate<std::shared_ptr<Pane>>);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -89,7 +89,7 @@ private:
|
|||||||
|
|
||||||
bool _lastActive{ false };
|
bool _lastActive{ false };
|
||||||
std::optional<GUID> _profile{ std::nullopt };
|
std::optional<GUID> _profile{ std::nullopt };
|
||||||
winrt::event_token _connectionClosedToken{ 0 };
|
winrt::event_token _connectionStateChangedToken{ 0 };
|
||||||
winrt::event_token _firstClosedToken{ 0 };
|
winrt::event_token _firstClosedToken{ 0 };
|
||||||
winrt::event_token _secondClosedToken{ 0 };
|
winrt::event_token _secondClosedToken{ 0 };
|
||||||
|
|
||||||
@ -119,7 +119,7 @@ private:
|
|||||||
void _CloseChild(const bool closeFirst);
|
void _CloseChild(const bool closeFirst);
|
||||||
|
|
||||||
void _FocusFirstChild();
|
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);
|
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 BackgroundImageStretchModeKey{ "backgroundImageStretchMode" };
|
||||||
static constexpr std::string_view BackgroundImageAlignmentKey{ "backgroundImageAlignment" };
|
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
|
// Possible values for Scrollbar state
|
||||||
static constexpr std::wstring_view AlwaysVisible{ L"visible" };
|
static constexpr std::wstring_view AlwaysVisible{ L"visible" };
|
||||||
static constexpr std::wstring_view AlwaysHide{ L"hidden" };
|
static constexpr std::wstring_view AlwaysHide{ L"hidden" };
|
||||||
@ -109,7 +114,7 @@ Profile::Profile(const std::optional<GUID>& guid) :
|
|||||||
_acrylicTransparency{ 0.5 },
|
_acrylicTransparency{ 0.5 },
|
||||||
_useAcrylic{ false },
|
_useAcrylic{ false },
|
||||||
_scrollbarState{},
|
_scrollbarState{},
|
||||||
_closeOnExit{ true },
|
_closeOnExitMode{ CloseOnExitMode::Graceful },
|
||||||
_padding{ DEFAULT_PADDING },
|
_padding{ DEFAULT_PADDING },
|
||||||
_icon{},
|
_icon{},
|
||||||
_backgroundImage{},
|
_backgroundImage{},
|
||||||
@ -170,7 +175,6 @@ TerminalSettings Profile::CreateTerminalSettings(const std::unordered_map<std::w
|
|||||||
|
|
||||||
// Fill in the remaining properties from the profile
|
// Fill in the remaining properties from the profile
|
||||||
terminalSettings.UseAcrylic(_useAcrylic);
|
terminalSettings.UseAcrylic(_useAcrylic);
|
||||||
terminalSettings.CloseOnExit(_closeOnExit);
|
|
||||||
terminalSettings.TintOpacity(_acrylicTransparency);
|
terminalSettings.TintOpacity(_acrylicTransparency);
|
||||||
|
|
||||||
terminalSettings.FontFace(_fontFace);
|
terminalSettings.FontFace(_fontFace);
|
||||||
@ -299,13 +303,11 @@ Json::Value Profile::ToJson() const
|
|||||||
}
|
}
|
||||||
root[JsonKey(CursorShapeKey)] = winrt::to_string(_SerializeCursorStyle(_cursorShape));
|
root[JsonKey(CursorShapeKey)] = winrt::to_string(_SerializeCursorStyle(_cursorShape));
|
||||||
|
|
||||||
///// Control Settings /////
|
|
||||||
root[JsonKey(CommandlineKey)] = winrt::to_string(_commandline);
|
root[JsonKey(CommandlineKey)] = winrt::to_string(_commandline);
|
||||||
root[JsonKey(FontFaceKey)] = winrt::to_string(_fontFace);
|
root[JsonKey(FontFaceKey)] = winrt::to_string(_fontFace);
|
||||||
root[JsonKey(FontSizeKey)] = _fontSize;
|
root[JsonKey(FontSizeKey)] = _fontSize;
|
||||||
root[JsonKey(AcrylicTransparencyKey)] = _acrylicTransparency;
|
root[JsonKey(AcrylicTransparencyKey)] = _acrylicTransparency;
|
||||||
root[JsonKey(UseAcrylicKey)] = _useAcrylic;
|
root[JsonKey(UseAcrylicKey)] = _useAcrylic;
|
||||||
root[JsonKey(CloseOnExitKey)] = _closeOnExit;
|
|
||||||
root[JsonKey(PaddingKey)] = winrt::to_string(_padding);
|
root[JsonKey(PaddingKey)] = winrt::to_string(_padding);
|
||||||
|
|
||||||
if (_connectionType)
|
if (_connectionType)
|
||||||
@ -359,6 +361,8 @@ Json::Value Profile::ToJson() const
|
|||||||
root[JsonKey(BackgroundImageAlignmentKey)] = SerializeImageAlignment(_backgroundImageAlignment.value()).data();
|
root[JsonKey(BackgroundImageAlignmentKey)] = SerializeImageAlignment(_backgroundImageAlignment.value()).data();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
root[JsonKey(CloseOnExitKey)] = _SerializeCloseOnExitMode(_closeOnExitMode).data();
|
||||||
|
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -689,7 +693,7 @@ void Profile::LayerJson(const Json::Value& json)
|
|||||||
if (json.isMember(JsonKey(CloseOnExitKey)))
|
if (json.isMember(JsonKey(CloseOnExitKey)))
|
||||||
{
|
{
|
||||||
auto closeOnExit{ json[JsonKey(CloseOnExitKey)] };
|
auto closeOnExit{ json[JsonKey(CloseOnExitKey)] };
|
||||||
_closeOnExit = closeOnExit.asBool();
|
_closeOnExitMode = ParseCloseOnExitMode(closeOnExit);
|
||||||
}
|
}
|
||||||
if (json.isMember(JsonKey(PaddingKey)))
|
if (json.isMember(JsonKey(PaddingKey)))
|
||||||
{
|
{
|
||||||
@ -767,9 +771,9 @@ void Profile::SetSelectionBackground(COLORREF selectionBackground) noexcept
|
|||||||
_selectionBackground = selectionBackground;
|
_selectionBackground = selectionBackground;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Profile::SetCloseOnExit(bool defaultClose) noexcept
|
void Profile::SetCloseOnExitMode(CloseOnExitMode mode) noexcept
|
||||||
{
|
{
|
||||||
_closeOnExit = defaultClose;
|
_closeOnExitMode = mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Profile::SetConnectionType(GUID connectionType) noexcept
|
void Profile::SetConnectionType(GUID connectionType) noexcept
|
||||||
@ -876,9 +880,9 @@ GUID Profile::GetConnectionType() const noexcept
|
|||||||
_GUID{};
|
_GUID{};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Profile::GetCloseOnExit() const noexcept
|
CloseOnExitMode Profile::GetCloseOnExitMode() const noexcept
|
||||||
{
|
{
|
||||||
return _closeOnExit;
|
return _closeOnExitMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Method Description:
|
// 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:
|
// Method Description:
|
||||||
// - Helper function for converting a user-specified scrollbar state to its corresponding enum
|
// - Helper function for converting a user-specified scrollbar state to its corresponding enum
|
||||||
// Arguments:
|
// Arguments:
|
||||||
|
|||||||
@ -35,6 +35,13 @@ constexpr GUID RUNTIME_GENERATED_PROFILE_NAMESPACE_GUID = { 0xf65ddb7e, 0x706b,
|
|||||||
namespace TerminalApp
|
namespace TerminalApp
|
||||||
{
|
{
|
||||||
class Profile;
|
class Profile;
|
||||||
|
|
||||||
|
enum class CloseOnExitMode
|
||||||
|
{
|
||||||
|
Never = 0,
|
||||||
|
Graceful,
|
||||||
|
Always
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
class TerminalApp::Profile final
|
class TerminalApp::Profile final
|
||||||
@ -76,7 +83,7 @@ public:
|
|||||||
void SetDefaultForeground(COLORREF defaultForeground) noexcept;
|
void SetDefaultForeground(COLORREF defaultForeground) noexcept;
|
||||||
void SetDefaultBackground(COLORREF defaultBackground) noexcept;
|
void SetDefaultBackground(COLORREF defaultBackground) noexcept;
|
||||||
void SetSelectionBackground(COLORREF selectionBackground) noexcept;
|
void SetSelectionBackground(COLORREF selectionBackground) noexcept;
|
||||||
void SetCloseOnExit(bool defaultClose) noexcept;
|
void SetCloseOnExitMode(CloseOnExitMode mode) noexcept;
|
||||||
void SetConnectionType(GUID connectionType) noexcept;
|
void SetConnectionType(GUID connectionType) noexcept;
|
||||||
|
|
||||||
bool HasIcon() const noexcept;
|
bool HasIcon() const noexcept;
|
||||||
@ -86,7 +93,7 @@ public:
|
|||||||
bool HasBackgroundImage() const noexcept;
|
bool HasBackgroundImage() const noexcept;
|
||||||
winrt::hstring GetExpandedBackgroundImagePath() const;
|
winrt::hstring GetExpandedBackgroundImagePath() const;
|
||||||
|
|
||||||
bool GetCloseOnExit() const noexcept;
|
CloseOnExitMode GetCloseOnExitMode() const noexcept;
|
||||||
bool GetSuppressApplicationTitle() const noexcept;
|
bool GetSuppressApplicationTitle() const noexcept;
|
||||||
bool IsHidden() 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> 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 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 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 winrt::Microsoft::Terminal::Settings::CursorStyle _ParseCursorShape(const std::wstring& cursorShapeString);
|
||||||
static std::wstring_view _SerializeCursorStyle(const winrt::Microsoft::Terminal::Settings::CursorStyle cursorShape);
|
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::tuple<winrt::Windows::UI::Xaml::HorizontalAlignment, winrt::Windows::UI::Xaml::VerticalAlignment>> _backgroundImageAlignment;
|
||||||
|
|
||||||
std::optional<std::wstring> _scrollbarState;
|
std::optional<std::wstring> _scrollbarState;
|
||||||
bool _closeOnExit;
|
CloseOnExitMode _closeOnExitMode;
|
||||||
std::wstring _padding;
|
std::wstring _padding;
|
||||||
|
|
||||||
std::optional<std::wstring> _icon;
|
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 = std::make_shared<Pane>(profile, control, true);
|
||||||
|
|
||||||
_rootPane->Closed([=]() {
|
_rootPane->Closed([=](auto&& /*s*/, auto&& /*e*/) {
|
||||||
_closedHandlers();
|
_ClosedHandlers(nullptr, nullptr);
|
||||||
});
|
});
|
||||||
|
|
||||||
_activePane = _rootPane;
|
_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<>);
|
DEFINE_EVENT(Tab, ActivePaneChanged, _ActivePaneChangedHandlers, winrt::delegate<>);
|
||||||
|
|||||||
@ -35,7 +35,7 @@ public:
|
|||||||
|
|
||||||
void ClosePane();
|
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<>);
|
DECLARE_EVENT(ActivePaneChanged, _ActivePaneChangedHandlers, winrt::delegate<>);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@ -464,7 +464,7 @@ namespace winrt::TerminalApp::implementation
|
|||||||
tabViewItem.PointerPressed({ this, &TerminalPage::_OnTabClick });
|
tabViewItem.PointerPressed({ this, &TerminalPage::_OnTabClick });
|
||||||
|
|
||||||
// When the tab is closed, remove it from our list of tabs.
|
// 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]() {
|
_tabView.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [tabViewItem, this]() {
|
||||||
_RemoveTabViewItem(tabViewItem);
|
_RemoveTabViewItem(tabViewItem);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -17,7 +17,7 @@
|
|||||||
"commandline": "powershell.exe",
|
"commandline": "powershell.exe",
|
||||||
"hidden": false,
|
"hidden": false,
|
||||||
"startingDirectory": "%USERPROFILE%",
|
"startingDirectory": "%USERPROFILE%",
|
||||||
"closeOnExit": true,
|
"closeOnExit": "graceful",
|
||||||
"colorScheme": "Campbell Powershell",
|
"colorScheme": "Campbell Powershell",
|
||||||
"cursorColor": "#FFFFFF",
|
"cursorColor": "#FFFFFF",
|
||||||
"cursorShape": "bar",
|
"cursorShape": "bar",
|
||||||
@ -35,7 +35,7 @@
|
|||||||
"commandline": "cmd.exe",
|
"commandline": "cmd.exe",
|
||||||
"hidden": false,
|
"hidden": false,
|
||||||
"startingDirectory": "%USERPROFILE%",
|
"startingDirectory": "%USERPROFILE%",
|
||||||
"closeOnExit": true,
|
"closeOnExit": "graceful",
|
||||||
"colorScheme": "Campbell",
|
"colorScheme": "Campbell",
|
||||||
"cursorColor": "#FFFFFF",
|
"cursorColor": "#FFFFFF",
|
||||||
"cursorShape": "bar",
|
"cursorShape": "bar",
|
||||||
|
|||||||
@ -49,51 +49,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||||||
// - str: the string to write.
|
// - str: the string to write.
|
||||||
void AzureConnection::_WriteStringWithNewline(const winrt::hstring& str)
|
void AzureConnection::_WriteStringWithNewline(const winrt::hstring& str)
|
||||||
{
|
{
|
||||||
_outputHandlers(str + L"\r\n");
|
_TerminalOutputHandlers(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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Method description:
|
// Method description:
|
||||||
@ -112,7 +68,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||||||
|
|
||||||
THROW_LAST_ERROR_IF_NULL(_hOutputThread);
|
THROW_LAST_ERROR_IF_NULL(_hOutputThread);
|
||||||
|
|
||||||
_connected = true;
|
_transitionToState(ConnectionState::Connecting);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Method description:
|
// Method description:
|
||||||
@ -122,7 +78,8 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||||||
// the user's input
|
// the user's input
|
||||||
void AzureConnection::WriteInput(hstring const& data)
|
void AzureConnection::WriteInput(hstring const& data)
|
||||||
{
|
{
|
||||||
if (!_connected || _closing.load())
|
// We read input while connected AND connecting.
|
||||||
|
if (!_isStateOneOf(ConnectionState::Connected, ConnectionState::Connecting))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -131,7 +88,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||||||
switch (_state)
|
switch (_state)
|
||||||
{
|
{
|
||||||
// The user has stored connection settings, let them choose one of them, create a new one or remove all stored ones
|
// 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);
|
const auto s = winrt::to_string(data);
|
||||||
int storeNum = -1;
|
int storeNum = -1;
|
||||||
@ -172,7 +129,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// The user has multiple tenants in their Azure account, let them choose one of them
|
// The user has multiple tenants in their Azure account, let them choose one of them
|
||||||
case State::TenantChoice:
|
case AzureState::TenantChoice:
|
||||||
{
|
{
|
||||||
int tenantNum = -1;
|
int tenantNum = -1;
|
||||||
try
|
try
|
||||||
@ -195,7 +152,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// User has the option to save their connection settings for future logins
|
// 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 };
|
std::lock_guard<std::mutex> lg{ _commonMutex };
|
||||||
if (data == RS_(L"AzureUserEntry_Yes"))
|
if (data == RS_(L"AzureUserEntry_Yes"))
|
||||||
@ -218,7 +175,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// We are connected, send user's input over the websocket
|
// We are connected, send user's input over the websocket
|
||||||
case State::TermConnected:
|
case AzureState::TermConnected:
|
||||||
{
|
{
|
||||||
websocket_outgoing_message msg;
|
websocket_outgoing_message msg;
|
||||||
const auto str = winrt::to_string(data);
|
const auto str = winrt::to_string(data);
|
||||||
@ -238,12 +195,12 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||||||
// - the new rows/cols values
|
// - the new rows/cols values
|
||||||
void AzureConnection::Resize(uint32_t rows, uint32_t columns)
|
void AzureConnection::Resize(uint32_t rows, uint32_t columns)
|
||||||
{
|
{
|
||||||
if (!_connected || !(_state == State::TermConnected))
|
if (!_isConnected())
|
||||||
{
|
{
|
||||||
_initialRows = rows;
|
_initialRows = rows;
|
||||||
_initialCols = columns;
|
_initialCols = columns;
|
||||||
}
|
}
|
||||||
else if (!_closing.load())
|
else // We only transition to Connected when we've established the websocket.
|
||||||
{
|
{
|
||||||
// Initialize client
|
// Initialize client
|
||||||
http_client terminalClient(_cloudShellUri);
|
http_client terminalClient(_cloudShellUri);
|
||||||
@ -264,24 +221,24 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||||||
// - closes the websocket connection and the output thread
|
// - closes the websocket connection and the output thread
|
||||||
void AzureConnection::Close()
|
void AzureConnection::Close()
|
||||||
{
|
{
|
||||||
if (!_connected)
|
if (_transitionToState(ConnectionState::Closing))
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_closing.exchange(true))
|
|
||||||
{
|
{
|
||||||
_canProceed.notify_all();
|
_canProceed.notify_all();
|
||||||
if (_state == State::TermConnected)
|
if (_state == AzureState::TermConnected)
|
||||||
{
|
{
|
||||||
// Close the websocket connection
|
// Close the websocket connection
|
||||||
auto closedTask = _cloudShellSocket.close();
|
auto closedTask = _cloudShellSocket.close();
|
||||||
closedTask.wait();
|
closedTask.wait();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tear down our output thread
|
if (_hOutputThread)
|
||||||
WaitForSingleObject(_hOutputThread.get(), INFINITE);
|
{
|
||||||
_hOutputThread.reset();
|
// 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)
|
while (true)
|
||||||
{
|
{
|
||||||
|
if (_isStateAtOrBeyond(ConnectionState::Closing))
|
||||||
|
{
|
||||||
|
// If we enter a new state while closing, just bail.
|
||||||
|
return S_FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
switch (_state)
|
switch (_state)
|
||||||
{
|
{
|
||||||
// Initial state, check if the user has any stored connection settings and allow them to login with those
|
// 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
|
// 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());
|
RETURN_IF_FAILED(_AccessHelper());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// User has no saved connection settings or has opted to login with a different account
|
// User has no saved connection settings or has opted to login with a different account
|
||||||
// Azure authentication happens here
|
// Azure authentication happens here
|
||||||
case State::DeviceFlow:
|
case AzureState::DeviceFlow:
|
||||||
{
|
{
|
||||||
RETURN_IF_FAILED(_DeviceFlowHelper());
|
RETURN_IF_FAILED(_DeviceFlowHelper());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// User has multiple tenants in their Azure account, they need to choose which one to connect to
|
// 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());
|
RETURN_IF_FAILED(_TenantChoiceHelper());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// Ask the user if they want to save these connection settings for future logins
|
// Ask the user if they want to save these connection settings for future logins
|
||||||
case State::StoreTokens:
|
case AzureState::StoreTokens:
|
||||||
{
|
{
|
||||||
RETURN_IF_FAILED(_StoreHelper());
|
RETURN_IF_FAILED(_StoreHelper());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// Connect to Azure, we only get here once we have everything we need (tenantID, accessToken, refreshToken)
|
// 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());
|
RETURN_IF_FAILED(_ConnectHelper());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// We are connected, continuously read from the websocket until its closed
|
// We are connected, continuously read from the websocket until its closed
|
||||||
case State::TermConnected:
|
case AzureState::TermConnected:
|
||||||
{
|
{
|
||||||
|
_transitionToState(ConnectionState::Connected);
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
// Read from websocket
|
// Read from websocket
|
||||||
@ -370,15 +334,15 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||||||
}
|
}
|
||||||
catch (...)
|
catch (...)
|
||||||
{
|
{
|
||||||
// Websocket has been closed
|
// Websocket has been closed; consider it a graceful exit?
|
||||||
if (!_closing.load())
|
// This should result in our termination.
|
||||||
|
if (_transitionToState(ConnectionState::Closed))
|
||||||
{
|
{
|
||||||
_state = State::NoConnect;
|
// End the output thread.
|
||||||
_disconnectHandlers();
|
|
||||||
return S_FALSE;
|
return S_FALSE;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto msg = msgT.get();
|
auto msg = msgT.get();
|
||||||
auto msgStringTask = msg.extract_string();
|
auto msgStringTask = msg.extract_string();
|
||||||
auto msgString = msgStringTask.get();
|
auto msgString = msgStringTask.get();
|
||||||
@ -387,21 +351,21 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||||||
const auto hstr = winrt::to_hstring(msgString);
|
const auto hstr = winrt::to_hstring(msgString);
|
||||||
|
|
||||||
// Pass the output to our registered event handlers
|
// Pass the output to our registered event handlers
|
||||||
_outputHandlers(hstr);
|
_TerminalOutputHandlers(hstr);
|
||||||
}
|
}
|
||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
case State::NoConnect:
|
case AzureState::NoConnect:
|
||||||
{
|
{
|
||||||
_WriteStringWithNewline(RS_(L"AzureInternetOrServerIssue"));
|
_WriteStringWithNewline(RS_(L"AzureInternetOrServerIssue"));
|
||||||
_disconnectHandlers();
|
_transitionToState(ConnectionState::Failed);
|
||||||
return E_FAIL;
|
return E_FAIL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (...)
|
catch (...)
|
||||||
{
|
{
|
||||||
_state = State::NoConnect;
|
_state = AzureState::NoConnect;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -425,7 +389,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||||||
catch (...)
|
catch (...)
|
||||||
{
|
{
|
||||||
// No credentials are stored, so start the device flow
|
// No credentials are stored, so start the device flow
|
||||||
_state = State::DeviceFlow;
|
_state = AzureState::DeviceFlow;
|
||||||
return S_FALSE;
|
return S_FALSE;
|
||||||
}
|
}
|
||||||
_maxStored = 0;
|
_maxStored = 0;
|
||||||
@ -458,7 +422,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||||||
_WriteStringWithNewline(RS_(L"AzureOldCredentialsFlushedMessage"));
|
_WriteStringWithNewline(RS_(L"AzureOldCredentialsFlushedMessage"));
|
||||||
}
|
}
|
||||||
// No valid up-to-date credentials were found, so start the device flow
|
// No valid up-to-date credentials were found, so start the device flow
|
||||||
_state = State::DeviceFlow;
|
_state = AzureState::DeviceFlow;
|
||||||
return S_FALSE;
|
return S_FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -468,10 +432,10 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||||||
|
|
||||||
std::unique_lock<std::mutex> storedLock{ _commonMutex };
|
std::unique_lock<std::mutex> storedLock{ _commonMutex };
|
||||||
_canProceed.wait(storedLock, [=]() {
|
_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
|
// User might have closed the tab while we waited for input
|
||||||
if (_closing.load())
|
if (_isStateAtOrBeyond(ConnectionState::Closing))
|
||||||
{
|
{
|
||||||
return E_FAIL;
|
return E_FAIL;
|
||||||
}
|
}
|
||||||
@ -479,13 +443,13 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||||||
{
|
{
|
||||||
// User wants to remove the stored settings
|
// User wants to remove the stored settings
|
||||||
_RemoveCredentials();
|
_RemoveCredentials();
|
||||||
_state = State::DeviceFlow;
|
_state = AzureState::DeviceFlow;
|
||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
else if (_removeOrNew.has_value() && !_removeOrNew.value())
|
else if (_removeOrNew.has_value() && !_removeOrNew.value())
|
||||||
{
|
{
|
||||||
// User wants to login with a different account
|
// User wants to login with a different account
|
||||||
_state = State::DeviceFlow;
|
_state = AzureState::DeviceFlow;
|
||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
// User wants to login with one of the saved connection settings
|
// 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
|
// We have everything we need, so go ahead and connect
|
||||||
_state = State::TermConnecting;
|
_state = AzureState::TermConnecting;
|
||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -558,6 +522,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||||||
if (tenantListAsArray.size() == 0)
|
if (tenantListAsArray.size() == 0)
|
||||||
{
|
{
|
||||||
_WriteStringWithNewline(RS_(L"AzureNoTenants"));
|
_WriteStringWithNewline(RS_(L"AzureNoTenants"));
|
||||||
|
_transitionToState(ConnectionState::Failed);
|
||||||
return E_FAIL;
|
return E_FAIL;
|
||||||
}
|
}
|
||||||
else if (_tenantList.size() == 1)
|
else if (_tenantList.size() == 1)
|
||||||
@ -571,11 +536,11 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||||||
_refreshToken = refreshResponse.at(L"refresh_token").as_string();
|
_refreshToken = refreshResponse.at(L"refresh_token").as_string();
|
||||||
_expiry = std::stoi(refreshResponse.at(L"expires_on").as_string());
|
_expiry = std::stoi(refreshResponse.at(L"expires_on").as_string());
|
||||||
|
|
||||||
_state = State::StoreTokens;
|
_state = AzureState::StoreTokens;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_state = State::TenantChoice;
|
_state = AzureState::TenantChoice;
|
||||||
}
|
}
|
||||||
return S_OK;
|
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
|
// Use a lock to wait for the user to input a valid number
|
||||||
std::unique_lock<std::mutex> tenantNumberLock{ _commonMutex };
|
std::unique_lock<std::mutex> tenantNumberLock{ _commonMutex };
|
||||||
_canProceed.wait(tenantNumberLock, [=]() {
|
_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
|
// User might have closed the tab while we waited for input
|
||||||
if (_closing.load())
|
if (_isStateAtOrBeyond(ConnectionState::Closing))
|
||||||
{
|
{
|
||||||
return E_FAIL;
|
return E_FAIL;
|
||||||
}
|
}
|
||||||
@ -619,7 +584,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||||||
_refreshToken = refreshResponse.at(L"refresh_token").as_string();
|
_refreshToken = refreshResponse.at(L"refresh_token").as_string();
|
||||||
_expiry = std::stoi(refreshResponse.at(L"expires_on").as_string());
|
_expiry = std::stoi(refreshResponse.at(L"expires_on").as_string());
|
||||||
|
|
||||||
_state = State::StoreTokens;
|
_state = AzureState::StoreTokens;
|
||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
CATCH_RETURN();
|
CATCH_RETURN();
|
||||||
@ -636,10 +601,10 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||||||
// Wait for user input
|
// Wait for user input
|
||||||
std::unique_lock<std::mutex> storeLock{ _commonMutex };
|
std::unique_lock<std::mutex> storeLock{ _commonMutex };
|
||||||
_canProceed.wait(storeLock, [=]() {
|
_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
|
// User might have closed the tab while we waited for input
|
||||||
if (_closing.load())
|
if (_isStateAtOrBeyond(ConnectionState::Closing))
|
||||||
{
|
{
|
||||||
return E_FAIL;
|
return E_FAIL;
|
||||||
}
|
}
|
||||||
@ -651,7 +616,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||||||
_WriteStringWithNewline(RS_(L"AzureTokensStored"));
|
_WriteStringWithNewline(RS_(L"AzureTokensStored"));
|
||||||
}
|
}
|
||||||
|
|
||||||
_state = State::TermConnecting;
|
_state = AzureState::TermConnecting;
|
||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -667,6 +632,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||||||
if (settingsResponse.has_field(L"error"))
|
if (settingsResponse.has_field(L"error"))
|
||||||
{
|
{
|
||||||
_WriteStringWithNewline(RS_(L"AzureNoCloudAccount"));
|
_WriteStringWithNewline(RS_(L"AzureNoCloudAccount"));
|
||||||
|
_transitionToState(ConnectionState::Failed);
|
||||||
return E_FAIL;
|
return E_FAIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -683,13 +649,13 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||||||
const auto shellType = L"bash";
|
const auto shellType = L"bash";
|
||||||
_WriteStringWithNewline(RS_(L"AzureRequestingTerminal"));
|
_WriteStringWithNewline(RS_(L"AzureRequestingTerminal"));
|
||||||
const auto socketUri = _GetTerminal(shellType);
|
const auto socketUri = _GetTerminal(shellType);
|
||||||
_outputHandlers(L"\r\n");
|
_TerminalOutputHandlers(L"\r\n");
|
||||||
|
|
||||||
// Step 8: connecting to said terminal
|
// Step 8: connecting to said terminal
|
||||||
const auto connReqTask = _cloudShellSocket.connect(socketUri);
|
const auto connReqTask = _cloudShellSocket.connect(socketUri);
|
||||||
connReqTask.wait();
|
connReqTask.wait();
|
||||||
|
|
||||||
_state = State::TermConnected;
|
_state = AzureState::TermConnected;
|
||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -759,7 +725,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||||||
for (int count = 0; count < expiresIn / pollInterval; count++)
|
for (int count = 0; count < expiresIn / pollInterval; count++)
|
||||||
{
|
{
|
||||||
// User might close the tab while we wait for them to authenticate, this case handles that
|
// 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.";
|
throw "Tab closed.";
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,26 +11,24 @@
|
|||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
|
|
||||||
|
#include "../cascadia/inc/cppwinrt_utils.h"
|
||||||
|
#include "ConnectionStateHolder.h"
|
||||||
|
|
||||||
namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||||
{
|
{
|
||||||
struct AzureConnection : AzureConnectionT<AzureConnection>
|
struct AzureConnection : AzureConnectionT<AzureConnection>, ConnectionStateHolder<AzureConnection>
|
||||||
{
|
{
|
||||||
static bool IsAzureConnectionAvailable();
|
static bool IsAzureConnectionAvailable();
|
||||||
AzureConnection(const uint32_t rows, const uint32_t cols);
|
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 Start();
|
||||||
void WriteInput(hstring const& data);
|
void WriteInput(hstring const& data);
|
||||||
void Resize(uint32_t rows, uint32_t columns);
|
void Resize(uint32_t rows, uint32_t columns);
|
||||||
void Close();
|
void Close();
|
||||||
|
|
||||||
private:
|
WINRT_CALLBACK(TerminalOutput, TerminalOutputHandler);
|
||||||
winrt::event<TerminalConnection::TerminalOutputEventArgs> _outputHandlers;
|
|
||||||
winrt::event<TerminalConnection::TerminalDisconnectedEventArgs> _disconnectHandlers;
|
|
||||||
|
|
||||||
|
private:
|
||||||
uint32_t _initialRows{};
|
uint32_t _initialRows{};
|
||||||
uint32_t _initialCols{};
|
uint32_t _initialCols{};
|
||||||
int _storedNumber{ -1 };
|
int _storedNumber{ -1 };
|
||||||
@ -40,7 +38,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||||||
std::condition_variable _canProceed;
|
std::condition_variable _canProceed;
|
||||||
std::mutex _commonMutex;
|
std::mutex _commonMutex;
|
||||||
|
|
||||||
enum class State
|
enum class AzureState
|
||||||
{
|
{
|
||||||
AccessStored,
|
AccessStored,
|
||||||
DeviceFlow,
|
DeviceFlow,
|
||||||
@ -51,14 +49,11 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||||||
NoConnect
|
NoConnect
|
||||||
};
|
};
|
||||||
|
|
||||||
State _state{ State::AccessStored };
|
AzureState _state{ AzureState::AccessStored };
|
||||||
|
|
||||||
std::optional<bool> _store;
|
std::optional<bool> _store;
|
||||||
std::optional<bool> _removeOrNew;
|
std::optional<bool> _removeOrNew;
|
||||||
|
|
||||||
bool _connected{};
|
|
||||||
std::atomic<bool> _closing{ false };
|
|
||||||
|
|
||||||
wil::unique_handle _hOutputThread;
|
wil::unique_handle _hOutputThread;
|
||||||
|
|
||||||
static DWORD WINAPI StaticOutputThreadProc(LPVOID lpParameter);
|
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/Utils.hpp"
|
||||||
#include "../../types/inc/Environment.hpp"
|
#include "../../types/inc/Environment.hpp"
|
||||||
#include "../../types/inc/UTF8OutPipeReader.hpp"
|
#include "../../types/inc/UTF8OutPipeReader.hpp"
|
||||||
|
#include "LibraryResources.h"
|
||||||
|
|
||||||
using namespace ::Microsoft::Console;
|
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
|
namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||||
{
|
{
|
||||||
// Function Description:
|
// Function Description:
|
||||||
@ -150,26 +166,6 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||||||
return _guid;
|
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()
|
void ConptyConnection::Start()
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -205,32 +201,70 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||||||
|
|
||||||
SetThreadpoolWait(_clientExitWait.get(), _piClient.hProcess, nullptr);
|
SetThreadpoolWait(_clientExitWait.get(), _piClient.hProcess, nullptr);
|
||||||
|
|
||||||
_connected = true;
|
_transitionToState(ConnectionState::Connected);
|
||||||
}
|
}
|
||||||
catch (...)
|
catch (...)
|
||||||
{
|
{
|
||||||
|
// EXIT POINT
|
||||||
const auto hr = wil::ResultFromCaughtException();
|
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
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO GH#2563 - get the exit code from the process and see whether it was a failing one.
|
// EXIT POINT
|
||||||
_disconnectHandlers();
|
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)
|
void ConptyConnection::WriteInput(hstring const& data)
|
||||||
{
|
{
|
||||||
if (!_connected || _closing.load())
|
if (!_isConnected())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -248,7 +282,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||||||
_initialRows = rows;
|
_initialRows = rows;
|
||||||
_initialCols = columns;
|
_initialCols = columns;
|
||||||
}
|
}
|
||||||
else if (!_closing.load())
|
else if (_isConnected())
|
||||||
{
|
{
|
||||||
THROW_IF_FAILED(ConptyResizePseudoConsole(_hPC.get(), { Utils::ClampToShortMax(columns, 1), Utils::ClampToShortMax(rows, 1) }));
|
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()
|
void ConptyConnection::Close()
|
||||||
{
|
{
|
||||||
if (!_connected)
|
if (_transitionToState(ConnectionState::Closing))
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_closing.exchange(true))
|
|
||||||
{
|
{
|
||||||
|
// EXIT POINT
|
||||||
_clientExitWait.reset(); // immediately stop waiting for the client to exit.
|
_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();
|
_outPipe.reset();
|
||||||
|
|
||||||
// Tear down our output thread -- now that the output pipe was closed on the
|
if (_hOutputThread)
|
||||||
// far side, we can run down our local reader.
|
{
|
||||||
LOG_LAST_ERROR_IF(WAIT_FAILED == WaitForSingleObject(_hOutputThread.get(), INFINITE));
|
// Tear down our output thread -- now that the output pipe was closed on the
|
||||||
_hOutputThread.reset();
|
// 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.
|
if (_piClient.hProcess)
|
||||||
LOG_LAST_ERROR_IF(WAIT_FAILED == WaitForSingleObject(_piClient.hProcess, INFINITE));
|
{
|
||||||
_piClient.reset();
|
// 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);
|
HRESULT result = pipeReader.Read(strView);
|
||||||
if (FAILED(result) || result == S_FALSE)
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
_disconnectHandlers();
|
// EXIT POINT
|
||||||
|
_indicateExitWithStatus(result); // print a message
|
||||||
|
_transitionToState(ConnectionState::Failed);
|
||||||
return (DWORD)-1;
|
return (DWORD)-1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -326,7 +366,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||||||
auto hstr{ winrt::to_hstring(strView) };
|
auto hstr{ winrt::to_hstring(strView) };
|
||||||
|
|
||||||
// Pass the output to our registered event handlers
|
// Pass the output to our registered event handlers
|
||||||
_outputHandlers(hstr);
|
_TerminalOutputHandlers(hstr);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@ -1,9 +1,12 @@
|
|||||||
// Copyright (c) Microsoft Corporation.
|
// Copyright (c) Microsoft Corporation.
|
||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "ConptyConnection.g.h"
|
#include "ConptyConnection.g.h"
|
||||||
|
#include "ConnectionStateHolder.h"
|
||||||
|
#include "../inc/cppwinrt_utils.h"
|
||||||
|
|
||||||
#include <conpty-static.h>
|
#include <conpty-static.h>
|
||||||
|
|
||||||
namespace wil
|
namespace wil
|
||||||
@ -14,14 +17,10 @@ namespace wil
|
|||||||
|
|
||||||
namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
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);
|
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 Start();
|
||||||
void WriteInput(hstring const& data);
|
void WriteInput(hstring const& data);
|
||||||
void Resize(uint32_t rows, uint32_t columns);
|
void Resize(uint32_t rows, uint32_t columns);
|
||||||
@ -29,13 +28,13 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||||||
|
|
||||||
winrt::guid Guid() const noexcept;
|
winrt::guid Guid() const noexcept;
|
||||||
|
|
||||||
|
WINRT_CALLBACK(TerminalOutput, TerminalOutputHandler);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
HRESULT _LaunchAttachedClient() noexcept;
|
HRESULT _LaunchAttachedClient() noexcept;
|
||||||
|
void _indicateExitWithStatus(unsigned int status) noexcept;
|
||||||
void _ClientTerminated() noexcept;
|
void _ClientTerminated() noexcept;
|
||||||
|
|
||||||
winrt::event<TerminalConnection::TerminalOutputEventArgs> _outputHandlers;
|
|
||||||
winrt::event<TerminalConnection::TerminalDisconnectedEventArgs> _disconnectHandlers;
|
|
||||||
|
|
||||||
uint32_t _initialRows{};
|
uint32_t _initialRows{};
|
||||||
uint32_t _initialCols{};
|
uint32_t _initialCols{};
|
||||||
hstring _commandline;
|
hstring _commandline;
|
||||||
@ -43,8 +42,6 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||||||
hstring _startingTitle;
|
hstring _startingTitle;
|
||||||
guid _guid{}; // A unique session identifier for connected client
|
guid _guid{}; // A unique session identifier for connected client
|
||||||
|
|
||||||
bool _connected{};
|
|
||||||
std::atomic<bool> _closing{ false };
|
|
||||||
bool _recievedFirstByte{ false };
|
bool _recievedFirstByte{ false };
|
||||||
std::chrono::high_resolution_clock::time_point _startTime{};
|
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()
|
void EchoConnection::Start()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -56,7 +35,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||||||
prettyPrint << wch;
|
prettyPrint << wch;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_outputHandlers(prettyPrint.str());
|
_TerminalOutputHandlers(prettyPrint.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
void EchoConnection::Resize(uint32_t rows, uint32_t columns)
|
void EchoConnection::Resize(uint32_t rows, uint32_t columns)
|
||||||
|
|||||||
@ -5,23 +5,23 @@
|
|||||||
|
|
||||||
#include "EchoConnection.g.h"
|
#include "EchoConnection.g.h"
|
||||||
|
|
||||||
|
#include "../cascadia/inc/cppwinrt_utils.h"
|
||||||
|
|
||||||
namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||||
{
|
{
|
||||||
struct EchoConnection : EchoConnectionT<EchoConnection>
|
struct EchoConnection : EchoConnectionT<EchoConnection>
|
||||||
{
|
{
|
||||||
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 Start();
|
||||||
void WriteInput(hstring const& data);
|
void WriteInput(hstring const& data);
|
||||||
void Resize(uint32_t rows, uint32_t columns);
|
void Resize(uint32_t rows, uint32_t columns);
|
||||||
void Close();
|
void Close();
|
||||||
|
|
||||||
private:
|
ConnectionState State() const noexcept { return ConnectionState::Connected; }
|
||||||
winrt::event<TerminalConnection::TerminalOutputEventArgs> _outputHandlers;
|
|
||||||
|
WINRT_CALLBACK(TerminalOutput, TerminalOutputHandler);
|
||||||
|
TYPED_EVENT(StateChanged, ITerminalConnection, IInspectable);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,18 +3,28 @@
|
|||||||
|
|
||||||
namespace Microsoft.Terminal.TerminalConnection
|
namespace Microsoft.Terminal.TerminalConnection
|
||||||
{
|
{
|
||||||
delegate void TerminalOutputEventArgs(String output);
|
enum ConnectionState
|
||||||
delegate void TerminalDisconnectedEventArgs();
|
{
|
||||||
|
NotConnected = 0,
|
||||||
|
Connecting,
|
||||||
|
Connected,
|
||||||
|
Closing,
|
||||||
|
Closed,
|
||||||
|
Failed
|
||||||
|
};
|
||||||
|
|
||||||
|
delegate void TerminalOutputHandler(String output);
|
||||||
|
|
||||||
interface ITerminalConnection
|
interface ITerminalConnection
|
||||||
{
|
{
|
||||||
event TerminalOutputEventArgs TerminalOutput;
|
|
||||||
event TerminalDisconnectedEventArgs TerminalDisconnected;
|
|
||||||
|
|
||||||
void Start();
|
void Start();
|
||||||
void WriteInput(String data);
|
void WriteInput(String data);
|
||||||
void Resize(UInt32 rows, UInt32 columns);
|
void Resize(UInt32 rows, UInt32 columns);
|
||||||
void Close();
|
void Close();
|
||||||
};
|
|
||||||
|
|
||||||
|
event TerminalOutputHandler TerminalOutput;
|
||||||
|
|
||||||
|
event Windows.Foundation.TypedEventHandler<ITerminalConnection, Object> StateChanged;
|
||||||
|
ConnectionState State { get; };
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -205,4 +205,13 @@
|
|||||||
<value>n</value>
|
<value>n</value>
|
||||||
<comment>The user must enter this character to signify NO</comment>
|
<comment>The user must enter this character to signify NO</comment>
|
||||||
</data>
|
</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>
|
</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.
|
// 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.
|
// Subscribe to the connection's disconnected event and call our connection closed handlers.
|
||||||
_connection.TerminalDisconnected([=]() {
|
_connectionStateChangedRevoker = _connection.StateChanged(winrt::auto_revoke, [weakThis = get_weak()](auto&& /*s*/, auto&& /*v*/) {
|
||||||
_connectionClosedHandlers();
|
if (auto strongThis{ weakThis.get() })
|
||||||
|
{
|
||||||
|
strongThis->_ConnectionStateChangedHandlers(*strongThis, nullptr);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -421,6 +424,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
|||||||
return _swapChainPanel.Margin();
|
return _swapChainPanel.Margin();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TerminalConnection::ConnectionState TermControl::ConnectionState() const
|
||||||
|
{
|
||||||
|
return _connection.State();
|
||||||
|
}
|
||||||
|
|
||||||
void TermControl::SwapChainChanged()
|
void TermControl::SwapChainChanged()
|
||||||
{
|
{
|
||||||
if (!_initializedTerminal)
|
if (!_initializedTerminal)
|
||||||
@ -1564,8 +1572,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
|||||||
{
|
{
|
||||||
if (!_closing.exchange(true))
|
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);
|
_connection.TerminalOutput(_connectionOutputEventToken);
|
||||||
|
_connectionStateChangedRevoker.revoke();
|
||||||
|
|
||||||
// Clear out the cursor timer, so it doesn't trigger again on us once we're destructed.
|
// 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) })
|
if (auto localCursorTimer{ std::exchange(_cursorTimer, std::nullopt) })
|
||||||
@ -1858,17 +1867,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
|||||||
return flags;
|
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:
|
// Method Description:
|
||||||
// - Gets the corresponding viewport terminal position for the cursor
|
// - Gets the corresponding viewport terminal position for the cursor
|
||||||
// by excluding the padding and normalizing with the font size.
|
// 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.
|
// Winrt events need a method for adding a callback to the event and removing the callback.
|
||||||
// These macros will define them both for you.
|
// These macros will define them both for you.
|
||||||
DEFINE_EVENT(TermControl, TitleChanged, _titleChangedHandlers, TerminalControl::TitleChangedEventArgs);
|
DEFINE_EVENT(TermControl, TitleChanged, _titleChangedHandlers, TerminalControl::TitleChangedEventArgs);
|
||||||
DEFINE_EVENT(TermControl, ConnectionClosed, _connectionClosedHandlers, TerminalControl::ConnectionClosedEventArgs);
|
|
||||||
DEFINE_EVENT(TermControl, ScrollPositionChanged, _scrollPositionChangedHandlers, TerminalControl::ScrollPositionChangedEventArgs);
|
DEFINE_EVENT(TermControl, ScrollPositionChanged, _scrollPositionChangedHandlers, TerminalControl::ScrollPositionChangedEventArgs);
|
||||||
|
|
||||||
DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(TermControl, PasteFromClipboard, _clipboardPasteHandlers, TerminalControl::TermControl, TerminalControl::PasteFromClipboardEventArgs);
|
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);
|
bool CopySelectionToClipboard(bool trimTrailingWhitespace);
|
||||||
void PasteTextFromClipboard();
|
void PasteTextFromClipboard();
|
||||||
void Close();
|
void Close();
|
||||||
bool ShouldCloseOnExit() const noexcept;
|
|
||||||
Windows::Foundation::Size CharacterDimensions() const;
|
Windows::Foundation::Size CharacterDimensions() const;
|
||||||
Windows::Foundation::Size MinimumSize() const;
|
Windows::Foundation::Size MinimumSize() const;
|
||||||
|
|
||||||
@ -82,16 +81,19 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
|||||||
const FontInfo GetActualFont() const;
|
const FontInfo GetActualFont() const;
|
||||||
const Windows::UI::Xaml::Thickness GetPadding() 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);
|
static Windows::Foundation::Point GetProposedDimensions(Microsoft::Terminal::Settings::IControlSettings const& settings, const uint32_t dpi);
|
||||||
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
// -------------------------------- WinRT Events ---------------------------------
|
// -------------------------------- WinRT Events ---------------------------------
|
||||||
DECLARE_EVENT(TitleChanged, _titleChangedHandlers, TerminalControl::TitleChangedEventArgs);
|
DECLARE_EVENT(TitleChanged, _titleChangedHandlers, TerminalControl::TitleChangedEventArgs);
|
||||||
DECLARE_EVENT(ConnectionClosed, _connectionClosedHandlers, TerminalControl::ConnectionClosedEventArgs);
|
|
||||||
DECLARE_EVENT(ScrollPositionChanged, _scrollPositionChangedHandlers, TerminalControl::ScrollPositionChangedEventArgs);
|
DECLARE_EVENT(ScrollPositionChanged, _scrollPositionChangedHandlers, TerminalControl::ScrollPositionChangedEventArgs);
|
||||||
|
|
||||||
DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(PasteFromClipboard, _clipboardPasteHandlers, TerminalControl::TermControl, TerminalControl::PasteFromClipboardEventArgs);
|
DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(PasteFromClipboard, _clipboardPasteHandlers, TerminalControl::TermControl, TerminalControl::PasteFromClipboardEventArgs);
|
||||||
DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(CopyToClipboard, _clipboardCopyHandlers, TerminalControl::TermControl, TerminalControl::CopyToClipboardEventArgs);
|
DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(CopyToClipboard, _clipboardCopyHandlers, TerminalControl::TermControl, TerminalControl::CopyToClipboardEventArgs);
|
||||||
|
|
||||||
|
TYPED_EVENT(ConnectionStateChanged, TerminalControl::TermControl, IInspectable);
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -105,6 +107,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
|||||||
TSFInputControl _tsfInputControl;
|
TSFInputControl _tsfInputControl;
|
||||||
|
|
||||||
event_token _connectionOutputEventToken;
|
event_token _connectionOutputEventToken;
|
||||||
|
TerminalConnection::ITerminalConnection::StateChanged_revoker _connectionStateChangedRevoker;
|
||||||
|
|
||||||
std::unique_ptr<::Microsoft::Terminal::Core::Terminal> _terminal;
|
std::unique_ptr<::Microsoft::Terminal::Core::Terminal> _terminal;
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,6 @@
|
|||||||
namespace Microsoft.Terminal.TerminalControl
|
namespace Microsoft.Terminal.TerminalControl
|
||||||
{
|
{
|
||||||
delegate void TitleChangedEventArgs(String newTitle);
|
delegate void TitleChangedEventArgs(String newTitle);
|
||||||
delegate void ConnectionClosedEventArgs();
|
|
||||||
delegate void ScrollPositionChangedEventArgs(Int32 viewTop, Int32 viewHeight, Int32 bufferLength);
|
delegate void ScrollPositionChangedEventArgs(Int32 viewTop, Int32 viewHeight, Int32 bufferLength);
|
||||||
|
|
||||||
runtimeclass CopyToClipboardEventArgs
|
runtimeclass CopyToClipboardEventArgs
|
||||||
@ -29,16 +28,19 @@ namespace Microsoft.Terminal.TerminalControl
|
|||||||
void UpdateSettings(Microsoft.Terminal.Settings.IControlSettings newSettings);
|
void UpdateSettings(Microsoft.Terminal.Settings.IControlSettings newSettings);
|
||||||
|
|
||||||
event TitleChangedEventArgs TitleChanged;
|
event TitleChangedEventArgs TitleChanged;
|
||||||
event ConnectionClosedEventArgs ConnectionClosed;
|
|
||||||
event Windows.Foundation.TypedEventHandler<TermControl, CopyToClipboardEventArgs> CopyToClipboard;
|
event Windows.Foundation.TypedEventHandler<TermControl, CopyToClipboardEventArgs> CopyToClipboard;
|
||||||
event Windows.Foundation.TypedEventHandler<TermControl, PasteFromClipboardEventArgs> PasteFromClipboard;
|
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; };
|
String Title { get; };
|
||||||
|
|
||||||
Boolean CopySelectionToClipboard(Boolean trimTrailingWhitespace);
|
Boolean CopySelectionToClipboard(Boolean trimTrailingWhitespace);
|
||||||
void PasteTextFromClipboard();
|
void PasteTextFromClipboard();
|
||||||
void Close();
|
void Close();
|
||||||
Boolean ShouldCloseOnExit { get; };
|
|
||||||
Windows.Foundation.Size CharacterDimensions { get; };
|
Windows.Foundation.Size CharacterDimensions { get; };
|
||||||
Windows.Foundation.Size MinimumSize { get; };
|
Windows.Foundation.Size MinimumSize { get; };
|
||||||
|
|
||||||
|
|||||||
@ -20,7 +20,6 @@ namespace Microsoft.Terminal.Settings
|
|||||||
interface IControlSettings requires Microsoft.Terminal.Settings.ICoreSettings
|
interface IControlSettings requires Microsoft.Terminal.Settings.ICoreSettings
|
||||||
{
|
{
|
||||||
Boolean UseAcrylic;
|
Boolean UseAcrylic;
|
||||||
Boolean CloseOnExit;
|
|
||||||
Double TintOpacity;
|
Double TintOpacity;
|
||||||
ScrollbarState ScrollState;
|
ScrollbarState ScrollState;
|
||||||
|
|
||||||
|
|||||||
@ -24,7 +24,6 @@ namespace winrt::Microsoft::Terminal::Settings::implementation
|
|||||||
_wordDelimiters{ DEFAULT_WORD_DELIMITERS },
|
_wordDelimiters{ DEFAULT_WORD_DELIMITERS },
|
||||||
_copyOnSelect{ false },
|
_copyOnSelect{ false },
|
||||||
_useAcrylic{ false },
|
_useAcrylic{ false },
|
||||||
_closeOnExit{ true },
|
|
||||||
_tintOpacity{ 0.5 },
|
_tintOpacity{ 0.5 },
|
||||||
_padding{ DEFAULT_PADDING },
|
_padding{ DEFAULT_PADDING },
|
||||||
_fontFace{ DEFAULT_FONT_FACE },
|
_fontFace{ DEFAULT_FONT_FACE },
|
||||||
@ -181,16 +180,6 @@ namespace winrt::Microsoft::Terminal::Settings::implementation
|
|||||||
_useAcrylic = value;
|
_useAcrylic = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TerminalSettings::CloseOnExit()
|
|
||||||
{
|
|
||||||
return _closeOnExit;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TerminalSettings::CloseOnExit(bool value)
|
|
||||||
{
|
|
||||||
_closeOnExit = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
double TerminalSettings::TintOpacity()
|
double TerminalSettings::TintOpacity()
|
||||||
{
|
{
|
||||||
return _tintOpacity;
|
return _tintOpacity;
|
||||||
|
|||||||
@ -55,8 +55,6 @@ namespace winrt::Microsoft::Terminal::Settings::implementation
|
|||||||
|
|
||||||
bool UseAcrylic();
|
bool UseAcrylic();
|
||||||
void UseAcrylic(bool value);
|
void UseAcrylic(bool value);
|
||||||
bool CloseOnExit();
|
|
||||||
void CloseOnExit(bool value);
|
|
||||||
double TintOpacity();
|
double TintOpacity();
|
||||||
void TintOpacity(double value);
|
void TintOpacity(double value);
|
||||||
hstring Padding();
|
hstring Padding();
|
||||||
@ -114,7 +112,6 @@ namespace winrt::Microsoft::Terminal::Settings::implementation
|
|||||||
hstring _wordDelimiters;
|
hstring _wordDelimiters;
|
||||||
|
|
||||||
bool _useAcrylic;
|
bool _useAcrylic;
|
||||||
bool _closeOnExit;
|
|
||||||
double _tintOpacity;
|
double _tintOpacity;
|
||||||
hstring _fontFace;
|
hstring _fontFace;
|
||||||
int32_t _fontSize;
|
int32_t _fontSize;
|
||||||
|
|||||||
@ -71,6 +71,20 @@ public:
|
|||||||
private: \
|
private: \
|
||||||
winrt::event<winrt::Windows::Foundation::TypedEventHandler<sender, args>> _##name##Handlers;
|
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
|
// 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
|
// 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
|
// class's members. This type of event could be considered "forwarded" or
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user