diff --git a/OpenConsole.sln b/OpenConsole.sln index 493cba041da..7afd974a612 100644 --- a/OpenConsole.sln +++ b/OpenConsole.sln @@ -336,6 +336,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WpfTerminalTestNetCore", "s EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "wt", "src\cascadia\wt\wt.vcxproj", "{506FD703-BAA7-4F6E-9361-64F550EC8FCA}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "elevate-shim", "src\cascadia\ElevateShim\elevate-shim.vcxproj", "{416FD703-BAA7-4F6E-9361-64F550EC8FCA}" +EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.Terminal.Settings.Editor", "src\cascadia\TerminalSettingsEditor\Microsoft.Terminal.Settings.Editor.vcxproj", "{CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32}" ProjectSection(ProjectDependencies) = postProject {CA5CAD1A-082C-4476-9F33-94B339494076} = {CA5CAD1A-082C-4476-9F33-94B339494076} @@ -400,6 +402,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WindowsTerminal.UIA.Tests", EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "api-ms-win-core-synch-l1-2-0", "src\api-ms-win-core-synch-l1-2-0\api-ms-win-core-synch-l1-2-0.vcxproj", "{9CF74355-F018-4C19-81AD-9DC6B7F2C6F5}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Utils", "Utils", "{61901E80-E97D-4D61-A9BB-E8F2FDA8B40C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution AuditMode|Any CPU = AuditMode|Any CPU @@ -2803,6 +2807,43 @@ 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 + {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 + {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 + {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 + {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.AuditMode|ARM64.Build.0 = AuditMode|ARM64 + {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.AuditMode|DotNet_x64Test.ActiveCfg = AuditMode|Win32 + {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.AuditMode|DotNet_x86Test.ActiveCfg = AuditMode|Win32 + {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.AuditMode|x64.ActiveCfg = AuditMode|x64 + {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.AuditMode|x64.Build.0 = AuditMode|x64 + {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.AuditMode|x86.ActiveCfg = AuditMode|Win32 + {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.AuditMode|x86.Build.0 = AuditMode|Win32 + {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.Debug|ARM.ActiveCfg = Debug|Win32 + {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.Debug|ARM64.Build.0 = Debug|ARM64 + {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.Debug|DotNet_x64Test.ActiveCfg = Debug|Win32 + {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.Debug|DotNet_x86Test.ActiveCfg = Debug|Win32 + {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.Debug|x64.ActiveCfg = Debug|x64 + {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.Debug|x64.Build.0 = Debug|x64 + {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.Debug|x86.ActiveCfg = Debug|Win32 + {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.Debug|x86.Build.0 = Debug|Win32 + {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 + {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 + {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 + {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.Fuzzing|DotNet_x64Test.ActiveCfg = Fuzzing|Win32 + {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.Fuzzing|DotNet_x86Test.ActiveCfg = Fuzzing|Win32 + {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 + {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 + {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.Release|Any CPU.ActiveCfg = Release|Win32 + {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.Release|ARM.ActiveCfg = Release|Win32 + {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.Release|ARM64.ActiveCfg = Release|ARM64 + {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.Release|ARM64.Build.0 = Release|ARM64 + {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.Release|DotNet_x64Test.ActiveCfg = Release|Win32 + {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.Release|DotNet_x86Test.ActiveCfg = Release|Win32 + {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.Release|x64.ActiveCfg = Release|x64 + {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.Release|x64.Build.0 = Release|x64 + {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.Release|x86.ActiveCfg = Release|Win32 + {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.Release|x86.Build.0 = Release|Win32 {CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32}.AuditMode|Any CPU.ActiveCfg = Release|x64 {CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32}.AuditMode|Any CPU.Build.0 = Release|x64 {CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32}.AuditMode|Any CPU.Deploy.0 = Release|x64 @@ -3403,7 +3444,7 @@ Global {CA5CAD1A-9A12-429C-B551-8562EC954746} = {59840756-302F-44DF-AA47-441A9D673202} {CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506} = {BDB237B6-1D1D-400F-84CC-40A58FA59C8E} {48D21369-3D7B-4431-9967-24E81292CF63} = {05500DEF-2294-41E3-AF9A-24E580B82836} - {CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE} = {59840756-302F-44DF-AA47-441A9D673202} + {CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE} = {61901E80-E97D-4D61-A9BB-E8F2FDA8B40C} {B0AC39D6-7B40-49A9-8202-58549BAE1FB1} = {59840756-302F-44DF-AA47-441A9D673202} {58A03BB2-DF5A-4B66-91A0-7EF3BA01269A} = {E8F24881-5E37-4362-B191-A3BA0ED7F4EB} {A22EC5F6-7851-4B88-AC52-47249D437A52} = {E8F24881-5E37-4362-B191-A3BA0ED7F4EB} @@ -3416,10 +3457,11 @@ Global {D3EF7B96-CD5E-47C9-B9A9-136259563033} = {04170EEF-983A-4195-BFEF-2321E5E38A1E} {95B136F9-B238-490C-A7C5-5843C1FECAC4} = {05500DEF-2294-41E3-AF9A-24E580B82836} {024052DE-83FB-4653-AEA4-90790D29D5BD} = {E8F24881-5E37-4362-B191-A3BA0ED7F4EB} - {067F0A06-FCB7-472C-96E9-B03B54E8E18D} = {59840756-302F-44DF-AA47-441A9D673202} + {067F0A06-FCB7-472C-96E9-B03B54E8E18D} = {61901E80-E97D-4D61-A9BB-E8F2FDA8B40C} {6BAE5851-50D5-4934-8D5E-30361A8A40F3} = {81C352DB-1818-45B7-A284-18E259F1CC87} {1588FD7C-241E-4E7D-9113-43735F3E6BAD} = {4DAF0299-495E-4CD1-A982-9BAC16A45932} - {506FD703-BAA7-4F6E-9361-64F550EC8FCA} = {59840756-302F-44DF-AA47-441A9D673202} + {506FD703-BAA7-4F6E-9361-64F550EC8FCA} = {61901E80-E97D-4D61-A9BB-E8F2FDA8B40C} + {416FD703-BAA7-4F6E-9361-64F550EC8FCA} = {61901E80-E97D-4D61-A9BB-E8F2FDA8B40C} {CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32} = {77875138-BB08-49F9-8BB1-409C2150E0E1} {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907} = {77875138-BB08-49F9-8BB1-409C2150E0E1} {CA5CAD1A-082C-4476-9F33-94B339494076} = {77875138-BB08-49F9-8BB1-409C2150E0E1} @@ -3438,6 +3480,7 @@ Global {C323DAEE-B307-4C7B-ACE5-7293CBEFCB5B} = {BDB237B6-1D1D-400F-84CC-40A58FA59C8E} {F19DACD5-0C6E-40DC-B6E4-767A3200542C} = {BDB237B6-1D1D-400F-84CC-40A58FA59C8E} {9CF74355-F018-4C19-81AD-9DC6B7F2C6F5} = {89CDCC5C-9F53-4054-97A4-639D99F169CD} + {61901E80-E97D-4D61-A9BB-E8F2FDA8B40C} = {59840756-302F-44DF-AA47-441A9D673202} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3140B1B7-C8EE-43D1-A772-D82A7061A271} diff --git a/src/cascadia/CascadiaPackage/CascadiaPackage.wapproj b/src/cascadia/CascadiaPackage/CascadiaPackage.wapproj index ba722f24665..c898bfd0b22 100644 --- a/src/cascadia/CascadiaPackage/CascadiaPackage.wapproj +++ b/src/cascadia/CascadiaPackage/CascadiaPackage.wapproj @@ -68,6 +68,7 @@ + diff --git a/src/cascadia/ElevateShim/elevate-shim.cpp b/src/cascadia/ElevateShim/elevate-shim.cpp new file mode 100644 index 00000000000..30670043f03 --- /dev/null +++ b/src/cascadia/ElevateShim/elevate-shim.cpp @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include +#include + +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include +#include + +// BODGY +// +// If we try to do this in the Terminal itself, then there's a bunch of weird +// things that can go wrong and prevent the elevated Terminal window from +// getting created. Specifically, if the origin Terminal exits right away after +// spawning the elevated WT, then ShellExecute might not successfully complete +// the elevation. What's even more, the Terminal will mysteriously crash +// somewhere in XAML land. +// +// To mitigate this, the Terminal will call into us with the commandline it +// wants elevated. We'll hang around until ShellExecute is finished, so that the +// process can successfully elevate. + +#pragma warning(suppress : 26461) // we can't change the signature of wWinMain +int __stdcall wWinMain(HINSTANCE, HINSTANCE, LPWSTR pCmdLine, int) +{ + // All of the args passed to us (something like `new-tab -p {guid}`) are in + // pCmdLine + + // Get the path to WindowsTerminal.exe, which should live next to us. + std::filesystem::path module{ wil::GetModuleFileNameW(nullptr) }; + // Swap elevate-shim.exe for WindowsTerminal.exe + module.replace_filename(L"WindowsTerminal.exe"); + + // Go! + SHELLEXECUTEINFOW seInfo{ 0 }; + seInfo.cbSize = sizeof(seInfo); + seInfo.fMask = SEE_MASK_DEFAULT; + seInfo.lpVerb = L"runas"; // This asks the shell to elevate the process + seInfo.lpFile = module.c_str(); // This is `...\WindowsTerminal.exe` + seInfo.lpParameters = pCmdLine; // This is `new-tab -p {guid}` + seInfo.nShow = SW_SHOWNORMAL; + LOG_IF_WIN32_BOOL_FALSE(ShellExecuteExW(&seInfo)); +} diff --git a/src/cascadia/ElevateShim/elevate-shim.rc b/src/cascadia/ElevateShim/elevate-shim.rc new file mode 100644 index 00000000000..20d7e068e25 --- /dev/null +++ b/src/cascadia/ElevateShim/elevate-shim.rc @@ -0,0 +1,69 @@ +// 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""\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" + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/src/cascadia/ElevateShim/elevate-shim.vcxproj b/src/cascadia/ElevateShim/elevate-shim.vcxproj new file mode 100644 index 00000000000..f81353ad368 --- /dev/null +++ b/src/cascadia/ElevateShim/elevate-shim.vcxproj @@ -0,0 +1,34 @@ + + + + {416fd703-baa7-4f6e-9361-64f550ec8fca} + Win32Proj + elevate-shim + elevate-shim + elevate-shim + Application + + + + + + + + + NotUsing + + + + + + + + + + + + + onecore.lib + + + diff --git a/src/cascadia/ElevateShim/resource.h b/src/cascadia/ElevateShim/resource.h new file mode 100644 index 00000000000..62d8839e293 --- /dev/null +++ b/src/cascadia/ElevateShim/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by wt.rc +// +#define IDI_APPICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp b/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp index f2c6fac9d81..a3dcbb45f4e 100644 --- a/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp +++ b/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp @@ -69,6 +69,8 @@ namespace TerminalAppLocalTests TEST_METHOD(TestIterableColorSchemeCommands); + TEST_METHOD(TestElevateArg); + TEST_CLASS_SETUP(ClassSetup) { return true; @@ -1267,4 +1269,264 @@ namespace TerminalAppLocalTests } } + void SettingsTests::TestElevateArg() + { + static constexpr std::wstring_view settingsJson{ LR"( + { + "defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}", + "profiles": [ + { + "name": "profile0", + "guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}", + "commandline": "cmd.exe" + }, + { + "name": "profile1", + "guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}", + "elevate": true, + "commandline": "pwsh.exe" + }, + { + "name": "profile2", + "guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}", + "elevate": false, + "commandline": "wsl.exe" + } + ], + "keybindings": [ + { "keys": ["ctrl+a"], "command": { "action": "newTab", "profile": "profile0" } }, + { "keys": ["ctrl+b"], "command": { "action": "newTab", "profile": "profile1" } }, + { "keys": ["ctrl+c"], "command": { "action": "newTab", "profile": "profile2" } }, + + { "keys": ["ctrl+d"], "command": { "action": "newTab", "profile": "profile0", "elevate": false } }, + { "keys": ["ctrl+e"], "command": { "action": "newTab", "profile": "profile1", "elevate": false } }, + { "keys": ["ctrl+f"], "command": { "action": "newTab", "profile": "profile2", "elevate": false } }, + + { "keys": ["ctrl+g"], "command": { "action": "newTab", "profile": "profile0", "elevate": true } }, + { "keys": ["ctrl+h"], "command": { "action": "newTab", "profile": "profile1", "elevate": true } }, + { "keys": ["ctrl+i"], "command": { "action": "newTab", "profile": "profile2", "elevate": true } }, + ] + })" }; + + 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}") }; + const winrt::guid guid2{ ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}") }; + + CascadiaSettings settings{ settingsJson, {} }; + + auto keymap = settings.GlobalSettings().ActionMap(); + 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(9u, keymap.KeyBindings().Size()); + + { + Log::Comment(L"profile.elevate=omitted, action.elevate=nullopt: don't auto elevate"); + + KeyChord kc{ true, false, false, false, static_cast('A'), 0 }; + 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_FALSE(realArgs.TerminalArgs().Profile().empty()); + VERIFY_ARE_EQUAL(L"profile0", realArgs.TerminalArgs().Profile()); + VERIFY_IS_NULL(realArgs.TerminalArgs().Elevate()); + + const auto termSettingsResult = TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr); + const auto termSettings = termSettingsResult.DefaultSettings(); + VERIFY_ARE_EQUAL(L"cmd.exe", termSettings.Commandline()); + VERIFY_ARE_EQUAL(false, termSettings.Elevate()); + } + { + Log::Comment(L"profile.elevate=true, action.elevate=nullopt: DO auto elevate"); + + KeyChord kc{ true, false, false, false, static_cast('B'), 0 }; + 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_FALSE(realArgs.TerminalArgs().Profile().empty()); + VERIFY_ARE_EQUAL(L"profile1", realArgs.TerminalArgs().Profile()); + VERIFY_IS_NULL(realArgs.TerminalArgs().Elevate()); + + const auto termSettingsResult = TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr); + const auto termSettings = termSettingsResult.DefaultSettings(); + VERIFY_ARE_EQUAL(L"pwsh.exe", termSettings.Commandline()); + VERIFY_ARE_EQUAL(true, termSettings.Elevate()); + } + { + Log::Comment(L"profile.elevate=false, action.elevate=nullopt: don't auto elevate"); + + KeyChord kc{ true, false, false, false, static_cast('C'), 0 }; + 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_FALSE(realArgs.TerminalArgs().Profile().empty()); + VERIFY_ARE_EQUAL(L"profile2", realArgs.TerminalArgs().Profile()); + VERIFY_IS_NULL(realArgs.TerminalArgs().Elevate()); + + const auto termSettingsResult = TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr); + const auto termSettings = termSettingsResult.DefaultSettings(); + VERIFY_ARE_EQUAL(L"wsl.exe", termSettings.Commandline()); + VERIFY_ARE_EQUAL(false, termSettings.Elevate()); + } + + { + Log::Comment(L"profile.elevate=omitted, action.elevate=false: don't auto elevate"); + + KeyChord kc{ true, false, false, false, static_cast('D'), 0 }; + 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_FALSE(realArgs.TerminalArgs().Profile().empty()); + VERIFY_ARE_EQUAL(L"profile0", realArgs.TerminalArgs().Profile()); + VERIFY_IS_NOT_NULL(realArgs.TerminalArgs().Elevate()); + VERIFY_IS_FALSE(realArgs.TerminalArgs().Elevate().Value()); + + const auto termSettingsResult = TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr); + const auto termSettings = termSettingsResult.DefaultSettings(); + VERIFY_ARE_EQUAL(L"cmd.exe", termSettings.Commandline()); + VERIFY_ARE_EQUAL(false, termSettings.Elevate()); + } + { + Log::Comment(L"profile.elevate=true, action.elevate=false: don't auto elevate"); + + KeyChord kc{ true, false, false, false, static_cast('E'), 0 }; + 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_FALSE(realArgs.TerminalArgs().Profile().empty()); + VERIFY_ARE_EQUAL(L"profile1", realArgs.TerminalArgs().Profile()); + VERIFY_IS_NOT_NULL(realArgs.TerminalArgs().Elevate()); + VERIFY_IS_FALSE(realArgs.TerminalArgs().Elevate().Value()); + + const auto termSettingsResult = TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr); + const auto termSettings = termSettingsResult.DefaultSettings(); + VERIFY_ARE_EQUAL(L"pwsh.exe", termSettings.Commandline()); + VERIFY_ARE_EQUAL(false, termSettings.Elevate()); + } + { + Log::Comment(L"profile.elevate=false, action.elevate=false: don't auto elevate"); + + KeyChord kc{ true, false, false, false, static_cast('F'), 0 }; + 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_FALSE(realArgs.TerminalArgs().Profile().empty()); + VERIFY_ARE_EQUAL(L"profile2", realArgs.TerminalArgs().Profile()); + VERIFY_IS_NOT_NULL(realArgs.TerminalArgs().Elevate()); + VERIFY_IS_FALSE(realArgs.TerminalArgs().Elevate().Value()); + + const auto termSettingsResult = TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr); + const auto termSettings = termSettingsResult.DefaultSettings(); + VERIFY_ARE_EQUAL(L"wsl.exe", termSettings.Commandline()); + VERIFY_ARE_EQUAL(false, termSettings.Elevate()); + } + + { + Log::Comment(L"profile.elevate=omitted, action.elevate=true: DO auto elevate"); + + KeyChord kc{ true, false, false, false, static_cast('G'), 0 }; + 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_FALSE(realArgs.TerminalArgs().Profile().empty()); + VERIFY_ARE_EQUAL(L"profile0", realArgs.TerminalArgs().Profile()); + VERIFY_IS_NOT_NULL(realArgs.TerminalArgs().Elevate()); + VERIFY_IS_TRUE(realArgs.TerminalArgs().Elevate().Value()); + + const auto termSettingsResult = TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr); + const auto termSettings = termSettingsResult.DefaultSettings(); + VERIFY_ARE_EQUAL(L"cmd.exe", termSettings.Commandline()); + VERIFY_ARE_EQUAL(true, termSettings.Elevate()); + } + { + Log::Comment(L"profile.elevate=true, action.elevate=true: DO auto elevate"); + KeyChord kc{ true, false, false, false, static_cast('H'), 0 }; + 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_FALSE(realArgs.TerminalArgs().Profile().empty()); + VERIFY_ARE_EQUAL(L"profile1", realArgs.TerminalArgs().Profile()); + VERIFY_IS_NOT_NULL(realArgs.TerminalArgs().Elevate()); + VERIFY_IS_TRUE(realArgs.TerminalArgs().Elevate().Value()); + + const auto termSettingsResult = TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr); + const auto termSettings = termSettingsResult.DefaultSettings(); + VERIFY_ARE_EQUAL(L"pwsh.exe", termSettings.Commandline()); + VERIFY_ARE_EQUAL(true, termSettings.Elevate()); + } + { + Log::Comment(L"profile.elevate=false, action.elevate=true: DO auto elevate"); + + KeyChord kc{ true, false, false, false, static_cast('I'), 0 }; + 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_FALSE(realArgs.TerminalArgs().Profile().empty()); + VERIFY_ARE_EQUAL(L"profile2", realArgs.TerminalArgs().Profile()); + VERIFY_IS_NOT_NULL(realArgs.TerminalArgs().Elevate()); + VERIFY_IS_TRUE(realArgs.TerminalArgs().Elevate().Value()); + + const auto termSettingsResult = TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr); + const auto termSettings = termSettingsResult.DefaultSettings(); + VERIFY_ARE_EQUAL(L"wsl.exe", termSettings.Commandline()); + VERIFY_ARE_EQUAL(true, termSettings.Elevate()); + } + } + } diff --git a/src/cascadia/TerminalApp/TabManagement.cpp b/src/cascadia/TerminalApp/TabManagement.cpp index d5b4fe27598..054e6997497 100644 --- a/src/cascadia/TerminalApp/TabManagement.cpp +++ b/src/cascadia/TerminalApp/TabManagement.cpp @@ -69,6 +69,17 @@ namespace winrt::TerminalApp::implementation const auto profile{ _settings.GetProfileForArgs(newTerminalArgs) }; const auto settings{ TerminalSettings::CreateWithNewTerminalArgs(_settings, newTerminalArgs, *_bindings) }; + // Try to handle auto-elevation + if (_maybeElevate(newTerminalArgs, settings, profile)) + { + return S_OK; + } + // We can't go in the other direction (elevated->unelevated) + // unfortunately. This seems to be due to Centennial quirks. It works + // unpackaged, but not packaged. + // + // This call to _MakePane won't return nullptr, we already checked that + // case above with the _maybeElevate call. _CreateNewTabFromPane(_MakePane(newTerminalArgs, false, existingConnection)); const uint32_t tabCount = _tabs.Size(); @@ -241,8 +252,11 @@ namespace winrt::TerminalApp::implementation // - pane: The pane to use as the root. void TerminalPage::_CreateNewTabFromPane(std::shared_ptr pane) { - auto newTabImpl = winrt::make_self(pane); - _InitializeTab(newTabImpl); + if (pane) + { + auto newTabImpl = winrt::make_self(pane); + _InitializeTab(newTabImpl); + } } // Method Description: diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 70cd3497a15..1ff40f4b25c 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -12,6 +12,8 @@ #include "TerminalPage.g.cpp" #include +#include "../WinRTUtils/inc/WtExeUtils.h" + #include "TabRowControl.h" #include "ColorHelper.h" #include "DebugTapConnection.h" @@ -549,7 +551,24 @@ namespace winrt::TerminalApp::implementation void TerminalPage::_CompleteInitialization() { _startupState = StartupState::Initialized; - _InitializedHandlers(*this, nullptr); + + // GH#632 - It's possible that the user tried to create the terminal + // with only one tab, with only an elevated profile. If that happens, + // we'll create _another_ process to host the elevated version of that + // profile. This can happen from the jumplist, or if the default profile + // is `elevate:true`, or from the commandline. + // + // However, we need to make sure to close this window in that scenario. + // Since there aren't any _tabs_ in this window, we won't ever get a + // closed event. So do it manually. + if (_tabs.Size() == 0) + { + _LastTabClosedHandlers(*this, nullptr); + } + else + { + _InitializedHandlers(*this, nullptr); + } } // Method Description: @@ -865,6 +884,13 @@ namespace winrt::TerminalApp::implementation else { const auto newPane = _MakePane(newTerminalArgs); + // If the newTerminalArgs caused us to open an elevated window + // instead of creating a pane, it may have returned nullptr. Just do + // nothing then. + if (!newPane) + { + return; + } if (altPressed && !debugTap) { this->_SplitPane(SplitDirection::Automatic, @@ -1754,6 +1780,41 @@ namespace winrt::TerminalApp::implementation } } + // Method Description: + // - If the requested settings want us to elevate this new terminal + // instance, and we're not currently elevated, then open the new terminal + // as an elevated instance (using _OpenElevatedWT). Does nothing if we're + // already elevated, or if the control settings don't want to be elevated. + // Arguments: + // - newTerminalArgs: The NewTerminalArgs for this terminal instance + // - controlSettings: The constructed TerminalSettingsCreateResult for this Terminal instance + // - profile: The Profile we're using to launch this Terminal instance + // Return Value: + // - true iff we tossed this request to an elevated window. Callers can use + // this result to early-return if needed. + bool TerminalPage::_maybeElevate(const NewTerminalArgs& newTerminalArgs, + const TerminalSettingsCreateResult& controlSettings, + const Profile& profile) + { + // Try to handle auto-elevation + const bool requestedElevation = controlSettings.DefaultSettings().Elevate(); + const bool currentlyElevated = IsElevated(); + + // We aren't elevated, but we want to be. + if (requestedElevation && !currentlyElevated) + { + // Manually set the Profile of the NewTerminalArgs to the guid we've + // resolved to. If there was a profile in the NewTerminalArgs, this + // will be that profile's GUID. If there wasn't, then we'll use + // whatever the default profile's GUID is. + + newTerminalArgs.Profile(::Microsoft::Console::Utils::GuidToString(profile.Guid())); + _OpenElevatedWT(newTerminalArgs); + return true; + } + return false; + } + // Method Description: // - Split the focused pane either horizontally or vertically, and place the // given pane accordingly in the tree @@ -1767,13 +1828,6 @@ namespace winrt::TerminalApp::implementation std::shared_ptr newPane) { const auto focusedTab{ _GetFocusedTabImpl() }; - - // Do nothing if no TerminalTab is focused - if (!focusedTab) - { - return; - } - _SplitPane(*focusedTab, splitDirection, splitSize, newPane); } @@ -1791,6 +1845,14 @@ namespace winrt::TerminalApp::implementation const float splitSize, std::shared_ptr newPane) { + // If the caller is calling us with the return value of _MakePane + // directly, it's possible that nullptr was returned, if the connections + // was supposed to be launched in an elevated window. In that case, do + // nothing here. We don't have a pane with which to create the split. + if (!newPane) + { + return; + } const float contentWidth = ::base::saturated_cast(_tabContent.ActualWidth()); const float contentHeight = ::base::saturated_cast(_tabContent.ActualHeight()); const winrt::Windows::Foundation::Size availableSpace{ contentWidth, contentHeight }; @@ -2416,7 +2478,13 @@ namespace winrt::TerminalApp::implementation // a duplicate of the currently focused pane // - existingConnection: optionally receives a connection from the outside // world instead of attempting to create one - std::shared_ptr TerminalPage::_MakePane(const NewTerminalArgs& newTerminalArgs, const bool duplicate, TerminalConnection::ITerminalConnection existingConnection) + // Return Value: + // - If the newTerminalArgs required us to open the pane as a new elevated + // connection, then we'll return nullptr. Otherwise, we'll return a new + // Pane for this connection. + std::shared_ptr TerminalPage::_MakePane(const NewTerminalArgs& newTerminalArgs, + const bool duplicate, + TerminalConnection::ITerminalConnection existingConnection) { TerminalSettingsCreateResult controlSettings{ nullptr }; Profile profile{ nullptr }; @@ -2447,6 +2515,12 @@ namespace winrt::TerminalApp::implementation controlSettings = TerminalSettings::CreateWithNewTerminalArgs(_settings, newTerminalArgs, *_bindings); } + // Try to handle auto-elevation + if (_maybeElevate(newTerminalArgs, controlSettings, profile)) + { + return nullptr; + } + auto connection = existingConnection ? existingConnection : _CreateConnectionFromSettings(profile, controlSettings.DefaultSettings()); if (existingConnection) { @@ -3630,6 +3704,63 @@ namespace winrt::TerminalApp::implementation return profile; } + // Function Description: + // - Helper to launch a new WT instance elevated. It'll do this by spawning + // a helper process, who will asking the shell to elevate the process for + // us. This might cause a UAC prompt. The elevation is performed on a + // background thread, as to not block the UI thread. + // Arguments: + // - newTerminalArgs: A NewTerminalArgs describing the terminal instance + // that should be spawned. The Profile should be filled in with the GUID + // of the profile we want to launch. + // Return Value: + // - + // Important: Don't take the param by reference, since we'll be doing work + // on another thread. + void TerminalPage::_OpenElevatedWT(NewTerminalArgs newTerminalArgs) + { + // BODGY + // + // We're going to construct the commandline we want, then toss it to a + // helper process called `elevate-shim.exe` that happens to live next to + // us. elevate-shim.exe will be the one to call ShellExecute with the + // args that we want (to elevate the given profile). + // + // We can't be the one to call ShellExecute ourselves. ShellExecute + // requires that the calling process stays alive until the child is + // spawned. However, in the case of something like `wt -p + // AlwaysElevateMe`, then the original WT will try to ShellExecute a new + // wt.exe (elevated) and immediately exit, preventing ShellExecute from + // successfully spawning the elevated WT. + + std::filesystem::path exePath = wil::GetModuleFileNameW(nullptr); + exePath.replace_filename(L"elevate-shim.exe"); + + // Build the commandline to pass to wt for this set of NewTerminalArgs + std::wstring cmdline{ + fmt::format(L"new-tab {}", newTerminalArgs.ToCommandline().c_str()) + }; + + wil::unique_process_information pi; + STARTUPINFOW si{}; + si.cb = sizeof(si); + + LOG_IF_WIN32_BOOL_FALSE(CreateProcessW(exePath.c_str(), + cmdline.data(), + nullptr, + nullptr, + FALSE, + 0, + nullptr, + nullptr, + &si, + &pi)); + + // TODO: GH#8592 - It may be useful to pop a Toast here in the original + // Terminal window informing the user that the tab was opened in a new + // window. + } + // Method Description: // - Handles the change of connection state. // If the connection state is failure show information bar suggesting to configure termination behavior @@ -3765,4 +3896,5 @@ namespace winrt::TerminalApp::implementation applicationState.DismissedMessages(std::move(messages)); } + } diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 2d3efb9c025..9ca03d20221 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -404,6 +404,11 @@ namespace winrt::TerminalApp::implementation winrt::Microsoft::Terminal::Settings::Model::Profile GetClosestProfileForDuplicationOfProfile(const winrt::Microsoft::Terminal::Settings::Model::Profile& profile) const noexcept; + bool _maybeElevate(const winrt::Microsoft::Terminal::Settings::Model::NewTerminalArgs& newTerminalArgs, + const winrt::Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult& controlSettings, + const winrt::Microsoft::Terminal::Settings::Model::Profile& profile); + void _OpenElevatedWT(winrt::Microsoft::Terminal::Settings::Model::NewTerminalArgs newTerminalArgs); + bool _shouldPromptForCommandline(const winrt::hstring& cmdline) const; void _adminWarningPrimaryClicked(const winrt::TerminalApp::AdminWarningPlaceholder& sender, const winrt::Windows::UI::Xaml::RoutedEventArgs& args); diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.h b/src/cascadia/TerminalSettingsModel/ActionArgs.h index 1c95209e43e..b797dd4d72a 100644 --- a/src/cascadia/TerminalSettingsModel/ActionArgs.h +++ b/src/cascadia/TerminalSettingsModel/ActionArgs.h @@ -110,6 +110,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation ACTION_ARG(winrt::hstring, Profile, L""); ACTION_ARG(Windows::Foundation::IReference, SuppressApplicationTitle, nullptr); ACTION_ARG(winrt::hstring, ColorScheme); + ACTION_ARG(Windows::Foundation::IReference, Elevate, nullptr); static constexpr std::string_view CommandlineKey{ "commandline" }; static constexpr std::string_view StartingDirectoryKey{ "startingDirectory" }; @@ -119,6 +120,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation static constexpr std::string_view ProfileKey{ "profile" }; static constexpr std::string_view SuppressApplicationTitleKey{ "suppressApplicationTitle" }; static constexpr std::string_view ColorSchemeKey{ "colorScheme" }; + static constexpr std::string_view ElevateKey{ "elevate" }; public: hstring GenerateName() const; @@ -136,6 +138,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation otherAsUs->_ProfileIndex == _ProfileIndex && otherAsUs->_Profile == _Profile && otherAsUs->_SuppressApplicationTitle == _SuppressApplicationTitle && + otherAsUs->_Elevate == _Elevate && otherAsUs->_ColorScheme == _ColorScheme; } return false; @@ -152,6 +155,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation JsonUtils::GetValueForKey(json, TabColorKey, args->_TabColor); JsonUtils::GetValueForKey(json, SuppressApplicationTitleKey, args->_SuppressApplicationTitle); JsonUtils::GetValueForKey(json, ColorSchemeKey, args->_ColorScheme); + JsonUtils::GetValueForKey(json, ElevateKey, args->_Elevate); return *args; } static Json::Value ToJson(const Model::NewTerminalArgs& val) @@ -170,6 +174,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation JsonUtils::SetValueForKey(json, TabColorKey, args->_TabColor); JsonUtils::SetValueForKey(json, SuppressApplicationTitleKey, args->_SuppressApplicationTitle); JsonUtils::SetValueForKey(json, ColorSchemeKey, args->_ColorScheme); + JsonUtils::SetValueForKey(json, ElevateKey, args->_Elevate); return json; } Model::NewTerminalArgs Copy() const @@ -183,11 +188,20 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation copy->_Profile = _Profile; copy->_SuppressApplicationTitle = _SuppressApplicationTitle; copy->_ColorScheme = _ColorScheme; + copy->_Elevate = _Elevate; return *copy; } size_t Hash() const { - return ::Microsoft::Terminal::Settings::Model::HashUtils::HashProperty(Commandline(), StartingDirectory(), TabTitle(), TabColor(), ProfileIndex(), Profile(), SuppressApplicationTitle(), ColorScheme()); + return ::Microsoft::Terminal::Settings::Model::HashUtils::HashProperty(Commandline(), + StartingDirectory(), + TabTitle(), + TabColor(), + ProfileIndex(), + Profile(), + SuppressApplicationTitle(), + ColorScheme(), + Elevate()); } }; diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.idl b/src/cascadia/TerminalSettingsModel/ActionArgs.idl index 457feefced7..8506c3354e4 100644 --- a/src/cascadia/TerminalSettingsModel/ActionArgs.idl +++ b/src/cascadia/TerminalSettingsModel/ActionArgs.idl @@ -117,13 +117,20 @@ namespace Microsoft.Terminal.Settings.Model String TabTitle; Windows.Foundation.IReference TabColor; String Profile; // Either a GUID or a profile's name if the GUID isn't a match + + // We use IReference<> to treat some args as nullable where null means + // "use the inherited value". See ProfileIndex, + // SuppressApplicationTitle, Elevate. Strings that behave this way just + // use `null` as "use the inherited value". + // ProfileIndex can be null (for "use the default"), so this needs to be // a IReference, so it's nullable Windows.Foundation.IReference ProfileIndex { get; }; - Windows.Foundation.IReference SuppressApplicationTitle; - String ColorScheme; + // This needs to be an optional so that the default value (null) does + // not modify whatever the profile's value is (either true or false) + Windows.Foundation.IReference Elevate { get; }; Boolean Equals(NewTerminalArgs other); String GenerateName(); diff --git a/src/cascadia/TerminalSettingsModel/MTSMSettings.h b/src/cascadia/TerminalSettingsModel/MTSMSettings.h index 6719818e745..c29977a5e15 100644 --- a/src/cascadia/TerminalSettingsModel/MTSMSettings.h +++ b/src/cascadia/TerminalSettingsModel/MTSMSettings.h @@ -73,7 +73,8 @@ Author(s): X(hstring, Icon, "icon", L"\uE756") \ X(CloseOnExitMode, CloseOnExit, "closeOnExit", CloseOnExitMode::Graceful) \ X(hstring, TabTitle, "tabTitle") \ - X(Model::BellStyle, BellStyle, "bellStyle", BellStyle::Audible) + X(Model::BellStyle, BellStyle, "bellStyle", BellStyle::Audible) \ + X(bool, Elevate, "elevate", false) #define MTSM_FONT_SETTINGS(X) \ X(hstring, FontFace, "face", DEFAULT_FONT_FACE) \ diff --git a/src/cascadia/TerminalSettingsModel/Profile.cpp b/src/cascadia/TerminalSettingsModel/Profile.cpp index f56e99d4afe..912e6bc3380 100644 --- a/src/cascadia/TerminalSettingsModel/Profile.cpp +++ b/src/cascadia/TerminalSettingsModel/Profile.cpp @@ -30,6 +30,7 @@ static constexpr std::string_view FontInfoKey{ "font" }; static constexpr std::string_view PaddingKey{ "padding" }; static constexpr std::string_view TabColorKey{ "tabColor" }; static constexpr std::string_view UnfocusedAppearanceKey{ "unfocusedAppearance" }; +static constexpr std::string_view ElevateKey{ "elevate" }; Profile::Profile(guid guid) noexcept : _Guid(guid) @@ -333,5 +334,7 @@ Json::Value Profile::ToJson() const json[JsonKey(UnfocusedAppearanceKey)] = winrt::get_self(_UnfocusedAppearance.value())->ToJson(); } + JsonUtils::SetValueForKey(json, ElevateKey, _Elevate); + return json; } diff --git a/src/cascadia/TerminalSettingsModel/Profile.idl b/src/cascadia/TerminalSettingsModel/Profile.idl index 746caad252b..810448b0397 100644 --- a/src/cascadia/TerminalSettingsModel/Profile.idl +++ b/src/cascadia/TerminalSettingsModel/Profile.idl @@ -79,5 +79,6 @@ namespace Microsoft.Terminal.Settings.Model INHERITABLE_PROFILE_SETTING(Boolean, SnapOnInput); INHERITABLE_PROFILE_SETTING(Boolean, AltGrAliasing); INHERITABLE_PROFILE_SETTING(BellStyle, BellStyle); + INHERITABLE_PROFILE_SETTING(Boolean, Elevate); } } diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp b/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp index ae995398641..5bc300f0b4d 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp @@ -155,6 +155,14 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation defaultSettings.ApplyColorScheme(scheme); } } + // Elevate on NewTerminalArgs is an optional value, so the default + // value (null) doesn't override a profile's value. Note that + // elevate:false in an already elevated terminal does nothing - the + // profile will still be launched elevated. + if (newTerminalArgs.Elevate()) + { + defaultSettings.Elevate(newTerminalArgs.Elevate().Value()); + } } return settingsPair; @@ -307,6 +315,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation const til::color colorRef{ profile.TabColor().Value() }; _TabColor = static_cast(colorRef); } + + _Elevate = profile.Elevate(); } // Method Description: diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.h b/src/cascadia/TerminalSettingsModel/TerminalSettings.h index 096f18f7988..0baf0b7158f 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettings.h +++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.h @@ -159,6 +159,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation INHERITABLE_SETTING(Model::TerminalSettings, hstring, PixelShaderPath); INHERITABLE_SETTING(Model::TerminalSettings, bool, IntenseIsBold); + INHERITABLE_SETTING(Model::TerminalSettings, bool, Elevate, false); + private: std::optional> _ColorTable; gsl::span _getColorTableImpl(); diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.idl b/src/cascadia/TerminalSettingsModel/TerminalSettings.idl index 80231906e8f..52620a5eb65 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettings.idl +++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.idl @@ -35,5 +35,7 @@ namespace Microsoft.Terminal.Settings.Model void ApplyColorScheme(ColorScheme scheme); ColorScheme AppliedColorScheme; + + Boolean Elevate; }; }