General improvements in preparation for #16598 (#16601)

This contains all the parts of #16598 that aren't specific to session
restore, but are required for the code in #16598:
* Adds new GUID<>String functions that remove the `{}` brackets.
* Adds `SessionId` to the `ITerminalConnection` interface.
* Flush the `ApplicationState` before we terminate the process.
* Not monitoring `state.json` for changes is important as it prevents
  disturbing the session state while session persistence is ongoing.
  That's because when `ApplicationState` flushes to disk, the FS
  monitor will be triggered and reload the `ApplicationState` again.
This commit is contained in:
Leonard Hecker 2024-02-06 23:58:19 +01:00 committed by GitHub
parent b70fd5e9c6
commit 5dda50767b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 174 additions and 148 deletions

View File

@ -183,6 +183,7 @@ chh
chshdng
CHT
Cic
CLASSSTRING
CLE
cleartype
CLICKACTIVE

View File

@ -124,8 +124,7 @@ namespace winrt::TerminalApp::implementation
return appLogic->GetSettings();
}
AppLogic::AppLogic() :
_reloadState{ std::chrono::milliseconds(100), []() { ApplicationState::SharedInstance().Reload(); } }
AppLogic::AppLogic()
{
// For your own sanity, it's better to do setup outside the ctor.
// If you do any setup in the ctor that ends up throwing an exception,
@ -327,10 +326,6 @@ namespace winrt::TerminalApp::implementation
{
_reloadSettings->Run();
}
else if (ApplicationState::SharedInstance().IsStatePath(modifiedBasename))
{
_reloadState();
}
});
}

View File

@ -91,7 +91,6 @@ namespace winrt::TerminalApp::implementation
::TerminalApp::AppCommandlineArgs _settingsAppArgs;
std::shared_ptr<ThrottledFuncTrailing<>> _reloadSettings;
til::throttled_func_trailing<> _reloadState;
std::vector<Microsoft::Terminal::Settings::Model::SettingsLoadWarnings> _warnings{};

View File

@ -50,6 +50,7 @@ namespace winrt::Microsoft::TerminalApp::implementation
void TerminalOutput(const winrt::event_token& token) noexcept { _wrappedConnection.TerminalOutput(token); };
winrt::event_token StateChanged(const TypedEventHandler<ITerminalConnection, IInspectable>& handler) { return _wrappedConnection.StateChanged(handler); };
void StateChanged(const winrt::event_token& token) noexcept { _wrappedConnection.StateChanged(token); };
winrt::guid SessionId() const noexcept { return {}; }
ConnectionState State() const noexcept { return _wrappedConnection.State(); }
private:
@ -98,6 +99,15 @@ namespace winrt::Microsoft::TerminalApp::implementation
_wrappedConnection = nullptr;
}
guid DebugTapConnection::SessionId() const noexcept
{
if (const auto c = _wrappedConnection.get())
{
return c.SessionId();
}
return {};
}
ConnectionState DebugTapConnection::State() const noexcept
{
if (auto strongConnection{ _wrappedConnection.get() })

View File

@ -19,6 +19,8 @@ namespace winrt::Microsoft::TerminalApp::implementation
void WriteInput(const hstring& data);
void Resize(uint32_t rows, uint32_t columns);
void Close();
winrt::guid SessionId() const noexcept;
winrt::Microsoft::Terminal::TerminalConnection::ConnectionState State() const noexcept;
void SetInputTap(const Microsoft::Terminal::TerminalConnection::ITerminalConnection& inputTap);

View File

@ -1210,7 +1210,7 @@ namespace winrt::TerminalApp::implementation
TerminalConnection::ITerminalConnection connection{ nullptr };
auto connectionType = profile.ConnectionType();
winrt::guid sessionGuid{};
Windows::Foundation::Collections::ValueSet valueSet;
if (connectionType == TerminalConnection::AzureConnection::ConnectionType() &&
TerminalConnection::AzureConnection::IsAzureConnectionAvailable())
@ -1226,23 +1226,16 @@ namespace winrt::TerminalApp::implementation
connection = TerminalConnection::ConptyConnection{};
}
auto valueSet = TerminalConnection::ConptyConnection::CreateSettings(azBridgePath.native(),
L".",
L"Azure",
false,
L"",
nullptr,
settings.InitialRows(),
settings.InitialCols(),
winrt::guid(),
profile.Guid());
if constexpr (Feature_VtPassthroughMode::IsEnabled())
{
valueSet.Insert(L"passthroughMode", Windows::Foundation::PropertyValue::CreateBoolean(settings.VtPassthrough()));
}
connection.Initialize(valueSet);
valueSet = TerminalConnection::ConptyConnection::CreateSettings(azBridgePath.native(),
L".",
L"Azure",
false,
L"",
nullptr,
settings.InitialRows(),
settings.InitialCols(),
winrt::guid(),
profile.Guid());
}
else
@ -1267,38 +1260,38 @@ namespace winrt::TerminalApp::implementation
// process until later, on another thread, after we've already
// restored the CWD to its original value.
auto newWorkingDirectory{ _evaluatePathForCwd(settings.StartingDirectory()) };
auto conhostConn = TerminalConnection::ConptyConnection();
auto valueSet = TerminalConnection::ConptyConnection::CreateSettings(settings.Commandline(),
newWorkingDirectory,
settings.StartingTitle(),
settings.ReloadEnvironmentVariables(),
_WindowProperties.VirtualEnvVars(),
environment,
settings.InitialRows(),
settings.InitialCols(),
winrt::guid(),
profile.Guid());
valueSet.Insert(L"passthroughMode", Windows::Foundation::PropertyValue::CreateBoolean(settings.VtPassthrough()));
connection = TerminalConnection::ConptyConnection{};
valueSet = TerminalConnection::ConptyConnection::CreateSettings(settings.Commandline(),
newWorkingDirectory,
settings.StartingTitle(),
settings.ReloadEnvironmentVariables(),
_WindowProperties.VirtualEnvVars(),
environment,
settings.InitialRows(),
settings.InitialCols(),
winrt::guid(),
profile.Guid());
if (inheritCursor)
{
valueSet.Insert(L"inheritCursor", Windows::Foundation::PropertyValue::CreateBoolean(true));
}
conhostConn.Initialize(valueSet);
sessionGuid = conhostConn.Guid();
connection = conhostConn;
}
if constexpr (Feature_VtPassthroughMode::IsEnabled())
{
valueSet.Insert(L"passthroughMode", Windows::Foundation::PropertyValue::CreateBoolean(settings.VtPassthrough()));
}
connection.Initialize(valueSet);
TraceLoggingWrite(
g_hTerminalAppProvider,
"ConnectionCreated",
TraceLoggingDescription("Event emitted upon the creation of a connection"),
TraceLoggingGuid(connectionType, "ConnectionTypeGuid", "The type of the connection"),
TraceLoggingGuid(profile.Guid(), "ProfileGuid", "The profile's GUID"),
TraceLoggingGuid(sessionGuid, "SessionGuid", "The WT_SESSION's GUID"),
TraceLoggingGuid(connection.SessionId(), "SessionGuid", "The WT_SESSION's GUID"),
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage));

View File

@ -77,8 +77,14 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
{
if (settings)
{
_initialRows = gsl::narrow<til::CoordType>(winrt::unbox_value_or<uint32_t>(settings.TryLookup(L"initialRows").try_as<Windows::Foundation::IPropertyValue>(), _initialRows));
_initialCols = gsl::narrow<til::CoordType>(winrt::unbox_value_or<uint32_t>(settings.TryLookup(L"initialCols").try_as<Windows::Foundation::IPropertyValue>(), _initialCols));
_initialRows = unbox_prop_or<uint32_t>(settings, L"initialRows", _initialRows);
_initialCols = unbox_prop_or<uint32_t>(settings, L"initialCols", _initialCols);
_sessionId = unbox_prop_or<guid>(settings, L"sessionId", _sessionId);
}
if (_sessionId == guid{})
{
_sessionId = Utils::CreateGuid();
}
}

