Implement basic profile matching (#11390)
This implements command line matching for `CascadiaSettings::GetProfileForArgs`. The command lines for all user profiles are resolved to absolute file paths, argument quotes are standardized ("canonicalized") and the results are cached. When `GetProfileForArgs` is called with a Commandline() value, we "canonicalize" the argument as well and find the profile that is the longest prefix. If none could be found the default profile is returned. ## PR Checklist * [x] Closes #9458 * [x] Closes #10952 * [x] I work here * [ ] Tests added/passed ## Validation Steps Performed * Open a `cmd.exe` tab in the store-version of WT * Run `start cmd` --> A tab with the `cmd.exe` profile opens * Run `start pwsh.exe` --> A tab with the PowerShell 7 profile opens * Run PowerShell 7 from the start menu --> A tab with the PowerShell 7 profile opens * Create a symlink for PowerShell 7 and launch `pwsh.exe` from there --> A tab with the PowerShell 7 profile opens
This commit is contained in:
parent
dd5dbb2a40
commit
479ef264b2
|
@ -1,5 +1,6 @@
|
|||
ACLs
|
||||
ADMINS
|
||||
advapi
|
||||
altform
|
||||
altforms
|
||||
appendwttlogging
|
||||
|
@ -15,6 +16,7 @@ CPLs
|
|||
cpptools
|
||||
cppvsdbg
|
||||
CPRs
|
||||
cryptbase
|
||||
DACL
|
||||
DACLs
|
||||
diffs
|
||||
|
@ -46,6 +48,7 @@ powershell
|
|||
propkey
|
||||
pscustomobject
|
||||
QWORD
|
||||
regedit
|
||||
robocopy
|
||||
SACLs
|
||||
sdkddkver
|
||||
|
|
|
@ -668,6 +668,7 @@ dwriteglyphrundescriptionclustermap
|
|||
dxgi
|
||||
dxgidwm
|
||||
dxinterop
|
||||
dxsm
|
||||
dxttbmp
|
||||
eachother
|
||||
eae
|
||||
|
@ -1231,6 +1232,7 @@ KLF
|
|||
KLMNO
|
||||
KLMNOPQRST
|
||||
KLMNOPQRSTQQQQQ
|
||||
KPRIORITY
|
||||
KVM
|
||||
langid
|
||||
LANGUAGELIST
|
||||
|
@ -1641,6 +1643,7 @@ onecoreuapuuid
|
|||
onecoreuuid
|
||||
ONECOREWINDOWS
|
||||
onehalf
|
||||
oneseq
|
||||
ONLCR
|
||||
openbash
|
||||
opencode
|
||||
|
@ -1707,6 +1710,7 @@ pcch
|
|||
PCCHAR
|
||||
PCCONSOLE
|
||||
PCD
|
||||
pcg
|
||||
pch
|
||||
PCHAR
|
||||
PCIDLIST
|
||||
|
@ -1802,6 +1806,7 @@ POSX
|
|||
POSXSCROLL
|
||||
POSYSCROLL
|
||||
ppci
|
||||
PPEB
|
||||
ppf
|
||||
ppguid
|
||||
ppidl
|
||||
|
@ -2021,6 +2026,7 @@ Rike
|
|||
RIPMSG
|
||||
RIS
|
||||
RMENU
|
||||
rng
|
||||
roadmap
|
||||
robomac
|
||||
roundtrip
|
||||
|
|
|
@ -0,0 +1,201 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
|
@ -0,0 +1,19 @@
|
|||
Copyright (c) 2014-2017 Melissa O'Neill and PCG Project contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"Registrations": [
|
||||
{
|
||||
"component": {
|
||||
"type": "git",
|
||||
"git": {
|
||||
"repositoryUrl": "https://github.com/imneme/pcg-cpp",
|
||||
"commitHash": "ffd522e7188bef30a00c74dc7eb9de5faff90092"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"Version": 1
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
// PCG Random Number Generation for C++
|
||||
//
|
||||
// Copyright 2014-2019 Melissa O'Neill <oneill@pcg-random.org>,
|
||||
// and the PCG Project contributors.
|
||||
//
|
||||
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (provided in
|
||||
// LICENSE-APACHE.txt and at http://www.apache.org/licenses/LICENSE-2.0)
|
||||
// or under the MIT license (provided in LICENSE-MIT.txt and at
|
||||
// http://opensource.org/licenses/MIT), at your option. This file may not
|
||||
// be copied, modified, or distributed except according to those terms.
|
||||
//
|
||||
// Distributed on an "AS IS" BASIS, WITHOUT WARRANTY OF ANY KIND, either
|
||||
// express or implied. See your chosen license for details.
|
||||
//
|
||||
// For additional information about the PCG random number generation scheme,
|
||||
// visit http://www.pcg-random.org/.
|
||||
//
|
||||
// -----------------------------------------------------------------------------
|
||||
//
|
||||
// Leonard Hecker <lhecker@microsoft.com>:
|
||||
// The following contents are an extract of pcg_engines::oneseq_dxsm_64_32
|
||||
// reduced down to the bare essentials, while retaining base functionality.
|
||||
|
||||
namespace pcg_engines {
|
||||
class oneseq_dxsm_64_32 {
|
||||
using xtype = uint32_t;
|
||||
using itype = uint64_t;
|
||||
|
||||
itype state_;
|
||||
|
||||
static constexpr uint64_t multiplier() {
|
||||
return 6364136223846793005ULL;
|
||||
}
|
||||
|
||||
static constexpr uint64_t increment() {
|
||||
return 1442695040888963407ULL;
|
||||
}
|
||||
|
||||
static itype bump(itype state) {
|
||||
return state * multiplier() + increment();
|
||||
}
|
||||
|
||||
itype base_generate0() {
|
||||
itype old_state = state_;
|
||||
state_ = bump(state_);
|
||||
return old_state;
|
||||
}
|
||||
|
||||
public:
|
||||
explicit oneseq_dxsm_64_32(itype state = 0xcafef00dd15ea5e5ULL) : state_(bump(state + increment())) {
|
||||
}
|
||||
|
||||
// Returns a value in the interval [0, UINT32_MAX].
|
||||
xtype operator()() {
|
||||
constexpr auto xtypebits = uint8_t(sizeof(xtype) * 8);
|
||||
constexpr auto itypebits = uint8_t(sizeof(itype) * 8);
|
||||
|
||||
auto internal = base_generate0();
|
||||
auto hi = xtype(internal >> (itypebits - xtypebits));
|
||||
auto lo = xtype(internal);
|
||||
|
||||
lo |= 1;
|
||||
hi ^= hi >> (xtypebits / 2);
|
||||
hi *= xtype(multiplier());
|
||||
hi ^= hi >> (3 * (xtypebits / 4));
|
||||
hi *= lo;
|
||||
return hi;
|
||||
}
|
||||
|
||||
// Returns a value in the interval [0, upper_bound).
|
||||
xtype operator()(xtype upper_bound) {
|
||||
uint32_t threshold = (UINT64_MAX + uint32_t(1) - upper_bound) % upper_bound;
|
||||
for (;;) {
|
||||
auto r = operator()();
|
||||
if (r >= threshold)
|
||||
return r % upper_bound;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
#include "pch.h"
|
||||
|
||||
#include <til/rand.h>
|
||||
|
||||
#include "../TerminalSettingsModel/CascadiaSettings.h"
|
||||
#include "../TerminalSettingsModel/TerminalSettings.h"
|
||||
#include "TestUtils.h"
|
||||
|
@ -34,14 +36,12 @@ namespace SettingsModelLocalTests
|
|||
END_TEST_CLASS()
|
||||
|
||||
TEST_METHOD(TryCreateWinRTType);
|
||||
|
||||
TEST_METHOD(TestTerminalArgsForBinding);
|
||||
|
||||
TEST_METHOD(CommandLineToArgvW);
|
||||
TEST_METHOD(GetProfileForArgsWithCommandline);
|
||||
TEST_METHOD(MakeSettingsForProfile);
|
||||
TEST_METHOD(MakeSettingsForDefaultProfileThatDoesntExist);
|
||||
|
||||
TEST_METHOD(TestLayerProfileOnColorScheme);
|
||||
|
||||
TEST_METHOD(TestCommandlineToTitlePromotion);
|
||||
|
||||
TEST_CLASS_SETUP(ClassSetup)
|
||||
|
@ -60,6 +60,139 @@ namespace SettingsModelLocalTests
|
|||
VERIFY_ARE_NOT_EQUAL(oldFontSize, newFontSize);
|
||||
}
|
||||
|
||||
// CascadiaSettings::_normalizeCommandLine abuses some aspects from CommandLineToArgvW
|
||||
// to simplify the implementation. It assumes that all arguments returned by
|
||||
// CommandLineToArgvW are returned back to back in memory as "arg1\0arg2\0arg3\0...".
|
||||
// This test ensures CommandLineToArgvW doesn't change just to be sure.
|
||||
void TerminalSettingsTests::CommandLineToArgvW()
|
||||
{
|
||||
pcg_engines::oneseq_dxsm_64_32 rng{ til::gen_random<uint64_t>() };
|
||||
|
||||
const auto expectedArgc = static_cast<int>(rng(16) + 1);
|
||||
std::wstring expectedArgv;
|
||||
std::wstring input;
|
||||
|
||||
// We generate up to 16 arguments. Each argument is up to 64 chars long, is quoted
|
||||
// (2 chars, only applies to the input) and separated by a whitespace (1 char).
|
||||
expectedArgv.reserve(expectedArgc * 65);
|
||||
input.reserve(expectedArgc * 67);
|
||||
|
||||
for (int i = 0; i < expectedArgc; ++i)
|
||||
{
|
||||
const bool useQuotes = static_cast<bool>(rng(2));
|
||||
const auto count = static_cast<size_t>(rng(64));
|
||||
const auto ch = static_cast<wchar_t>(rng('z' - 'a' + 1) + 'a');
|
||||
|
||||
if (i != 0)
|
||||
{
|
||||
expectedArgv.push_back(L'\0');
|
||||
input.push_back(L' ');
|
||||
}
|
||||
|
||||
if (useQuotes)
|
||||
{
|
||||
input.push_back(L'"');
|
||||
}
|
||||
|
||||
expectedArgv.append(count, ch);
|
||||
input.append(count, ch);
|
||||
|
||||
if (useQuotes)
|
||||
{
|
||||
input.push_back(L'"');
|
||||
}
|
||||
}
|
||||
|
||||
int argc;
|
||||
wil::unique_hlocal_ptr<PWSTR[]> argv{ ::CommandLineToArgvW(input.c_str(), &argc) };
|
||||
VERIFY_ARE_EQUAL(expectedArgc, argc);
|
||||
VERIFY_IS_NOT_NULL(argv);
|
||||
|
||||
const auto lastArg = argv[argc - 1];
|
||||
const auto beg = argv[0];
|
||||
const auto end = lastArg + wcslen(lastArg);
|
||||
VERIFY_IS_GREATER_THAN(end, beg);
|
||||
VERIFY_ARE_EQUAL(expectedArgv.size(), static_cast<size_t>(end - beg));
|
||||
VERIFY_ARE_EQUAL(0, memcmp(beg, expectedArgv.data(), expectedArgv.size()));
|
||||
}
|
||||
|
||||
void TerminalSettingsTests::GetProfileForArgsWithCommandline()
|
||||
{
|
||||
// I'm exclusively using cmd.exe as I know exactly where it resides at.
|
||||
static constexpr std::string_view settingsJson{ R"({
|
||||
"profiles": {
|
||||
"defaults": {
|
||||
"historySize": 123
|
||||
},
|
||||
"list": [
|
||||
{
|
||||
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"commandline": "%SystemRoot%\\System32\\cmd.exe"
|
||||
},
|
||||
{
|
||||
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"commandline": "cmd.exe /A"
|
||||
},
|
||||
{
|
||||
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
|
||||
"commandline": "cmd.exe /A /B"
|
||||
},
|
||||
{
|
||||
"guid": "{6239a42c-3333-49a3-80bd-e8fdd045185c}",
|
||||
"commandline": "cmd.exe /A /C",
|
||||
"connectionType": "{9a9977a7-1fe0-49c0-b6c0-13a0cd1c98a1}"
|
||||
}
|
||||
]
|
||||
}
|
||||
})" };
|
||||
|
||||
const auto settings = winrt::make_self<implementation::CascadiaSettings>(settingsJson);
|
||||
|
||||
struct TestCase
|
||||
{
|
||||
std::wstring_view input;
|
||||
int expected;
|
||||
};
|
||||
|
||||
static constexpr std::array testCases{
|
||||
// Base test.
|
||||
TestCase{ L"cmd.exe", 0 },
|
||||
// SearchPathW() normalization + case insensitive matching.
|
||||
TestCase{ L"cmd.exe /a", 1 },
|
||||
TestCase{ L"C:\\Windows\\System32\\cmd.exe /A", 1 },
|
||||
// Test that we don't pick the equally long but different "/A /B" variant.
|
||||
TestCase{ L"C:\\Windows\\System32\\cmd.exe /A /C", 1 },
|
||||
// Test that we don't pick the shorter "/A" variant,
|
||||
// but do pick the shorter "/A /B" variant for longer inputs.
|
||||
TestCase{ L"cmd.exe /A /B", 2 },
|
||||
TestCase{ L"cmd.exe /A /B /C", 2 },
|
||||
// Ignore profiles with a connection type, like the Azure cloud shell.
|
||||
// Instead it should pick any other prefix.
|
||||
TestCase{ L"C:\\Windows\\System32\\cmd.exe /A /C", 1 },
|
||||
// Return base layer profile for missing profiles.
|
||||
TestCase{ L"C:\\Windows\\regedit.exe", -1 },
|
||||
};
|
||||
|
||||
for (const auto& testCase : testCases)
|
||||
{
|
||||
NewTerminalArgs args;
|
||||
args.Commandline(testCase.input);
|
||||
|
||||
const auto profile = settings->GetProfileForArgs(args);
|
||||
VERIFY_IS_NOT_NULL(profile);
|
||||
|
||||
if (testCase.expected < 0)
|
||||
{
|
||||
VERIFY_ARE_EQUAL(123, profile.HistorySize());
|
||||
}
|
||||
else
|
||||
{
|
||||
GUID expectedGUID{ 0x6239a42c, static_cast<uint16_t>(0x1111 * testCase.expected), 0x49a3, { 0x80, 0xbd, 0xe8, 0xfd, 0xd0, 0x45, 0x18, 0x5c } };
|
||||
VERIFY_ARE_EQUAL(expectedGUID, static_cast<const GUID&>(profile.Guid()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TerminalSettingsTests::TestTerminalArgsForBinding()
|
||||
{
|
||||
static constexpr std::string_view settingsJson{ R"(
|
||||
|
|
|
@ -218,7 +218,7 @@ namespace winrt::TerminalApp::implementation
|
|||
_RegisterActionCallbacks();
|
||||
|
||||
// Hook up inbound connection event handler
|
||||
TerminalConnection::ConptyConnection::NewConnection({ this, &TerminalPage::_OnNewConnection });
|
||||
ConptyConnection::NewConnection({ this, &TerminalPage::_OnNewConnection });
|
||||
|
||||
//Event Bindings (Early)
|
||||
_newTabButton.Click([weakThis{ get_weak() }](auto&&, auto&&) {
|
||||
|
@ -2697,52 +2697,40 @@ namespace winrt::TerminalApp::implementation
|
|||
return _isAlwaysOnTop;
|
||||
}
|
||||
|
||||
HRESULT TerminalPage::_OnNewConnection(winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection connection)
|
||||
HRESULT TerminalPage::_OnNewConnection(const ConptyConnection& connection)
|
||||
{
|
||||
// We need to be on the UI thread in order for _OpenNewTab to run successfully.
|
||||
// HasThreadAccess will return true if we're currently on a UI thread and false otherwise.
|
||||
// When we're on a COM thread, we'll need to dispatch the calls to the UI thread
|
||||
// and wait on it hence the locking mechanism.
|
||||
if (Dispatcher().HasThreadAccess())
|
||||
{
|
||||
try
|
||||
{
|
||||
NewTerminalArgs newTerminalArgs{};
|
||||
// TODO GH#10952: When we pass the actual commandline (or originating application), the
|
||||
// settings model can choose the right settings based on command matching, or synthesize
|
||||
// a profile from the registry/link settings (TODO GH#9458).
|
||||
// TODO GH#9458: Get and pass the LNK/EXE filenames.
|
||||
// Passing in a commandline forces GetProfileForArgs to use Base Layer instead of Default Profile;
|
||||
// in the future, it can make a better decision based on the value we pull out of the process handle.
|
||||
// TODO GH#5047: When we hang on to the N.T.A., try not to spawn "default... .exe" :)
|
||||
newTerminalArgs.Commandline(L"default-terminal-invocation-placeholder");
|
||||
const auto profile{ _settings.GetProfileForArgs(newTerminalArgs) };
|
||||
const auto settings{ TerminalSettings::CreateWithProfile(_settings, profile, *_bindings) };
|
||||
|
||||
_CreateNewTabWithProfileAndSettings(profile, settings, connection);
|
||||
|
||||
// Request a summon of this window to the foreground
|
||||
_SummonWindowRequestedHandlers(*this, nullptr);
|
||||
}
|
||||
CATCH_RETURN();
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
else
|
||||
if (!Dispatcher().HasThreadAccess())
|
||||
{
|
||||
til::latch latch{ 1 };
|
||||
HRESULT finalVal = S_OK;
|
||||
|
||||
Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [&]() {
|
||||
// Re-running ourselves under the dispatcher will cause us to take the first branch above.
|
||||
finalVal = _OnNewConnection(connection);
|
||||
|
||||
latch.count_down();
|
||||
});
|
||||
|
||||
latch.wait();
|
||||
return finalVal;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
NewTerminalArgs newTerminalArgs;
|
||||
newTerminalArgs.Commandline(connection.Commandline());
|
||||
const auto profile{ _settings.GetProfileForArgs(newTerminalArgs) };
|
||||
const auto settings{ TerminalSettings::CreateWithProfile(_settings, profile, *_bindings) };
|
||||
|
||||
_CreateNewTabWithProfileAndSettings(profile, settings, connection);
|
||||
|
||||
// Request a summon of this window to the foreground
|
||||
_SummonWindowRequestedHandlers(*this, nullptr);
|
||||
return S_OK;
|
||||
}
|
||||
CATCH_RETURN()
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
|
|
@ -388,7 +388,7 @@ namespace winrt::TerminalApp::implementation
|
|||
winrt::Microsoft::Terminal::Settings::Model::Command _lastPreviewedCommand{ nullptr };
|
||||
std::vector<std::function<void()>> _restorePreviewFuncs{};
|
||||
|
||||
HRESULT _OnNewConnection(winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection connection);
|
||||
HRESULT _OnNewConnection(const winrt::Microsoft::Terminal::TerminalConnection::ConptyConnection& connection);
|
||||
void _HandleToggleInboundPty(const IInspectable& sender, const Microsoft::Terminal::Settings::Model::ActionEventArgs& args);
|
||||
|
||||
void _WindowRenamerActionClick(const IInspectable& sender, const IInspectable& eventArgs);
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
|
||||
#include "ConptyConnection.h"
|
||||
|
||||
#include <windows.h>
|
||||
#include <userenv.h>
|
||||
#include <UserEnv.h>
|
||||
#include <winternl.h>
|
||||
|
||||
#include "ConptyConnection.g.cpp"
|
||||
#include "CTerminalHandoff.h"
|
||||
|
@ -276,24 +276,18 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
const HANDLE hClientProcess) :
|
||||
_initialRows{ 25 },
|
||||
_initialCols{ 80 },
|
||||
_commandline{ L"" },
|
||||
_startingDirectory{ L"" },
|
||||
_startingTitle{ L"" },
|
||||
_environment{ nullptr },
|
||||
_guid{},
|
||||
_u8State{},
|
||||
_u16Str{},
|
||||
_buffer{},
|
||||
_guid{ Utils::CreateGuid() },
|
||||
_inPipe{ hIn },
|
||||
_outPipe{ hOut }
|
||||
{
|
||||
THROW_IF_FAILED(ConptyPackPseudoConsole(hServerProcess, hRef, hSig, &_hPC));
|
||||
if (_guid == guid{})
|
||||
{
|
||||
_guid = Utils::CreateGuid();
|
||||
}
|
||||
|
||||
_piClient.hProcess = hClientProcess;
|
||||
|
||||
try
|
||||
{
|
||||
_commandline = _commandlineFromProcess(hClientProcess);
|
||||
}
|
||||
CATCH_LOG()
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
|
@ -355,6 +349,11 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
return _guid;
|
||||
}
|
||||
|
||||
winrt::hstring ConptyConnection::Commandline() const
|
||||
{
|
||||
return _commandline;
|
||||
}
|
||||
|
||||
void ConptyConnection::Start()
|
||||
try
|
||||
{
|
||||
|
@ -560,6 +559,38 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
}
|
||||
CATCH_LOG()
|
||||
|
||||
// Returns the command line of the given process.
|
||||
// Requires PROCESS_BASIC_INFORMATION | PROCESS_VM_READ privileges.
|
||||
winrt::hstring ConptyConnection::_commandlineFromProcess(HANDLE process)
|
||||
{
|
||||
struct PROCESS_BASIC_INFORMATION
|
||||
{
|
||||
NTSTATUS ExitStatus;
|
||||
PPEB PebBaseAddress;
|
||||
ULONG_PTR AffinityMask;
|
||||
KPRIORITY BasePriority;
|
||||
ULONG_PTR UniqueProcessId;
|
||||
ULONG_PTR InheritedFromUniqueProcessId;
|
||||
} info;
|
||||
THROW_IF_NTSTATUS_FAILED(NtQueryInformationProcess(process, ProcessBasicInformation, &info, sizeof(info), nullptr));
|
||||
|
||||
// PEB: Process Environment Block
|
||||
// This is a funny structure allocated by the kernel which contains all sorts of useful
|
||||
// information, only a tiny fraction of which are documented publicly unfortunately.
|
||||
// Fortunately however it contains a copy of the command line the process launched with.
|
||||
PEB peb;
|
||||
THROW_IF_WIN32_BOOL_FALSE(ReadProcessMemory(process, info.PebBaseAddress, &peb, sizeof(peb), nullptr));
|
||||
|
||||
RTL_USER_PROCESS_PARAMETERS params;
|
||||
THROW_IF_WIN32_BOOL_FALSE(ReadProcessMemory(process, peb.ProcessParameters, ¶ms, sizeof(params), nullptr));
|
||||
|
||||
// Yeah I know... Don't use "impl" stuff... But why do you make something _that_ useful private? :(
|
||||
// The hstring_builder allows us to create a hstring without intermediate copies. Neat!
|
||||
winrt::impl::hstring_builder commandline{ params.CommandLine.Length / 2u };
|
||||
THROW_IF_WIN32_BOOL_FALSE(ReadProcessMemory(process, params.CommandLine.Buffer, commandline.data(), params.CommandLine.Length, nullptr));
|
||||
return commandline.to_hstring();
|
||||
}
|
||||
|
||||
DWORD ConptyConnection::_OutputThread()
|
||||
{
|
||||
// Keep us alive until the output thread terminates; the destructor
|
||||
|
@ -636,8 +667,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
HRESULT ConptyConnection::NewHandoff(HANDLE in, HANDLE out, HANDLE signal, HANDLE ref, HANDLE server, HANDLE client) noexcept
|
||||
try
|
||||
{
|
||||
auto conn = winrt::make<implementation::ConptyConnection>(signal, in, out, ref, server, client);
|
||||
_newConnectionHandlers(conn);
|
||||
_newConnectionHandlers(winrt::make<ConptyConnection>(signal, in, out, ref, server, client));
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
void ClearBuffer();
|
||||
|
||||
winrt::guid Guid() const noexcept;
|
||||
winrt::hstring Commandline() const;
|
||||
|
||||
static void StartInboundListener();
|
||||
static void StopInboundListener();
|
||||
|
@ -56,12 +57,13 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
WINRT_CALLBACK(TerminalOutput, TerminalOutputHandler);
|
||||
|
||||
private:
|
||||
static HRESULT NewHandoff(HANDLE in, HANDLE out, HANDLE signal, HANDLE ref, HANDLE server, HANDLE client) noexcept;
|
||||
static winrt::hstring _commandlineFromProcess(HANDLE process);
|
||||
|
||||
HRESULT _LaunchAttachedClient() noexcept;
|
||||
void _indicateExitWithStatus(unsigned int status) noexcept;
|
||||
void _ClientTerminated() noexcept;
|
||||
|
||||
static HRESULT NewHandoff(HANDLE in, HANDLE out, HANDLE signal, HANDLE ref, HANDLE server, HANDLE client) noexcept;
|
||||
|
||||
uint32_t _initialRows{};
|
||||
uint32_t _initialCols{};
|
||||
hstring _commandline{};
|
||||
|
|
|
@ -5,10 +5,13 @@ import "ITerminalConnection.idl";
|
|||
|
||||
namespace Microsoft.Terminal.TerminalConnection
|
||||
{
|
||||
delegate void NewConnectionHandler(ConptyConnection connection);
|
||||
|
||||
[default_interface] runtimeclass ConptyConnection : ITerminalConnection
|
||||
{
|
||||
ConptyConnection();
|
||||
Guid Guid { get; };
|
||||
String Commandline { get; };
|
||||
void ClearBuffer();
|
||||
|
||||
static event NewConnectionHandler NewConnection;
|
||||
|
|
|
@ -29,6 +29,4 @@ namespace Microsoft.Terminal.TerminalConnection
|
|||
event Windows.Foundation.TypedEventHandler<ITerminalConnection, Object> StateChanged;
|
||||
ConnectionState State { get; };
|
||||
};
|
||||
|
||||
delegate void NewConnectionHandler(ITerminalConnection connection);
|
||||
}
|
||||
|
|
|
@ -12,6 +12,9 @@
|
|||
#include <VersionHelpers.h>
|
||||
#include <WtExeUtils.h>
|
||||
|
||||
#include <shellapi.h>
|
||||
#include <shlwapi.h>
|
||||
|
||||
using namespace winrt::Microsoft::Terminal;
|
||||
using namespace winrt::Microsoft::Terminal::Settings;
|
||||
using namespace winrt::Microsoft::Terminal::Settings::Model::implementation;
|
||||
|
@ -539,9 +542,12 @@ Model::Profile CascadiaSettings::GetProfileForArgs(const Model::NewTerminalArgs&
|
|||
{
|
||||
if (newTerminalArgs)
|
||||
{
|
||||
if (auto profile = GetProfileByName(newTerminalArgs.Profile()))
|
||||
if (const auto name = newTerminalArgs.Profile(); !name.empty())
|
||||
{
|
||||
return profile;
|
||||
if (auto profile = GetProfileByName(name))
|
||||
{
|
||||
return profile;
|
||||
}
|
||||
}
|
||||
|
||||
if (const auto index = newTerminalArgs.ProfileIndex())
|
||||
|
@ -551,6 +557,14 @@ Model::Profile CascadiaSettings::GetProfileForArgs(const Model::NewTerminalArgs&
|
|||
return profile;
|
||||
}
|
||||
}
|
||||
|
||||
if (const auto commandLine = newTerminalArgs.Commandline(); !commandLine.empty())
|
||||
{
|
||||
if (auto profile = _getProfileForCommandLine(commandLine))
|
||||
{
|
||||
return profile;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if constexpr (Feature_ShowProfileDefaultsInSettings::IsEnabled())
|
||||
|
@ -573,6 +587,199 @@ Model::Profile CascadiaSettings::GetProfileForArgs(const Model::NewTerminalArgs&
|
|||
}
|
||||
}
|
||||
|
||||
// The method does some crude command line matching for our console hand-off support.
|
||||
// If you have hand-off enabled and start PowerShell from the start menu we might be called with
|
||||
// "C:\Program Files\PowerShell\7\pwsh.exe -WorkingDirectory ~"
|
||||
// This function then checks all known user profiles for one that's compatible with the commandLine.
|
||||
// In this case we might have a profile with the command line
|
||||
// "C:\Program Files\PowerShell\7\pwsh.exe"
|
||||
// This function will then match this profile return it.
|
||||
//
|
||||
// If no matching profile could be found a nullptr will be returned.
|
||||
Model::Profile CascadiaSettings::_getProfileForCommandLine(const winrt::hstring& commandLine) const
|
||||
{
|
||||
// We're going to cache all the command lines we got, as
|
||||
// _normalizeCommandLine is a relatively heavy operation.
|
||||
std::call_once(_commandLinesCacheOnce, [this]() {
|
||||
_commandLinesCache.reserve(_allProfiles.Size());
|
||||
|
||||
for (const auto& profile : _allProfiles)
|
||||
{
|
||||
if (profile.ConnectionType() != winrt::guid{})
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto cmd = profile.Commandline();
|
||||
if (cmd.empty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_commandLinesCache.emplace_back(_normalizeCommandLine(cmd.c_str()), profile);
|
||||
}
|
||||
CATCH_LOG()
|
||||
}
|
||||
|
||||
// We're trying to find the command line with the longest common prefix below.
|
||||
// Given the commandLine "foo.exe -bar -baz" and these two user profiles:
|
||||
// * "foo.exe"
|
||||
// * "foo.exe -bar"
|
||||
// we want to choose the second one. By sorting the _commandLinesCache in a descending order
|
||||
// by command line length, we can return from this function the moment we found a matching
|
||||
// profile as there cannot possibly be any other profile anymore with a longer command line.
|
||||
std::stable_sort(_commandLinesCache.begin(), _commandLinesCache.end(), [](const auto& lhs, const auto& rhs) {
|
||||
return lhs.first.size() > rhs.first.size();
|
||||
});
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
const auto needle = _normalizeCommandLine(commandLine.c_str());
|
||||
|
||||
// til::starts_with(string, prefix) will always return false if prefix.size() > string.size().
|
||||
// --> Using binary search we can safely skip all items in _commandLinesCache where .first.size() > needle.size().
|
||||
const auto end = _commandLinesCache.end();
|
||||
auto it = std::lower_bound(_commandLinesCache.begin(), end, needle, [&](const auto& lhs, const auto& rhs) {
|
||||
return lhs.first.size() > rhs.size();
|
||||
});
|
||||
|
||||
// `it` is now at a position where it->first.size() <= needle.size().
|
||||
// Hopefully we'll now find a command line with matching prefix.
|
||||
for (; it != end; ++it)
|
||||
{
|
||||
const auto& prefix = it->first;
|
||||
const auto length = gsl::narrow<int>(prefix.size());
|
||||
if (CompareStringOrdinal(needle.data(), length, prefix.data(), length, TRUE) == CSTR_EQUAL)
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_CAUGHT_EXCEPTION();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Given a commandLine like the following:
|
||||
// * "C:\WINDOWS\System32\cmd.exe"
|
||||
// * "pwsh -WorkingDirectory ~"
|
||||
// * "C:\Program Files\PowerShell\7\pwsh.exe"
|
||||
// * "C:\Program Files\PowerShell\7\pwsh.exe -WorkingDirectory ~"
|
||||
//
|
||||
// This function returns:
|
||||
// * "C:\Windows\System32\cmd.exe"
|
||||
// * "C:\Program Files\PowerShell\7\pwsh.exe\0-WorkingDirectory\0~"
|
||||
// * "C:\Program Files\PowerShell\7\pwsh.exe"
|
||||
// * "C:\Program Files\PowerShell\7\pwsh.exe\0-WorkingDirectory\0~"
|
||||
//
|
||||
// The resulting strings are then used for comparisons in _getProfileForCommandLine().
|
||||
// For instance a resulting string of
|
||||
// "C:\Program Files\PowerShell\7\pwsh.exe"
|
||||
// is considered a compatible profile with
|
||||
// "C:\Program Files\PowerShell\7\pwsh.exe -WorkingDirectory ~"
|
||||
// as it shares the same (normalized) prefix.
|
||||
std::wstring CascadiaSettings::_normalizeCommandLine(LPCWSTR commandLine)
|
||||
{
|
||||
// Turn "%SystemRoot%\System32\cmd.exe" into "C:\WINDOWS\System32\cmd.exe".
|
||||
// We do this early, as environment variables might occur anywhere in the commandLine.
|
||||
std::wstring normalized;
|
||||
THROW_IF_FAILED(wil::ExpandEnvironmentStringsW(commandLine, normalized));
|
||||
|
||||
// One of the most important things this function does is to strip quotes.
|
||||
// That way the commandLine "foo.exe -bar" and "\"foo.exe\" \"-bar\"" appear identical.
|
||||
// We'll abuse CommandLineToArgvW for that as it's close to what CreateProcessW uses.
|
||||
int argc = 0;
|
||||
wil::unique_hlocal_ptr<PWSTR[]> argv{ CommandLineToArgvW(normalized.c_str(), &argc) };
|
||||
THROW_LAST_ERROR_IF(!argc);
|
||||
|
||||
// The given commandLine should start with an executable name or path.
|
||||
// For instance given the following argv arrays:
|
||||
// * {"C:\WINDOWS\System32\cmd.exe"}
|
||||
// * {"pwsh", "-WorkingDirectory", "~"}
|
||||
// * {"C:\Program", "Files\PowerShell\7\pwsh.exe"}
|
||||
// ^^^^
|
||||
// Notice how there used to be a space in the path, which was split by ExpandEnvironmentStringsW().
|
||||
// CreateProcessW() supports such atrocities, so we got to do the same.
|
||||
// * {"C:\Program Files\PowerShell\7\pwsh.exe", "-WorkingDirectory", "~"}
|
||||
//
|
||||
// This loop tries to resolve relative paths, as well as executable names in %PATH%
|
||||
// into absolute paths and normalizes them. The results for the above would be:
|
||||
// * "C:\Windows\System32\cmd.exe"
|
||||
// * "C:\Program Files\PowerShell\7\pwsh.exe"
|
||||
// * "C:\Program Files\PowerShell\7\pwsh.exe"
|
||||
// * "C:\Program Files\PowerShell\7\pwsh.exe"
|
||||
for (;;)
|
||||
{
|
||||
// CreateProcessW uses RtlGetExePath to get the lpPath for SearchPathW.
|
||||
// The difference between the behavior of SearchPathW if lpPath is nullptr and what RtlGetExePath returns
|
||||
// seems to be mostly whether SafeProcessSearchMode is respected and the support for relative paths.
|
||||
// Windows Terminal makes the use relative paths rather impractical which is why we simply dropped the call to RtlGetExePath.
|
||||
const auto status = wil::SearchPathW(nullptr, argv[0], L".exe", normalized);
|
||||
|
||||
if (status == S_OK)
|
||||
{
|
||||
std::filesystem::path path{ std::move(normalized) };
|
||||
|
||||
// ExpandEnvironmentStringsW() might have returned a string that's not in the canonical capitalization.
|
||||
// For instance %SystemRoot% is set to C:\WINDOWS on my system (ugh), even though the path is actually C:\Windows.
|
||||
// We need to fix this as case-sensitive path comparisons will fail otherwise (Windows supports case-sensitive file systems).
|
||||
// If we fail to resolve the path for whatever reason (pretty unlikely given that SearchPathW found it)
|
||||
// we fall back to leaving the path as is. Better than throwing a random exception and making this unusable.
|
||||
{
|
||||
std::error_code ec;
|
||||
auto canonicalPath = std::filesystem::canonical(path, ec);
|
||||
if (!ec)
|
||||
{
|
||||
path = std::move(canonicalPath);
|
||||
}
|
||||
}
|
||||
|
||||
// std::filesystem::path has no way to extract the internal path.
|
||||
// So about that.... I own you, computer. Give me that path.
|
||||
normalized = std::move(const_cast<std::wstring&>(path.native()));
|
||||
break;
|
||||
}
|
||||
|
||||
// If the file path couldn't be found by SearchPathW this could be the result of us being given a commandLine
|
||||
// like "C:\foo bar\baz.exe -arg" which is resolved to the argv array {"C:\foo", "bar\baz.exe", "-arg"}.
|
||||
// Just like CreateProcessW() we thus try to concatenate arguments until we successfully resolve a valid path.
|
||||
// Of course we can only do that if we have at least 2 remaining arguments in argv.
|
||||
// All other error types aren't handled at the moment.
|
||||
if (argc < 2 || status != HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// As described in the comment right above, we concatenate arguments in an attempt to resolve a valid path.
|
||||
// The code below turns argv from {"C:\foo", "bar\baz.exe", "-arg"} into {"C:\foo bar\baz.exe", "-arg"}.
|
||||
// The code abuses the fact that CommandLineToArgvW allocates all arguments back-to-back on the heap separated by '\0'.
|
||||
argv[1][-1] = L' ';
|
||||
--argc;
|
||||
}
|
||||
|
||||
// We've (hopefully) finished resolving the path to the executable.
|
||||
// We're now going to append all remaining arguments to the resulting string.
|
||||
// If argv is {"C:\Program Files\PowerShell\7\pwsh.exe", "-WorkingDirectory", "~"},
|
||||
// then we'll get "C:\Program Files\PowerShell\7\pwsh.exe\0-WorkingDirectory\0~"
|
||||
if (argc > 1)
|
||||
{
|
||||
// normalized contains a canonical form of argv[0] at this point.
|
||||
// -1 allows us to include the \0 between argv[0] and argv[1] in the call to append().
|
||||
const auto beg = argv[1] - 1;
|
||||
const auto lastArg = argv[argc - 1];
|
||||
const auto end = lastArg + wcslen(lastArg);
|
||||
normalized.append(beg, end);
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Helper to get a profile given a name that could be a guid or an actual name.
|
||||
// Arguments:
|
||||
|
|
|
@ -141,8 +141,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
|
||||
private:
|
||||
static const std::filesystem::path& _settingsPath();
|
||||
static std::wstring _normalizeCommandLine(LPCWSTR commandLine);
|
||||
|
||||
winrt::com_ptr<implementation::Profile> _createNewProfile(const std::wstring_view& name) const;
|
||||
Model::Profile _getProfileForCommandLine(const winrt::hstring& commandLine) const;
|
||||
void _refreshDefaultTerminals();
|
||||
|
||||
void _resolveDefaultProfile() const;
|
||||
|
@ -168,6 +170,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
// defterm
|
||||
winrt::Windows::Foundation::Collections::IObservableVector<Model::DefaultTerminal> _defaultTerminals{ nullptr };
|
||||
Model::DefaultTerminal _currentDefaultTerminal{ nullptr };
|
||||
|
||||
// GetProfileForArgs cache
|
||||
mutable std::once_flag _commandLinesCacheOnce;
|
||||
mutable std::vector<std::pair<std::wstring, Model::Profile>> _commandLinesCache;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -269,14 +269,12 @@ static bool getWslNames(const wil::unique_hkey& wslRootKey,
|
|||
|
||||
std::wstring buffer;
|
||||
auto result = wil::AdaptFixedSizeToAllocatedResult<std::wstring, 256>(buffer, [&](PWSTR value, size_t valueLength, size_t* valueLengthNeededWithNull) -> HRESULT {
|
||||
auto length = static_cast<DWORD>(valueLength);
|
||||
auto length = gsl::narrow<DWORD>(valueLength * sizeof(wchar_t));
|
||||
const auto status = RegQueryValueExW(distroKey.get(), RegKeyDistroName, 0, nullptr, reinterpret_cast<BYTE*>(value), &length);
|
||||
// length will receive the number of bytes - convert to a number of
|
||||
// wchar_t's. AdaptFixedSizeToAllocatedResult will resize buffer to
|
||||
// valueLengthNeededWithNull
|
||||
*valueLengthNeededWithNull = (length / sizeof(wchar_t));
|
||||
// If you add one for another trailing null, then there'll actually
|
||||
// be _two_ trailing nulls in the buffer.
|
||||
// length will receive the number of bytes including trailing null byte. Convert to a number of wchar_t's.
|
||||
// AdaptFixedSizeToAllocatedResult will then resize buffer to valueLengthNeededWithNull.
|
||||
// We're rounding up to prevent infinite loops if the data isn't a REG_SZ and length isn't divisible by 2.
|
||||
*valueLengthNeededWithNull = (length + sizeof(wchar_t) - 1) / sizeof(wchar_t);
|
||||
return status == ERROR_MORE_DATA ? S_OK : HRESULT_FROM_WIN32(status);
|
||||
});
|
||||
|
||||
|
|
|
@ -112,7 +112,7 @@
|
|||
<SDLCheck>true</SDLCheck>
|
||||
<PrecompiledHeaderFile>precomp.h</PrecompiledHeaderFile>
|
||||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)\src\inc;$(SolutionDir)\dep;$(SolutionDir)\dep\Console;$(SolutionDir)\dep\Win32K;$(SolutionDir)\dep\gsl\include;$(SolutionDir)\dep\wil\include;$(SolutionDir)\oss\chromium;$(SolutionDir)\oss\fmt\include;$(SolutionDir)\oss\dynamic_bitset;$(SolutionDir)\oss\libpopcnt;$(SolutionDir)\oss\interval_tree;$(SolutionDir)\oss\boost\boost_1_73_0;%(AdditionalIncludeDirectories);</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)\src\inc;$(SolutionDir)\dep;$(SolutionDir)\dep\Console;$(SolutionDir)\dep\gsl\include;$(SolutionDir)\dep\wil\include;$(SolutionDir)\dep\Win32K;$(SolutionDir)\oss\boost\boost_1_73_0;$(SolutionDir)\oss\chromium;$(SolutionDir)\oss\dynamic_bitset;$(SolutionDir)\oss\fmt\include;$(SolutionDir)\oss\interval_tree;$(SolutionDir)\oss\libpopcnt;$(SolutionDir)\oss\pcg\include;%(AdditionalIncludeDirectories);</AdditionalIncludeDirectories>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<MinimalRebuild>false</MinimalRebuild>
|
||||
<RuntimeTypeInfo>false</RuntimeTypeInfo>
|
||||
|
|
|
@ -31,9 +31,7 @@
|
|||
return Status;
|
||||
}
|
||||
|
||||
// This is the actual field name, but in the public SDK, it's named Reserved3. We need to pursue publishing the real name.
|
||||
//*ProcessId = (ULONG)BasicInfo.InheritedFromUniqueProcessId;
|
||||
*ProcessId = (ULONG)BasicInfo.Reserved3;
|
||||
*ProcessId = (ULONG)BasicInfo.InheritedFromUniqueProcessId;
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
|
|
|
@ -28,11 +28,12 @@ typedef enum _PROCESSINFOCLASS
|
|||
|
||||
typedef struct _PROCESS_BASIC_INFORMATION
|
||||
{
|
||||
PVOID Reserved1;
|
||||
NTSTATUS ExitStatus;
|
||||
PVOID PebBaseAddress;
|
||||
PVOID Reserved2[2];
|
||||
ULONG_PTR AffinityMask;
|
||||
LONG BasePriority;
|
||||
ULONG_PTR UniqueProcessId;
|
||||
ULONG_PTR Reserved3;
|
||||
ULONG_PTR InheritedFromUniqueProcessId;
|
||||
} PROCESS_BASIC_INFORMATION;
|
||||
typedef PROCESS_BASIC_INFORMATION* PPROCESS_BASIC_INFORMATION;
|
||||
|
||||
|
|
|
@ -453,7 +453,7 @@ try
|
|||
|
||||
RETURN_IF_WIN32_BOOL_FALSE(CreatePipe(outPipeTheirSide.addressof(), outPipeOurSide.addressof(), nullptr, 0));
|
||||
|
||||
wil::unique_handle clientProcess{ OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, TRUE, static_cast<DWORD>(connectMessage->Descriptor.Process)) };
|
||||
wil::unique_handle clientProcess{ OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | SYNCHRONIZE, TRUE, static_cast<DWORD>(connectMessage->Descriptor.Process)) };
|
||||
RETURN_LAST_ERROR_IF_NULL(clientProcess.get());
|
||||
|
||||
wil::unique_handle refHandle;
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include <pcg_random.hpp>
|
||||
|
||||
namespace til
|
||||
{
|
||||
namespace details
|
||||
{
|
||||
typedef BOOLEAN(APIENTRY* RtlGenRandom)(PVOID RandomBuffer, ULONG RandomBufferLength);
|
||||
|
||||
struct RtlGenRandomLoader
|
||||
{
|
||||
RtlGenRandomLoader() noexcept :
|
||||
// The documentation states to use advapi32.dll, but technically
|
||||
// SystemFunction036 lives in cryptbase.dll since Windows 7.
|
||||
module{ LoadLibraryExW(L"cryptbase.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32) },
|
||||
proc{ reinterpret_cast<decltype(proc)>(GetProcAddress(module.get(), "SystemFunction036")) }
|
||||
{
|
||||
FAIL_FAST_LAST_ERROR_IF(!proc);
|
||||
}
|
||||
|
||||
inline void operator()(PVOID RandomBuffer, ULONG RandomBufferLength)
|
||||
{
|
||||
proc(RandomBuffer, RandomBufferLength);
|
||||
}
|
||||
|
||||
private:
|
||||
wil::unique_hmodule module;
|
||||
RtlGenRandom proc;
|
||||
};
|
||||
}
|
||||
|
||||
inline void gen_random(void* data, uint32_t length)
|
||||
{
|
||||
static details::RtlGenRandomLoader loader;
|
||||
loader(data, length);
|
||||
}
|
||||
|
||||
template<typename T, typename = std::enable_if_t<std::is_standard_layout_v<T>>>
|
||||
T gen_random()
|
||||
{
|
||||
T value;
|
||||
gen_random(&value, sizeof(T));
|
||||
return value;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue