diff --git a/.github/actions/spelling/dictionary/apis.txt b/.github/actions/spelling/dictionary/apis.txt index 169ca5dbe9b..cd95ff2d5be 100644 --- a/.github/actions/spelling/dictionary/apis.txt +++ b/.github/actions/spelling/dictionary/apis.txt @@ -14,6 +14,7 @@ DERR environstrings EXPCMDFLAGS EXPCMDSTATE +FORCEMINIMIZE frac fullkbd futex @@ -41,6 +42,7 @@ IObject IPackage IPeasant IStorage +istream IStringable ITab ITaskbar @@ -80,6 +82,7 @@ schandle semver serializer shobjidl +SHOWMINIMIZED SIZENS smoothstep GETDESKWALLPAPER @@ -108,4 +111,9 @@ wpc wsregex XDocument XElement +xlocmes +xlocmon +xlocnum +xloctime XParse +xstring diff --git a/.github/actions/spelling/dictionary/microsoft.txt b/.github/actions/spelling/dictionary/microsoft.txt index d6743107907..68aa9fa709c 100644 --- a/.github/actions/spelling/dictionary/microsoft.txt +++ b/.github/actions/spelling/dictionary/microsoft.txt @@ -8,6 +8,8 @@ bitmaps BOMs CPLs CPRs +cpptools +cppvsdbg DACL DACLs diffs @@ -16,10 +18,12 @@ dotnetfeed DTDs DWINRT enablewttlogging +Intelli LKG mfcribbon microsoft microsoftonline +msixbundle muxc netcore osgvsowi @@ -42,6 +46,7 @@ tdbuildteamid VCRT vcruntime visualstudio +vscode VSTHRD wlk wslpath diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index 96c6fe7f479..c3aba85a2c8 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -327,6 +327,7 @@ conattrs conbufferout concat concfg +conclnt conddkrefs condrv conechokey @@ -563,6 +564,7 @@ DECSWL DECTCEM Dedupe deduplicated +DEFAPP DEFAULTBACKGROUND DEFAULTFOREGROUND defaultsettings @@ -754,6 +756,7 @@ failfast FAILIFTHERE fallthrough FARPROC +fastlink fcb fcharset fclose diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000000..a50e6fd79ff --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,13 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. + // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp + + // List of extensions which should be recommended for users of this workspace. + "recommendations": [ + "ms-vscode.cpptools" + ], + // List of extensions recommended by VS Code that should not be recommended for users of this workspace. + "unwantedRecommendations": [ + + ] +} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000000..6ef547e9767 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,24 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Debug OpenConsole by Launching (x64, debug)", + "type": "cppvsdbg", + "request": "launch", + "program": "${workspaceFolder}\\bin\\x64\\debug\\openconsole.exe", + "args": [], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [], + }, + { + "name": "Debug Terminal by Attaching (You go build/register/launch it first.)", + "type": "cppvsdbg", + "request": "attach", + "processId": "${command:pickProcess}" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000000..4534d560156 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,32 @@ +{ + "C_Cpp.default.browse.databaseFilename": "${workspaceFolder}\\.vscode\\.BROWSE.VC.DB", + "C_Cpp.default.browse.path": [ + "${workspaceFolder}" + ], + "C_Cpp.loggingLevel": "None", + "files.associations": { + "xstring": "cpp", + "*.idl": "cpp", + "array": "cpp", + "future": "cpp", + "istream": "cpp", + "memory": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "utility": "cpp", + "variant": "cpp", + "xlocmes": "cpp", + "xlocmon": "cpp", + "xlocnum": "cpp", + "xloctime": "cpp", + "multi_span": "cpp", + "pointers": "cpp", + "vector": "cpp" + }, + "files.exclude": { + "**/bin/**": true, + "**/obj/**": true, + "**/packages/**": true, + "**/generated files/**": true + } +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 00000000000..827e4c40762 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,107 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "process", + "label": "Build Terminal/Console", + "command": "powershell.exe", + "args": [ + "-Command", + "Import-Module ${workspaceFolder}\\tools\\OpenConsole.psm1;", + "Set-MsBuildDevEnvironment;", + "$project = switch(\"${input:buildProjectChoice}\"){OpenConsole{\"Conhost\\Host_EXE\"} Terminal{\"Terminal\\CascadiaPackage\"}};", + "$target = switch(\"${input:buildModeChoice}\"){Build{\"\"} Rebuild{\":Rebuild\"} Clean{\":Clean\"}};", + "$target = $project + $target;", + "msbuild", + "${workspaceFolder}\\OpenConsole.sln", + "/p:Configuration=${input:configChoice}", + "/p:Platform=${input:platformChoice}", + "/t:$target", + "/verbosity:minimal" + ], + "problemMatcher": ["$msCompile"], + "group": { + "kind": "build", + "isDefault": true + }, + "runOptions": { + "reevaluateOnRerun": false, + "instanceLimit": 1, + "runOn": "default" + } + }, + { + "type": "process", + "label": "Register Windows Terminal x64 Debug", + "command": "powershell.exe", + "args": [ + "-Command", + "Import-Module ${workspaceFolder}\\tools\\OpenConsole.psm1;", + "Set-MsBuildDevEnvironment;", + "Set-Location -Path ${workspaceFolder}\\src\\cascadia\\CascadiaPackage\\AppPackages\\CascadiaPackage_0.0.1.0_x64_Debug_Test;", + "if ((Get-AppxPackage -Name 'WindowsTerminalDev*') -ne $null) { Remove-AppxPackage 'WindowsTerminalDev_0.0.1.0_x64__8wekyb3d8bbwe'};", + "New-Item ..\\loose -Type Directory -Force;", + "makeappx unpack /v /o /p .\\CascadiaPackage_0.0.1.0_x64_Debug.msix /d ..\\Loose\\;", + "Add-AppxPackage -Path ..\\loose\\AppxManifest.xml -Register -ForceUpdateFromAnyVersion -ForceApplicationShutdown" + ], + "problemMatcher": ["$msCompile"], + "group": { + "kind": "build", + "isDefault": true + } + }, + { + "type": "process", + "label": "Run Windows Terminal Dev", + "command": "wtd.exe", + "args": [ + ], + "problemMatcher": ["$msCompile"], + } + ], + "inputs":[ + { + "id": "platformChoice", + "type": "pickString", + "description": "Processor architecture choice", + "options":[ + "x64", + "x86", + "arm64" + ], + "default": "x64" + }, + { + "id": "configChoice", + "type": "pickString", + "description": "Debug or release?", + "options":[ + "Debug", + "Release" + ], + "default": "Debug" + }, + { + "id": "buildModeChoice", + "type": "pickString", + "description": "Build, rebuild, or clean?", + "options":[ + "Build", + "Rebuild", + "Clean" + ], + "default": "Build" + }, + { + "id": "buildProjectChoice", + "type": "pickString", + "description": "OpenConsole or Terminal?", + "options":[ + "OpenConsole", + "Terminal" + ], + "default": "Terminal" + } + + ] +} \ No newline at end of file diff --git a/README.md b/README.md index aa9cfc4a80b..74062a85be4 100644 --- a/README.md +++ b/README.md @@ -33,10 +33,23 @@ This is our preferred method. #### Via GitHub -For users who are unable to install Terminal from the Microsoft Store, Terminal -builds can be manually downloaded from this repository's [Releases +For users who are unable to install Windows Terminal from the Microsoft Store, +released builds can be manually downloaded from this repository's [Releases page](https://github.com/microsoft/terminal/releases). +Download the `Microsoft.WindowsTerminal_.msixbundle` file from +the **Assets** section. To install the app, you can simply double-click on the +`.msixbundle` file, and the app installer should automatically run. If that +fails for any reason, you can try the following command at a PowerShell prompt: + +```powershell +# NOTE: If you are using PowerShell 7+, please run +# Import-Module Appx -UseWindowsPowerShell +# before using Add-AppxPackage. + +Add-AppxPackage Microsoft.WindowsTerminal_.msixbundle +``` + > 🔴 Note: If you install Terminal manually: > > * Terminal will not auto-update when new builds are released so you will need diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json index c93db72d912..89a28ad27a9 100644 --- a/doc/cascadia/profiles.schema.json +++ b/doc/cascadia/profiles.schema.json @@ -222,6 +222,11 @@ "$ref": "#/definitions/Color", "default": null, "description": "If provided, will set the tab's color to the given value" + }, + "suppressApplicationTitle": { + "type": "boolean", + "default": "false", + "description": "When set to true, tabTitle overrides the default title of the tab and any title change messages from the application will be suppressed. When set to false, tabTitle behaves as normal" } }, "type": "object" @@ -993,9 +998,9 @@ "description": "Sets the color of the cursor. Overrides the cursor color from the color scheme. Uses hex color format: \"#rrggbb\"." }, "cursorHeight": { - "description": "Sets the percentage height of the cursor starting from the bottom. Only works when cursorShape is set to \"vintage\". Accepts values from 25-100.", + "description": "Sets the percentage height of the cursor starting from the bottom. Only works when cursorShape is set to \"vintage\". Accepts values from 1-100.", "maximum": 100, - "minimum": 25, + "minimum": 1, "type": ["integer","null"], "default": 25 }, diff --git a/patch.diff b/patch.diff deleted file mode 100644 index 4fda47e4210..00000000000 --- a/patch.diff +++ /dev/null @@ -1,1745 +0,0 @@ -From f33c69d8b4171ed43adc09113f2bd9975fdf4422 Mon Sep 17 00:00:00 2001 -From: Mike Griese -Date: Fri, 17 Jul 2020 13:01:07 -0500 -Subject: [PATCH] Add a scratch island project for testing - ---- - OpenConsole.sln | 38 ++ - src/tools/ScratchIsland/AppHost.cpp | 176 +++++ - src/tools/ScratchIsland/AppHost.h | 24 + - src/tools/ScratchIsland/BaseWindow.h | 228 +++++++ - src/tools/ScratchIsland/IslandWindow.cpp | 604 ++++++++++++++++++ - src/tools/ScratchIsland/IslandWindow.h | 77 +++ - src/tools/ScratchIsland/ScratchIsland.def | 1 + - .../ScratchIsland/ScratchIsland.manifest | 24 + - src/tools/ScratchIsland/ScratchIsland.rc | 94 +++ - src/tools/ScratchIsland/ScratchIsland.vcxproj | 164 +++++ - src/tools/ScratchIsland/main.cpp | 54 ++ - src/tools/ScratchIsland/packages.config | 8 + - src/tools/ScratchIsland/pch.cpp | 4 + - src/tools/ScratchIsland/pch.h | 74 +++ - src/tools/ScratchIsland/resource.h | 24 + - 15 files changed, 1594 insertions(+) - create mode 100644 src/tools/ScratchIsland/AppHost.cpp - create mode 100644 src/tools/ScratchIsland/AppHost.h - create mode 100644 src/tools/ScratchIsland/BaseWindow.h - create mode 100644 src/tools/ScratchIsland/IslandWindow.cpp - create mode 100644 src/tools/ScratchIsland/IslandWindow.h - create mode 100644 src/tools/ScratchIsland/ScratchIsland.def - create mode 100644 src/tools/ScratchIsland/ScratchIsland.manifest - create mode 100644 src/tools/ScratchIsland/ScratchIsland.rc - create mode 100644 src/tools/ScratchIsland/ScratchIsland.vcxproj - create mode 100644 src/tools/ScratchIsland/main.cpp - create mode 100644 src/tools/ScratchIsland/packages.config - create mode 100644 src/tools/ScratchIsland/pch.cpp - create mode 100644 src/tools/ScratchIsland/pch.h - create mode 100644 src/tools/ScratchIsland/resource.h - -diff --git a/OpenConsole.sln b/OpenConsole.sln -index dcae33515..5099f457d 100644 ---- a/OpenConsole.sln -+++ b/OpenConsole.sln -@@ -321,6 +321,12 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ScratchWinRTClient", "src\t - {D46D9547-F085-4645-B8F7-E8CD21559AB4} = {D46D9547-F085-4645-B8F7-E8CD21559AB4} - EndProjectSection - EndProject -+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ScratchIsland", "src\tools\ScratchIsland\ScratchIsland.vcxproj", "{23a1f736-cd19-4196-980f-84bcd50cf783}" -+ ProjectSection(ProjectDependencies) = postProject -+ {06382349-D62A-4C7D-A7D3-9CA817EAE092} = {06382349-D62A-4C7D-A7D3-9CA817EAE092} -+ {D46D9547-F085-4645-B8F7-E8CD21559AB4} = {D46D9547-F085-4645-B8F7-E8CD21559AB4} -+ EndProjectSection -+EndProject - Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "wt", "src\cascadia\wt\wt.vcxproj", "{506FD703-BAA7-4F6E-9361-64F550EC8FCA}" - EndProject - Global -@@ -2087,6 +2093,37 @@ Global - {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Release|x64.Build.0 = Release|x64 - {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Release|x86.ActiveCfg = Release|Win32 - {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Release|x86.Build.0 = Release|Win32 -+ -+ -+ -+ {23a1f736-cd19-4196-980f-84bcd50cf783}.AuditMode|Any CPU.ActiveCfg = Release|x64 -+ {23a1f736-cd19-4196-980f-84bcd50cf783}.AuditMode|Any CPU.Build.0 = Release|x64 -+ {23a1f736-cd19-4196-980f-84bcd50cf783}.AuditMode|ARM64.ActiveCfg = Release|x64 -+ {23a1f736-cd19-4196-980f-84bcd50cf783}.AuditMode|ARM64.Build.0 = Release|x64 -+ {23a1f736-cd19-4196-980f-84bcd50cf783}.AuditMode|DotNet_x64Test.ActiveCfg = Release|x64 -+ {23a1f736-cd19-4196-980f-84bcd50cf783}.AuditMode|DotNet_x64Test.Build.0 = Release|x64 -+ {23a1f736-cd19-4196-980f-84bcd50cf783}.AuditMode|DotNet_x86Test.ActiveCfg = Release|x64 -+ {23a1f736-cd19-4196-980f-84bcd50cf783}.AuditMode|DotNet_x86Test.Build.0 = Release|x64 -+ {23a1f736-cd19-4196-980f-84bcd50cf783}.AuditMode|x64.ActiveCfg = Release|x64 -+ {23a1f736-cd19-4196-980f-84bcd50cf783}.AuditMode|x64.Build.0 = Release|x64 -+ {23a1f736-cd19-4196-980f-84bcd50cf783}.AuditMode|x86.ActiveCfg = Release|Win32 -+ {23a1f736-cd19-4196-980f-84bcd50cf783}.AuditMode|x86.Build.0 = Release|Win32 -+ {23a1f736-cd19-4196-980f-84bcd50cf783}.Debug|Any CPU.ActiveCfg = Debug|Win32 -+ {23a1f736-cd19-4196-980f-84bcd50cf783}.Debug|ARM64.ActiveCfg = Debug|Win32 -+ {23a1f736-cd19-4196-980f-84bcd50cf783}.Debug|DotNet_x64Test.ActiveCfg = Debug|Win32 -+ {23a1f736-cd19-4196-980f-84bcd50cf783}.Debug|DotNet_x86Test.ActiveCfg = Debug|Win32 -+ {23a1f736-cd19-4196-980f-84bcd50cf783}.Debug|x64.ActiveCfg = Debug|x64 -+ {23a1f736-cd19-4196-980f-84bcd50cf783}.Debug|x64.Build.0 = Debug|x64 -+ {23a1f736-cd19-4196-980f-84bcd50cf783}.Debug|x86.ActiveCfg = Debug|Win32 -+ {23a1f736-cd19-4196-980f-84bcd50cf783}.Debug|x86.Build.0 = Debug|Win32 -+ {23a1f736-cd19-4196-980f-84bcd50cf783}.Release|Any CPU.ActiveCfg = Release|Win32 -+ {23a1f736-cd19-4196-980f-84bcd50cf783}.Release|ARM64.ActiveCfg = Release|Win32 -+ {23a1f736-cd19-4196-980f-84bcd50cf783}.Release|DotNet_x64Test.ActiveCfg = Release|Win32 -+ {23a1f736-cd19-4196-980f-84bcd50cf783}.Release|DotNet_x86Test.ActiveCfg = Release|Win32 -+ {23a1f736-cd19-4196-980f-84bcd50cf783}.Release|x64.ActiveCfg = Release|x64 -+ {23a1f736-cd19-4196-980f-84bcd50cf783}.Release|x64.Build.0 = Release|x64 -+ {23a1f736-cd19-4196-980f-84bcd50cf783}.Release|x86.ActiveCfg = Release|Win32 -+ {23a1f736-cd19-4196-980f-84bcd50cf783}.Release|x86.Build.0 = Release|Win32 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE -@@ -2169,6 +2206,7 @@ Global - {1588FD7C-241E-4E7D-9113-43735F3E6BAD} = {59840756-302F-44DF-AA47-441A9D673202} - {D46D9547-F085-4645-B8F7-E8CD21559AB4} = {A10C4720-DCA4-4640-9749-67F4314F527C} - {06382349-D62A-4C7D-A7D3-9CA817EAE092} = {A10C4720-DCA4-4640-9749-67F4314F527C} -+ {23a1f736-cd19-4196-980f-84bcd50cf783} = {A10C4720-DCA4-4640-9749-67F4314F527C} - {506FD703-BAA7-4F6E-9361-64F550EC8FCA} = {59840756-302F-44DF-AA47-441A9D673202} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution -diff --git a/src/tools/ScratchIsland/AppHost.cpp b/src/tools/ScratchIsland/AppHost.cpp -new file mode 100644 -index 000000000..d1ec7b004 ---- /dev/null -+++ b/src/tools/ScratchIsland/AppHost.cpp -@@ -0,0 +1,176 @@ -+// Copyright (c) Microsoft Corporation. -+// Licensed under the MIT license. -+ -+#include "pch.h" -+#include "AppHost.h" -+#include "../types/inc/Viewport.hpp" -+#include "../types/inc/utils.hpp" -+#include "../types/inc/User32Utils.hpp" -+ -+#include "resource.h" -+ -+using namespace winrt::Windows::UI; -+using namespace winrt::Windows::UI::Composition; -+using namespace winrt::Windows::UI::Xaml; -+using namespace winrt::Windows::UI::Xaml::Hosting; -+using namespace winrt::Windows::Foundation::Numerics; -+using namespace ::Microsoft::Console; -+using namespace ::Microsoft::Console::Types; -+ -+AppHost::AppHost() noexcept : -+ _window{ nullptr } -+{ -+ _window = std::make_unique(); -+ -+ // Tell the window to callback to us when it's about to handle a WM_CREATE -+ auto pfn = std::bind(&AppHost::_HandleCreateWindow, -+ this, -+ std::placeholders::_1, -+ std::placeholders::_2); -+ _window->SetCreateCallback(pfn); -+ -+ _window->MakeWindow(); -+} -+ -+AppHost::~AppHost() -+{ -+ // destruction order is important for proper teardown here -+ _window = nullptr; -+} -+ -+// Method Description: -+// - Initializes the XAML island, creates the terminal app, and sets the -+// island's content to that of the terminal app's content. Also registers some -+// callbacks with TermApp. -+// !!! IMPORTANT!!! -+// This must be called *AFTER* WindowsXamlManager::InitializeForCurrentThread. -+// If it isn't, then we won't be able to create the XAML island. -+// Arguments: -+// - -+// Return Value: -+// - -+void AppHost::Initialize() -+{ -+ _window->Initialize(); -+ -+ // Set up the content of the application. If the app has a custom titlebar, -+ // set that content as well. -+ winrt::Windows::UI::Xaml::Controls::Grid g; -+ // TODO: INITIALIZE THIS UI HERE -+ _window->SetContent(g); -+ -+ _window->OnAppInitialized(); -+} -+ -+// Method Description: -+// - Resize the window we're about to create to the appropriate dimensions, as -+// specified in the settings. This will be called during the handling of -+// WM_CREATE. We'll load the settings for the app, then get the proposed size -+// of the terminal from the app. Using that proposed size, we'll resize the -+// window we're creating, so that it'll match the values in the settings. -+// Arguments: -+// - hwnd: The HWND of the window we're about to create. -+// - proposedRect: The location and size of the window that we're about to -+// create. We'll use this rect to determine which monitor the window is about -+// to appear on. -+// - launchMode: A LaunchMode enum reference that indicates the launch mode -+// Return Value: -+// - None -+void AppHost::_HandleCreateWindow(const HWND hwnd, RECT proposedRect) -+{ -+ // Acquire the actual initial position -+ winrt::Windows::Foundation::Point initialPosition{ (float)proposedRect.left, (float)proposedRect.top }; -+ proposedRect.left = gsl::narrow_cast(initialPosition.X); -+ proposedRect.top = gsl::narrow_cast(initialPosition.Y); -+ -+ long adjustedHeight = 0; -+ long adjustedWidth = 0; -+ -+ // Find nearest monitor. -+ HMONITOR hmon = MonitorFromRect(&proposedRect, MONITOR_DEFAULTTONEAREST); -+ -+ // Get nearest monitor information -+ MONITORINFO monitorInfo; -+ monitorInfo.cbSize = sizeof(MONITORINFO); -+ GetMonitorInfo(hmon, &monitorInfo); -+ -+ // This API guarantees that dpix and dpiy will be equal, but neither is an -+ // optional parameter so give two UINTs. -+ UINT dpix = USER_DEFAULT_SCREEN_DPI; -+ UINT dpiy = USER_DEFAULT_SCREEN_DPI; -+ // If this fails, we'll use the default of 96. -+ GetDpiForMonitor(hmon, MDT_EFFECTIVE_DPI, &dpix, &dpiy); -+ -+ // We need to check if the top left point of the titlebar of the window is within any screen -+ RECT offScreenTestRect; -+ offScreenTestRect.left = proposedRect.left; -+ offScreenTestRect.top = proposedRect.top; -+ offScreenTestRect.right = offScreenTestRect.left + 1; -+ offScreenTestRect.bottom = offScreenTestRect.top + 1; -+ -+ bool isTitlebarIntersectWithMonitors = false; -+ EnumDisplayMonitors( -+ nullptr, &offScreenTestRect, [](HMONITOR, HDC, LPRECT, LPARAM lParam) -> BOOL { -+ auto intersectWithMonitor = reinterpret_cast(lParam); -+ *intersectWithMonitor = true; -+ // Continue the enumeration -+ return FALSE; -+ }, -+ reinterpret_cast(&isTitlebarIntersectWithMonitors)); -+ -+ if (!isTitlebarIntersectWithMonitors) -+ { -+ // If the title bar is out-of-screen, we set the initial position to -+ // the top left corner of the nearest monitor -+ proposedRect.left = monitorInfo.rcWork.left; -+ proposedRect.top = monitorInfo.rcWork.top; -+ } -+ -+ winrt::Windows::Foundation::Size initialSize{ 800, 600 }; -+ -+ const short islandWidth = Utils::ClampToShortMax( -+ static_cast(ceil(initialSize.Width)), 1); -+ const short islandHeight = Utils::ClampToShortMax( -+ static_cast(ceil(initialSize.Height)), 1); -+ -+ // Get the size of a window we'd need to host that client rect. This will -+ // add the titlebar space. -+ const auto nonClientSize = _window->GetTotalNonClientExclusiveSize(dpix); -+ adjustedWidth = islandWidth + nonClientSize.cx; -+ adjustedHeight = islandHeight + nonClientSize.cy; -+ -+ const COORD origin{ gsl::narrow(proposedRect.left), -+ gsl::narrow(proposedRect.top) }; -+ const COORD dimensions{ Utils::ClampToShortMax(adjustedWidth, 1), -+ Utils::ClampToShortMax(adjustedHeight, 1) }; -+ -+ const auto newPos = Viewport::FromDimensions(origin, dimensions); -+ bool succeeded = SetWindowPos(hwnd, -+ nullptr, -+ newPos.Left(), -+ newPos.Top(), -+ newPos.Width(), -+ newPos.Height(), -+ SWP_NOACTIVATE | SWP_NOZORDER); -+ -+ // Refresh the dpi of HWND because the dpi where the window will launch may be different -+ // at this time -+ _window->RefreshCurrentDPI(); -+ -+ // If we can't resize the window, that's really okay. We can just go on with -+ // the originally proposed window size. -+ LOG_LAST_ERROR_IF(!succeeded); -+} -+ -+// Method Description: -+// - Called when the app wants to change its theme. We'll forward this to the -+// IslandWindow, so it can update the root UI element of the entire XAML tree. -+// Arguments: -+// - sender: unused -+// - arg: the ElementTheme to use as the new theme for the UI -+// Return Value: -+// - -+void AppHost::_UpdateTheme(const winrt::Windows::Foundation::IInspectable&, const winrt::Windows::UI::Xaml::ElementTheme& arg) -+{ -+ _window->OnApplicationThemeChanged(arg); -+} -diff --git a/src/tools/ScratchIsland/AppHost.h b/src/tools/ScratchIsland/AppHost.h -new file mode 100644 -index 000000000..e4a957cd8 ---- /dev/null -+++ b/src/tools/ScratchIsland/AppHost.h -@@ -0,0 +1,24 @@ -+// Copyright (c) Microsoft Corporation. -+// Licensed under the MIT license. -+ -+#include "pch.h" -+ -+#include "IslandWindow.h" -+ -+class AppHost -+{ -+public: -+ AppHost() noexcept; -+ virtual ~AppHost(); -+ -+ void Initialize(); -+ -+private: -+ bool _useNonClientArea{ false }; -+ -+ std::unique_ptr _window; -+ -+ void _HandleCreateWindow(const HWND hwnd, RECT proposedRect); -+ void _UpdateTheme(const winrt::Windows::Foundation::IInspectable&, -+ const winrt::Windows::UI::Xaml::ElementTheme& arg); -+}; -diff --git a/src/tools/ScratchIsland/BaseWindow.h b/src/tools/ScratchIsland/BaseWindow.h -new file mode 100644 -index 000000000..d1a8b86d7 ---- /dev/null -+++ b/src/tools/ScratchIsland/BaseWindow.h -@@ -0,0 +1,228 @@ -+// Copyright (c) Microsoft Corporation. -+// Licensed under the MIT license. -+ -+#pragma once -+ -+// Custom window messages -+#define CM_UPDATE_TITLE (WM_USER) -+ -+#include -+ -+template -+class BaseWindow -+{ -+public: -+ virtual ~BaseWindow() = 0; -+ static T* GetThisFromHandle(HWND const window) noexcept -+ { -+ return reinterpret_cast(GetWindowLongPtr(window, GWLP_USERDATA)); -+ } -+ -+ [[nodiscard]] static LRESULT __stdcall WndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept -+ { -+ WINRT_ASSERT(window); -+ -+ if (WM_NCCREATE == message) -+ { -+ auto cs = reinterpret_cast(lparam); -+ T* that = static_cast(cs->lpCreateParams); -+ WINRT_ASSERT(that); -+ WINRT_ASSERT(!that->_window); -+ that->_window = wil::unique_hwnd(window); -+ -+ return that->_OnNcCreate(wparam, lparam); -+ } -+ else if (T* that = GetThisFromHandle(window)) -+ { -+ return that->MessageHandler(message, wparam, lparam); -+ } -+ -+ return DefWindowProc(window, message, wparam, lparam); -+ } -+ -+ [[nodiscard]] virtual LRESULT MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept -+ { -+ switch (message) -+ { -+ case WM_DPICHANGED: -+ { -+ return HandleDpiChange(_window.get(), wparam, lparam); -+ } -+ -+ case WM_DESTROY: -+ { -+ PostQuitMessage(0); -+ return 0; -+ } -+ -+ case WM_SIZE: -+ { -+ UINT width = LOWORD(lparam); -+ UINT height = HIWORD(lparam); -+ -+ switch (wparam) -+ { -+ case SIZE_MAXIMIZED: -+ [[fallthrough]]; -+ case SIZE_RESTORED: -+ if (_minimized) -+ { -+ _minimized = false; -+ OnRestore(); -+ } -+ -+ // We always need to fire the resize event, even when we're transitioning from minimized. -+ // We might be transitioning directly from minimized to maximized, and we'll need -+ // to trigger any size-related content changes. -+ OnResize(width, height); -+ break; -+ case SIZE_MINIMIZED: -+ if (!_minimized) -+ { -+ _minimized = true; -+ OnMinimize(); -+ } -+ break; -+ default: -+ // do nothing. -+ break; -+ } -+ break; -+ } -+ case CM_UPDATE_TITLE: -+ { -+ SetWindowTextW(_window.get(), _title.c_str()); -+ break; -+ } -+ } -+ -+ return DefWindowProc(_window.get(), message, wparam, lparam); -+ } -+ -+ // DPI Change handler. on WM_DPICHANGE resize the window -+ [[nodiscard]] LRESULT HandleDpiChange(const HWND hWnd, const WPARAM wParam, const LPARAM lParam) -+ { -+ _inDpiChange = true; -+ const HWND hWndStatic = GetWindow(hWnd, GW_CHILD); -+ if (hWndStatic != nullptr) -+ { -+ const UINT uDpi = HIWORD(wParam); -+ -+ // Resize the window -+ auto lprcNewScale = reinterpret_cast(lParam); -+ -+ SetWindowPos(hWnd, nullptr, lprcNewScale->left, lprcNewScale->top, lprcNewScale->right - lprcNewScale->left, lprcNewScale->bottom - lprcNewScale->top, SWP_NOZORDER | SWP_NOACTIVATE); -+ -+ _currentDpi = uDpi; -+ } -+ _inDpiChange = false; -+ return 0; -+ } -+ -+ virtual void OnResize(const UINT width, const UINT height) = 0; -+ virtual void OnMinimize() = 0; -+ virtual void OnRestore() = 0; -+ -+ RECT GetWindowRect() const noexcept -+ { -+ RECT rc = { 0 }; -+ ::GetWindowRect(_window.get(), &rc); -+ return rc; -+ } -+ -+ HWND GetHandle() const noexcept -+ { -+ return _window.get(); -+ } -+ -+ float GetCurrentDpiScale() const noexcept -+ { -+ const auto dpi = ::GetDpiForWindow(_window.get()); -+ const auto scale = static_cast(dpi) / static_cast(USER_DEFAULT_SCREEN_DPI); -+ return scale; -+ } -+ -+ //// Gets the physical size of the client area of the HWND in _window -+ SIZE GetPhysicalSize() const noexcept -+ { -+ RECT rect = {}; -+ GetClientRect(_window.get(), &rect); -+ const auto windowsWidth = rect.right - rect.left; -+ const auto windowsHeight = rect.bottom - rect.top; -+ return SIZE{ windowsWidth, windowsHeight }; -+ } -+ -+ //// Gets the logical (in DIPs) size of a physical size specified by the parameter physicalSize -+ //// Remarks: -+ //// XAML coordinate system is always in Display Independent Pixels (a.k.a DIPs or Logical). However Win32 GDI (because of legacy reasons) -+ //// in DPI mode "Per-Monitor and Per-Monitor (V2) DPI Awareness" is always in physical pixels. -+ //// The formula to transform is: -+ //// logical = (physical / dpi) + 0.5 // 0.5 is to ensure that we pixel snap correctly at the edges, this is necessary with odd DPIs like 1.25, 1.5, 1, .75 -+ //// See also: -+ //// https://docs.microsoft.com/en-us/windows/desktop/LearnWin32/dpi-and-device-independent-pixels -+ //// https://docs.microsoft.com/en-us/windows/desktop/hidpi/high-dpi-desktop-application-development-on-windows#per-monitor-and-per-monitor-v2-dpi-awareness -+ winrt::Windows::Foundation::Size GetLogicalSize(const SIZE physicalSize) const noexcept -+ { -+ const auto scale = GetCurrentDpiScale(); -+ // 0.5 is to ensure that we pixel snap correctly at the edges, this is necessary with odd DPIs like 1.25, 1.5, 1, .75 -+ const auto logicalWidth = (physicalSize.cx / scale) + 0.5f; -+ const auto logicalHeight = (physicalSize.cy / scale) + 0.5f; -+ return winrt::Windows::Foundation::Size(logicalWidth, logicalHeight); -+ } -+ -+ winrt::Windows::Foundation::Size GetLogicalSize() const noexcept -+ { -+ return GetLogicalSize(GetPhysicalSize()); -+ } -+ -+ // Method Description: -+ // - Sends a message to our message loop to update the title of the window. -+ // Arguments: -+ // - newTitle: a string to use as the new title of the window. -+ // Return Value: -+ // - -+ void UpdateTitle(std::wstring_view newTitle) -+ { -+ _title = newTitle; -+ PostMessageW(_window.get(), CM_UPDATE_TITLE, 0, reinterpret_cast(nullptr)); -+ } -+ -+ // Method Description: -+ // Reset the current dpi of the window. This method is only called after we change the -+ // initial launch position. This makes sure the dpi is consistent with the monitor on which -+ // the window will launch -+ void RefreshCurrentDPI() -+ { -+ _currentDpi = GetDpiForWindow(_window.get()); -+ } -+ -+protected: -+ using base_type = BaseWindow; -+ wil::unique_hwnd _window; -+ -+ unsigned int _currentDpi = 0; -+ bool _inDpiChange = false; -+ -+ std::wstring _title = L""; -+ -+ bool _minimized = false; -+ -+ // Method Description: -+ // - This method is called when the window receives the WM_NCCREATE message. -+ // Return Value: -+ // - The value returned from the window proc. -+ virtual [[nodiscard]] LRESULT _OnNcCreate(WPARAM wParam, LPARAM lParam) noexcept -+ { -+ SetWindowLongPtr(_window.get(), GWLP_USERDATA, reinterpret_cast(this)); -+ -+ EnableNonClientDpiScaling(_window.get()); -+ _currentDpi = GetDpiForWindow(_window.get()); -+ -+ return DefWindowProc(_window.get(), WM_NCCREATE, wParam, lParam); -+ }; -+}; -+ -+template -+inline BaseWindow::~BaseWindow() -+{ -+} -diff --git a/src/tools/ScratchIsland/IslandWindow.cpp b/src/tools/ScratchIsland/IslandWindow.cpp -new file mode 100644 -index 000000000..3813c6e70 ---- /dev/null -+++ b/src/tools/ScratchIsland/IslandWindow.cpp -@@ -0,0 +1,604 @@ -+// Copyright (c) Microsoft Corporation. -+// Licensed under the MIT license. -+ -+#include "pch.h" -+#include "IslandWindow.h" -+#include "../types/inc/Viewport.hpp" -+#include "resource.h" -+ -+extern "C" IMAGE_DOS_HEADER __ImageBase; -+ -+using namespace winrt::Windows::UI; -+using namespace winrt::Windows::UI::Composition; -+using namespace winrt::Windows::UI::Xaml; -+using namespace winrt::Windows::UI::Xaml::Hosting; -+using namespace winrt::Windows::Foundation::Numerics; -+using namespace ::Microsoft::Console::Types; -+ -+#define XAML_HOSTING_WINDOW_CLASS_NAME L"SCRATCH_ISLAND_CLASS" -+ -+IslandWindow::IslandWindow() noexcept : -+ _interopWindowHandle{ nullptr }, -+ _rootGrid{ nullptr }, -+ _source{ nullptr }, -+ _pfnCreateCallback{ nullptr } -+{ -+} -+ -+IslandWindow::~IslandWindow() -+{ -+ _source.Close(); -+} -+ -+// Method Description: -+// - Create the actual window that we'll use for the application. -+// Arguments: -+// - -+// Return Value: -+// - -+void IslandWindow::MakeWindow() noexcept -+{ -+ WNDCLASS wc{}; -+ wc.hCursor = LoadCursor(nullptr, IDC_ARROW); -+ wc.hInstance = reinterpret_cast(&__ImageBase); -+ wc.lpszClassName = XAML_HOSTING_WINDOW_CLASS_NAME; -+ wc.style = CS_HREDRAW | CS_VREDRAW; -+ wc.lpfnWndProc = WndProc; -+ wc.hIcon = LoadIconW(wc.hInstance, MAKEINTRESOURCEW(IDI_APPICON)); -+ RegisterClass(&wc); -+ WINRT_ASSERT(!_window); -+ -+ // Create the window with the default size here - During the creation of the -+ // window, the system will give us a chance to set its size in WM_CREATE. -+ // WM_CREATE will be handled synchronously, before CreateWindow returns. -+ WINRT_VERIFY(CreateWindowEx(_alwaysOnTop ? WS_EX_TOPMOST : 0, -+ wc.lpszClassName, -+ L"Scratch Island", -+ WS_OVERLAPPEDWINDOW, -+ CW_USEDEFAULT, -+ CW_USEDEFAULT, -+ CW_USEDEFAULT, -+ CW_USEDEFAULT, -+ nullptr, -+ nullptr, -+ wc.hInstance, -+ this)); -+ -+ WINRT_ASSERT(_window); -+} -+ -+// Method Description: -+// - Called when no tab is remaining to close the window. -+// Arguments: -+// - -+// Return Value: -+// - -+void IslandWindow::Close() -+{ -+ PostQuitMessage(0); -+} -+ -+// Method Description: -+// - Set a callback to be called when we process a WM_CREATE message. This gives -+// the AppHost a chance to resize the window to the proper size. -+// Arguments: -+// - pfn: a function to be called during the handling of WM_CREATE. It takes two -+// parameters: -+// * HWND: the HWND of the window that's being created. -+// * RECT: The position on the screen that the system has proposed for our -+// window. -+// Return Value: -+// - -+void IslandWindow::SetCreateCallback(std::function pfn) noexcept -+{ -+ _pfnCreateCallback = pfn; -+} -+ -+// Method Description: -+// - Handles a WM_CREATE message. Calls our create callback, if one's been set. -+// Arguments: -+// - wParam: unused -+// - lParam: the lParam of a WM_CREATE, which is a pointer to a CREATESTRUCTW -+// Return Value: -+// - -+void IslandWindow::_HandleCreateWindow(const WPARAM, const LPARAM lParam) noexcept -+{ -+ // Get proposed window rect from create structure -+ CREATESTRUCTW* pcs = reinterpret_cast(lParam); -+ RECT rc; -+ rc.left = pcs->x; -+ rc.top = pcs->y; -+ rc.right = rc.left + pcs->cx; -+ rc.bottom = rc.top + pcs->cy; -+ -+ if (_pfnCreateCallback) -+ { -+ _pfnCreateCallback(_window.get(), rc); -+ } -+ -+ int nCmdShow = SW_SHOW; -+ -+ ShowWindow(_window.get(), nCmdShow); -+ -+ UpdateWindow(_window.get()); -+} -+ -+// Method Description: -+// - Handles a WM_SIZING message, which occurs when user drags a window border -+// or corner. It intercepts this resize action and applies 'snapping' i.e. -+// aligns the terminal's size to its cell grid. We're given the window size, -+// which we then adjust based on the terminal's properties (like font size). -+// Arguments: -+// - wParam: Specifies which edge of the window is being dragged. -+// - lParam: Pointer to the requested window rectangle (this is, the one that -+// originates from current drag action). It also acts as the return value -+// (it's a ref parameter). -+// Return Value: -+// - -+LRESULT IslandWindow::_OnSizing(const WPARAM /*wParam*/, const LPARAM /*lParam*/) -+{ -+ // If we haven't been given the callback that would adjust the dimension, -+ // then we can't do anything, so just bail out. -+ return FALSE; -+} -+ -+void IslandWindow::Initialize() -+{ -+ const bool initialized = (_interopWindowHandle != nullptr); -+ -+ _source = DesktopWindowXamlSource{}; -+ -+ auto interop = _source.as(); -+ winrt::check_hresult(interop->AttachToWindow(_window.get())); -+ -+ // stash the child interop handle so we can resize it when the main hwnd is resized -+ interop->get_WindowHandle(&_interopWindowHandle); -+ -+ _rootGrid = winrt::Windows::UI::Xaml::Controls::Grid(); -+ _source.Content(_rootGrid); -+} -+ -+void IslandWindow::OnSize(const UINT width, const UINT height) -+{ -+ // update the interop window size -+ SetWindowPos(_interopWindowHandle, nullptr, 0, 0, width, height, SWP_SHOWWINDOW); -+ -+ if (_rootGrid) -+ { -+ const auto size = GetLogicalSize(); -+ _rootGrid.Width(size.Width); -+ _rootGrid.Height(size.Height); -+ } -+} -+ -+[[nodiscard]] LRESULT IslandWindow::MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept -+{ -+ switch (message) -+ { -+ case WM_CREATE: -+ { -+ _HandleCreateWindow(wparam, lparam); -+ return 0; -+ } -+ case WM_SETFOCUS: -+ { -+ if (_interopWindowHandle != nullptr) -+ { -+ // send focus to the child window -+ SetFocus(_interopWindowHandle); -+ return 0; // eat the message -+ } -+ } -+ -+ case WM_NCLBUTTONDOWN: -+ case WM_NCLBUTTONUP: -+ case WM_NCMBUTTONDOWN: -+ case WM_NCMBUTTONUP: -+ case WM_NCRBUTTONDOWN: -+ case WM_NCRBUTTONUP: -+ case WM_NCXBUTTONDOWN: -+ case WM_NCXBUTTONUP: -+ { -+ // If we clicked in the titlebar, raise an event so the app host can -+ // dispatch an appropriate event. -+ _DragRegionClickedHandlers(); -+ break; -+ } -+ case WM_MENUCHAR: -+ { -+ // GH#891: return this LRESULT here to prevent the app from making a -+ // bell when alt+key is pressed. A menu is active and the user presses a -+ // key that does not correspond to any mnemonic or accelerator key, -+ return MAKELRESULT(0, MNC_CLOSE); -+ } -+ case WM_SIZING: -+ { -+ return _OnSizing(wparam, lparam); -+ } -+ case WM_CLOSE: -+ { -+ // If the user wants to close the app by clicking 'X' button, -+ // we hand off the close experience to the app layer. If all the tabs -+ // are closed, the window will be closed as well. -+ _windowCloseButtonClickedHandler(); -+ return 0; -+ } -+ case WM_MOUSEWHEEL: -+ try -+ { -+ // This whole handler is a hack for GH#979. -+ // -+ // On some laptops, their trackpads won't scroll inactive windows -+ // _ever_. With our entire window just being one giant XAML Island, the -+ // touchpad driver thinks our entire window is inactive, and won't -+ // scroll the XAML island. On those types of laptops, we'll get a -+ // WM_MOUSEWHEEL here, in our root window, when the trackpad scrolls. -+ // We're going to take that message and manually plumb it through to our -+ // TermControl's, or anything else that implements IMouseWheelListener. -+ -+ // https://msdn.microsoft.com/en-us/library/windows/desktop/ms645617(v=vs.85).aspx -+ // Important! Do not use the LOWORD or HIWORD macros to extract the x- -+ // and y- coordinates of the cursor position because these macros return -+ // incorrect results on systems with multiple monitors. Systems with -+ // multiple monitors can have negative x- and y- coordinates, and LOWORD -+ // and HIWORD treat the coordinates as unsigned quantities. -+ const til::point eventPoint{ GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam) }; -+ // This mouse event is relative to the display origin, not the window. Convert here. -+ const til::rectangle windowRect{ GetWindowRect() }; -+ const auto origin = windowRect.origin(); -+ const auto relative = eventPoint - origin; -+ // Convert to logical scaling before raising the event. -+ const auto real = relative / GetCurrentDpiScale(); -+ -+ const short wheelDelta = static_cast(HIWORD(wparam)); -+ -+ // Raise an event, so any listeners can handle the mouse wheel event manually. -+ _MouseScrolledHandlers(real, wheelDelta); -+ return 0; -+ } -+ CATCH_LOG(); -+ } -+ -+ // TODO: handle messages here... -+ return base_type::MessageHandler(message, wparam, lparam); -+} -+ -+// Method Description: -+// - Called when the window has been resized (or maximized) -+// Arguments: -+// - width: the new width of the window _in pixels_ -+// - height: the new height of the window _in pixels_ -+void IslandWindow::OnResize(const UINT width, const UINT height) -+{ -+ if (_interopWindowHandle) -+ { -+ OnSize(width, height); -+ } -+} -+ -+// Method Description: -+// - Called when the window is minimized to the taskbar. -+void IslandWindow::OnMinimize() -+{ -+ // TODO GH#1989 Stop rendering island content when the app is minimized. -+} -+ -+// Method Description: -+// - Called when the window is restored from having been minimized. -+void IslandWindow::OnRestore() -+{ -+ // TODO GH#1989 Stop rendering island content when the app is minimized. -+} -+ -+void IslandWindow::SetContent(winrt::Windows::UI::Xaml::UIElement content) -+{ -+ _rootGrid.Children().Clear(); -+ _rootGrid.Children().Append(content); -+} -+ -+// Method Description: -+// - Gets the difference between window and client area size. -+// Arguments: -+// - dpi: dpi of a monitor on which the window is placed -+// Return Value -+// - The size difference -+SIZE IslandWindow::GetTotalNonClientExclusiveSize(const UINT dpi) const noexcept -+{ -+ const auto windowStyle = static_cast(GetWindowLong(_window.get(), GWL_STYLE)); -+ RECT islandFrame{}; -+ -+ // If we failed to get the correct window size for whatever reason, log -+ // the error and go on. We'll use whatever the control proposed as the -+ // size of our window, which will be at least close. -+ LOG_IF_WIN32_BOOL_FALSE(AdjustWindowRectExForDpi(&islandFrame, windowStyle, false, 0, dpi)); -+ -+ return { -+ islandFrame.right - islandFrame.left, -+ islandFrame.bottom - islandFrame.top -+ }; -+} -+ -+void IslandWindow::OnAppInitialized() -+{ -+ // Do a quick resize to force the island to paint -+ const auto size = GetPhysicalSize(); -+ OnSize(size.cx, size.cy); -+} -+ -+// Method Description: -+// - Called when the app wants to change its theme. We'll update the root UI -+// element of the entire XAML tree, so that all UI elements get the theme -+// applied. -+// Arguments: -+// - arg: the ElementTheme to use as the new theme for the UI -+// Return Value: -+// - -+void IslandWindow::OnApplicationThemeChanged(const winrt::Windows::UI::Xaml::ElementTheme& requestedTheme) -+{ -+ _rootGrid.RequestedTheme(requestedTheme); -+ // Invalidate the window rect, so that we'll repaint any elements we're -+ // drawing ourselves to match the new theme -+ ::InvalidateRect(_window.get(), nullptr, false); -+} -+ -+// Method Description: -+// - Updates our focus mode state. See _SetIsBorderless for more details. -+// Arguments: -+// - -+// Return Value: -+// - -+void IslandWindow::FocusModeChanged(const bool focusMode) -+{ -+ // Do nothing if the value was unchanged. -+ if (focusMode == _borderless) -+ { -+ return; -+ } -+ -+ _SetIsBorderless(focusMode); -+} -+ -+// Method Description: -+// - Updates our fullscreen state. See _SetIsFullscreen for more details. -+// Arguments: -+// - -+// Return Value: -+// - -+void IslandWindow::FullscreenChanged(const bool fullscreen) -+{ -+ // Do nothing if the value was unchanged. -+ if (fullscreen == _fullscreen) -+ { -+ return; -+ } -+ -+ _SetIsFullscreen(fullscreen); -+} -+ -+// Method Description: -+// - Enter or exit the "always on top" state. Before the window is created, this -+// value will later be used when we create the window to create the window on -+// top of all others. After the window is created, it will either enter the -+// group of topmost windows, or exit the group of topmost windows. -+// Arguments: -+// - alwaysOnTop: whether we should be entering or exiting always on top mode. -+// Return Value: -+// - -+void IslandWindow::SetAlwaysOnTop(const bool alwaysOnTop) -+{ -+ _alwaysOnTop = alwaysOnTop; -+ -+ const auto hwnd = GetHandle(); -+ if (hwnd) -+ { -+ SetWindowPos(hwnd, -+ _alwaysOnTop ? HWND_TOPMOST : HWND_NOTOPMOST, -+ 0, // the window dimensions are unused, because we're passing SWP_NOSIZE -+ 0, -+ 0, -+ 0, -+ SWP_NOMOVE | SWP_NOSIZE); -+ } -+} -+ -+// From GdiEngine::s_SetWindowLongWHelper -+void _SetWindowLongWHelper(const HWND hWnd, const int nIndex, const LONG dwNewLong) noexcept -+{ -+ // SetWindowLong has strange error handling. On success, it returns the -+ // previous Window Long value and doesn't modify the Last Error state. To -+ // deal with this, we set the last error to 0/S_OK first, call it, and if -+ // the previous long was 0, we check if the error was non-zero before -+ // reporting. Otherwise, we'll get an "Error: The operation has completed -+ // successfully." and there will be another screenshot on the internet -+ // making fun of Windows. See: -+ // https://msdn.microsoft.com/en-us/library/windows/desktop/ms633591(v=vs.85).aspx -+ SetLastError(0); -+ LONG const lResult = SetWindowLongW(hWnd, nIndex, dwNewLong); -+ if (0 == lResult) -+ { -+ LOG_LAST_ERROR_IF(0 != GetLastError()); -+ } -+} -+ -+// Method Description: -+// - This is a helper to figure out what the window styles should be, given the -+// current state of flags like borderless mode and fullscreen mode. -+// Arguments: -+// - -+// Return Value: -+// - a LONG with the appropriate flags set for our current window mode, to be used with GWL_STYLE -+LONG IslandWindow::_getDesiredWindowStyle() const -+{ -+ auto windowStyle = GetWindowLongW(GetHandle(), GWL_STYLE); -+ -+ // If we're both fullscreen and borderless, fullscreen mode takes precedence. -+ -+ if (_fullscreen) -+ { -+ // When moving to fullscreen, remove WS_OVERLAPPEDWINDOW, which specifies -+ // styles for non-fullscreen windows (e.g. caption bar), and add the -+ // WS_POPUP style to allow us to size ourselves to the monitor size. -+ // Do the reverse when restoring from fullscreen. -+ // Doing these modifications to that window will cause a vista-style -+ // window frame to briefly appear when entering and exiting fullscreen. -+ WI_ClearFlag(windowStyle, WS_BORDER); -+ WI_ClearFlag(windowStyle, WS_SIZEBOX); -+ WI_ClearAllFlags(windowStyle, WS_OVERLAPPEDWINDOW); -+ -+ WI_SetFlag(windowStyle, WS_POPUP); -+ return windowStyle; -+ } -+ else if (_borderless) -+ { -+ // When moving to borderless, remove WS_OVERLAPPEDWINDOW, which -+ // specifies styles for non-fullscreen windows (e.g. caption bar), and -+ // add the WS_BORDER and WS_SIZEBOX styles. This allows us to still have -+ // a small resizing frame, but without a full titlebar, nor caption -+ // buttons. -+ -+ WI_ClearAllFlags(windowStyle, WS_OVERLAPPEDWINDOW); -+ WI_ClearFlag(windowStyle, WS_POPUP); -+ -+ WI_SetFlag(windowStyle, WS_BORDER); -+ WI_SetFlag(windowStyle, WS_SIZEBOX); -+ return windowStyle; -+ } -+ -+ // Here, we're not in either fullscreen or borderless mode. Return to -+ // WS_OVERLAPPEDWINDOW. -+ WI_ClearFlag(windowStyle, WS_POPUP); -+ WI_ClearFlag(windowStyle, WS_BORDER); -+ WI_ClearFlag(windowStyle, WS_SIZEBOX); -+ -+ WI_SetAllFlags(windowStyle, WS_OVERLAPPEDWINDOW); -+ -+ return windowStyle; -+} -+ -+// Method Description: -+// - Enable or disable focus mode. When entering focus mode, we'll -+// need to manually hide the entire titlebar. -+// - When we're entering focus we need to do some additional modification -+// of our window styles. However, the NonClientIslandWindow very explicitly -+// _doesn't_ need to do these steps. -+// Arguments: -+// - borderlessEnabled: If true, we're entering focus mode. If false, we're leaving. -+// Return Value: -+// - -+void IslandWindow::_SetIsBorderless(const bool borderlessEnabled) -+{ -+ _borderless = borderlessEnabled; -+ -+ HWND const hWnd = GetHandle(); -+ -+ // First, modify regular window styles as appropriate -+ auto windowStyle = _getDesiredWindowStyle(); -+ _SetWindowLongWHelper(hWnd, GWL_STYLE, windowStyle); -+ -+ // Now modify extended window styles as appropriate -+ // When moving to fullscreen, remove the window edge style to avoid an -+ // ugly border when not focused. -+ auto exWindowStyle = GetWindowLongW(hWnd, GWL_EXSTYLE); -+ WI_UpdateFlag(exWindowStyle, WS_EX_WINDOWEDGE, !_fullscreen); -+ _SetWindowLongWHelper(hWnd, GWL_EXSTYLE, exWindowStyle); -+ -+ // Resize the window, with SWP_FRAMECHANGED, to trigger user32 to -+ // recalculate the non/client areas -+ const til::rectangle windowPos{ GetWindowRect() }; -+ SetWindowPos(GetHandle(), -+ HWND_TOP, -+ windowPos.left(), -+ windowPos.top(), -+ windowPos.width(), -+ windowPos.height(), -+ SWP_SHOWWINDOW | SWP_FRAMECHANGED); -+} -+ -+// Method Description: -+// - Controls setting us into or out of fullscreen mode. Largely taken from -+// Window::SetIsFullscreen in conhost. -+// - When entering fullscreen mode, we'll save the current window size and -+// location, and expand to take the entire monitor size. When leaving, we'll -+// use that saved size to restore back to. -+// Arguments: -+// - fullscreenEnabled true if we should enable fullscreen mode, false to disable. -+// Return Value: -+// - -+void IslandWindow::_SetIsFullscreen(const bool fullscreenEnabled) -+{ -+ // It is possible to enter _SetIsFullscreen even if we're already in full -+ // screen. Use the old is in fullscreen flag to gate checks that rely on the -+ // current state. -+ const auto oldIsInFullscreen = _fullscreen; -+ _fullscreen = fullscreenEnabled; -+ -+ HWND const hWnd = GetHandle(); -+ -+ // First, modify regular window styles as appropriate -+ auto windowStyle = _getDesiredWindowStyle(); -+ _SetWindowLongWHelper(hWnd, GWL_STYLE, windowStyle); -+ -+ // Now modify extended window styles as appropriate -+ // When moving to fullscreen, remove the window edge style to avoid an -+ // ugly border when not focused. -+ auto exWindowStyle = GetWindowLongW(hWnd, GWL_EXSTYLE); -+ WI_UpdateFlag(exWindowStyle, WS_EX_WINDOWEDGE, !_fullscreen); -+ _SetWindowLongWHelper(hWnd, GWL_EXSTYLE, exWindowStyle); -+ -+ // When entering/exiting fullscreen mode, we also need to backup/restore the -+ // current window size, and resize the window to match the new state. -+ _BackupWindowSizes(oldIsInFullscreen); -+ _ApplyWindowSize(); -+} -+ -+// Method Description: -+// - Used in entering/exiting fullscreen mode. Saves the current window size, -+// and the full size of the monitor, for use in _ApplyWindowSize. -+// - Taken from conhost's Window::_BackupWindowSizes -+// Arguments: -+// - fCurrentIsInFullscreen: true if we're currently in fullscreen mode. -+// Return Value: -+// - -+void IslandWindow::_BackupWindowSizes(const bool fCurrentIsInFullscreen) -+{ -+ if (_fullscreen) -+ { -+ // Note: the current window size depends on the current state of the -+ // window. So don't back it up if we're already in full screen. -+ if (!fCurrentIsInFullscreen) -+ { -+ _nonFullscreenWindowSize = GetWindowRect(); -+ } -+ -+ // get and back up the current monitor's size -+ HMONITOR const hCurrentMonitor = MonitorFromWindow(GetHandle(), MONITOR_DEFAULTTONEAREST); -+ MONITORINFO currMonitorInfo; -+ currMonitorInfo.cbSize = sizeof(currMonitorInfo); -+ if (GetMonitorInfo(hCurrentMonitor, &currMonitorInfo)) -+ { -+ _fullscreenWindowSize = currMonitorInfo.rcMonitor; -+ } -+ } -+} -+ -+// Method Description: -+// - Applys the appropriate window size for transitioning to/from fullscreen mode. -+// - Taken from conhost's Window::_ApplyWindowSize -+// Arguments: -+// - -+// Return Value: -+// - -+void IslandWindow::_ApplyWindowSize() -+{ -+ const auto newSize = _fullscreen ? _fullscreenWindowSize : _nonFullscreenWindowSize; -+ LOG_IF_WIN32_BOOL_FALSE(SetWindowPos(GetHandle(), -+ HWND_TOP, -+ newSize.left, -+ newSize.top, -+ newSize.right - newSize.left, -+ newSize.bottom - newSize.top, -+ SWP_FRAMECHANGED)); -+} -+ -+DEFINE_EVENT(IslandWindow, DragRegionClicked, _DragRegionClickedHandlers, winrt::delegate<>); -+DEFINE_EVENT(IslandWindow, WindowCloseButtonClicked, _windowCloseButtonClickedHandler, winrt::delegate<>); -diff --git a/src/tools/ScratchIsland/IslandWindow.h b/src/tools/ScratchIsland/IslandWindow.h -new file mode 100644 -index 000000000..109f7c1ec ---- /dev/null -+++ b/src/tools/ScratchIsland/IslandWindow.h -@@ -0,0 +1,77 @@ -+// Copyright (c) Microsoft Corporation. -+// Licensed under the MIT license. -+ -+#include "pch.h" -+#include "BaseWindow.h" -+#include "../../cascadia/inc/cppwinrt_utils.h" -+ -+class IslandWindow : -+ public BaseWindow -+{ -+public: -+ IslandWindow() noexcept; -+ virtual ~IslandWindow() override; -+ -+ virtual void MakeWindow() noexcept; -+ void Close(); -+ virtual void OnSize(const UINT width, const UINT height); -+ -+ [[nodiscard]] virtual LRESULT MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept override; -+ void OnResize(const UINT width, const UINT height) override; -+ void OnMinimize() override; -+ void OnRestore() override; -+ virtual void OnAppInitialized(); -+ virtual void SetContent(winrt::Windows::UI::Xaml::UIElement content); -+ virtual void OnApplicationThemeChanged(const winrt::Windows::UI::Xaml::ElementTheme& requestedTheme); -+ virtual SIZE GetTotalNonClientExclusiveSize(const UINT dpi) const noexcept; -+ -+ virtual void Initialize(); -+ -+ void SetCreateCallback(std::function pfn) noexcept; -+ -+ void FocusModeChanged(const bool focusMode); -+ void FullscreenChanged(const bool fullscreen); -+ void SetAlwaysOnTop(const bool alwaysOnTop); -+ -+#pragma endregion -+ -+ DECLARE_EVENT(DragRegionClicked, _DragRegionClickedHandlers, winrt::delegate<>); -+ DECLARE_EVENT(WindowCloseButtonClicked, _windowCloseButtonClickedHandler, winrt::delegate<>); -+ WINRT_CALLBACK(MouseScrolled, winrt::delegate); -+ -+protected: -+ void ForceResize() -+ { -+ // Do a quick resize to force the island to paint -+ const auto size = GetPhysicalSize(); -+ OnSize(size.cx, size.cy); -+ } -+ -+ HWND _interopWindowHandle; -+ -+ winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource _source; -+ -+ winrt::Windows::UI::Xaml::Controls::Grid _rootGrid; -+ -+ std::function _pfnCreateCallback; -+ -+ void _HandleCreateWindow(const WPARAM wParam, const LPARAM lParam) noexcept; -+ [[nodiscard]] LRESULT _OnSizing(const WPARAM wParam, const LPARAM lParam); -+ -+ bool _borderless{ false }; -+ bool _fullscreen{ false }; -+ bool _alwaysOnTop{ false }; -+ RECT _fullscreenWindowSize; -+ RECT _nonFullscreenWindowSize; -+ -+ virtual void _SetIsBorderless(const bool borderlessEnabled); -+ virtual void _SetIsFullscreen(const bool fullscreenEnabled); -+ void _BackupWindowSizes(const bool currentIsInFullscreen); -+ void _ApplyWindowSize(); -+ -+ LONG _getDesiredWindowStyle() const; -+ -+private: -+ // This minimum width allows for width the tabs fit -+ static constexpr long minimumWidth = 460L; -+}; -diff --git a/src/tools/ScratchIsland/ScratchIsland.def b/src/tools/ScratchIsland/ScratchIsland.def -new file mode 100644 -index 000000000..5f282702b ---- /dev/null -+++ b/src/tools/ScratchIsland/ScratchIsland.def -@@ -0,0 +1 @@ -+ -\ No newline at end of file -diff --git a/src/tools/ScratchIsland/ScratchIsland.manifest b/src/tools/ScratchIsland/ScratchIsland.manifest -new file mode 100644 -index 000000000..a447bc8fd ---- /dev/null -+++ b/src/tools/ScratchIsland/ScratchIsland.manifest -@@ -0,0 +1,24 @@ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ PerMonitorV2 -+ true -+ -+ -+ -diff --git a/src/tools/ScratchIsland/ScratchIsland.rc b/src/tools/ScratchIsland/ScratchIsland.rc -new file mode 100644 -index 000000000..78a784cf2 ---- /dev/null -+++ b/src/tools/ScratchIsland/ScratchIsland.rc -@@ -0,0 +1,94 @@ -+// Microsoft Visual C++ generated resource script. -+// -+#include "resource.h" -+ -+#define APSTUDIO_READONLY_SYMBOLS -+///////////////////////////////////////////////////////////////////////////// -+// -+// Generated from the TEXTINCLUDE 2 resource. -+// -+#include "winres.h" -+ -+///////////////////////////////////////////////////////////////////////////// -+#undef APSTUDIO_READONLY_SYMBOLS -+ -+///////////////////////////////////////////////////////////////////////////// -+// English (United States) resources -+ -+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) -+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US -+#pragma code_page(1252) -+ -+#ifdef APSTUDIO_INVOKED -+///////////////////////////////////////////////////////////////////////////// -+// -+// TEXTINCLUDE -+// -+ -+1 TEXTINCLUDE -+BEGIN -+ "resource.h\0" -+END -+ -+2 TEXTINCLUDE -+BEGIN -+ "#include ""winres.h""\r\n" -+ "\0" -+END -+ -+3 TEXTINCLUDE -+BEGIN -+ "\r\n" -+ "\0" -+END -+ -+#endif // APSTUDIO_INVOKED -+ -+ -+///////////////////////////////////////////////////////////////////////////// -+// -+// Icon -+// -+ -+// Icon with lowest ID value placed first to ensure application icon -+// remains consistent on all systems. -+IDI_APPICON ICON "..\\..\\..\\res\\terminal.ico" -+ -+ -+///////////////////////////////////////////////////////////////////////////// -+// -+// String Table -+// -+ -+STRINGTABLE -+BEGIN -+ IDS_ERROR_DIALOG_TITLE "Error" -+ IDS_HELP_DIALOG_TITLE "Help" -+ IDS_ERROR_ARCHITECTURE_FORMAT -+ "Windows Terminal is designed to run on your system's native architecture (%s).\nYou are currently using the %s version.\n\nPlease use the version of Windows Terminal that matches your system's native architecture." -+ IDS_X86_ARCHITECTURE "i386" -+END -+ -+STRINGTABLE -+BEGIN -+ IDS_AMD64_ARCHITECTURE "AMD64" -+ IDS_ARM64_ARCHITECTURE "ARM64" -+ IDS_ARM_ARCHITECTURE "ARM" -+ IDS_UNKNOWN_ARCHITECTURE "Unknown" -+END -+ -+#endif // English (United States) resources -+///////////////////////////////////////////////////////////////////////////// -+ -+ -+ -+#ifndef APSTUDIO_INVOKED -+///////////////////////////////////////////////////////////////////////////// -+// -+// Generated from the TEXTINCLUDE 3 resource. -+// -+ -+ -+///////////////////////////////////////////////////////////////////////////// -+#endif // not APSTUDIO_INVOKED -+ -diff --git a/src/tools/ScratchIsland/ScratchIsland.vcxproj b/src/tools/ScratchIsland/ScratchIsland.vcxproj -new file mode 100644 -index 000000000..d155853ab ---- /dev/null -+++ b/src/tools/ScratchIsland/ScratchIsland.vcxproj -@@ -0,0 +1,164 @@ -+ -+ -+ -+ -+ -+ {23a1f736-cd19-4196-980f-84bcd50cf783} -+ Win32Proj -+ ScratchIsland -+ ScratchIsland -+ ScratchIsland -+ Application -+ false -+ Windows Store -+ true -+ false -+ -+ -+ 10.0.18362.0 -+ -+ true -+ -+ -+ -+ -+ -+ -+ -+ true -+ -+ -+ -+ -+ -+ $(OpenConsoleDir)\src\inc;$(OpenConsoleDir)\dep;$(OpenConsoleDir)\dep\Console;$(OpenConsoleDir)\dep\Win32K;$(OpenConsoleDir)\dep\gsl\include;%(AdditionalIncludeDirectories); -+ -+ -+ -+ "$(OpenConsoleDir)src\cascadia\TerminalCore\lib\Generated Files";%(AdditionalIncludeDirectories); -+ -+ -+ gdi32.lib;dwmapi.lib;Shcore.lib;UxTheme.lib;%(AdditionalDependencies) -+ -+ -+ -+ true -+ true -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ Create -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ WindowsLocalDebugger -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ <_ContinueOnError Condition="'$(BuildingProject)' == 'true'">true -+ <_ContinueOnError Condition="'$(BuildingProject)' != 'true'">false -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ x86 -+ $(Platform) -+ -+ -+ -+ -+ <_OpenConsoleVCLibToCopy Include="$(VCToolsRedistInstallDir)\$(ReasonablePlatform)\Microsoft.VC142.CRT\*.dll" /> -+ -+ -+ $(ProjectName) -+ BuiltProjectOutputGroup -+ %(Filename)%(Extension) -+ -+ -+ -+ -+ -+ -+ -+ -+ -diff --git a/src/tools/ScratchIsland/main.cpp b/src/tools/ScratchIsland/main.cpp -new file mode 100644 -index 000000000..777e3ee9e ---- /dev/null -+++ b/src/tools/ScratchIsland/main.cpp -@@ -0,0 +1,54 @@ -+// Copyright (c) Microsoft Corporation. -+// Licensed under the MIT license. -+ -+#include "pch.h" -+#include "AppHost.h" -+#include "resource.h" -+ -+using namespace winrt; -+using namespace winrt::Windows::UI; -+using namespace winrt::Windows::UI::Composition; -+using namespace winrt::Windows::UI::Xaml::Hosting; -+using namespace winrt::Windows::Foundation::Numerics; -+ -+int __stdcall wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int) -+{ -+ // If Terminal is spawned by a shortcut that requests that it run in a new process group -+ // while attached to a console session, that request is nonsense. That request will, however, -+ // cause WT to start with Ctrl-C disabled. This wouldn't matter, because it's a Windows-subsystem -+ // application. Unfortunately, that state is heritable. In short, if you start WT using cmd in -+ // a weird way, ^C stops working _inside_ the terminal. Mad. -+ SetConsoleCtrlHandler(NULL, FALSE); -+ -+ // Make sure to call this so we get WM_POINTER messages. -+ EnableMouseInPointer(true); -+ -+ // !!! LOAD BEARING !!! -+ // We must initialize the main thread as a single-threaded apartment before -+ // constructing any Xaml objects. Failing to do so will cause some issues -+ // in accessibility somewhere down the line when a UIAutomation object will -+ // be queried on the wrong thread at the wrong time. -+ // We used to initialize as STA only _after_ initializing the application -+ // host, which loaded the settings. The settings needed to be loaded in MTA -+ // because we were using the Windows.Storage APIs. Since we're no longer -+ // doing that, we can safely init as STA before any WinRT dispatches. -+ winrt::init_apartment(winrt::apartment_type::single_threaded); -+ -+ // Create the AppHost object, which will create both the window and the -+ // Terminal App. This MUST BE constructed before the Xaml manager as TermApp -+ // provides an implementation of Windows.UI.Xaml.Application. -+ AppHost host; -+ -+ // Initialize the xaml content. This must be called AFTER the -+ // WindowsXamlManager is initialized. -+ host.Initialize(); -+ -+ MSG message; -+ -+ while (GetMessage(&message, nullptr, 0, 0)) -+ { -+ TranslateMessage(&message); -+ DispatchMessage(&message); -+ } -+ return 0; -+} -diff --git a/src/tools/ScratchIsland/packages.config b/src/tools/ScratchIsland/packages.config -new file mode 100644 -index 000000000..166f8861f ---- /dev/null -+++ b/src/tools/ScratchIsland/packages.config -@@ -0,0 +1,8 @@ -+ -+ -+ -+ -+ -+ -+ -+ -\ No newline at end of file -diff --git a/src/tools/ScratchIsland/pch.cpp b/src/tools/ScratchIsland/pch.cpp -new file mode 100644 -index 000000000..398a99f66 ---- /dev/null -+++ b/src/tools/ScratchIsland/pch.cpp -@@ -0,0 +1,4 @@ -+// Copyright (c) Microsoft Corporation. -+// Licensed under the MIT license. -+ -+#include "pch.h" -diff --git a/src/tools/ScratchIsland/pch.h b/src/tools/ScratchIsland/pch.h -new file mode 100644 -index 000000000..40be55842 ---- /dev/null -+++ b/src/tools/ScratchIsland/pch.h -@@ -0,0 +1,74 @@ -+/*++ -+Copyright (c) Microsoft Corporation -+Licensed under the MIT license. -+ -+Module Name: -+- pch.h -+ -+Abstract: -+- Contains external headers to include in the precompile phase of console build process. -+- Avoid including internal project headers. Instead include them only in the classes that need them (helps with test project building). -+--*/ -+ -+#pragma once -+ -+// Ignore checked iterators warning from VC compiler. -+#define _SCL_SECURE_NO_WARNINGS -+ -+// Block minwindef.h min/max macros to prevent conflict -+#define NOMINMAX -+ -+#define WIN32_LEAN_AND_MEAN -+#include -+ -+#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0) -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+// Manually include til after we include Windows.Foundation to give it winrt superpowers -+#define BLOCK_TIL -+#include "../inc/LibraryIncludes.h" -+ -+// This is inexplicable, but for whatever reason, cppwinrt conflicts with the -+// SDK definition of this function, so the only fix is to undef it. -+// from WinBase.h -+// Windows::UI::Xaml::Media::Animation::IStoryboard::GetCurrentTime -+#ifdef GetCurrentTime -+#undef GetCurrentTime -+#endif -+ -+#include -+ -+// Needed just for XamlIslands to work at all: -+#include -+#include -+#include -+#include -+ -+// Additional headers for various xaml features. We need: -+// * Controls for grid -+// * Media for ScaleTransform -+#include -+#include -+#include -+ -+#include -+#include -+ -+// Including TraceLogging essentials for the binary -+#include -+#include -+TRACELOGGING_DECLARE_PROVIDER(g_hWindowsTerminalProvider); -+#include -+#include -+ -+// For commandline argument processing -+#include -+#include -+ -+#include "til.h" -diff --git a/src/tools/ScratchIsland/resource.h b/src/tools/ScratchIsland/resource.h -new file mode 100644 -index 000000000..011413eae ---- /dev/null -+++ b/src/tools/ScratchIsland/resource.h -@@ -0,0 +1,24 @@ -+//{{NO_DEPENDENCIES}} -+// Microsoft Visual C++ generated include file. -+// Used by WindowsTerminal.rc -+// -+#define IDI_APPICON 101 -+#define IDS_ERROR_DIALOG_TITLE 105 -+#define IDS_HELP_DIALOG_TITLE 106 -+#define IDS_ERROR_ARCHITECTURE_FORMAT 110 -+#define IDS_X86_ARCHITECTURE 111 -+#define IDS_AMD64_ARCHITECTURE 112 -+#define IDS_ARM64_ARCHITECTURE 113 -+#define IDS_ARM_ARCHITECTURE 114 -+#define IDS_UNKNOWN_ARCHITECTURE 115 -+ -+// Next default values for new objects -+// -+#ifdef APSTUDIO_INVOKED -+#ifndef APSTUDIO_READONLY_SYMBOLS -+#define _APS_NEXT_RESOURCE_VALUE 104 -+#define _APS_NEXT_COMMAND_VALUE 40001 -+#define _APS_NEXT_CONTROL_VALUE 1001 -+#define _APS_NEXT_SYMED_VALUE 101 -+#endif -+#endif --- -2.20.1.windows.1 - diff --git a/res/Cascadia.ttf b/res/Cascadia.ttf index 7f892bb5d50..3c232862f1b 100644 Binary files a/res/Cascadia.ttf and b/res/Cascadia.ttf differ diff --git a/res/CascadiaMono.ttf b/res/CascadiaMono.ttf index b733afdbdbb..253eae2d161 100644 Binary files a/res/CascadiaMono.ttf and b/res/CascadiaMono.ttf differ diff --git a/res/README.md b/res/README.md index 8a03cf53e67..0fb16a9ff09 100644 --- a/res/README.md +++ b/res/README.md @@ -17,5 +17,5 @@ Please consult the [license](https://raw.githubusercontent.com/microsoft/cascadi ### Fonts Included -* Cascadia Code, Cascadia Mono (2102.03) - * from microsoft/cascadia-code@b358d1ba3d1629c113671312b18eab52797cc055 +* Cascadia Code, Cascadia Mono (2102.25) + * from microsoft/cascadia-code@911dc421f333e3b72b97381d16fee5b71eb48f04 diff --git a/src/cascadia/LocalTests_SettingsModel/DeserializationTests.cpp b/src/cascadia/LocalTests_SettingsModel/DeserializationTests.cpp index 90549b412fa..640f47e6b7b 100644 --- a/src/cascadia/LocalTests_SettingsModel/DeserializationTests.cpp +++ b/src/cascadia/LocalTests_SettingsModel/DeserializationTests.cpp @@ -79,6 +79,7 @@ namespace SettingsModelLocalTests TEST_METHOD(TestCommandsAndKeybindings); TEST_METHOD(TestNestedCommandWithoutName); + TEST_METHOD(TestNestedCommandWithBadSubCommands); TEST_METHOD(TestUnbindNestedCommand); TEST_METHOD(TestRebindNestedCommand); @@ -1376,7 +1377,7 @@ namespace SettingsModelLocalTests }, { "name": "parent", - "commands": [ + "commands": [ { "command": { "action": "setColorScheme", "colorScheme": "invalidScheme" } } ] } @@ -1403,11 +1404,11 @@ namespace SettingsModelLocalTests }, { "name": "grandparent", - "commands": [ + "commands": [ { "name": "parent", "commands": [ - { + { "command": { "action": "setColorScheme", "colorScheme": "invalidScheme" } } ] @@ -1909,7 +1910,8 @@ namespace SettingsModelLocalTests "keybindings": [ { "command": { "action": "splitPane", "split":"auto" }, "keys": [ "ctrl+alt+t", "ctrl+a" ] }, { "command": { "action": "moveFocus" }, "keys": [ "ctrl+a" ] }, - { "command": { "action": "resizePane" }, "keys": [ "ctrl+b" ] } + { "command": { "action": "resizePane" }, "keys": [ "ctrl+b" ] }, + { "name": "invalid nested", "commands":[ { "name" : "hello" }, { "name" : "world" } ] } ] })" }; @@ -1918,18 +1920,20 @@ namespace SettingsModelLocalTests VERIFY_ARE_EQUAL(0u, settings->_globals->_keymap->_keyShortcuts.size()); - VERIFY_ARE_EQUAL(3u, settings->_globals->_keybindingsWarnings.size()); + VERIFY_ARE_EQUAL(4u, settings->_globals->_keybindingsWarnings.size()); VERIFY_ARE_EQUAL(SettingsLoadWarnings::TooManyKeysForChord, settings->_globals->_keybindingsWarnings.at(0)); VERIFY_ARE_EQUAL(SettingsLoadWarnings::MissingRequiredParameter, settings->_globals->_keybindingsWarnings.at(1)); VERIFY_ARE_EQUAL(SettingsLoadWarnings::MissingRequiredParameter, settings->_globals->_keybindingsWarnings.at(2)); + VERIFY_ARE_EQUAL(SettingsLoadWarnings::FailedToParseSubCommands, settings->_globals->_keybindingsWarnings.at(3)); settings->_ValidateKeybindings(); - VERIFY_ARE_EQUAL(4u, settings->_warnings.Size()); + VERIFY_ARE_EQUAL(5u, settings->_warnings.Size()); VERIFY_ARE_EQUAL(SettingsLoadWarnings::AtLeastOneKeybindingWarning, settings->_warnings.GetAt(0)); VERIFY_ARE_EQUAL(SettingsLoadWarnings::TooManyKeysForChord, settings->_warnings.GetAt(1)); VERIFY_ARE_EQUAL(SettingsLoadWarnings::MissingRequiredParameter, settings->_warnings.GetAt(2)); VERIFY_ARE_EQUAL(SettingsLoadWarnings::MissingRequiredParameter, settings->_warnings.GetAt(3)); + VERIFY_ARE_EQUAL(SettingsLoadWarnings::FailedToParseSubCommands, settings->_warnings.GetAt(4)); } void DeserializationTests::ValidateExecuteCommandlineWarning() @@ -2316,6 +2320,53 @@ namespace SettingsModelLocalTests VERIFY_ARE_EQUAL(0u, commands.Size()); } + void DeserializationTests::TestNestedCommandWithBadSubCommands() + { + // This test tests a nested command without a name specified. This type + // of command should just be ignored, since we can't auto-generate names + // for nested commands, they _must_ have names specified. + + const std::string settingsJson{ R"( + { + "defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}", + "profiles": [ + { + "name": "profile0", + "guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}", + "historySize": 1, + "commandline": "cmd.exe" + } + ], + "actions": [ + { + "name": "nested command", + "commands": [ + { + "name": "child1" + }, + { + "name": "child2" + } + ] + }, + ], + "schemes": [ { "name": "Campbell" } ] // This is included here to prevent settings validation errors. + })" }; + + VerifyParseSucceeded(settingsJson); + + auto settings = winrt::make_self(); + settings->_ParseJsonString(settingsJson, false); + settings->LayerJson(settings->_userSettings); + auto commands = settings->_globals->Commands(); + settings->_ValidateSettings(); + + VERIFY_ARE_EQUAL(2u, settings->_warnings.Size()); + VERIFY_ARE_EQUAL(SettingsLoadWarnings::AtLeastOneKeybindingWarning, settings->_warnings.GetAt(0)); + VERIFY_ARE_EQUAL(SettingsLoadWarnings::FailedToParseSubCommands, settings->_warnings.GetAt(1)); + VERIFY_ARE_EQUAL(0u, commands.Size()); + } + void DeserializationTests::TestUnbindNestedCommand() { // Test that layering a command with `"commands": null` set will unbind a command that already exists. diff --git a/src/cascadia/LocalTests_SettingsModel/SettingsModel.LocalTests.vcxproj b/src/cascadia/LocalTests_SettingsModel/SettingsModel.LocalTests.vcxproj index fc904d3740a..3f3fdf5964a 100644 --- a/src/cascadia/LocalTests_SettingsModel/SettingsModel.LocalTests.vcxproj +++ b/src/cascadia/LocalTests_SettingsModel/SettingsModel.LocalTests.vcxproj @@ -40,6 +40,7 @@ + Create diff --git a/src/cascadia/LocalTests_SettingsModel/TerminalSettingsTests.cpp b/src/cascadia/LocalTests_SettingsModel/TerminalSettingsTests.cpp new file mode 100644 index 00000000000..22e7d6b9e4a --- /dev/null +++ b/src/cascadia/LocalTests_SettingsModel/TerminalSettingsTests.cpp @@ -0,0 +1,543 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" + +#include "../TerminalSettingsModel/CascadiaSettings.h" +#include "../TerminalSettingsModel/TerminalSettings.h" +#include "TestUtils.h" + +using namespace Microsoft::Console; +using namespace WEX::Logging; +using namespace WEX::TestExecution; +using namespace WEX::Common; +using namespace winrt::Microsoft::Terminal::Settings::Model; +using namespace winrt::Microsoft::Terminal::TerminalControl; + +namespace SettingsModelLocalTests +{ + // TODO:microsoft/terminal#3838: + // Unfortunately, these tests _WILL NOT_ work in our CI. We're waiting for + // an updated TAEF that will let us install framework packages when the test + // package is deployed. Until then, these tests won't deploy in CI. + + class TerminalSettingsTests + { + // Use a custom AppxManifest to ensure that we can activate winrt types + // from our test. This property will tell taef to manually use this as + // the AppxManifest for this test class. + // This does not yet work for anything XAML-y. See TabTests.cpp for more + // details on that. + BEGIN_TEST_CLASS(TerminalSettingsTests) + TEST_CLASS_PROPERTY(L"RunAs", L"UAP") + TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml") + END_TEST_CLASS() + + TEST_METHOD(TryCreateWinRTType); + + TEST_METHOD(TestTerminalArgsForBinding); + + TEST_METHOD(MakeSettingsForProfileThatDoesntExist); + TEST_METHOD(MakeSettingsForDefaultProfileThatDoesntExist); + + TEST_METHOD(TestLayerProfileOnColorScheme); + + TEST_CLASS_SETUP(ClassSetup) + { + return true; + } + }; + + void TerminalSettingsTests::TryCreateWinRTType() + { + TerminalSettings settings; + VERIFY_IS_NOT_NULL(settings); + auto oldFontSize = settings.FontSize(); + settings.FontSize(oldFontSize + 5); + auto newFontSize = settings.FontSize(); + VERIFY_ARE_NOT_EQUAL(oldFontSize, newFontSize); + } + + void TerminalSettingsTests::TestTerminalArgsForBinding() + { + const std::string settingsJson{ R"( + { + "defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}", + "profiles": [ + { + "name": "profile0", + "guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}", + "historySize": 1, + "commandline": "cmd.exe" + }, + { + "name": "profile1", + "guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}", + "historySize": 2, + "commandline": "pwsh.exe" + }, + { + "name": "profile2", + "historySize": 3, + "commandline": "wsl.exe" + } + ], + "keybindings": [ + { "keys": ["ctrl+a"], "command": { "action": "splitPane", "split": "vertical" } }, + { "keys": ["ctrl+b"], "command": { "action": "splitPane", "split": "vertical", "profile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}" } }, + { "keys": ["ctrl+c"], "command": { "action": "splitPane", "split": "vertical", "profile": "profile1" } }, + { "keys": ["ctrl+d"], "command": { "action": "splitPane", "split": "vertical", "profile": "profile2" } }, + { "keys": ["ctrl+e"], "command": { "action": "splitPane", "split": "horizontal", "commandline": "foo.exe" } }, + { "keys": ["ctrl+f"], "command": { "action": "splitPane", "split": "horizontal", "profile": "profile1", "commandline": "foo.exe" } }, + { "keys": ["ctrl+g"], "command": { "action": "newTab" } }, + { "keys": ["ctrl+h"], "command": { "action": "newTab", "startingDirectory": "c:\\foo" } }, + { "keys": ["ctrl+i"], "command": { "action": "newTab", "profile": "profile2", "startingDirectory": "c:\\foo" } }, + { "keys": ["ctrl+j"], "command": { "action": "newTab", "tabTitle": "bar" } }, + { "keys": ["ctrl+k"], "command": { "action": "newTab", "profile": "profile2", "tabTitle": "bar" } }, + { "keys": ["ctrl+l"], "command": { "action": "newTab", "profile": "profile1", "tabTitle": "bar", "startingDirectory": "c:\\foo", "commandline":"foo.exe" } } + ] + })" }; + + const winrt::guid guid0{ ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-0000-49a3-80bd-e8fdd045185c}") }; + const winrt::guid guid1{ ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}") }; + + CascadiaSettings settings{ til::u8u16(settingsJson) }; + + auto keymap = settings.GlobalSettings().KeyMap(); + VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size()); + + const auto profile2Guid = settings.ActiveProfiles().GetAt(2).Guid(); + VERIFY_ARE_NOT_EQUAL(winrt::guid{}, profile2Guid); + + VERIFY_ARE_EQUAL(12u, keymap.Size()); + + { + KeyChord kc{ true, false, false, static_cast('A') }; + auto actionAndArgs = ::TestUtils::GetActionAndArgs(keymap, kc); + VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); + const auto& realArgs = actionAndArgs.Args().try_as(); + VERIFY_IS_NOT_NULL(realArgs); + // Verify the args have the expected value + VERIFY_ARE_EQUAL(SplitState::Vertical, realArgs.SplitStyle()); + VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); + VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); + VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); + VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); + VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty()); + + const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) }; + const auto termSettings{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) }; + VERIFY_ARE_EQUAL(guid0, guid); + VERIFY_ARE_EQUAL(L"cmd.exe", termSettings.Commandline()); + VERIFY_ARE_EQUAL(1, termSettings.HistorySize()); + } + { + KeyChord kc{ true, false, false, static_cast('B') }; + auto actionAndArgs = ::TestUtils::GetActionAndArgs(keymap, kc); + VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); + const auto& realArgs = actionAndArgs.Args().try_as(); + VERIFY_IS_NOT_NULL(realArgs); + // Verify the args have the expected value + VERIFY_ARE_EQUAL(SplitState::Vertical, realArgs.SplitStyle()); + VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); + VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); + VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); + VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); + VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); + VERIFY_ARE_EQUAL(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}", realArgs.TerminalArgs().Profile()); + + const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) }; + const auto termSettings{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) }; + VERIFY_ARE_EQUAL(guid1, guid); + VERIFY_ARE_EQUAL(L"pwsh.exe", termSettings.Commandline()); + VERIFY_ARE_EQUAL(2, termSettings.HistorySize()); + } + { + KeyChord kc{ true, false, false, static_cast('C') }; + auto actionAndArgs = ::TestUtils::GetActionAndArgs(keymap, kc); + VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); + const auto& realArgs = actionAndArgs.Args().try_as(); + VERIFY_IS_NOT_NULL(realArgs); + // Verify the args have the expected value + VERIFY_ARE_EQUAL(SplitState::Vertical, realArgs.SplitStyle()); + VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); + VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); + VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); + VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); + VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); + VERIFY_ARE_EQUAL(L"profile1", realArgs.TerminalArgs().Profile()); + + const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) }; + const auto termSettings{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) }; + VERIFY_ARE_EQUAL(guid1, guid); + VERIFY_ARE_EQUAL(L"pwsh.exe", termSettings.Commandline()); + VERIFY_ARE_EQUAL(2, termSettings.HistorySize()); + } + { + KeyChord kc{ true, false, false, static_cast('D') }; + auto actionAndArgs = ::TestUtils::GetActionAndArgs(keymap, kc); + VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); + const auto& realArgs = actionAndArgs.Args().try_as(); + VERIFY_IS_NOT_NULL(realArgs); + // Verify the args have the expected value + VERIFY_ARE_EQUAL(SplitState::Vertical, realArgs.SplitStyle()); + VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); + VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); + VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); + VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); + VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); + VERIFY_ARE_EQUAL(L"profile2", realArgs.TerminalArgs().Profile()); + + const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) }; + const auto termSettings{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) }; + VERIFY_ARE_EQUAL(profile2Guid, guid); + VERIFY_ARE_EQUAL(L"wsl.exe", termSettings.Commandline()); + VERIFY_ARE_EQUAL(3, termSettings.HistorySize()); + } + { + KeyChord kc{ true, false, false, static_cast('E') }; + auto actionAndArgs = ::TestUtils::GetActionAndArgs(keymap, kc); + VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); + const auto& realArgs = actionAndArgs.Args().try_as(); + VERIFY_IS_NOT_NULL(realArgs); + // Verify the args have the expected value + VERIFY_ARE_EQUAL(SplitState::Horizontal, realArgs.SplitStyle()); + VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); + VERIFY_IS_FALSE(realArgs.TerminalArgs().Commandline().empty()); + VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); + VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); + VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty()); + VERIFY_ARE_EQUAL(L"foo.exe", realArgs.TerminalArgs().Commandline()); + + const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) }; + const auto termSettings{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) }; + VERIFY_ARE_EQUAL(guid0, guid); + VERIFY_ARE_EQUAL(L"foo.exe", termSettings.Commandline()); + VERIFY_ARE_EQUAL(1, termSettings.HistorySize()); + } + { + KeyChord kc{ true, false, false, static_cast('F') }; + auto actionAndArgs = ::TestUtils::GetActionAndArgs(keymap, kc); + VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); + const auto& realArgs = actionAndArgs.Args().try_as(); + VERIFY_IS_NOT_NULL(realArgs); + // Verify the args have the expected value + VERIFY_ARE_EQUAL(SplitState::Horizontal, realArgs.SplitStyle()); + VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); + VERIFY_IS_FALSE(realArgs.TerminalArgs().Commandline().empty()); + VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); + VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); + VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); + VERIFY_ARE_EQUAL(L"profile1", realArgs.TerminalArgs().Profile()); + VERIFY_ARE_EQUAL(L"foo.exe", realArgs.TerminalArgs().Commandline()); + + const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) }; + const auto termSettings{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) }; + VERIFY_ARE_EQUAL(guid1, guid); + VERIFY_ARE_EQUAL(L"foo.exe", termSettings.Commandline()); + VERIFY_ARE_EQUAL(2, termSettings.HistorySize()); + } + { + KeyChord kc{ true, false, false, static_cast('G') }; + auto actionAndArgs = ::TestUtils::GetActionAndArgs(keymap, kc); + VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action()); + const auto& realArgs = actionAndArgs.Args().try_as(); + VERIFY_IS_NOT_NULL(realArgs); + // Verify the args have the expected value + VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); + VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); + VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); + VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); + VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty()); + + const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) }; + const auto termSettings{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) }; + VERIFY_ARE_EQUAL(guid0, guid); + VERIFY_ARE_EQUAL(L"cmd.exe", termSettings.Commandline()); + VERIFY_ARE_EQUAL(1, termSettings.HistorySize()); + } + { + KeyChord kc{ true, false, false, static_cast('H') }; + auto actionAndArgs = ::TestUtils::GetActionAndArgs(keymap, kc); + VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action()); + const auto& realArgs = actionAndArgs.Args().try_as(); + VERIFY_IS_NOT_NULL(realArgs); + // Verify the args have the expected value + VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); + VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); + VERIFY_IS_FALSE(realArgs.TerminalArgs().StartingDirectory().empty()); + VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); + VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty()); + VERIFY_ARE_EQUAL(L"c:\\foo", realArgs.TerminalArgs().StartingDirectory()); + + const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) }; + const auto termSettings{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) }; + VERIFY_ARE_EQUAL(guid0, guid); + VERIFY_ARE_EQUAL(L"cmd.exe", termSettings.Commandline()); + VERIFY_ARE_EQUAL(L"c:\\foo", termSettings.StartingDirectory()); + VERIFY_ARE_EQUAL(1, termSettings.HistorySize()); + } + { + KeyChord kc{ true, false, false, static_cast('I') }; + auto actionAndArgs = ::TestUtils::GetActionAndArgs(keymap, kc); + VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action()); + const auto& realArgs = actionAndArgs.Args().try_as(); + VERIFY_IS_NOT_NULL(realArgs); + // Verify the args have the expected value + VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); + VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); + VERIFY_IS_FALSE(realArgs.TerminalArgs().StartingDirectory().empty()); + VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); + VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); + VERIFY_ARE_EQUAL(L"c:\\foo", realArgs.TerminalArgs().StartingDirectory()); + VERIFY_ARE_EQUAL(L"profile2", realArgs.TerminalArgs().Profile()); + + const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) }; + const auto termSettings{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) }; + VERIFY_ARE_EQUAL(profile2Guid, guid); + VERIFY_ARE_EQUAL(L"wsl.exe", termSettings.Commandline()); + VERIFY_ARE_EQUAL(L"c:\\foo", termSettings.StartingDirectory()); + VERIFY_ARE_EQUAL(3, termSettings.HistorySize()); + } + { + KeyChord kc{ true, false, false, static_cast('J') }; + auto actionAndArgs = ::TestUtils::GetActionAndArgs(keymap, kc); + VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action()); + const auto& realArgs = actionAndArgs.Args().try_as(); + VERIFY_IS_NOT_NULL(realArgs); + // Verify the args have the expected value + VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); + VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); + VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); + VERIFY_IS_FALSE(realArgs.TerminalArgs().TabTitle().empty()); + VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty()); + VERIFY_ARE_EQUAL(L"bar", realArgs.TerminalArgs().TabTitle()); + + const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) }; + const auto termSettings{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) }; + VERIFY_ARE_EQUAL(guid0, guid); + VERIFY_ARE_EQUAL(L"cmd.exe", termSettings.Commandline()); + VERIFY_ARE_EQUAL(L"bar", termSettings.StartingTitle()); + VERIFY_ARE_EQUAL(1, termSettings.HistorySize()); + } + { + KeyChord kc{ true, false, false, static_cast('K') }; + auto actionAndArgs = ::TestUtils::GetActionAndArgs(keymap, kc); + VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action()); + const auto& realArgs = actionAndArgs.Args().try_as(); + VERIFY_IS_NOT_NULL(realArgs); + // Verify the args have the expected value + VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); + VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); + VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); + VERIFY_IS_FALSE(realArgs.TerminalArgs().TabTitle().empty()); + VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); + VERIFY_ARE_EQUAL(L"bar", realArgs.TerminalArgs().TabTitle()); + VERIFY_ARE_EQUAL(L"profile2", realArgs.TerminalArgs().Profile()); + + const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) }; + const auto termSettings{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) }; + VERIFY_ARE_EQUAL(profile2Guid, guid); + VERIFY_ARE_EQUAL(L"wsl.exe", termSettings.Commandline()); + VERIFY_ARE_EQUAL(L"bar", termSettings.StartingTitle()); + VERIFY_ARE_EQUAL(3, termSettings.HistorySize()); + } + { + KeyChord kc{ true, false, false, static_cast('L') }; + auto actionAndArgs = ::TestUtils::GetActionAndArgs(keymap, kc); + VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action()); + const auto& realArgs = actionAndArgs.Args().try_as(); + VERIFY_IS_NOT_NULL(realArgs); + // Verify the args have the expected value + VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); + VERIFY_IS_FALSE(realArgs.TerminalArgs().Commandline().empty()); + VERIFY_IS_FALSE(realArgs.TerminalArgs().StartingDirectory().empty()); + VERIFY_IS_FALSE(realArgs.TerminalArgs().TabTitle().empty()); + VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); + VERIFY_ARE_EQUAL(L"foo.exe", realArgs.TerminalArgs().Commandline()); + VERIFY_ARE_EQUAL(L"c:\\foo", realArgs.TerminalArgs().StartingDirectory()); + VERIFY_ARE_EQUAL(L"bar", realArgs.TerminalArgs().TabTitle()); + VERIFY_ARE_EQUAL(L"profile1", realArgs.TerminalArgs().Profile()); + + const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) }; + const auto termSettings{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) }; + VERIFY_ARE_EQUAL(guid1, guid); + VERIFY_ARE_EQUAL(L"foo.exe", termSettings.Commandline()); + VERIFY_ARE_EQUAL(L"bar", termSettings.StartingTitle()); + VERIFY_ARE_EQUAL(L"c:\\foo", termSettings.StartingDirectory()); + VERIFY_ARE_EQUAL(2, termSettings.HistorySize()); + } + } + + void TerminalSettingsTests::MakeSettingsForProfileThatDoesntExist() + { + // Test that making settings throws when the GUID doesn't exist + const std::string settingsString{ R"( + { + "defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}", + "profiles": [ + { + "name" : "profile0", + "guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}", + "historySize": 1 + }, + { + "name" : "profile1", + "guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}", + "historySize": 2 + } + ] + })" }; + CascadiaSettings settings{ til::u8u16(settingsString) }; + + 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 guid3 = ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-3333-49a3-80bd-e8fdd045185c}"); + + try + { + auto terminalSettings{ TerminalSettings::CreateWithProfileByID(settings, guid1, nullptr) }; + VERIFY_ARE_NOT_EQUAL(nullptr, terminalSettings); + VERIFY_ARE_EQUAL(1, terminalSettings.HistorySize()); + } + catch (...) + { + VERIFY_IS_TRUE(false, L"This call to CreateWithProfileByID should succeed"); + } + + try + { + auto terminalSettings{ TerminalSettings::CreateWithProfileByID(settings, guid2, nullptr) }; + VERIFY_ARE_NOT_EQUAL(nullptr, terminalSettings); + VERIFY_ARE_EQUAL(2, terminalSettings.HistorySize()); + } + catch (...) + { + VERIFY_IS_TRUE(false, L"This call to CreateWithProfileByID should succeed"); + } + + VERIFY_THROWS(auto terminalSettings = TerminalSettings::CreateWithProfileByID(settings, guid3, nullptr), wil::ResultException, L"This call to constructor should fail"); + + try + { + const auto termSettings{ TerminalSettings::CreateWithNewTerminalArgs(settings, nullptr, nullptr) }; + VERIFY_ARE_NOT_EQUAL(nullptr, termSettings); + VERIFY_ARE_EQUAL(1, termSettings.HistorySize()); + } + catch (...) + { + VERIFY_IS_TRUE(false, L"This call to CreateWithNewTerminalArgs should succeed"); + } + } + + void TerminalSettingsTests::MakeSettingsForDefaultProfileThatDoesntExist() + { + // Test that MakeSettings _doesnt_ throw when we load settings with a + // defaultProfile that's not in the list, we validate the settings, and + // then call MakeSettings(nullopt). The validation should ensure that + // the default profile is something reasonable + const std::string settingsString{ R"( + { + "defaultProfile": "{6239a42c-3333-49a3-80bd-e8fdd045185c}", + "profiles": [ + { + "name" : "profile0", + "guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}", + "historySize": 1 + }, + { + "name" : "profile1", + "guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}", + "historySize": 2 + } + ] + })" }; + CascadiaSettings settings{ til::u8u16(settingsString) }; + + VERIFY_ARE_EQUAL(2u, settings.Warnings().Size()); + VERIFY_ARE_EQUAL(2u, settings.ActiveProfiles().Size()); + VERIFY_ARE_EQUAL(settings.GlobalSettings().DefaultProfile(), settings.ActiveProfiles().GetAt(0).Guid()); + try + { + const auto termSettings{ TerminalSettings::CreateWithNewTerminalArgs(settings, nullptr, nullptr) }; + VERIFY_ARE_NOT_EQUAL(nullptr, termSettings); + VERIFY_ARE_EQUAL(1, termSettings.HistorySize()); + } + catch (...) + { + VERIFY_IS_TRUE(false, L"This call to CreateWithNewTerminalArgs should succeed"); + } + } + + void TerminalSettingsTests::TestLayerProfileOnColorScheme() + { + Log::Comment(NoThrowString().Format( + L"Ensure that setting (or not) a property in the profile that should override a property of the color scheme works correctly.")); + + const std::string settings0String{ R"( + { + "defaultProfile": "profile5", + "profiles": [ + { + "name" : "profile0", + "colorScheme": "schemeWithCursorColor" + }, + { + "name" : "profile1", + "colorScheme": "schemeWithoutCursorColor" + }, + { + "name" : "profile2", + "colorScheme": "schemeWithCursorColor", + "cursorColor": "#234567" + }, + { + "name" : "profile3", + "colorScheme": "schemeWithoutCursorColor", + "cursorColor": "#345678" + }, + { + "name" : "profile4", + "cursorColor": "#456789" + }, + { + "name" : "profile5" + } + ], + "schemes": [ + { + "name": "schemeWithCursorColor", + "cursorColor": "#123456" + }, + { + "name": "schemeWithoutCursorColor" + } + ] + })" }; + + CascadiaSettings settings{ til::u8u16(settings0String) }; + + VERIFY_ARE_EQUAL(6u, settings.ActiveProfiles().Size()); + VERIFY_ARE_EQUAL(2u, settings.GlobalSettings().ColorSchemes().Size()); + + auto createTerminalSettings = [&](const auto& profile, const auto& schemes) { + auto terminalSettings{ winrt::make_self() }; + terminalSettings->_ApplyProfileSettings(profile, schemes); + return terminalSettings; + }; + + auto terminalSettings0 = createTerminalSettings(settings.ActiveProfiles().GetAt(0), settings.GlobalSettings().ColorSchemes()); + auto terminalSettings1 = createTerminalSettings(settings.ActiveProfiles().GetAt(1), settings.GlobalSettings().ColorSchemes()); + auto terminalSettings2 = createTerminalSettings(settings.ActiveProfiles().GetAt(2), settings.GlobalSettings().ColorSchemes()); + auto terminalSettings3 = createTerminalSettings(settings.ActiveProfiles().GetAt(3), settings.GlobalSettings().ColorSchemes()); + auto terminalSettings4 = createTerminalSettings(settings.ActiveProfiles().GetAt(4), settings.GlobalSettings().ColorSchemes()); + auto terminalSettings5 = createTerminalSettings(settings.ActiveProfiles().GetAt(5), settings.GlobalSettings().ColorSchemes()); + + VERIFY_ARE_EQUAL(ARGB(0, 0x12, 0x34, 0x56), terminalSettings0->CursorColor()); // from color scheme + VERIFY_ARE_EQUAL(DEFAULT_CURSOR_COLOR, terminalSettings1->CursorColor()); // default + VERIFY_ARE_EQUAL(ARGB(0, 0x23, 0x45, 0x67), terminalSettings2->CursorColor()); // from profile (trumps color scheme) + VERIFY_ARE_EQUAL(ARGB(0, 0x34, 0x56, 0x78), terminalSettings3->CursorColor()); // from profile (not set in color scheme) + VERIFY_ARE_EQUAL(ARGB(0, 0x45, 0x67, 0x89), terminalSettings4->CursorColor()); // from profile (no color scheme) + VERIFY_ARE_EQUAL(DEFAULT_CURSOR_COLOR, terminalSettings5->CursorColor()); // default + } +} diff --git a/src/cascadia/LocalTests_TerminalApp/CommandlineTest.cpp b/src/cascadia/LocalTests_TerminalApp/CommandlineTest.cpp index a593aed03ce..0afeb245e8b 100644 --- a/src/cascadia/LocalTests_TerminalApp/CommandlineTest.cpp +++ b/src/cascadia/LocalTests_TerminalApp/CommandlineTest.cpp @@ -1594,6 +1594,9 @@ namespace TerminalAppLocalTests void CommandlineTest::TestFindTargetWindow() { { + Log::Comment(L"wt.exe with no args should always use the value from" + L" the settings (passed as the second argument)."); + std::vector args{ L"wt.exe" }; auto result = appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseNew); VERIFY_ARE_EQUAL(WindowingBehaviorUseNew, result.WindowId()); @@ -1608,6 +1611,8 @@ namespace TerminalAppLocalTests VERIFY_ARE_EQUAL(L"", result.WindowName()); } { + Log::Comment(L"-w -1 should always result in a new window"); + std::vector args{ L"wt.exe", L"-w", L"-1" }; auto result = appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseNew); VERIFY_ARE_EQUAL(WindowingBehaviorUseNew, result.WindowId()); @@ -1622,6 +1627,8 @@ namespace TerminalAppLocalTests VERIFY_ARE_EQUAL(L"", result.WindowName()); } { + Log::Comment(L"\"new\" should always result in a new window"); + std::vector args{ L"wt.exe", L"-w", L"new" }; auto result = appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseNew); VERIFY_ARE_EQUAL(WindowingBehaviorUseNew, result.WindowId()); @@ -1636,6 +1643,9 @@ namespace TerminalAppLocalTests VERIFY_ARE_EQUAL(L"", result.WindowName()); } { + Log::Comment(L"-w with a negative number should always result in a " + L"new window"); + std::vector args{ L"wt.exe", L"-w", L"-12345" }; auto result = appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseNew); VERIFY_ARE_EQUAL(WindowingBehaviorUseNew, result.WindowId()); @@ -1650,6 +1660,10 @@ namespace TerminalAppLocalTests VERIFY_ARE_EQUAL(L"", result.WindowName()); } { + Log::Comment(L"-w with a positive number should result in us trying" + L" to either make a new one or find an existing one " + L"with that ID, depending on the provided argument"); + std::vector args{ L"wt.exe", L"-w", L"12345" }; auto result = appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseNew); VERIFY_ARE_EQUAL(12345, result.WindowId()); @@ -1664,6 +1678,8 @@ namespace TerminalAppLocalTests VERIFY_ARE_EQUAL(L"", result.WindowName()); } { + Log::Comment(L"-w 0 should always use the \"current\" window"); + std::vector args{ L"wt.exe", L"-w", L"0" }; auto result = appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseNew); VERIFY_ARE_EQUAL(WindowingBehaviorUseCurrent, result.WindowId()); @@ -1678,6 +1694,9 @@ namespace TerminalAppLocalTests VERIFY_ARE_EQUAL(L"", result.WindowName()); } { + Log::Comment(L"-w last should always use the most recent window on " + L"this desktop"); + std::vector args{ L"wt.exe", L"-w", L"last" }; auto result = appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseNew); VERIFY_ARE_EQUAL(WindowingBehaviorUseExisting, result.WindowId()); @@ -1692,6 +1711,9 @@ namespace TerminalAppLocalTests VERIFY_ARE_EQUAL(L"", result.WindowName()); } { + Log::Comment(L"Make sure we follow the provided argument when a " + L"--window-id wasn't explicitly provided"); + std::vector args{ L"wt.exe", L"new-tab" }; auto result = appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseNew); VERIFY_ARE_EQUAL(WindowingBehaviorUseNew, result.WindowId()); @@ -1706,6 +1728,9 @@ namespace TerminalAppLocalTests VERIFY_ARE_EQUAL(L"", result.WindowName()); } { + Log::Comment(L"Even if someone uses a subcommand as a window name, " + L"that should work"); + std::vector args{ L"wt.exe", L"-w", L"new-tab" }; auto result = appImpl::AppLogic::_doFindTargetWindow({ args }, WindowingMode::UseNew); VERIFY_ARE_EQUAL(WindowingBehaviorUseName, result.WindowId()); diff --git a/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp b/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp index abd1c219ef8..9b6444bdb5f 100644 --- a/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp +++ b/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp @@ -4,7 +4,6 @@ #include "pch.h" #include "../TerminalApp/TerminalPage.h" -#include "../TerminalApp/TerminalSettings.h" #include "../LocalTests_SettingsModel/TestUtils.h" using namespace Microsoft::Console; @@ -34,15 +33,6 @@ namespace TerminalAppLocalTests TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml") END_TEST_CLASS() - TEST_METHOD(TryCreateWinRTType); - - TEST_METHOD(TestTerminalArgsForBinding); - - TEST_METHOD(MakeSettingsForProfileThatDoesntExist); - TEST_METHOD(MakeSettingsForDefaultProfileThatDoesntExist); - - TEST_METHOD(TestLayerProfileOnColorScheme); - TEST_METHOD(TestIterateCommands); TEST_METHOD(TestIterateOnGeneratedNamedCommands); TEST_METHOD(TestIterateOnBadJson); @@ -83,487 +73,6 @@ namespace TerminalAppLocalTests } }; - void SettingsTests::TryCreateWinRTType() - { - TerminalSettings settings; - VERIFY_IS_NOT_NULL(settings); - auto oldFontSize = settings.FontSize(); - settings.FontSize(oldFontSize + 5); - auto newFontSize = settings.FontSize(); - VERIFY_ARE_NOT_EQUAL(oldFontSize, newFontSize); - } - - void SettingsTests::TestTerminalArgsForBinding() - { - const std::string settingsJson{ R"( - { - "defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}", - "profiles": [ - { - "name": "profile0", - "guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}", - "historySize": 1, - "commandline": "cmd.exe" - }, - { - "name": "profile1", - "guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}", - "historySize": 2, - "commandline": "pwsh.exe" - }, - { - "name": "profile2", - "historySize": 3, - "commandline": "wsl.exe" - } - ], - "keybindings": [ - { "keys": ["ctrl+a"], "command": { "action": "splitPane", "split": "vertical" } }, - { "keys": ["ctrl+b"], "command": { "action": "splitPane", "split": "vertical", "profile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}" } }, - { "keys": ["ctrl+c"], "command": { "action": "splitPane", "split": "vertical", "profile": "profile1" } }, - { "keys": ["ctrl+d"], "command": { "action": "splitPane", "split": "vertical", "profile": "profile2" } }, - { "keys": ["ctrl+e"], "command": { "action": "splitPane", "split": "horizontal", "commandline": "foo.exe" } }, - { "keys": ["ctrl+f"], "command": { "action": "splitPane", "split": "horizontal", "profile": "profile1", "commandline": "foo.exe" } }, - { "keys": ["ctrl+g"], "command": { "action": "newTab" } }, - { "keys": ["ctrl+h"], "command": { "action": "newTab", "startingDirectory": "c:\\foo" } }, - { "keys": ["ctrl+i"], "command": { "action": "newTab", "profile": "profile2", "startingDirectory": "c:\\foo" } }, - { "keys": ["ctrl+j"], "command": { "action": "newTab", "tabTitle": "bar" } }, - { "keys": ["ctrl+k"], "command": { "action": "newTab", "profile": "profile2", "tabTitle": "bar" } }, - { "keys": ["ctrl+l"], "command": { "action": "newTab", "profile": "profile1", "tabTitle": "bar", "startingDirectory": "c:\\foo", "commandline":"foo.exe" } } - ] - })" }; - - const winrt::guid guid0{ ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-0000-49a3-80bd-e8fdd045185c}") }; - const winrt::guid guid1{ ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}") }; - - CascadiaSettings settings{ til::u8u16(settingsJson) }; - - auto keymap = settings.GlobalSettings().KeyMap(); - VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size()); - - const auto profile2Guid = settings.ActiveProfiles().GetAt(2).Guid(); - VERIFY_ARE_NOT_EQUAL(winrt::guid{}, profile2Guid); - - VERIFY_ARE_EQUAL(12u, keymap.Size()); - - { - KeyChord kc{ true, false, false, static_cast('A') }; - auto actionAndArgs = TestUtils::GetActionAndArgs(keymap, kc); - VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); - const auto& realArgs = actionAndArgs.Args().try_as(); - VERIFY_IS_NOT_NULL(realArgs); - // Verify the args have the expected value - VERIFY_ARE_EQUAL(SplitState::Vertical, realArgs.SplitStyle()); - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty()); - - const auto [guid, termSettings] = winrt::TerminalApp::implementation::TerminalSettings::BuildSettings(settings, realArgs.TerminalArgs(), nullptr); - VERIFY_ARE_EQUAL(guid0, guid); - VERIFY_ARE_EQUAL(L"cmd.exe", termSettings.Commandline()); - VERIFY_ARE_EQUAL(1, termSettings.HistorySize()); - } - { - KeyChord kc{ true, false, false, static_cast('B') }; - auto actionAndArgs = TestUtils::GetActionAndArgs(keymap, kc); - VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); - const auto& realArgs = actionAndArgs.Args().try_as(); - VERIFY_IS_NOT_NULL(realArgs); - // Verify the args have the expected value - VERIFY_ARE_EQUAL(SplitState::Vertical, realArgs.SplitStyle()); - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}", realArgs.TerminalArgs().Profile()); - - const auto [guid, termSettings] = winrt::TerminalApp::implementation::TerminalSettings::BuildSettings(settings, realArgs.TerminalArgs(), nullptr); - VERIFY_ARE_EQUAL(guid1, guid); - VERIFY_ARE_EQUAL(L"pwsh.exe", termSettings.Commandline()); - VERIFY_ARE_EQUAL(2, termSettings.HistorySize()); - } - { - KeyChord kc{ true, false, false, static_cast('C') }; - auto actionAndArgs = TestUtils::GetActionAndArgs(keymap, kc); - VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); - const auto& realArgs = actionAndArgs.Args().try_as(); - VERIFY_IS_NOT_NULL(realArgs); - // Verify the args have the expected value - VERIFY_ARE_EQUAL(SplitState::Vertical, realArgs.SplitStyle()); - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"profile1", realArgs.TerminalArgs().Profile()); - - const auto [guid, termSettings] = winrt::TerminalApp::implementation::TerminalSettings::BuildSettings(settings, realArgs.TerminalArgs(), nullptr); - VERIFY_ARE_EQUAL(guid1, guid); - VERIFY_ARE_EQUAL(L"pwsh.exe", termSettings.Commandline()); - VERIFY_ARE_EQUAL(2, termSettings.HistorySize()); - } - { - KeyChord kc{ true, false, false, static_cast('D') }; - auto actionAndArgs = TestUtils::GetActionAndArgs(keymap, kc); - VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); - const auto& realArgs = actionAndArgs.Args().try_as(); - VERIFY_IS_NOT_NULL(realArgs); - // Verify the args have the expected value - VERIFY_ARE_EQUAL(SplitState::Vertical, realArgs.SplitStyle()); - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"profile2", realArgs.TerminalArgs().Profile()); - - const auto [guid, termSettings] = winrt::TerminalApp::implementation::TerminalSettings::BuildSettings(settings, realArgs.TerminalArgs(), nullptr); - VERIFY_ARE_EQUAL(profile2Guid, guid); - VERIFY_ARE_EQUAL(L"wsl.exe", termSettings.Commandline()); - VERIFY_ARE_EQUAL(3, termSettings.HistorySize()); - } - { - KeyChord kc{ true, false, false, static_cast('E') }; - auto actionAndArgs = TestUtils::GetActionAndArgs(keymap, kc); - VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); - const auto& realArgs = actionAndArgs.Args().try_as(); - VERIFY_IS_NOT_NULL(realArgs); - // Verify the args have the expected value - VERIFY_ARE_EQUAL(SplitState::Horizontal, realArgs.SplitStyle()); - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"foo.exe", realArgs.TerminalArgs().Commandline()); - - const auto [guid, termSettings] = winrt::TerminalApp::implementation::TerminalSettings::BuildSettings(settings, realArgs.TerminalArgs(), nullptr); - VERIFY_ARE_EQUAL(guid0, guid); - VERIFY_ARE_EQUAL(L"foo.exe", termSettings.Commandline()); - VERIFY_ARE_EQUAL(1, termSettings.HistorySize()); - } - { - KeyChord kc{ true, false, false, static_cast('F') }; - auto actionAndArgs = TestUtils::GetActionAndArgs(keymap, kc); - VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); - const auto& realArgs = actionAndArgs.Args().try_as(); - VERIFY_IS_NOT_NULL(realArgs); - // Verify the args have the expected value - VERIFY_ARE_EQUAL(SplitState::Horizontal, realArgs.SplitStyle()); - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"profile1", realArgs.TerminalArgs().Profile()); - VERIFY_ARE_EQUAL(L"foo.exe", realArgs.TerminalArgs().Commandline()); - - const auto [guid, termSettings] = winrt::TerminalApp::implementation::TerminalSettings::BuildSettings(settings, realArgs.TerminalArgs(), nullptr); - VERIFY_ARE_EQUAL(guid1, guid); - VERIFY_ARE_EQUAL(L"foo.exe", termSettings.Commandline()); - VERIFY_ARE_EQUAL(2, termSettings.HistorySize()); - } - { - KeyChord kc{ true, false, false, static_cast('G') }; - auto actionAndArgs = TestUtils::GetActionAndArgs(keymap, kc); - VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action()); - const auto& realArgs = actionAndArgs.Args().try_as(); - VERIFY_IS_NOT_NULL(realArgs); - // Verify the args have the expected value - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty()); - - const auto [guid, termSettings] = winrt::TerminalApp::implementation::TerminalSettings::BuildSettings(settings, realArgs.TerminalArgs(), nullptr); - VERIFY_ARE_EQUAL(guid0, guid); - VERIFY_ARE_EQUAL(L"cmd.exe", termSettings.Commandline()); - VERIFY_ARE_EQUAL(1, termSettings.HistorySize()); - } - { - KeyChord kc{ true, false, false, static_cast('H') }; - auto actionAndArgs = TestUtils::GetActionAndArgs(keymap, kc); - VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action()); - const auto& realArgs = actionAndArgs.Args().try_as(); - VERIFY_IS_NOT_NULL(realArgs); - // Verify the args have the expected value - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"c:\\foo", realArgs.TerminalArgs().StartingDirectory()); - - const auto [guid, termSettings] = winrt::TerminalApp::implementation::TerminalSettings::BuildSettings(settings, realArgs.TerminalArgs(), nullptr); - VERIFY_ARE_EQUAL(guid0, guid); - VERIFY_ARE_EQUAL(L"cmd.exe", termSettings.Commandline()); - VERIFY_ARE_EQUAL(L"c:\\foo", termSettings.StartingDirectory()); - VERIFY_ARE_EQUAL(1, termSettings.HistorySize()); - } - { - KeyChord kc{ true, false, false, static_cast('I') }; - auto actionAndArgs = TestUtils::GetActionAndArgs(keymap, kc); - VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action()); - const auto& realArgs = actionAndArgs.Args().try_as(); - VERIFY_IS_NOT_NULL(realArgs); - // Verify the args have the expected value - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"c:\\foo", realArgs.TerminalArgs().StartingDirectory()); - VERIFY_ARE_EQUAL(L"profile2", realArgs.TerminalArgs().Profile()); - - const auto [guid, termSettings] = winrt::TerminalApp::implementation::TerminalSettings::BuildSettings(settings, realArgs.TerminalArgs(), nullptr); - VERIFY_ARE_EQUAL(profile2Guid, guid); - VERIFY_ARE_EQUAL(L"wsl.exe", termSettings.Commandline()); - VERIFY_ARE_EQUAL(L"c:\\foo", termSettings.StartingDirectory()); - VERIFY_ARE_EQUAL(3, termSettings.HistorySize()); - } - { - KeyChord kc{ true, false, false, static_cast('J') }; - auto actionAndArgs = TestUtils::GetActionAndArgs(keymap, kc); - VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action()); - const auto& realArgs = actionAndArgs.Args().try_as(); - VERIFY_IS_NOT_NULL(realArgs); - // Verify the args have the expected value - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"bar", realArgs.TerminalArgs().TabTitle()); - - const auto [guid, termSettings] = winrt::TerminalApp::implementation::TerminalSettings::BuildSettings(settings, realArgs.TerminalArgs(), nullptr); - VERIFY_ARE_EQUAL(guid0, guid); - VERIFY_ARE_EQUAL(L"cmd.exe", termSettings.Commandline()); - VERIFY_ARE_EQUAL(L"bar", termSettings.StartingTitle()); - VERIFY_ARE_EQUAL(1, termSettings.HistorySize()); - } - { - KeyChord kc{ true, false, false, static_cast('K') }; - auto actionAndArgs = TestUtils::GetActionAndArgs(keymap, kc); - VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action()); - const auto& realArgs = actionAndArgs.Args().try_as(); - VERIFY_IS_NOT_NULL(realArgs); - // Verify the args have the expected value - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"bar", realArgs.TerminalArgs().TabTitle()); - VERIFY_ARE_EQUAL(L"profile2", realArgs.TerminalArgs().Profile()); - - const auto [guid, termSettings] = winrt::TerminalApp::implementation::TerminalSettings::BuildSettings(settings, realArgs.TerminalArgs(), nullptr); - VERIFY_ARE_EQUAL(profile2Guid, guid); - VERIFY_ARE_EQUAL(L"wsl.exe", termSettings.Commandline()); - VERIFY_ARE_EQUAL(L"bar", termSettings.StartingTitle()); - VERIFY_ARE_EQUAL(3, termSettings.HistorySize()); - } - { - KeyChord kc{ true, false, false, static_cast('L') }; - auto actionAndArgs = TestUtils::GetActionAndArgs(keymap, kc); - VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action()); - const auto& realArgs = actionAndArgs.Args().try_as(); - VERIFY_IS_NOT_NULL(realArgs); - // Verify the args have the expected value - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"foo.exe", realArgs.TerminalArgs().Commandline()); - VERIFY_ARE_EQUAL(L"c:\\foo", realArgs.TerminalArgs().StartingDirectory()); - VERIFY_ARE_EQUAL(L"bar", realArgs.TerminalArgs().TabTitle()); - VERIFY_ARE_EQUAL(L"profile1", realArgs.TerminalArgs().Profile()); - - const auto [guid, termSettings] = winrt::TerminalApp::implementation::TerminalSettings::BuildSettings(settings, realArgs.TerminalArgs(), nullptr); - VERIFY_ARE_EQUAL(guid1, guid); - VERIFY_ARE_EQUAL(L"foo.exe", termSettings.Commandline()); - VERIFY_ARE_EQUAL(L"bar", termSettings.StartingTitle()); - VERIFY_ARE_EQUAL(L"c:\\foo", termSettings.StartingDirectory()); - VERIFY_ARE_EQUAL(2, termSettings.HistorySize()); - } - } - - void SettingsTests::MakeSettingsForProfileThatDoesntExist() - { - // Test that MakeSettings throws when the GUID doesn't exist - const std::string settingsString{ R"( - { - "defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}", - "profiles": [ - { - "name" : "profile0", - "guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}", - "historySize": 1 - }, - { - "name" : "profile1", - "guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}", - "historySize": 2 - } - ] - })" }; - CascadiaSettings settings{ til::u8u16(settingsString) }; - - 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 guid3 = ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-3333-49a3-80bd-e8fdd045185c}"); - - try - { - auto terminalSettings = winrt::make(settings, guid1, nullptr); - VERIFY_ARE_NOT_EQUAL(nullptr, terminalSettings); - VERIFY_ARE_EQUAL(1, terminalSettings.HistorySize()); - } - catch (...) - { - VERIFY_IS_TRUE(false, L"This call to BuildSettings should succeed"); - } - - try - { - auto terminalSettings = winrt::make(settings, guid2, nullptr); - VERIFY_ARE_NOT_EQUAL(nullptr, terminalSettings); - VERIFY_ARE_EQUAL(2, terminalSettings.HistorySize()); - } - catch (...) - { - VERIFY_IS_TRUE(false, L"This call to BuildSettings should succeed"); - } - - VERIFY_THROWS(auto terminalSettings = winrt::make(settings, guid3, nullptr), wil::ResultException, L"This call to BuildSettings should fail"); - - try - { - const auto [guid, termSettings] = winrt::TerminalApp::implementation::TerminalSettings::BuildSettings(settings, nullptr, nullptr); - VERIFY_ARE_NOT_EQUAL(nullptr, termSettings); - VERIFY_ARE_EQUAL(1, termSettings.HistorySize()); - } - catch (...) - { - VERIFY_IS_TRUE(false, L"This call to BuildSettings should succeed"); - } - } - - void SettingsTests::MakeSettingsForDefaultProfileThatDoesntExist() - { - // Test that MakeSettings _doesnt_ throw when we load settings with a - // defaultProfile that's not in the list, we validate the settings, and - // then call MakeSettings(nullopt). The validation should ensure that - // the default profile is something reasonable - const std::string settingsString{ R"( - { - "defaultProfile": "{6239a42c-3333-49a3-80bd-e8fdd045185c}", - "profiles": [ - { - "name" : "profile0", - "guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}", - "historySize": 1 - }, - { - "name" : "profile1", - "guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}", - "historySize": 2 - } - ] - })" }; - CascadiaSettings settings{ til::u8u16(settingsString) }; - - VERIFY_ARE_EQUAL(2u, settings.Warnings().Size()); - VERIFY_ARE_EQUAL(2u, settings.ActiveProfiles().Size()); - VERIFY_ARE_EQUAL(settings.GlobalSettings().DefaultProfile(), settings.ActiveProfiles().GetAt(0).Guid()); - try - { - const auto [guid, termSettings] = winrt::TerminalApp::implementation::TerminalSettings::BuildSettings(settings, nullptr, nullptr); - VERIFY_ARE_NOT_EQUAL(nullptr, termSettings); - VERIFY_ARE_EQUAL(1, termSettings.HistorySize()); - } - catch (...) - { - VERIFY_IS_TRUE(false, L"This call to BuildSettings should succeed"); - } - } - - void SettingsTests::TestLayerProfileOnColorScheme() - { - Log::Comment(NoThrowString().Format( - L"Ensure that setting (or not) a property in the profile that should override a property of the color scheme works correctly.")); - - const std::string settings0String{ R"( - { - "defaultProfile": "profile5", - "profiles": [ - { - "name" : "profile0", - "colorScheme": "schemeWithCursorColor" - }, - { - "name" : "profile1", - "colorScheme": "schemeWithoutCursorColor" - }, - { - "name" : "profile2", - "colorScheme": "schemeWithCursorColor", - "cursorColor": "#234567" - }, - { - "name" : "profile3", - "colorScheme": "schemeWithoutCursorColor", - "cursorColor": "#345678" - }, - { - "name" : "profile4", - "cursorColor": "#456789" - }, - { - "name" : "profile5" - } - ], - "schemes": [ - { - "name": "schemeWithCursorColor", - "cursorColor": "#123456" - }, - { - "name": "schemeWithoutCursorColor" - } - ] - })" }; - - CascadiaSettings settings{ til::u8u16(settings0String) }; - - VERIFY_ARE_EQUAL(6u, settings.ActiveProfiles().Size()); - VERIFY_ARE_EQUAL(2u, settings.GlobalSettings().ColorSchemes().Size()); - - auto createTerminalSettings = [&](const auto& profile, const auto& schemes) { - auto terminalSettings{ winrt::make_self() }; - terminalSettings->_ApplyProfileSettings(profile, schemes); - return terminalSettings; - }; - - auto terminalSettings0 = createTerminalSettings(settings.ActiveProfiles().GetAt(0), settings.GlobalSettings().ColorSchemes()); - auto terminalSettings1 = createTerminalSettings(settings.ActiveProfiles().GetAt(1), settings.GlobalSettings().ColorSchemes()); - auto terminalSettings2 = createTerminalSettings(settings.ActiveProfiles().GetAt(2), settings.GlobalSettings().ColorSchemes()); - auto terminalSettings3 = createTerminalSettings(settings.ActiveProfiles().GetAt(3), settings.GlobalSettings().ColorSchemes()); - auto terminalSettings4 = createTerminalSettings(settings.ActiveProfiles().GetAt(4), settings.GlobalSettings().ColorSchemes()); - auto terminalSettings5 = createTerminalSettings(settings.ActiveProfiles().GetAt(5), settings.GlobalSettings().ColorSchemes()); - - VERIFY_ARE_EQUAL(ARGB(0, 0x12, 0x34, 0x56), terminalSettings0->CursorColor()); // from color scheme - VERIFY_ARE_EQUAL(DEFAULT_CURSOR_COLOR, terminalSettings1->CursorColor()); // default - VERIFY_ARE_EQUAL(ARGB(0, 0x23, 0x45, 0x67), terminalSettings2->CursorColor()); // from profile (trumps color scheme) - VERIFY_ARE_EQUAL(ARGB(0, 0x34, 0x56, 0x78), terminalSettings3->CursorColor()); // from profile (not set in color scheme) - VERIFY_ARE_EQUAL(ARGB(0, 0x45, 0x67, 0x89), terminalSettings4->CursorColor()); // from profile (no color scheme) - VERIFY_ARE_EQUAL(DEFAULT_CURSOR_COLOR, terminalSettings5->CursorColor()); // default - } - void SettingsTests::TestIterateCommands() { // For this test, put an iterable command with a given `name`, diff --git a/src/cascadia/LocalTests_TerminalApp/TabTests.cpp b/src/cascadia/LocalTests_TerminalApp/TabTests.cpp index 4d11366db36..e6f15f44d90 100644 --- a/src/cascadia/LocalTests_TerminalApp/TabTests.cpp +++ b/src/cascadia/LocalTests_TerminalApp/TabTests.cpp @@ -330,7 +330,7 @@ namespace TerminalAppLocalTests { // * Create a tab with a profile with GUID 1 // * Reload the settings so that GUID 1 is no longer in the list of profiles - // * Try calling _DuplicateTabViewItem on tab 1 + // * Try calling _DuplicateFocusedTab on tab 1 // * No new tab should be created (and more importantly, the app should not crash) // // Created to test GH#2455 @@ -392,7 +392,7 @@ namespace TerminalAppLocalTests Log::Comment(L"Duplicate the first tab"); result = RunOnUIThread([&page]() { - page->_DuplicateTabViewItem(); + page->_DuplicateFocusedTab(); VERIFY_ARE_EQUAL(2u, page->_tabs.Size()); }); VERIFY_SUCCEEDED(result); @@ -407,7 +407,7 @@ namespace TerminalAppLocalTests Log::Comment(L"Duplicate the tab, and don't crash"); result = RunOnUIThread([&page]() { - page->_DuplicateTabViewItem(); + page->_DuplicateFocusedTab(); VERIFY_ARE_EQUAL(2u, page->_tabs.Size(), L"We should gracefully do nothing here - the profile no longer exists."); }); VERIFY_SUCCEEDED(result); diff --git a/src/cascadia/Remoting/FindTargetWindowArgs.h b/src/cascadia/Remoting/FindTargetWindowArgs.h index 309793afd4e..d7d85848995 100644 --- a/src/cascadia/Remoting/FindTargetWindowArgs.h +++ b/src/cascadia/Remoting/FindTargetWindowArgs.h @@ -25,9 +25,9 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation { struct FindTargetWindowArgs : public FindTargetWindowArgsT { - GETSET_PROPERTY(winrt::Microsoft::Terminal::Remoting::CommandlineArgs, Args, nullptr); - GETSET_PROPERTY(int, ResultTargetWindow, -1); - GETSET_PROPERTY(winrt::hstring, ResultTargetWindowName, L""); + WINRT_PROPERTY(winrt::Microsoft::Terminal::Remoting::CommandlineArgs, Args, nullptr); + WINRT_PROPERTY(int, ResultTargetWindow, -1); + WINRT_PROPERTY(winrt::hstring, ResultTargetWindowName); public: FindTargetWindowArgs(winrt::Microsoft::Terminal::Remoting::CommandlineArgs args) : diff --git a/src/cascadia/Remoting/Monarch.cpp b/src/cascadia/Remoting/Monarch.cpp index 49093daaea5..d519840dbd3 100644 --- a/src/cascadia/Remoting/Monarch.cpp +++ b/src/cascadia/Remoting/Monarch.cpp @@ -149,7 +149,16 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation } } - uint64_t Monarch::_lookupPeasantIdForName(const winrt::hstring& name) + // Method Description: + // - Find the ID of the peasant with the given name. If no such peasant + // exists, then we'll return 0. If we encounter any peasants who have died + // during this process, then we'll remove them from the set of _peasants + // Arguments: + // - name: The window name to look for + // Return Value: + // - 0 if we didn't find the given peasant, otherwise a positive number for + // the window's ID. + uint64_t Monarch::_lookupPeasantIdForName(std::wstring_view name) { if (name.empty()) { @@ -177,12 +186,6 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation // Instead, pull a good ole Java and collect this id for removal // later. peasantsToErase.push_back(id); - - // // Remove the peasant from the list of peasants - // _peasants.erase(id); - // // Remove the peasant from the list of MRU windows. They're dead. - // // They can't be the MRU anymore. - // _clearOldMruEntries(id); } } @@ -452,7 +455,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation // After the event was handled, ResultTargetWindow() will be filled with // the parsed result. const auto targetWindow = findWindowArgs->ResultTargetWindow(); - const auto& targetWindowName = findWindowArgs->ResultTargetWindowName(); + const auto targetWindowName = findWindowArgs->ResultTargetWindowName(); TraceLoggingWrite(g_hRemotingProvider, "Monarch_ProposeCommandline", @@ -506,7 +509,6 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation if (auto targetPeasant{ _getPeasant(windowID) }) { auto result{ winrt::make_self(false) }; - result->WindowName(targetWindowName); try { // This will raise the peasant's ExecuteCommandlineRequested @@ -520,6 +522,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation // If we fail to propose the commandline to the peasant (it // died?) then just tell this process to become a new window // instead. + result->WindowName(targetWindowName); result->ShouldCreateWindow(true); // If this fails, it'll be logged in the following @@ -571,6 +574,16 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation return *result; } + // Method Description: + // - This is an event handler for the IdentifyWindowsRequested event. A + // Peasant may raise that event if they want _all_ windows to identify + // themselves. + // - This will tell each and every peasant to identify themselves. This will + // eventually propagate down to TerminalPage::IdentifyWindow. + // Arguments: + // - + // Return Value: + // - void Monarch::_identifyWindows(const winrt::Windows::Foundation::IInspectable& /*sender*/, const winrt::Windows::Foundation::IInspectable& /*args*/) { diff --git a/src/cascadia/Remoting/Monarch.h b/src/cascadia/Remoting/Monarch.h index 186bcb1c2bc..58a1bdc927e 100644 --- a/src/cascadia/Remoting/Monarch.h +++ b/src/cascadia/Remoting/Monarch.h @@ -67,7 +67,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation winrt::Microsoft::Terminal::Remoting::IPeasant _getPeasant(uint64_t peasantID); uint64_t _getMostRecentPeasantID(bool limitToCurrentDesktop); - uint64_t _lookupPeasantIdForName(const winrt::hstring& name); + uint64_t _lookupPeasantIdForName(std::wstring_view name); void _peasantWindowActivated(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Microsoft::Terminal::Remoting::WindowActivatedArgs& args); diff --git a/src/cascadia/Remoting/Peasant.cpp b/src/cascadia/Remoting/Peasant.cpp index eaaaf317557..55564d1ae9b 100644 --- a/src/cascadia/Remoting/Peasant.cpp +++ b/src/cascadia/Remoting/Peasant.cpp @@ -117,6 +117,14 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation return _lastActivatedArgs; } + // Method Description: + // - Tell this window to display it's window ID. We'll raise a + // DisplayWindowIdRequested event, which will get handled in the AppHost, + // and used to tell the app to display the ID toast. + // Arguments: + // - + // Return Value: + // - void Peasant::DisplayWindowId() { // Not worried about try/catching this. The handler is in AppHost, which @@ -124,6 +132,14 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation _DisplayWindowIdRequestedHandlers(*this, nullptr); } + // Method Description: + // - Raises an event to ask that all windows be identified. This will come + // back to us when the Monarch handles the event and calls our + // DisplayWindowId method. + // Arguments: + // - + // Return Value: + // - void Peasant::RequestIdentifyWindows() { bool successfullyNotified = false; diff --git a/src/cascadia/Remoting/Peasant.h b/src/cascadia/Remoting/Peasant.h index fed09727dc7..67785c95bd1 100644 --- a/src/cascadia/Remoting/Peasant.h +++ b/src/cascadia/Remoting/Peasant.h @@ -30,7 +30,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation winrt::Microsoft::Terminal::Remoting::WindowActivatedArgs GetLastActivatedArgs(); winrt::Microsoft::Terminal::Remoting::CommandlineArgs InitialArgs(); - GETSET_PROPERTY(winrt::hstring, WindowName, L""); + WINRT_PROPERTY(winrt::hstring, WindowName); TYPED_EVENT(WindowActivated, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::WindowActivatedArgs); TYPED_EVENT(ExecuteCommandlineRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::CommandlineArgs); diff --git a/src/cascadia/Remoting/ProposeCommandlineResult.h b/src/cascadia/Remoting/ProposeCommandlineResult.h index b095be75b8b..97b8c7c304f 100644 --- a/src/cascadia/Remoting/ProposeCommandlineResult.h +++ b/src/cascadia/Remoting/ProposeCommandlineResult.h @@ -26,9 +26,9 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation struct ProposeCommandlineResult : public ProposeCommandlineResultT { public: - GETSET_PROPERTY(Windows::Foundation::IReference, Id); - GETSET_PROPERTY(winrt::hstring, WindowName, L""); - GETSET_PROPERTY(bool, ShouldCreateWindow, true); + WINRT_PROPERTY(Windows::Foundation::IReference, Id); + WINRT_PROPERTY(winrt::hstring, WindowName); + WINRT_PROPERTY(bool, ShouldCreateWindow, true); public: ProposeCommandlineResult(bool shouldCreateWindow) : diff --git a/src/cascadia/Remoting/RenameRequestArgs.h b/src/cascadia/Remoting/RenameRequestArgs.h index 62eb89cc297..7fb751ade3b 100644 --- a/src/cascadia/Remoting/RenameRequestArgs.h +++ b/src/cascadia/Remoting/RenameRequestArgs.h @@ -15,8 +15,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation { struct RenameRequestArgs : public RenameRequestArgsT { - GETSET_PROPERTY(winrt::hstring, NewName, L""); - GETSET_PROPERTY(bool, Succeeded, false); + WINRT_PROPERTY(winrt::hstring, NewName); + WINRT_PROPERTY(bool, Succeeded, false); public: RenameRequestArgs(winrt::hstring newName) : diff --git a/src/cascadia/Remoting/WindowActivatedArgs.h b/src/cascadia/Remoting/WindowActivatedArgs.h index 19ea914ef89..b08862e6203 100644 --- a/src/cascadia/Remoting/WindowActivatedArgs.h +++ b/src/cascadia/Remoting/WindowActivatedArgs.h @@ -27,10 +27,10 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation }; struct WindowActivatedArgs : public WindowActivatedArgsT { - GETSET_PROPERTY(uint64_t, PeasantID, 0); - GETSET_PROPERTY(winrt::guid, DesktopID, {}); - GETSET_PROPERTY(winrt::Windows::Foundation::DateTime, ActivatedTime, {}); - GETSET_PROPERTY(uint64_t, Hwnd, 0); + WINRT_PROPERTY(uint64_t, PeasantID, 0); + WINRT_PROPERTY(winrt::guid, DesktopID); + WINRT_PROPERTY(winrt::Windows::Foundation::DateTime, ActivatedTime, {}); + WINRT_PROPERTY(uint64_t, Hwnd, 0); public: WindowActivatedArgs(uint64_t peasantID, diff --git a/src/cascadia/Remoting/WindowManager.cpp b/src/cascadia/Remoting/WindowManager.cpp index cbf36830f77..f76bfd47821 100644 --- a/src/cascadia/Remoting/WindowManager.cpp +++ b/src/cascadia/Remoting/WindowManager.cpp @@ -71,7 +71,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation // Otherwise, the King will tell us if we should make a new window _shouldCreateWindow = _isKing; std::optional givenID; - winrt::hstring givenName = L""; // TODO:MG If we're the king, we might STILL WANT TO GET THE NAME. How do we get the name? + winrt::hstring givenName{}; if (!_isKing) { // The monarch may respond back "you should be a new @@ -120,10 +120,17 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation // possible someone started the _first_ wt with something like `wt // -w king` as the commandline - we want to make sure we set our // name to "king". + // + // The FindTargetWindow event is the WindowManager's way of saying + // "I do not know how to figure out how to turn this list of args + // into a window ID/name. Whoever's listening to this event does, so + // I'll ask them". It's a convoluted way of hooking the + // WindowManager up to AppLogic without actually telling it anything + // about TerminalApp (or even WindowsTerminal) auto findWindowArgs{ winrt::make_self(args) }; _raiseFindTargetWindowRequested(nullptr, *findWindowArgs); - auto responseId = findWindowArgs->ResultTargetWindow(); + const auto responseId = findWindowArgs->ResultTargetWindow(); if (responseId > 0) { givenID = ::base::saturated_cast(responseId); @@ -142,6 +149,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation TraceLoggingWrite(g_hRemotingProvider, "WindowManager_ProposeCommandline_AsMonarch", TraceLoggingBoolean(_shouldCreateWindow, "CreateWindow", "true iff we should create a new window"), + TraceLoggingUInt64(0, "Id", "The ID we should assign our peasant"), TraceLoggingWideString(givenName.c_str(), "Name", "The name we should assign this window"), TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE)); } @@ -150,6 +158,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation TraceLoggingWrite(g_hRemotingProvider, "WindowManager_ProposeCommandline_AsMonarch", TraceLoggingBoolean(_shouldCreateWindow, "CreateWindow", "true iff we should create a new window"), + TraceLoggingUInt64(0, "Id", "The ID we should assign our peasant"), + TraceLoggingWideString(L"", "Name", "The name we should assign this window"), TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE)); } } diff --git a/src/cascadia/Remoting/packages.config b/src/cascadia/Remoting/packages.config index 8a013cf32b2..13c85cc69d9 100644 --- a/src/cascadia/Remoting/packages.config +++ b/src/cascadia/Remoting/packages.config @@ -1,4 +1,4 @@  - + diff --git a/src/cascadia/ShellExtension/PlaceholderType.h b/src/cascadia/ShellExtension/PlaceholderType.h index d56395c9257..81139600d8d 100644 --- a/src/cascadia/ShellExtension/PlaceholderType.h +++ b/src/cascadia/ShellExtension/PlaceholderType.h @@ -27,7 +27,7 @@ namespace winrt::Microsoft::Terminal::ShellExtension::implementation struct PlaceholderType : PlaceholderTypeT { PlaceholderType() = default; - GETSET_PROPERTY(int32_t, Placeholder, 42); + WINRT_PROPERTY(int32_t, Placeholder, 42); }; } diff --git a/src/cascadia/ShellExtension/packages.config b/src/cascadia/ShellExtension/packages.config index 8a013cf32b2..13c85cc69d9 100644 --- a/src/cascadia/ShellExtension/packages.config +++ b/src/cascadia/ShellExtension/packages.config @@ -1,4 +1,4 @@  - + diff --git a/src/cascadia/TerminalApp/ActionPaletteItem.h b/src/cascadia/TerminalApp/ActionPaletteItem.h index 91e0ccb5968..5f3fd73554b 100644 --- a/src/cascadia/TerminalApp/ActionPaletteItem.h +++ b/src/cascadia/TerminalApp/ActionPaletteItem.h @@ -14,7 +14,7 @@ namespace winrt::TerminalApp::implementation ActionPaletteItem() = default; ActionPaletteItem(Microsoft::Terminal::Settings::Model::Command const& command); - GETSET_PROPERTY(Microsoft::Terminal::Settings::Model::Command, Command, nullptr); + WINRT_PROPERTY(Microsoft::Terminal::Settings::Model::Command, Command, nullptr); private: Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _commandChangedRevoker; diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp index 1ed298e256f..f6f785d4f59 100644 --- a/src/cascadia/TerminalApp/AppActionHandlers.cpp +++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp @@ -39,7 +39,7 @@ namespace winrt::TerminalApp::implementation void TerminalPage::_HandleDuplicateTab(const IInspectable& /*sender*/, const ActionEventArgs& args) { - _DuplicateTabViewItem(); + _DuplicateFocusedTab(); args.Handled(true); } @@ -376,7 +376,7 @@ namespace winrt::TerminalApp::implementation if (const auto scheme = _settings.GlobalSettings().ColorSchemes().TryLookup(realArgs.SchemeName())) { auto controlSettings = activeControl.Settings().as(); - controlSettings->ApplyColorScheme(scheme); + controlSettings.ApplyColorScheme(scheme); activeControl.UpdateSettings(); args.Handled(true); } @@ -598,8 +598,8 @@ namespace winrt::TerminalApp::implementation // - // Important: Don't take the param by reference, since we'll be doing work // on another thread. - fire_and_forget _OpenNewWindow(const bool elevate, - const NewTerminalArgs newTerminalArgs) + fire_and_forget TerminalPage::_OpenNewWindow(const bool elevate, + const NewTerminalArgs newTerminalArgs) { // Hop to the BG thread co_await winrt::resume_background(); @@ -658,9 +658,8 @@ namespace winrt::TerminalApp::implementation newTerminalArgs = NewTerminalArgs(); } - auto [profileGuid, settings] = TerminalSettings::BuildSettings(_settings, - newTerminalArgs, - *_bindings); + const auto profileGuid{ _settings.GetProfileForArgs(newTerminalArgs) }; + const auto settings{ TerminalSettings::CreateWithNewTerminalArgs(_settings, newTerminalArgs, *_bindings) }; // Manually fill in the evaluated profile. newTerminalArgs.Profile(::Microsoft::Console::Utils::GuidToString(profileGuid)); @@ -668,24 +667,36 @@ namespace winrt::TerminalApp::implementation actionArgs.Handled(true); } + // Method Description: + // - Raise a IdentifyWindowsRequested event. This will bubble up to the + // AppLogic, to the AppHost, to the Peasant, to the Monarch, then get + // distributed down to _all_ the Peasants, as to display info about the + // window in _every_ Peasant window. + // - This action is also buggy right now, because TeachingTips behave + // weird in XAML Islands. See microsoft-ui-xaml#4382 + // Arguments: + // - + // Return Value: + // - void TerminalPage::_HandleIdentifyWindows(const IInspectable& /*sender*/, const ActionEventArgs& args) { - // Raise a IdentifyWindowsRequested event. This will bubble up to the - // AppLogic, to the AppHost, to the Peasant, to the Monarch, then get - // distributed down to _all_ the Peasants, as to display info about the - // window in _every_ Peasant window. - // - // This action is also buggy right now, because TeachingTips behave - // weird in XAML Islands. See microsoft-ui-xaml#4382 _IdentifyWindowsRequestedHandlers(*this, nullptr); args.Handled(true); } + + // Method Description: + // - Display the "Toast" with the name and ID of this window. + // - Unlike _HandleIdentifyWindow**s**, this event just displays the window + // ID and name in the current window. It does not involve any bubbling + // up/down the page/logic/host/manager/peasant/monarch. + // Arguments: + // - + // Return Value: + // - void TerminalPage::_HandleIdentifyWindow(const IInspectable& /*sender*/, const ActionEventArgs& args) { - // Unlike _HandleIdentifyWindow**s**, this event just displays the - // window ID and name in the current window. IdentifyWindow(); args.Handled(true); } diff --git a/src/cascadia/TerminalApp/AppCommandlineArgs.cpp b/src/cascadia/TerminalApp/AppCommandlineArgs.cpp index 030fe2780cc..67b75906841 100644 --- a/src/cascadia/TerminalApp/AppCommandlineArgs.cpp +++ b/src/cascadia/TerminalApp/AppCommandlineArgs.cpp @@ -417,6 +417,11 @@ void AppCommandlineArgs::_addNewTerminalArgs(AppCommandlineArgs::NewTerminalSubc _startingTabColor, RS_A(L"CmdTabColorArgDesc")); + subcommand.suppressApplicationTitleOption = subcommand.subcommand->add_flag( + "--suppressApplicationTitle,!--useApplicationTitle", + _suppressApplicationTitle, + RS_A(L"CmdSuppressApplicationTitleDesc")); + // Using positionals_at_end allows us to support "wt new-tab -d wsl -d Ubuntu" // without CLI11 thinking that we've specified -d twice. // There's an alternate construction where we make all subcommands "prefix commands", @@ -484,6 +489,11 @@ NewTerminalArgs AppCommandlineArgs::_getNewTerminalArgs(AppCommandlineArgs::NewT args.TabColor(static_cast(tabColor)); } + if (*subcommand.suppressApplicationTitleOption) + { + args.SuppressApplicationTitle(_suppressApplicationTitle); + } + return args; } @@ -522,6 +532,7 @@ void AppCommandlineArgs::_resetStateToDefault() _startingTitle.clear(); _startingTabColor.clear(); _commandline.clear(); + _suppressApplicationTitle = false; _splitVertical = false; _splitHorizontal = false; @@ -854,16 +865,10 @@ void AppCommandlineArgs::FullResetState() _exitMessage = ""; _shouldExitEarly = false; - _windowTarget = ""; + _windowTarget = {}; } std::string_view AppCommandlineArgs::GetTargetWindow() const noexcept { - // // If the user provides _any_ negative number, then treat it as -1, for "use a new window". - // if (_windowTarget.has_value() && *_windowTarget < 0) - // { - // return { -1 }; - // } - return _windowTarget; } diff --git a/src/cascadia/TerminalApp/AppCommandlineArgs.h b/src/cascadia/TerminalApp/AppCommandlineArgs.h index 9c8662293e1..4c3456932eb 100644 --- a/src/cascadia/TerminalApp/AppCommandlineArgs.h +++ b/src/cascadia/TerminalApp/AppCommandlineArgs.h @@ -61,6 +61,7 @@ class TerminalApp::AppCommandlineArgs final CLI::Option* startingDirectoryOption; CLI::Option* titleOption; CLI::Option* tabColorOption; + CLI::Option* suppressApplicationTitleOption; }; struct NewPaneSubcommand : public NewTerminalSubcommand @@ -85,6 +86,7 @@ class TerminalApp::AppCommandlineArgs final std::string _startingDirectory; std::string _startingTitle; std::string _startingTabColor; + bool _suppressApplicationTitle{ false }; winrt::Microsoft::Terminal::Settings::Model::FocusDirection _moveFocusDirection{ winrt::Microsoft::Terminal::Settings::Model::FocusDirection::None }; @@ -106,7 +108,7 @@ class TerminalApp::AppCommandlineArgs final std::string _exitMessage; bool _shouldExitEarly{ false }; - std::string _windowTarget{ "" }; + std::string _windowTarget{}; // Are you adding more args or attributes here? If they are not reset in _resetStateToDefault, make sure to reset them in FullResetState winrt::Microsoft::Terminal::Settings::Model::NewTerminalArgs _getNewTerminalArgs(NewTerminalSubcommand& subcommand); diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp index 06fd8904c98..d83eefad209 100644 --- a/src/cascadia/TerminalApp/AppLogic.cpp +++ b/src/cascadia/TerminalApp/AppLogic.cpp @@ -46,7 +46,8 @@ static const std::array(SettingsLoadWar USES_RESOURCE(L"FailedToWriteToSettings"), USES_RESOURCE(L"InvalidColorSchemeInCmd"), USES_RESOURCE(L"InvalidSplitSize"), - USES_RESOURCE(L"FailedToParseStartupActions") + USES_RESOURCE(L"FailedToParseStartupActions"), + USES_RESOURCE(L"FailedToParseSubCommands"), }; static const std::array(SettingsLoadErrors::ERRORS_SIZE)> settingsLoadErrorsLabels { USES_RESOURCE(L"NoProfilesText"), @@ -576,7 +577,7 @@ namespace winrt::TerminalApp::implementation } // Use the default profile to determine how big of a window we need. - const auto [_, settings] = TerminalSettings::BuildSettings(_settings, nullptr, nullptr); + const auto settings{ TerminalSettings::CreateWithNewTerminalArgs(_settings, nullptr, nullptr) }; auto proposedSize = TermControl::GetProposedDimensions(settings, dpi); @@ -1240,7 +1241,7 @@ namespace winrt::TerminalApp::implementation { if (!appArgs.GetExitMessage().empty()) { - return winrt::make(WindowingBehaviorUseNew, L""); + return winrt::make(WindowingBehaviorUseNew); } const std::string parsedTarget{ appArgs.GetTargetWindow() }; @@ -1263,7 +1264,7 @@ namespace winrt::TerminalApp::implementation windowId = WindowingBehaviorUseAnyExisting; break; } - return winrt::make(windowId, L""); + return winrt::make(windowId); } // Here, the user _has_ provided a window-id on the commandline. @@ -1282,7 +1283,7 @@ namespace winrt::TerminalApp::implementation // Hooray! This is a valid integer. The set of possible values // here is {-1, 0, ℤ+}. Let's return that window ID. - return winrt::make(windowId, L""); + return winrt::make(windowId); } catch (...) { @@ -1292,11 +1293,11 @@ namespace winrt::TerminalApp::implementation // First, check the reserved keywords: if (parsedTarget == "new") { - return winrt::make(WindowingBehaviorUseNew, L""); + return winrt::make(WindowingBehaviorUseNew); } else if (parsedTarget == "last") { - return winrt::make(WindowingBehaviorUseExisting, L""); + return winrt::make(WindowingBehaviorUseExisting); } else { @@ -1319,7 +1320,7 @@ namespace winrt::TerminalApp::implementation // create a new window. Then, in that new window, we'll try to set the // StartupActions, which will again fail, returning the correct error // message. - return winrt::make(WindowingBehaviorUseNew, L""); + return winrt::make(WindowingBehaviorUseNew); } // Method Description: diff --git a/src/cascadia/TerminalApp/AppLogic.h b/src/cascadia/TerminalApp/AppLogic.h index 68599cde67e..87082590e4d 100644 --- a/src/cascadia/TerminalApp/AppLogic.h +++ b/src/cascadia/TerminalApp/AppLogic.h @@ -21,12 +21,15 @@ namespace winrt::TerminalApp::implementation { struct FindTargetWindowResult : FindTargetWindowResultT { - GETSET_PROPERTY(int32_t, WindowId, -1); - GETSET_PROPERTY(winrt::hstring, WindowName, L""); + WINRT_PROPERTY(int32_t, WindowId, -1); + WINRT_PROPERTY(winrt::hstring, WindowName, L""); public: FindTargetWindowResult(const int32_t id, const winrt::hstring& name) : _WindowId{ id }, _WindowName{ name } {}; + + FindTargetWindowResult(const int32_t id) : + FindTargetWindowResult(id, L""){}; }; struct AppLogic : AppLogicT diff --git a/src/cascadia/TerminalApp/CommandLinePaletteItem.h b/src/cascadia/TerminalApp/CommandLinePaletteItem.h index 9251456e590..b4a92d435e7 100644 --- a/src/cascadia/TerminalApp/CommandLinePaletteItem.h +++ b/src/cascadia/TerminalApp/CommandLinePaletteItem.h @@ -14,7 +14,7 @@ namespace winrt::TerminalApp::implementation CommandLinePaletteItem() = default; CommandLinePaletteItem(winrt::hstring const& commandLine); - GETSET_PROPERTY(winrt::hstring, CommandLine); + WINRT_PROPERTY(winrt::hstring, CommandLine); }; } diff --git a/src/cascadia/TerminalApp/CommandPalette.h b/src/cascadia/TerminalApp/CommandPalette.h index 8b8528e9711..0b51191859a 100644 --- a/src/cascadia/TerminalApp/CommandPalette.h +++ b/src/cascadia/TerminalApp/CommandPalette.h @@ -48,12 +48,12 @@ namespace winrt::TerminalApp::implementation void EnableTabSearchMode(); WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler); - OBSERVABLE_GETSET_PROPERTY(winrt::hstring, NoMatchesText, _PropertyChangedHandlers); - OBSERVABLE_GETSET_PROPERTY(winrt::hstring, SearchBoxPlaceholderText, _PropertyChangedHandlers); - OBSERVABLE_GETSET_PROPERTY(winrt::hstring, PrefixCharacter, _PropertyChangedHandlers); - OBSERVABLE_GETSET_PROPERTY(winrt::hstring, ControlName, _PropertyChangedHandlers); - OBSERVABLE_GETSET_PROPERTY(winrt::hstring, ParentCommandName, _PropertyChangedHandlers); - OBSERVABLE_GETSET_PROPERTY(winrt::hstring, ParsedCommandLineText, _PropertyChangedHandlers); + WINRT_OBSERVABLE_PROPERTY(winrt::hstring, NoMatchesText, _PropertyChangedHandlers); + WINRT_OBSERVABLE_PROPERTY(winrt::hstring, SearchBoxPlaceholderText, _PropertyChangedHandlers); + WINRT_OBSERVABLE_PROPERTY(winrt::hstring, PrefixCharacter, _PropertyChangedHandlers); + WINRT_OBSERVABLE_PROPERTY(winrt::hstring, ControlName, _PropertyChangedHandlers); + WINRT_OBSERVABLE_PROPERTY(winrt::hstring, ParentCommandName, _PropertyChangedHandlers); + WINRT_OBSERVABLE_PROPERTY(winrt::hstring, ParsedCommandLineText, _PropertyChangedHandlers); TYPED_EVENT(SwitchToTabRequested, winrt::TerminalApp::CommandPalette, winrt::TerminalApp::TabBase); TYPED_EVENT(CommandLineExecutionRequested, winrt::TerminalApp::CommandPalette, winrt::hstring); diff --git a/src/cascadia/TerminalApp/FilteredCommand.h b/src/cascadia/TerminalApp/FilteredCommand.h index eb1f14436fe..b6aef7c995c 100644 --- a/src/cascadia/TerminalApp/FilteredCommand.h +++ b/src/cascadia/TerminalApp/FilteredCommand.h @@ -25,10 +25,10 @@ namespace winrt::TerminalApp::implementation static int Compare(winrt::TerminalApp::FilteredCommand const& first, winrt::TerminalApp::FilteredCommand const& second); WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler); - OBSERVABLE_GETSET_PROPERTY(winrt::TerminalApp::PaletteItem, Item, _PropertyChangedHandlers, nullptr); - OBSERVABLE_GETSET_PROPERTY(winrt::hstring, Filter, _PropertyChangedHandlers); - OBSERVABLE_GETSET_PROPERTY(winrt::TerminalApp::HighlightedText, HighlightedName, _PropertyChangedHandlers); - OBSERVABLE_GETSET_PROPERTY(int, Weight, _PropertyChangedHandlers); + WINRT_OBSERVABLE_PROPERTY(winrt::TerminalApp::PaletteItem, Item, _PropertyChangedHandlers, nullptr); + WINRT_OBSERVABLE_PROPERTY(winrt::hstring, Filter, _PropertyChangedHandlers); + WINRT_OBSERVABLE_PROPERTY(winrt::TerminalApp::HighlightedText, HighlightedName, _PropertyChangedHandlers); + WINRT_OBSERVABLE_PROPERTY(int, Weight, _PropertyChangedHandlers); private: winrt::TerminalApp::HighlightedText _computeHighlightedName(); diff --git a/src/cascadia/TerminalApp/HighlightedText.h b/src/cascadia/TerminalApp/HighlightedText.h index c4e5e464aab..88567d36a7e 100644 --- a/src/cascadia/TerminalApp/HighlightedText.h +++ b/src/cascadia/TerminalApp/HighlightedText.h @@ -17,8 +17,8 @@ namespace winrt::TerminalApp::implementation HighlightedTextSegment(winrt::hstring const& text, bool isHighlighted); WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler); - OBSERVABLE_GETSET_PROPERTY(winrt::hstring, TextSegment, _PropertyChangedHandlers); - OBSERVABLE_GETSET_PROPERTY(bool, IsHighlighted, _PropertyChangedHandlers); + WINRT_OBSERVABLE_PROPERTY(winrt::hstring, TextSegment, _PropertyChangedHandlers); + WINRT_OBSERVABLE_PROPERTY(bool, IsHighlighted, _PropertyChangedHandlers); }; struct HighlightedText : HighlightedTextT @@ -27,7 +27,7 @@ namespace winrt::TerminalApp::implementation HighlightedText(Windows::Foundation::Collections::IObservableVector const& segments); WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler); - OBSERVABLE_GETSET_PROPERTY(Windows::Foundation::Collections::IObservableVector, Segments, _PropertyChangedHandlers); + WINRT_OBSERVABLE_PROPERTY(Windows::Foundation::Collections::IObservableVector, Segments, _PropertyChangedHandlers); }; } diff --git a/src/cascadia/TerminalApp/MinMaxCloseControl.cpp b/src/cascadia/TerminalApp/MinMaxCloseControl.cpp index ee5f3e0d9b7..72b48b7f739 100644 --- a/src/cascadia/TerminalApp/MinMaxCloseControl.cpp +++ b/src/cascadia/TerminalApp/MinMaxCloseControl.cpp @@ -10,6 +10,9 @@ #include "MinMaxCloseControl.h" #include "MinMaxCloseControl.g.cpp" + +#include + using namespace winrt::Windows::UI::Xaml; namespace winrt::TerminalApp::implementation @@ -77,6 +80,7 @@ namespace winrt::TerminalApp::implementation MinimizeButton().Height(maximizedHeight); MaximizeButton().Height(maximizedHeight); CloseButton().Height(maximizedHeight); + MaximizeToolTip().Text(RS_(L"WindowRestoreDownButtonToolTip")); break; case WindowVisualState::WindowVisualStateNormal: @@ -87,6 +91,7 @@ namespace winrt::TerminalApp::implementation MinimizeButton().Height(windowedHeight); MaximizeButton().Height(windowedHeight); CloseButton().Height(windowedHeight); + MaximizeToolTip().Text(RS_(L"WindowMaximizeButtonToolTip")); break; } } diff --git a/src/cascadia/TerminalApp/MinMaxCloseControl.xaml b/src/cascadia/TerminalApp/MinMaxCloseControl.xaml index bb7566cc004..d84de6b6518 100644 --- a/src/cascadia/TerminalApp/MinMaxCloseControl.xaml +++ b/src/cascadia/TerminalApp/MinMaxCloseControl.xaml @@ -188,6 +188,13 @@ the MIT License. See LICENSE in the project root for license information. --> M 0 2 h 8 v 8 h -8 v -8 M 2 2 v -2 h 8 v 8 h -2 + + + + + + +