View File

@ -8,12 +8,12 @@
#include <mutex>
#include <condition_variable>
#include "ConnectionStateHolder.h"
#include "BaseTerminalConnection.h"
#include "AzureClient.h"
namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
{
struct AzureConnection : AzureConnectionT<AzureConnection>, ConnectionStateHolder<AzureConnection>
struct AzureConnection : AzureConnectionT<AzureConnection>, BaseTerminalConnection<AzureConnection>
{
static winrt::guid ConnectionType() noexcept;
static bool IsAzureConnectionAvailable() noexcept;

View File

@ -4,13 +4,28 @@
namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
{
template<typename T>
struct ConnectionStateHolder
struct BaseTerminalConnection
{
public:
ConnectionState State() const noexcept { return _connectionState; }
winrt::guid SessionId() const noexcept
{
return _sessionId;
}
ConnectionState State() const noexcept
{
return _connectionState;
}
TYPED_EVENT(StateChanged, ITerminalConnection, winrt::Windows::Foundation::IInspectable);
protected:
template<typename U>
U unbox_prop_or(const Windows::Foundation::Collections::ValueSet& blob, std::wstring_view key, U defaultValue)
{
return winrt::unbox_value_or<U>(blob.TryLookup(key).try_as<Windows::Foundation::IPropertyValue>(), defaultValue);
}
#pragma warning(push)
#pragma warning(disable : 26447) // Analyzer is still upset about noexcepts throwing even with function level try.
// Method Description:
@ -86,6 +101,8 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
return _isStateOneOf(ConnectionState::Connected);
}
winrt::guid _sessionId{};
private:
std::atomic<ConnectionState> _connectionState{ ConnectionState::NotConnected };
mutable std::mutex _stateMutex;

View File

@ -85,18 +85,12 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
auto environment = _initialEnv;
{
// Convert connection Guid to string and ignore the enclosing '{}'.
auto wsGuid{ Utils::GuidToString(_guid) };
wsGuid.pop_back();
const auto guidSubStr = std::wstring_view{ wsGuid }.substr(1);
// Ensure every connection has the unique identifier in the environment.
environment.as_map().insert_or_assign(L"WT_SESSION", guidSubStr.data());
// Convert connection Guid to string and ignore the enclosing '{}'.
environment.as_map().insert_or_assign(L"WT_SESSION", Utils::GuidToPlainString(_sessionId));
// The profile Guid does include the enclosing '{}'
const auto profileGuid{ Utils::GuidToString(_profileGuid) };
environment.as_map().insert_or_assign(L"WT_PROFILE_ID", profileGuid.data());
environment.as_map().insert_or_assign(L"WT_PROFILE_ID", Utils::GuidToString(_profileGuid));
// WSLENV is a colon-delimited list of environment variables (+flags) that should appear inside WSL
// https://devblogs.microsoft.com/commandline/share-environment-vars-between-wsl-and-windows/
@ -171,7 +165,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
g_hTerminalConnectionProvider,
"ConPtyConnected",
TraceLoggingDescription("Event emitted when ConPTY connection is started"),
TraceLoggingGuid(_guid, "SessionGuid", "The WT_SESSION's GUID"),
TraceLoggingGuid(_sessionId, "SessionGuid", "The WT_SESSION's GUID"),
TraceLoggingWideString(_clientName.c_str(), "Client", "The attached client process"),
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage));
@ -189,7 +183,6 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
TERMINAL_STARTUP_INFO startupInfo) :
_rows{ 25 },
_cols{ 80 },
_guid{ Utils::CreateGuid() },
_inPipe{ hIn },
_outPipe{ hOut }
{
@ -249,12 +242,6 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
return vs;
}
template<typename T>
T unbox_prop_or(const Windows::Foundation::Collections::ValueSet& blob, std::wstring_view key, T defaultValue)
{
return winrt::unbox_value_or<T>(blob.TryLookup(key).try_as<Windows::Foundation::IPropertyValue>(), defaultValue);
}
void ConptyConnection::Initialize(const Windows::Foundation::Collections::ValueSet& settings)
{
if (settings)
@ -268,7 +255,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
_startingTitle = unbox_prop_or<winrt::hstring>(settings, L"startingTitle", _startingTitle);
_rows = unbox_prop_or<uint32_t>(settings, L"initialRows", _rows);
_cols = unbox_prop_or<uint32_t>(settings, L"initialCols", _cols);
_guid = unbox_prop_or<winrt::guid>(settings, L"guid", _guid);
_sessionId = unbox_prop_or<winrt::guid>(settings, L"sessionId", _sessionId);
_environment = settings.TryLookup(L"environment").try_as<Windows::Foundation::Collections::ValueSet>();
if constexpr (Feature_VtPassthroughMode::IsEnabled())
{
@ -299,17 +286,12 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
_initialEnv = til::env::from_current_environment();
}
}
if (_guid == guid{})
{
_guid = Utils::CreateGuid();
}
}
}
winrt::guid ConptyConnection::Guid() const noexcept
{
return _guid;
if (_sessionId == guid{})
{
_sessionId = Utils::CreateGuid();
}
}
winrt::hstring ConptyConnection::Commandline() const
@ -382,7 +364,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
g_hTerminalConnectionProvider,
"ConPtyConnectedToDefterm",
TraceLoggingDescription("Event emitted when ConPTY connection is started, for a defterm session"),
TraceLoggingGuid(_guid, "SessionGuid", "The WT_SESSION's GUID"),
TraceLoggingGuid(_sessionId, "SessionGuid", "The WT_SESSION's GUID"),
TraceLoggingWideString(_clientName.c_str(), "Client", "The attached client process"),
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage));
@ -686,7 +668,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
TraceLoggingWrite(g_hTerminalConnectionProvider,
"ReceivedFirstByte",
TraceLoggingDescription("An event emitted when the connection receives the first byte"),
TraceLoggingGuid(_guid, "SessionGuid", "The WT_SESSION's GUID"),
TraceLoggingGuid(_sessionId, "SessionGuid", "The WT_SESSION's GUID"),
TraceLoggingFloat64(delta.count(), "Duration"),
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));

