From 601286ac692d7816cae883c2a701fa799c6ef595 Mon Sep 17 00:00:00 2001 From: Malcolm Smith Date: Mon, 1 Jun 2020 10:19:05 -0700 Subject: [PATCH 1/8] Find icon from shortcut target if shortcut doesn't specify it (#6277) Implements what I was suggesting in #6266 where if a shortcut doesn't specify an icon, the shortcut target full path is used before searching for a matching executable in the path. ## References Found due to not getting the right icon in conhost from the Yori installer. It's fixed in the installer from https://github.com/malxau/yori/commit/5af366b6a5ea4c92dce3940abb8616ab5bd300a6 for all current users of conhost though, so this PR is just trying to minimize surprises for the next guy. ## Detailed Description of the Pull Request / Additional comments I know conhost and shortcut settings aren't really the team's focus which is why I'm doing this. I understand though if there's a better way or there are factors that I hadn't considered. Note that the path searching code is used when programs are launched without using a shortcut, and it will match if the working directory of the shortcut is the directory containing the executable. ## Validation Steps Performed Created a shortcut that didn't specify an icon to a binary that wasn't in the path, and verified that the icon in the upper left of the console window could resolve correctly when opening the shortcut. I'm not aware of a way to get into this path (of launching via a shortcut to a command line process) without replacing the system conhost, which is what I did to verify it. In order to diagnose it, I used hardcoded DebugBreak() since even ImageFileExecutionOptions didn't like running against conhost- is there are better way to debug and test these cases without being so invasive on the system? Closes #6266 --- .../win32/SystemConfigurationProvider.cpp | 42 +++++++++++++------ src/propsheet/PropSheetHandler.cpp | 2 +- src/propslib/ShortcutSerialization.cpp | 14 ++++++- src/propslib/ShortcutSerialization.hpp | 2 + 4 files changed, 45 insertions(+), 15 deletions(-) diff --git a/src/interactivity/win32/SystemConfigurationProvider.cpp b/src/interactivity/win32/SystemConfigurationProvider.cpp index 1c61bb052ec..814c64dc3f4 100644 --- a/src/interactivity/win32/SystemConfigurationProvider.cpp +++ b/src/interactivity/win32/SystemConfigurationProvider.cpp @@ -63,6 +63,7 @@ void SystemConfigurationProvider::GetSettingsFromLink( _In_ PCWSTR pwszAppName) { CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + WCHAR wszLinkTarget[MAX_PATH] = { 0 }; WCHAR wszIconLocation[MAX_PATH] = { 0 }; int iIconIndex = 0; @@ -96,6 +97,8 @@ void SystemConfigurationProvider::GetSettingsFromLink( &fReadConsoleProperties, wszShortcutTitle, ARRAYSIZE(wszShortcutTitle), + wszLinkTarget, + ARRAYSIZE(wszLinkTarget), wszIconLocation, ARRAYSIZE(wszIconLocation), &iIconIndex, @@ -111,6 +114,12 @@ void SystemConfigurationProvider::GetSettingsFromLink( dwHotKey = wHotKey; pLinkSettings->SetHotKey(dwHotKey); + if (wszLinkTarget[0] != L'\0') + { + // guarantee null termination to make OACR happy. + wszLinkTarget[ARRAYSIZE(wszLinkTarget) - 1] = L'\0'; + } + // if we got a title, use it. even on overall link value load failure, the title will be correct if // filled out. if (wszShortcutTitle[0] != L'\0') @@ -152,21 +161,28 @@ void SystemConfigurationProvider::GetSettingsFromLink( // Go get the icon if (wszIconLocation[0] == L'\0') { - // search for the application along the path so that we can load its icons (if we didn't find one explicitly in - // the shortcut) - const DWORD dwLinkLen = SearchPathW(pwszCurrDir, pwszAppName, nullptr, ARRAYSIZE(wszIconLocation), wszIconLocation, nullptr); - - // If we cannot find the application in the path, then try to fall back and see if the window title is a valid path and use that. - if (dwLinkLen <= 0 || dwLinkLen > sizeof(wszIconLocation)) + if (PathFileExists(wszLinkTarget)) { - if (PathFileExistsW(pwszTitle) && (wcslen(pwszTitle) < sizeof(wszIconLocation))) - { - StringCchCopyW(wszIconLocation, ARRAYSIZE(wszIconLocation), pwszTitle); - } - else + StringCchCopyW(wszIconLocation, ARRAYSIZE(wszIconLocation), wszLinkTarget); + } + else + { + // search for the application along the path so that we can load its icons (if we didn't find one explicitly in + // the shortcut) + const DWORD dwLinkLen = SearchPathW(pwszCurrDir, pwszAppName, nullptr, ARRAYSIZE(wszIconLocation), wszIconLocation, nullptr); + + // If we cannot find the application in the path, then try to fall back and see if the window title is a valid path and use that. + if (dwLinkLen <= 0 || dwLinkLen > sizeof(wszIconLocation)) { - // If all else fails, just stick the app name into the path and try to resolve just the app name. - StringCchCopyW(wszIconLocation, ARRAYSIZE(wszIconLocation), pwszAppName); + if (PathFileExistsW(pwszTitle) && (wcslen(pwszTitle) < sizeof(wszIconLocation))) + { + StringCchCopyW(wszIconLocation, ARRAYSIZE(wszIconLocation), pwszTitle); + } + else + { + // If all else fails, just stick the app name into the path and try to resolve just the app name. + StringCchCopyW(wszIconLocation, ARRAYSIZE(wszIconLocation), pwszAppName); + } } } } diff --git a/src/propsheet/PropSheetHandler.cpp b/src/propsheet/PropSheetHandler.cpp index 27880e748be..f06d4fc6ebf 100644 --- a/src/propsheet/PropSheetHandler.cpp +++ b/src/propsheet/PropSheetHandler.cpp @@ -118,7 +118,7 @@ class ConsolePropertySheetHandler WrlFinal : public RuntimeClass 0) + { + pwszLinkTarget[0] = L'\0'; + } + if (pwszIconLocation && cchIconLocation > 0) { pwszIconLocation[0] = L'\0'; @@ -374,7 +381,12 @@ void ShortcutSerialization::s_GetLinkTitle(_In_ PCWSTR pwszShortcutFilename, s_GetLinkTitle(pStateInfo->LinkTitle, pwszShortcutTitle, cchShortcutTitle); } - if (pwszIconLocation && piIcon) + if (pwszLinkTarget) + { + hr = psl->GetPath(pwszLinkTarget, static_cast(cchLinkTarget), NULL, 0); + } + + if (SUCCEEDED(hr) && pwszIconLocation && piIcon) { hr = psl->GetIconLocation(pwszIconLocation, static_cast(cchIconLocation), piIcon); } diff --git a/src/propslib/ShortcutSerialization.hpp b/src/propslib/ShortcutSerialization.hpp index a21e57386f7..853cd68bbe4 100644 --- a/src/propslib/ShortcutSerialization.hpp +++ b/src/propslib/ShortcutSerialization.hpp @@ -30,6 +30,8 @@ class ShortcutSerialization _Out_ BOOL* const pfReadConsoleProperties, _Out_writes_opt_(cchShortcutTitle) PWSTR pwszShortcutTitle, const size_t cchShortcutTitle, + _Out_writes_opt_(cchLinkTarget) PWSTR pwszLinkTarget, + const size_t cchLinkTarget, _Out_writes_opt_(cchIconLocation) PWSTR pwszIconLocation, const size_t cchIconLocation, _Out_opt_ int* const piIcon, From 94eab6e391a099814ce8f9a08c8cccf9f588b7d1 Mon Sep 17 00:00:00 2001 From: Chester Liu Date: Tue, 2 Jun 2020 02:36:28 +0800 Subject: [PATCH 2/8] Skip glyph shaping analysis when the entire text is simple (#6206) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary of the Pull Request As the title suggests, this PR will make CustomTextLayout skip glyph shaping analysis when the entire text is detected as simple. ## References My main reference is [DirectX Factor - Who’s Afraid of Glyph Runs?](https://docs.microsoft.com/en-us/archive/msdn-magazine/2013/november/directx-factor-who%e2%80%99s-afraid-of-glyph-runs) And also #2959 ## PR Checklist * [x] Closes @skyline75489's continuous drive for perf gainz. * [x] CLA signed. * [x] Manual tests. * [x] Nah on docs. * [x] Discussed with core contributors in this PR. ## Detailed Description of the Pull Request / Additional comments This can be seen as a followup of #2959. The idea is the same: make use of simple text (which makes up I think 95% of all terminal text) as much as possible. The performance boost is huge. Cacafire is actually on fire this time (and remember I'm using 4K!). The frame rate is also boosted since more CPU time can be used for actual drawing. Before: ![图片](https://user-images.githubusercontent.com/4710575/82913277-b21c3c00-9fa0-11ea-8785-a14b347bbcbd.png) After: ![图片](https://user-images.githubusercontent.com/4710575/82912969-4afe8780-9fa0-11ea-8795-92617dde822f.png) ## Validation Steps Performed Manually validated. --- src/renderer/dx/CustomTextLayout.cpp | 91 +++++++++++++++++++++++++--- src/renderer/dx/CustomTextLayout.h | 7 +++ 2 files changed, 89 insertions(+), 9 deletions(-) diff --git a/src/renderer/dx/CustomTextLayout.cpp b/src/renderer/dx/CustomTextLayout.cpp index 7593e17d7de..d19d2d0c338 100644 --- a/src/renderer/dx/CustomTextLayout.cpp +++ b/src/renderer/dx/CustomTextLayout.cpp @@ -41,7 +41,8 @@ CustomTextLayout::CustomTextLayout(gsl::not_null const factory _runs{}, _breakpoints{}, _runIndex{ 0 }, - _width{ width } + _width{ width }, + _isEntireTextSimple{ false } { // Fetch the locale name out once now from the format _localeName.resize(gsl::narrow_cast(format->GetLocaleNameLength()) + 1); // +1 for null @@ -76,6 +77,7 @@ CustomTextLayout::CustomTextLayout(gsl::not_null const factory RETURN_HR_IF_NULL(E_INVALIDARG, columns); *columns = 0; + RETURN_IF_FAILED(_AnalyzeTextComplexity()); RETURN_IF_FAILED(_AnalyzeRuns()); RETURN_IF_FAILED(_ShapeGlyphRuns()); @@ -106,6 +108,7 @@ CustomTextLayout::CustomTextLayout(gsl::not_null const factory FLOAT originX, FLOAT originY) noexcept { + RETURN_IF_FAILED(_AnalyzeTextComplexity()); RETURN_IF_FAILED(_AnalyzeRuns()); RETURN_IF_FAILED(_ShapeGlyphRuns()); RETURN_IF_FAILED(_CorrectGlyphRuns()); @@ -119,6 +122,44 @@ CustomTextLayout::CustomTextLayout(gsl::not_null const factory return S_OK; } +// Routine Description: +// - Uses the internal text information and the analyzers/font information from construction +// to determine the complexity of the text. If the text is determined to be entirely simple, +// we'll have more chances to optimize the layout process. +// Arguments: +// - - Uses internal state +// Return Value: +// - S_OK or suitable DirectWrite or STL error code +[[nodiscard]] HRESULT CustomTextLayout::_AnalyzeTextComplexity() noexcept +{ + try + { + const auto textLength = gsl::narrow(_text.size()); + + BOOL isTextSimple = FALSE; + UINT32 uiLengthRead = 0; + + // Start from the beginning. + const UINT32 glyphStart = 0; + + _glyphIndices.resize(textLength); + + const HRESULT hr = _analyzer->GetTextComplexity( + _text.c_str(), + textLength, + _font.Get(), + &isTextSimple, + &uiLengthRead, + &_glyphIndices.at(glyphStart)); + + RETURN_IF_FAILED(hr); + + _isEntireTextSimple = isTextSimple && uiLengthRead == textLength; + } + CATCH_RETURN(); + return S_OK; +} + // Routine Description: // - Uses the internal text information and the analyzers/font information from construction // to determine the complexity of the text inside this layout, compute the subsections (or runs) @@ -149,11 +190,7 @@ CustomTextLayout::CustomTextLayout(gsl::not_null const factory // Allocate enough room to have one breakpoint per code unit. _breakpoints.resize(_text.size()); - BOOL isTextSimple = FALSE; - UINT32 uiLengthRead = 0; - RETURN_IF_FAILED(_analyzer->GetTextComplexity(_text.c_str(), textLength, _font.Get(), &isTextSimple, &uiLengthRead, NULL)); - - if (!(isTextSimple && uiLengthRead == _text.size())) + if (!_isEntireTextSimple) { // Call each of the analyzers in sequence, recording their results. RETURN_IF_FAILED(_analyzer->AnalyzeLineBreakpoints(this, 0, textLength, this)); @@ -274,6 +311,39 @@ CustomTextLayout::CustomTextLayout(gsl::not_null const factory _glyphIndices.resize(totalGlyphsArrayCount); } + if (_isEntireTextSimple) + { + // When the entire text is simple, we can skip GetGlyphs and directly retrieve glyph indices and + // advances(in font design unit). With the help of font metrics, we can calculate the actual glyph + // advances without the need of GetGlyphPlacements. This shortcut will significantly reduce the time + // needed for text analysis. + DWRITE_FONT_METRICS1 metrics; + run.fontFace->GetMetrics(&metrics); + + // With simple text, there's only one run. The actual glyph count is the same as textLength. + _glyphDesignUnitAdvances.resize(textLength); + _glyphAdvances.resize(textLength); + _glyphOffsets.resize(textLength); + + USHORT designUnitsPerEm = metrics.designUnitsPerEm; + + RETURN_IF_FAILED(_font->GetDesignGlyphAdvances( + textLength, + &_glyphIndices.at(glyphStart), + &_glyphDesignUnitAdvances.at(glyphStart), + run.isSideways)); + + for (size_t i = glyphStart; i < _glyphAdvances.size(); i++) + { + _glyphAdvances.at(i) = (float)_glyphDesignUnitAdvances.at(i) / designUnitsPerEm * _format->GetFontSize() * run.fontScale; + } + + run.glyphCount = textLength; + glyphStart += textLength; + + return S_OK; + } + std::vector textProps(textLength); std::vector glyphProps(maxGlyphCount); @@ -371,6 +441,12 @@ CustomTextLayout::CustomTextLayout(gsl::not_null const factory { try { + // For simple text, there is no need to correct runs. + if (_isEntireTextSimple) + { + return S_OK; + } + // Correct each run separately. This is needed whenever script, locale, // or reading direction changes. for (UINT32 runIndex = 0; runIndex < _runs.size(); ++runIndex) @@ -508,9 +584,6 @@ try // We're going to walk through and check for advances that don't match the space that we expect to give out. - DWRITE_FONT_METRICS1 metrics; - run.fontFace->GetMetrics(&metrics); - // Glyph Indices represents the number inside the selected font where the glyph image/paths are found. // Text represents the original text we gave in. // Glyph Clusters represents the map between Text and Glyph Indices. diff --git a/src/renderer/dx/CustomTextLayout.h b/src/renderer/dx/CustomTextLayout.h index 37097227416..f3916419dcc 100644 --- a/src/renderer/dx/CustomTextLayout.h +++ b/src/renderer/dx/CustomTextLayout.h @@ -134,6 +134,7 @@ namespace Microsoft::Console::Render [[nodiscard]] HRESULT STDMETHODCALLTYPE _AnalyzeBoxDrawing(gsl::not_null const source, UINT32 textPosition, UINT32 textLength); [[nodiscard]] HRESULT STDMETHODCALLTYPE _SetBoxEffect(UINT32 textPosition, UINT32 textLength); + [[nodiscard]] HRESULT _AnalyzeTextComplexity() noexcept; [[nodiscard]] HRESULT _AnalyzeRuns() noexcept; [[nodiscard]] HRESULT _ShapeGlyphRuns() noexcept; [[nodiscard]] HRESULT _ShapeGlyphRun(const UINT32 runIndex, UINT32& glyphStart) noexcept; @@ -180,6 +181,9 @@ namespace Microsoft::Console::Render // Glyph shaping results + // Whether the entire text is determined to be simple and does not require full script shaping. + bool _isEntireTextSimple; + std::vector _glyphOffsets; // Clusters are complicated. They're in respect to each individual run. @@ -191,6 +195,9 @@ namespace Microsoft::Console::Render // This appears to be the index of the glyph inside each font. std::vector _glyphIndices; + // This is for calculating glyph advances when the entire text is simple. + std::vector _glyphDesignUnitAdvances; + std::vector _glyphAdvances; struct ScaleCorrection From fc8fff17dbfefbfa38a60dd1239ded3813a8da21 Mon Sep 17 00:00:00 2001 From: Josh Elster Date: Mon, 1 Jun 2020 15:24:43 -0500 Subject: [PATCH 3/8] Add startup task, setting to launch application on login (#4908) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary of the Pull Request This PR adds a new boolean global setting, startOnUserLogin, along with associated AppLogic to request enabling or disabling of the StartupTask. Added UAP5 extensions to AppX manifests. ## References #2189 ## PR Checklist * [x] Closes #2189 * [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA * [x] Tests added/passed * [x] Requires documentation to be updated * [x] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #2189 ## Detailed Description of the Pull Request / Additional comments Please note, I'm a non-practicing C++ developer, there are a number of things I wasn't sure how to handle in the appropriate fashion, mostly around error handling and what probably looks like an incredibly naive (and messy) way to implement the async co_await behavior. Error handling-wise, I found (don't ask me how!) that if you somehow mismatch the startup task's ID between the manifest and the call to `StartupTask::GetAsync(hstring taskId)`, you'll get a very opaque WinRT exception that boils down to a generic invalid argument message. This isn't likely to happen in the wild, but worth mentioning... I had enough trouble getting myself familiarized with the project, environment, and C++/WinRT in general didn't want to try to tackle adding tests for this quite yet since (as I mentioned) I don't really know what I'm doing. I'm happy to give it a try with perhaps a bit of assistance in getting started 😃 Further work in this area of the application outside of this immediate PR might need to include adding an additional setting to contain launch args that the startup task can pass to the app so that users can specify a non-default profile to launch on start, window position (e.g., #653). ## Validation Steps Performed ✔️ Default settings: Given the user does not have the `startOnUserLogin` setting in their profile.json, When the default settings are opened (via alt+click on Settings), Then the global settings should contain the `"startOnUserLogin": false` token ✔️ Applying setting on application launch Given the `startOnUserLogin` is `true` and the `Windows Terminal` startup task is `disabled` and the application is not running When the application is launched Then the `Windows Terminal` entry in the user's Startup list should be `enabled` ✔️ Applying setting on settings change Given the `startOnUserLogin` is `true` and the `Windows Terminal` startup task is `enabled` and the application is running When the `startOnUserLogin` setting is changed to `false` and the settings file is saved to disk Then the `Windows Terminal` startup task entry should be `disabled` ✔️ Setting is ignored when user has manually disabled startup Given the `startOnUserLogin` is `true` and the application is not running and the `Windows Terminal` startup task has been set to `disabled` via user action When the application is launched Then the startup task should remain disabled and the application should not throw an exception #### note: Task Manager does not seem to re-scan startup task states after launch; the Settings -> Apps -> Startup page also requires closing or moving away to refresh the status of entries --- doc/cascadia/SettingsSchema.md | 9 ++++ .../CascadiaPackage/Package-Dev.appxmanifest | 7 ++++ .../CascadiaPackage/Package-Pre.appxmanifest | 8 +++- .../CascadiaPackage/Package.appxmanifest | 7 ++++ src/cascadia/TerminalApp/App.cpp | 1 - src/cascadia/TerminalApp/AppLogic.cpp | 41 +++++++++++++++++++ src/cascadia/TerminalApp/AppLogic.h | 1 + .../TerminalApp/GlobalAppSettings.cpp | 3 ++ src/cascadia/TerminalApp/GlobalAppSettings.h | 2 + src/cascadia/TerminalApp/defaults.json | 1 + src/cascadia/TerminalApp/lib/pch.h | 1 + 11 files changed, 79 insertions(+), 2 deletions(-) diff --git a/doc/cascadia/SettingsSchema.md b/doc/cascadia/SettingsSchema.md index 55a0f7ec616..1354b0fbbb2 100644 --- a/doc/cascadia/SettingsSchema.md +++ b/doc/cascadia/SettingsSchema.md @@ -1,6 +1,7 @@ # Settings.json Documentation ## Globals + Properties listed below affect the entire window, regardless of the profile settings. | Property | Necessity | Type | Default | Description | @@ -21,11 +22,13 @@ Properties listed below affect the entire window, regardless of the profile sett | `tabWidthMode` | Optional | String | `equal` | Sets the width of the tabs. Possible values: `"equal"`, `"titleLength"` | | `wordDelimiters` | Optional | String |  /\()"'-:,.;<>~!@#$%^&*|+=[]{}~?│
_(`│` is `U+2502 BOX DRAWINGS LIGHT VERTICAL`)_ | Determines the delimiters used in a double click selection. | | `confirmCloseAllTabs` | Optional | Boolean | `true` | When set to `true` closing a window with multiple tabs open WILL require confirmation. When set to `false` closing a window with multiple tabs open WILL NOT require confirmation. | +| `startOnUserLogin` | Optional | Boolean | `false` | When set to `true` enables the launch of Windows Terminal at startup. Setting to `false` will disable the startup task entry. Note: if the Windows Terminal startup task entry is disabled either by org policy or by user action this setting will have no effect. | | `disabledProfileSources` | Optional | Array[String] | `[]` | Disables all the dynamic profile generators in this list, preventing them from adding their profiles to the list of profiles on startup. This array can contain any combination of `Windows.Terminal.Wsl`, `Windows.Terminal.Azure`, or `Windows.Terminal.PowershellCore`. For more information, see [UsingJsonSettings.md](https://github.com/microsoft/terminal/blob/master/doc/user-docs/UsingJsonSettings.md#dynamic-profiles) | | `experimental.rendering.forceFullRepaint` | Optional | Boolean | `false` | When set to true, we will redraw the entire screen each frame. When set to false, we will render only the updates to the screen between frames. | | `experimental.rendering.software` | Optional | Boolean | `false` | When set to true, we will use the software renderer (a.k.a. WARP) instead of the hardware one. | ## Profiles + Properties listed below are specific to each unique profile. | Property | Necessity | Type | Default | Description | @@ -64,6 +67,7 @@ Properties listed below are specific to each unique profile. | `experimental.retroTerminalEffect` | Optional | Boolean | `false` | When set to `true`, enable retro terminal effects. This is an experimental feature, and its continued existence is not guaranteed. | ## Schemes + Properties listed below are specific to each color scheme. [ColorTool](https://github.com/microsoft/terminal/tree/master/src/tools/ColorTool) is a great tool you can use to create and explore new color schemes. All colors use hex color format. | Property | Necessity | Type | Description | @@ -91,6 +95,7 @@ Properties listed below are specific to each color scheme. [ColorTool](https://g | `yellow` | _Required_ | String | Sets the color used as ANSI yellow. | ## Keybindings + Properties listed below are specific to each custom key binding. | Property | Necessity | Type | Description | @@ -146,6 +151,7 @@ For commands with arguments: `ctrl+`, `shift+`, `alt+` #### Keys + | Type | Keys | | ---- | ---- | | Function and Alphanumeric Keys | `f1-f24`, `a-z`, `0-9` | @@ -155,6 +161,7 @@ For commands with arguments: | Numpad Keys | `numpad_0-numpad_9`, `numpad0-numpad9`, `numpad_add`, `numpad_plus`, `numpad_decimal`, `numpad_period`, `numpad_divide`, `numpad_minus`, `numpad_subtract`, `numpad_multiply` | ## Background Images and Icons + Some Terminal settings allow you to specify custom background images and icons. It is recommended that custom images and icons are stored in system-provided folders and are referred to using the correct [URI Schemes](https://docs.microsoft.com/en-us/windows/uwp/app-resources/uri-schemes). URI Schemes provide a way to reference files independent of their physical paths (which may change in the future). The most useful URI schemes to remember when customizing background images and icons are: @@ -167,6 +174,7 @@ The most useful URI schemes to remember when customizing background images and i > ⚠ Note: Do not rely on file references using the `ms-appx` URI Scheme (i.e. icons). These files are considered an internal implementation detail and may change name/location or may be omitted in the future. ### Icons + Terminal displays icons for each of your profiles which Terminal generates for any built-in shells - PowerShell Core, PowerShell, and any installed Linux/WSL distros. Each profile refers to a stock icon via the `ms-appx` URI Scheme. > ⚠ Note: Do not rely on the files referenced by the `ms-appx` URI Scheme - they are considered an internal implementation detail and may change name/location or may be omitted in the future. @@ -180,6 +188,7 @@ You can refer to you own icons if you wish, e.g.: > 👉 Tip: Icons should be sized to 32x32px in an appropriate raster image format (e.g. .PNG, .GIF, or .ICO) to avoid having to scale your icons during runtime (causing a noticeable delay and loss of quality.) ### Custom Background Images + You can apply a background image to each of your profiles, allowing you to configure/brand/style each of your profiles independently from one another if you wish. To do so, specify your preferred `backgroundImage`, position it using `backgroundImageAlignment`, set its opacity with `backgroundImageOpacity`, and/or specify how your image fill the available space using `backgroundImageStretchMode`. diff --git a/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest b/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest index da2051a1711..263431693d6 100644 --- a/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest +++ b/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest @@ -11,6 +11,7 @@ xmlns:desktop4="http://schemas.microsoft.com/appx/manifest/desktop/windows10/4" xmlns:desktop5="http://schemas.microsoft.com/appx/manifest/desktop/windows10/5" xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" + xmlns:uap5="http://schemas.microsoft.com/appx/manifest/uap/windows10/5" IgnorableNamespaces="uap mp rescap"> + + + diff --git a/src/cascadia/CascadiaPackage/Package-Pre.appxmanifest b/src/cascadia/CascadiaPackage/Package-Pre.appxmanifest index d38b642dc77..b004722a60b 100644 --- a/src/cascadia/CascadiaPackage/Package-Pre.appxmanifest +++ b/src/cascadia/CascadiaPackage/Package-Pre.appxmanifest @@ -7,6 +7,7 @@ xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3" xmlns:uap4="http://schemas.microsoft.com/appx/manifest/uap/windows10/4" + xmlns:uap5="http://schemas.microsoft.com/appx/manifest/uap/windows10/5" xmlns:uap7="http://schemas.microsoft.com/appx/manifest/uap/windows10/7" xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10" xmlns:desktop4="http://schemas.microsoft.com/appx/manifest/desktop/windows10/4" @@ -63,7 +64,12 @@ - + + + diff --git a/src/cascadia/CascadiaPackage/Package.appxmanifest b/src/cascadia/CascadiaPackage/Package.appxmanifest index 76401a73f17..e6f3a30a0a1 100644 --- a/src/cascadia/CascadiaPackage/Package.appxmanifest +++ b/src/cascadia/CascadiaPackage/Package.appxmanifest @@ -7,6 +7,7 @@ xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3" xmlns:uap4="http://schemas.microsoft.com/appx/manifest/uap/windows10/4" + xmlns:uap5="http://schemas.microsoft.com/appx/manifest/uap/windows10/5" xmlns:uap7="http://schemas.microsoft.com/appx/manifest/uap/windows10/7" xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10" xmlns:desktop4="http://schemas.microsoft.com/appx/manifest/desktop/windows10/4" @@ -63,6 +64,12 @@ + + + diff --git a/src/cascadia/TerminalApp/App.cpp b/src/cascadia/TerminalApp/App.cpp index 2a949461bde..0c7ff464db7 100644 --- a/src/cascadia/TerminalApp/App.cpp +++ b/src/cascadia/TerminalApp/App.cpp @@ -6,7 +6,6 @@ #include "App.g.cpp" using namespace winrt; -using namespace winrt::Windows::ApplicationModel; using namespace winrt::Windows::ApplicationModel::Activation; using namespace winrt::Windows::Foundation; using namespace winrt::Windows::UI::Xaml; diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp index df537db5d4c..68c25645019 100644 --- a/src/cascadia/TerminalApp/AppLogic.cpp +++ b/src/cascadia/TerminalApp/AppLogic.cpp @@ -8,6 +8,7 @@ #include +using namespace winrt::Windows::ApplicationModel; using namespace winrt::Windows::ApplicationModel::DataTransfer; using namespace winrt::Windows::UI::Xaml; using namespace winrt::Windows::UI::Core; @@ -23,6 +24,7 @@ namespace winrt using IInspectable = Windows::Foundation::IInspectable; } +static const winrt::hstring StartupTaskName = L"StartTerminalOnLoginTask"; // clang-format off // !!! IMPORTANT !!! // Make sure that these keys are in the same order as the @@ -243,6 +245,7 @@ namespace winrt::TerminalApp::implementation _root->Create(); _ApplyTheme(_settings->GlobalSettings().GetTheme()); + _ApplyStartupTaskStateChange(); TraceLoggingWrite( g_hTerminalAppProvider, @@ -755,6 +758,43 @@ namespace winrt::TerminalApp::implementation _ApplyTheme(_settings->GlobalSettings().GetTheme()); } + fire_and_forget AppLogic::_ApplyStartupTaskStateChange() + { + auto weakThis{ get_weak() }; + co_await winrt::resume_foreground(_root->Dispatcher(), CoreDispatcherPriority::Normal); + if (auto page{ weakThis.get() }) + { + StartupTaskState state; + bool tryEnableStartupTask = _settings->GlobalSettings().StartOnUserLogin(); + StartupTask task = co_await StartupTask::GetAsync(StartupTaskName); + + state = task.State(); + switch (state) + { + case StartupTaskState::Disabled: + { + if (tryEnableStartupTask) + { + co_await task.RequestEnableAsync(); + } + break; + } + case StartupTaskState::DisabledByUser: + { + // TODO: GH#6254: define UX for other StartupTaskStates + break; + } + case StartupTaskState::Enabled: + { + if (!tryEnableStartupTask) + { + task.Disable(); + } + break; + } + } + } + } // Method Description: // - Reloads the settings from the profile.json. void AppLogic::_ReloadSettings() @@ -783,6 +823,7 @@ namespace winrt::TerminalApp::implementation _root->SetSettings(_settings, true); _RefreshThemeRoutine(); + _ApplyStartupTaskStateChange(); } // Method Description: diff --git a/src/cascadia/TerminalApp/AppLogic.h b/src/cascadia/TerminalApp/AppLogic.h index 767b704248c..07e3204d466 100644 --- a/src/cascadia/TerminalApp/AppLogic.h +++ b/src/cascadia/TerminalApp/AppLogic.h @@ -83,6 +83,7 @@ namespace winrt::TerminalApp::implementation fire_and_forget _LoadErrorsDialogRoutine(); fire_and_forget _ShowLoadWarningsDialogRoutine(); fire_and_forget _RefreshThemeRoutine(); + fire_and_forget _ApplyStartupTaskStateChange(); void _OnLoaded(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs); diff --git a/src/cascadia/TerminalApp/GlobalAppSettings.cpp b/src/cascadia/TerminalApp/GlobalAppSettings.cpp index cdb67c1b81d..74653c2d89d 100644 --- a/src/cascadia/TerminalApp/GlobalAppSettings.cpp +++ b/src/cascadia/TerminalApp/GlobalAppSettings.cpp @@ -42,6 +42,7 @@ static constexpr std::wstring_view FullscreenLaunchModeValue{ L"fullscreen" }; static constexpr std::wstring_view LightThemeValue{ L"light" }; static constexpr std::wstring_view DarkThemeValue{ L"dark" }; static constexpr std::wstring_view SystemThemeValue{ L"system" }; +static constexpr std::string_view EnableStartupTaskKey{ "startOnUserLogin" }; static constexpr std::string_view ForceFullRepaintRenderingKey{ "experimental.rendering.forceFullRepaint" }; static constexpr std::string_view SoftwareRenderingKey{ "experimental.rendering.software" }; @@ -342,6 +343,8 @@ void GlobalAppSettings::LayerJson(const Json::Value& json) // GetBool will only override the current value if the key exists JsonUtils::GetBool(json, DebugFeaturesKey, _debugFeatures); + + JsonUtils::GetBool(json, EnableStartupTaskKey, _StartOnUserLogin); } // Method Description: diff --git a/src/cascadia/TerminalApp/GlobalAppSettings.h b/src/cascadia/TerminalApp/GlobalAppSettings.h index 72bd1cd8c02..2dc35bf7f89 100644 --- a/src/cascadia/TerminalApp/GlobalAppSettings.h +++ b/src/cascadia/TerminalApp/GlobalAppSettings.h @@ -91,6 +91,8 @@ class TerminalApp::GlobalAppSettings final GETSET_PROPERTY(bool, SnapToGridOnResize, true); + GETSET_PROPERTY(bool, StartOnUserLogin, false); + private: GUID _defaultProfile; winrt::com_ptr _keybindings; diff --git a/src/cascadia/TerminalApp/defaults.json b/src/cascadia/TerminalApp/defaults.json index 7e895de93e3..9d2824ccb88 100644 --- a/src/cascadia/TerminalApp/defaults.json +++ b/src/cascadia/TerminalApp/defaults.json @@ -20,6 +20,7 @@ // Miscellaneous "confirmCloseAllTabs": true, + "startOnUserLogin": false, "theme": "system", "rowsToScroll": "system", "snapToGridOnResize": true, diff --git a/src/cascadia/TerminalApp/lib/pch.h b/src/cascadia/TerminalApp/lib/pch.h index 7f53e1e40cd..291021bd4bc 100644 --- a/src/cascadia/TerminalApp/lib/pch.h +++ b/src/cascadia/TerminalApp/lib/pch.h @@ -39,6 +39,7 @@ #include "winrt/Windows.UI.Xaml.Markup.h" #include "winrt/Windows.UI.Xaml.Documents.h" #include "winrt/Windows.UI.Xaml.Automation.h" +#include #include #include From 9ed776bf3e2213431615879581f65f4f6cf2178b Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett (MSFT)" Date: Mon, 1 Jun 2020 13:26:00 -0700 Subject: [PATCH 4/8] Allow the default profile to be specified by name (#5706) ## Summary of the Pull Request This looks like a big diff, but there's a bunch of existing code that just got moved around, and there's a cool new Utils template. The tests all pass, and this passed manual validation. I tried weird things like "making a profile named `{ }`" (w/ enough spaces to look like a guid), and yeah it doesn't let you specify that one as a name, but _why would you do that?!_ Okay, this pull request abstracts the conversion of a profile name into an optional profile guid out of the "New Terminal Tab Args" handler and into a common space for all of CascadiaSettings to use. It also cleans up the conversion of indices and names into optional GUIDs and turns _those_ into further helpers. It also introduces a cool new template for running value_or multiple times on a chain of optionals. CoalesceOptionals is a "choose first, with fallback" for N>1 optionals. On top of all this, I've built support for an "unparsed default GUID": we load the user's defaultProfile as a string, and as part of settings validation we unpack that string using the helpers outlined above. ## References Couples well with #5690. ## PR Checklist * [x] Incidentally fixes #2876 * [x] Core Contributor * [x] Tests added/passed * [x] Requires documentation to be updated (done) * [x] I've discussed this with core contributors already ## Validation Steps Performed Added additional test collateral to make sure that this works. --- doc/cascadia/profiles.schema.json | 4 +- .../LocalTests_TerminalApp/SettingsTests.cpp | 71 +++++--- src/cascadia/TerminalApp/CascadiaSettings.cpp | 155 ++++++++---------- src/cascadia/TerminalApp/CascadiaSettings.h | 5 +- .../CascadiaSettingsSerialization.cpp | 7 +- .../TerminalApp/GlobalAppSettings.cpp | 14 +- src/cascadia/TerminalApp/GlobalAppSettings.h | 4 +- src/types/inc/utils.hpp | 29 ++++ 8 files changed, 174 insertions(+), 115 deletions(-) diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json index 59592b51f64..41c44952bba 100644 --- a/doc/cascadia/profiles.schema.json +++ b/doc/cascadia/profiles.schema.json @@ -290,8 +290,8 @@ "type": "boolean" }, "defaultProfile": { - "$ref": "#/definitions/ProfileGuid", - "description": "Sets the default profile. Opens by clicking the \"+\" icon or typing the key binding assigned to \"newTab\". The \"guid\" of the desired default profile is used as the value." + "description": "Sets the default profile. Opens by clicking the \"+\" icon or typing the key binding assigned to \"newTab\".", + "type": "string" }, "disabledProfileSources": { "description": "Disables all the dynamic profile generators in this list, preventing them from adding their profiles to the list of profiles on startup.", diff --git a/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp b/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp index 612f2b0dd0f..af05a4908fa 100644 --- a/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp +++ b/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp @@ -203,12 +203,28 @@ namespace TerminalAppLocalTests ] })" }; + const std::string goodProfilesSpecifiedByName{ R"( + { + "defaultProfile": "profile1", + "profiles": [ + { + "name" : "profile0", + "guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}" + }, + { + "name" : "profile1", + "guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}" + } + ] + })" }; + { // Case 1: Good settings Log::Comment(NoThrowString().Format( L"Testing a pair of profiles with unique guids, and the defaultProfile is one of those guids")); const auto settingsObject = VerifyParseSucceeded(goodProfiles); auto settings = CascadiaSettings::FromJson(settingsObject); + settings->_ResolveDefaultProfile(); settings->_ValidateDefaultProfileExists(); VERIFY_ARE_EQUAL(static_cast(0), settings->_warnings.size()); VERIFY_ARE_EQUAL(static_cast(2), settings->_profiles.size()); @@ -220,6 +236,7 @@ namespace TerminalAppLocalTests L"Testing a pair of profiles with unique guids, but the defaultProfile is NOT one of those guids")); const auto settingsObject = VerifyParseSucceeded(badProfiles); auto settings = CascadiaSettings::FromJson(settingsObject); + settings->_ResolveDefaultProfile(); settings->_ValidateDefaultProfileExists(); VERIFY_ARE_EQUAL(static_cast(1), settings->_warnings.size()); VERIFY_ARE_EQUAL(::TerminalApp::SettingsLoadWarnings::MissingDefaultProfile, settings->_warnings.at(0)); @@ -233,6 +250,7 @@ namespace TerminalAppLocalTests L"Testing a pair of profiles with unique guids, and no defaultProfile at all")); const auto settingsObject = VerifyParseSucceeded(badProfiles); auto settings = CascadiaSettings::FromJson(settingsObject); + settings->_ResolveDefaultProfile(); settings->_ValidateDefaultProfileExists(); VERIFY_ARE_EQUAL(static_cast(1), settings->_warnings.size()); VERIFY_ARE_EQUAL(::TerminalApp::SettingsLoadWarnings::MissingDefaultProfile, settings->_warnings.at(0)); @@ -240,6 +258,18 @@ namespace TerminalAppLocalTests VERIFY_ARE_EQUAL(static_cast(2), settings->_profiles.size()); VERIFY_ARE_EQUAL(settings->_globals.GetDefaultProfile(), settings->_profiles.at(0).GetGuid()); } + { + // Case 4: Good settings, default profile is a string + Log::Comment(NoThrowString().Format( + L"Testing a pair of profiles with unique guids, and the defaultProfile is one of the profile names")); + const auto settingsObject = VerifyParseSucceeded(goodProfilesSpecifiedByName); + auto settings = CascadiaSettings::FromJson(settingsObject); + settings->_ResolveDefaultProfile(); + settings->_ValidateDefaultProfileExists(); + VERIFY_ARE_EQUAL(static_cast(0), settings->_warnings.size()); + VERIFY_ARE_EQUAL(static_cast(2), settings->_profiles.size()); + VERIFY_ARE_EQUAL(settings->_globals.GetDefaultProfile(), settings->_profiles.at(1).GetGuid()); + } } void SettingsTests::ValidateDuplicateProfiles() @@ -867,7 +897,7 @@ namespace TerminalAppLocalTests VERIFY_IS_TRUE(settings._profiles.at(0)._guid.has_value()); VERIFY_IS_TRUE(settings._profiles.at(1)._guid.has_value()); VERIFY_ARE_EQUAL(L"Windows PowerShell", settings._profiles.at(0)._name); - VERIFY_ARE_EQUAL(L"cmd", settings._profiles.at(1)._name); + VERIFY_ARE_EQUAL(L"Command Prompt", settings._profiles.at(1)._name); settings._ParseJsonString(settings0String, false); settings.LayerJson(settings._userSettings); @@ -968,7 +998,7 @@ namespace TerminalAppLocalTests VERIFY_IS_TRUE(settings._profiles.at(0)._guid.has_value()); VERIFY_IS_TRUE(settings._profiles.at(1)._guid.has_value()); VERIFY_ARE_EQUAL(L"Windows PowerShell", settings._profiles.at(0)._name); - VERIFY_ARE_EQUAL(L"cmd", settings._profiles.at(1)._name); + VERIFY_ARE_EQUAL(L"Command Prompt", settings._profiles.at(1)._name); settings._ParseJsonString(settings0String, false); settings.LayerJson(settings._userSettings); @@ -979,7 +1009,7 @@ namespace TerminalAppLocalTests VERIFY_IS_FALSE(settings._profiles.at(2)._guid.has_value()); VERIFY_IS_TRUE(settings._profiles.at(3)._guid.has_value()); VERIFY_ARE_EQUAL(L"Windows PowerShell", settings._profiles.at(0)._name); - VERIFY_ARE_EQUAL(L"cmd", settings._profiles.at(1)._name); + VERIFY_ARE_EQUAL(L"Command Prompt", settings._profiles.at(1)._name); VERIFY_ARE_EQUAL(L"ThisProfileShouldNotCrash", settings._profiles.at(2)._name); VERIFY_ARE_EQUAL(L"Ubuntu", settings._profiles.at(3)._name); @@ -990,7 +1020,7 @@ namespace TerminalAppLocalTests VERIFY_IS_TRUE(settings._profiles.at(1)._guid.has_value()); VERIFY_IS_TRUE(settings._profiles.at(2)._guid.has_value()); VERIFY_IS_TRUE(settings._profiles.at(3)._guid.has_value()); - VERIFY_ARE_EQUAL(L"cmd", settings._profiles.at(0)._name); + VERIFY_ARE_EQUAL(L"Command Prompt", settings._profiles.at(0)._name); VERIFY_ARE_EQUAL(L"ThisProfileShouldNotCrash", settings._profiles.at(1)._name); VERIFY_ARE_EQUAL(L"Ubuntu", settings._profiles.at(2)._name); VERIFY_ARE_EQUAL(L"Windows PowerShell", settings._profiles.at(3)._name); @@ -1029,7 +1059,7 @@ namespace TerminalAppLocalTests VERIFY_IS_TRUE(settings._profiles.at(0)._guid.has_value()); VERIFY_IS_TRUE(settings._profiles.at(1)._guid.has_value()); VERIFY_ARE_EQUAL(L"Windows PowerShell", settings._profiles.at(0)._name); - VERIFY_ARE_EQUAL(L"cmd", settings._profiles.at(1)._name); + VERIFY_ARE_EQUAL(L"Command Prompt", settings._profiles.at(1)._name); Log::Comment(NoThrowString().Format( L"Parse the user settings")); @@ -1043,7 +1073,7 @@ namespace TerminalAppLocalTests VERIFY_IS_FALSE(settings._profiles.at(3)._guid.has_value()); VERIFY_IS_FALSE(settings._profiles.at(4)._guid.has_value()); VERIFY_ARE_EQUAL(L"Windows PowerShell", settings._profiles.at(0)._name); - VERIFY_ARE_EQUAL(L"cmd", settings._profiles.at(1)._name); + VERIFY_ARE_EQUAL(L"Command Prompt", settings._profiles.at(1)._name); VERIFY_ARE_EQUAL(L"ThisProfileIsGood", settings._profiles.at(2)._name); VERIFY_ARE_EQUAL(L"ThisProfileShouldNotLayer", settings._profiles.at(3)._name); VERIFY_ARE_EQUAL(L"NeitherShouldThisOne", settings._profiles.at(4)._name); @@ -1082,7 +1112,7 @@ namespace TerminalAppLocalTests VERIFY_IS_TRUE(settings._profiles.at(0)._guid.has_value()); VERIFY_IS_TRUE(settings._profiles.at(1)._guid.has_value()); VERIFY_ARE_EQUAL(L"Windows PowerShell", settings._profiles.at(0)._name); - VERIFY_ARE_EQUAL(L"cmd", settings._profiles.at(1)._name); + VERIFY_ARE_EQUAL(L"Command Prompt", settings._profiles.at(1)._name); Log::Comment(NoThrowString().Format( L"Parse the user settings")); @@ -1096,7 +1126,7 @@ namespace TerminalAppLocalTests VERIFY_IS_FALSE(settings._profiles.at(3)._guid.has_value()); VERIFY_IS_FALSE(settings._profiles.at(4)._guid.has_value()); VERIFY_ARE_EQUAL(L"Windows PowerShell", settings._profiles.at(0)._name); - VERIFY_ARE_EQUAL(L"cmd", settings._profiles.at(1)._name); + VERIFY_ARE_EQUAL(L"Command Prompt", settings._profiles.at(1)._name); VERIFY_ARE_EQUAL(L"ThisProfileIsGood", settings._profiles.at(2)._name); VERIFY_ARE_EQUAL(L"ThisProfileShouldNotDuplicate", settings._profiles.at(3)._name); VERIFY_ARE_EQUAL(L"NeitherShouldThisOne", settings._profiles.at(4)._name); @@ -1129,7 +1159,7 @@ namespace TerminalAppLocalTests VERIFY_IS_FALSE(settings2._profiles.at(3)._guid.has_value()); VERIFY_IS_FALSE(settings2._profiles.at(4)._guid.has_value()); VERIFY_ARE_EQUAL(L"Windows PowerShell", settings2._profiles.at(0)._name); - VERIFY_ARE_EQUAL(L"cmd", settings2._profiles.at(1)._name); + VERIFY_ARE_EQUAL(L"Command Prompt", settings2._profiles.at(1)._name); VERIFY_ARE_EQUAL(L"ThisProfileIsGood", settings2._profiles.at(2)._name); VERIFY_ARE_EQUAL(L"ThisProfileShouldNotDuplicate", settings2._profiles.at(3)._name); VERIFY_ARE_EQUAL(L"NeitherShouldThisOne", settings2._profiles.at(4)._name); @@ -1149,7 +1179,7 @@ namespace TerminalAppLocalTests VERIFY_ARE_EQUAL(L"ThisProfileShouldNotDuplicate", settings._profiles.at(1)._name); VERIFY_ARE_EQUAL(L"NeitherShouldThisOne", settings._profiles.at(2)._name); VERIFY_ARE_EQUAL(L"Windows PowerShell", settings._profiles.at(3)._name); - VERIFY_ARE_EQUAL(L"cmd", settings._profiles.at(4)._name); + VERIFY_ARE_EQUAL(L"Command Prompt", settings._profiles.at(4)._name); } void SettingsTests::TestHideAllProfiles() @@ -1318,11 +1348,11 @@ namespace TerminalAppLocalTests settings._ParseJsonString(settings0String, false); settings.LayerJson(settings._userSettings); - VERIFY_ARE_EQUAL(guid0, settings.FindGuid(name0)); - VERIFY_ARE_EQUAL(guid1, settings.FindGuid(name1)); - VERIFY_ARE_EQUAL(guid2, settings.FindGuid(name2)); - VERIFY_ARE_EQUAL(badGuid, settings.FindGuid(name3)); - VERIFY_ARE_EQUAL(badGuid, settings.FindGuid(badName)); + VERIFY_ARE_EQUAL(guid0, settings._GetProfileGuidByName(name0)); + VERIFY_ARE_EQUAL(guid1, settings._GetProfileGuidByName(name1)); + VERIFY_ARE_EQUAL(guid2, settings._GetProfileGuidByName(name2)); + VERIFY_ARE_EQUAL(badGuid, settings._GetProfileGuidByName(name3)); + VERIFY_ARE_EQUAL(badGuid, settings._GetProfileGuidByName(badName)); auto prof0{ settings.FindProfile(guid0) }; auto prof1{ settings.FindProfile(guid1) }; @@ -1475,8 +1505,7 @@ namespace TerminalAppLocalTests })" }; VerifyParseSucceeded(settings0String); - const auto guid1 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}"); - const auto guid2 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}"); + const auto guid1String = L"{6239a42c-1111-49a3-80bd-e8fdd045185c}"; { CascadiaSettings settings{ false }; @@ -1486,7 +1515,7 @@ namespace TerminalAppLocalTests VERIFY_IS_FALSE(settings._userDefaultProfileSettings == Json::Value::null); settings.LayerJson(settings._userSettings); - VERIFY_ARE_EQUAL(guid1, settings._globals._defaultProfile); + VERIFY_ARE_EQUAL(guid1String, settings._globals._unparsedDefaultProfile); VERIFY_ARE_EQUAL(2u, settings._profiles.size()); VERIFY_ARE_EQUAL(2345, settings._profiles.at(0)._historySize); @@ -1522,7 +1551,8 @@ namespace TerminalAppLocalTests })" }; VerifyParseSucceeded(settings0String); - const auto guid1 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}"); + const auto guid1String = L"{6239a42c-1111-49a3-80bd-e8fdd045185c}"; + const auto guid1 = Microsoft::Console::Utils::GuidFromString(guid1String); const auto guid2 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}"); { @@ -1543,7 +1573,7 @@ namespace TerminalAppLocalTests settings.LayerJson(settings._userSettings); - VERIFY_ARE_EQUAL(guid1, settings._globals._defaultProfile); + VERIFY_ARE_EQUAL(guid1String, settings._globals._unparsedDefaultProfile); VERIFY_ARE_EQUAL(4u, settings._profiles.size()); VERIFY_ARE_EQUAL(guid1, settings._profiles.at(2)._guid); @@ -2036,6 +2066,7 @@ namespace TerminalAppLocalTests })" }; const auto settingsJsonObj = VerifyParseSucceeded(settingsString); auto settings = CascadiaSettings::FromJson(settingsJsonObj); + settings->_ResolveDefaultProfile(); const auto guid1 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}"); const auto guid2 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}"); diff --git a/src/cascadia/TerminalApp/CascadiaSettings.cpp b/src/cascadia/TerminalApp/CascadiaSettings.cpp index 767aaa9f546..113d1959416 100644 --- a/src/cascadia/TerminalApp/CascadiaSettings.cpp +++ b/src/cascadia/TerminalApp/CascadiaSettings.cpp @@ -63,34 +63,6 @@ CascadiaSettings::CascadiaSettings(const bool addDynamicProfiles) } } -// Method Description: -// - Finds a GUID associated with the given profile name. If no profile matches -// the profile name, returns a std::nullopt. -// Arguments: -// - profileName: the name of the profile's GUID to return. -// Return Value: -// - the GUID associated with the profile name. -std::optional CascadiaSettings::FindGuid(const std::wstring_view profileName) const noexcept -{ - std::optional profileGuid{}; - - for (const auto& profile : _profiles) - { - if (profileName == profile.GetName()) - { - try - { - profileGuid = profile.GetGuid(); - } - CATCH_LOG(); - - break; - } - } - - return profileGuid; -} - // Method Description: // - Finds a profile that matches the given GUID. If there is no profile in this // settings object that matches, returns nullptr. @@ -191,6 +163,9 @@ void CascadiaSettings::_ValidateSettings() // Then do some validation on the profiles. The order of these does not // terribly matter. _ValidateNoDuplicateProfiles(); + + // Resolve the default profile before we validate that it exists. + _ResolveDefaultProfile(); _ValidateDefaultProfileExists(); // Ensure that all the profile's color scheme names are @@ -245,6 +220,17 @@ void CascadiaSettings::_ValidateProfilesHaveGuid() } } +// Method Description: +// - Resolves the "defaultProfile", which can be a profile name, to a GUID +// and stores it back to the globals. +void CascadiaSettings::_ResolveDefaultProfile() +{ + const auto unparsedDefaultProfile{ GlobalSettings().GetUnparsedDefaultProfile() }; + auto maybeParsedDefaultProfile{ _GetProfileGuidByName(unparsedDefaultProfile) }; + auto defaultProfileGuid{ Utils::CoalesceOptionals(maybeParsedDefaultProfile, GUID{}) }; + GlobalSettings().SetDefaultProfile(defaultProfileGuid); +} + // Method Description: // - Checks if the "defaultProfile" is set to one of the profiles we // actually have. If the value is unset, or the value is set to something that @@ -569,58 +555,66 @@ TerminalSettings CascadiaSettings::BuildSettings(GUID profileGuid) const // - the GUID of the profile corresponding to this combination of index and NewTerminalArgs GUID CascadiaSettings::_GetProfileForArgs(const NewTerminalArgs& newTerminalArgs) const { - std::optional profileIndex{ std::nullopt }; - if (newTerminalArgs && - newTerminalArgs.ProfileIndex() != nullptr) + std::optional profileByIndex, profileByName; + if (newTerminalArgs) { - profileIndex = newTerminalArgs.ProfileIndex().Value(); + if (newTerminalArgs.ProfileIndex() != nullptr) + { + profileByIndex = _GetProfileGuidByIndex(newTerminalArgs.ProfileIndex().Value()); + } + + profileByName = _GetProfileGuidByName(newTerminalArgs.Profile()); } - GUID profileGuid = _GetProfileForIndex(profileIndex); - if (newTerminalArgs) - { - const auto profileString = newTerminalArgs.Profile(); + return Utils::CoalesceOptionals(profileByName, profileByIndex, _globals.GetDefaultProfile()); +} - // First, try and parse the "profile" argument as a GUID. If it's a - // GUID, and the GUID of one of our profiles, then use that as the - // profile GUID instead. If it's not, then try looking it up as a - // name of a profile. If it's still not that, then just ignore it. - if (!profileString.empty()) +// Method Description: +// - Helper to get the GUID of a profile given a name that could be a guid or an actual name. +// Arguments: +// - name: a guid string _or_ the name of a profile +// Return Value: +// - the GUID of the profile corresponding to this name +std::optional CascadiaSettings::_GetProfileGuidByName(const std::wstring_view name) const +try +{ + // First, try and parse the "name" as a GUID. If it's a + // GUID, and the GUID of one of our profiles, then use that as the + // profile GUID instead. If it's not, then try looking it up as a + // name of a profile. If it's still not that, then just ignore it. + if (!name.empty()) + { + // Do a quick heuristic check - is the profile 38 chars long (the + // length of a GUID string), and does it start with '{'? Because if + // it doesn't, it's _definitely_ not a GUID. + if (name.size() == 38 && name[0] == L'{') { - bool wasGuid = false; - - // Do a quick heuristic check - is the profile 38 chars long (the - // length of a GUID string), and does it start with '{'? Because if - // it doesn't, it's _definitely_ not a GUID. - if (profileString.size() == 38 && profileString[0] == L'{') + const auto newGUID{ Utils::GuidFromString(static_cast(name)) }; + if (FindProfile(newGUID)) { - try - { - const auto newGUID = Utils::GuidFromString(profileString.c_str()); - if (FindProfile(newGUID)) - { - profileGuid = newGUID; - wasGuid = true; - } - } - CATCH_LOG(); + return newGUID; } + } - // Here, we were unable to use the profile string as a GUID to - // lookup a profile. Instead, try using the string to look the - // Profile up by name. - if (!wasGuid) - { - const auto guidFromName = FindGuid(profileString.c_str()); - if (guidFromName.has_value()) - { - profileGuid = guidFromName.value(); - } - } + // Here, we were unable to use the profile string as a GUID to + // lookup a profile. Instead, try using the string to look the + // Profile up by name. + const auto profileIterator{ std::find_if(_profiles.cbegin(), _profiles.cend(), [&](auto&& profile) { + return profile.GetName().compare(name) == 0; + }) }; + + if (profileIterator != _profiles.cend()) + { + return profileIterator->GetGuid(); } } - return profileGuid; + return std::nullopt; +} +catch (...) +{ + LOG_CAUGHT_EXCEPTION(); + return std::nullopt; } // Method Description: @@ -633,27 +627,20 @@ GUID CascadiaSettings::_GetProfileForArgs(const NewTerminalArgs& newTerminalArgs // If omitted, instead return the default profile's GUID // Return Value: // - the Nth profile's GUID, or the default profile's GUID -GUID CascadiaSettings::_GetProfileForIndex(std::optional index) const +std::optional CascadiaSettings::_GetProfileGuidByIndex(std::optional index) const { - GUID profileGuid; if (index) { - const auto realIndex = index.value(); + const auto realIndex{ index.value() }; // If we don't have that many profiles, then do nothing. - if (realIndex < 0 || - realIndex >= gsl::narrow_cast(_profiles.size())) + if (realIndex >= 0 && + realIndex < gsl::narrow_cast(_profiles.size())) { - return _globals.GetDefaultProfile(); + const auto& selectedProfile = _profiles.at(realIndex); + return selectedProfile.GetGuid(); } - const auto& selectedProfile = _profiles.at(realIndex); - profileGuid = selectedProfile.GetGuid(); - } - else - { - // get Guid for the default profile - profileGuid = _globals.GetDefaultProfile(); } - return profileGuid; + return std::nullopt; } // Method Description: @@ -715,7 +702,7 @@ std::string CascadiaSettings::_ApplyFirstRunChangesToSettingsTemplate(std::strin } }; std::wstring defaultProfileGuid{ DEFAULT_WINDOWS_POWERSHELL_GUID }; - if (const auto psCoreProfileGuid{ FindGuid(PowershellCoreProfileGenerator::GetPreferredPowershellProfileName()) }) + if (const auto psCoreProfileGuid{ _GetProfileGuidByName(PowershellCoreProfileGenerator::GetPreferredPowershellProfileName()) }) { defaultProfileGuid = Utils::GuidToString(*psCoreProfileGuid); } diff --git a/src/cascadia/TerminalApp/CascadiaSettings.h b/src/cascadia/TerminalApp/CascadiaSettings.h index 6c21d5a3760..8bf65b926bc 100644 --- a/src/cascadia/TerminalApp/CascadiaSettings.h +++ b/src/cascadia/TerminalApp/CascadiaSettings.h @@ -69,7 +69,6 @@ class TerminalApp::CascadiaSettings final static std::filesystem::path GetSettingsPath(); static std::filesystem::path GetDefaultSettingsPath(); - std::optional FindGuid(const std::wstring_view profileName) const noexcept; const Profile* FindProfile(GUID profileGuid) const noexcept; std::vector& GetWarnings(); @@ -106,7 +105,8 @@ class TerminalApp::CascadiaSettings final static std::optional _ReadUserSettings(); static std::optional _ReadFile(HANDLE hFile); - GUID _GetProfileForIndex(std::optional index) const; + std::optional _GetProfileGuidByName(const std::wstring_view) const; + std::optional _GetProfileGuidByIndex(std::optional index) const; GUID _GetProfileForArgs(const winrt::TerminalApp::NewTerminalArgs& newTerminalArgs) const; void _ValidateSettings(); @@ -114,6 +114,7 @@ class TerminalApp::CascadiaSettings final void _ValidateProfilesHaveGuid(); void _ValidateDefaultProfileExists(); void _ValidateNoDuplicateProfiles(); + void _ResolveDefaultProfile(); void _ReorderProfilesToMatchUserSettingsOrder(); void _RemoveHiddenProfiles(); void _ValidateAllSchemesExist(); diff --git a/src/cascadia/TerminalApp/CascadiaSettingsSerialization.cpp b/src/cascadia/TerminalApp/CascadiaSettingsSerialization.cpp index 0c3d815606a..8d3e23f93c6 100644 --- a/src/cascadia/TerminalApp/CascadiaSettingsSerialization.cpp +++ b/src/cascadia/TerminalApp/CascadiaSettingsSerialization.cpp @@ -61,7 +61,7 @@ std::unique_ptr CascadiaSettings::LoadAll() // GH 3588, we need this below to know if the user chose something that wasn't our default. // Collect it up here in case it gets modified by any of the other layers between now and when // the user's preferences are loaded and layered. - const auto hardcodedDefaultGuid = resultPtr->GlobalSettings().GetDefaultProfile(); + const auto hardcodedDefaultGuid = resultPtr->GlobalSettings().GetUnparsedDefaultProfile(); std::optional fileData = _ReadUserSettings(); const bool foundFile = fileData.has_value(); @@ -141,11 +141,12 @@ std::unique_ptr CascadiaSettings::LoadAll() // is a lot of computation we can skip if no one cares. if (TraceLoggingProviderEnabled(g_hTerminalAppProvider, 0, MICROSOFT_KEYWORD_MEASURES)) { - auto guid = resultPtr->GlobalSettings().GetDefaultProfile(); + const auto hardcodedDefaultGuidAsGuid = Utils::GuidFromString(hardcodedDefaultGuid); + const auto guid = resultPtr->GlobalSettings().GetDefaultProfile(); // Compare to the defaults.json one that we set on install. // If it's different, log what the user chose. - if (hardcodedDefaultGuid != guid) + if (hardcodedDefaultGuidAsGuid != guid) { TraceLoggingWrite( g_hTerminalAppProvider, // handle to TerminalApp tracelogging provider diff --git a/src/cascadia/TerminalApp/GlobalAppSettings.cpp b/src/cascadia/TerminalApp/GlobalAppSettings.cpp index 74653c2d89d..03e2d4d6172 100644 --- a/src/cascadia/TerminalApp/GlobalAppSettings.cpp +++ b/src/cascadia/TerminalApp/GlobalAppSettings.cpp @@ -59,6 +59,7 @@ GlobalAppSettings::GlobalAppSettings() : _keybindings{ winrt::make_self() }, _keybindingsWarnings{}, _colorSchemes{}, + _unparsedDefaultProfile{ std::nullopt }, _defaultProfile{}, _alwaysShowTabs{ true }, _confirmCloseAllTabs{ true }, @@ -97,14 +98,22 @@ const std::unordered_map& GlobalAppSettings::GetColor void GlobalAppSettings::SetDefaultProfile(const GUID defaultProfile) noexcept { + _unparsedDefaultProfile.reset(); _defaultProfile = defaultProfile; } -GUID GlobalAppSettings::GetDefaultProfile() const noexcept +GUID GlobalAppSettings::GetDefaultProfile() const { + // If we have an unresolved default profile, we should likely explode. + THROW_HR_IF(E_INVALIDARG, _unparsedDefaultProfile.has_value()); return _defaultProfile; } +std::wstring GlobalAppSettings::GetUnparsedDefaultProfile() const +{ + return _unparsedDefaultProfile.value(); +} + AppKeyBindings GlobalAppSettings::GetKeybindings() const noexcept { return *_keybindings; @@ -268,8 +277,7 @@ void GlobalAppSettings::LayerJson(const Json::Value& json) { if (auto defaultProfile{ json[JsonKey(DefaultProfileKey)] }) { - auto guid = Utils::GuidFromString(GetWstringFromJson(defaultProfile)); - _defaultProfile = guid; + _unparsedDefaultProfile.emplace(GetWstringFromJson(defaultProfile)); } JsonUtils::GetBool(json, AlwaysShowTabsKey, _alwaysShowTabs); diff --git a/src/cascadia/TerminalApp/GlobalAppSettings.h b/src/cascadia/TerminalApp/GlobalAppSettings.h index 2dc35bf7f89..ff0af9c9dc9 100644 --- a/src/cascadia/TerminalApp/GlobalAppSettings.h +++ b/src/cascadia/TerminalApp/GlobalAppSettings.h @@ -40,7 +40,8 @@ class TerminalApp::GlobalAppSettings final void AddColorScheme(ColorScheme scheme); void SetDefaultProfile(const GUID defaultProfile) noexcept; - GUID GetDefaultProfile() const noexcept; + GUID GetDefaultProfile() const; + std::wstring GetUnparsedDefaultProfile() const; winrt::TerminalApp::AppKeyBindings GetKeybindings() const noexcept; @@ -94,6 +95,7 @@ class TerminalApp::GlobalAppSettings final GETSET_PROPERTY(bool, StartOnUserLogin, false); private: + std::optional _unparsedDefaultProfile; GUID _defaultProfile; winrt::com_ptr _keybindings; std::vector<::TerminalApp::SettingsLoadWarnings> _keybindingsWarnings; diff --git a/src/types/inc/utils.hpp b/src/types/inc/utils.hpp index 418cbf2b1d6..9aee7bbc6e7 100644 --- a/src/types/inc/utils.hpp +++ b/src/types/inc/utils.hpp @@ -85,4 +85,33 @@ namespace Microsoft::Console::Utils } GUID CreateV5Uuid(const GUID& namespaceGuid, const gsl::span name); + + // Method Description: + // - Base case provided to handle the last argument to CoalesceOptionals() + template + T CoalesceOptionals(const T& base) + { + return base; + } + + // Method Description: + // - Base case provided to throw an assertion if you call CoalesceOptionals(opt, opt, opt) + template + T CoalesceOptionals(const std::optional& base) + { + static_assert(false, "CoalesceOptionals must be passed a base non-optional value to be used if all optionals are empty"); + return T{}; + } + + // Method Description: + // - Returns the value from the first populated optional, or a base value if none were populated. + template + T CoalesceOptionals(const std::optional& t1, Ts&&... t2) + { + // Initially, I wanted to check "has_value" and short-circuit out so that we didn't + // evaluate value_or for every single optional, but has_value/value emits exception handling + // code that value_or doesn't. Less exception handling is cheaper than calling value_or a + // few more times. + return t1.value_or(CoalesceOptionals(std::forward(t2)...)); + } } From 44dcc861ad8dd6a985b1af1f87e2722f3215e500 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Mon, 1 Jun 2020 13:26:21 -0700 Subject: [PATCH 5/8] Adds Alt+Click to auto split pane (#5928) ## Summary of the Pull Request Users can now open an auto split pane with the mouse. When opening the dropdown, alt+invoke the profile of choice and it should open in an auto sized pane. ## References #5025 - further discussion there as to whether this actually closes it. ## Detailed Description of the Pull Request / Additional comments Had to do a special check for debugTap because that's triggered by holding both alts. ## Validation Steps Performed alt+click/enter on a new profile. Looks great! --- src/cascadia/TerminalApp/TerminalPage.cpp | 24 ++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 750d97a09b8..b45829b7ca5 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -364,7 +364,29 @@ namespace winrt::TerminalApp::implementation { auto newTerminalArgs = winrt::make_self(); newTerminalArgs->ProfileIndex(profileIndex); - page->_OpenNewTab(*newTerminalArgs); + + // if alt is pressed, open a pane + const CoreWindow window = CoreWindow::GetForCurrentThread(); + const auto rAltState = window.GetKeyState(VirtualKey::RightMenu); + const auto lAltState = window.GetKeyState(VirtualKey::LeftMenu); + const bool altPressed = WI_IsFlagSet(lAltState, CoreVirtualKeyStates::Down) || + WI_IsFlagSet(rAltState, CoreVirtualKeyStates::Down); + + // Check for DebugTap + bool debugTap = page->_settings->GlobalSettings().DebugFeaturesEnabled() && + WI_IsFlagSet(lAltState, CoreVirtualKeyStates::Down) && + WI_IsFlagSet(rAltState, CoreVirtualKeyStates::Down); + + if (altPressed && !debugTap) + { + page->_SplitPane(TerminalApp::SplitState::Automatic, + TerminalApp::SplitType::Manual, + *newTerminalArgs); + } + else + { + page->_OpenNewTab(*newTerminalArgs); + } } }); newTabFlyout.Items().Append(profileMenuItem); From d92c8293ce4491755bec19e76504ab4b41c94427 Mon Sep 17 00:00:00 2001 From: James Holderness Date: Mon, 1 Jun 2020 22:20:40 +0100 Subject: [PATCH 6/8] Add support for VT52 emulation (#4789) ## Summary of the Pull Request This PR adds support for the core VT52 commands, and implements the `DECANM` private mode sequence, which switches the terminal between ANSI mode and VT52-compatible mode. ## References PR #2017 defined the initial specification for VT52 support. PR #4044 removed the original VT52 cursor ops that conflicted with VT100 sequences. ## PR Checklist * [x] Closes #976 * [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA * [x] Tests added/passed * [ ] Requires documentation to be updated * [x] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #2017 ## Detailed Description of the Pull Request / Additional comments Most of the work involves updates to the parsing state machine, which behaves differently in VT52 mode. `CSI`, `OSC`, and `SS3` sequences are not applicable, and there is one special-case escape sequence (_Direct Cursor Address_), which requires an additional state to handle parameters that come _after_ the final character. Once the parsing is handled though, it's mostly just a matter of dispatching the commands to existing methods in the `ITermDispatch` interface. Only one new method was required in the interface to handle the _Identify_ command. The only real new functionality is in the `TerminalInput` class, which needs to generate different escape sequences for certain keys in VT52 mode. This does not yet support _all_ of the VT52 key sequences, because the VT100 support is itself not yet complete. But the basics are in place, and I think the rest is best left for a follow-up issue, and potentially a refactor of the `TerminalInput` class. I should point out that the original spec called for a new _Graphic Mode_ character set, but I've since discovered that the VT terminals that _emulate_ VT52 just use the existing VT100 _Special Graphics_ set, so that is really what we should be doing too. We can always consider adding the VT52 graphic set as a option later, if there is demand for strict VT52 compatibility. ## Validation Steps Performed I've added state machine and adapter tests to confirm that the `DECANM` mode changing sequences are correctly dispatched and forwarded to the `ConGetSet` handler. I've also added state machine tests that confirm the VT52 escape sequences are dispatched correctly when the ANSI mode is reset. For fuzzing support, I've extended the VT command fuzzer to generate the different kinds of VT52 sequences, as well as mode change sequences to switch between the ANSI and VT52 modes. In terms of manual testing, I've confirmed that the _Test of VT52 mode_ in Vttest now works as expected. --- src/host/outputStream.cpp | 17 +++ src/host/outputStream.hpp | 1 + src/terminal/adapter/DispatchTypes.hpp | 1 + src/terminal/adapter/ITermDispatch.hpp | 2 + src/terminal/adapter/adaptDispatch.cpp | 30 ++++ src/terminal/adapter/adaptDispatch.hpp | 2 + src/terminal/adapter/conGetSet.hpp | 1 + src/terminal/adapter/termDispatch.hpp | 2 + .../adapter/ut_adapter/adapterTest.cpp | 34 +++++ src/terminal/input/terminalInput.cpp | 68 ++++++++- src/terminal/input/terminalInput.hpp | 2 + src/terminal/parser/IStateMachineEngine.hpp | 3 + .../parser/InputStateMachineEngine.cpp | 18 +++ .../parser/InputStateMachineEngine.hpp | 4 + .../parser/OutputStateMachineEngine.cpp | 83 +++++++++++ .../parser/OutputStateMachineEngine.hpp | 23 +++ .../parser/ft_fuzzer/VTCommandFuzzer.cpp | 31 +++- src/terminal/parser/stateMachine.cpp | 138 +++++++++++++++-- src/terminal/parser/stateMachine.hpp | 10 +- .../parser/ut_parser/OutputEngineTest.cpp | 139 ++++++++++++++++++ .../parser/ut_parser/StateMachineTest.cpp | 4 + 21 files changed, 592 insertions(+), 21 deletions(-) diff --git a/src/host/outputStream.cpp b/src/host/outputStream.cpp index d3b40ffd120..c9c8f7cd2be 100644 --- a/src/host/outputStream.cpp +++ b/src/host/outputStream.cpp @@ -239,6 +239,23 @@ bool ConhostInternalGetSet::PrivateSetKeypadMode(const bool fApplicationMode) return NT_SUCCESS(DoSrvPrivateSetKeypadMode(fApplicationMode)); } +// Routine Description: +// - Sets the terminal emulation mode to either ANSI-compatible or VT52. +// PrivateSetAnsiMode is an internal-only "API" call that the vt commands can execute, +// but it is not represented as a function call on out public API surface. +// Arguments: +// - ansiMode - set to true to enable the ANSI mode, false for VT52 mode. +// Return Value: +// - true if successful. false otherwise. +bool ConhostInternalGetSet::PrivateSetAnsiMode(const bool ansiMode) +{ + auto& stateMachine = _io.GetActiveOutputBuffer().GetStateMachine(); + stateMachine.SetAnsiMode(ansiMode); + auto& terminalInput = _io.GetActiveInputBuffer()->GetTerminalInput(); + terminalInput.ChangeAnsiMode(ansiMode); + return true; +} + // Routine Description: // - Connects the PrivateSetScreenMode call directly into our Driver Message servicing call inside Conhost.exe // PrivateSetScreenMode is an internal-only "API" call that the vt commands can execute, diff --git a/src/host/outputStream.hpp b/src/host/outputStream.hpp index c39047bbdf7..10b73d79253 100644 --- a/src/host/outputStream.hpp +++ b/src/host/outputStream.hpp @@ -74,6 +74,7 @@ class ConhostInternalGetSet final : public Microsoft::Console::VirtualTerminal:: bool PrivateSetCursorKeysMode(const bool applicationMode) override; bool PrivateSetKeypadMode(const bool applicationMode) override; + bool PrivateSetAnsiMode(const bool ansiMode) override; bool PrivateSetScreenMode(const bool reverseMode) override; bool PrivateSetAutoWrapMode(const bool wrapAtEOL) override; diff --git a/src/terminal/adapter/DispatchTypes.hpp b/src/terminal/adapter/DispatchTypes.hpp index f45536cfb45..8917e7269db 100644 --- a/src/terminal/adapter/DispatchTypes.hpp +++ b/src/terminal/adapter/DispatchTypes.hpp @@ -82,6 +82,7 @@ namespace Microsoft::Console::VirtualTerminal::DispatchTypes enum PrivateModeParams : unsigned short { DECCKM_CursorKeysMode = 1, + DECANM_AnsiMode = 2, DECCOLM_SetNumberOfColumns = 3, DECSCNM_ScreenMode = 5, DECOM_OriginMode = 6, diff --git a/src/terminal/adapter/ITermDispatch.hpp b/src/terminal/adapter/ITermDispatch.hpp index e627854ad36..c0aa5ac4301 100644 --- a/src/terminal/adapter/ITermDispatch.hpp +++ b/src/terminal/adapter/ITermDispatch.hpp @@ -54,6 +54,7 @@ class Microsoft::Console::VirtualTerminal::ITermDispatch virtual bool SetCursorKeysMode(const bool applicationMode) = 0; // DECCKM virtual bool SetKeypadMode(const bool applicationMode) = 0; // DECKPAM, DECKPNM virtual bool EnableCursorBlinking(const bool enable) = 0; // ATT610 + virtual bool SetAnsiMode(const bool ansiMode) = 0; // DECANM virtual bool SetScreenMode(const bool reverseMode) = 0; // DECSCNM virtual bool SetOriginMode(const bool relativeMode) = 0; // DECOM virtual bool SetAutoWrapMode(const bool wrapAtEOL) = 0; // DECAWM @@ -92,6 +93,7 @@ class Microsoft::Console::VirtualTerminal::ITermDispatch virtual bool DeviceStatusReport(const DispatchTypes::AnsiStatusType statusType) = 0; // DSR, DSR-OS, DSR-CPR virtual bool DeviceAttributes() = 0; // DA1 + virtual bool Vt52DeviceAttributes() = 0; // VT52 Identify virtual bool DesignateCharset(const wchar_t wchCharset) = 0; // SCS diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp index 65a6fdcf453..336494a0374 100644 --- a/src/terminal/adapter/adaptDispatch.cpp +++ b/src/terminal/adapter/adaptDispatch.cpp @@ -714,6 +714,19 @@ bool AdaptDispatch::DeviceAttributes() return _WriteResponse(L"\x1b[?1;0c"); } +// Routine Description: +// - VT52 Identify - Reports the identity of the terminal in VT52 emulation mode. +// An actual VT52 terminal would typically identify itself with ESC / K. +// But for a terminal that is emulating a VT52, the sequence should be ESC / Z. +// Arguments: +// - +// Return Value: +// - True if handled successfully. False otherwise. +bool AdaptDispatch::Vt52DeviceAttributes() +{ + return _WriteResponse(L"\x1b/Z"); +} + // Routine Description: // - DSR-OS - Reports the operating status back to the input channel // Arguments: @@ -956,6 +969,9 @@ bool AdaptDispatch::_PrivateModeParamsHelper(const DispatchTypes::PrivateModePar // set - Enable Application Mode, reset - Normal mode success = SetCursorKeysMode(enable); break; + case DispatchTypes::PrivateModeParams::DECANM_AnsiMode: + success = SetAnsiMode(enable); + break; case DispatchTypes::PrivateModeParams::DECCOLM_SetNumberOfColumns: success = _DoDECCOLMHelper(enable ? DispatchTypes::s_sDECCOLMSetColumns : DispatchTypes::s_sDECCOLMResetColumns); break; @@ -1129,6 +1145,20 @@ bool AdaptDispatch::DeleteLine(const size_t distance) return _pConApi->DeleteLines(distance); } +// - DECANM - Sets the terminal emulation mode to either ANSI-compatible or VT52. +// Arguments: +// - ansiMode - set to true to enable the ANSI mode, false for VT52 mode. +// Return Value: +// - True if handled successfully. False otherwise. +bool AdaptDispatch::SetAnsiMode(const bool ansiMode) +{ + // When an attempt is made to update the mode, the designated character sets + // need to be reset to defaults, even if the mode doesn't actually change. + _termOutput = {}; + + return _pConApi->PrivateSetAnsiMode(ansiMode); +} + // Routine Description: // - DECSCNM - Sets the screen mode to either normal or reverse. // When in reverse screen mode, the background and foreground colors are switched. diff --git a/src/terminal/adapter/adaptDispatch.hpp b/src/terminal/adapter/adaptDispatch.hpp index a5319d9126b..5f7b51e86a5 100644 --- a/src/terminal/adapter/adaptDispatch.hpp +++ b/src/terminal/adapter/adaptDispatch.hpp @@ -58,6 +58,7 @@ namespace Microsoft::Console::VirtualTerminal bool SetGraphicsRendition(const std::basic_string_view options) override; // SGR bool DeviceStatusReport(const DispatchTypes::AnsiStatusType statusType) override; // DSR, DSR-OS, DSR-CPR bool DeviceAttributes() override; // DA1 + bool Vt52DeviceAttributes() override; // VT52 Identify bool ScrollUp(const size_t distance) override; // SU bool ScrollDown(const size_t distance) override; // SD bool InsertLine(const size_t distance) override; // IL @@ -68,6 +69,7 @@ namespace Microsoft::Console::VirtualTerminal bool SetCursorKeysMode(const bool applicationMode) override; // DECCKM bool SetKeypadMode(const bool applicationMode) override; // DECKPAM, DECKPNM bool EnableCursorBlinking(const bool enable) override; // ATT610 + bool SetAnsiMode(const bool ansiMode) override; // DECANM bool SetScreenMode(const bool reverseMode) override; // DECSCNM bool SetOriginMode(const bool relativeMode) noexcept override; // DECOM bool SetAutoWrapMode(const bool wrapAtEOL) override; // DECAWM diff --git a/src/terminal/adapter/conGetSet.hpp b/src/terminal/adapter/conGetSet.hpp index 36368c67448..79c97b2ebf6 100644 --- a/src/terminal/adapter/conGetSet.hpp +++ b/src/terminal/adapter/conGetSet.hpp @@ -46,6 +46,7 @@ namespace Microsoft::Console::VirtualTerminal virtual bool PrivateSetCursorKeysMode(const bool applicationMode) = 0; virtual bool PrivateSetKeypadMode(const bool applicationMode) = 0; + virtual bool PrivateSetAnsiMode(const bool ansiMode) = 0; virtual bool PrivateSetScreenMode(const bool reverseMode) = 0; virtual bool PrivateSetAutoWrapMode(const bool wrapAtEOL) = 0; diff --git a/src/terminal/adapter/termDispatch.hpp b/src/terminal/adapter/termDispatch.hpp index f3b1e1ccbda..b111210776e 100644 --- a/src/terminal/adapter/termDispatch.hpp +++ b/src/terminal/adapter/termDispatch.hpp @@ -48,6 +48,7 @@ class Microsoft::Console::VirtualTerminal::TermDispatch : public Microsoft::Cons bool SetCursorKeysMode(const bool /*applicationMode*/) noexcept override { return false; } // DECCKM bool SetKeypadMode(const bool /*applicationMode*/) noexcept override { return false; } // DECKPAM, DECKPNM bool EnableCursorBlinking(const bool /*enable*/) noexcept override { return false; } // ATT610 + bool SetAnsiMode(const bool /*ansiMode*/) noexcept override { return false; } // DECANM bool SetScreenMode(const bool /*reverseMode*/) noexcept override { return false; } // DECSCNM bool SetOriginMode(const bool /*relativeMode*/) noexcept override { return false; }; // DECOM bool SetAutoWrapMode(const bool /*wrapAtEOL*/) noexcept override { return false; }; // DECAWM @@ -86,6 +87,7 @@ class Microsoft::Console::VirtualTerminal::TermDispatch : public Microsoft::Cons bool DeviceStatusReport(const DispatchTypes::AnsiStatusType /*statusType*/) noexcept override { return false; } // DSR, DSR-OS, DSR-CPR bool DeviceAttributes() noexcept override { return false; } // DA1 + bool Vt52DeviceAttributes() noexcept override { return false; } // VT52 Identify bool DesignateCharset(const wchar_t /*wchCharset*/) noexcept override { return false; } // SCS diff --git a/src/terminal/adapter/ut_adapter/adapterTest.cpp b/src/terminal/adapter/ut_adapter/adapterTest.cpp index 7b16c3d690d..49eaa3294f0 100644 --- a/src/terminal/adapter/ut_adapter/adapterTest.cpp +++ b/src/terminal/adapter/ut_adapter/adapterTest.cpp @@ -162,6 +162,18 @@ class TestGetSet final : public ConGetSet return _privateSetKeypadModeResult; } + bool PrivateSetAnsiMode(const bool ansiMode) override + { + Log::Comment(L"PrivateSetAnsiMode MOCK called..."); + + if (_privateSetAnsiModeResult) + { + VERIFY_ARE_EQUAL(_expectedAnsiMode, ansiMode); + } + + return _privateSetAnsiModeResult; + } + bool PrivateSetScreenMode(const bool /*reverseMode*/) override { Log::Comment(L"PrivateSetScreenMode MOCK called..."); @@ -744,6 +756,8 @@ class TestGetSet final : public ConGetSet bool _privateSetKeypadModeResult = false; bool _cursorKeysApplicationMode = false; bool _keypadApplicationMode = false; + bool _privateSetAnsiModeResult = false; + bool _expectedAnsiMode = false; bool _privateAllowCursorBlinkingResult = false; bool _enable = false; // for cursor blinking bool _privateSetScrollingRegionResult = false; @@ -1691,6 +1705,26 @@ class AdapterTest VERIFY_IS_TRUE(_pDispatch.get()->SetKeypadMode(true)); } + TEST_METHOD(AnsiModeTest) + { + Log::Comment(L"Starting test..."); + + // success cases + // set ansi mode = true + Log::Comment(L"Test 1: ansi mode = true"); + _testGetSet->_privateSetAnsiModeResult = true; + _testGetSet->_expectedAnsiMode = true; + + VERIFY_IS_TRUE(_pDispatch.get()->SetAnsiMode(true)); + + // set ansi mode = false + Log::Comment(L"Test 2: ansi mode = false."); + _testGetSet->_privateSetAnsiModeResult = true; + _testGetSet->_expectedAnsiMode = false; + + VERIFY_IS_TRUE(_pDispatch.get()->SetAnsiMode(false)); + } + TEST_METHOD(AllowBlinkingTest) { Log::Comment(L"Starting test..."); diff --git a/src/terminal/input/terminalInput.cpp b/src/terminal/input/terminalInput.cpp index d77edca5d60..64d6e28661a 100644 --- a/src/terminal/input/terminalInput.cpp +++ b/src/terminal/input/terminalInput.cpp @@ -68,6 +68,15 @@ static constexpr std::array s_cursorKeysApplicationMapping{ TermKeyMap{ VK_END, L"\x1bOF" }, }; +static constexpr std::array s_cursorKeysVt52Mapping{ + TermKeyMap{ VK_UP, L"\033A" }, + TermKeyMap{ VK_DOWN, L"\033B" }, + TermKeyMap{ VK_RIGHT, L"\033C" }, + TermKeyMap{ VK_LEFT, L"\033D" }, + TermKeyMap{ VK_HOME, L"\033H" }, + TermKeyMap{ VK_END, L"\033F" }, +}; + static constexpr std::array s_keypadNumericMapping{ TermKeyMap{ VK_TAB, L"\x09" }, TermKeyMap{ VK_BACK, L"\x7f" }, @@ -150,6 +159,29 @@ static constexpr std::array s_keypadApplicationMapping{ // TermKeyMap{ VK_TAB, L"\x1bOI" }, // So I left them here as a reference just in case. }; +static constexpr std::array s_keypadVt52Mapping{ + TermKeyMap{ VK_TAB, L"\x09" }, + TermKeyMap{ VK_BACK, L"\x7f" }, + TermKeyMap{ VK_PAUSE, L"\x1a" }, + TermKeyMap{ VK_ESCAPE, L"\x1b" }, + TermKeyMap{ VK_INSERT, L"\x1b[2~" }, + TermKeyMap{ VK_DELETE, L"\x1b[3~" }, + TermKeyMap{ VK_PRIOR, L"\x1b[5~" }, + TermKeyMap{ VK_NEXT, L"\x1b[6~" }, + TermKeyMap{ VK_F1, L"\x1bP" }, + TermKeyMap{ VK_F2, L"\x1bQ" }, + TermKeyMap{ VK_F3, L"\x1bR" }, + TermKeyMap{ VK_F4, L"\x1bS" }, + TermKeyMap{ VK_F5, L"\x1b[15~" }, + TermKeyMap{ VK_F6, L"\x1b[17~" }, + TermKeyMap{ VK_F7, L"\x1b[18~" }, + TermKeyMap{ VK_F8, L"\x1b[19~" }, + TermKeyMap{ VK_F9, L"\x1b[20~" }, + TermKeyMap{ VK_F10, L"\x1b[21~" }, + TermKeyMap{ VK_F11, L"\x1b[23~" }, + TermKeyMap{ VK_F12, L"\x1b[24~" }, +}; + // Sequences to send when a modifier is pressed with any of these keys // Basically, the 'm' will be replaced with a character indicating which // modifier keys are pressed. @@ -220,6 +252,11 @@ const wchar_t* const CTRL_QUESTIONMARK_SEQUENCE = L"\x7F"; const wchar_t* const CTRL_ALT_SLASH_SEQUENCE = L"\x1b\x1f"; const wchar_t* const CTRL_ALT_QUESTIONMARK_SEQUENCE = L"\x1b\x7F"; +void TerminalInput::ChangeAnsiMode(const bool ansiMode) noexcept +{ + _ansiMode = ansiMode; +} + void TerminalInput::ChangeKeypadMode(const bool applicationMode) noexcept { _keypadApplicationMode = applicationMode; @@ -231,29 +268,44 @@ void TerminalInput::ChangeCursorKeysMode(const bool applicationMode) noexcept } static const std::basic_string_view _getKeyMapping(const KeyEvent& keyEvent, + const bool ansiMode, const bool cursorApplicationMode, const bool keypadApplicationMode) noexcept { - if (keyEvent.IsCursorKey()) + if (ansiMode) { - if (cursorApplicationMode) + if (keyEvent.IsCursorKey()) { - return { s_cursorKeysApplicationMapping.data(), s_cursorKeysApplicationMapping.size() }; + if (cursorApplicationMode) + { + return { s_cursorKeysApplicationMapping.data(), s_cursorKeysApplicationMapping.size() }; + } + else + { + return { s_cursorKeysNormalMapping.data(), s_cursorKeysNormalMapping.size() }; + } } else { - return { s_cursorKeysNormalMapping.data(), s_cursorKeysNormalMapping.size() }; + if (keypadApplicationMode) + { + return { s_keypadApplicationMapping.data(), s_keypadApplicationMapping.size() }; + } + else + { + return { s_keypadNumericMapping.data(), s_keypadNumericMapping.size() }; + } } } else { - if (keypadApplicationMode) + if (keyEvent.IsCursorKey()) { - return { s_keypadApplicationMapping.data(), s_keypadApplicationMapping.size() }; + return { s_cursorKeysVt52Mapping.data(), s_cursorKeysVt52Mapping.size() }; } else { - return { s_keypadNumericMapping.data(), s_keypadNumericMapping.size() }; + return { s_keypadVt52Mapping.data(), s_keypadVt52Mapping.size() }; } } } @@ -560,7 +612,7 @@ bool TerminalInput::HandleKey(const IInputEvent* const pInEvent) } // Check any other key mappings (like those for the F1-F12 keys). - const auto mapping = _getKeyMapping(keyEvent, _cursorApplicationMode, _keypadApplicationMode); + const auto mapping = _getKeyMapping(keyEvent, _ansiMode, _cursorApplicationMode, _keypadApplicationMode); if (_translateDefaultMapping(keyEvent, mapping, senderFunc)) { return true; diff --git a/src/terminal/input/terminalInput.hpp b/src/terminal/input/terminalInput.hpp index ebaee780563..00cf92bb949 100644 --- a/src/terminal/input/terminalInput.hpp +++ b/src/terminal/input/terminalInput.hpp @@ -34,6 +34,7 @@ namespace Microsoft::Console::VirtualTerminal ~TerminalInput() = default; bool HandleKey(const IInputEvent* const pInEvent); + void ChangeAnsiMode(const bool ansiMode) noexcept; void ChangeKeypadMode(const bool applicationMode) noexcept; void ChangeCursorKeysMode(const bool applicationMode) noexcept; @@ -67,6 +68,7 @@ namespace Microsoft::Console::VirtualTerminal // storage location for the leading surrogate of a utf-16 surrogate pair std::optional _leadingSurrogate; + bool _ansiMode = true; bool _keypadApplicationMode = false; bool _cursorApplicationMode = false; diff --git a/src/terminal/parser/IStateMachineEngine.hpp b/src/terminal/parser/IStateMachineEngine.hpp index 42cc7275c3a..12904256428 100644 --- a/src/terminal/parser/IStateMachineEngine.hpp +++ b/src/terminal/parser/IStateMachineEngine.hpp @@ -32,6 +32,9 @@ namespace Microsoft::Console::VirtualTerminal virtual bool ActionEscDispatch(const wchar_t wch, const std::basic_string_view intermediates) = 0; + virtual bool ActionVt52EscDispatch(const wchar_t wch, + const std::basic_string_view intermediates, + const std::basic_string_view parameters) = 0; virtual bool ActionCsiDispatch(const wchar_t wch, const std::basic_string_view intermediates, const std::basic_string_view parameters) = 0; diff --git a/src/terminal/parser/InputStateMachineEngine.cpp b/src/terminal/parser/InputStateMachineEngine.cpp index 9f07d418650..882495108ae 100644 --- a/src/terminal/parser/InputStateMachineEngine.cpp +++ b/src/terminal/parser/InputStateMachineEngine.cpp @@ -333,6 +333,24 @@ bool InputStateMachineEngine::ActionEscDispatch(const wchar_t wch, return success; } +// Method Description: +// - Triggers the Vt52EscDispatch action to indicate that the listener should handle +// a VT52 escape sequence. These sequences start with ESC and a single letter, +// sometimes followed by parameters. +// Arguments: +// - wch - Character to dispatch. +// - intermediates - Intermediate characters in the sequence. +// - parameters - Set of parameters collected while parsing the sequence. +// Return Value: +// - true iff we successfully dispatched the sequence. +bool InputStateMachineEngine::ActionVt52EscDispatch(const wchar_t /*wch*/, + const std::basic_string_view /*intermediates*/, + const std::basic_string_view /*parameters*/) noexcept +{ + // VT52 escape sequences are not used in the input state machine. + return false; +} + // Method Description: // - Triggers the CsiDispatch action to indicate that the listener should handle // a control sequence. These sequences perform various API-type commands diff --git a/src/terminal/parser/InputStateMachineEngine.hpp b/src/terminal/parser/InputStateMachineEngine.hpp index a4c1cef366b..4d971b02eda 100644 --- a/src/terminal/parser/InputStateMachineEngine.hpp +++ b/src/terminal/parser/InputStateMachineEngine.hpp @@ -147,6 +147,10 @@ namespace Microsoft::Console::VirtualTerminal bool ActionEscDispatch(const wchar_t wch, const std::basic_string_view intermediates) override; + bool ActionVt52EscDispatch(const wchar_t wch, + const std::basic_string_view intermediates, + const std::basic_string_view parameters) noexcept override; + bool ActionCsiDispatch(const wchar_t wch, const std::basic_string_view intermediates, const std::basic_string_view parameters) override; diff --git a/src/terminal/parser/OutputStateMachineEngine.cpp b/src/terminal/parser/OutputStateMachineEngine.cpp index 74a957c6bb3..c2b13d27d04 100644 --- a/src/terminal/parser/OutputStateMachineEngine.cpp +++ b/src/terminal/parser/OutputStateMachineEngine.cpp @@ -286,6 +286,89 @@ bool OutputStateMachineEngine::ActionEscDispatch(const wchar_t wch, return success; } +// Method Description: +// - Triggers the Vt52EscDispatch action to indicate that the listener should handle +// a VT52 escape sequence. These sequences start with ESC and a single letter, +// sometimes followed by parameters. +// Arguments: +// - wch - Character to dispatch. +// - intermediates - Intermediate characters in the sequence. +// - parameters - Set of parameters collected while parsing the sequence. +// Return Value: +// - true iff we successfully dispatched the sequence. +bool OutputStateMachineEngine::ActionVt52EscDispatch(const wchar_t wch, + const std::basic_string_view intermediates, + const std::basic_string_view parameters) +{ + bool success = false; + + // no intermediates. + if (intermediates.empty()) + { + switch (wch) + { + case Vt52ActionCodes::CursorUp: + success = _dispatch->CursorUp(1); + break; + case Vt52ActionCodes::CursorDown: + success = _dispatch->CursorDown(1); + break; + case Vt52ActionCodes::CursorRight: + success = _dispatch->CursorForward(1); + break; + case Vt52ActionCodes::CursorLeft: + success = _dispatch->CursorBackward(1); + break; + case Vt52ActionCodes::EnterGraphicsMode: + success = _dispatch->DesignateCharset(DispatchTypes::VTCharacterSets::DEC_LineDrawing); + break; + case Vt52ActionCodes::ExitGraphicsMode: + success = _dispatch->DesignateCharset(DispatchTypes::VTCharacterSets::USASCII); + break; + case Vt52ActionCodes::CursorToHome: + success = _dispatch->CursorPosition(1, 1); + break; + case Vt52ActionCodes::ReverseLineFeed: + success = _dispatch->ReverseLineFeed(); + break; + case Vt52ActionCodes::EraseToEndOfScreen: + success = _dispatch->EraseInDisplay(DispatchTypes::EraseType::ToEnd); + break; + case Vt52ActionCodes::EraseToEndOfLine: + success = _dispatch->EraseInLine(DispatchTypes::EraseType::ToEnd); + break; + case Vt52ActionCodes::DirectCursorAddress: + // VT52 cursor addresses are provided as ASCII characters, with + // the lowest value being a space, representing an address of 1. + success = _dispatch->CursorPosition(parameters.at(0) - ' ' + 1, parameters.at(1) - ' ' + 1); + break; + case Vt52ActionCodes::Identify: + success = _dispatch->Vt52DeviceAttributes(); + break; + case Vt52ActionCodes::EnterAlternateKeypadMode: + success = _dispatch->SetKeypadMode(true); + break; + case Vt52ActionCodes::ExitAlternateKeypadMode: + success = _dispatch->SetKeypadMode(false); + break; + case Vt52ActionCodes::ExitVt52Mode: + { + const DispatchTypes::PrivateModeParams mode[] = { DispatchTypes::PrivateModeParams::DECANM_AnsiMode }; + success = _dispatch->SetPrivateModes(mode); + break; + } + default: + // If no functions to call, overall dispatch was a failure. + success = false; + break; + } + } + + _ClearLastChar(); + + return success; +} + // Routine Description: // - Triggers the CsiDispatch action to indicate that the listener should handle // a control sequence. These sequences perform various API-type commands diff --git a/src/terminal/parser/OutputStateMachineEngine.hpp b/src/terminal/parser/OutputStateMachineEngine.hpp index 23ae3922d55..4c31342599e 100644 --- a/src/terminal/parser/OutputStateMachineEngine.hpp +++ b/src/terminal/parser/OutputStateMachineEngine.hpp @@ -36,6 +36,10 @@ namespace Microsoft::Console::VirtualTerminal bool ActionEscDispatch(const wchar_t wch, const std::basic_string_view intermediates) override; + bool ActionVt52EscDispatch(const wchar_t wch, + const std::basic_string_view intermediates, + const std::basic_string_view parameters) override; + bool ActionCsiDispatch(const wchar_t wch, const std::basic_string_view intermediates, const std::basic_string_view parameters) override; @@ -126,6 +130,25 @@ namespace Microsoft::Console::VirtualTerminal DECALN_ScreenAlignmentPattern = L'8' }; + enum Vt52ActionCodes : wchar_t + { + CursorUp = L'A', + CursorDown = L'B', + CursorRight = L'C', + CursorLeft = L'D', + EnterGraphicsMode = L'F', + ExitGraphicsMode = L'G', + CursorToHome = L'H', + ReverseLineFeed = L'I', + EraseToEndOfScreen = L'J', + EraseToEndOfLine = L'K', + DirectCursorAddress = L'Y', + Identify = L'Z', + EnterAlternateKeypadMode = L'=', + ExitAlternateKeypadMode = L'>', + ExitVt52Mode = L'<' + }; + enum OscActionCodes : unsigned int { SetIconAndWindowTitle = 0, diff --git a/src/terminal/parser/ft_fuzzer/VTCommandFuzzer.cpp b/src/terminal/parser/ft_fuzzer/VTCommandFuzzer.cpp index 8e445f40f76..70c823efe12 100644 --- a/src/terminal/parser/ft_fuzzer/VTCommandFuzzer.cpp +++ b/src/terminal/parser/ft_fuzzer/VTCommandFuzzer.cpp @@ -38,6 +38,8 @@ static std::string GenerateOscTitleToken(); static std::string GenerateHardResetToken(); static std::string GenerateSoftResetToken(); static std::string GenerateOscColorTableToken(); +static std::string GenerateVt52Token(); +static std::string GenerateVt52CursorAddressToken(); const fuzz::_fuzz_type_entry g_repeatMap[] = { { 4, [](BYTE) { return CFuzzChance::GetRandom(2, 0xF); } }, @@ -58,7 +60,9 @@ const std::function g_tokenGenerators[] = { GenerateOscTitleToken, GenerateHardResetToken, GenerateSoftResetToken, - GenerateOscColorTableToken + GenerateOscColorTableToken, + GenerateVt52Token, + GenerateVt52CursorAddressToken }; std::string GenerateTokenLowProbability() @@ -302,6 +306,7 @@ std::string GeneratePrivateModeParamToken() const _fuzz_type_entry map[] = { { 12, [](std::string) { std::string s; AppendFormat(s, "?%02d", CFuzzChance::GetRandom()); return s; } }, { 8, [](std::string) { return std::string("?1"); } }, + { 8, [](std::string) { return std::string("?2"); } }, { 8, [](std::string) { return std::string("?3"); } }, { 8, [](std::string) { return std::string("?12"); } }, { 8, [](std::string) { return std::string("?25"); } }, @@ -505,6 +510,30 @@ std::string GenerateOscColorTableToken() return GenerateFuzzedOscToken(FUZZ_MAP(map), tokens, ARRAYSIZE(tokens)); } +// VT52 sequences without parameters. +std::string GenerateVt52Token() +{ + const LPSTR tokens[] = { "A", "B", "C", "D", "F", "G", "H", "I", "J", "K", "Z", "<" }; + std::string cux(ESC); + cux += GenerateTokenLowProbability(); + cux += CFuzzChance::SelectOne(tokens, ARRAYSIZE(tokens)); + return cux; +} + +// VT52 direct cursor address sequence with parameters. +std::string GenerateVt52CursorAddressToken() +{ + const LPSTR tokens[] = { "Y" }; + std::string cux(ESC); + cux += GenerateTokenLowProbability(); + cux += CFuzzChance::SelectOne(tokens, ARRAYSIZE(tokens)); + cux += GenerateTokenLowProbability(); + AppendFormat(cux, "%c", CFuzzChance::GetRandom(32, 255)); + cux += GenerateTokenLowProbability(); + AppendFormat(cux, "%c", CFuzzChance::GetRandom(32, 255)); + return cux; +} + int __cdecl wmain(int argc, WCHAR* argv[]) { if (argc != 3) diff --git a/src/terminal/parser/stateMachine.cpp b/src/terminal/parser/stateMachine.cpp index 4e360e8cd40..d39433da098 100644 --- a/src/terminal/parser/stateMachine.cpp +++ b/src/terminal/parser/stateMachine.cpp @@ -14,6 +14,7 @@ StateMachine::StateMachine(std::unique_ptr engine) : _engine(std::move(engine)), _state(VTStates::Ground), _trace(Microsoft::Console::VirtualTerminal::ParserTracing()), + _isInAnsiMode(true), _intermediates{}, _parameters{}, _oscString{}, @@ -23,6 +24,11 @@ StateMachine::StateMachine(std::unique_ptr engine) : _ActionClear(); } +void StateMachine::SetAnsiMode(bool ansiMode) noexcept +{ + _isInAnsiMode = ansiMode; +} + const IStateMachineEngine& StateMachine::Engine() const noexcept { return *_engine; @@ -197,6 +203,19 @@ static constexpr bool _isSs3Indicator(const wchar_t wch) noexcept return wch == L'O'; // 0x4F } +// Routine Description: +// - Determines if a character is the VT52 "Direct Cursor Address" command. +// This immediately follows an escape and signifies the start of a multiple +// character command sequence. +// Arguments: +// - wch - Character to check. +// Return Value: +// - True if it is. False if it isn't. +static constexpr bool _isVt52CursorAddress(const wchar_t wch) noexcept +{ + return wch == L'Y'; // 0x59 +} + // Routine Description: // - Determines if a character is a "Single Shift Select" indicator. // This immediately follows an escape and signifies a varying length control string. @@ -346,6 +365,31 @@ void StateMachine::_ActionEscDispatch(const wchar_t wch) } } +// Routine Description: +// - Triggers the Vt52EscDispatch action to indicate that the listener should handle a VT52 escape sequence. +// These sequences start with ESC and a single letter, sometimes followed by parameters. +// Arguments: +// - wch - Character to dispatch. +// Return Value: +// - +void StateMachine::_ActionVt52EscDispatch(const wchar_t wch) +{ + _trace.TraceOnAction(L"Vt52EscDispatch"); + + const bool success = _engine->ActionVt52EscDispatch(wch, + { _intermediates.data(), _intermediates.size() }, + { _parameters.data(), _parameters.size() }); + + // Trace the result. + _trace.DispatchSequenceTrace(success); + + if (!success) + { + // Suppress it and log telemetry on failed cases + TermTelemetry::Instance().LogFailed(wch); + } +} + // Routine Description: // - Triggers the CsiDispatch action to indicate that the listener should handle a control sequence. // These sequences perform various API-type commands that can include many parameters. @@ -699,6 +743,20 @@ void StateMachine::_EnterSs3Param() noexcept _trace.TraceStateChange(L"Ss3Param"); } +// Routine Description: +// - Moves the state machine into the VT52Param state. +// This state is entered: +// 1. When a VT52 Cursor Address escape is detected, so parameters are expected to follow. +// Arguments: +// - +// Return Value: +// - +void StateMachine::_EnterVt52Param() noexcept +{ + _state = VTStates::Vt52Param; + _trace.TraceStateChange(L"Vt52Param"); +} + // Routine Description: // - Processes a character event into an Action that occurs while in the Ground state. // Events in this state will: @@ -716,7 +774,7 @@ void StateMachine::_EventGround(const wchar_t wch) { _ActionExecute(wch); } - else if (_isC1Csi(wch)) + else if (_isC1Csi(wch) && _isInAnsiMode) { _EnterCsiEntry(); } @@ -770,21 +828,33 @@ void StateMachine::_EventEscape(const wchar_t wch) _EnterEscapeIntermediate(); } } - else if (_isCsiIndicator(wch)) + else if (_isInAnsiMode) { - _EnterCsiEntry(); - } - else if (_isOscIndicator(wch)) - { - _EnterOscParam(); + if (_isCsiIndicator(wch)) + { + _EnterCsiEntry(); + } + else if (_isOscIndicator(wch)) + { + _EnterOscParam(); + } + else if (_isSs3Indicator(wch)) + { + _EnterSs3Entry(); + } + else + { + _ActionEscDispatch(wch); + _EnterGround(); + } } - else if (_isSs3Indicator(wch)) + else if (_isVt52CursorAddress(wch)) { - _EnterSs3Entry(); + _EnterVt52Param(); } else { - _ActionEscDispatch(wch); + _ActionVt52EscDispatch(wch); _EnterGround(); } } @@ -815,11 +885,20 @@ void StateMachine::_EventEscapeIntermediate(const wchar_t wch) { _ActionIgnore(); } - else + else if (_isInAnsiMode) { _ActionEscDispatch(wch); _EnterGround(); } + else if (_isVt52CursorAddress(wch)) + { + _EnterVt52Param(); + } + else + { + _ActionVt52EscDispatch(wch); + _EnterGround(); + } } // Routine Description: @@ -1156,6 +1235,41 @@ void StateMachine::_EventSs3Param(const wchar_t wch) } } +// Routine Description: +// - Processes a character event into an Action that occurs while in the Vt52Param state. +// Events in this state will: +// 1. Execute C0 control characters +// 2. Ignore Delete characters +// 3. Store exactly two parameter characters +// 4. Dispatch a control sequence with parameters for action (always Direct Cursor Address) +// Arguments: +// - wch - Character that triggered the event +// Return Value: +// - +void StateMachine::_EventVt52Param(const wchar_t wch) +{ + _trace.TraceOnEvent(L"Vt52Param"); + if (_isC0Code(wch)) + { + _ActionExecute(wch); + } + else if (_isDelete(wch)) + { + _ActionIgnore(); + } + else + { + _parameters.push_back(wch); + if (_parameters.size() == 2) + { + // The command character is processed before the parameter values, + // but it will always be 'Y', the Direct Cursor Address command. + _ActionVt52EscDispatch(L'Y'); + _EnterGround(); + } + } +} + // Routine Description: // - Entry to the state machine. Takes characters one by one and processes them according to the state machine rules. // Arguments: @@ -1213,6 +1327,8 @@ void StateMachine::ProcessCharacter(const wchar_t wch) return _EventSs3Entry(wch); case VTStates::Ss3Param: return _EventSs3Param(wch); + case VTStates::Vt52Param: + return _EventVt52Param(wch); default: return; } diff --git a/src/terminal/parser/stateMachine.hpp b/src/terminal/parser/stateMachine.hpp index 5735e75175a..dfa66e5ec1e 100644 --- a/src/terminal/parser/stateMachine.hpp +++ b/src/terminal/parser/stateMachine.hpp @@ -37,6 +37,8 @@ namespace Microsoft::Console::VirtualTerminal public: StateMachine(std::unique_ptr engine); + void SetAnsiMode(bool ansiMode) noexcept; + void ProcessCharacter(const wchar_t wch); void ProcessString(const std::wstring_view string); @@ -52,6 +54,7 @@ namespace Microsoft::Console::VirtualTerminal void _ActionExecuteFromEscape(const wchar_t wch); void _ActionPrint(const wchar_t wch); void _ActionEscDispatch(const wchar_t wch); + void _ActionVt52EscDispatch(const wchar_t wch); void _ActionCollect(const wchar_t wch); void _ActionParam(const wchar_t wch); void _ActionCsiDispatch(const wchar_t wch); @@ -75,6 +78,7 @@ namespace Microsoft::Console::VirtualTerminal void _EnterOscTermination() noexcept; void _EnterSs3Entry(); void _EnterSs3Param() noexcept; + void _EnterVt52Param() noexcept; void _EventGround(const wchar_t wch); void _EventEscape(const wchar_t wch); @@ -88,6 +92,7 @@ namespace Microsoft::Console::VirtualTerminal void _EventOscTermination(const wchar_t wch); void _EventSs3Entry(const wchar_t wch); void _EventSs3Param(const wchar_t wch); + void _EventVt52Param(const wchar_t wch); void _AccumulateTo(const wchar_t wch, size_t& value) noexcept; @@ -104,7 +109,8 @@ namespace Microsoft::Console::VirtualTerminal OscString, OscTermination, Ss3Entry, - Ss3Param + Ss3Param, + Vt52Param }; Microsoft::Console::VirtualTerminal::ParserTracing _trace; @@ -113,6 +119,8 @@ namespace Microsoft::Console::VirtualTerminal VTStates _state; + bool _isInAnsiMode; + std::wstring_view _run; std::vector _intermediates; diff --git a/src/terminal/parser/ut_parser/OutputEngineTest.cpp b/src/terminal/parser/ut_parser/OutputEngineTest.cpp index 6884a943223..c6871a99c3e 100644 --- a/src/terminal/parser/ut_parser/OutputEngineTest.cpp +++ b/src/terminal/parser/ut_parser/OutputEngineTest.cpp @@ -675,9 +675,11 @@ class StatefulDispatch final : public TermDispatch _statusReportType{ (DispatchTypes::AnsiStatusType)-1 }, _deviceStatusReport{ false }, _deviceAttributes{ false }, + _vt52DeviceAttributes{ false }, _isAltBuffer{ false }, _cursorKeysMode{ false }, _cursorBlinking{ true }, + _isInAnsiMode{ true }, _isScreenModeReversed{ false }, _isOriginModeRelative{ false }, _isAutoWrapEnabled{ true }, @@ -685,6 +687,7 @@ class StatefulDispatch final : public TermDispatch _carriageReturn{ false }, _lineFeed{ false }, _lineFeedType{ (DispatchTypes::LineFeedType)-1 }, + _reverseLineFeed{ false }, _forwardTab{ false }, _numTabs{ 0 }, _isDECCOLMAllowed{ false }, @@ -847,6 +850,13 @@ class StatefulDispatch final : public TermDispatch return true; } + bool Vt52DeviceAttributes() noexcept override + { + _vt52DeviceAttributes = true; + + return true; + } + bool _PrivateModeParamsHelper(_In_ DispatchTypes::PrivateModeParams const param, const bool fEnable) { bool fSuccess = false; @@ -856,6 +866,9 @@ class StatefulDispatch final : public TermDispatch // set - Enable Application Mode, reset - Numeric/normal mode fSuccess = SetVirtualTerminalInputMode(fEnable); break; + case DispatchTypes::PrivateModeParams::DECANM_AnsiMode: + fSuccess = SetAnsiMode(fEnable); + break; case DispatchTypes::PrivateModeParams::DECCOLM_SetNumberOfColumns: fSuccess = SetColumns(static_cast(fEnable ? DispatchTypes::s_sDECCOLMSetColumns : DispatchTypes::s_sDECCOLMResetColumns)); break; @@ -928,6 +941,12 @@ class StatefulDispatch final : public TermDispatch return true; } + bool SetAnsiMode(const bool ansiMode) noexcept override + { + _isInAnsiMode = ansiMode; + return true; + } + bool SetScreenMode(const bool reverseMode) noexcept override { _isScreenModeReversed = reverseMode; @@ -965,6 +984,12 @@ class StatefulDispatch final : public TermDispatch return true; } + bool ReverseLineFeed() noexcept override + { + _reverseLineFeed = true; + return true; + } + bool ForwardTab(const size_t numTabs) noexcept override { _forwardTab = true; @@ -1016,9 +1041,11 @@ class StatefulDispatch final : public TermDispatch DispatchTypes::AnsiStatusType _statusReportType; bool _deviceStatusReport; bool _deviceAttributes; + bool _vt52DeviceAttributes; bool _isAltBuffer; bool _cursorKeysMode; bool _cursorBlinking; + bool _isInAnsiMode; bool _isScreenModeReversed; bool _isOriginModeRelative; bool _isAutoWrapEnabled; @@ -1026,6 +1053,7 @@ class StatefulDispatch final : public TermDispatch bool _carriageReturn; bool _lineFeed; DispatchTypes::LineFeedType _lineFeedType; + bool _reverseLineFeed; bool _forwardTab; size_t _numTabs; bool _isDECCOLMAllowed; @@ -1299,6 +1327,26 @@ class StateMachineExternalTest final pDispatch->ClearState(); } + TEST_METHOD(TestAnsiMode) + { + auto dispatch = std::make_unique(); + auto pDispatch = dispatch.get(); + auto engine = std::make_unique(std::move(dispatch)); + StateMachine mach(std::move(engine)); + + mach.ProcessString(L"\x1b[?2l"); + VERIFY_IS_FALSE(pDispatch->_isInAnsiMode); + + pDispatch->ClearState(); + pDispatch->_isInAnsiMode = false; + mach.SetAnsiMode(false); + + mach.ProcessString(L"\x1b<"); + VERIFY_IS_TRUE(pDispatch->_isInAnsiMode); + + pDispatch->ClearState(); + } + TEST_METHOD(TestSetNumberOfColumns) { auto dispatch = std::make_unique(); @@ -2011,4 +2059,95 @@ class StateMachineExternalTest final pDispatch->ClearState(); } + + TEST_METHOD(TestVt52Sequences) + { + auto dispatch = std::make_unique(); + auto pDispatch = dispatch.get(); + auto engine = std::make_unique(std::move(dispatch)); + StateMachine mach(std::move(engine)); + + // ANSI mode must be reset for VT52 sequences to be recognized. + mach.SetAnsiMode(false); + + Log::Comment(L"Cursor Up"); + mach.ProcessCharacter(AsciiChars::ESC); + mach.ProcessCharacter(L'A'); + VERIFY_IS_TRUE(pDispatch->_cursorUp); + VERIFY_ARE_EQUAL(1u, pDispatch->_cursorDistance); + + pDispatch->ClearState(); + + Log::Comment(L"Cursor Down"); + mach.ProcessCharacter(AsciiChars::ESC); + mach.ProcessCharacter(L'B'); + VERIFY_IS_TRUE(pDispatch->_cursorDown); + VERIFY_ARE_EQUAL(1u, pDispatch->_cursorDistance); + + pDispatch->ClearState(); + + Log::Comment(L"Cursor Right"); + mach.ProcessCharacter(AsciiChars::ESC); + mach.ProcessCharacter(L'C'); + VERIFY_IS_TRUE(pDispatch->_cursorForward); + VERIFY_ARE_EQUAL(1u, pDispatch->_cursorDistance); + + pDispatch->ClearState(); + + Log::Comment(L"Cursor Left"); + mach.ProcessCharacter(AsciiChars::ESC); + mach.ProcessCharacter(L'D'); + VERIFY_IS_TRUE(pDispatch->_cursorBackward); + VERIFY_ARE_EQUAL(1u, pDispatch->_cursorDistance); + + pDispatch->ClearState(); + + Log::Comment(L"Cursor to Home"); + mach.ProcessCharacter(AsciiChars::ESC); + mach.ProcessCharacter(L'H'); + VERIFY_IS_TRUE(pDispatch->_cursorPosition); + VERIFY_ARE_EQUAL(1u, pDispatch->_line); + VERIFY_ARE_EQUAL(1u, pDispatch->_column); + + pDispatch->ClearState(); + + Log::Comment(L"Reverse Line Feed"); + mach.ProcessCharacter(AsciiChars::ESC); + mach.ProcessCharacter(L'I'); + VERIFY_IS_TRUE(pDispatch->_reverseLineFeed); + + pDispatch->ClearState(); + + Log::Comment(L"Erase to End of Screen"); + mach.ProcessCharacter(AsciiChars::ESC); + mach.ProcessCharacter(L'J'); + VERIFY_IS_TRUE(pDispatch->_eraseDisplay); + VERIFY_ARE_EQUAL(DispatchTypes::EraseType::ToEnd, pDispatch->_eraseType); + + pDispatch->ClearState(); + + Log::Comment(L"Erase to End of Line"); + mach.ProcessCharacter(AsciiChars::ESC); + mach.ProcessCharacter(L'K'); + VERIFY_IS_TRUE(pDispatch->_eraseLine); + VERIFY_ARE_EQUAL(DispatchTypes::EraseType::ToEnd, pDispatch->_eraseType); + + pDispatch->ClearState(); + + Log::Comment(L"Direct Cursor Address"); + mach.ProcessCharacter(AsciiChars::ESC); + mach.ProcessCharacter(L'Y'); + mach.ProcessCharacter(L' ' + 3); // Coordinates must be printable ASCII values, + mach.ProcessCharacter(L' ' + 5); // so are relative to 0x20 (the space character). + VERIFY_IS_TRUE(pDispatch->_cursorPosition); + VERIFY_ARE_EQUAL(3u, pDispatch->_line - 1); // CursorPosition coordinates are 1-based, + VERIFY_ARE_EQUAL(5u, pDispatch->_column - 1); // so are 1 more than the expected values. + + pDispatch->ClearState(); + + Log::Comment(L"Identify Device"); + mach.ProcessCharacter(AsciiChars::ESC); + mach.ProcessCharacter(L'Z'); + VERIFY_IS_TRUE(pDispatch->_vt52DeviceAttributes); + } }; diff --git a/src/terminal/parser/ut_parser/StateMachineTest.cpp b/src/terminal/parser/ut_parser/StateMachineTest.cpp index 77f5395f386..de6ce92b6eb 100644 --- a/src/terminal/parser/ut_parser/StateMachineTest.cpp +++ b/src/terminal/parser/ut_parser/StateMachineTest.cpp @@ -53,6 +53,10 @@ class Microsoft::Console::VirtualTerminal::TestStateMachineEngine : public IStat bool ActionEscDispatch(const wchar_t /* wch */, const std::basic_string_view /* intermediates */) override { return true; }; + bool ActionVt52EscDispatch(const wchar_t /*wch*/, + const std::basic_string_view /*intermediates*/, + const std::basic_string_view /*parameters*/) override { return true; }; + bool ActionClear() override { return true; }; bool ActionIgnore() override { return true; }; From 8987486e85ceb9514067918e982fec293dba669d Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Mon, 1 Jun 2020 16:57:30 -0500 Subject: [PATCH 7/8] Add support for `--fullscreen`, `--maximized` (#6139) ## Summary of the Pull Request Adds two new flags to the `wt.exe` alias: * `--maximized,-M`: Launch the new Terminal window maximized. This flag cannot be combined with `--fullscreen`. * `--fullscreen,-F`: Launch the new Terminal window fullscreen. This flag cannot be combined with `--maximized`. ## References * This builds on the work done in #6060. * The cmdline args megathread: #4632 ## PR Checklist * [x] Closes #5801 * [x] I work here * [ ] Tests added/passed * [n/a] Requires documentation to be updated ## Detailed Description of the Pull Request / Additional comments * I had to move the commandline arg parsing up a layer from `TerminalPage` to `AppLogic`, because `AppLogic` controls the Terminal's settings, including launch mode settings. This seems like a reasonable change, to put both the settings from the file and the commandline in the same place. - **Most of the diff is that movement of code** * _"What happens when you try to pass both flags, like `wtd -M -F new-tab`?"_: ![image](https://user-images.githubusercontent.com/18356694/82679939-3cffde00-9c11-11ea-8d88-03ec7db83e59.png) ## Validation Steps Performed * Ran a bunch of commandlines to see what happened. --- .../TerminalApp/AppActionHandlers.cpp | 2 +- .../TerminalApp/AppCommandlineArgs.cpp | 24 ++++ src/cascadia/TerminalApp/AppCommandlineArgs.h | 4 + src/cascadia/TerminalApp/AppLogic.cpp | 122 ++++++++++++++++-- src/cascadia/TerminalApp/AppLogic.h | 3 + .../Resources/en-US/Resources.resw | 6 + src/cascadia/TerminalApp/TerminalPage.cpp | 113 ++-------------- src/cascadia/TerminalApp/TerminalPage.h | 11 +- src/cascadia/TerminalApp/TerminalPage.idl | 4 - 9 files changed, 160 insertions(+), 129 deletions(-) diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp index d0e1c027f7f..0bf63758157 100644 --- a/src/cascadia/TerminalApp/AppActionHandlers.cpp +++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp @@ -232,7 +232,7 @@ namespace winrt::TerminalApp::implementation void TerminalPage::_HandleToggleFullscreen(const IInspectable& /*sender*/, const TerminalApp::ActionEventArgs& args) { - _ToggleFullscreen(); + ToggleFullscreen(); args.Handled(true); } } diff --git a/src/cascadia/TerminalApp/AppCommandlineArgs.cpp b/src/cascadia/TerminalApp/AppCommandlineArgs.cpp index 0d8dbec1a71..62116961957 100644 --- a/src/cascadia/TerminalApp/AppCommandlineArgs.cpp +++ b/src/cascadia/TerminalApp/AppCommandlineArgs.cpp @@ -157,6 +157,7 @@ int AppCommandlineArgs::_handleExit(const CLI::App& command, const CLI::Error& e // - void AppCommandlineArgs::_buildParser() { + // -v,--version: Displays version info auto versionCallback = [this](int64_t /*count*/) { if (const auto appLogic{ winrt::TerminalApp::implementation::AppLogic::Current() }) { @@ -173,6 +174,20 @@ void AppCommandlineArgs::_buildParser() }; _app.add_flag_function("-v,--version", versionCallback, RS_A(L"CmdVersionDesc")); + // Maximized and Fullscreen flags + // -M,--maximized: Maximizes the window on launch + // -F,--fullscreen: Fullscreens the window on launch + auto maximizedCallback = [this](int64_t /*count*/) { + _launchMode = winrt::TerminalApp::LaunchMode::MaximizedMode; + }; + auto fullscreenCallback = [this](int64_t /*count*/) { + _launchMode = winrt::TerminalApp::LaunchMode::FullscreenMode; + }; + auto maximized = _app.add_flag_function("-M,--maximized", maximizedCallback, RS_A(L"CmdMaximizedDesc")); + auto fullscreen = _app.add_flag_function("-F,--fullscreen", fullscreenCallback, RS_A(L"CmdFullscreenDesc")); + maximized->excludes(fullscreen); + + // Subcommands _buildNewTabParser(); _buildSplitPaneParser(); _buildFocusTabParser(); @@ -410,6 +425,10 @@ void AppCommandlineArgs::_resetStateToDefault() _focusTabIndex = -1; _focusNextTab = false; _focusPrevTab = false; + + // DON'T clear _launchMode here! This will get called once for every + // subcommand, so we don't want `wt -F new-tab ; split-pane` clearing out + // the "global" fullscreen flag (-F). } // Function Description: @@ -604,3 +623,8 @@ void AppCommandlineArgs::ValidateStartupCommands() _startupActions.push_front(*newTabAction); } } + +std::optional AppCommandlineArgs::GetLaunchMode() const noexcept +{ + return _launchMode; +} diff --git a/src/cascadia/TerminalApp/AppCommandlineArgs.h b/src/cascadia/TerminalApp/AppCommandlineArgs.h index 1a46d644b8a..268ce55a037 100644 --- a/src/cascadia/TerminalApp/AppCommandlineArgs.h +++ b/src/cascadia/TerminalApp/AppCommandlineArgs.h @@ -38,6 +38,8 @@ class TerminalApp::AppCommandlineArgs final const std::string& GetExitMessage(); bool ShouldExitEarly() const noexcept; + std::optional GetLaunchMode() const noexcept; + private: static const std::wregex _commandDelimiterRegex; @@ -76,6 +78,8 @@ class TerminalApp::AppCommandlineArgs final int _focusTabIndex{ -1 }; bool _focusNextTab{ false }; bool _focusPrevTab{ false }; + + std::optional _launchMode{ std::nullopt }; // Are you adding more args here? Make sure to reset them in _resetStateToDefault std::deque _startupActions; diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp index 68c25645019..ada5517833f 100644 --- a/src/cascadia/TerminalApp/AppLogic.cpp +++ b/src/cascadia/TerminalApp/AppLogic.cpp @@ -242,6 +242,17 @@ namespace winrt::TerminalApp::implementation _root->SetSettings(_settings, false); _root->Loaded({ this, &AppLogic::_OnLoaded }); + _root->Initialized([this](auto&&, auto&&) { + // GH#288 - When we finish initialization, if the user wanted us + // launched _fullscreen_, toggle fullscreen mode. This will make sure + // that the window size is _first_ set up as something sensible, so + // leaving fullscreen returns to a reasonable size. + const auto launchMode = this->GetLaunchMode(); + if (launchMode == LaunchMode::FullscreenMode) + { + _root->ToggleFullscreen(); + } + }); _root->Create(); _ApplyTheme(_settings->GlobalSettings().GetTheme()); @@ -529,7 +540,13 @@ namespace winrt::TerminalApp::implementation LoadSettings(); } - return _settings->GlobalSettings().GetLaunchMode(); + // GH#4620/#5801 - If the user passed --maximized or --fullscreen on the + // commandline, then use that to override the value from the settings. + const auto valueFromSettings = _settings->GlobalSettings().GetLaunchMode(); + const auto valueFromCommandlineArgs = _appArgs.GetLaunchMode(); + return valueFromCommandlineArgs.has_value() ? + valueFromCommandlineArgs.value() : + valueFromSettings; } // Method Description: @@ -934,31 +951,108 @@ namespace winrt::TerminalApp::implementation } } - int32_t AppLogic::SetStartupCommandline(array_view actions) + // Method Description: + // - Sets the initial commandline to process on startup, and attempts to + // parse it. Commands will be parsed into a list of ShortcutActions that + // will be processed on TerminalPage::Create(). + // - This function will have no effective result after Create() is called. + // - This function returns 0, unless a there was a non-zero result from + // trying to parse one of the commands provided. In that case, no commands + // after the failing command will be parsed, and the non-zero code + // returned. + // Arguments: + // - args: an array of strings to process as a commandline. These args can contain spaces + // Return Value: + // - the result of the first command who's parsing returned a non-zero code, + // or 0. (see AppLogic::_ParseArgs) + int32_t AppLogic::SetStartupCommandline(array_view args) { - if (_root) + const auto result = _ParseArgs(args); + if (result == 0) { - return _root->SetStartupCommandline(actions); + _appArgs.ValidateStartupCommands(); + _root->SetStartupActions(_appArgs.GetStartupActions()); } - return 0; + + return result; } - winrt::hstring AppLogic::ParseCommandlineMessage() + // Method Description: + // - Attempts to parse an array of commandline args into a list of + // commands to execute, and then parses these commands. As commands are + // successfully parsed, they will generate ShortcutActions for us to be + // able to execute. If we fail to parse any commands, we'll return the + // error code from the failure to parse that command, and stop processing + // additional commands. + // Arguments: + // - args: an array of strings to process as a commandline. These args can contain spaces + // Return Value: + // - 0 if the commandline was successfully parsed + int AppLogic::_ParseArgs(winrt::array_view& args) { - if (_root) + auto commands = ::TerminalApp::AppCommandlineArgs::BuildCommands(args); + + for (auto& cmdBlob : commands) { - return _root->ParseCommandlineMessage(); + // On one hand, it seems like we should be able to have one + // AppCommandlineArgs for parsing all of them, and collect the + // results one at a time. + // + // On the other hand, re-using a CLI::App seems to leave state from + // previous parsings around, so we could get mysterious behavior + // where one command affects the values of the next. + // + // From https://cliutils.github.io/CLI11/book/chapters/options.html: + // > If that option is not given, CLI11 will not touch the initial + // > value. This allows you to set up defaults by simply setting + // > your value beforehand. + // + // So we pretty much need the to either manually reset the state + // each command, or build new ones. + const auto result = _appArgs.ParseCommand(cmdBlob); + + // If this succeeded, result will be 0. Otherwise, the caller should + // exit(result), to exit the program. + if (result != 0) + { + return result; + } } - return { L"" }; + + // If all the args were successfully parsed, we'll have some commands + // built in _appArgs, which we'll use when the application starts up. + return 0; } + // Method Description: + // - If there were any errors parsing the commandline that was used to + // initialize the terminal, this will return a string containing that + // message. If there were no errors, this message will be blank. + // - If the user requested help on any command (using --help), this will + // contain the help message. + // - If the user requested the version number (using --version), this will + // contain the version string. + // Arguments: + // - + // Return Value: + // - the help text or error message for the provided commandline, if one + // exists, otherwise the empty string. + winrt::hstring AppLogic::ParseCommandlineMessage() + { + return winrt::to_hstring(_appArgs.GetExitMessage()); + } + + // Method Description: + // - Returns true if we should exit the application before even starting the + // window. We might want to do this if we're displaying an error message or + // the version string, or if we want to open the settings file. + // Arguments: + // - + // Return Value: + // - true iff we should exit the application before even starting the window bool AppLogic::ShouldExitEarly() { - if (_root) - { - return _root->ShouldExitEarly(); - } - return false; + return _appArgs.ShouldExitEarly(); } winrt::hstring AppLogic::ApplicationDisplayName() const diff --git a/src/cascadia/TerminalApp/AppLogic.h b/src/cascadia/TerminalApp/AppLogic.h index 07e3204d466..c189b1aab38 100644 --- a/src/cascadia/TerminalApp/AppLogic.h +++ b/src/cascadia/TerminalApp/AppLogic.h @@ -76,6 +76,9 @@ namespace winrt::TerminalApp::implementation std::atomic _settingsReloadQueued{ false }; + ::TerminalApp::AppCommandlineArgs _appArgs; + int _ParseArgs(winrt::array_view& args); + fire_and_forget _ShowDialog(const winrt::Windows::Foundation::IInspectable& sender, winrt::Windows::UI::Xaml::Controls::ContentDialog dialog); void _ShowLoadErrorsDialog(const winrt::hstring& titleKey, const winrt::hstring& contentKey, HRESULT settingsLoadedResult); void _ShowLoadWarningsDialog(); diff --git a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw index bdc3cf861e8..6afdb0e006c 100644 --- a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw @@ -259,6 +259,12 @@ Display the application version + + Launch the window maximized + + + Launch the window in fullscreen mode + Press the button to open a new terminal tab with your default profile. Open the flyout to select which profile you want to open. diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index b45829b7ca5..6e8d850c055 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -184,8 +184,7 @@ namespace winrt::TerminalApp::implementation if (_startupState == StartupState::NotInitialized) { _startupState = StartupState::InStartup; - _appArgs.ValidateStartupCommands(); - if (_appArgs.GetStartupActions().empty()) + if (_startupActions.empty()) { _OpenNewTab(nullptr); @@ -208,7 +207,7 @@ namespace winrt::TerminalApp::implementation winrt::fire_and_forget TerminalPage::_ProcessStartupActions() { // If there are no actions left, do nothing. - if (_appArgs.GetStartupActions().empty()) + if (_startupActions.empty()) { return; } @@ -218,7 +217,7 @@ namespace winrt::TerminalApp::implementation co_await winrt::resume_foreground(Dispatcher(), CoreDispatcherPriority::Normal); if (auto page{ weakThis.get() }) { - for (const auto& action : _appArgs.GetStartupActions()) + for (const auto& action : _startupActions) { _actionDispatch->DoAction(action); } @@ -237,14 +236,6 @@ namespace winrt::TerminalApp::implementation // - void TerminalPage::_CompleteInitialization() { - // GH#288 - When we finish initialization, if the user wanted us - // launched _fullscreen_, toggle fullscreen mode. This will make sure - // that the window size is _first_ set up as something sensible, so - // leaving fullscreen returns to a reasonable size. - if (_settings->GlobalSettings().GetLaunchMode() == winrt::TerminalApp::LaunchMode::FullscreenMode) - { - _ToggleFullscreen(); - } _startupState = StartupState::Initialized; _InitializedHandlers(*this, nullptr); } @@ -1776,69 +1767,16 @@ namespace winrt::TerminalApp::implementation } // Method Description: - // - Sets the initial commandline to process on startup, and attempts to - // parse it. Commands will be parsed into a list of ShortcutActions that - // will be processed on TerminalPage::Create(). + // - Sets the initial actions to process on startup. We'll make a copy of + // this list, and process these actions when we're loaded. // - This function will have no effective result after Create() is called. - // - This function returns 0, unless a there was a non-zero result from - // trying to parse one of the commands provided. In that case, no commands - // after the failing command will be parsed, and the non-zero code - // returned. // Arguments: - // - args: an array of strings to process as a commandline. These args can contain spaces + // - actions: a list of Actions to process on startup. // Return Value: - // - the result of the first command who's parsing returned a non-zero code, - // or 0. (see TerminalPage::_ParseArgs) - int32_t TerminalPage::SetStartupCommandline(winrt::array_view args) + // - + void TerminalPage::SetStartupActions(std::deque& actions) { - return _ParseArgs(args); - } - - // Method Description: - // - Attempts to parse an array of commandline args into a list of - // commands to execute, and then parses these commands. As commands are - // successfully parsed, they will generate ShortcutActions for us to be - // able to execute. If we fail to parse any commands, we'll return the - // error code from the failure to parse that command, and stop processing - // additional commands. - // Arguments: - // - args: an array of strings to process as a commandline. These args can contain spaces - // Return Value: - // - 0 if the commandline was successfully parsed - int TerminalPage::_ParseArgs(winrt::array_view& args) - { - auto commands = ::TerminalApp::AppCommandlineArgs::BuildCommands(args); - - for (auto& cmdBlob : commands) - { - // On one hand, it seems like we should be able to have one - // AppCommandlineArgs for parsing all of them, and collect the - // results one at a time. - // - // On the other hand, re-using a CLI::App seems to leave state from - // previous parsings around, so we could get mysterious behavior - // where one command affects the values of the next. - // - // From https://cliutils.github.io/CLI11/book/chapters/options.html: - // > If that option is not given, CLI11 will not touch the initial - // > value. This allows you to set up defaults by simply setting - // > your value beforehand. - // - // So we pretty much need the to either manually reset the state - // each command, or build new ones. - const auto result = _appArgs.ParseCommand(cmdBlob); - - // If this succeeded, result will be 0. Otherwise, the caller should - // exit(result), to exit the program. - if (result != 0) - { - return result; - } - } - - // If all the args were successfully parsed, we'll have some commands - // built in _appArgs, which we'll use when the application starts up. - return 0; + _startupActions = actions; } // Method Description: @@ -1877,7 +1815,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - - void TerminalPage::_ToggleFullscreen() + void TerminalPage::ToggleFullscreen() { _toggleFullscreenHandlers(*this, nullptr); @@ -1886,37 +1824,6 @@ namespace winrt::TerminalApp::implementation _UpdateTabView(); } - // Method Description: - // - If there were any errors parsing the commandline that was used to - // initialize the terminal, this will return a string containing that - // message. If there were no errors, this message will be blank. - // - If the user requested help on any command (using --help), this will - // contain the help message. - // - If the user requested the version number (using --version), this will - // contain the version string. - // Arguments: - // - - // Return Value: - // - the help text or error message for the provided commandline, if one - // exists, otherwise the empty string. - winrt::hstring TerminalPage::ParseCommandlineMessage() - { - return winrt::to_hstring(_appArgs.GetExitMessage()); - } - - // Method Description: - // - Returns true if we should exit the application before even starting the - // window. We might want to do this if we're displaying an error message or - // the version string, or if we want to open the settings file. - // Arguments: - // - - // Return Value: - // - true iff we should exit the application before even starting the window - bool TerminalPage::ShouldExitEarly() - { - return _appArgs.ShouldExitEarly(); - } - // Method Description: // - Returns a com_ptr to the implementation type of the tab at the given index // Arguments: diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index f03a5d639c2..86af8a51069 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -49,9 +49,9 @@ namespace winrt::TerminalApp::implementation void CloseWindow(); - int32_t SetStartupCommandline(winrt::array_view args); - winrt::hstring ParseCommandlineMessage(); - bool ShouldExitEarly(); + void ToggleFullscreen(); + + void SetStartupActions(std::deque& actions); // -------------------------------- WinRT Events --------------------------------- DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(TitleChanged, _titleChangeHandlers, winrt::Windows::Foundation::IInspectable, winrt::hstring); @@ -92,8 +92,7 @@ namespace winrt::TerminalApp::implementation winrt::Windows::UI::Xaml::Controls::Grid::LayoutUpdated_revoker _layoutUpdatedRevoker; StartupState _startupState{ StartupState::NotInitialized }; - ::TerminalApp::AppCommandlineArgs _appArgs; - int _ParseArgs(winrt::array_view& args); + std::deque _startupActions; winrt::fire_and_forget _ProcessStartupActions(); void _ShowAboutDialog(); @@ -166,8 +165,6 @@ namespace winrt::TerminalApp::implementation winrt::fire_and_forget _RefreshUIForSettingsReload(); - void _ToggleFullscreen(); - void _SetNonClientAreaColors(const Windows::UI::Color& selectedTabColor); void _ClearNonClientAreaColors(); void _SetNewTabButtonColor(const Windows::UI::Color& color, const Windows::UI::Color& accentColor); diff --git a/src/cascadia/TerminalApp/TerminalPage.idl b/src/cascadia/TerminalApp/TerminalPage.idl index ef7ca699de0..d02450fa04e 100644 --- a/src/cascadia/TerminalApp/TerminalPage.idl +++ b/src/cascadia/TerminalApp/TerminalPage.idl @@ -10,10 +10,6 @@ namespace TerminalApp { TerminalPage(); - Int32 SetStartupCommandline(String[] commands); - String ParseCommandlineMessage { get; }; - Boolean ShouldExitEarly { get; }; - // XAML bound properties String ApplicationDisplayName { get; }; String ApplicationVersion { get; }; From 48b3262eaace9a9a657c787c9757b7cea56c15a5 Mon Sep 17 00:00:00 2001 From: Michael Niksa Date: Mon, 1 Jun 2020 15:29:05 -0700 Subject: [PATCH 8/8] Update wil. Fixes GDI handle leak (#6229) ## Summary of the Pull Request When resizing the window title, a GDI object would be leaked. This has to do with our island message handler using `wil` to track these objects and `wil` having a bug. ## References microsoft/wil#100 ## PR Checklist * [x] Closes #5949 * [x] I work here. * [x] Tested manually * [x] Doc not required. * [x] Am core contributor. ## Validation Steps Performed * [x] Added the GDI Objects column to Task Manager, set the Terminal to use the `titleWidth` size tabs, then changed the title a bunch with PowerShell. Confirmed repro before (increasing GDI count). Confirmed it's gone after (no change to object count). --- .github/actions/spell-check/dictionary/api.txt | 0 dep/wil | 2 +- src/buffer/out/CharRow.cpp | 2 +- src/buffer/out/CharRow.hpp | 2 +- src/cascadia/PublicTerminalCore/HwndTerminal.cpp | 6 +++++- src/cascadia/TerminalConnection/ConptyConnection.cpp | 4 ++++ src/renderer/dx/DxRenderer.cpp | 2 ++ 7 files changed, 14 insertions(+), 4 deletions(-) create mode 100644 .github/actions/spell-check/dictionary/api.txt diff --git a/.github/actions/spell-check/dictionary/api.txt b/.github/actions/spell-check/dictionary/api.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/dep/wil b/dep/wil index e8c599bca6c..3c00e7f1d8c 160000 --- a/dep/wil +++ b/dep/wil @@ -1 +1 @@ -Subproject commit e8c599bca6c56c44b6730ad93f6abbc9ecd60fc1 +Subproject commit 3c00e7f1d8cf9930bbb8e5be3ef0df65c84e8928 diff --git a/src/buffer/out/CharRow.cpp b/src/buffer/out/CharRow.cpp index 07a66bb92f9..cf3bed4d810 100644 --- a/src/buffer/out/CharRow.cpp +++ b/src/buffer/out/CharRow.cpp @@ -321,7 +321,7 @@ COORD CharRow::GetStorageKey(const size_t column) const noexcept // - Updates the pointer to the parent row (which might change if we shuffle the rows around) // Arguments: // - pParent - Pointer to the parent row -void CharRow::UpdateParent(ROW* const pParent) noexcept +void CharRow::UpdateParent(ROW* const pParent) { _pParent = FAIL_FAST_IF_NULL(pParent); } diff --git a/src/buffer/out/CharRow.hpp b/src/buffer/out/CharRow.hpp index c0f8f19e8e2..f3ccd3dc55e 100644 --- a/src/buffer/out/CharRow.hpp +++ b/src/buffer/out/CharRow.hpp @@ -88,7 +88,7 @@ class CharRow final const UnicodeStorage& GetUnicodeStorage() const noexcept; COORD GetStorageKey(const size_t column) const noexcept; - void UpdateParent(ROW* const pParent) noexcept; + void UpdateParent(ROW* const pParent); friend CharRowCellReference; friend constexpr bool operator==(const CharRow& a, const CharRow& b) noexcept; diff --git a/src/cascadia/PublicTerminalCore/HwndTerminal.cpp b/src/cascadia/PublicTerminalCore/HwndTerminal.cpp index 79bebbe1705..7dcc5c3976c 100644 --- a/src/cascadia/PublicTerminalCore/HwndTerminal.cpp +++ b/src/cascadia/PublicTerminalCore/HwndTerminal.cpp @@ -29,6 +29,7 @@ LRESULT CALLBACK HwndTerminal::HwndTerminalWndProc( UINT uMsg, WPARAM wParam, LPARAM lParam) noexcept +try { #pragma warning(suppress : 26490) // Win32 APIs can only store void*, have to use reinterpret_cast HwndTerminal* terminal = reinterpret_cast(GetWindowLongPtr(hwnd, GWLP_USERDATA)); @@ -84,6 +85,7 @@ LRESULT CALLBACK HwndTerminal::HwndTerminalWndProc( } return DefWindowProc(hwnd, uMsg, wParam, lParam); } +CATCH_LOG() static bool RegisterTermClass(HINSTANCE hInstance) noexcept { @@ -686,6 +688,7 @@ void __stdcall TerminalKillFocus(void* terminal) // - rows - Rows of text data to copy // - fAlsoCopyFormatting - true if the color and formatting should also be copied, false otherwise HRESULT HwndTerminal::_CopyTextToSystemClipboard(const TextBuffer::TextAndColor& rows, bool const fAlsoCopyFormatting) +try { std::wstring finalString; @@ -714,7 +717,7 @@ HRESULT HwndTerminal::_CopyTextToSystemClipboard(const TextBuffer::TextAndColor& RETURN_LAST_ERROR_IF(!OpenClipboard(_hwnd.get())); { // Clipboard Scope - auto clipboardCloser = wil::scope_exit([]() noexcept { + auto clipboardCloser = wil::scope_exit([]() { LOG_LAST_ERROR_IF(!CloseClipboard()); }); @@ -742,6 +745,7 @@ HRESULT HwndTerminal::_CopyTextToSystemClipboard(const TextBuffer::TextAndColor& return S_OK; } +CATCH_RETURN() // Routine Description: // - Copies the given string onto the global system clipboard in the specified format diff --git a/src/cascadia/TerminalConnection/ConptyConnection.cpp b/src/cascadia/TerminalConnection/ConptyConnection.cpp index b0a0482c01f..4734b4835a1 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.cpp +++ b/src/cascadia/TerminalConnection/ConptyConnection.cpp @@ -294,6 +294,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation // Method Description: // - called when the client application (not necessarily its pty) exits for any reason void ConptyConnection::_ClientTerminated() noexcept + try { if (_isStateAtOrBeyond(ConnectionState::Closing)) { @@ -321,6 +322,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation _piClient.reset(); } + CATCH_LOG() void ConptyConnection::WriteInput(hstring const& data) { @@ -349,6 +351,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation } void ConptyConnection::Close() noexcept + try { if (_transitionToState(ConnectionState::Closing)) { @@ -378,6 +381,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation _transitionToState(ConnectionState::Closed); } } + CATCH_LOG() DWORD ConptyConnection::_OutputThread() { diff --git a/src/renderer/dx/DxRenderer.cpp b/src/renderer/dx/DxRenderer.cpp index b61f1e090a5..a36c1856222 100644 --- a/src/renderer/dx/DxRenderer.cpp +++ b/src/renderer/dx/DxRenderer.cpp @@ -2236,6 +2236,7 @@ void DxEngine::SetAntialiasingMode(const D2D1_TEXT_ANTIALIAS_MODE antialiasingMo // Return Value: // - void DxEngine::SetDefaultTextBackgroundOpacity(const float opacity) noexcept +try { _defaultTextBackgroundOpacity = opacity; @@ -2244,3 +2245,4 @@ void DxEngine::SetDefaultTextBackgroundOpacity(const float opacity) noexcept // We don't terribly care if this fails. LOG_IF_FAILED(InvalidateAll()); } +CATCH_LOG()