diff --git a/.github/actions/spelling/allow/apis.txt b/.github/actions/spelling/allow/apis.txt index 87d479f7f..85121e065 100644 --- a/.github/actions/spelling/allow/apis.txt +++ b/.github/actions/spelling/allow/apis.txt @@ -133,6 +133,7 @@ SRWLOCK STDCPP STDMETHOD strchr +strcpy streambuf strtoul Stubless diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index 94f8fc911..05ccc895a 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -169,6 +169,7 @@ brandings BRK Browsable bsearch +Bspace bstr BTNFACE buf @@ -270,10 +271,12 @@ cmder CMDEXT Cmdlet cmdline +cmh CMOUSEBUTTONS cmp cmpeq cmt +cmw cmyk CNL cnt @@ -406,11 +409,13 @@ csbiex csharp CSHORT CSIDL +Cspace csproj Csr csrmsg CSRSS csrutil +css cstdarg cstddef cstdio @@ -509,6 +514,7 @@ DECAWM DECCKM DECCOLM DECDHL +DECDLD DECDWL DECEKBD DECID @@ -789,6 +795,7 @@ FONTENUMPROC FONTFACE FONTFAMILY FONTHEIGHT +FONTINFO fontlist FONTOK FONTSIZE @@ -902,6 +909,7 @@ github gitlab gle globals +GLYPHENTRY gmail GMEM GNUC @@ -950,6 +958,7 @@ hdrstop HEIGHTSCROLL hfile hfont +hfontresource hglobal hhh HHmm @@ -1272,6 +1281,7 @@ locsrc locstudio Loewen LOGFONT +LOGFONTA LOGFONTW logissue lowercased @@ -1935,6 +1945,7 @@ realloc reamapping rects redef +redefinable Redir redirector redist @@ -1980,6 +1991,7 @@ rfc rftp rgb rgba +RGBCOLOR rgbi rgci rgfae @@ -2149,6 +2161,7 @@ SIGDN SINGLEFLAG SINGLETHREADED siup +sixel SIZEBOX sizeof SIZESCROLL @@ -2754,6 +2767,7 @@ WTo wtof wtoi WTs +WTSOFTFONT wtw wtypes Wubi diff --git a/build/pipelines/release.yml b/build/pipelines/release.yml index ff6d49cf7..e200db166 100644 --- a/build/pipelines/release.yml +++ b/build/pipelines/release.yml @@ -2,8 +2,9 @@ trigger: none pr: none -pool: - name: Package ES Standard Build +pool: + name: WinDevPool-L + demands: ImageOverride -equals WinDevVS16-latest parameters: - name: branding @@ -70,11 +71,9 @@ jobs: clean: true submodules: true persistCredentials: True - - task: PkgESSetupBuild@10 + - task: PkgESSetupBuild@12 displayName: Package ES - Setup Build inputs: - useDfs: false - productName: OpenConsole disableOutputRedirect: true - task: PowerShell@2 displayName: Rationalize Build Platform @@ -275,11 +274,9 @@ jobs: clean: true submodules: true persistCredentials: True - - task: PkgESSetupBuild@10 + - task: PkgESSetupBuild@12 displayName: Package ES - Setup Build inputs: - useDfs: false - productName: OpenConsole disableOutputRedirect: true - task: DownloadBuildArtifacts@0 displayName: Download Artifacts (*.appx, *.msix) @@ -354,11 +351,9 @@ jobs: clean: true submodules: true persistCredentials: True - - task: PkgESSetupBuild@10 + - task: PkgESSetupBuild@12 displayName: Package ES - Setup Build inputs: - useDfs: false - productName: OpenConsole disableOutputRedirect: true - task: DownloadBuildArtifacts@0 displayName: Download x86 PublicTerminalCore @@ -480,7 +475,7 @@ jobs: mv Microsoft.WindowsTerminal_8wekyb3d8bbwe.msixbundle .\WindowsTerminal.app\ workingDirectory: $(System.ArtifactsDirectory)\appxbundle-signed - - task: PkgESVPack@10 + - task: PkgESVPack@12 displayName: 'Package ES - VPack' env: SYSTEM_ACCESSTOKEN: $(System.AccessToken) diff --git a/build/pipelines/templates/build-console-audit-job.yml b/build/pipelines/templates/build-console-audit-job.yml index 1c9a90d6e..2f1283f34 100644 --- a/build/pipelines/templates/build-console-audit-job.yml +++ b/build/pipelines/templates/build-console-audit-job.yml @@ -8,9 +8,12 @@ jobs: variables: BuildConfiguration: AuditMode BuildPlatform: ${{ parameters.platform }} - pool: "windevbuildagents" - # The public pool is also an option! - # pool: { vmImage: windows-2019 } + pool: + ${{ if eq(variables['System.CollectionUri'], 'https://dev.azure.com/ms/') }}: + name: WinDevPoolOSS-L + ${{ if ne(variables['System.CollectionUri'], 'https://dev.azure.com/ms/') }}: + name: WinDevPool-L + demands: ImageOverride -equals WinDevVS16-latest steps: - checkout: self diff --git a/build/pipelines/templates/build-console-ci.yml b/build/pipelines/templates/build-console-ci.yml index 0b9ce8685..0ff8b6b54 100644 --- a/build/pipelines/templates/build-console-ci.yml +++ b/build/pipelines/templates/build-console-ci.yml @@ -11,9 +11,12 @@ jobs: variables: BuildConfiguration: ${{ parameters.configuration }} BuildPlatform: ${{ parameters.platform }} - pool: "windevbuildagents" - # The public pool is also an option! - # pool: { vmImage: windows-2019 } + pool: + ${{ if eq(variables['System.CollectionUri'], 'https://dev.azure.com/ms/') }}: + name: WinDevPoolOSS-L + ${{ if ne(variables['System.CollectionUri'], 'https://dev.azure.com/ms/') }}: + name: WinDevPool-L + demands: ImageOverride -equals WinDevVS16-latest steps: - template: build-console-steps.yml diff --git a/build/pipelines/templates/build-console-pgo.yml b/build/pipelines/templates/build-console-pgo.yml index 8af4ec5d8..1e33e82c8 100644 --- a/build/pipelines/templates/build-console-pgo.yml +++ b/build/pipelines/templates/build-console-pgo.yml @@ -12,9 +12,12 @@ jobs: BuildConfiguration: ${{ parameters.configuration }} BuildPlatform: ${{ parameters.platform }} PGOBuildMode: 'Instrument' - pool: "windevbuildagents" - # The public pool is also an option! - # pool: { vmImage: windows-2019 } + pool: + ${{ if eq(variables['System.CollectionUri'], 'https://dev.azure.com/ms/') }}: + name: WinDevPoolOSS-L + ${{ if ne(variables['System.CollectionUri'], 'https://dev.azure.com/ms/') }}: + name: WinDevPool-L + demands: ImageOverride -equals WinDevVS16-latest steps: - template: build-console-steps.yml diff --git a/src/buffer/out/TextColor.cpp b/src/buffer/out/TextColor.cpp index 5a628384d..dcd0a8df3 100644 --- a/src/buffer/out/TextColor.cpp +++ b/src/buffer/out/TextColor.cpp @@ -192,21 +192,24 @@ COLORREF TextColor::GetColor(const std::array& colorTable, const unsigned long index; return _BitScanForward(&index, mask) ? til::at(colorTable, static_cast(index) + 8) : defaultColor; // 5. #elif _M_AMD64 - // If you look closely this SSE2 algorithm is the exact same as the AVX one. + // If you look closely this SSE2 algorithm is the same as the AVX one. // The two differences are that we need to: // * do everything twice, because SSE is limited to 128 bits and not 256. // * use _mm_packs_epi32 to merge two 128 bits vectors into one in step 3.5. // _mm_packs_epi32 takes two SSE registers and truncates all 8 DWORDs into 8 WORDs, // the latter of which fits into a single register (which is then used in the identical step 4). + // * since the result are now 8 WORDs, we need to use _mm_movemask_epi8 (there's no 16-bit variant), + // which unlike AVX's step 4 results in in something like 0b0000110000000000. + // --> the index returned by _BitScanForward must be divided by 2. const auto haystack1 = _mm_loadu_si128(reinterpret_cast(colorTable.data() + 0)); const auto haystack2 = _mm_loadu_si128(reinterpret_cast(colorTable.data() + 4)); const auto needle = _mm_set1_epi32(__builtin_bit_cast(int, defaultColor)); const auto result1 = _mm_cmpeq_epi32(haystack1, needle); const auto result2 = _mm_cmpeq_epi32(haystack2, needle); const auto result = _mm_packs_epi32(result1, result2); // 3.5 - const auto mask = _mm_movemask_ps(_mm_castsi128_ps(result)); + const auto mask = _mm_movemask_epi8(result); unsigned long index; - return _BitScanForward(&index, mask) ? til::at(colorTable, static_cast(index) + 8) : defaultColor; + return _BitScanForward(&index, mask) ? til::at(colorTable, static_cast(index / 2) + 8) : defaultColor; #else for (size_t i = 0; i < 8; i++) { diff --git a/src/cascadia/LocalTests_SettingsModel/CommandTests.cpp b/src/cascadia/LocalTests_SettingsModel/CommandTests.cpp index ce5f06014..bdc4d5692 100644 --- a/src/cascadia/LocalTests_SettingsModel/CommandTests.cpp +++ b/src/cascadia/LocalTests_SettingsModel/CommandTests.cpp @@ -397,6 +397,10 @@ namespace SettingsModelLocalTests "name":"action6", "command": { "action": "newWindow", "startingDirectory":"C:\\foo", "commandline": "bar.exe" } }, + { + "name":"action7_startingDirectoryWithTrailingSlash", + "command": { "action": "newWindow", "startingDirectory":"C:\\", "commandline": "bar.exe" } + }, ])" }; const auto commands0Json = VerifyParseSucceeded(commands0String); @@ -405,7 +409,7 @@ namespace SettingsModelLocalTests VERIFY_ARE_EQUAL(0u, commands.Size()); auto warnings = implementation::Command::LayerJson(commands, commands0Json); VERIFY_ARE_EQUAL(0u, warnings.size()); - VERIFY_ARE_EQUAL(7u, commands.Size()); + VERIFY_ARE_EQUAL(8u, commands.Size()); { auto command = commands.Lookup(L"action0"); @@ -503,5 +507,20 @@ namespace SettingsModelLocalTests L"cmdline: \"%s\"", cmdline.c_str())); VERIFY_ARE_EQUAL(L"--startingDirectory \"C:\\foo\" -- \"bar.exe\"", terminalArgs.ToCommandline()); } + + { + auto command = commands.Lookup(L"action7_startingDirectoryWithTrailingSlash"); + VERIFY_IS_NOT_NULL(command); + VERIFY_IS_NOT_NULL(command.ActionAndArgs()); + VERIFY_ARE_EQUAL(ShortcutAction::NewWindow, command.ActionAndArgs().Action()); + const auto& realArgs = command.ActionAndArgs().Args().try_as(); + VERIFY_IS_NOT_NULL(realArgs); + const auto& terminalArgs = realArgs.TerminalArgs(); + VERIFY_IS_NOT_NULL(terminalArgs); + auto cmdline = terminalArgs.ToCommandline(); + Log::Comment(NoThrowString().Format( + L"cmdline: \"%s\"", cmdline.c_str())); + VERIFY_ARE_EQUAL(L"--startingDirectory \"C:\\\\\" -- \"bar.exe\"", terminalArgs.ToCommandline()); + } } } diff --git a/src/cascadia/LocalTests_SettingsModel/KeyBindingsTests.cpp b/src/cascadia/LocalTests_SettingsModel/KeyBindingsTests.cpp index a5479f6a7..e5c354d25 100644 --- a/src/cascadia/LocalTests_SettingsModel/KeyBindingsTests.cpp +++ b/src/cascadia/LocalTests_SettingsModel/KeyBindingsTests.cpp @@ -40,7 +40,7 @@ namespace SettingsModelLocalTests TEST_METHOD(ManyKeysSameAction); TEST_METHOD(LayerKeybindings); TEST_METHOD(UnbindKeybindings); - + TEST_METHOD(TestExplicitUnbind); TEST_METHOD(TestArbitraryArgs); TEST_METHOD(TestSplitPaneArgs); @@ -232,6 +232,31 @@ namespace SettingsModelLocalTests VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast('C'), 0 })); } + void KeyBindingsTests::TestExplicitUnbind() + { + const std::string bindings0String{ R"([ { "command": "copy", "keys": ["ctrl+c"] } ])" }; + const std::string bindings1String{ R"([ { "command": "unbound", "keys": ["ctrl+c"] } ])" }; + const std::string bindings2String{ R"([ { "command": "copy", "keys": ["ctrl+c"] } ])" }; + + const auto bindings0Json = VerifyParseSucceeded(bindings0String); + const auto bindings1Json = VerifyParseSucceeded(bindings1String); + const auto bindings2Json = VerifyParseSucceeded(bindings2String); + + const KeyChord keyChord{ VirtualKeyModifiers::Control, static_cast('C'), 0 }; + + auto actionMap = winrt::make_self(); + VERIFY_IS_FALSE(actionMap->IsKeyChordExplicitlyUnbound(keyChord)); + + actionMap->LayerJson(bindings0Json); + VERIFY_IS_FALSE(actionMap->IsKeyChordExplicitlyUnbound(keyChord)); + + actionMap->LayerJson(bindings1Json); + VERIFY_IS_TRUE(actionMap->IsKeyChordExplicitlyUnbound(keyChord)); + + actionMap->LayerJson(bindings2Json); + VERIFY_IS_FALSE(actionMap->IsKeyChordExplicitlyUnbound(keyChord)); + } + void KeyBindingsTests::TestArbitraryArgs() { const std::string bindings0String{ R"([ diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp index 60401122c..f88017e1e 100644 --- a/src/cascadia/TerminalApp/AppActionHandlers.cpp +++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp @@ -264,12 +264,12 @@ namespace winrt::TerminalApp::implementation { if (args == nullptr) { - _OpenNewTab(nullptr); + LOG_IF_FAILED(_OpenNewTab(nullptr)); args.Handled(true); } else if (const auto& realArgs = args.ActionArgs().try_as()) { - _OpenNewTab(realArgs.TerminalArgs()); + LOG_IF_FAILED(_OpenNewTab(realArgs.TerminalArgs())); args.Handled(true); } } diff --git a/src/cascadia/TerminalApp/AppKeyBindings.cpp b/src/cascadia/TerminalApp/AppKeyBindings.cpp index 71a3a03bb..3f921bc55 100644 --- a/src/cascadia/TerminalApp/AppKeyBindings.cpp +++ b/src/cascadia/TerminalApp/AppKeyBindings.cpp @@ -21,6 +21,11 @@ namespace winrt::TerminalApp::implementation return false; } + bool AppKeyBindings::IsKeyChordExplicitlyUnbound(const KeyChord& kc) + { + return _actionMap.IsKeyChordExplicitlyUnbound(kc); + } + void AppKeyBindings::SetDispatch(const winrt::TerminalApp::ShortcutActionDispatch& dispatch) { _dispatch = dispatch; diff --git a/src/cascadia/TerminalApp/AppKeyBindings.h b/src/cascadia/TerminalApp/AppKeyBindings.h index 5004edeca..f4f6c20ba 100644 --- a/src/cascadia/TerminalApp/AppKeyBindings.h +++ b/src/cascadia/TerminalApp/AppKeyBindings.h @@ -20,6 +20,7 @@ namespace winrt::TerminalApp::implementation AppKeyBindings() = default; bool TryKeyChord(winrt::Microsoft::Terminal::Control::KeyChord const& kc); + bool IsKeyChordExplicitlyUnbound(winrt::Microsoft::Terminal::Control::KeyChord const& kc); void SetDispatch(const winrt::TerminalApp::ShortcutActionDispatch& dispatch); void SetActionMap(const Microsoft::Terminal::Settings::Model::IActionMapView& actionMap); diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp index 441fbd4de..420630774 100644 --- a/src/cascadia/TerminalApp/AppLogic.cpp +++ b/src/cascadia/TerminalApp/AppLogic.cpp @@ -203,12 +203,16 @@ namespace winrt::TerminalApp::implementation _isElevated = _isUserAdmin(); _root = winrt::make_self(); - _reloadSettings = std::make_shared>(_root->Dispatcher(), std::chrono::milliseconds(100), [weakSelf = get_weak()]() { + _reloadSettings = std::make_shared>(winrt::Windows::System::DispatcherQueue::GetForCurrentThread(), std::chrono::milliseconds(100), [weakSelf = get_weak()]() { if (auto self{ weakSelf.get() }) { self->_ReloadSettings(); } }); + + _languageProfileNotifier = winrt::make_self([this]() { + _reloadSettings->Run(); + }); } // Method Description: @@ -1125,28 +1129,11 @@ namespace winrt::TerminalApp::implementation } } - // Method Description: - // - Gets the taskbar state value from the last active control - // Return Value: - // - The taskbar state of the last active control - uint64_t AppLogic::GetLastActiveControlTaskbarState() + winrt::TerminalApp::TaskbarState AppLogic::TaskbarState() { if (_root) { - return _root->GetLastActiveControlTaskbarState(); - } - return {}; - } - - // Method Description: - // - Gets the taskbar progress value from the last active control - // Return Value: - // - The taskbar progress of the last active control - uint64_t AppLogic::GetLastActiveControlTaskbarProgress() - { - if (_root) - { - return _root->GetLastActiveControlTaskbarProgress(); + return _root->TaskbarState(); } return {}; } @@ -1229,6 +1216,11 @@ namespace winrt::TerminalApp::implementation auto actions = winrt::single_threaded_vector(std::move(appArgs.GetStartupActions())); _root->ProcessStartupActions(actions, false, cwd); + + if (appArgs.IsHandoffListener()) + { + _root->SetInboundListener(true); + } } // Return the result of parsing with commandline, though it may or may not be used. return result; diff --git a/src/cascadia/TerminalApp/AppLogic.h b/src/cascadia/TerminalApp/AppLogic.h index 3d7782039..4131d28f1 100644 --- a/src/cascadia/TerminalApp/AppLogic.h +++ b/src/cascadia/TerminalApp/AppLogic.h @@ -5,8 +5,9 @@ #include "AppLogic.g.h" #include "FindTargetWindowResult.g.h" -#include "TerminalPage.h" #include "Jumplist.h" +#include "LanguageProfileNotifier.h" +#include "TerminalPage.h" #include #include @@ -89,8 +90,7 @@ namespace winrt::TerminalApp::implementation void WindowCloseButtonClicked(); - uint64_t GetLastActiveControlTaskbarState(); - uint64_t GetLastActiveControlTaskbarProgress(); + winrt::TerminalApp::TaskbarState TaskbarState(); winrt::Windows::Foundation::IAsyncOperation ShowDialog(winrt::Windows::UI::Xaml::Controls::ContentDialog dialog); @@ -110,12 +110,8 @@ namespace winrt::TerminalApp::implementation // ALSO: If you add any UIElements as roots here, make sure they're // updated in _ApplyTheme. The root currently is _root. winrt::com_ptr _root{ nullptr }; - Microsoft::Terminal::Settings::Model::CascadiaSettings _settings{ nullptr }; - wil::unique_folder_change_reader_nothrow _reader; - std::shared_ptr> _reloadSettings; - til::throttled_func_trailing<> _reloadState; winrt::hstring _settingsLoadExceptionText; HRESULT _settingsLoadedResult = S_OK; bool _loadedInitialSettings = false; @@ -124,6 +120,15 @@ namespace winrt::TerminalApp::implementation ::TerminalApp::AppCommandlineArgs _appArgs; ::TerminalApp::AppCommandlineArgs _settingsAppArgs; + + std::shared_ptr> _reloadSettings; + til::throttled_func_trailing<> _reloadState; + + // These fields invoke _reloadSettings and must be destroyed before _reloadSettings. + // (C++ destroys members in reverse-declaration-order.) + winrt::com_ptr _languageProfileNotifier; + wil::unique_folder_change_reader_nothrow _reader; + static TerminalApp::FindTargetWindowResult _doFindTargetWindow(winrt::array_view args, const Microsoft::Terminal::Settings::Model::WindowingMode& windowingBehavior); diff --git a/src/cascadia/TerminalApp/AppLogic.idl b/src/cascadia/TerminalApp/AppLogic.idl index bb334d26f..0b8891038 100644 --- a/src/cascadia/TerminalApp/AppLogic.idl +++ b/src/cascadia/TerminalApp/AppLogic.idl @@ -68,8 +68,7 @@ namespace TerminalApp void TitlebarClicked(); void WindowCloseButtonClicked(); - UInt64 GetLastActiveControlTaskbarState(); - UInt64 GetLastActiveControlTaskbarProgress(); + TaskbarState TaskbarState{ get; }; FindTargetWindowResult FindTargetWindow(String[] args); diff --git a/src/cascadia/TerminalApp/LanguageProfileNotifier.cpp b/src/cascadia/TerminalApp/LanguageProfileNotifier.cpp new file mode 100644 index 000000000..9655947a4 --- /dev/null +++ b/src/cascadia/TerminalApp/LanguageProfileNotifier.cpp @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include "LanguageProfileNotifier.h" + +using namespace winrt::TerminalApp::implementation; + +LanguageProfileNotifier::LanguageProfileNotifier(std::function&& callback) : + _callback{ std::move(callback) }, + _currentKeyboardLayout{ GetKeyboardLayout(0) } +{ + const auto manager = wil::CoCreateInstance(CLSID_TF_ThreadMgr); + _source = manager.query(); + if (FAILED(_source->AdviseSink(IID_ITfInputProcessorProfileActivationSink, static_cast(this), &_cookie))) + { + _cookie = TF_INVALID_COOKIE; + THROW_LAST_ERROR(); + } +} + +LanguageProfileNotifier::~LanguageProfileNotifier() +{ + if (_cookie != TF_INVALID_COOKIE) + { + _source->UnadviseSink(_cookie); + } +} + +STDMETHODIMP LanguageProfileNotifier::OnActivated(DWORD /*dwProfileType*/, LANGID /*langid*/, REFCLSID /*clsid*/, REFGUID /*catid*/, REFGUID /*guidProfile*/, HKL hkl, DWORD /*dwFlags*/) +{ + if (hkl && hkl != _currentKeyboardLayout) + { + _currentKeyboardLayout = hkl; + try + { + _callback(); + } + CATCH_RETURN(); + } + return S_OK; +} diff --git a/src/cascadia/TerminalApp/LanguageProfileNotifier.h b/src/cascadia/TerminalApp/LanguageProfileNotifier.h new file mode 100644 index 000000000..865330455 --- /dev/null +++ b/src/cascadia/TerminalApp/LanguageProfileNotifier.h @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#pragma once + +namespace winrt::TerminalApp::implementation +{ + class LanguageProfileNotifier : public winrt::implements + { + public: + explicit LanguageProfileNotifier(std::function&& callback); + ~LanguageProfileNotifier(); + STDMETHODIMP OnActivated(DWORD dwProfileType, LANGID langid, REFCLSID clsid, REFGUID catid, REFGUID guidProfile, HKL hkl, DWORD dwFlags); + + private: + std::function _callback; + wil::com_ptr _source; + DWORD _cookie = TF_INVALID_COOKIE; + HKL _currentKeyboardLayout; + }; +} diff --git a/src/cascadia/TerminalApp/Pane.cpp b/src/cascadia/TerminalApp/Pane.cpp index 59dacf6b9..45db24870 100644 --- a/src/cascadia/TerminalApp/Pane.cpp +++ b/src/cascadia/TerminalApp/Pane.cpp @@ -2548,6 +2548,29 @@ bool Pane::ContainsReadOnly() const return _IsLeaf() ? _control.ReadOnly() : (_firstChild->ContainsReadOnly() || _secondChild->ContainsReadOnly()); } +// Method Description: +// - If we're a parent, place the taskbar state for all our leaves into the +// provided vector. +// - If we're a leaf, place our own state into the vector. +// Arguments: +// - states: a vector that will receive all the states of all leaves in the tree +// Return Value: +// - +void Pane::CollectTaskbarStates(std::vector& states) +{ + if (_IsLeaf()) + { + auto tbState{ winrt::make(_control.TaskbarState(), + _control.TaskbarProgress()) }; + states.push_back(tbState); + } + else + { + _firstChild->CollectTaskbarStates(states); + _secondChild->CollectTaskbarStates(states); + } +} + DEFINE_EVENT(Pane, GotFocus, _GotFocusHandlers, winrt::delegate>); DEFINE_EVENT(Pane, LostFocus, _LostFocusHandlers, winrt::delegate>); DEFINE_EVENT(Pane, PaneRaiseBell, _PaneRaiseBellHandlers, winrt::Windows::Foundation::EventHandler); diff --git a/src/cascadia/TerminalApp/Pane.h b/src/cascadia/TerminalApp/Pane.h index eed685d32..34d696340 100644 --- a/src/cascadia/TerminalApp/Pane.h +++ b/src/cascadia/TerminalApp/Pane.h @@ -21,6 +21,7 @@ #pragma once #include "../../cascadia/inc/cppwinrt_utils.h" +#include "TaskbarState.h" // fwdecl unittest classes namespace TerminalAppLocalTests @@ -92,6 +93,8 @@ public: bool ContainsReadOnly() const; + void CollectTaskbarStates(std::vector& states); + WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler); DECLARE_EVENT(GotFocus, _GotFocusHandlers, winrt::delegate>); DECLARE_EVENT(LostFocus, _LostFocusHandlers, winrt::delegate>); diff --git a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw index 2ab7e83ac..86f890912 100644 --- a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw @@ -665,6 +665,9 @@ Find... + + Split Tab + Open a new window with given starting directory diff --git a/src/cascadia/TerminalApp/TabManagement.cpp b/src/cascadia/TerminalApp/TabManagement.cpp index 02dc0120f..70cdfaca8 100644 --- a/src/cascadia/TerminalApp/TabManagement.cpp +++ b/src/cascadia/TerminalApp/TabManagement.cpp @@ -56,7 +56,7 @@ namespace winrt::TerminalApp::implementation // - existingConnection: An optional connection that is already established to a PTY // for this tab to host instead of creating one. // If not defined, the tab will create the connection. - void TerminalPage::_OpenNewTab(const NewTerminalArgs& newTerminalArgs, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection) + HRESULT TerminalPage::_OpenNewTab(const NewTerminalArgs& newTerminalArgs, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection) try { const auto profileGuid{ _settings.GetProfileForArgs(newTerminalArgs) }; @@ -89,8 +89,10 @@ namespace winrt::TerminalApp::implementation TraceLoggingWideString(schemeName.data(), "SchemeName", "Color scheme set in the settings"), TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance)); + + return S_OK; } - CATCH_LOG(); + CATCH_RETURN(); // Method Description: // - Creates a new tab with the given settings. If the tab bar is not being @@ -192,6 +194,16 @@ namespace winrt::TerminalApp::implementation } }); + newTabImpl->SplitTabRequested([weakTab, weakThis{ get_weak() }]() { + auto page{ weakThis.get() }; + auto tab{ weakTab.get() }; + + if (page && tab) + { + page->_SplitTab(*tab); + } + }); + newTabImpl->FindRequested([weakTab, weakThis{ get_weak() }]() { auto page{ weakThis.get() }; auto tab{ weakTab.get() }; @@ -367,6 +379,20 @@ namespace winrt::TerminalApp::implementation CATCH_LOG(); } + // Method Description: + // - Sets the specified tab as the focused tab and splits its active pane + // Arguments: + // - tab: tab to split + void TerminalPage::_SplitTab(TerminalTab& tab) + { + try + { + _SetFocusedTab(tab); + _SplitPane(tab, SplitState::Automatic, SplitType::Duplicate); + } + CATCH_LOG(); + } + // Method Description: // - Removes the tab (both TerminalControl and XAML) after prompting for approval // Arguments: diff --git a/src/cascadia/TerminalApp/TaskbarState.cpp b/src/cascadia/TerminalApp/TaskbarState.cpp new file mode 100644 index 000000000..183460556 --- /dev/null +++ b/src/cascadia/TerminalApp/TaskbarState.cpp @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include "TaskbarState.h" +#include "TaskbarState.g.cpp" + +namespace winrt::TerminalApp::implementation +{ + // Default to unset, 0%. + TaskbarState::TaskbarState() : + TaskbarState(0, 0){}; + + TaskbarState::TaskbarState(const uint64_t dispatchTypesState, const uint64_t progressParam) : + _State{ dispatchTypesState }, + _Progress{ progressParam } {} + + uint64_t TaskbarState::Priority() const + { + // This seemingly nonsensical ordering is from + // https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-itaskbarlist3-setprogressstate#how-the-taskbar-button-chooses-the-progress-indicator-for-a-group + switch (_State) + { + case 0: // Clear = 0, + return 5; + case 1: // Set = 1, + return 3; + case 2: // Error = 2, + return 1; + case 3: // Indeterminate = 3, + return 4; + case 4: // Paused = 4 + return 2; + } + // Here, return 6, to definitely be greater than all the other valid values. + // This should never really happen. + return 6; + } + + int TaskbarState::ComparePriority(const winrt::TerminalApp::TaskbarState& lhs, const winrt::TerminalApp::TaskbarState& rhs) + { + return lhs.Priority() < rhs.Priority(); + } + +} diff --git a/src/cascadia/TerminalApp/TaskbarState.h b/src/cascadia/TerminalApp/TaskbarState.h new file mode 100644 index 000000000..e36b4440c --- /dev/null +++ b/src/cascadia/TerminalApp/TaskbarState.h @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#pragma once +#include "inc/cppwinrt_utils.h" +#include "TaskbarState.g.h" + +// fwdecl unittest classes +namespace TerminalAppLocalTests +{ + class TabTests; +}; + +namespace winrt::TerminalApp::implementation +{ + struct TaskbarState : TaskbarStateT + { + public: + TaskbarState(); + TaskbarState(const uint64_t dispatchTypesState, const uint64_t progress); + + static int ComparePriority(const winrt::TerminalApp::TaskbarState& lhs, const winrt::TerminalApp::TaskbarState& rhs); + + uint64_t Priority() const; + + WINRT_PROPERTY(uint64_t, State, 0); + WINRT_PROPERTY(uint64_t, Progress, 0); + }; +} + +namespace winrt::TerminalApp::factory_implementation +{ + BASIC_FACTORY(TaskbarState); +} diff --git a/src/cascadia/TerminalApp/TaskbarState.idl b/src/cascadia/TerminalApp/TaskbarState.idl new file mode 100644 index 000000000..159242a17 --- /dev/null +++ b/src/cascadia/TerminalApp/TaskbarState.idl @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +namespace TerminalApp +{ + [default_interface] runtimeclass TaskbarState + { + TaskbarState(); + TaskbarState(UInt64 dispatchTypesState, UInt64 progress); + + UInt64 State{ get; }; + UInt64 Progress{ get; }; + UInt64 Priority { get; }; + } +} diff --git a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj index 67317f4e4..4d9b5d3dc 100644 --- a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj +++ b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj @@ -53,7 +53,7 @@ Designer - + Designer @@ -74,6 +74,7 @@ + MinMaxCloseControl.xaml @@ -89,6 +90,9 @@ TabBase.idl + + TaskbarState.idl + TerminalTab.idl @@ -112,7 +116,7 @@ HighlightedTextControl.xaml - + ColorPickupFlyout.xaml @@ -139,7 +143,7 @@ AppLogic.idl - + @@ -149,6 +153,7 @@ + MinMaxCloseControl.xaml @@ -164,6 +169,9 @@ TabBase.idl + + TaskbarState.idl + TerminalTab.idl @@ -195,7 +203,7 @@ HighlightedTextControl.xaml - + ColorPickupFlyout.xaml @@ -256,6 +264,7 @@ + TerminalPage.xaml @@ -280,7 +289,7 @@ HighlightedTextControl.xaml Code - + ColorPickupFlyout.xaml Code diff --git a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj.filters b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj.filters index 1511e14d9..f041b68d7 100644 --- a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj.filters +++ b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj.filters @@ -13,9 +13,6 @@ pane - - tab - pane @@ -23,7 +20,6 @@ - tab @@ -49,10 +45,10 @@ highlightedText + + - - app @@ -60,9 +56,6 @@ pane - - tab - @@ -92,14 +85,13 @@ highlightedText + + app - - settings - settings @@ -107,9 +99,6 @@ settings - - tab - tab @@ -125,6 +114,9 @@ tab + + + @@ -160,9 +152,6 @@ commandPalette - - commandPalette - commandPalette diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index fc552d767..4d49779ad 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -19,6 +19,8 @@ #include "RenameWindowRequestedArgs.g.cpp" #include "../inc/WindowingBehavior.h" +#include + using namespace winrt; using namespace winrt::Windows::Foundation::Collections; using namespace winrt::Windows::UI::Xaml; @@ -253,16 +255,8 @@ namespace winrt::TerminalApp::implementation path = path.parent_path(); } - std::wstring pathText = path.wstring(); - - // Handle edge case of "C:\\", seems like the "StartingDirectory" doesn't like path which ends with '\' - if (pathText.back() == L'\\') - { - pathText.erase(std::prev(pathText.end())); - } - NewTerminalArgs args; - args.StartingDirectory(winrt::hstring{ pathText }); + args.StartingDirectory(winrt::hstring{ path.wstring() }); this->_OpenNewTerminal(args); TraceLoggingWrite( @@ -356,34 +350,12 @@ namespace winrt::TerminalApp::implementation winrt::Microsoft::Terminal::TerminalConnection::ConptyConnection::StartInboundListener(); } // If we failed to start the listener, it will throw. - // We should fail fast here or the Terminal will be in a very strange state. - // We only start the listener if the Terminal was started with the COM server - // `-Embedding` flag and we make no tabs as a result. - // Therefore, if the listener cannot start itself up to make that tab with - // the inbound connection that caused the COM activation in the first place... - // we would be left with an empty terminal frame with no tabs. - // Instead, crash out so COM sees the server die and things unwind - // without a weird empty frame window. + // We don't want to fail fast here because if a peasant has some trouble with + // starting the listener, we don't want it to crash and take all its tabs down + // with it. catch (...) { - // However, we cannot always fail fast because of MSFT:33501832. Sometimes the COM catalog - // tears the state between old and new versions and fails here for that reason. - // As we're always becoming an inbound server in the monarch, even when COM didn't strictly - // ask us yet...we might just crash always. - // Instead... we're going to differentiate. If COM started us... we will fail fast - // so it sees the process die and falls back. - // If we were just starting normally as a Monarch and opportunistically listening for - // inbound connections... then we'll just log the failure and move on assuming - // the version state is torn and will fix itself whenever the packaging upgrade - // tasks decide to clean up. - if (_isEmbeddingInboundListener) - { - FAIL_FAST_CAUGHT_EXCEPTION(); - } - else - { - LOG_CAUGHT_EXCEPTION(); - } + LOG_CAUGHT_EXCEPTION(); } } } @@ -767,7 +739,7 @@ namespace winrt::TerminalApp::implementation } else { - this->_OpenNewTab(newTerminalArgs); + LOG_IF_FAILED(this->_OpenNewTab(newTerminalArgs)); } } @@ -1246,6 +1218,33 @@ namespace winrt::TerminalApp::implementation return; } + _SplitPane(*focusedTab, splitType, splitMode, splitSize, newTerminalArgs); + } + + // Method Description: + // - Split the focused pane of the given tab, either horizontally or vertically, and place the + // given TermControl into the newly created pane. + // - If splitType == SplitState::None, this method does nothing. + // Arguments: + // - tab: The tab that is going to be split. + // - splitType: one value from the TerminalApp::SplitState enum, indicating how the + // new pane should be split from its parent. + // - splitMode: value from TerminalApp::SplitType enum, indicating the profile to be used in the newly split pane. + // - newTerminalArgs: An object that may contain a blob of parameters to + // control which profile is created and with possible other + // configurations. See CascadiaSettings::BuildSettings for more details. + void TerminalPage::_SplitPane(TerminalTab& tab, + const SplitState splitType, + const SplitType splitMode, + const float splitSize, + const NewTerminalArgs& newTerminalArgs) + { + // Do nothing if we're requesting no split. + if (splitType == SplitState::None) + { + return; + } + try { TerminalSettingsCreateResult controlSettings{ nullptr }; @@ -1254,12 +1253,12 @@ namespace winrt::TerminalApp::implementation if (splitMode == SplitType::Duplicate) { - std::optional current_guid = focusedTab->GetFocusedProfile(); + std::optional current_guid = tab.GetFocusedProfile(); if (current_guid) { profileFound = true; controlSettings = TerminalSettings::CreateWithProfileByID(_settings, current_guid.value(), *_bindings); - const auto workingDirectory = focusedTab->GetActiveTerminalControl().WorkingDirectory(); + const auto workingDirectory = tab.GetActiveTerminalControl().WorkingDirectory(); const auto validWorkingDirectory = !workingDirectory.empty(); if (validWorkingDirectory) { @@ -1295,10 +1294,10 @@ namespace winrt::TerminalApp::implementation auto realSplitType = splitType; if (realSplitType == SplitState::Automatic) { - realSplitType = focusedTab->PreCalculateAutoSplit(availableSpace); + realSplitType = tab.PreCalculateAutoSplit(availableSpace); } - const auto canSplit = focusedTab->PreCalculateCanSplit(realSplitType, splitSize, availableSpace); + const auto canSplit = tab.PreCalculateCanSplit(realSplitType, splitSize, availableSpace); if (!canSplit) { return; @@ -1307,11 +1306,11 @@ namespace winrt::TerminalApp::implementation auto newControl = _InitControl(controlSettings, controlConnection); // Hookup our event handlers to the new terminal - _RegisterTerminalEvents(newControl, *focusedTab); + _RegisterTerminalEvents(newControl, tab); _UnZoomIfNeeded(); - focusedTab->SplitPane(realSplitType, splitSize, realGuid, newControl); + tab.SplitPane(realSplitType, splitSize, realGuid, newControl); } CATCH_LOG(); } @@ -2078,29 +2077,35 @@ namespace winrt::TerminalApp::implementation } // Method Description: - // - Gets the taskbar state value from the last active control + // - Get the combined taskbar state for the page. This is the combination of + // all the states of all the tabs, which are themselves a combination of + // all their panes. Taskbar states are given a priority based on the rules + // in: + // https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-itaskbarlist3-setprogressstate + // under "How the Taskbar Button Chooses the Progress Indicator for a Group" + // Arguments: + // - // Return Value: - // - The taskbar state of the last active control - uint64_t TerminalPage::GetLastActiveControlTaskbarState() + // - A TaskbarState object representing the combined taskbar state and + // progress percentage of all our tabs. + winrt::TerminalApp::TaskbarState TerminalPage::TaskbarState() const { - if (auto control{ _GetActiveControl() }) - { - return control.TaskbarState(); - } - return {}; - } + auto state{ winrt::make() }; - // Method Description: - // - Gets the taskbar progress value from the last active control - // Return Value: - // - The taskbar progress of the last active control - uint64_t TerminalPage::GetLastActiveControlTaskbarProgress() - { - if (auto control{ _GetActiveControl() }) + for (const auto& tab : _tabs) { - return control.TaskbarProgress(); + if (auto tabImpl{ _GetTerminalTabImpl(tab) }) + { + auto tabState{ tabImpl->GetCombinedTaskbarState() }; + // lowest priority wins + if (tabState.Priority() < state.Priority()) + { + state = tabState; + } + } } - return {}; + + return state; } // Method Description: @@ -2386,13 +2391,38 @@ namespace winrt::TerminalApp::implementation return _isAlwaysOnTop; } - void TerminalPage::_OnNewConnection(winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection connection) + HRESULT TerminalPage::_OnNewConnection(winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection connection) { - // TODO: GH 9458 will give us more context so we can try to choose a better profile. - _OpenNewTab(nullptr, connection); + // We need to be on the UI thread in order for _OpenNewTab to run successfully. + // HasThreadAccess will return true if we're currently on a UI thread and false otherwise. + // When we're on a COM thread, we'll need to dispatch the calls to the UI thread + // and wait on it hence the locking mechanism. + if (Dispatcher().HasThreadAccess()) + { + // TODO: GH 9458 will give us more context so we can try to choose a better profile. + auto hr = _OpenNewTab(nullptr, connection); - // Request a summon of this window to the foreground - _SummonWindowRequestedHandlers(*this, nullptr); + // Request a summon of this window to the foreground + _SummonWindowRequestedHandlers(*this, nullptr); + + return hr; + } + else + { + til::latch latch{ 1 }; + HRESULT finalVal = S_OK; + + Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [&]() { + finalVal = _OpenNewTab(nullptr, connection); + + _SummonWindowRequestedHandlers(*this, nullptr); + + latch.count_down(); + }); + + latch.wait(); + return finalVal; + } } // Method Description: diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index aa2db9529..6ec7bd7a0 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -85,8 +85,7 @@ namespace winrt::TerminalApp::implementation winrt::TerminalApp::IDialogPresenter DialogPresenter() const; void DialogPresenter(winrt::TerminalApp::IDialogPresenter dialogPresenter); - uint64_t GetLastActiveControlTaskbarState(); - uint64_t GetLastActiveControlTaskbarProgress(); + winrt::TerminalApp::TaskbarState TaskbarState() const; void ShowKeyboardServiceWarning(); winrt::hstring KeyboardServiceDisabledText(); @@ -188,7 +187,7 @@ namespace winrt::TerminalApp::implementation void _CreateNewTabFlyout(); void _OpenNewTabDropdown(); - void _OpenNewTab(const Microsoft::Terminal::Settings::Model::NewTerminalArgs& newTerminalArgs, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection = nullptr); + HRESULT _OpenNewTab(const Microsoft::Terminal::Settings::Model::NewTerminalArgs& newTerminalArgs, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection = nullptr); void _CreateNewTabFromSettings(GUID profileGuid, const Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult& settings, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection = nullptr); winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection _CreateConnectionFromSettings(GUID profileGuid, Microsoft::Terminal::Settings::Model::TerminalSettings settings); @@ -219,6 +218,8 @@ namespace winrt::TerminalApp::implementation void _DuplicateFocusedTab(); void _DuplicateTab(const TerminalTab& tab); + void _SplitTab(TerminalTab& tab); + winrt::Windows::Foundation::IAsyncAction _HandleCloseTabRequested(winrt::TerminalApp::TabBase tab); void _CloseTabAtIndex(uint32_t index); void _RemoveTab(const winrt::TerminalApp::TabBase& tab); @@ -254,6 +255,11 @@ namespace winrt::TerminalApp::implementation const Microsoft::Terminal::Settings::Model::SplitType splitMode = Microsoft::Terminal::Settings::Model::SplitType::Manual, const float splitSize = 0.5f, const Microsoft::Terminal::Settings::Model::NewTerminalArgs& newTerminalArgs = nullptr); + void _SplitPane(TerminalTab& tab, + const Microsoft::Terminal::Settings::Model::SplitState splitType, + const Microsoft::Terminal::Settings::Model::SplitType splitMode = Microsoft::Terminal::Settings::Model::SplitType::Manual, + const float splitSize = 0.5f, + const Microsoft::Terminal::Settings::Model::NewTerminalArgs& newTerminalArgs = nullptr); void _ResizePane(const Microsoft::Terminal::Settings::Model::ResizeDirection& direction); void _ToggleSplitOrientation(); @@ -337,7 +343,7 @@ namespace winrt::TerminalApp::implementation winrt::Microsoft::Terminal::Settings::Model::Command _lastPreviewedCommand{ nullptr }; winrt::Microsoft::Terminal::Settings::Model::TerminalSettings _originalSettings{ nullptr }; - void _OnNewConnection(winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection connection); + HRESULT _OnNewConnection(winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection connection); void _HandleToggleInboundPty(const IInspectable& sender, const Microsoft::Terminal::Settings::Model::ActionEventArgs& args); void _WindowRenamerActionClick(const IInspectable& sender, const IInspectable& eventArgs); diff --git a/src/cascadia/TerminalApp/TerminalPage.idl b/src/cascadia/TerminalApp/TerminalPage.idl index 0d0557822..7d4c580e9 100644 --- a/src/cascadia/TerminalApp/TerminalPage.idl +++ b/src/cascadia/TerminalApp/TerminalPage.idl @@ -1,5 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +import "TaskbarState.idl"; namespace TerminalApp { @@ -42,8 +43,7 @@ namespace TerminalApp void ShowKeyboardServiceWarning(); String KeyboardServiceDisabledText { get; }; - UInt64 GetLastActiveControlTaskbarState(); - UInt64 GetLastActiveControlTaskbarProgress(); + TaskbarState TaskbarState{ get; }; event Windows.Foundation.TypedEventHandler TitleChanged; event Windows.Foundation.TypedEventHandler LastTabClosed; diff --git a/src/cascadia/TerminalApp/TerminalTab.cpp b/src/cascadia/TerminalApp/TerminalTab.cpp index 44a9ce520..908dd6d51 100644 --- a/src/cascadia/TerminalApp/TerminalTab.cpp +++ b/src/cascadia/TerminalApp/TerminalTab.cpp @@ -177,10 +177,9 @@ namespace winrt::TerminalApp::implementation { lastFocusedControl.Focus(_focusState); - // Update our own progress state, and fire an event signaling + // Update our own progress state. This will fire an event signaling // that our taskbar progress changed. _UpdateProgressState(); - _TaskbarProgressChangedHandlers(lastFocusedControl, nullptr); } // When we gain focus, remove the bell indicator if it is active if (_tabStatus.BellIndicator()) @@ -675,6 +674,26 @@ namespace winrt::TerminalApp::implementation }); } + // Method Description: + // - Get the combined taskbar state for the tab. This is the combination of + // all the states of all our panes. Taskbar states are given a priority + // based on the rules in: + // https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-itaskbarlist3-setprogressstate + // under "How the Taskbar Button Chooses the Progress Indicator for a + // Group" + // Arguments: + // - + // Return Value: + // - A TaskbarState object representing the combined taskbar state and + // progress percentage of all our panes. + winrt::TerminalApp::TaskbarState TerminalTab::GetCombinedTaskbarState() const + { + std::vector states; + _rootPane->CollectTaskbarStates(states); + return states.empty() ? winrt::make() : + *std::min_element(states.begin(), states.end(), TerminalApp::implementation::TaskbarState::ComparePriority); + } + // Method Description: // - This should be called on the UI thread. If you don't, then it might // silently do nothing. @@ -690,37 +709,39 @@ namespace winrt::TerminalApp::implementation // - void TerminalTab::_UpdateProgressState() { - if (const auto& activeControl{ GetActiveTerminalControl() }) - { - const auto taskbarState = activeControl.TaskbarState(); - // The progress of the control changed, but not necessarily the progress of the tab. - // Set the tab's progress ring to the active pane's progress - if (taskbarState > 0) - { - if (taskbarState == 3) - { - // 3 is the indeterminate state, set the progress ring as such - _tabStatus.IsProgressRingIndeterminate(true); - } - else - { - // any non-indeterminate state has a value, set the progress ring as such - _tabStatus.IsProgressRingIndeterminate(false); + const auto state{ GetCombinedTaskbarState() }; - const auto progressValue = gsl::narrow(activeControl.TaskbarProgress()); - _tabStatus.ProgressValue(progressValue); - } - // Hide the tab icon (the progress ring is placed over it) - HideIcon(true); - _tabStatus.IsProgressRingActive(true); + const auto taskbarState = state.State(); + // The progress of the control changed, but not necessarily the progress of the tab. + // Set the tab's progress ring to the active pane's progress + if (taskbarState > 0) + { + if (taskbarState == 3) + { + // 3 is the indeterminate state, set the progress ring as such + _tabStatus.IsProgressRingIndeterminate(true); } else { - // Show the tab icon - HideIcon(false); - _tabStatus.IsProgressRingActive(false); + // any non-indeterminate state has a value, set the progress ring as such + _tabStatus.IsProgressRingIndeterminate(false); + + const auto progressValue = gsl::narrow(state.Progress()); + _tabStatus.ProgressValue(progressValue); } + // Hide the tab icon (the progress ring is placed over it) + HideIcon(true); + _tabStatus.IsProgressRingActive(true); } + else + { + // Show the tab icon + HideIcon(false); + _tabStatus.IsProgressRingActive(false); + } + + // fire an event signaling that our taskbar progress changed. + _TaskbarProgressChangedHandlers(nullptr, nullptr); } // Method Description: @@ -953,6 +974,23 @@ namespace winrt::TerminalApp::implementation findMenuItem.Icon(findSymbol); } + Controls::MenuFlyoutItem splitTabMenuItem; + { + // "Split Tab" + Controls::FontIcon splitTabSymbol; + splitTabSymbol.FontFamily(Media::FontFamily{ L"Segoe MDL2 Assets" }); + splitTabSymbol.Glyph(L"\xF246"); // ViewDashboard + + splitTabMenuItem.Click([weakThis](auto&&, auto&&) { + if (auto tab{ weakThis.get() }) + { + tab->_SplitTabRequestedHandlers(); + } + }); + splitTabMenuItem.Text(RS_(L"SplitTabText")); + splitTabMenuItem.Icon(splitTabSymbol); + } + // Build the menu Controls::MenuFlyout contextMenuFlyout; Controls::MenuFlyoutSeparator menuSeparator; @@ -960,6 +998,7 @@ namespace winrt::TerminalApp::implementation contextMenuFlyout.Items().Append(chooseColorMenuItem); contextMenuFlyout.Items().Append(renameTabMenuItem); contextMenuFlyout.Items().Append(duplicateTabMenuItem); + contextMenuFlyout.Items().Append(splitTabMenuItem); contextMenuFlyout.Items().Append(menuSeparator); contextMenuFlyout.Items().Append(findMenuItem); contextMenuFlyout.Items().Append(menuSeparator2); @@ -1336,4 +1375,5 @@ namespace winrt::TerminalApp::implementation DEFINE_EVENT(TerminalTab, TabRaiseVisualBell, _TabRaiseVisualBellHandlers, winrt::delegate<>); DEFINE_EVENT(TerminalTab, DuplicateRequested, _DuplicateRequestedHandlers, winrt::delegate<>); DEFINE_EVENT(TerminalTab, FindRequested, _FindRequestedHandlers, winrt::delegate<>); + DEFINE_EVENT(TerminalTab, SplitTabRequested, _SplitTabRequestedHandlers, winrt::delegate<>); } diff --git a/src/cascadia/TerminalApp/TerminalTab.h b/src/cascadia/TerminalApp/TerminalTab.h index c0d35886c..2d462e7c2 100644 --- a/src/cascadia/TerminalApp/TerminalTab.h +++ b/src/cascadia/TerminalApp/TerminalTab.h @@ -83,6 +83,7 @@ namespace winrt::TerminalApp::implementation void TogglePaneReadOnly(); std::shared_ptr GetActivePane() const; + winrt::TerminalApp::TaskbarState GetCombinedTaskbarState() const; winrt::TerminalApp::TerminalTabStatus TabStatus() { @@ -95,6 +96,7 @@ namespace winrt::TerminalApp::implementation DECLARE_EVENT(TabRaiseVisualBell, _TabRaiseVisualBellHandlers, winrt::delegate<>); DECLARE_EVENT(DuplicateRequested, _DuplicateRequestedHandlers, winrt::delegate<>); DECLARE_EVENT(FindRequested, _FindRequestedHandlers, winrt::delegate<>); + DECLARE_EVENT(SplitTabRequested, _SplitTabRequestedHandlers, winrt::delegate<>); TYPED_EVENT(TaskbarProgressChanged, IInspectable, IInspectable); private: diff --git a/src/cascadia/TerminalApp/pch.h b/src/cascadia/TerminalApp/pch.h index 299d7de63..1f84f821e 100644 --- a/src/cascadia/TerminalApp/pch.h +++ b/src/cascadia/TerminalApp/pch.h @@ -66,6 +66,7 @@ TRACELOGGING_DECLARE_PROVIDER(g_hTerminalAppProvider); #include #include +#include #include #include diff --git a/src/cascadia/TerminalConnection/CTerminalHandoff.cpp b/src/cascadia/TerminalConnection/CTerminalHandoff.cpp index 0138921fd..5023a8188 100644 --- a/src/cascadia/TerminalConnection/CTerminalHandoff.cpp +++ b/src/cascadia/TerminalConnection/CTerminalHandoff.cpp @@ -11,6 +11,8 @@ using namespace Microsoft::WRL; static NewHandoffFunction _pfnHandoff = nullptr; // The registration ID of the class object for clean up later static DWORD g_cTerminalHandoffRegistration = 0; +// Mutex so we only do start/stop/establish one at a time. +static std::shared_mutex _mtx; // Routine Description: // - Starts listening for TerminalHandoff requests by registering @@ -19,9 +21,11 @@ static DWORD g_cTerminalHandoffRegistration = 0; // - pfnHandoff - Function to callback when a handoff is received // Return Value: // - S_OK, E_NOT_VALID_STATE (start called when already started) or relevant COM registration error. -HRESULT CTerminalHandoff::s_StartListening(NewHandoffFunction pfnHandoff) noexcept +HRESULT CTerminalHandoff::s_StartListening(NewHandoffFunction pfnHandoff) try { + std::unique_lock lock{ _mtx }; + RETURN_HR_IF(E_NOT_VALID_STATE, _pfnHandoff != nullptr); const auto classFactory = Make>(); @@ -31,7 +35,7 @@ try ComPtr unk; RETURN_IF_FAILED(classFactory.As(&unk)); - RETURN_IF_FAILED(CoRegisterClassObject(__uuidof(CTerminalHandoff), unk.Get(), CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE, &g_cTerminalHandoffRegistration)); + RETURN_IF_FAILED(CoRegisterClassObject(__uuidof(CTerminalHandoff), unk.Get(), CLSCTX_LOCAL_SERVER, REGCLS_SINGLEUSE, &g_cTerminalHandoffRegistration)); _pfnHandoff = pfnHandoff; @@ -46,8 +50,10 @@ CATCH_RETURN() // - // Return Value: // - S_OK, E_NOT_VALID_STATE (stop called when not started), or relevant COM class revoke error -HRESULT CTerminalHandoff::s_StopListening() noexcept +HRESULT CTerminalHandoff::s_StopListening() { + std::unique_lock lock{ _mtx }; + RETURN_HR_IF_NULL(E_NOT_VALID_STATE, _pfnHandoff); _pfnHandoff = nullptr; @@ -91,10 +97,19 @@ static HRESULT _duplicateHandle(const HANDLE in, HANDLE& out) noexcept // - E_NOT_VALID_STATE if a event handler is not registered before calling. `::DuplicateHandle` // error codes if we cannot manage to make our own copy of handles to retain. Or S_OK/error // from the registered handler event function. -HRESULT CTerminalHandoff::EstablishPtyHandoff(HANDLE in, HANDLE out, HANDLE signal, HANDLE ref, HANDLE server, HANDLE client) noexcept +HRESULT CTerminalHandoff::EstablishPtyHandoff(HANDLE in, HANDLE out, HANDLE signal, HANDLE ref, HANDLE server, HANDLE client) { + // Stash a local copy of _pfnHandoff before we stop listening. + auto localPfnHandoff = _pfnHandoff; + + // Because we are REGCLS_SINGLEUSE... we need to `CoRevokeClassObject` after we handle this ONE call. + // COM does not automatically clean that up for us. We must do it. + s_StopListening(); + + std::unique_lock lock{ _mtx }; + // Report an error if no one registered a handoff function before calling this. - RETURN_HR_IF_NULL(E_NOT_VALID_STATE, _pfnHandoff); + RETURN_HR_IF_NULL(E_NOT_VALID_STATE, localPfnHandoff); // Duplicate the handles from what we received. // The contract with COM specifies that any HANDLEs we receive from the caller belong @@ -108,5 +123,5 @@ HRESULT CTerminalHandoff::EstablishPtyHandoff(HANDLE in, HANDLE out, HANDLE sign RETURN_IF_FAILED(_duplicateHandle(client, client)); // Call registered handler from when we started listening. - return _pfnHandoff(in, out, signal, ref, server, client); + return localPfnHandoff(in, out, signal, ref, server, client); } diff --git a/src/cascadia/TerminalConnection/CTerminalHandoff.h b/src/cascadia/TerminalConnection/CTerminalHandoff.h index 2e173e0a3..2ba644f41 100644 --- a/src/cascadia/TerminalConnection/CTerminalHandoff.h +++ b/src/cascadia/TerminalConnection/CTerminalHandoff.h @@ -37,12 +37,12 @@ struct __declspec(uuid(__CLSID_CTerminalHandoff)) HANDLE signal, HANDLE ref, HANDLE server, - HANDLE client) noexcept override; + HANDLE client) override; #pragma endregion - static HRESULT s_StartListening(NewHandoffFunction pfnHandoff) noexcept; - static HRESULT s_StopListening() noexcept; + static HRESULT s_StartListening(NewHandoffFunction pfnHandoff); + static HRESULT s_StopListening(); }; // Disable warnings from the CoCreatableClass macro as the value it provides for diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index bf106322a..ac7bf1b07 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -23,6 +23,16 @@ using namespace winrt::Windows::Graphics::Display; using namespace winrt::Windows::System; using namespace winrt::Windows::ApplicationModel::DataTransfer; +// The minimum delay between updates to the scroll bar's values. +// The updates are throttled to limit power usage. +constexpr const auto ScrollBarUpdateInterval = std::chrono::milliseconds(8); + +// The minimum delay between updating the TSF input control. +constexpr const auto TsfRedrawInterval = std::chrono::milliseconds(100); + +// The minimum delay between updating the locations of regex patterns +constexpr const auto UpdatePatternLocationsInterval = std::chrono::milliseconds(500); + namespace winrt::Microsoft::Terminal::Control::implementation { // Helper static function to ensure that all ambiguous-width glyphs are reported as narrow. @@ -94,6 +104,61 @@ namespace winrt::Microsoft::Terminal::Control::implementation auto pfnTerminalTaskbarProgressChanged = std::bind(&ControlCore::_terminalTaskbarProgressChanged, this); _terminal->TaskbarProgressChangedCallback(pfnTerminalTaskbarProgressChanged); + // Get our dispatcher. If we're hosted in-proc with XAML, this will get + // us the same dispatcher as TermControl::Dispatcher(). If we're out of + // proc, this'll return null. We'll need to instead make a new + // DispatcherQueue (on a new thread), so we can use that for throttled + // functions. + _dispatcher = winrt::Windows::System::DispatcherQueue::GetForCurrentThread(); + if (!_dispatcher) + { + auto controller{ winrt::Windows::System::DispatcherQueueController::CreateOnDedicatedThread() }; + _dispatcher = controller.DispatcherQueue(); + } + + // A few different events should be throttled, so they don't fire absolutely all the time: + // * _tsfTryRedrawCanvas: When the cursor position moves, we need to + // inform TSF, so it can move the canvas for the composition. We + // throttle this so that we're not hopping across the process boundary + // every time that the cursor moves. + // * _updatePatternLocations: When there's new output, or we scroll the + // viewport, we should re-check if there are any visible hyperlinks. + // But we don't really need to do this every single time text is + // output, we can limit this update to once every 500ms. + // * _updateScrollBar: Same idea as the TSF update - we don't _really_ + // need to hop across the process boundary every time text is output. + // We can throttle this to once every 8ms, which will get us out of + // the way of the main output & rendering threads. + _tsfTryRedrawCanvas = std::make_shared>( + _dispatcher, + TsfRedrawInterval, + [weakThis = get_weak()]() { + if (auto core{ weakThis.get() }; !core->_IsClosing()) + { + core->_CursorPositionChangedHandlers(*core, nullptr); + } + }); + + _updatePatternLocations = std::make_shared>( + _dispatcher, + UpdatePatternLocationsInterval, + [weakThis = get_weak()]() { + if (auto core{ weakThis.get() }; !core->_IsClosing()) + { + core->UpdatePatternLocations(); + } + }); + + _updateScrollBar = std::make_shared>( + _dispatcher, + ScrollBarUpdateInterval, + [weakThis = get_weak()](const auto& update) { + if (auto core{ weakThis.get() }; !core->_IsClosing()) + { + core->_ScrollPositionChangedHandlers(*core, update); + } + }); + UpdateSettings(settings); } @@ -739,6 +804,18 @@ namespace winrt::Microsoft::Terminal::Control::implementation return; } + // Convert our new dimensions to characters + const auto viewInPixels = Viewport::FromDimensions({ 0, 0 }, + { static_cast(size.cx), static_cast(size.cy) }); + const auto vp = _renderEngine->GetViewportInCharacters(viewInPixels); + const auto currentVP = _terminal->GetViewport(); + + // Don't actually resize if viewport dimensions didn't change + if (vp.Height() == currentVP.Height() && vp.Width() == currentVP.Width()) + { + return; + } + _terminal->ClearSelection(); // Tell the dx engine that our window is now the new size. @@ -747,11 +824,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation // Invalidate everything _renderer->TriggerRedrawAll(); - // Convert our new dimensions to characters - const auto viewInPixels = Viewport::FromDimensions({ 0, 0 }, - { static_cast(size.cx), static_cast(size.cy) }); - const auto vp = _renderEngine->GetViewportInCharacters(viewInPixels); - // If this function succeeds with S_FALSE, then the terminal didn't // actually change size. No need to notify the connection of this no-op. const HRESULT hr = _terminal->UserResize({ vp.Width(), vp.Height() }); @@ -1103,15 +1175,28 @@ namespace winrt::Microsoft::Terminal::Control::implementation // TODO GH#9617: refine locking around pattern tree _terminal->ClearPatternTree(); - _ScrollPositionChangedHandlers(*this, - winrt::make(viewTop, - viewHeight, - bufferSize)); + // Start the throttled update of our scrollbar. + auto update{ winrt::make(viewTop, + viewHeight, + bufferSize) }; + if (!_inUnitTests) + { + _updateScrollBar->Run(update); + } + else + { + _ScrollPositionChangedHandlers(*this, update); + } + + // Additionally, start the throttled update of where our links are. + _updatePatternLocations->Run(); } void ControlCore::_terminalCursorPositionChanged() { - _CursorPositionChangedHandlers(*this, nullptr); + // When the buffer's cursor moves, start the throttled func to + // eventually dispatch a CursorPositionChanged event. + _tsfTryRedrawCanvas->Run(); } void ControlCore::_terminalTaskbarProgressChanged() @@ -1221,8 +1306,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation void ControlCore::Close() { - if (!_closing.exchange(true)) + if (!_IsClosing()) { + _closing = true; + // Stop accepting new output and state changes before we disconnect everything. _connection.TerminalOutput(_connectionOutputEventToken); _connectionStateChangedRevoker.revoke(); @@ -1400,18 +1487,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation { _terminal->Write(hstr); - // NOTE: We're raising an event here to inform the TermControl that - // output has been received, so it can queue up a throttled - // UpdatePatternLocations call. In the future, we should have the - // _updatePatternLocations ThrottledFunc internal to this class, and - // run on this object's dispatcher queue. - // - // We're not doing that quite yet, because the Core will eventually - // be out-of-proc from the UI thread, and won't be able to just use - // the UI thread as the dispatcher queue thread. - // - // See TODO: https://github.com/microsoft/terminal/projects/5#card-50760282 - _ReceivedOutputHandlers(*this, nullptr); + // Start the throttled update of where our hyperlinks are. + _updatePatternLocations->Run(); } } diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h index 855aa66d3..3bb321f0b 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -168,7 +168,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation private: bool _initializedTerminal{ false }; - std::atomic _closing{ false }; + bool _closing{ false }; TerminalConnection::ITerminalConnection _connection{ nullptr }; event_token _connectionOutputEventToken; @@ -206,6 +206,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation double _panelHeight{ 0 }; double _compositionScale{ 0 }; + winrt::Windows::System::DispatcherQueue _dispatcher{ nullptr }; + std::shared_ptr> _tsfTryRedrawCanvas; + std::shared_ptr> _updatePatternLocations; + std::shared_ptr> _updateScrollBar; + winrt::fire_and_forget _asyncCloseConnection(); void _setFontSize(int fontSize); @@ -239,8 +244,24 @@ namespace winrt::Microsoft::Terminal::Control::implementation void _connectionOutputHandler(const hstring& hstr); void _updateHoveredCell(const std::optional terminalPosition); + inline bool _IsClosing() const noexcept + { +#ifndef NDEBUG + if (_dispatcher) + { + // _closing isn't atomic and may only be accessed from the main thread. + // + // Though, the unit tests don't actually run in TAEF's main + // thread, so we don't care when we're running in tests. + assert(_inUnitTests || _dispatcher.HasThreadAccess()); + } +#endif + return _closing; + } + friend class ControlUnitTests::ControlCoreTests; friend class ControlUnitTests::ControlInteractivityTests; + bool _inUnitTests{ false }; }; } diff --git a/src/cascadia/TerminalControl/IKeyBindings.idl b/src/cascadia/TerminalControl/IKeyBindings.idl index 2b3901559..83d665faa 100644 --- a/src/cascadia/TerminalControl/IKeyBindings.idl +++ b/src/cascadia/TerminalControl/IKeyBindings.idl @@ -9,5 +9,6 @@ namespace Microsoft.Terminal.Control interface IKeyBindings { Boolean TryKeyChord(KeyChord kc); + Boolean IsKeyChordExplicitlyUnbound(KeyChord kc); } } diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index e1ea1d7a0..238d75c90 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -33,7 +33,8 @@ using namespace winrt::Windows::ApplicationModel::DataTransfer; constexpr const auto ScrollBarUpdateInterval = std::chrono::milliseconds(8); // The minimum delay between updating the TSF input control. -constexpr const auto TsfRedrawInterval = std::chrono::milliseconds(100); +// This is already throttled primarily in the ControlCore, with a timeout of 100ms. We're adding another smaller one here, as the (potentially x-proc) call will come in off the UI thread +constexpr const auto TsfRedrawInterval = std::chrono::milliseconds(8); // The minimum delay between updating the locations of regex patterns constexpr const auto UpdatePatternLocationsInterval = std::chrono::milliseconds(500); @@ -64,10 +65,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation _interactivity = winrt::make(settings, connection); _core = _interactivity.Core(); - // Use a manual revoker on the output event, so we can immediately stop - // worrying about it on destruction. - _coreOutputEventToken = _core.ReceivedOutput({ this, &TermControl::_coreReceivedOutput }); - // These events might all be triggered by the connection, but that // should be drained and closed before we complete destruction. So these // are safe. @@ -104,37 +101,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation } }); - // Many of these ThrottledFunc's should be inside ControlCore. However, - // currently they depend on the Dispatcher() of the UI thread, which the - // Core eventually won't have access to. When we get to - // https://github.com/microsoft/terminal/projects/5#card-50760282 - // then we'll move the applicable ones. - // - // These four throttled functions are triggered by terminal output and interact with the UI. + // Get our dispatcher. This will get us the same dispatcher as + // TermControl::Dispatcher(). + auto dispatcher = winrt::Windows::System::DispatcherQueue::GetForCurrentThread(); + + // These three throttled functions are triggered by terminal output and interact with the UI. // Since Close() is the point after which we are removed from the UI, but before the // destructor has run, we MUST check control->_IsClosing() before actually doing anything. - _tsfTryRedrawCanvas = std::make_shared>( - Dispatcher(), - TsfRedrawInterval, - [weakThis = get_weak()]() { - if (auto control{ weakThis.get() }; !control->_IsClosing()) - { - control->TSFInputControl().TryRedrawCanvas(); - } - }); - - _updatePatternLocations = std::make_shared>( - Dispatcher(), - UpdatePatternLocationsInterval, - [weakThis = get_weak()]() { - if (auto control{ weakThis.get() }; !control->_IsClosing()) - { - control->_core.UpdatePatternLocations(); - } - }); - _playWarningBell = std::make_shared( - Dispatcher(), + dispatcher, TerminalWarningBellInterval, [weakThis = get_weak()]() { if (auto control{ weakThis.get() }; !control->_IsClosing()) @@ -144,7 +119,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation }); _updateScrollBar = std::make_shared>( - Dispatcher(), + dispatcher, ScrollBarUpdateInterval, [weakThis = get_weak()](const auto& update) { if (auto control{ weakThis.get() }; !control->_IsClosing()) @@ -540,7 +515,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation { // create a custom automation peer with this code pattern: // (https://docs.microsoft.com/en-us/windows/uwp/design/accessibility/custom-automation-peers) - if (const auto& interactivityAutoPeer = _interactivity.OnCreateAutomationPeer()) + if (const auto& interactivityAutoPeer{ _interactivity.OnCreateAutomationPeer() }) { _automationPeer = winrt::make(this, interactivityAutoPeer); return _automationPeer; @@ -786,28 +761,39 @@ namespace winrt::Microsoft::Terminal::Control::implementation (void)_TrySendKeyEvent(VK_MENU, scanCode, modifiers, false); handled = true; } - else if (vkey == VK_F7 && down) + else if ((vkey == VK_F7 || vkey == VK_SPACE) && down) { // Manually generate an F7 event into the key bindings or terminal. // This is required as part of GH#638. + // Or do so for alt+space; only send to terminal when explicitly unbound + // That is part of #GH7125 auto bindings{ _settings.KeyBindings() }; + bool isUnbound = false; + const KeyChord kc = { + modifiers.IsCtrlPressed(), + modifiers.IsAltPressed(), + modifiers.IsShiftPressed(), + modifiers.IsWinPressed(), + gsl::narrow_cast(vkey), + 0 + }; if (bindings) { - handled = bindings.TryKeyChord({ - modifiers.IsCtrlPressed(), - modifiers.IsAltPressed(), - modifiers.IsShiftPressed(), - modifiers.IsWinPressed(), - VK_F7, - 0, - }); + handled = bindings.TryKeyChord(kc); + + if (!handled) + { + isUnbound = bindings.IsKeyChordExplicitlyUnbound(kc); + } } - if (!handled) + const bool sendToTerminal = vkey == VK_F7 || (vkey == VK_SPACE && isUnbound); + + if (!handled && sendToTerminal) { // _TrySendKeyEvent pretends it didn't handle F7 for some unknown reason. - (void)_TrySendKeyEvent(VK_F7, scanCode, modifiers, true); + (void)_TrySendKeyEvent(gsl::narrow_cast(vkey), scanCode, modifiers, true); // GH#6438: Note that we're _not_ sending the key up here - that'll // get passed through XAML to our KeyUp handler normally. handled = true; @@ -1276,23 +1262,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation CATCH_LOG(); } - void TermControl::_coreReceivedOutput(const IInspectable& /*sender*/, - const IInspectable& /*args*/) - { - // Queue up a throttled UpdatePatternLocations call. In the future, we - // should have the _updatePatternLocations ThrottledFunc internal to - // ControlCore, and run on that object's dispatcher queue. - // - // We're not doing that quite yet, because the Core will eventually - // be out-of-proc from the UI thread, and won't be able to just use - // the UI thread as the dispatcher queue thread. - // - // THIS IS CALLED ON EVERY STRING OF TEXT OUTPUT TO THE TERMINAL. Think - // twice before adding anything here. - - _updatePatternLocations->Run(); - } - // Method Description: // - Reset the font size of the terminal to its default size. // Arguments: @@ -1330,8 +1299,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation _updateScrollBar->ModifyPending([](auto& update) { update.newValue.reset(); }); - - _updatePatternLocations->Run(); } // Method Description: @@ -1665,7 +1632,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation update.newValue = args.ViewTop(); _updateScrollBar->Run(update); - _updatePatternLocations->Run(); } // Method Description: @@ -1673,10 +1639,20 @@ namespace winrt::Microsoft::Terminal::Control::implementation // to be where the current cursor position is. // Arguments: // - N/A - void TermControl::_CursorPositionChanged(const IInspectable& /*sender*/, - const IInspectable& /*args*/) + winrt::fire_and_forget TermControl::_CursorPositionChanged(const IInspectable& /*sender*/, + const IInspectable& /*args*/) { - _tsfTryRedrawCanvas->Run(); + // Prior to GH#10187, this fired a trailing throttled func to update the + // TSF canvas only every 100ms. Now, the throttling occurs on the + // ControlCore side. If we're told to update the cursor position, we can + // just go ahead and do it. + // This can come in off the COM thread - hop back to the UI thread. + auto weakThis{ get_weak() }; + co_await resume_foreground(Dispatcher()); + if (auto control{ weakThis.get() }; !control->_IsClosing()) + { + control->TSFInputControl().TryRedrawCanvas(); + } } hstring TermControl::Title() @@ -1730,8 +1706,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation { _closing = true; - _core.ReceivedOutput(_coreOutputEventToken); _RestorePointerCursorHandlers(*this, nullptr); + // Disconnect the TSF input control so it doesn't receive EditContext events. TSFInputControl().Close(); _autoScrollTimer.Stop(); diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index 6c9aa6757..e16ecbaae 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -153,8 +153,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation bool _focused{ false }; bool _initializedTerminal{ false }; - std::shared_ptr> _tsfTryRedrawCanvas; - std::shared_ptr> _updatePatternLocations; std::shared_ptr _playWarningBell; struct ScrollBarUpdate @@ -164,7 +162,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation double newMinimum; double newViewportSize; }; + std::shared_ptr> _updateScrollBar; + bool _isInternalScrollBarUpdate; // Auto scroll occurs when user, while selecting, drags cursor outside @@ -181,8 +181,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation std::optional _cursorTimer; std::optional _blinkTimer; - event_token _coreOutputEventToken; - winrt::Windows::UI::Xaml::Controls::SwapChainPanel::LayoutUpdated_revoker _layoutUpdatedRevoker; inline bool _IsClosing() const noexcept @@ -233,7 +231,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation void _TerminalTabColorChanged(const std::optional color); void _ScrollPositionChanged(const IInspectable& sender, const Control::ScrollPositionChangedArgs& args); - void _CursorPositionChanged(const IInspectable& sender, const IInspectable& args); + winrt::fire_and_forget _CursorPositionChanged(const IInspectable& sender, const IInspectable& args); bool _CapturePointer(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e); bool _ReleasePointerCapture(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e); @@ -265,7 +263,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation const int fontHeight, const bool isInitialChange); winrt::fire_and_forget _coreTransparencyChanged(IInspectable sender, Control::TransparencyChangedEventArgs args); - void _coreReceivedOutput(const IInspectable& sender, const IInspectable& args); void _coreRaisedNotice(const IInspectable& s, const Control::NoticeEventArgs& args); void _coreWarningBell(const IInspectable& sender, const IInspectable& args); }; diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp index 68e8424ed..53ca70878 100644 --- a/src/cascadia/TerminalCore/Terminal.cpp +++ b/src/cascadia/TerminalCore/Terminal.cpp @@ -594,14 +594,6 @@ bool Terminal::SendKeyEvent(const WORD vkey, const auto isAltOnlyPressed = states.IsAltPressed() && !states.IsCtrlPressed(); - // DON'T manually handle Alt+Space - the system will use this to bring up - // the system menu for restore, min/maximize, size, move, close. - // (This doesn't apply to Ctrl+Alt+Space.) - if (isAltOnlyPressed && vkey == VK_SPACE) - { - return false; - } - // By default Windows treats Ctrl+Alt as an alias for AltGr. // When the altGrAliasing setting is set to false, this behaviour should be disabled. // @@ -672,13 +664,6 @@ bool Terminal::SendMouseEvent(const COORD viewportPos, const unsigned int uiButt // - false otherwise. bool Terminal::SendCharEvent(const wchar_t ch, const WORD scanCode, const ControlKeyStates states) { - // DON'T manually handle Alt+Space - the system will use this to bring up - // the system menu for restore, min/maximize, size, move, close. - if (ch == L' ' && states.IsAltPressed() && !states.IsCtrlPressed()) - { - return false; - } - auto vkey = _TakeVirtualKeyFromLastKeyEvent(scanCode); if (vkey == 0 && scanCode != 0) { diff --git a/src/cascadia/TerminalCore/TerminalApi.cpp b/src/cascadia/TerminalCore/TerminalApi.cpp index 1fc36389d..83c898e47 100644 --- a/src/cascadia/TerminalCore/TerminalApi.cpp +++ b/src/cascadia/TerminalCore/TerminalApi.cpp @@ -16,7 +16,7 @@ try _WriteBuffer(stringView); return true; } -CATCH_LOG_RETURN_FALSE() +CATCH_RETURN_FALSE() bool Terminal::ExecuteChar(wchar_t wch) noexcept try @@ -24,7 +24,7 @@ try _WriteBuffer({ &wch, 1 }); return true; } -CATCH_LOG_RETURN_FALSE() +CATCH_RETURN_FALSE() TextAttribute Terminal::GetTextAttributes() const noexcept { @@ -54,7 +54,7 @@ try return true; } -CATCH_LOG_RETURN_FALSE() +CATCH_RETURN_FALSE() COORD Terminal::GetCursorPosition() noexcept { @@ -75,7 +75,7 @@ try _buffer->GetCursor().SetColor(color); return true; } -CATCH_LOG_RETURN_FALSE() +CATCH_RETURN_FALSE() // Method Description: // - Moves the cursor down one line, and possibly also to the leftmost column. @@ -101,7 +101,7 @@ try return true; } -CATCH_LOG_RETURN_FALSE() +CATCH_RETURN_FALSE() // Method Description: // - deletes count characters starting from the cursor's current position @@ -150,7 +150,7 @@ try return true; } -CATCH_LOG_RETURN_FALSE() +CATCH_RETURN_FALSE() // Method Description: // - Inserts count spaces starting from the cursor's current position, moving over the existing text @@ -205,7 +205,7 @@ try return true; } -CATCH_LOG_RETURN_FALSE() +CATCH_RETURN_FALSE() bool Terminal::EraseCharacters(const size_t numChars) noexcept try @@ -218,7 +218,7 @@ try _buffer->Write(eraseIter, absoluteCursorPos); return true; } -CATCH_LOG_RETURN_FALSE() +CATCH_RETURN_FALSE() // Method description: // - erases a line of text, either from @@ -264,7 +264,7 @@ try _buffer->Write(eraseIter, startPos, false); return true; } -CATCH_LOG_RETURN_FALSE() +CATCH_RETURN_FALSE() // Method description: // - erases text in the buffer in two ways depending on erase type @@ -348,7 +348,7 @@ try return true; } -CATCH_LOG_RETURN_FALSE() +CATCH_RETURN_FALSE() bool Terminal::WarningBell() noexcept try @@ -356,7 +356,7 @@ try _pfnWarningBell(); return true; } -CATCH_LOG_RETURN_FALSE() +CATCH_RETURN_FALSE() bool Terminal::SetWindowTitle(std::wstring_view title) noexcept try @@ -368,7 +368,7 @@ try } return true; } -CATCH_LOG_RETURN_FALSE() +CATCH_RETURN_FALSE() // Method Description: // - Updates the value in the colortable at index tableIndex to the new color @@ -387,7 +387,7 @@ try _buffer->GetRenderTarget().TriggerRedrawAll(); return true; } -CATCH_LOG_RETURN_FALSE() +CATCH_RETURN_FALSE() // Method Description: // - Sets the cursor style to the given style. @@ -457,7 +457,7 @@ try _buffer->GetRenderTarget().TriggerRedrawAll(); return true; } -CATCH_LOG_RETURN_FALSE() +CATCH_RETURN_FALSE() // Method Description: // - Updates the default background color from a COLORREF, format 0x00BBGGRR. @@ -475,7 +475,7 @@ try _buffer->GetRenderTarget().TriggerRedrawAll(); return true; } -CATCH_LOG_RETURN_FALSE() +CATCH_RETURN_FALSE() til::color Terminal::GetDefaultBackground() const noexcept { @@ -509,7 +509,7 @@ try _buffer->GetRenderTarget().TriggerRedrawAll(); return true; } -CATCH_LOG_RETURN_FALSE() +CATCH_RETURN_FALSE() bool Terminal::EnableVT200MouseMode(const bool enabled) noexcept { @@ -591,7 +591,7 @@ try return true; } -CATCH_LOG_RETURN_FALSE() +CATCH_RETURN_FALSE() // Method Description: // - Updates the buffer's current text attributes to start a hyperlink diff --git a/src/cascadia/TerminalSettingsEditor/Actions.xaml b/src/cascadia/TerminalSettingsEditor/Actions.xaml index e4dd4ccc7..11c1f65e4 100644 --- a/src/cascadia/TerminalSettingsEditor/Actions.xaml +++ b/src/cascadia/TerminalSettingsEditor/Actions.xaml @@ -158,7 +158,6 @@ - + Visibility="{x:Bind local:Converters.InvertedBooleanToVisibility(IsInEditMode), Mode=OneWay}" /> + Visibility="{x:Bind local:Converters.InvertedBooleanToVisibility(IsInEditMode), Mode=OneWay}"> + Visibility="{x:Bind local:Converters.InvertedBooleanToVisibility(IsNewlyAdded), Mode=OneWay}"> diff --git a/src/cascadia/TerminalSettingsEditor/Appearances.h b/src/cascadia/TerminalSettingsEditor/Appearances.h index db00e79c4..6860b0361 100644 --- a/src/cascadia/TerminalSettingsEditor/Appearances.h +++ b/src/cascadia/TerminalSettingsEditor/Appearances.h @@ -51,6 +51,19 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation public: AppearanceViewModel(const Model::AppearanceConfig& appearance); + void SetFontWeightFromDouble(double fontWeight) + { + FontWeight(winrt::Microsoft::Terminal::Settings::Editor::Converters::DoubleToFontWeight(fontWeight)); + } + void SetBackgroundImageOpacityFromPercentageValue(double percentageValue) + { + BackgroundImageOpacity(winrt::Microsoft::Terminal::Settings::Editor::Converters::PercentageValueToPercentage(percentageValue)); + } + void SetBackgroundImagePath(winrt::hstring path) + { + BackgroundImagePath(path); + } + // background image bool UseDesktopBGImage(); void UseDesktopBGImage(const bool useDesktop); diff --git a/src/cascadia/TerminalSettingsEditor/Appearances.idl b/src/cascadia/TerminalSettingsEditor/Appearances.idl index a4faa7a30..ddc83abed 100644 --- a/src/cascadia/TerminalSettingsEditor/Appearances.idl +++ b/src/cascadia/TerminalSettingsEditor/Appearances.idl @@ -23,6 +23,10 @@ namespace Microsoft.Terminal.Settings.Editor { Boolean IsDefault; + void SetFontWeightFromDouble(Double fontWeight); + void SetBackgroundImageOpacityFromPercentageValue(Double percentageValue); + void SetBackgroundImagePath(String path); + Boolean UseDesktopBGImage; Boolean BackgroundImageSettingsVisible { get; }; @@ -68,5 +72,6 @@ namespace Microsoft.Terminal.Settings.Editor Windows.Foundation.Collections.IObservableVector FontWeightList { get; }; IInspectable CurrentFontFace { get; }; + Windows.UI.Xaml.Controls.Slider BIOpacitySlider { get; }; } } diff --git a/src/cascadia/TerminalSettingsEditor/Appearances.xaml b/src/cascadia/TerminalSettingsEditor/Appearances.xaml index f31e625ec..d49de2051 100644 --- a/src/cascadia/TerminalSettingsEditor/Appearances.xaml +++ b/src/cascadia/TerminalSettingsEditor/Appearances.xaml @@ -34,16 +34,6 @@ - - - - - - - - - - @@ -87,7 +77,7 @@ SelectedItem="{x:Bind CurrentFontFace, Mode=OneWay}" SelectionChanged="FontFace_SelectionChanged" Style="{StaticResource ComboBoxSettingStyle}" - Visibility="{x:Bind ShowAllFonts, Mode=OneWay, Converter={StaticResource InvertedBooleanToVisibilityConverter}}" /> + Visibility="{x:Bind local:Converters.InvertedBooleanToVisibility(ShowAllFonts), Mode=OneWay}" /> + Value="{x:Bind local:Converters.FontWeightToDouble(Appearance.FontWeight), BindBack=Appearance.SetFontWeightFromDouble, Mode=TwoWay}" /> - + Text="{x:Bind local:Converters.StringFallBackToEmptyString('desktopWallpaper', Appearance.BackgroundImagePath), Mode=TwoWay, BindBack=Appearance.SetBackgroundImagePath}" /> - - - - - @@ -106,7 +101,7 @@ + Visibility="{x:Bind local:Converters.InvertedBooleanToVisibility(IsRenaming), Mode=OneWay}"> (value))); - } - - Foundation::IInspectable ColorToBrushConverter::ConvertBack(Foundation::IInspectable const& /*value*/, - Windows::UI::Xaml::Interop::TypeName const& /* targetType */, - Foundation::IInspectable const& /*parameter*/, - hstring const& /* language */) - { - throw hresult_not_implemented(); - } -} diff --git a/src/cascadia/TerminalSettingsEditor/ColorToBrushConverter.h b/src/cascadia/TerminalSettingsEditor/ColorToBrushConverter.h deleted file mode 100644 index 94c20db6d..000000000 --- a/src/cascadia/TerminalSettingsEditor/ColorToBrushConverter.h +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#pragma once - -#include "ColorToBrushConverter.g.h" -#include "Utils.h" - -namespace winrt::Microsoft::Terminal::Settings::Editor::implementation -{ - struct ColorToBrushConverter : ColorToBrushConverterT - { - ColorToBrushConverter() = default; - - Windows::Foundation::IInspectable Convert(Windows::Foundation::IInspectable const& value, - Windows::UI::Xaml::Interop::TypeName const& targetType, - Windows::Foundation::IInspectable const& parameter, - hstring const& language); - - Windows::Foundation::IInspectable ConvertBack(Windows::Foundation::IInspectable const& value, - Windows::UI::Xaml::Interop::TypeName const& targetType, - Windows::Foundation::IInspectable const& parameter, - hstring const& language); - }; -} - -namespace winrt::Microsoft::Terminal::Settings::Editor::factory_implementation -{ - BASIC_FACTORY(ColorToBrushConverter); -} diff --git a/src/cascadia/TerminalSettingsEditor/ColorToHexConverter.cpp b/src/cascadia/TerminalSettingsEditor/ColorToHexConverter.cpp deleted file mode 100644 index 1d9379ab3..000000000 --- a/src/cascadia/TerminalSettingsEditor/ColorToHexConverter.cpp +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "pch.h" -#include "ColorToHexConverter.h" -#include "ColorToHexConverter.g.cpp" - -using namespace winrt::Windows; -using namespace winrt::Windows::UI::Xaml; - -namespace winrt::Microsoft::Terminal::Settings::Editor::implementation -{ - Foundation::IInspectable ColorToHexConverter::Convert(Foundation::IInspectable const& value, - Windows::UI::Xaml::Interop::TypeName const& /* targetType */, - Foundation::IInspectable const& /* parameter */, - hstring const& /* language */) - { - til::color color{ winrt::unbox_value(value) }; - auto hex = winrt::to_hstring(color.ToHexString().data()); - return winrt::box_value(hex); - } - - Foundation::IInspectable ColorToHexConverter::ConvertBack(Foundation::IInspectable const& /*value*/, - Windows::UI::Xaml::Interop::TypeName const& /* targetType */, - Foundation::IInspectable const& /*parameter*/, - hstring const& /* language */) - { - throw hresult_not_implemented(); - } -} diff --git a/src/cascadia/TerminalSettingsEditor/ColorToHexConverter.h b/src/cascadia/TerminalSettingsEditor/ColorToHexConverter.h deleted file mode 100644 index 53e2963df..000000000 --- a/src/cascadia/TerminalSettingsEditor/ColorToHexConverter.h +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#pragma once - -#include "ColorToHexConverter.g.h" -#include "Utils.h" - -namespace winrt::Microsoft::Terminal::Settings::Editor::implementation -{ - struct ColorToHexConverter : ColorToHexConverterT - { - ColorToHexConverter() = default; - - Windows::Foundation::IInspectable Convert(Windows::Foundation::IInspectable const& value, - Windows::UI::Xaml::Interop::TypeName const& targetType, - Windows::Foundation::IInspectable const& parameter, - hstring const& language); - - Windows::Foundation::IInspectable ConvertBack(Windows::Foundation::IInspectable const& value, - Windows::UI::Xaml::Interop::TypeName const& targetType, - Windows::Foundation::IInspectable const& parameter, - hstring const& language); - }; -} - -namespace winrt::Microsoft::Terminal::Settings::Editor::factory_implementation -{ - BASIC_FACTORY(ColorToHexConverter); -} diff --git a/src/cascadia/TerminalSettingsEditor/Converters.cpp b/src/cascadia/TerminalSettingsEditor/Converters.cpp new file mode 100644 index 000000000..d9cff642e --- /dev/null +++ b/src/cascadia/TerminalSettingsEditor/Converters.cpp @@ -0,0 +1,106 @@ +#include "pch.h" +#include "Converters.h" +#if __has_include("Converters.g.cpp") +#include "Converters.g.cpp" +#endif + +namespace winrt::Microsoft::Terminal::Settings::Editor::implementation +{ + winrt::hstring Converters::AppendPercentageSign(double value) + { + const auto number{ value }; + return to_hstring((int)number) + L"%"; + } + + winrt::Windows::UI::Xaml::Media::SolidColorBrush Converters::ColorToBrush(winrt::Windows::UI::Color color) + { + return Windows::UI::Xaml::Media::SolidColorBrush(color); + } + + winrt::Windows::UI::Text::FontWeight Converters::DoubleToFontWeight(double value) + { + return winrt::Windows::UI::Text::FontWeight{ base::ClampedNumeric(value) }; + } + + double Converters::FontWeightToDouble(winrt::Windows::UI::Text::FontWeight fontWeight) + { + return fontWeight.Weight; + } + + bool Converters::InvertBoolean(bool value) + { + return !value; + } + + winrt::Windows::UI::Xaml::Visibility Converters::InvertedBooleanToVisibility(bool value) + { + return value ? winrt::Windows::UI::Xaml::Visibility::Collapsed : winrt::Windows::UI::Xaml::Visibility::Visible; + } + + winrt::Windows::UI::Color Converters::LightenColor(winrt::Windows::UI::Color color) + { + color.A = 128; // halfway transparent + return color; + } + + double Converters::MaxValueFromPaddingString(winrt::hstring paddingString) + { + const wchar_t singleCharDelim = L','; + std::wstringstream tokenStream(paddingString.c_str()); + std::wstring token; + double maxVal = 0; + size_t* idx = nullptr; + + // Get padding values till we run out of delimiter separated values in the stream + // Non-numeral values detected will default to 0 + // std::getline will not throw exception unless flags are set on the wstringstream + // std::stod will throw invalid_argument exception if the input is an invalid double value + // std::stod will throw out_of_range exception if the input value is more than DBL_MAX + try + { + while (std::getline(tokenStream, token, singleCharDelim)) + { + // std::stod internally calls wcstod which handles whitespace prefix (which is ignored) + // & stops the scan when first char outside the range of radix is encountered + // We'll be permissive till the extent that stod function allows us to be by default + // Ex. a value like 100.3#535w2 will be read as 100.3, but ;df25 will fail + const auto curVal = std::stod(token, idx); + if (curVal > maxVal) + { + maxVal = curVal; + } + } + } + catch (...) + { + // If something goes wrong, even if due to a single bad padding value, we'll return default 0 padding + maxVal = 0; + LOG_CAUGHT_EXCEPTION(); + } + + return maxVal; + } + + int Converters::PercentageToPercentageValue(double value) + { + return base::ClampMul(value, 100u); + } + + double Converters::PercentageValueToPercentage(double value) + { + return base::ClampDiv(value, 100); + } + + bool Converters::StringsAreNotEqual(winrt::hstring expected, winrt::hstring actual) + { + return expected != actual; + } + winrt::Windows::UI::Xaml::Visibility Converters::StringNotEmptyToVisibility(winrt::hstring value) + { + return value.empty() ? winrt::Windows::UI::Xaml::Visibility::Collapsed : winrt::Windows::UI::Xaml::Visibility::Visible; + } + winrt::hstring Converters::StringFallBackToEmptyString(winrt::hstring expected, winrt::hstring actual) + { + return expected == actual ? expected : L""; + } +} diff --git a/src/cascadia/TerminalSettingsEditor/Converters.h b/src/cascadia/TerminalSettingsEditor/Converters.h new file mode 100644 index 000000000..d7f99cbf2 --- /dev/null +++ b/src/cascadia/TerminalSettingsEditor/Converters.h @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#pragma once + +#include "Converters.g.h" + +namespace winrt::Microsoft::Terminal::Settings::Editor::implementation +{ + struct Converters : ConvertersT + { + static winrt::hstring AppendPercentageSign(double value); + static winrt::Windows::UI::Text::FontWeight DoubleToFontWeight(double value); + static winrt::Windows::UI::Xaml::Media::SolidColorBrush ColorToBrush(winrt::Windows::UI::Color color); + static double FontWeightToDouble(winrt::Windows::UI::Text::FontWeight fontWeight); + static bool InvertBoolean(bool value); + static winrt::Windows::UI::Xaml::Visibility InvertedBooleanToVisibility(bool value); + static winrt::Windows::UI::Color LightenColor(winrt::Windows::UI::Color color); + static double MaxValueFromPaddingString(winrt::hstring paddingString); + static int PercentageToPercentageValue(double value); + static double PercentageValueToPercentage(double value); + static bool StringsAreNotEqual(winrt::hstring expected, winrt::hstring actual); + static winrt::Windows::UI::Xaml::Visibility StringNotEmptyToVisibility(winrt::hstring value); + static winrt::hstring StringFallBackToEmptyString(winrt::hstring expected, winrt::hstring actual); + }; +} + +namespace winrt::Microsoft::Terminal::Settings::Editor::factory_implementation +{ + struct Converters : ConvertersT + { + }; +} diff --git a/src/cascadia/TerminalSettingsEditor/Converters.idl b/src/cascadia/TerminalSettingsEditor/Converters.idl index 89eb05791..a5b79d5fc 100644 --- a/src/cascadia/TerminalSettingsEditor/Converters.idl +++ b/src/cascadia/TerminalSettingsEditor/Converters.idl @@ -4,63 +4,22 @@ namespace Microsoft.Terminal.Settings.Editor { - runtimeclass ColorLightenConverter : [default] Windows.UI.Xaml.Data.IValueConverter + [bindable] + [default_interface] static runtimeclass Converters { - ColorLightenConverter(); - }; - runtimeclass FontWeightConverter : [default] Windows.UI.Xaml.Data.IValueConverter - { - FontWeightConverter(); - }; - - runtimeclass InvertedBooleanConverter : [default] Windows.UI.Xaml.Data.IValueConverter - { - InvertedBooleanConverter(); - }; - - runtimeclass InvertedBooleanToVisibilityConverter : [default] Windows.UI.Xaml.Data.IValueConverter - { - InvertedBooleanToVisibilityConverter(); - }; - - runtimeclass ColorToBrushConverter : [default] Windows.UI.Xaml.Data.IValueConverter - { - ColorToBrushConverter(); - }; - - runtimeclass ColorToHexConverter : [default] Windows.UI.Xaml.Data.IValueConverter - { - ColorToHexConverter(); - }; - - runtimeclass PercentageConverter : [default] Windows.UI.Xaml.Data.IValueConverter - { - PercentageConverter(); - }; - - runtimeclass PercentageSignConverter : [default] Windows.UI.Xaml.Data.IValueConverter - { - PercentageSignConverter(); - }; - - runtimeclass StringIsEmptyConverter : [default] Windows.UI.Xaml.Data.IValueConverter - { - StringIsEmptyConverter(); - }; - - runtimeclass PaddingConverter : [default] Windows.UI.Xaml.Data.IValueConverter - { - PaddingConverter(); - }; - - runtimeclass StringIsNotDesktopConverter : [default] Windows.UI.Xaml.Data.IValueConverter - { - StringIsNotDesktopConverter(); - }; - - runtimeclass DesktopWallpaperToEmptyStringConverter : [default] Windows.UI.Xaml.Data.IValueConverter - { - DesktopWallpaperToEmptyStringConverter(); - }; + static String AppendPercentageSign(Double value); + static Windows.UI.Text.FontWeight DoubleToFontWeight(Double value); + static Windows.UI.Xaml.Media.SolidColorBrush ColorToBrush(Windows.UI.Color color); + static Double FontWeightToDouble(Windows.UI.Text.FontWeight fontWeight); + static Boolean InvertBoolean(Boolean value); + static Windows.UI.Xaml.Visibility InvertedBooleanToVisibility(Boolean value); + static Windows.UI.Color LightenColor(Windows.UI.Color color); + static Double MaxValueFromPaddingString(String paddingString); + static Int32 PercentageToPercentageValue(Double value); + static Double PercentageValueToPercentage(Double value); + static Boolean StringsAreNotEqual(String expected, String actual); + static Windows.UI.Xaml.Visibility StringNotEmptyToVisibility(String value); + static String StringFallBackToEmptyString(String expected, String actual); + } } diff --git a/src/cascadia/TerminalSettingsEditor/FontWeightConverter.cpp b/src/cascadia/TerminalSettingsEditor/FontWeightConverter.cpp deleted file mode 100644 index 5b39eded1..000000000 --- a/src/cascadia/TerminalSettingsEditor/FontWeightConverter.cpp +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "pch.h" -#include "FontWeightConverter.h" -#include "FontWeightConverter.g.cpp" - -using namespace winrt::Windows; -using namespace winrt::Windows::UI::Xaml; -using namespace winrt::Windows::UI::Text; - -namespace winrt::Microsoft::Terminal::Settings::Editor::implementation -{ - Foundation::IInspectable FontWeightConverter::Convert(Foundation::IInspectable const& value, - Windows::UI::Xaml::Interop::TypeName const& /* targetType */, - Foundation::IInspectable const& /* parameter */, - hstring const& /* language */) - { - const auto weight{ winrt::unbox_value(value) }; - return winrt::box_value(weight.Weight); - } - - Foundation::IInspectable FontWeightConverter::ConvertBack(Foundation::IInspectable const& value, - Windows::UI::Xaml::Interop::TypeName const& /* targetType */, - Foundation::IInspectable const& /*parameter*/, - hstring const& /* language */) - { - const auto sliderVal{ winrt::unbox_value(value) }; - FontWeight weight{ base::ClampedNumeric(sliderVal) }; - return winrt::box_value(weight); - } -} diff --git a/src/cascadia/TerminalSettingsEditor/FontWeightConverter.h b/src/cascadia/TerminalSettingsEditor/FontWeightConverter.h deleted file mode 100644 index 249e7987d..000000000 --- a/src/cascadia/TerminalSettingsEditor/FontWeightConverter.h +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#pragma once - -#include "FontWeightConverter.g.h" -#include "Utils.h" - -namespace winrt::Microsoft::Terminal::Settings::Editor::implementation -{ - struct FontWeightConverter : FontWeightConverterT - { - FontWeightConverter() = default; - - Windows::Foundation::IInspectable Convert(Windows::Foundation::IInspectable const& value, - Windows::UI::Xaml::Interop::TypeName const& targetType, - Windows::Foundation::IInspectable const& parameter, - hstring const& language); - - Windows::Foundation::IInspectable ConvertBack(Windows::Foundation::IInspectable const& value, - Windows::UI::Xaml::Interop::TypeName const& targetType, - Windows::Foundation::IInspectable const& parameter, - hstring const& language); - }; -} - -namespace winrt::Microsoft::Terminal::Settings::Editor::factory_implementation -{ - BASIC_FACTORY(FontWeightConverter); -} diff --git a/src/cascadia/TerminalSettingsEditor/GlobalAppearance.xaml b/src/cascadia/TerminalSettingsEditor/GlobalAppearance.xaml index ae030da7c..c4f4bf2b4 100644 --- a/src/cascadia/TerminalSettingsEditor/GlobalAppearance.xaml +++ b/src/cascadia/TerminalSettingsEditor/GlobalAppearance.xaml @@ -21,8 +21,6 @@ x:DataType="local:EnumEntry"> - - @@ -79,7 +77,7 @@ - + diff --git a/src/cascadia/TerminalSettingsEditor/InvertedBooleanConverter.cpp b/src/cascadia/TerminalSettingsEditor/InvertedBooleanConverter.cpp deleted file mode 100644 index d0bb98075..000000000 --- a/src/cascadia/TerminalSettingsEditor/InvertedBooleanConverter.cpp +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "pch.h" -#include "InvertedBooleanConverter.h" -#include "InvertedBooleanConverter.g.cpp" - -using namespace winrt::Windows; -using namespace winrt::Windows::UI::Xaml; - -namespace winrt::Microsoft::Terminal::Settings::Editor::implementation -{ - Foundation::IInspectable InvertedBooleanConverter::Convert(Foundation::IInspectable const& value, - Windows::UI::Xaml::Interop::TypeName const& /* targetType */, - Foundation::IInspectable const& /* parameter */, - hstring const& /* language */) - { - return winrt::box_value(!winrt::unbox_value(value)); - } - - Foundation::IInspectable InvertedBooleanConverter::ConvertBack(Foundation::IInspectable const& value, - Windows::UI::Xaml::Interop::TypeName const& /* targetType */, - Foundation::IInspectable const& /*parameter*/, - hstring const& /* language */) - { - return winrt::box_value(!winrt::unbox_value(value)); - } -} diff --git a/src/cascadia/TerminalSettingsEditor/InvertedBooleanConverter.h b/src/cascadia/TerminalSettingsEditor/InvertedBooleanConverter.h deleted file mode 100644 index 9da6768fe..000000000 --- a/src/cascadia/TerminalSettingsEditor/InvertedBooleanConverter.h +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#pragma once - -#include "InvertedBooleanConverter.g.h" -#include "../inc/cppwinrt_utils.h" - -DECLARE_CONVERTER(winrt::Microsoft::Terminal::Settings::Editor, InvertedBooleanConverter); diff --git a/src/cascadia/TerminalSettingsEditor/InvertedBooleanToVisibilityConverter.cpp b/src/cascadia/TerminalSettingsEditor/InvertedBooleanToVisibilityConverter.cpp deleted file mode 100644 index fd3c9e6fc..000000000 --- a/src/cascadia/TerminalSettingsEditor/InvertedBooleanToVisibilityConverter.cpp +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "pch.h" -#include "InvertedBooleanToVisibilityConverter.h" -#include "InvertedBooleanToVisibilityConverter.g.cpp" - -using namespace winrt::Windows; -using namespace winrt::Windows::UI::Xaml; - -namespace winrt::Microsoft::Terminal::Settings::Editor::implementation -{ - Foundation::IInspectable InvertedBooleanToVisibilityConverter::Convert(Foundation::IInspectable const& value, - Windows::UI::Xaml::Interop::TypeName const& /* targetType */, - Foundation::IInspectable const& /* parameter */, - hstring const& /* language */) - { - return winrt::box_value(winrt::unbox_value(value) ? Visibility::Collapsed : Visibility::Visible); - } - - Foundation::IInspectable InvertedBooleanToVisibilityConverter::ConvertBack(Foundation::IInspectable const& /*value*/, - Windows::UI::Xaml::Interop::TypeName const& /* targetType */, - Foundation::IInspectable const& /*parameter*/, - hstring const& /* language */) - { - throw hresult_not_implemented(); - } -} diff --git a/src/cascadia/TerminalSettingsEditor/InvertedBooleanToVisibilityConverter.h b/src/cascadia/TerminalSettingsEditor/InvertedBooleanToVisibilityConverter.h deleted file mode 100644 index 90c6bbb74..000000000 --- a/src/cascadia/TerminalSettingsEditor/InvertedBooleanToVisibilityConverter.h +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#pragma once - -#include "InvertedBooleanToVisibilityConverter.g.h" -#include "../inc/cppwinrt_utils.h" - -DECLARE_CONVERTER(winrt::Microsoft::Terminal::Settings::Editor, InvertedBooleanToVisibilityConverter); diff --git a/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj b/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj index 4861cb1f4..e60fccb32 100644 --- a/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj +++ b/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj @@ -42,41 +42,9 @@ AddProfile.xaml - - Converters.idl - - - Converters.idl - - - Converters.idl - - - Converters.idl - - - Converters.idl - - - Converters.idl - - - KeyChordListener.xaml - - - Converters.idl - - - Converters.idl - - - Converters.idl - - - Converters.idl - - + Converters.idl + Code EnumEntry.idl @@ -91,6 +59,9 @@ Interaction.xaml + + KeyChordListener.xaml + Launch.xaml @@ -171,41 +142,9 @@ AddProfile.xaml - - Converters.idl - - - Converters.idl - - - Converters.idl - - - Converters.idl - - - Converters.idl - - - Converters.idl - - - KeyChordListener.xaml - - - Converters.idl - - - Converters.idl - - - Converters.idl - - - Converters.idl - - + Converters.idl + Code GlobalAppearance.xaml @@ -217,6 +156,9 @@ Interaction.xaml + + KeyChordListener.xaml + Launch.xaml @@ -339,7 +281,6 @@ true false - false @@ -379,4 +320,4 @@ - + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj.filters b/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj.filters index 66e8b687d..3b0b9186e 100644 --- a/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj.filters +++ b/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj.filters @@ -10,16 +10,10 @@ - - Converters - - - Converters - @@ -49,9 +43,4 @@ - - - {00f725c8-41b4-40a8-995e-8ee2e49a4a4c} - - - + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsEditor/PaddingConverter.cpp b/src/cascadia/TerminalSettingsEditor/PaddingConverter.cpp deleted file mode 100644 index 6c0e3472c..000000000 --- a/src/cascadia/TerminalSettingsEditor/PaddingConverter.cpp +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "pch.h" -#include "PaddingConverter.h" -#include "PaddingConverter.g.cpp" - -using namespace winrt::Windows; -using namespace winrt::Windows::UI::Xaml; -using namespace winrt::Windows::UI::Text; - -namespace winrt::Microsoft::Terminal::Settings::Editor::implementation -{ - Foundation::IInspectable PaddingConverter::Convert(Foundation::IInspectable const& value, - Windows::UI::Xaml::Interop::TypeName const& /* targetType */, - Foundation::IInspectable const& /* parameter */, - hstring const& /* language */) - { - const auto& padding = winrt::unbox_value(value); - - const wchar_t singleCharDelim = L','; - std::wstringstream tokenStream(padding.c_str()); - std::wstring token; - double maxVal = 0; - size_t* idx = nullptr; - - // Get padding values till we run out of delimiter separated values in the stream - // Non-numeral values detected will default to 0 - // std::getline will not throw exception unless flags are set on the wstringstream - // std::stod will throw invalid_argument exception if the input is an invalid double value - // std::stod will throw out_of_range exception if the input value is more than DBL_MAX - try - { - while (std::getline(tokenStream, token, singleCharDelim)) - { - // std::stod internally calls wcstod which handles whitespace prefix (which is ignored) - // & stops the scan when first char outside the range of radix is encountered - // We'll be permissive till the extent that stod function allows us to be by default - // Ex. a value like 100.3#535w2 will be read as 100.3, but ;df25 will fail - const auto curVal = std::stod(token, idx); - if (curVal > maxVal) - { - maxVal = curVal; - } - } - } - catch (...) - { - // If something goes wrong, even if due to a single bad padding value, we'll return default 0 padding - maxVal = 0; - LOG_CAUGHT_EXCEPTION(); - } - - return winrt::box_value(maxVal); - } - - Foundation::IInspectable PaddingConverter::ConvertBack(Foundation::IInspectable const& value, - Windows::UI::Xaml::Interop::TypeName const& /* targetType */, - Foundation::IInspectable const& /*parameter*/, - hstring const& /* language */) - { - const auto padding{ winrt::unbox_value(value) }; - return winrt::box_value(winrt::to_hstring(padding)); - } -} diff --git a/src/cascadia/TerminalSettingsEditor/PaddingConverter.h b/src/cascadia/TerminalSettingsEditor/PaddingConverter.h deleted file mode 100644 index c56dd6d9f..000000000 --- a/src/cascadia/TerminalSettingsEditor/PaddingConverter.h +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#pragma once - -#include "PaddingConverter.g.h" -#include "../inc/cppwinrt_utils.h" - -DECLARE_CONVERTER(winrt::Microsoft::Terminal::Settings::Editor, PaddingConverter); diff --git a/src/cascadia/TerminalSettingsEditor/PercentageConverter.cpp b/src/cascadia/TerminalSettingsEditor/PercentageConverter.cpp deleted file mode 100644 index b414532bf..000000000 --- a/src/cascadia/TerminalSettingsEditor/PercentageConverter.cpp +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "pch.h" -#include "PercentageConverter.h" -#include "PercentageConverter.g.cpp" - -using namespace winrt::Windows; -using namespace winrt::Windows::UI::Xaml; - -namespace winrt::Microsoft::Terminal::Settings::Editor::implementation -{ - Foundation::IInspectable PercentageConverter::Convert(Foundation::IInspectable const& value, - Windows::UI::Xaml::Interop::TypeName const& /* targetType */, - Foundation::IInspectable const& /* parameter */, - hstring const& /* language */) - { - const auto decimal{ winrt::unbox_value(value) }; - const unsigned int number{ base::ClampMul(decimal, 100u) }; - return winrt::box_value(number); - } - - Foundation::IInspectable PercentageConverter::ConvertBack(Foundation::IInspectable const& value, - Windows::UI::Xaml::Interop::TypeName const& /* targetType */, - Foundation::IInspectable const& /*parameter*/, - hstring const& /* language */) - { - const auto number{ winrt::unbox_value(value) }; - const auto decimal{ base::ClampDiv(number, 100) }; - return winrt::box_value(decimal); - } -} diff --git a/src/cascadia/TerminalSettingsEditor/PercentageConverter.h b/src/cascadia/TerminalSettingsEditor/PercentageConverter.h deleted file mode 100644 index e3aa16bbf..000000000 --- a/src/cascadia/TerminalSettingsEditor/PercentageConverter.h +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#pragma once - -#include "PercentageConverter.g.h" -#include "Utils.h" - -namespace winrt::Microsoft::Terminal::Settings::Editor::implementation -{ - struct PercentageConverter : PercentageConverterT - { - PercentageConverter() = default; - - Windows::Foundation::IInspectable Convert(Windows::Foundation::IInspectable const& value, - Windows::UI::Xaml::Interop::TypeName const& targetType, - Windows::Foundation::IInspectable const& parameter, - hstring const& language); - - Windows::Foundation::IInspectable ConvertBack(Windows::Foundation::IInspectable const& value, - Windows::UI::Xaml::Interop::TypeName const& targetType, - Windows::Foundation::IInspectable const& parameter, - hstring const& language); - }; -} - -namespace winrt::Microsoft::Terminal::Settings::Editor::factory_implementation -{ - BASIC_FACTORY(PercentageConverter); -} diff --git a/src/cascadia/TerminalSettingsEditor/Profiles.h b/src/cascadia/TerminalSettingsEditor/Profiles.h index c39786bfd..65a151cb0 100644 --- a/src/cascadia/TerminalSettingsEditor/Profiles.h +++ b/src/cascadia/TerminalSettingsEditor/Profiles.h @@ -20,6 +20,16 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation Model::TerminalSettings TermSettings() const; + void SetAcrylicOpacityPercentageValue(double value) + { + AcrylicOpacity(winrt::Microsoft::Terminal::Settings::Editor::Converters::PercentageValueToPercentage(value)); + }; + + void SetPadding(double value) + { + Padding(to_hstring(value)); + } + // starting directory bool UseParentProcessDirectory(); void UseParentProcessDirectory(const bool useParent); diff --git a/src/cascadia/TerminalSettingsEditor/Profiles.idl b/src/cascadia/TerminalSettingsEditor/Profiles.idl index ff8fc6565..4ecc48899 100644 --- a/src/cascadia/TerminalSettingsEditor/Profiles.idl +++ b/src/cascadia/TerminalSettingsEditor/Profiles.idl @@ -19,6 +19,9 @@ namespace Microsoft.Terminal.Settings.Editor Windows.Foundation.Collections.IObservableVector MonospaceFontList { get; }; Microsoft.Terminal.Settings.Model.TerminalSettings TermSettings { get; }; + void SetAcrylicOpacityPercentageValue(Double value); + void SetPadding(Double value); + Boolean CanDeleteProfile { get; }; Boolean IsBaseLayer; Boolean UseParentProcessDirectory; @@ -105,5 +108,7 @@ namespace Microsoft.Terminal.Settings.Editor IInspectable CurrentScrollState; Windows.Foundation.Collections.IObservableVector ScrollStateList { get; }; + + Windows.UI.Xaml.Controls.Slider AcrylicOpacitySlider { get; }; } } diff --git a/src/cascadia/TerminalSettingsEditor/Profiles.xaml b/src/cascadia/TerminalSettingsEditor/Profiles.xaml index e2fb38987..763532256 100644 --- a/src/cascadia/TerminalSettingsEditor/Profiles.xaml +++ b/src/cascadia/TerminalSettingsEditor/Profiles.xaml @@ -33,17 +33,6 @@ - - - - - - - - - - - @@ -78,7 +67,7 @@ --> + Visibility="{x:Bind local:Converters.InvertedBooleanToVisibility(State.Profile.IsBaseLayer), Mode=OneWay}"> @@ -90,7 +79,7 @@ ClearSettingValue="{x:Bind State.Profile.ClearCommandline}" HasSettingValue="{x:Bind State.Profile.HasCommandline, Mode=OneWay}" SettingOverrideSource="{x:Bind State.Profile.CommandlineOverrideSource, Mode=OneWay}" - Visibility="{x:Bind State.Profile.IsBaseLayer, Mode=OneWay, Converter={StaticResource InvertedBooleanToVisibilityConverter}}"> + Visibility="{x:Bind local:Converters.InvertedBooleanToVisibility(State.Profile.IsBaseLayer), Mode=OneWay}"> @@ -150,7 +139,7 @@ + Visibility="{x:Bind local:Converters.InvertedBooleanToVisibility(State.Profile.IsBaseLayer), Mode=OneWay}"> @@ -280,10 +269,10 @@ + Value="{x:Bind local:Converters.PercentageToPercentageValue(State.Profile.AcrylicOpacity), BindBack=State.Profile.SetAcrylicOpacityPercentageValue, Mode=TwoWay}" /> + Text="{x:Bind local:Converters.AppendPercentageSign(AcrylicOpacitySlider.Value), Mode=OneWay}" /> @@ -307,7 +296,7 @@ + Value="{x:Bind local:Converters.MaxValueFromPaddingString(State.Profile.Padding), BindBack=State.Profile.SetPadding, Mode=TwoWay}" /> @@ -333,7 +322,7 @@ Margin="32,0,0,0" Click="CreateUnfocusedAppearance_Click" Style="{StaticResource BaseButtonStyle}" - Visibility="{x:Bind State.Profile.HasUnfocusedAppearance, Mode=OneWay, Converter={StaticResource InvertedBooleanToVisibilityConverter}}"> + Visibility="{x:Bind local:Converters.InvertedBooleanToVisibility(State.Profile.HasUnfocusedAppearance), Mode=OneWay}"> diff --git a/src/cascadia/TerminalSettingsEditor/ReadOnlyActions.xaml b/src/cascadia/TerminalSettingsEditor/ReadOnlyActions.xaml index ec1a685fb..eef40cd8d 100644 --- a/src/cascadia/TerminalSettingsEditor/ReadOnlyActions.xaml +++ b/src/cascadia/TerminalSettingsEditor/ReadOnlyActions.xaml @@ -17,9 +17,6 @@ - - -