View File

@ -4,14 +4,14 @@
#pragma once
#include "ConptyConnection.g.h"
#include "ConnectionStateHolder.h"
#include "BaseTerminalConnection.h"
#include "ITerminalHandoff.h"
#include <til/env.h>
namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
{
struct ConptyConnection : ConptyConnectionT<ConptyConnection>, ConnectionStateHolder<ConptyConnection>
struct ConptyConnection : ConptyConnectionT<ConptyConnection>, BaseTerminalConnection<ConptyConnection>
{
ConptyConnection(const HANDLE hSig,
const HANDLE hIn,
@ -36,7 +36,6 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
void ReparentWindow(const uint64_t newParent);
winrt::guid Guid() const noexcept;
winrt::hstring Commandline() const;
winrt::hstring StartingTitle() const;
WORD ShowWindow() const noexcept;
@ -77,7 +76,6 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
hstring _startingTitle{};
bool _initialVisibility{ true };
Windows::Foundation::Collections::ValueSet _environment{ nullptr };
guid _guid{}; // A unique session identifier for connected client
hstring _clientName{}; // The name of the process hosted by this ConPTY connection (as of launch).
bool _receivedFirstByte{ false };

View File

@ -10,7 +10,6 @@ namespace Microsoft.Terminal.TerminalConnection
[default_interface] runtimeclass ConptyConnection : ITerminalConnection
{
ConptyConnection();
Guid Guid { get; };
String Commandline { get; };
String StartingTitle { get; };
UInt16 ShowWindow { get; };

View File

@ -18,6 +18,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
void Initialize(const Windows::Foundation::Collections::ValueSet& /*settings*/) const noexcept {};
winrt::guid SessionId() const noexcept { return {}; }
ConnectionState State() const noexcept { return ConnectionState::Connected; }
WINRT_CALLBACK(TerminalOutput, TerminalOutputHandler);

View File

@ -25,8 +25,9 @@ namespace Microsoft.Terminal.TerminalConnection
void Close();
event TerminalOutputHandler TerminalOutput;
event Windows.Foundation.TypedEventHandler<ITerminalConnection, Object> StateChanged;
Guid SessionId { get; };
ConnectionState State { get; };
};
}

View File

@ -17,6 +17,7 @@
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.pre.props" />
<ItemGroup>
<ClInclude Include="AzureClientID.h" />
<ClInclude Include="BaseTerminalConnection.h" />
<ClInclude Include="ConnectionInformation.h">
<DependentUpon>ConnectionInformation.idl</DependentUpon>
</ClInclude>

View File

@ -26,6 +26,7 @@
<ClInclude Include="AzureConnection.h" />
<ClInclude Include="AzureClientID.h" />
<ClInclude Include="CTerminalHandoff.h" />
<ClInclude Include="BaseTerminalConnection.h" />
</ItemGroup>
<ItemGroup>
<Midl Include="ITerminalConnection.idl" />
@ -34,11 +35,9 @@
<Midl Include="ConptyConnection.idl" />
<Midl Include="ConnectionInformation.idl" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Natvis Include="$(SolutionDir)tools\ConsoleTypes.natvis" />
<Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" />
</ItemGroup>
<ItemGroup>
<PRIResource Include="Resources\en-US\Resources.resw" />

View File

@ -29,6 +29,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void DisplayPowerlineGlyphs(bool d) noexcept;
winrt::guid SessionId() const noexcept { return {}; }
winrt::Microsoft::Terminal::TerminalConnection::ConnectionState State() const noexcept { return winrt::Microsoft::Terminal::TerminalConnection::ConnectionState::Connected; }
WINRT_CALLBACK(TerminalOutput, winrt::Microsoft::Terminal::TerminalConnection::TerminalOutputHandler);

View File

@ -117,7 +117,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
private:
Model::Profile _profile;
winrt::guid _originalProfileGuid;
winrt::guid _originalProfileGuid{};
winrt::hstring _lastBgImagePath;
winrt::hstring _lastStartingDirectoryPath;
Editor::AppearanceViewModel _defaultAppearanceViewModel;

View File

@ -102,34 +102,14 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
// The destructor ensures that the last write is flushed to disk before returning.
ApplicationState::~ApplicationState()
{
TraceLoggingWrite(g_hSettingsModelProvider,
"ApplicationState_Dtor_Start",
TraceLoggingDescription("Event at the start of the ApplicationState destructor"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
Flush();
}
void ApplicationState::Flush()
{
// This will ensure that we not just cancel the last outstanding timer,
// but instead force it to run as soon as possible and wait for it to complete.
_throttler.flush();
TraceLoggingWrite(g_hSettingsModelProvider,
"ApplicationState_Dtor_End",
TraceLoggingDescription("Event at the end of the ApplicationState destructor"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
// Re-read the state.json from disk.
void ApplicationState::Reload() const noexcept
{
_read();
}
bool ApplicationState::IsStatePath(const winrt::hstring& filename)
{
static const auto sharedPath{ _sharedPath.filename() };
static const auto elevatedPath{ _elevatedPath.filename() };
return filename == sharedPath || filename == elevatedPath;
}
// Method Description:
@ -299,7 +279,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
Json::Value ApplicationState::_toJsonWithBlob(Json::Value& root, FileSource parseSource) const noexcept
{
{
auto state = _state.lock_shared();
const auto state = _state.lock_shared();
// GH#11222: We only write properties that are of the same type (Local
// or Shared) which we requested. If we didn't want to serialize this
@ -326,7 +306,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
void ApplicationState::name(const type& value) noexcept \
{ \
{ \
auto state = _state.lock(); \
const auto state = _state.lock(); \
state->name.emplace(value); \
} \
\

View File

@ -63,15 +63,12 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
~ApplicationState();
// Methods
void Reload() const noexcept;
void Flush();
void Reset() noexcept;
void FromJson(const Json::Value& root, FileSource parseSource) const noexcept;
Json::Value ToJson(FileSource parseSource) const noexcept;
// General getters/setters
bool IsStatePath(const winrt::hstring& filename);
// State getters/setters
#define MTSM_APPLICATION_STATE_GEN(source, type, name, key, ...) \
type name() const noexcept; \

View File

@ -28,11 +28,9 @@ namespace Microsoft.Terminal.Settings.Model
[default_interface] runtimeclass ApplicationState {
static ApplicationState SharedInstance();
void Reload();
void Flush();
void Reset();
Boolean IsStatePath(String filename);
String SettingsHash;
Windows.Foundation.Collections.IVector<WindowLayout> PersistedWindowLayouts;
Windows.Foundation.Collections.IVector<String> RecentCommands;

View File

@ -410,7 +410,7 @@ bool SettingsLoader::FixupUserSettings()
{
struct CommandlinePatch
{
winrt::guid guid;
winrt::guid guid{};
std::wstring_view before;
std::wstring_view after;
};

View File

@ -83,7 +83,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
static constexpr bool debugFeaturesDefault{ true };
#endif
winrt::guid _defaultProfile;
winrt::guid _defaultProfile{};
bool _legacyReloadEnvironmentVariables{ true };
winrt::com_ptr<implementation::ActionMap> _actionMap{ winrt::make_self<implementation::ActionMap>() };

View File

@ -23,6 +23,7 @@ namespace ControlUnitTests
void Resize(uint32_t /*rows*/, uint32_t /*columns*/) noexcept {}
void Close() noexcept {}
winrt::guid SessionId() const noexcept { return {}; }
winrt::Microsoft::Terminal::TerminalConnection::ConnectionState State() const noexcept { return winrt::Microsoft::Terminal::TerminalConnection::ConnectionState::Connected; }
WINRT_CALLBACK(TerminalOutput, winrt::Microsoft::Terminal::TerminalConnection::TerminalOutputHandler);

View File

@ -127,6 +127,9 @@ void WindowEmperor::WaitForWindows()
TranslateMessage(&message);
DispatchMessage(&message);
}
_finalizeSessionPersistence();
TerminateProcess(GetCurrentProcess(), 0);
}
void WindowEmperor::_createNewWindowThread(const Remoting::WindowRequestedArgs& args)
@ -575,6 +578,14 @@ winrt::fire_and_forget WindowEmperor::_close()
PostQuitMessage(0);
}
void WindowEmperor::_finalizeSessionPersistence() const
{
const auto state = ApplicationState::SharedInstance();
// Ensure to write the state.json before we TerminateProcess()
state.Flush();
}
#pragma endregion
#pragma region GlobalHotkeys

View File

@ -78,6 +78,7 @@ private:
winrt::fire_and_forget _setupGlobalHotkeys();
winrt::fire_and_forget _close();
void _finalizeSessionPersistence() const;
void _createNotificationIcon();
void _destroyNotificationIcon();

View File

@ -39,8 +39,10 @@ namespace Microsoft::Console::Utils
static_cast<long>(SHRT_MAX)));
}
std::wstring GuidToString(const GUID guid);
std::wstring GuidToString(const GUID& guid);
std::wstring GuidToPlainString(const GUID& guid);
GUID GuidFromString(_Null_terminated_ const wchar_t* str);
GUID GuidFromPlainString(_Null_terminated_ const wchar_t* str);
GUID CreateGuid();
std::string ColorToHexString(const til::color color);

View File

@ -66,15 +66,23 @@ void UtilsTests::TestClampToShortMax()
void UtilsTests::TestGuidToString()
{
constexpr GUID constantGuid{
static constexpr GUID constantGuid{
0x01020304, 0x0506, 0x0708, { 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10 }
};
constexpr std::wstring_view constantGuidString{ L"{01020304-0506-0708-090a-0b0c0d0e0f10}" };
auto generatedGuid{ GuidToString(constantGuid) };
{
const auto str = GuidToString(constantGuid);
const auto guid = GuidFromString(str.c_str());
VERIFY_ARE_EQUAL(L"{01020304-0506-0708-090a-0b0c0d0e0f10}", str);
VERIFY_ARE_EQUAL(constantGuid, guid);
}
VERIFY_ARE_EQUAL(constantGuidString.size(), generatedGuid.size());
VERIFY_ARE_EQUAL(constantGuidString, generatedGuid);
{
const auto str = GuidToPlainString(constantGuid);
const auto guid = GuidFromPlainString(str.c_str());
VERIFY_ARE_EQUAL(L"01020304-0506-0708-090a-0b0c0d0e0f10", str);
VERIFY_ARE_EQUAL(constantGuid, guid);
}
}
void UtilsTests::TestSplitString()

View File

@ -3,6 +3,9 @@
#include "precomp.h"
#include "inc/utils.hpp"
#include <propsys.h>
#include "inc/colorTable.hpp"
#include <wil/token_helpers.h>
@ -21,27 +24,28 @@ static constexpr bool _isNumber(const wchar_t wch) noexcept
return wch >= L'0' && wch <= L'9'; // 0x30 - 0x39
}
// Function Description:
// - Creates a String representation of a guid, in the format
// "{12345678-ABCD-EF12-3456-7890ABCDEF12}"
// Arguments:
// - guid: the GUID to create the string for
// Return Value:
// - a string representation of the GUID. On failure, throws E_INVALIDARG.
std::wstring Utils::GuidToString(const GUID guid)
[[gsl::suppress(bounds)]] static std::wstring guidToStringCommon(const GUID& guid, size_t offset, size_t length)
{
return wil::str_printf<std::wstring>(L"{%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}", guid.Data1, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]);
// This is just like StringFromGUID2 but with lowercase hexadecimal.
wchar_t buffer[39];
swprintf_s(&buffer[0], 39, L"{%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}", guid.Data1, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]);
return { &buffer[offset], length };
}
// Method Description:
// - Parses a GUID from a string representation of the GUID. Throws an exception
// if it fails to parse the GUID. See documentation of IIDFromString for
// details.
// Arguments:
// - wstr: a string representation of the GUID to parse
// Return Value:
// - A GUID if the string could successfully be parsed. On failure, throws the
// failing HRESULT.
// Creates a string from the given GUID in the format "{12345678-abcd-ef12-3456-7890abcdef12}".
std::wstring Utils::GuidToString(const GUID& guid)
{
return guidToStringCommon(guid, 0, 38);
}
// Creates a string from the given GUID in the format "12345678-abcd-ef12-3456-7890abcdef12".
std::wstring Utils::GuidToPlainString(const GUID& guid)
{
return guidToStringCommon(guid, 1, 36);
}
// Creates a GUID from a string in the format "{12345678-abcd-ef12-3456-7890abcdef12}".
// Throws if the conversion failed.
GUID Utils::GuidFromString(_Null_terminated_ const wchar_t* str)
{
GUID result;
@ -49,6 +53,25 @@ GUID Utils::GuidFromString(_Null_terminated_ const wchar_t* str)
return result;
}
// Creates a GUID from a string in the format "12345678-abcd-ef12-3456-7890abcdef12".
// Throws if the conversion failed.
//
// Side-note: An interesting quirk of this method is that the given string doesn't need to be null-terminated.
// This method could be combined with GuidFromString() so that it also doesn't require null-termination.
[[gsl::suppress(bounds)]] GUID Utils::GuidFromPlainString(_Null_terminated_ const wchar_t* str)
{
// Add "{}" brackets around our string, as required by IIDFromString().
wchar_t buffer[39];
buffer[0] = L'{';
// This wcscpy_s() copies 36 characters and 1 terminating null.
// The latter forces us to call this method before filling buffer[37] with '}'.
THROW_HR_IF(CO_E_CLASSSTRING, wcscpy_s(&buffer[1], 37, str));
buffer[37] = L'}';
buffer[38] = L'\0';
return GuidFromString(&buffer[0]);
}
// Method Description:
// - Creates a GUID, but not via an out parameter.
// Return Value: