From 42c3eea136326688a10f6fa9ba75a3256bfd6539 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Mon, 30 Aug 2021 12:47:10 -0500 Subject: [PATCH 01/92] pull application state members into a base class --- src/cascadia/LocalTests_SettingsModel/pch.h | 3 + .../ApplicationState.cpp | 109 ++++++------------ .../TerminalSettingsModel/ApplicationState.h | 28 +++-- .../BaseApplicationState.cpp | 83 +++++++++++++ .../BaseApplicationState.h | 36 ++++++ ...crosoft.Terminal.Settings.ModelLib.vcxproj | 2 + src/cascadia/TerminalSettingsModel/pch.h | 3 + 7 files changed, 177 insertions(+), 87 deletions(-) create mode 100644 src/cascadia/TerminalSettingsModel/BaseApplicationState.cpp create mode 100644 src/cascadia/TerminalSettingsModel/BaseApplicationState.h diff --git a/src/cascadia/LocalTests_SettingsModel/pch.h b/src/cascadia/LocalTests_SettingsModel/pch.h index 53f6145d27e..01b4cdfe87f 100644 --- a/src/cascadia/LocalTests_SettingsModel/pch.h +++ b/src/cascadia/LocalTests_SettingsModel/pch.h @@ -61,6 +61,9 @@ Author(s): // Manually include til after we include Windows.Foundation to give it winrt superpowers #include "til.h" +#include +#include + // Common includes for most tests: #include "../../inc/argb.h" #include "../../inc/conattrs.hpp" diff --git a/src/cascadia/TerminalSettingsModel/ApplicationState.cpp b/src/cascadia/TerminalSettingsModel/ApplicationState.cpp index f0f536ecfec..f16f6e71fc0 100644 --- a/src/cascadia/TerminalSettingsModel/ApplicationState.cpp +++ b/src/cascadia/TerminalSettingsModel/ApplicationState.cpp @@ -110,31 +110,44 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return *state; } - ApplicationState::ApplicationState(std::filesystem::path path) noexcept : - _path{ std::move(path) }, - _throttler{ std::chrono::seconds(1), [this]() { _write(); } } + // ApplicationState::ApplicationState(std::filesystem::path path) noexcept : + // _path{ std::move(path) }, + // _throttler{ std::chrono::seconds(1), [this]() { _write(); } } + // { + // _read(); + // } + + // // The destructor ensures that the last write is flushed to disk before returning. + // ApplicationState::~ApplicationState() + // { + // // 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(); + // } + + void ApplicationState::FromJson(const Json::Value& root) const noexcept { - _read(); - } - - // The destructor ensures that the last write is flushed to disk before returning. - ApplicationState::~ApplicationState() - { - // 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(); + auto state = _state.lock(); + // GetValueForKey() comes in two variants: + // * take a std::optional reference + // * return std::optional by value + // At the time of writing the former version skips missing fields in the json, + // but we want to explicitly clear state fields that were removed from state.json. +#define MTSM_APPLICATION_STATE_GEN(type, name, key, ...) state->name = JsonUtils::GetValueForKey>(root, key); + MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN) +#undef MTSM_APPLICATION_STATE_GEN } - - // Re-read the state.json from disk. - void ApplicationState::Reload() const noexcept + Json::Value ApplicationState::ToJson() const noexcept { - _read(); - } + Json::Value root{ Json::objectValue }; - // Returns the state.json path on the disk. - winrt::hstring ApplicationState::FilePath() const noexcept - { - return winrt::hstring{ _path.wstring() }; + { + auto state = _state.lock_shared(); +#define MTSM_APPLICATION_STATE_GEN(type, name, key, ...) JsonUtils::SetValueForKey(root, key, state->name); + MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN) +#undef MTSM_APPLICATION_STATE_GEN + } + return root; } // Generate all getter/setters @@ -158,58 +171,4 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN) #undef MTSM_APPLICATION_STATE_GEN - // Deserializes the state.json at _path into this ApplicationState. - // * ANY errors during app state will result in the creation of a new empty state. - // * ANY errors during runtime will result in changes being partially ignored. - void ApplicationState::_read() const noexcept - try - { - const auto data = ReadUTF8FileIfExists(_path).value_or(std::string{}); - if (data.empty()) - { - return; - } - - std::string errs; - std::unique_ptr reader{ Json::CharReaderBuilder::CharReaderBuilder().newCharReader() }; - - Json::Value root; - if (!reader->parse(data.data(), data.data() + data.size(), &root, &errs)) - { - throw winrt::hresult_error(WEB_E_INVALID_JSON_STRING, winrt::to_hstring(errs)); - } - - auto state = _state.lock(); - // GetValueForKey() comes in two variants: - // * take a std::optional reference - // * return std::optional by value - // At the time of writing the former version skips missing fields in the json, - // but we want to explicitly clear state fields that were removed from state.json. -#define MTSM_APPLICATION_STATE_GEN(type, name, key, ...) state->name = JsonUtils::GetValueForKey>(root, key); - MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN) -#undef MTSM_APPLICATION_STATE_GEN - } - CATCH_LOG() - - // Serialized this ApplicationState (in `context`) into the state.json at _path. - // * Errors are only logged. - // * _state->_writeScheduled is set to false, signaling our - // setters that _synchronize() needs to be called again. - void ApplicationState::_write() const noexcept - try - { - Json::Value root{ Json::objectValue }; - - { - auto state = _state.lock_shared(); -#define MTSM_APPLICATION_STATE_GEN(type, name, key, ...) JsonUtils::SetValueForKey(root, key, state->name); - MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN) -#undef MTSM_APPLICATION_STATE_GEN - } - - Json::StreamWriterBuilder wbuilder; - const auto content = Json::writeString(wbuilder, root); - WriteUTF8FileAtomic(_path, content); - } - CATCH_LOG() } diff --git a/src/cascadia/TerminalSettingsModel/ApplicationState.h b/src/cascadia/TerminalSettingsModel/ApplicationState.h index 3a9a1e8d741..1e17d781976 100644 --- a/src/cascadia/TerminalSettingsModel/ApplicationState.h +++ b/src/cascadia/TerminalSettingsModel/ApplicationState.h @@ -12,11 +12,12 @@ Module Name: --*/ #pragma once +#include "BaseApplicationState.h" #include "ApplicationState.g.h" #include -#include -#include +// #include +// #include // This macro generates all getters and setters for ApplicationState. // It provides X with the following arguments: @@ -27,18 +28,21 @@ Module Name: namespace winrt::Microsoft::Terminal::Settings::Model::implementation { - struct ApplicationState : ApplicationStateT + struct ApplicationState : ApplicationStateT, public BaseApplicationState { static Microsoft::Terminal::Settings::Model::ApplicationState SharedInstance(); ApplicationState(std::filesystem::path path) noexcept; ~ApplicationState(); - // Methods - void Reload() const noexcept; + void FromJson(const Json::Value& root) const noexcept override; + Json::Value ToJson() const noexcept override; - // General getters/setters - winrt::hstring FilePath() const noexcept; + // // Methods + // void Reload() const noexcept; + + // // General getters/setters + // winrt::hstring FilePath() const noexcept; // State getters/setters #define MTSM_APPLICATION_STATE_GEN(type, name, key, ...) \ @@ -54,13 +58,13 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN) #undef MTSM_APPLICATION_STATE_GEN }; + til::shared_mutex _state; - void _write() const noexcept; - void _read() const noexcept; + // void _write() const noexcept; + // void _read() const noexcept; - std::filesystem::path _path; - til::shared_mutex _state; - til::throttled_func_trailing<> _throttler; + // std::filesystem::path _path; + // til::throttled_func_trailing<> _throttler; }; } diff --git a/src/cascadia/TerminalSettingsModel/BaseApplicationState.cpp b/src/cascadia/TerminalSettingsModel/BaseApplicationState.cpp new file mode 100644 index 00000000000..e27acaf959e --- /dev/null +++ b/src/cascadia/TerminalSettingsModel/BaseApplicationState.cpp @@ -0,0 +1,83 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include "BaseApplicationState.h" +#include "CascadiaSettings.h" +// #include "ApplicationState.g.cpp" + +#include "JsonUtils.h" +#include "FileUtils.h" + +constexpr std::wstring_view stateFileName{ L"state.json" }; +using namespace ::Microsoft::Terminal::Settings::Model; + +// namespace winrt::Microsoft::Terminal::Settings::Model::implementation +// { +BaseApplicationState::BaseApplicationState(std::filesystem::path path) noexcept : + _path{ std::move(path) }, + _throttler{ std::chrono::seconds(1), [this]() { _write(); } } +{ + _read(); +} + +// The destructor ensures that the last write is flushed to disk before returning. +BaseApplicationState::~BaseApplicationState() +{ + // 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(); +} + +// Re-read the state.json from disk. +void BaseApplicationState::Reload() const noexcept +{ + _read(); +} + +// Returns the state.json path on the disk. +winrt::hstring BaseApplicationState::FilePath() const noexcept +{ + return winrt::hstring{ _path.wstring() }; +} + +// Deserializes the state.json at _path into this ApplicationState. +// * ANY errors during app state will result in the creation of a new empty state. +// * ANY errors during runtime will result in changes being partially ignored. +void BaseApplicationState::_read() const noexcept +try +{ + const auto data = ReadUTF8FileIfExists(_path).value_or(std::string{}); + if (data.empty()) + { + return; + } + + std::string errs; + std::unique_ptr reader{ Json::CharReaderBuilder::CharReaderBuilder().newCharReader() }; + + Json::Value root; + if (!reader->parse(data.data(), data.data() + data.size(), &root, &errs)) + { + throw winrt::hresult_error(WEB_E_INVALID_JSON_STRING, winrt::to_hstring(errs)); + } + + this->FromJson(root); +} +CATCH_LOG() + +// Serialized this ApplicationState (in `context`) into the state.json at _path. +// * Errors are only logged. +// * _state->_writeScheduled is set to false, signaling our +// setters that _synchronize() needs to be called again. +void BaseApplicationState::_write() const noexcept +try +{ + Json::Value root{ this->ToJson() }; + + Json::StreamWriterBuilder wbuilder; + const auto content = Json::writeString(wbuilder, root); + WriteUTF8FileAtomic(_path, content); +} +CATCH_LOG() +// } diff --git a/src/cascadia/TerminalSettingsModel/BaseApplicationState.h b/src/cascadia/TerminalSettingsModel/BaseApplicationState.h new file mode 100644 index 00000000000..5713beb93c4 --- /dev/null +++ b/src/cascadia/TerminalSettingsModel/BaseApplicationState.h @@ -0,0 +1,36 @@ +/*++ +Copyright (c) Microsoft Corporation +Licensed under the MIT license. + +Module Name: +- ApplicationState.h + +Abstract: +- TODO! +--*/ +#pragma once + +// namespace winrt::Microsoft::Terminal::Settings::Model::implementation +// { +struct BaseApplicationState +{ + BaseApplicationState(std::filesystem::path path) noexcept; + ~BaseApplicationState(); + + // Methods + void Reload() const noexcept; + + // General getters/setters + winrt::hstring FilePath() const noexcept; + + virtual void FromJson(const Json::Value& root) const noexcept = 0; + virtual Json::Value ToJson() const noexcept = 0; + +protected: + void _write() const noexcept; + void _read() const noexcept; + + std::filesystem::path _path; + til::throttled_func_trailing<> _throttler; +}; +// } diff --git a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj index 202f078084e..de191507ea8 100644 --- a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj +++ b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj @@ -21,6 +21,7 @@ IconPathConverter.idl + ActionArgs.idl @@ -88,6 +89,7 @@ IconPathConverter.idl + Create diff --git a/src/cascadia/TerminalSettingsModel/pch.h b/src/cascadia/TerminalSettingsModel/pch.h index d5473939c6d..25773a45d91 100644 --- a/src/cascadia/TerminalSettingsModel/pch.h +++ b/src/cascadia/TerminalSettingsModel/pch.h @@ -53,3 +53,6 @@ TRACELOGGING_DECLARE_PROVIDER(g_hSettingsModelProvider); // Manually include til after we include Windows.Foundation to give it winrt superpowers #include "til.h" + +#include +#include From eb243f5e112d20f58e4a4e8de919d4fde042a203 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Tue, 31 Aug 2021 16:15:22 -0500 Subject: [PATCH 02/92] Split ApplicationState into a Base and add an Elevated version --- .../ApplicationState.cpp | 19 +- .../TerminalSettingsModel/ApplicationState.h | 10 +- .../BaseApplicationState.cpp | 4 +- .../TerminalSettingsModel/ElevatedState.cpp | 163 ++++++++++++++++++ .../TerminalSettingsModel/ElevatedState.h | 57 ++++++ .../TerminalSettingsModel/ElevatedState.idl | 15 ++ ...crosoft.Terminal.Settings.ModelLib.vcxproj | 7 + 7 files changed, 251 insertions(+), 24 deletions(-) create mode 100644 src/cascadia/TerminalSettingsModel/ElevatedState.cpp create mode 100644 src/cascadia/TerminalSettingsModel/ElevatedState.h create mode 100644 src/cascadia/TerminalSettingsModel/ElevatedState.idl diff --git a/src/cascadia/TerminalSettingsModel/ApplicationState.cpp b/src/cascadia/TerminalSettingsModel/ApplicationState.cpp index f16f6e71fc0..4005e3529c8 100644 --- a/src/cascadia/TerminalSettingsModel/ApplicationState.cpp +++ b/src/cascadia/TerminalSettingsModel/ApplicationState.cpp @@ -103,28 +103,17 @@ using namespace ::Microsoft::Terminal::Settings::Model; namespace winrt::Microsoft::Terminal::Settings::Model::implementation { + ApplicationState::ApplicationState(std::filesystem::path path) noexcept : + BaseApplicationState{ path } {} + // Returns the application-global ApplicationState object. Microsoft::Terminal::Settings::Model::ApplicationState ApplicationState::SharedInstance() { static auto state = winrt::make_self(GetBaseSettingsPath() / stateFileName); + state->Reload(); return *state; } - // ApplicationState::ApplicationState(std::filesystem::path path) noexcept : - // _path{ std::move(path) }, - // _throttler{ std::chrono::seconds(1), [this]() { _write(); } } - // { - // _read(); - // } - - // // The destructor ensures that the last write is flushed to disk before returning. - // ApplicationState::~ApplicationState() - // { - // // 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(); - // } - void ApplicationState::FromJson(const Json::Value& root) const noexcept { auto state = _state.lock(); diff --git a/src/cascadia/TerminalSettingsModel/ApplicationState.h b/src/cascadia/TerminalSettingsModel/ApplicationState.h index 1e17d781976..67b91121edd 100644 --- a/src/cascadia/TerminalSettingsModel/ApplicationState.h +++ b/src/cascadia/TerminalSettingsModel/ApplicationState.h @@ -14,10 +14,7 @@ Module Name: #include "BaseApplicationState.h" #include "ApplicationState.g.h" - #include -// #include -// #include // This macro generates all getters and setters for ApplicationState. // It provides X with the following arguments: @@ -28,15 +25,14 @@ Module Name: namespace winrt::Microsoft::Terminal::Settings::Model::implementation { - struct ApplicationState : ApplicationStateT, public BaseApplicationState + struct ApplicationState : public BaseApplicationState, ApplicationStateT { static Microsoft::Terminal::Settings::Model::ApplicationState SharedInstance(); ApplicationState(std::filesystem::path path) noexcept; - ~ApplicationState(); - void FromJson(const Json::Value& root) const noexcept override; - Json::Value ToJson() const noexcept override; + virtual void FromJson(const Json::Value& root) const noexcept override; + virtual Json::Value ToJson() const noexcept override; // // Methods // void Reload() const noexcept; diff --git a/src/cascadia/TerminalSettingsModel/BaseApplicationState.cpp b/src/cascadia/TerminalSettingsModel/BaseApplicationState.cpp index e27acaf959e..126b56e7144 100644 --- a/src/cascadia/TerminalSettingsModel/BaseApplicationState.cpp +++ b/src/cascadia/TerminalSettingsModel/BaseApplicationState.cpp @@ -18,7 +18,7 @@ BaseApplicationState::BaseApplicationState(std::filesystem::path path) noexcept _path{ std::move(path) }, _throttler{ std::chrono::seconds(1), [this]() { _write(); } } { - _read(); + // _read(); } // The destructor ensures that the last write is flushed to disk before returning. @@ -62,7 +62,7 @@ try throw winrt::hresult_error(WEB_E_INVALID_JSON_STRING, winrt::to_hstring(errs)); } - this->FromJson(root); + FromJson(root); } CATCH_LOG() diff --git a/src/cascadia/TerminalSettingsModel/ElevatedState.cpp b/src/cascadia/TerminalSettingsModel/ElevatedState.cpp new file mode 100644 index 00000000000..0590b33bbfc --- /dev/null +++ b/src/cascadia/TerminalSettingsModel/ElevatedState.cpp @@ -0,0 +1,163 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include "ElevatedState.h" +#include "CascadiaSettings.h" +#include "ElevatedState.g.cpp" + +#include "JsonUtils.h" +#include "FileUtils.h" + +constexpr std::wstring_view stateFileName{ L"elevated-state.json" }; + +namespace Microsoft::Terminal::Settings::Model::JsonUtils +{ + // This trait exists in order to serialize the std::unordered_set for GeneratedProfiles. + template + struct ConversionTrait> + { + std::unordered_set FromJson(const Json::Value& json) const + { + ConversionTrait trait; + std::unordered_set val; + val.reserve(json.size()); + + for (const auto& element : json) + { + val.emplace(trait.FromJson(element)); + } + + return val; + } + + bool CanConvert(const Json::Value& json) const + { + ConversionTrait trait; + return json.isArray() && std::all_of(json.begin(), json.end(), [trait](const auto& json) -> bool { return trait.CanConvert(json); }); + } + + Json::Value ToJson(const std::unordered_set& val) + { + ConversionTrait trait; + Json::Value json{ Json::arrayValue }; + + for (const auto& key : val) + { + json.append(trait.ToJson(key)); + } + + return json; + } + + std::string TypeDescription() const + { + return fmt::format("{}[]", ConversionTrait{}.TypeDescription()); + } + }; + + template + struct ConversionTrait> + { + winrt::Windows::Foundation::Collections::IVector FromJson(const Json::Value& json) const + { + ConversionTrait trait; + std::vector val; + val.reserve(json.size()); + + for (const auto& element : json) + { + val.push_back(trait.FromJson(element)); + } + + return winrt::single_threaded_vector(move(val)); + } + + bool CanConvert(const Json::Value& json) const + { + ConversionTrait trait; + return json.isArray() && std::all_of(json.begin(), json.end(), [trait](const auto& json) -> bool { return trait.CanConvert(json); }); + } + + Json::Value ToJson(const winrt::Windows::Foundation::Collections::IVector& val) + { + ConversionTrait trait; + Json::Value json{ Json::arrayValue }; + + for (const auto& key : val) + { + json.append(trait.ToJson(key)); + } + + return json; + } + + std::string TypeDescription() const + { + return fmt::format("vector ({})", ConversionTrait{}.TypeDescription()); + } + }; +} +using namespace ::Microsoft::Terminal::Settings::Model; + +namespace winrt::Microsoft::Terminal::Settings::Model::implementation +{ + ElevatedState::ElevatedState(std::filesystem::path path) noexcept : + BaseApplicationState{ path } {} + + // Returns the application-global ElevatedState object. + Microsoft::Terminal::Settings::Model::ElevatedState ElevatedState::SharedInstance() + { + // TODO! place in a totally different file! and path! + static auto state = winrt::make_self(GetBaseSettingsPath() / stateFileName); + state->Reload(); + return *state; + } + + void ElevatedState::FromJson(const Json::Value& root) const noexcept + { + auto state = _state.lock(); + // GetValueForKey() comes in two variants: + // * take a std::optional reference + // * return std::optional by value + // At the time of writing the former version skips missing fields in the json, + // but we want to explicitly clear state fields that were removed from state.json. +#define MTSM_ELEVATED_STATE_GEN(type, name, key, ...) state->name = JsonUtils::GetValueForKey>(root, key); + MTSM_ELEVATED_STATE_FIELDS(MTSM_ELEVATED_STATE_GEN) +#undef MTSM_ELEVATED_STATE_GEN + } + Json::Value ElevatedState::ToJson() const noexcept + { + Json::Value root{ Json::objectValue }; + + { + auto state = _state.lock_shared(); +#define MTSM_ELEVATED_STATE_GEN(type, name, key, ...) JsonUtils::SetValueForKey(root, key, state->name); + MTSM_ELEVATED_STATE_FIELDS(MTSM_ELEVATED_STATE_GEN) +#undef MTSM_ELEVATED_STATE_GEN + } + return root; + } + + // Generate all getter/setters +#define MTSM_ELEVATED_STATE_GEN(type, name, key, ...) \ + type ElevatedState::name() const noexcept \ + { \ + const auto state = _state.lock_shared(); \ + const auto& value = state->name; \ + return value ? *value : type{ __VA_ARGS__ }; \ + } \ + \ + void ElevatedState::name(const type& value) noexcept \ + { \ + { \ + auto state = _state.lock(); \ + state->name.emplace(value); \ + } \ + \ + _throttler(); \ + } + MTSM_ELEVATED_STATE_FIELDS(MTSM_ELEVATED_STATE_GEN) +#undef MTSM_ELEVATED_STATE_GEN + +} diff --git a/src/cascadia/TerminalSettingsModel/ElevatedState.h b/src/cascadia/TerminalSettingsModel/ElevatedState.h new file mode 100644 index 00000000000..5d7f4173184 --- /dev/null +++ b/src/cascadia/TerminalSettingsModel/ElevatedState.h @@ -0,0 +1,57 @@ +/*++ +Copyright (c) Microsoft Corporation +Licensed under the MIT license. + +Module Name: +- ElevatedState.h + +Abstract: +- If the CascadiaSettings class were AppData, then this class would be LocalAppData. + Put anything in here that you wouldn't want to be stored next to user-editable settings. +- Modify ElevatedState.idl and MTSM_ELEVATED_STATE_FIELDS to add new fields. +--*/ +#pragma once + +#include "BaseApplicationState.h" +#include "ElevatedState.g.h" +#include + +// This macro generates all getters and setters for ElevatedState. +// It provides X with the following arguments: +// (type, function name, JSON key, ...variadic construction arguments) +#define MTSM_ELEVATED_STATE_FIELDS(X) \ + X(Windows::Foundation::Collections::IVector, AllowedCommandlines, "allowedCommandlines") + +namespace winrt::Microsoft::Terminal::Settings::Model::implementation +{ + struct ElevatedState : ElevatedStateT, public BaseApplicationState + { + static Microsoft::Terminal::Settings::Model::ElevatedState SharedInstance(); + + ElevatedState(std::filesystem::path path) noexcept; + + void FromJson(const Json::Value& root) const noexcept override; + Json::Value ToJson() const noexcept override; + + // State getters/setters +#define MTSM_ELEVATED_STATE_GEN(type, name, key, ...) \ + type name() const noexcept; \ + void name(const type& value) noexcept; + MTSM_ELEVATED_STATE_FIELDS(MTSM_ELEVATED_STATE_GEN) +#undef MTSM_ELEVATED_STATE_GEN + + private: + struct state_t + { +#define MTSM_ELEVATED_STATE_GEN(type, name, key, ...) std::optional name{ __VA_ARGS__ }; + MTSM_ELEVATED_STATE_FIELDS(MTSM_ELEVATED_STATE_GEN) +#undef MTSM_ELEVATED_STATE_GEN + }; + til::shared_mutex _state; + }; +} + +namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation +{ + BASIC_FACTORY(ElevatedState); +} diff --git a/src/cascadia/TerminalSettingsModel/ElevatedState.idl b/src/cascadia/TerminalSettingsModel/ElevatedState.idl new file mode 100644 index 00000000000..09e2d05b025 --- /dev/null +++ b/src/cascadia/TerminalSettingsModel/ElevatedState.idl @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +namespace Microsoft.Terminal.Settings.Model +{ + [default_interface] runtimeclass ElevatedState { + static ElevatedState SharedInstance(); + + void Reload(); + + String FilePath { get; }; + + Windows.Foundation.Collections.IVector AllowedCommandlines { get; set; }; + } +} diff --git a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj index de191507ea8..ce12741d136 100644 --- a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj +++ b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj @@ -36,6 +36,9 @@ ApplicationState.idl + + ElevatedState.idl + CascadiaSettings.idl @@ -109,6 +112,9 @@ ApplicationState.idl + + ElevatedState.idl + CascadiaSettings.idl @@ -158,6 +164,7 @@ + From 2857324777ab529f9027f89520bd9d4d50fc2ad4 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Tue, 31 Aug 2021 16:15:49 -0500 Subject: [PATCH 03/92] proof of concept add a dialog to pop when opening new tabs --- src/cascadia/TerminalApp/AppLogic.cpp | 9 +++-- .../Resources/en-US/Resources.resw | 12 ++++++ src/cascadia/TerminalApp/TabManagement.cpp | 38 +++++++++++++++++- src/cascadia/TerminalApp/TerminalPage.cpp | 40 +++++++++++++------ src/cascadia/TerminalApp/TerminalPage.h | 4 +- src/cascadia/TerminalApp/TerminalPage.xaml | 5 +++ 6 files changed, 89 insertions(+), 19 deletions(-) diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp index 106da0fbdea..6b5b07bd0d8 100644 --- a/src/cascadia/TerminalApp/AppLogic.cpp +++ b/src/cascadia/TerminalApp/AppLogic.cpp @@ -189,7 +189,7 @@ namespace winrt::TerminalApp::implementation } AppLogic::AppLogic() : - _reloadState{ std::chrono::milliseconds(100), []() { ApplicationState::SharedInstance().Reload(); } } + _reloadState{ std::chrono::milliseconds(100), []() { ApplicationState::SharedInstance().Reload(); ElevatedState::SharedInstance().Reload(); } } { // 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, @@ -870,6 +870,7 @@ namespace winrt::TerminalApp::implementation { const std::filesystem::path settingsPath{ std::wstring_view{ CascadiaSettings::SettingsPath() } }; const std::filesystem::path statePath{ std::wstring_view{ ApplicationState::SharedInstance().FilePath() } }; + // const std::filesystem::path elevatedStatePath{ std::wstring_view{ ElevatedState::SharedInstance().FilePath() } }; _reader.create( settingsPath.parent_path().c_str(), @@ -879,14 +880,14 @@ namespace winrt::TerminalApp::implementation // editors, who will write a temp file, then rename it to be the // actual file you wrote. So listen for that too. wil::FolderChangeEvents::FileName | wil::FolderChangeEvents::LastWriteTime, - [this, settingsBasename = settingsPath.filename(), stateBasename = statePath.filename()](wil::FolderChangeEvent, PCWSTR fileModified) { + [=](wil::FolderChangeEvent, PCWSTR fileModified) { const auto modifiedBasename = std::filesystem::path{ fileModified }.filename(); - if (modifiedBasename == settingsBasename) + if (modifiedBasename == settingsPath.filename()) { _reloadSettings->Run(); } - else if (modifiedBasename == stateBasename) + else if (modifiedBasename == statePath.filename() /*|| modifiedBasename == elevatedStatePath.filename()*/) { _reloadState(); } diff --git a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw index f9e8cbf1265..ad38d7a9bf8 100644 --- a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw @@ -486,6 +486,18 @@ Warning + + Cancel + + + You are about t execute the following commandline. {TODO! format this}. Do you wish to continue? + + + Allow Commandline + + + Warning + Type a command name... diff --git a/src/cascadia/TerminalApp/TabManagement.cpp b/src/cascadia/TerminalApp/TabManagement.cpp index 849bfcc69d3..ed1f40d3a08 100644 --- a/src/cascadia/TerminalApp/TabManagement.cpp +++ b/src/cascadia/TerminalApp/TabManagement.cpp @@ -235,8 +235,44 @@ namespace winrt::TerminalApp::implementation // - profile: profile settings for this connection // - settings: the TerminalSettings object to use to create the TerminalControl with. // - existingConnection: optionally receives a connection from the outside world instead of attempting to create one - void TerminalPage::_CreateNewTabWithProfileAndSettings(const Profile& profile, const TerminalSettingsCreateResult& settings, TerminalConnection::ITerminalConnection existingConnection) + winrt::fire_and_forget TerminalPage::_CreateNewTabWithProfileAndSettings(Microsoft::Terminal::Settings::Model::Profile profile, Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult settings, TerminalConnection::ITerminalConnection existingConnection) { + if (_isElevated()) + { + auto cmdline{ settings.DefaultSettings().Commandline() }; + auto allowedCommandlines{ ElevatedState::SharedInstance().AllowedCommandlines() }; + bool commandlineWasAllowed = false; + + if (allowedCommandlines) + { + for (const auto& approved : allowedCommandlines) + { + if (approved == cmdline) + { + commandlineWasAllowed = true; + break; + } + } + } + else + { + allowedCommandlines = winrt::single_threaded_vector(); + } + if (!commandlineWasAllowed) + { + ContentDialogResult warningResult = co_await _ShowCommandlineApproveWarning(); + if (warningResult != ContentDialogResult::Primary) + { + co_return; + } + else + { + allowedCommandlines.Append(cmdline); + } + ElevatedState::SharedInstance().AllowedCommandlines(allowedCommandlines); + } + } + // Initialize the new tab // Create a connection based on the values in our settings object if we weren't given one. auto connection = existingConnection ? existingConnection : _CreateConnectionFromSettings(profile, settings.DefaultSettings()); diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 8ae486548f6..1f0f0d35fe6 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -49,6 +49,23 @@ namespace winrt namespace winrt::TerminalApp::implementation { + bool TerminalPage::_isElevated() const noexcept + { + // GH#2455 - Make sure to try/catch calls to Application::Current, + // because that _won't_ be an instance of TerminalApp::App in the + // LocalTests + try + { + // GH#3581 - There's a platform limitation that causes us to crash when we rearrange tabs. + // Xaml tries to send a drag visual (to wit: a screenshot) to the drag hosting process, + // but that process is running at a different IL than us. + // For now, we're disabling elevated drag. + return ::winrt::Windows::UI::Xaml::Application::Current().as<::winrt::TerminalApp::App>().Logic().IsElevated(); + } + CATCH_LOG(); + return false; + } + TerminalPage::TerminalPage() : _tabs{ winrt::single_threaded_observable_vector() }, _mruTabs{ winrt::single_threaded_observable_vector() }, @@ -128,19 +145,7 @@ namespace winrt::TerminalApp::implementation _tabView = _tabRow.TabView(); _rearranging = false; - // GH#2455 - Make sure to try/catch calls to Application::Current, - // because that _won't_ be an instance of TerminalApp::App in the - // LocalTests - auto isElevated = false; - try - { - // GH#3581 - There's a platform limitation that causes us to crash when we rearrange tabs. - // Xaml tries to send a drag visual (to wit: a screenshot) to the drag hosting process, - // but that process is running at a different IL than us. - // For now, we're disabling elevated drag. - isElevated = ::winrt::Windows::UI::Xaml::Application::Current().as<::winrt::TerminalApp::App>().Logic().IsElevated(); - } - CATCH_LOG(); + const auto isElevated = _isElevated(); if (_settings.GlobalSettings().UseAcrylicInTabRow()) { @@ -574,6 +579,15 @@ namespace winrt::TerminalApp::implementation co_return ContentDialogResult::None; } + winrt::Windows::Foundation::IAsyncOperation TerminalPage::_ShowCommandlineApproveWarning() + { + if (auto presenter{ _dialogPresenter.get() }) + { + co_return co_await presenter.ShowDialog(FindName(L"ApproveCommandlineWarning").try_as()); + } + co_return ContentDialogResult::None; + } + // Method Description: // - Builds the flyout (dropdown) attached to the new tab button, and // attaches it to the button. Populates the flyout with one entry per diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 0bdcf4aaeb1..4c3ae8f8ea7 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -179,17 +179,19 @@ namespace winrt::TerminalApp::implementation std::shared_ptr _windowIdToast{ nullptr }; std::shared_ptr _windowRenameFailedToast{ nullptr }; + bool _isElevated() const noexcept; void _ShowAboutDialog(); winrt::Windows::Foundation::IAsyncOperation _ShowCloseWarningDialog(); winrt::Windows::Foundation::IAsyncOperation _ShowCloseReadOnlyDialog(); winrt::Windows::Foundation::IAsyncOperation _ShowMultiLinePasteWarningDialog(); winrt::Windows::Foundation::IAsyncOperation _ShowLargePasteWarningDialog(); + winrt::Windows::Foundation::IAsyncOperation _ShowCommandlineApproveWarning(); void _CreateNewTabFlyout(); void _OpenNewTabDropdown(); HRESULT _OpenNewTab(const Microsoft::Terminal::Settings::Model::NewTerminalArgs& newTerminalArgs, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection = nullptr); void _CreateNewTabFromPane(std::shared_ptr pane); - void _CreateNewTabWithProfileAndSettings(const Microsoft::Terminal::Settings::Model::Profile& profile, const Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult& settings, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection = nullptr); + winrt::fire_and_forget _CreateNewTabWithProfileAndSettings(Microsoft::Terminal::Settings::Model::Profile profile, Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult settings, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection = nullptr); winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection _CreateConnectionFromSettings(Microsoft::Terminal::Settings::Model::Profile profile, Microsoft::Terminal::Settings::Model::TerminalSettings settings); winrt::fire_and_forget _OpenNewWindow(const bool elevate, const Microsoft::Terminal::Settings::Model::NewTerminalArgs newTerminalArgs); diff --git a/src/cascadia/TerminalApp/TerminalPage.xaml b/src/cascadia/TerminalApp/TerminalPage.xaml index 35ac1807db6..d7c37a70b8d 100644 --- a/src/cascadia/TerminalApp/TerminalPage.xaml +++ b/src/cascadia/TerminalApp/TerminalPage.xaml @@ -103,6 +103,11 @@ + + Date: Wed, 1 Sep 2021 12:52:45 -0500 Subject: [PATCH 04/92] nits, cleanup --- .../BaseApplicationState.cpp | 9 +- .../TerminalSettingsModel/ElevatedState.cpp | 87 ------------------- 2 files changed, 4 insertions(+), 92 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/BaseApplicationState.cpp b/src/cascadia/TerminalSettingsModel/BaseApplicationState.cpp index 126b56e7144..37382933c09 100644 --- a/src/cascadia/TerminalSettingsModel/BaseApplicationState.cpp +++ b/src/cascadia/TerminalSettingsModel/BaseApplicationState.cpp @@ -4,7 +4,6 @@ #include "pch.h" #include "BaseApplicationState.h" #include "CascadiaSettings.h" -// #include "ApplicationState.g.cpp" #include "JsonUtils.h" #include "FileUtils.h" @@ -12,13 +11,14 @@ constexpr std::wstring_view stateFileName{ L"state.json" }; using namespace ::Microsoft::Terminal::Settings::Model; -// namespace winrt::Microsoft::Terminal::Settings::Model::implementation -// { BaseApplicationState::BaseApplicationState(std::filesystem::path path) noexcept : _path{ std::move(path) }, _throttler{ std::chrono::seconds(1), [this]() { _write(); } } { - // _read(); + // DON'T _read() here! _read() will call FromJson, which is virtual, and + // needs to be implemented in a derived class. Classes that derive from + // BaseApplicationState should make sure to call Reload() after construction + // to ensure the data is loaded. } // The destructor ensures that the last write is flushed to disk before returning. @@ -80,4 +80,3 @@ try WriteUTF8FileAtomic(_path, content); } CATCH_LOG() -// } diff --git a/src/cascadia/TerminalSettingsModel/ElevatedState.cpp b/src/cascadia/TerminalSettingsModel/ElevatedState.cpp index 0590b33bbfc..94fd045ce4d 100644 --- a/src/cascadia/TerminalSettingsModel/ElevatedState.cpp +++ b/src/cascadia/TerminalSettingsModel/ElevatedState.cpp @@ -11,93 +11,6 @@ constexpr std::wstring_view stateFileName{ L"elevated-state.json" }; -namespace Microsoft::Terminal::Settings::Model::JsonUtils -{ - // This trait exists in order to serialize the std::unordered_set for GeneratedProfiles. - template - struct ConversionTrait> - { - std::unordered_set FromJson(const Json::Value& json) const - { - ConversionTrait trait; - std::unordered_set val; - val.reserve(json.size()); - - for (const auto& element : json) - { - val.emplace(trait.FromJson(element)); - } - - return val; - } - - bool CanConvert(const Json::Value& json) const - { - ConversionTrait trait; - return json.isArray() && std::all_of(json.begin(), json.end(), [trait](const auto& json) -> bool { return trait.CanConvert(json); }); - } - - Json::Value ToJson(const std::unordered_set& val) - { - ConversionTrait trait; - Json::Value json{ Json::arrayValue }; - - for (const auto& key : val) - { - json.append(trait.ToJson(key)); - } - - return json; - } - - std::string TypeDescription() const - { - return fmt::format("{}[]", ConversionTrait{}.TypeDescription()); - } - }; - - template - struct ConversionTrait> - { - winrt::Windows::Foundation::Collections::IVector FromJson(const Json::Value& json) const - { - ConversionTrait trait; - std::vector val; - val.reserve(json.size()); - - for (const auto& element : json) - { - val.push_back(trait.FromJson(element)); - } - - return winrt::single_threaded_vector(move(val)); - } - - bool CanConvert(const Json::Value& json) const - { - ConversionTrait trait; - return json.isArray() && std::all_of(json.begin(), json.end(), [trait](const auto& json) -> bool { return trait.CanConvert(json); }); - } - - Json::Value ToJson(const winrt::Windows::Foundation::Collections::IVector& val) - { - ConversionTrait trait; - Json::Value json{ Json::arrayValue }; - - for (const auto& key : val) - { - json.append(trait.ToJson(key)); - } - - return json; - } - - std::string TypeDescription() const - { - return fmt::format("vector ({})", ConversionTrait{}.TypeDescription()); - } - }; -} using namespace ::Microsoft::Terminal::Settings::Model; namespace winrt::Microsoft::Terminal::Settings::Model::implementation From c66a56656ec6e87d2aea25cc68ee2250880bcd09 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Wed, 1 Sep 2021 12:53:46 -0500 Subject: [PATCH 05/92] Allow a Pane to host a UserControl instead of a TermControl --- src/cascadia/TerminalApp/Pane.cpp | 132 ++++++++++++++++++++---------- src/cascadia/TerminalApp/Pane.h | 8 +- 2 files changed, 93 insertions(+), 47 deletions(-) diff --git a/src/cascadia/TerminalApp/Pane.cpp b/src/cascadia/TerminalApp/Pane.cpp index 28646b11b62..f15ace73c6a 100644 --- a/src/cascadia/TerminalApp/Pane.cpp +++ b/src/cascadia/TerminalApp/Pane.cpp @@ -34,7 +34,7 @@ static const Duration AnimationDuration = DurationHelper::FromTimeSpan(winrt::Wi winrt::Windows::UI::Xaml::Media::SolidColorBrush Pane::s_focusedBorderBrush = { nullptr }; winrt::Windows::UI::Xaml::Media::SolidColorBrush Pane::s_unfocusedBorderBrush = { nullptr }; -Pane::Pane(const Profile& profile, const TermControl& control, const bool lastFocused) : +Pane::Pane(const Profile& profile, const Controls::UserControl& control, const bool lastFocused) : _control{ control }, _lastActive{ lastFocused }, _profile{ profile } @@ -42,8 +42,12 @@ Pane::Pane(const Profile& profile, const TermControl& control, const bool lastFo _root.Children().Append(_border); _border.Child(_control); - _connectionStateChangedToken = _control.ConnectionStateChanged({ this, &Pane::_ControlConnectionStateChangedHandler }); - _warningBellToken = _control.WarningBell({ this, &Pane::_ControlWarningBellHandler }); + const auto& termControl{ _control.try_as() }; + if (termControl) + { + _connectionStateChangedToken = termControl.ConnectionStateChanged({ this, &Pane::_ControlConnectionStateChangedHandler }); + _warningBellToken = termControl.WarningBell({ this, &Pane::_ControlWarningBellHandler }); + } // 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 @@ -829,8 +833,12 @@ void Pane::_ControlConnectionStateChangedHandler(const winrt::Windows::Foundatio { return; } - - const auto newConnectionState = _control.ConnectionState(); + const auto& termControl{ _control.try_as() }; + if (!termControl) + { + return; + } + const auto newConnectionState = termControl.ConnectionState(); const auto previousConnectionState = std::exchange(_connectionState, newConnectionState); if (newConnectionState < ConnectionState::Closed) @@ -873,7 +881,9 @@ void Pane::_ControlWarningBellHandler(const winrt::Windows::Foundation::IInspect { return; } - if (_profile) + + const auto& termControl{ _control.try_as() }; + if (_profile && termControl) { // We don't want to do anything if nothing is set, so check for that first if (static_cast(_profile.BellStyle()) != 0) @@ -887,7 +897,7 @@ void Pane::_ControlWarningBellHandler(const winrt::Windows::Foundation::IInspect if (WI_IsFlagSet(_profile.BellStyle(), winrt::Microsoft::Terminal::Settings::Model::BellStyle::Window)) { - _control.BellLightOn(); + termControl.BellLightOn(); } // raise the event with the bool value corresponding to the taskbar flag @@ -942,7 +952,11 @@ void Pane::Shutdown() std::unique_lock lock{ _createCloseLock }; if (_IsLeaf()) { - _control.Close(); + const auto& termControl{ _control.try_as() }; + if (termControl) + { + termControl.Close(); + } } else { @@ -952,7 +966,7 @@ void Pane::Shutdown() } // Method Description: -// - Get the root UIElement of this pane. There may be a single TermControl as a +// - Get the root UIElement of this pane. There may be a single UserControl as a // child, or an entire tree of grids and panes as children of this element. // Arguments: // - @@ -993,10 +1007,11 @@ std::shared_ptr Pane::GetActivePane() // Arguments: // - // Return Value: -// - nullptr if this Pane is a parent, otherwise the TermControl of this Pane. -TermControl Pane::GetTerminalControl() +// - nullptr if this Pane is a parent or isn't hosting a Terminal, otherwise the +// TermControl of this Pane. +TermControl Pane::GetTerminalControl() const { - return _IsLeaf() ? _control : nullptr; + return _IsLeaf() ? _control.try_as() : nullptr; } // Method Description: @@ -1147,9 +1162,13 @@ void Pane::_FocusFirstChild() void Pane::UpdateSettings(const TerminalSettingsCreateResult& settings, const Profile& profile) { assert(_IsLeaf()); - + const auto& termControl{ _control.try_as() }; + if (!termControl) + { + return; + } _profile = profile; - auto controlSettings = _control.Settings().as(); + auto controlSettings = termControl.Settings().as(); // Update the parent of the control's settings object (and not the object itself) so // that any overrides made by the control don't get affected by the reload controlSettings.SetParent(settings.DefaultSettings()); @@ -1162,8 +1181,8 @@ void Pane::UpdateSettings(const TerminalSettingsCreateResult& settings, const Pr // sure the unfocused settings inherit from that. unfocusedSettings.SetParent(controlSettings); } - _control.UnfocusedAppearance(unfocusedSettings); - _control.UpdateSettings(); + termControl.UnfocusedAppearance(unfocusedSettings); + termControl.UpdateSettings(); } // Method Description: @@ -1294,8 +1313,12 @@ void Pane::_CloseChild(const bool closeFirst, const bool isDetaching) _id = remainingChild->Id(); // Add our new event handler before revoking the old one. - _connectionStateChangedToken = _control.ConnectionStateChanged({ this, &Pane::_ControlConnectionStateChangedHandler }); - _warningBellToken = _control.WarningBell({ this, &Pane::_ControlWarningBellHandler }); + const auto& termControl{ _control.try_as() }; + if (termControl) + { + _connectionStateChangedToken = termControl.ConnectionStateChanged({ this, &Pane::_ControlConnectionStateChangedHandler }); + _warningBellToken = termControl.WarningBell({ this, &Pane::_ControlWarningBellHandler }); + } // Revoke the old event handlers. Remove both the handlers for the panes // themselves closing, and remove their handlers for their controls @@ -1306,21 +1329,30 @@ void Pane::_CloseChild(const bool closeFirst, const bool isDetaching) // handlers since it is just getting moved. if (!isDetaching) { - closedChild->_control.ConnectionStateChanged(closedChild->_connectionStateChangedToken); - closedChild->_control.WarningBell(closedChild->_warningBellToken); + const auto& closedControl{ closedChild->_control.try_as() }; + if (closedControl) + { + closedControl.ConnectionStateChanged(closedChild->_connectionStateChangedToken); + closedControl.WarningBell(closedChild->_warningBellToken); + } } closedChild->Closed(closedChildClosedToken); remainingChild->Closed(remainingChildClosedToken); - remainingChild->_control.ConnectionStateChanged(remainingChild->_connectionStateChangedToken); - remainingChild->_control.WarningBell(remainingChild->_warningBellToken); + + const auto& remainingControl{ remainingChild->_control.try_as() }; + if (remainingControl) + { + remainingControl.ConnectionStateChanged(remainingChild->_connectionStateChangedToken); + remainingControl.WarningBell(remainingChild->_warningBellToken); + } // If either of our children was focused, we want to take that focus from // them. _lastActive = _firstChild->_lastActive || _secondChild->_lastActive; // Remove all the ui elements of the remaining child. This'll make sure - // we can re-attach the TermControl to our Grid. + // we can re-attach the UserControl to our Grid. remainingChild->_root.Children().Clear(); remainingChild->_border.Child(nullptr); @@ -1330,7 +1362,7 @@ void Pane::_CloseChild(const bool closeFirst, const bool isDetaching) _root.ColumnDefinitions().Clear(); _root.RowDefinitions().Clear(); - // Reattach the TermControl to our grid. + // Reattach the UserControl to our grid. _root.Children().Append(_border); _border.Child(_control); @@ -1389,8 +1421,12 @@ void Pane::_CloseChild(const bool closeFirst, const bool isDetaching) closedChild->Closed(closedChildClosedToken); if (!isDetaching) { - closedChild->_control.ConnectionStateChanged(closedChild->_connectionStateChangedToken); - closedChild->_control.WarningBell(closedChild->_warningBellToken); + const auto& closedControl{ closedChild->_control.try_as() }; + if (closedControl) + { + closedControl.ConnectionStateChanged(closedChild->_connectionStateChangedToken); + closedControl.WarningBell(closedChild->_warningBellToken); + } } // Reset our UI: @@ -1958,18 +1994,18 @@ std::optional Pane::PreCalculateCanSplit(const std::shared_ptr targe // Method Description: // - Split the focused pane in our tree of panes, and place the given -// TermControl into the newly created pane. If we're the focused pane, then +// UserControl into the newly created pane. If we're the focused pane, then // we'll create two new children, and place them side-by-side in our Grid. // Arguments: // - splitType: what type of split we want to create. // - profile: The profile to associate with the newly created pane. -// - control: A TermControl to use in the new pane. +// - control: A UserControl to use in the new pane. // Return Value: // - The two newly created Panes std::pair, std::shared_ptr> Pane::Split(SplitState splitType, const float splitSize, const Profile& profile, - const TermControl& control) + const Controls::UserControl& control) { if (!_IsLeaf()) { @@ -2075,10 +2111,11 @@ std::pair, std::shared_ptr> Pane::_Split(SplitState // modify our tree std::unique_lock lock{ _createCloseLock }; + const auto& termControl{ _control.try_as() }; // revoke our handler - the child will take care of the control now. - _control.ConnectionStateChanged(_connectionStateChangedToken); + termControl.ConnectionStateChanged(_connectionStateChangedToken); _connectionStateChangedToken.value = 0; - _control.WarningBell(_warningBellToken); + termControl.WarningBell(_warningBellToken); _warningBellToken.value = 0; // Remove our old GotFocus handler from the control. We don't what the @@ -2091,7 +2128,7 @@ std::pair, std::shared_ptr> Pane::_Split(SplitState _desiredSplitPosition = 1.0f - splitSize; // Remove any children we currently have. We can't add the existing - // TermControl to a new grid until we do this. + // UserControl to a new grid until we do this. _root.Children().Clear(); _border.Child(nullptr); @@ -2413,8 +2450,13 @@ float Pane::CalcSnappedDimension(const bool widthOrHeight, const float dimension // If requested size is already snapped, then both returned values equal this value. Pane::SnapSizeResult Pane::_CalcSnappedDimension(const bool widthOrHeight, const float dimension) const { + const auto& termControl{ _control.try_as() }; if (_IsLeaf()) { + if (!termControl) + { + return { dimension, dimension }; + } // If we're a leaf pane, align to the grid of controlling terminal const auto minSize = _GetMinSize(); @@ -2425,7 +2467,7 @@ Pane::SnapSizeResult Pane::_CalcSnappedDimension(const bool widthOrHeight, const return { minDimension, minDimension }; } - float lower = _control.SnapDimensionToGrid(widthOrHeight, dimension); + float lower = termControl.SnapDimensionToGrid(widthOrHeight, dimension); if (widthOrHeight) { lower += WI_IsFlagSet(_borders, Borders::Left) ? PaneBorderSize : 0; @@ -2445,7 +2487,7 @@ Pane::SnapSizeResult Pane::_CalcSnappedDimension(const bool widthOrHeight, const } else { - const auto cellSize = _control.CharacterDimensions(); + const auto cellSize = termControl.CharacterDimensions(); const auto higher = lower + (widthOrHeight ? cellSize.Width : cellSize.Height); return { lower, higher }; } @@ -2490,7 +2532,8 @@ Pane::SnapSizeResult Pane::_CalcSnappedDimension(const bool widthOrHeight, const // - void Pane::_AdvanceSnappedDimension(const bool widthOrHeight, LayoutSizeNode& sizeNode) const { - if (_IsLeaf()) + const auto& termControl{ _control.try_as() }; + if (_IsLeaf() && termControl) { // We're a leaf pane, so just add one more row or column (unless isMinimumSize // is true, see below). @@ -2505,11 +2548,11 @@ void Pane::_AdvanceSnappedDimension(const bool widthOrHeight, LayoutSizeNode& si } else { - const auto cellSize = _control.CharacterDimensions(); + const auto cellSize = termControl.CharacterDimensions(); sizeNode.size += widthOrHeight ? cellSize.Width : cellSize.Height; } } - else + else if (!_IsLeaf()) { // We're a parent pane, so we have to advance dimension of our children panes. In // fact, we advance only one child (chosen later) to keep the growth fine-grained. @@ -2611,7 +2654,8 @@ Size Pane::_GetMinSize() const { if (_IsLeaf()) { - auto controlSize = _control.MinimumSize(); + const auto& termControl{ _control.try_as() }; + auto controlSize = termControl ? termControl.MinimumSize() : Size{ 1, 1 }; auto newWidth = controlSize.Width; auto newHeight = controlSize.Height; @@ -2800,7 +2844,8 @@ std::optional Pane::PreCalculateAutoSplit(const std::shared_ptrContainsReadOnly() || _secondChild->ContainsReadOnly()); + const auto& termControl{ GetTerminalControl() }; + return termControl ? termControl.ReadOnly() : (_firstChild->ContainsReadOnly() || _secondChild->ContainsReadOnly()); } // Method Description: @@ -2813,13 +2858,14 @@ bool Pane::ContainsReadOnly() const // - void Pane::CollectTaskbarStates(std::vector& states) { - if (_IsLeaf()) + const auto& termControl{ GetTerminalControl() }; + if (termControl) { - auto tbState{ winrt::make(_control.TaskbarState(), - _control.TaskbarProgress()) }; + auto tbState{ winrt::make(termControl.TaskbarState(), + termControl.TaskbarProgress()) }; states.push_back(tbState); } - else + else if (!_IsLeaf()) { _firstChild->CollectTaskbarStates(states); _secondChild->CollectTaskbarStates(states); diff --git a/src/cascadia/TerminalApp/Pane.h b/src/cascadia/TerminalApp/Pane.h index 2b9a49c0ec0..413dd8e64ad 100644 --- a/src/cascadia/TerminalApp/Pane.h +++ b/src/cascadia/TerminalApp/Pane.h @@ -48,11 +48,11 @@ class Pane : public std::enable_shared_from_this { public: Pane(const winrt::Microsoft::Terminal::Settings::Model::Profile& profile, - const winrt::Microsoft::Terminal::Control::TermControl& control, + const winrt::Windows::UI::Xaml::Controls::UserControl& control, const bool lastFocused = false); std::shared_ptr GetActivePane(); - winrt::Microsoft::Terminal::Control::TermControl GetTerminalControl(); + winrt::Microsoft::Terminal::Control::TermControl GetTerminalControl() const; winrt::Microsoft::Terminal::Settings::Model::Profile GetFocusedProfile(); // Method Description: @@ -84,7 +84,7 @@ class Pane : public std::enable_shared_from_this std::pair, std::shared_ptr> Split(winrt::Microsoft::Terminal::Settings::Model::SplitState splitType, const float splitSize, const winrt::Microsoft::Terminal::Settings::Model::Profile& profile, - const winrt::Microsoft::Terminal::Control::TermControl& control); + const winrt::Windows::UI::Xaml::Controls::UserControl& control); bool ToggleSplitOrientation(); float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const; std::optional PreCalculateAutoSplit(const std::shared_ptr target, @@ -155,7 +155,7 @@ class Pane : public std::enable_shared_from_this winrt::Windows::UI::Xaml::Controls::Grid _root{}; winrt::Windows::UI::Xaml::Controls::Border _border{}; - winrt::Microsoft::Terminal::Control::TermControl _control{ nullptr }; + winrt::Windows::UI::Xaml::Controls::UserControl _control{ nullptr }; winrt::Microsoft::Terminal::TerminalConnection::ConnectionState _connectionState{ winrt::Microsoft::Terminal::TerminalConnection::ConnectionState::NotConnected }; static winrt::Windows::UI::Xaml::Media::SolidColorBrush s_focusedBorderBrush; static winrt::Windows::UI::Xaml::Media::SolidColorBrush s_unfocusedBorderBrush; From ed1cf2aeac76bde54c57c4058ba92e5db742c0a3 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Wed, 1 Sep 2021 14:57:41 -0500 Subject: [PATCH 06/92] add the boilerplate for a custom content dialog like thing --- .../TerminalApp/AdminWarningPlaceholder.cpp | 27 ++++++++++++ .../TerminalApp/AdminWarningPlaceholder.h | 29 +++++++++++++ .../TerminalApp/AdminWarningPlaceholder.idl | 10 +++++ .../TerminalApp/AdminWarningPlaceholder.xaml | 41 +++++++++++++++++++ .../Resources/en-US/Resources.resw | 8 ++-- .../TerminalApp/TerminalAppLib.vcxproj | 13 ++++++ src/cascadia/TerminalApp/TerminalPage.xaml | 4 +- 7 files changed, 126 insertions(+), 6 deletions(-) create mode 100644 src/cascadia/TerminalApp/AdminWarningPlaceholder.cpp create mode 100644 src/cascadia/TerminalApp/AdminWarningPlaceholder.h create mode 100644 src/cascadia/TerminalApp/AdminWarningPlaceholder.idl create mode 100644 src/cascadia/TerminalApp/AdminWarningPlaceholder.xaml diff --git a/src/cascadia/TerminalApp/AdminWarningPlaceholder.cpp b/src/cascadia/TerminalApp/AdminWarningPlaceholder.cpp new file mode 100644 index 00000000000..54ad44ef3fb --- /dev/null +++ b/src/cascadia/TerminalApp/AdminWarningPlaceholder.cpp @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#pragma once + +#include "pch.h" +#include "AdminWarningPlaceholder.h" +#include "AdminWarningPlaceholder.g.cpp" +using namespace winrt::Windows::UI::Xaml; + +namespace winrt::TerminalApp::implementation +{ + AdminWarningPlaceholder::AdminWarningPlaceholder() + { + InitializeComponent(); + } + void AdminWarningPlaceholder::_primaryButtonClick(winrt::Windows::Foundation::IInspectable const& /*sender*/, + RoutedEventArgs const& e) + { + _PrimaryButtonClickedHandlers(*this, e); + } + void AdminWarningPlaceholder::_cancelButtonClick(winrt::Windows::Foundation::IInspectable const& /*sender*/, + RoutedEventArgs const& e) + { + _CancelButtonClickedHandlers(*this, e); + } +} diff --git a/src/cascadia/TerminalApp/AdminWarningPlaceholder.h b/src/cascadia/TerminalApp/AdminWarningPlaceholder.h new file mode 100644 index 00000000000..a8e8e253b23 --- /dev/null +++ b/src/cascadia/TerminalApp/AdminWarningPlaceholder.h @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#pragma once + +#include "AdminWarningPlaceholder.g.h" +#include "../../cascadia/inc/cppwinrt_utils.h" + +namespace winrt::TerminalApp::implementation +{ + struct AdminWarningPlaceholder : AdminWarningPlaceholderT + { + AdminWarningPlaceholder(); + TYPED_EVENT(PrimaryButtonClicked, TerminalApp::AdminWarningPlaceholder, winrt::Windows::UI::Xaml::RoutedEventArgs); + TYPED_EVENT(CancelButtonClicked, TerminalApp::AdminWarningPlaceholder, winrt::Windows::UI::Xaml::RoutedEventArgs); + + private: + friend struct AdminWarningPlaceholderT; // friend our parent so it can bind private event handlers + void _primaryButtonClick(winrt::Windows::Foundation::IInspectable const& sender, + winrt::Windows::UI::Xaml::RoutedEventArgs const& e); + void _cancelButtonClick(winrt::Windows::Foundation::IInspectable const& sender, + winrt::Windows::UI::Xaml::RoutedEventArgs const& e); + }; +} + +namespace winrt::TerminalApp::factory_implementation +{ + BASIC_FACTORY(AdminWarningPlaceholder); +} diff --git a/src/cascadia/TerminalApp/AdminWarningPlaceholder.idl b/src/cascadia/TerminalApp/AdminWarningPlaceholder.idl new file mode 100644 index 00000000000..f7505fe1823 --- /dev/null +++ b/src/cascadia/TerminalApp/AdminWarningPlaceholder.idl @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +namespace TerminalApp +{ + [default_interface] runtimeclass AdminWarningPlaceholder : Windows.UI.Xaml.Controls.UserControl + { + AdminWarningPlaceholder(); + } +} diff --git a/src/cascadia/TerminalApp/AdminWarningPlaceholder.xaml b/src/cascadia/TerminalApp/AdminWarningPlaceholder.xaml new file mode 100644 index 00000000000..546f79baf31 --- /dev/null +++ b/src/cascadia/TerminalApp/AdminWarningPlaceholder.xaml @@ -0,0 +1,41 @@ + + + + + + + + +