Compare commits

..

5 Commits

Author SHA1 Message Date
Mike Griese
5eb08ff804 Merge branch 'main' into dev/miniksa/env 2023-01-24 06:50:54 -06:00
Dustin L. Howett
65ada350ce Migrate spelling-0.0.21 changes from main 2022-01-14 18:04:01 -08:00
Michael Niksa
3fb8a8be4b make a note 2022-01-14 18:04:01 -08:00
Michael Niksa
cd1c4b6b10 IT WORKS! 2022-01-14 17:52:17 -08:00
Michael Niksa
6cac0969f3 attempt to replicate algorithm 2022-01-14 15:15:39 -08:00
32 changed files with 725 additions and 317 deletions

View File

@@ -14,7 +14,6 @@
<UseWmXml>true</UseWmXml>
<ConfigurationType>Application</ConfigurationType>
<OpenConsoleCppWinRTProject>true</OpenConsoleCppWinRTProject>
<EnableHybridCRT>false</EnableHybridCRT> <!-- C++/CLI projects can't deal -->
<!--
These two properties are very important!

View File

@@ -60,4 +60,4 @@
<AdditionalDependencies>Uiautomationcore.lib;onecoreuap.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
</Project>
</Project>

View File

@@ -68,7 +68,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
void WindowManager::SignalClose()
{
if (_monarch && _peasant)
if (_monarch)
{
try
{

View File

@@ -77,6 +77,7 @@ static void EnsureAllResourcesArePresent(const ScopedResourceLoader& loader)
#endif
static ScopedResourceLoader GetLibraryResourceLoader()
try
{
ScopedResourceLoader loader{ g_WinRTUtilsLibraryResourceScope };
#ifdef _DEBUG
@@ -84,15 +85,20 @@ static ScopedResourceLoader GetLibraryResourceLoader()
#endif
return loader;
}
CATCH_FAIL_FAST()
winrt::hstring GetLibraryResourceString(const std::wstring_view key)
try
{
static auto loader{ GetLibraryResourceLoader() };
return loader.GetLocalizedString(key);
}
CATCH_FAIL_FAST()
bool HasLibraryResourceWithName(const std::wstring_view key)
try
{
static auto loader{ GetLibraryResourceLoader() };
return loader.HasResourceWithName(key);
}
CATCH_FAIL_FAST()

View File

@@ -28,7 +28,7 @@ using namespace std::chrono_literals;
// "If the high-order bit is 1, the key is down; otherwise, it is up."
static constexpr short KeyPressed{ gsl::narrow_cast<short>(0x8000) };
AppHost::AppHost() :
AppHost::AppHost() noexcept :
_app{},
_windowManager{},
_logic{ nullptr }, // don't make one, we're going to take a ref on app's

View File

@@ -9,7 +9,7 @@
class AppHost
{
public:
AppHost();
AppHost() noexcept;
virtual ~AppHost();
void AppTitleChanged(const winrt::Windows::Foundation::IInspectable& sender, winrt::hstring newTitle);

View File

@@ -1,81 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "resource.h"
#include <WerApi.h>
typedef wil::unique_any<HREPORT, decltype(&WerReportCloseHandle), WerReportCloseHandle> unique_wer_report;
struct ErrorDialogContext
{
HRESULT hr;
std::wstring message;
HFONT font{ nullptr };
~ErrorDialogContext()
{
if (font)
{
DeleteObject(font);
}
}
};
static LRESULT CALLBACK ErrDlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_INITDIALOG:
{
// We've passed in the pointer to an error dialog context using DialogBoxParam
ErrorDialogContext* ctx{ reinterpret_cast<ErrorDialogContext*>(lParam) };
SetWindowLongPtrW(hDlg, DWLP_USER, reinterpret_cast<LONG_PTR>(ctx));
HWND editControl{ GetDlgItem(hDlg, IDC_ERRVALUE) };
const auto message{ std::format(L"HR 0x{0:08x}\r\n{1}", static_cast<unsigned int>(ctx->hr), ctx->message) };
SetWindowTextW(editControl, message.c_str());
HFONT& font{ ctx->font };
const auto fontHeight{ -MulDiv(10, GetDpiForWindow(hDlg), 72) };
font = CreateFontW(fontHeight, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, FF_DONTCARE | FIXED_PITCH, L"Cascadia Mono");
if (!font)
{
font = CreateFontW(fontHeight, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, FF_DONTCARE | FIXED_PITCH, L"Consolas");
}
// Context takes ownership of font and deletes it later
SendMessageW(editControl, WM_SETFONT, reinterpret_cast<WPARAM>(font), 0);
break;
}
case WM_DESTROY:
{
ErrorDialogContext* ctx{ reinterpret_cast<ErrorDialogContext*>(GetWindowLongPtrW(hDlg, DWLP_USER)) };
delete ctx;
break;
}
case WM_COMMAND:
{
switch (LOWORD(wParam))
{
case IDCANCEL:
case IDOK:
EndDialog(hDlg, TRUE);
return TRUE;
}
}
}
return FALSE;
}
void DisplayErrorDialogBlockingAndReport(const HRESULT hr, const std::wstring_view message)
{
auto ctx = new ErrorDialogContext{ hr, std::wstring{ message } };
DialogBoxParamW(nullptr, MAKEINTRESOURCEW(IDD_ERRDIALOG), nullptr, ErrDlgProc, reinterpret_cast<LPARAM>(ctx));
unique_wer_report errorReport;
WerReportCreate(L"AppCrash", WerReportApplicationCrash, nullptr, errorReport.put());
WerReportAddDump(errorReport.get(), GetCurrentProcess(), nullptr, WerDumpTypeMiniDump, nullptr, nullptr, 0);
WerReportSubmit(errorReport.get(), WerConsentNotAsked, 0, nullptr);
TerminateProcess(GetCurrentProcess(), 1);
}

View File

@@ -1,2 +0,0 @@
void DisplayErrorDialogBlockingAndReport(const HRESULT hr, const std::wstring_view message);

View File

@@ -68,23 +68,6 @@ IDI_APPICON_HC_WHITE ICON "..\\..\\..\\res\\terminal\\imag
#endif
/////////////////////////////////////////////////////////////////////////////
//
// Dialog
//
IDD_ERRDIALOG DIALOGEX 0, 0, 310, 177
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Dialog"
FONT 10, "MS Shell Dlg", 400, 0, 0x1
BEGIN
DEFPUSHBUTTON "OK",IDOK,253,156,50,14
ICON 32513,IDC_STATIC,7,7,20,20
LTEXT "Windows Terminal has encountered an error and must exit.",IDC_STATIC,31,7,272,32
EDITTEXT IDC_ERRVALUE,7,31,296,121,ES_MULTILINE | ES_READONLY
END
/////////////////////////////////////////////////////////////////////////////
//
// String Table

View File

@@ -38,7 +38,7 @@
<AdditionalIncludeDirectories>$(OpenConsoleDir)\src\inc;$(OpenConsoleDir)\dep;$(OpenConsoleDir)\dep\Console;$(OpenConsoleDir)\dep\Win32K;$(OpenConsoleDir)\dep\gsl\include;%(AdditionalIncludeDirectories);</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<AdditionalDependencies>wer.lib;gdi32.lib;dwmapi.lib;Shcore.lib;UxTheme.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>gdi32.lib;dwmapi.lib;Shcore.lib;UxTheme.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<PropertyGroup>
@@ -68,7 +68,6 @@
<ClCompile Include="NotificationIcon.cpp" />
<ClCompile Include="VirtualDesktopUtils.cpp" />
<ClCompile Include="icon.cpp" />
<ClCompile Include="ErrorDialog.cpp" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="WindowsTerminal.rc" />

View File

@@ -6,8 +6,6 @@
#include "resource.h"
#include "../types/inc/User32Utils.hpp"
#include <WilErrorReporting.h>
#include <winrt/Windows.ApplicationModel.Core.h>
#include "ErrorDialog.h"
using namespace winrt;
using namespace winrt::Windows::UI;
@@ -129,65 +127,25 @@ int __stdcall wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int)
// doing that, we can safely init as STA before any WinRT dispatches.
winrt::init_apartment(winrt::apartment_type::single_threaded);
auto unhandledErrorRevoker{
winrt::Windows::ApplicationModel::Core::CoreApplication::UnhandledErrorDetected(winrt::auto_revoke, [](auto&&...) {
winrt::com_ptr<IRestrictedErrorInfo> restrictedErrorInfo;
if (SUCCEEDED(GetRestrictedErrorInfo(restrictedErrorInfo.put())) && restrictedErrorInfo)
{
wil::unique_bstr description, restrictedDescription, unused;
HRESULT hr;
if (SUCCEEDED(restrictedErrorInfo->GetErrorDetails(description.put(), &hr, restrictedDescription.put(), unused.put())))
{
DisplayErrorDialogBlockingAndReport(hr, restrictedDescription.get());
}
}
})
};
std::optional<AppHost> possiblyAppHost{ std::nullopt };
try
// Create the AppHost object, which will create both the window and the
// Terminal App. This MUST BE constructed before the Xaml manager as TermApp
// provides an implementation of Windows.UI.Xaml.Application.
AppHost host;
if (!host.HasWindow())
{
// Create the AppHost object, which will create both the window and the
// Terminal App. This MUST BE constructed before the Xaml manager as TermApp
// provides an implementation of Windows.UI.Xaml.Application.
possiblyAppHost.emplace();
if (!possiblyAppHost->HasWindow())
{
// If we were told to not have a window, exit early. Make sure to use
// ExitProcess to die here. If you try just `return 0`, then
// the XAML app host will crash during teardown. ExitProcess avoids
// that.
ExitProcess(0);
}
// Initialize the xaml content. This must be called AFTER the
// WindowsXamlManager is initialized.
possiblyAppHost->Initialize();
}
catch (const std::exception& e)
{
const auto wideErrorMessage{ til::u8u16(e.what()) };
DisplayErrorDialogBlockingAndReport(E_FAIL, wideErrorMessage);
}
catch (const winrt::hresult_error& e)
{
DisplayErrorDialogBlockingAndReport(e.code(), e.message());
}
catch (...)
{
DisplayErrorDialogBlockingAndReport(E_FAIL, L"LOL");
// If we were told to not have a window, exit early. Make sure to use
// ExitProcess to die here. If you try just `return 0`, then
// the XAML app host will crash during teardown. ExitProcess avoids
// that.
ExitProcess(0);
}
AppHost& host{ *possiblyAppHost };
// Initialize the xaml content. This must be called AFTER the
// WindowsXamlManager is initialized.
host.Initialize();
MSG message;
if (!Feature_ReportErrorsThroughoutAppLifetime::IsEnabled())
{
// Disengage error reporting after XAML starts up.
unhandledErrorRevoker.revoke();
}
while (GetMessage(&message, nullptr, 0, 0))
{
// GH#638 (Pressing F7 brings up both the history AND a caret browsing message)

View File

@@ -15,10 +15,6 @@
#define IDS_ARM_ARCHITECTURE 114
#define IDS_UNKNOWN_ARCHITECTURE 115
#define IDD_ERRDIALOG 129
#define IDC_ERRVALUE 1001
#define IDC_STATIC -1
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED

View File

@@ -4,42 +4,6 @@
<Natvis Include="$(SolutionDir)tools\ConsoleTypes.natvis" />
</ItemGroup>
<!--
The Hybrid CRT model statically links the runtime and STL and dynamically
links the UCRT instead of the VC++ CRT. The UCRT ships with Windows.
WinAppSDK asserts that this is "supported according to the CRT maintainer."
This must come before Microsoft.Cpp.targets because it manipulates ClCompile.RuntimeLibrary.
-->
<ItemDefinitionGroup Condition="'$(EnableHybridCRT)'=='true' and '$(Configuration)'=='Debug'">
<ClCompile>
<!-- We use MultiThreadedDebug, rather than MultiThreadedDebugDLL, to avoid DLL dependencies on VCRUNTIME140d.dll and MSVCP140d.dll. -->
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
</ClCompile>
<Link>
<!-- Link statically against the runtime and STL, but link dynamically against the CRT by ignoring the static CRT
lib and instead linking against the Universal CRT DLL import library. This "hybrid" linking mechanism is
supported according to the CRT maintainer. Dynamic linking against the CRT makes the binaries a bit smaller
than they would otherwise be if the CRT, runtime, and STL were all statically linked in. -->
<IgnoreSpecificDefaultLibraries>%(IgnoreSpecificDefaultLibraries);libucrtd.lib</IgnoreSpecificDefaultLibraries>
<AdditionalOptions>%(AdditionalOptions) /defaultlib:ucrtd.lib</AdditionalOptions>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(EnableHybridCRT)'=='true' and '$(Configuration)'=='Release'">
<ClCompile>
<!-- We use MultiThreaded, rather than MultiThreadedDLL, to avoid DLL dependencies on VCRUNTIME140.dll and MSVCP140.dll. -->
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
</ClCompile>
<Link>
<!-- Link statically against the runtime and STL, but link dynamically against the CRT by ignoring the static CRT
lib and instead linking against the Universal CRT DLL import library. This "hybrid" linking mechanism is
supported according to the CRT maintainer. Dynamic linking against the CRT makes the binaries a bit smaller
than they would otherwise be if the CRT, runtime, and STL were all statically linked in. -->
<IgnoreSpecificDefaultLibraries>%(IgnoreSpecificDefaultLibraries);libucrt.lib</IgnoreSpecificDefaultLibraries>
<AdditionalOptions>%(AdditionalOptions) /defaultlib:ucrt.lib</AdditionalOptions>
</Link>
</ItemDefinitionGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ItemDefinitionGroup>

View File

@@ -28,7 +28,6 @@
<AppContainerApplication>true</AppContainerApplication>
<WindowsStoreApp>true</WindowsStoreApp>
<ApplicationType>Windows Store</ApplicationType>
<UseCrtSDKReference Condition="'$(EnableHybridCRT)'=='true'">false</UseCrtSDKReference> <!-- The SDK reference breaks the Hybrid CRT -->
</PropertyGroup>
<PropertyGroup Condition="'$(OpenConsoleUniversalApp)'!='true'">
<!-- Some of our projects include the cppwinrt build options to

View File

@@ -145,14 +145,4 @@
<stage>AlwaysDisabled</stage>
</feature>
<feature>
<name>Feature_ReportErrorsThroughoutAppLifetime</name>
<description>Keep the error handler enabled throughout the lifetime of the application rather than only during startup</description>
<stage>AlwaysEnabled</stage>
<alwaysDisabledBrandingTokens>
<!-- For now, let's just let Release crash. Seeing a bunch of error dialogs might, puzzlingly, make us seem *less* reliable -->
<brandingToken>Release</brandingToken>
</alwaysDisabledBrandingTokens>
</feature>
</featureStaging>

View File

@@ -805,7 +805,7 @@ using Microsoft::Console::VirtualTerminal::StateMachine;
// move cursor to the next line.
pwchBuffer++;
if (WI_IsFlagClear(screenInfo.OutputMode, DISABLE_NEWLINE_AUTO_RETURN))
if (gci.IsReturnOnNewlineAutomatic())
{
// Traditionally, we reset the X position to 0 with a newline automatically.
// Some things might not want this automatic "ONLCR line discipline" (for example, things that are expecting a *NIX behavior.)

View File

@@ -21,7 +21,6 @@ class ModeTests
TEST_METHOD(TestConsoleModeInputScenario);
TEST_METHOD(TestConsoleModeScreenBufferScenario);
TEST_METHOD(TestConsoleModeAcrossMultipleBuffers);
TEST_METHOD(TestGetConsoleDisplayMode);
@@ -95,55 +94,6 @@ void ModeTests::TestConsoleModeScreenBufferScenario()
VERIFY_ARE_EQUAL(dwOutputMode, (DWORD)0, L"Verify able to set zero output flags");
}
void ModeTests::TestConsoleModeAcrossMultipleBuffers()
{
auto dwInitialMode = (DWORD)-1;
VERIFY_WIN32_BOOL_SUCCEEDED(GetConsoleMode(Common::_hConsole, &dwInitialMode),
L"Get initial output flags");
Log::Comment(L"Verify initial flags match the expected defaults");
VERIFY_IS_TRUE(WI_IsFlagSet(dwInitialMode, ENABLE_PROCESSED_OUTPUT));
VERIFY_IS_TRUE(WI_IsFlagSet(dwInitialMode, ENABLE_WRAP_AT_EOL_OUTPUT));
VERIFY_IS_TRUE(WI_IsFlagClear(dwInitialMode, DISABLE_NEWLINE_AUTO_RETURN));
VERIFY_IS_TRUE(WI_IsFlagClear(dwInitialMode, ENABLE_LVB_GRID_WORLDWIDE));
// The initial VT flag may vary, dependent on the VirtualTerminalLevel registry entry.
const auto defaultVtFlag = WI_IsFlagSet(dwInitialMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING);
auto dwUpdatedMode = dwInitialMode;
WI_ClearFlag(dwUpdatedMode, ENABLE_PROCESSED_OUTPUT);
WI_ClearFlag(dwUpdatedMode, ENABLE_WRAP_AT_EOL_OUTPUT);
WI_SetFlag(dwUpdatedMode, DISABLE_NEWLINE_AUTO_RETURN);
WI_SetFlag(dwUpdatedMode, ENABLE_LVB_GRID_WORLDWIDE);
WI_UpdateFlag(dwUpdatedMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING, !defaultVtFlag);
VERIFY_WIN32_BOOL_SUCCEEDED(SetConsoleMode(Common::_hConsole, dwUpdatedMode),
L"Update flags to the opposite of their initial values");
auto hSecondBuffer = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE,
0 /*dwShareMode*/,
nullptr /*lpSecurityAttributes*/,
CONSOLE_TEXTMODE_BUFFER,
nullptr /*lpReserved*/);
VERIFY_ARE_NOT_EQUAL(INVALID_HANDLE_VALUE, hSecondBuffer, L"Create a second screen buffer");
auto dwSecondBufferMode = (DWORD)-1;
VERIFY_WIN32_BOOL_SUCCEEDED(GetConsoleMode(hSecondBuffer, &dwSecondBufferMode),
L"Get initial flags for second buffer");
VERIFY_ARE_EQUAL(dwInitialMode, dwSecondBufferMode, L"Verify second buffer initialized with defaults");
VERIFY_WIN32_BOOL_SUCCEEDED(SetConsoleMode(hSecondBuffer, dwSecondBufferMode),
L"Reapply mode to test if it affects the main buffer");
VERIFY_WIN32_BOOL_SUCCEEDED(CloseHandle(hSecondBuffer), L"Close the second buffer");
auto dwFinalMode = (DWORD)-1;
VERIFY_WIN32_BOOL_SUCCEEDED(GetConsoleMode(Common::_hConsole, &dwFinalMode),
L"Get flags from the main buffer again");
VERIFY_ARE_EQUAL(dwUpdatedMode, dwFinalMode, L"Verify main buffer flags haven't changed");
}
void ModeTests::TestGetConsoleDisplayMode()
{
DWORD dwMode = 0;

View File

@@ -436,6 +436,10 @@ void ApiRoutines::GetNumberOfConsoleMouseButtonsImpl(ULONG& buttons) noexcept
screenInfo.GetStateMachine().ResetState();
}
gci.SetVirtTermLevel(WI_IsFlagSet(dwNewMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING) ? 1 : 0);
gci.SetAutomaticReturnOnNewline(WI_IsFlagSet(screenInfo.OutputMode, DISABLE_NEWLINE_AUTO_RETURN) ? false : true);
gci.SetGridRenderingAllowedWorldwide(WI_IsFlagSet(screenInfo.OutputMode, ENABLE_LVB_GRID_WORLDWIDE));
// if we changed rendering modes then redraw the output buffer,
// but only do this if we're not in conpty mode.
if (!gci.IsInVtIoMode() &&

View File

@@ -166,8 +166,8 @@ void ConhostInternalGetSet::SetScrollingRegion(const til::inclusive_rect& scroll
// - true if a line feed also produces a carriage return. false otherwise.
bool ConhostInternalGetSet::GetLineFeedMode() const
{
auto& screenInfo = _io.GetActiveOutputBuffer();
return WI_IsFlagClear(screenInfo.OutputMode, DISABLE_NEWLINE_AUTO_RETURN);
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
return gci.IsReturnOnNewlineAutomatic();
}
// Routine Description:

View File

@@ -17,13 +17,6 @@
regular builds, while preventing the build failure during fuzzing builds.
-->
<ConfigurationType Condition="'$(Configuration)'=='Fuzzing'">StaticLibrary</ConfigurationType>
<!--
OpenConsoleProxy gets copied out of our app package and into a shared system store. As such, it can't take a
dependency on any libraries inside our package **or** inside any of our dependency packages. It has to stand
on its own.
-->
<EnableHybridCRT>true</EnableHybridCRT>
</PropertyGroup>
<Import Project="$(SolutionDir)src\common.build.pre.props" />
<ItemGroup>
@@ -84,6 +77,43 @@
</Link>
</ItemDefinitionGroup>
<!--
OpenConsoleProxy gets copied out of our app package and into a shared system store. As such, it can't take a
dependency on any libraries inside our package **or** inside any of our dependency packages. It has to stand
on its own.
Therefore, we're going to use the Hybrid CRT model from WinAppSDK for only OpenConsoleProxy. It statically
links the runtime and STL and dynamically links the UCRT instead of the VC++ CRT. The UCRT ships with Windows.
WinAppSDK asserts that this is "supported according to the CRT maintainer."
-->
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ClCompile>
<!-- We use MultiThreadedDebug, rather than MultiThreadedDebugDLL, to avoid DLL dependencies on VCRUNTIME140d.dll and MSVCP140d.dll. -->
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
</ClCompile>
<Link>
<!-- Link statically against the runtime and STL, but link dynamically against the CRT by ignoring the static CRT
lib and instead linking against the Universal CRT DLL import library. This "hybrid" linking mechanism is
supported according to the CRT maintainer. Dynamic linking against the CRT makes the binaries a bit smaller
than they would otherwise be if the CRT, runtime, and STL were all statically linked in. -->
<IgnoreSpecificDefaultLibraries>%(IgnoreSpecificDefaultLibraries);libucrtd.lib</IgnoreSpecificDefaultLibraries>
<AdditionalOptions>%(AdditionalOptions) /defaultlib:ucrtd.lib</AdditionalOptions>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<!-- We use MultiThreaded, rather than MultiThreadedDLL, to avoid DLL dependencies on VCRUNTIME140.dll and MSVCP140.dll. -->
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
</ClCompile>
<Link>
<!-- Link statically against the runtime and STL, but link dynamically against the CRT by ignoring the static CRT
lib and instead linking against the Universal CRT DLL import library. This "hybrid" linking mechanism is
supported according to the CRT maintainer. Dynamic linking against the CRT makes the binaries a bit smaller
than they would otherwise be if the CRT, runtime, and STL were all statically linked in. -->
<IgnoreSpecificDefaultLibraries>%(IgnoreSpecificDefaultLibraries);libucrt.lib</IgnoreSpecificDefaultLibraries>
<AdditionalOptions>%(AdditionalOptions) /defaultlib:ucrt.lib</AdditionalOptions>
</Link>
</ItemDefinitionGroup>
<!-- Careful reordering these. Some default props (contained in these files) are order sensitive. -->
<Import Project="$(SolutionDir)src\common.build.post.props" />
</Project>

View File

@@ -261,20 +261,26 @@ bool RenderData::IsCursorDoubleWidth() const noexcept
const bool RenderData::IsGridLineDrawingAllowed() noexcept
{
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
const auto outputMode = gci.GetActiveOutputBuffer().OutputMode;
// If virtual terminal output is set, grid line drawing is a must. It is also enabled
// if someone explicitly asked for worldwide line drawing.
if (WI_IsAnyFlagSet(outputMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING | ENABLE_LVB_GRID_WORLDWIDE))
// If virtual terminal output is set, grid line drawing is a must. It is always allowed.
if (WI_IsFlagSet(gci.GetActiveOutputBuffer().OutputMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING))
{
return true;
}
else
{
// Otherwise, for compatibility reasons with legacy applications that used the additional CHAR_INFO bits by accident or for their own purposes,
// we must enable grid line drawing only in a DBCS output codepage. (Line drawing historically only worked in DBCS codepages.)
// The only known instance of this is Image for Windows by TeraByte, Inc. (TeraByte Unlimited) which used the bits accidentally and for no purpose
// (according to the app developer) in conjunction with the Borland Turbo C cgscrn library.
return !!IsAvailableEastAsianCodePage(gci.OutputCP);
// If someone explicitly asked for worldwide line drawing, enable it.
if (gci.IsGridRenderingAllowedWorldwide())
{
return true;
}
else
{
// Otherwise, for compatibility reasons with legacy applications that used the additional CHAR_INFO bits by accident or for their own purposes,
// we must enable grid line drawing only in a DBCS output codepage. (Line drawing historically only worked in DBCS codepages.)
// The only known instance of this is Image for Windows by TeraByte, Inc. (TeraByte Unlimited) which used the bits accidentally and for no purpose
// (according to the app developer) in conjunction with the Borland Turbo C cgscrn library.
return !!IsAvailableEastAsianCodePage(gci.OutputCP);
}
}
}

View File

@@ -58,11 +58,10 @@ SCREEN_INFORMATION::SCREEN_INFORMATION(
_desiredFont{ fontInfo },
_ignoreLegacyEquivalentVTAttributes{ false }
{
// Check if VT mode should be enabled by default. This can be true if
// VirtualTerminalLevel is set to !=0 in the registry, or when conhost
// is started in conpty mode.
// Check if VT mode is enabled. Note that this can be true w/o calling
// SetConsoleMode, if VirtualTerminalLevel is set to !=0 in the registry.
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
if (gci.GetDefaultVirtTermLevel() != 0)
if (gci.GetVirtTermLevel() != 0)
{
OutputMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
}
@@ -1914,8 +1913,6 @@ const SCREEN_INFORMATION& SCREEN_INFORMATION::GetMainBuffer() const
auto altCursorPos = myCursor.GetPosition();
altCursorPos.y -= GetVirtualViewport().Top();
altCursor.SetPosition(altCursorPos);
// The alt buffer's output mode should match the main buffer.
createdBuffer->OutputMode = OutputMode;
s_InsertScreenBuffer(createdBuffer);
@@ -2081,9 +2078,6 @@ void SCREEN_INFORMATION::UseMainScreenBuffer()
mainCursor.SetIsVisible(altCursor.IsVisible());
mainCursor.SetBlinkingAllowed(altCursor.IsBlinkingAllowed());
// Copy the alt buffer's output mode back to the main buffer.
psiMain->OutputMode = psiAlt->OutputMode;
s_RemoveScreenBuffer(psiAlt); // this will also delete the alt buffer
// deleting the alt buffer will give the GetSet back to its main

View File

@@ -51,6 +51,8 @@ Settings::Settings() :
_fAllowAltF4Close(true),
_dwVirtTermLevel(0),
_fUseWindowSizePixels(false),
_fAutoReturnOnNewline(true), // the historic Windows behavior defaults this to on.
_fRenderGridWorldwide(false), // historically grid lines were only rendered in DBCS codepages, so this is false by default unless otherwise specified.
// window size pixels initialized below
_fInterceptCopyPaste(0),
_fUseDx(UseDx::Disabled),
@@ -356,11 +358,11 @@ void Settings::Validate()
FAIL_FAST_IF(!(_dwScreenBufferSize.Y > 0));
}
DWORD Settings::GetDefaultVirtTermLevel() const
DWORD Settings::GetVirtTermLevel() const
{
return _dwVirtTermLevel;
}
void Settings::SetDefaultVirtTermLevel(const DWORD dwVirtTermLevel)
void Settings::SetVirtTermLevel(const DWORD dwVirtTermLevel)
{
_dwVirtTermLevel = dwVirtTermLevel;
}
@@ -374,6 +376,33 @@ void Settings::SetAltF4CloseAllowed(const bool fAllowAltF4Close)
_fAllowAltF4Close = fAllowAltF4Close;
}
bool Settings::IsReturnOnNewlineAutomatic() const
{
return _fAutoReturnOnNewline;
}
void Settings::SetAutomaticReturnOnNewline(const bool fAutoReturnOnNewline)
{
_fAutoReturnOnNewline = fAutoReturnOnNewline;
}
bool Settings::IsGridRenderingAllowedWorldwide() const
{
return _fRenderGridWorldwide;
}
void Settings::SetGridRenderingAllowedWorldwide(const bool fGridRenderingAllowed)
{
// Only trigger a notification and update the status if something has changed.
if (_fRenderGridWorldwide != fGridRenderingAllowed)
{
_fRenderGridWorldwide = fGridRenderingAllowed;
if (ServiceLocator::LocateGlobals().pRender != nullptr)
{
ServiceLocator::LocateGlobals().pRender->TriggerRedrawAll();
}
}
}
bool Settings::GetFilterOnPaste() const
{
return _fFilterOnPaste;

View File

@@ -50,12 +50,18 @@ public:
RenderSettings& GetRenderSettings() noexcept { return _renderSettings; };
const RenderSettings& GetRenderSettings() const noexcept { return _renderSettings; };
DWORD GetDefaultVirtTermLevel() const;
void SetDefaultVirtTermLevel(const DWORD dwVirtTermLevel);
DWORD GetVirtTermLevel() const;
void SetVirtTermLevel(const DWORD dwVirtTermLevel);
bool IsAltF4CloseAllowed() const;
void SetAltF4CloseAllowed(const bool fAllowAltF4Close);
bool IsReturnOnNewlineAutomatic() const;
void SetAutomaticReturnOnNewline(const bool fAutoReturnOnNewline);
bool IsGridRenderingAllowedWorldwide() const;
void SetGridRenderingAllowedWorldwide(const bool fGridRenderingAllowed);
bool GetFilterOnPaste() const;
void SetFilterOnPaste(const bool fFilterOnPaste);
@@ -219,6 +225,8 @@ private:
std::wstring _LaunchFaceName;
bool _fAllowAltF4Close;
DWORD _dwVirtTermLevel;
bool _fAutoReturnOnNewline;
bool _fRenderGridWorldwide;
UseDx _fUseDx;
bool _fCopyColor;

View File

@@ -198,7 +198,7 @@ static bool s_IsOnDesktop()
// We want everyone to be using VT by default anyways, so this is a
// strong nudge in that direction. If an application _doesn't_ want VT
// processing, it's free to disable this setting, even in conpty mode.
settings.SetDefaultVirtTermLevel(1);
settings.SetVirtTermLevel(1);
// GH#9458 - In the case of a DefTerm handoff, the OriginalTitle might
// be stashed in the lnk. We want to crack that lnk open, so we can get

View File

@@ -429,7 +429,7 @@ void Telemetry::WriteFinalTraceLog()
TraceLoggingValue(gci.GetScreenBufferSize().width, "ScreenBufferSizeX"),
TraceLoggingValue(gci.GetScreenBufferSize().height, "ScreenBufferSizeY"),
TraceLoggingValue(gci.GetStartupFlags(), "StartupFlags"),
TraceLoggingValue(gci.GetDefaultVirtTermLevel(), "VirtualTerminalLevel"),
TraceLoggingValue(gci.GetVirtTermLevel(), "VirtualTerminalLevel"),
TraceLoggingValue(gci.GetWindowSize().width, "WindowSizeX"),
TraceLoggingValue(gci.GetWindowSize().height, "WindowSizeY"),
TraceLoggingValue(gci.GetWindowOrigin().width, "WindowOriginX"),

View File

@@ -1537,6 +1537,7 @@ void TextBufferTests::TestBackspaceStringsAPI()
const auto& tbi = si.GetTextBuffer();
const auto& cursor = tbi.GetCursor();
gci.SetVirtTermLevel(0);
WI_ClearFlag(si.OutputMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING);
const auto x0 = cursor.GetPosition().x;

510
src/inc/til/env.h Normal file
View File

@@ -0,0 +1,510 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include "wil\token_helpers.h"
#include <winternl.h>
namespace til // Terminal Implementation Library. Also: "Today I Learned"
{
namespace details
{
//
// A case-insensitive wide-character map is used to store environment variables
// due to documented requirements:
//
// "All strings in the environment block must be sorted alphabetically by name.
// The sort is case-insensitive, Unicode order, without regard to locale.
// Because the equal sign is a separator, it must not be used in the name of
// an environment variable."
// https://docs.microsoft.com/en-us/windows/desktop/ProcThread/changing-environment-variables
//
struct wstring_case_insensitive_compare
{
[[nodiscard]] bool operator()(const std::wstring& lhs, const std::wstring& rhs) const noexcept
{
return (::_wcsicmp(lhs.c_str(), rhs.c_str()) < 0);
}
};
namespace vars
{
static constexpr std::wstring_view system_root{ L"SystemRoot" };
static constexpr std::wstring_view system_drive{ L"SystemDrive" };
static constexpr std::wstring_view all_users_profile{ L"ALLUSERSPROFILE" };
static constexpr std::wstring_view public_var{ L"PUBLIC" };
static constexpr std::wstring_view program_data{ L"ProgramData" };
static constexpr std::wstring_view computer_name{ L"COMPUTERNAME" };
static constexpr std::wstring_view user_name{ L"USERNAME" };
static constexpr std::wstring_view user_domain{ L"USERDOMAIN" };
static constexpr std::wstring_view user_dns_domain{ L"USERDNSDOMAIN" };
static constexpr std::wstring_view home_drive{ L"HOMEDRIVE" };
static constexpr std::wstring_view home_share{ L"HOMESHARE" };
static constexpr std::wstring_view home_path{ L"HOMEPATH" };
static constexpr std::wstring_view user_profile{ L"USERPROFILE" };
static constexpr std::wstring_view app_data{ L"APPDATA" };
static constexpr std::wstring_view local_app_data{ L"LOCALAPPDATA" };
static constexpr std::wstring_view program_files{ L"ProgramFiles" };
static constexpr std::wstring_view program_files_x86{ L"ProgramFiles(x86)" };
static constexpr std::wstring_view program_files_arm64{ L"ProgramFiles(Arm)" };
static constexpr std::wstring_view program_w6432{ L"ProgramW6432" };
static constexpr std::wstring_view common_program_files{ L"CommonProgramFiles" };
static constexpr std::wstring_view common_program_files_x86{ L"CommonProgramFiles(x86)" };
static constexpr std::wstring_view common_program_files_arm64{ L"CommonProgramFiles(Arm)" };
static constexpr std::wstring_view common_program_w6432{ L"CommonProgramW6432" };
const std::map<std::wstring, std::wstring_view> program_files_map{
{ L"ProgramFilesDir", program_files },
{ L"CommonFilesDir", common_program_files },
#ifdef _WIN64
#ifdef _M_ARM64
{ L"ProgramFilesDir (Arm)", program_files_arm64 },
{ L"CommonFilesDir (Arm)", common_program_files_arm64 },
#endif
{ L"ProgramFilesDir (x86)", program_files_x86 },
{ L"CommonFilesDir (x86)", common_program_files_x86 },
{ L"ProgramW6432Dir", program_w6432 },
{ L"CommonW6432Dir", common_program_w6432 },
#endif
};
namespace reg
{
static constexpr std::wstring_view program_files_root{ LR"(Software\Microsoft\Windows\CurrentVersion)" };
static constexpr std::wstring_view system_env_var_root{ LR"(SYSTEM\CurrentControlSet\Control\Session Manager\Environment)" };
static constexpr std::wstring_view user_env_var_root{ LR"(Environment)" };
static constexpr std::wstring_view user_volatile_env_var_root{ LR"(Volatile Environment)" };
static constexpr std::wstring_view user_volatile_session_env_var_root_pattern{ LR"(Volatile Environment\{0:d})" };
};
};
namespace wiltmp
{
/** Looks up the computer name and fails if it is not found. */
template<typename string_type, size_t initialBufferLength = MAX_COMPUTERNAME_LENGTH + 1>
inline HRESULT GetComputerNameW(string_type& result) WI_NOEXCEPT
{
return wil::AdaptFixedSizeToAllocatedResult<string_type, initialBufferLength>(result,
[&](_Out_writes_(valueLength) PWSTR value, size_t valueLength, _Out_ size_t* valueLengthNeededWithNul) -> HRESULT {
// If the function succeeds, the return value is the number of characters stored in the buffer
// pointed to by lpBuffer, not including the terminating null character.
//
// If lpBuffer is not large enough to hold the data, the return value is the buffer size, in
// characters, required to hold the string and its terminating null character and the contents of
// lpBuffer are undefined.
//
// If the function fails, the return value is zero. If the specified environment variable was not
// found in the environment block, GetLastError returns ERROR_ENVVAR_NOT_FOUND.
::SetLastError(ERROR_SUCCESS);
DWORD length = static_cast<DWORD>(valueLength);
auto result = ::GetComputerNameW(value, &length);
*valueLengthNeededWithNul = length;
RETURN_IF_WIN32_BOOL_FALSE_EXPECTED(result);
if (*valueLengthNeededWithNul < valueLength)
{
(*valueLengthNeededWithNul)++; // It fit, account for the null.
}
return S_OK;
});
}
/** Looks up the computer name and returns null if it is not found. */
template<typename string_type, size_t initialBufferLength = MAX_COMPUTERNAME_LENGTH + 1>
HRESULT TryGetComputerNameW(string_type& result) WI_NOEXCEPT
{
const auto hr = wiltmp::GetComputerNameW<string_type, initialBufferLength>(result);
RETURN_HR_IF(hr, FAILED(hr) && (hr != HRESULT_FROM_WIN32(ERROR_ENVVAR_NOT_FOUND)));
return S_OK;
}
#ifdef WIL_ENABLE_EXCEPTIONS
/** Looks up the computer name and fails if it is not found. */
template<typename string_type = wil::unique_cotaskmem_string, size_t initialBufferLength = MAX_COMPUTERNAME_LENGTH + 1>
string_type GetComputerNameW()
{
string_type result;
THROW_IF_FAILED((wiltmp::GetComputerNameW<string_type, initialBufferLength>(result)));
return result;
}
/** Looks up the computer name and returns null if it is not found. */
template<typename string_type = wil::unique_cotaskmem_string, size_t initialBufferLength = MAX_COMPUTERNAME_LENGTH + 1>
string_type TryGetComputerNameW()
{
string_type result;
THROW_IF_FAILED((wiltmp::TryGetComputerNameW<string_type, initialBufferLength>(result)));
return result;
}
#endif
/** Looks up a registry value from 'key' and fails if it is not found. */
template<typename string_type, size_t initialBufferLength = 256>
inline HRESULT RegQueryValueExW(HKEY key, PCWSTR valueName, string_type& result) WI_NOEXCEPT
{
return wil::AdaptFixedSizeToAllocatedResult<string_type, initialBufferLength>(result,
[&](_Out_writes_(valueLength) PWSTR value, size_t valueLength, _Out_ size_t* valueLengthNeededWithNul) -> HRESULT {
auto length = gsl::narrow<DWORD>(valueLength * sizeof(wchar_t));
const auto status = ::RegQueryValueExW(key, valueName, 0, nullptr, reinterpret_cast<BYTE*>(value), &length);
// 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.
*valueLengthNeededWithNul = (length + sizeof(wchar_t) - 1) / sizeof(wchar_t);
return status == ERROR_MORE_DATA ? S_OK : HRESULT_FROM_WIN32(status);
});
}
/** Looks up a registry value from 'key' and returns null if it is not found. */
template<typename string_type, size_t initialBufferLength = 256>
HRESULT TryRegQueryValueExW(HKEY key, PCWSTR valueName, string_type& result) WI_NOEXCEPT
{
const auto hr = wiltmp::TryRegQueryValueExW<string_type, initialBufferLength>(key, valueName, result);
RETURN_HR_IF(hr, FAILED(hr) && (hr != HRESULT_FROM_WIN32(ERROR_ENVVAR_NOT_FOUND)));
return S_OK;
}
#ifdef WIL_ENABLE_EXCEPTIONS
/** Looks up a registry value from 'key' and fails if it is not found. */
template<typename string_type = wil::unique_cotaskmem_string, size_t initialBufferLength = 256>
string_type RegQueryValueExW(HKEY key, PCWSTR valueName)
{
string_type result;
THROW_IF_FAILED((wiltmp::RegQueryValueExW<string_type, initialBufferLength>(key, valueName, result)));
return result;
}
/** Looks up a registry value from 'key' and returns null if it is not found. */
template<typename string_type = wil::unique_cotaskmem_string, size_t initialBufferLength = 256>
string_type TryRegQueryValueExW(HKEY key, PCWSTR valueName)
{
string_type result;
THROW_IF_FAILED((wiltmp::TryRegQueryValueExW<string_type, initialBufferLength>(key, valueName, result)));
return result;
}
#endif
//! A strongly typed version of the Win32 API GetShortPathNameW.
//! Return a path in an allocated buffer for handling long paths.
template<typename string_type, size_t stackBufferLength = 256>
HRESULT GetShortPathNameW(PCWSTR file, string_type& path)
{
const auto hr = wil::AdaptFixedSizeToAllocatedResult<string_type, stackBufferLength>(path,
[&](_Out_writes_(valueLength) PWSTR value, size_t valueLength, _Out_ size_t* valueLengthNeededWithNull) -> HRESULT {
// Note that GetShortPathNameW() is not limited to MAX_PATH
// but it does take a fixed size buffer.
*valueLengthNeededWithNull = ::GetShortPathNameW(file, value, static_cast<DWORD>(valueLength));
RETURN_LAST_ERROR_IF(*valueLengthNeededWithNull == 0);
WI_ASSERT((*value != L'\0') == (*valueLengthNeededWithNull < valueLength));
if (*valueLengthNeededWithNull < valueLength)
{
(*valueLengthNeededWithNull)++; // it fit, account for the null
}
return S_OK;
});
return hr;
}
#ifdef WIL_ENABLE_EXCEPTIONS
//! A strongly typed version of the Win32 API GetShortPathNameW.
//! Return a path in an allocated buffer for handling long paths.
template<typename string_type = wil::unique_cotaskmem_string, size_t stackBufferLength = 256>
string_type GetShortPathNameW(PCWSTR file)
{
string_type result;
THROW_IF_FAILED((GetShortPathNameW<string_type, stackBufferLength>(file, result)));
return result;
}
#endif
};
};
class env : public std::map<std::wstring, std::wstring, til::details::wstring_case_insensitive_compare>
{
private:
#ifdef UNIT_TESTING
friend class EnvTests;
#endif
// these wstring_views better be null terminated.
void get(std::wstring variable)
{
if (auto value = wil::TryGetEnvironmentVariableW(variable.c_str()))
{
save_to_map(variable, value.get());
}
}
void get_computer_name()
{
if (auto value = til::details::wiltmp::TryGetComputerNameW())
{
save_to_map(std::wstring{ til::details::vars::computer_name }, value.get());
}
}
void get_user_name_and_domain()
try
{
auto token = wil::open_current_access_token();
auto user = wil::get_token_information<TOKEN_USER>(token.get());
DWORD accountNameSize = 0, userDomainSize = 0;
SID_NAME_USE sidNameUse;
SetLastError(ERROR_SUCCESS);
if (LookupAccountSidW(nullptr, user.get()->User.Sid, nullptr, &accountNameSize, nullptr, &userDomainSize, &sidNameUse) || GetLastError() == ERROR_INSUFFICIENT_BUFFER)
{
std::wstring accountName, userDomain;
accountName.resize(accountNameSize);
userDomain.resize(userDomainSize);
SetLastError(ERROR_SUCCESS);
if (LookupAccountSidW(nullptr, user.get()->User.Sid, accountName.data(), &accountNameSize, userDomain.data(), &userDomainSize, &sidNameUse))
{
save_to_map(std::wstring{ til::details::vars::user_name }, accountName);
save_to_map(std::wstring{ til::details::vars::user_domain }, userDomain);
}
}
}
CATCH_LOG()
void get_program_files()
{
wil::unique_hkey key;
if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, til::details::vars::reg::program_files_root.data(), 0, KEY_READ, &key) == ERROR_SUCCESS)
{
for (auto& [keyName, varName] : til::details::vars::program_files_map)
{
auto value = til::details::wiltmp::RegQueryValueExW<std::wstring, 256>(key.get(), keyName.c_str());
set_user_environment_var(std::wstring{ varName }, value);
}
}
}
void get_vars_from_registry(HKEY rootKey, std::wstring_view subkey)
{
wil::unique_hkey key;
if (RegOpenKeyExW(rootKey, subkey.data(), 0, KEY_READ, &key) == ERROR_SUCCESS)
{
DWORD maxValueNameSize = 0, maxValueDataSize = 0;
if (RegQueryInfoKeyW(key.get(), nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, &maxValueNameSize, &maxValueDataSize, nullptr, nullptr) == ERROR_SUCCESS)
{
maxValueNameSize++; // the query does not include the terminating null, but needs that space to enum
std::wstring valueName;
std::basic_string<BYTE> valueData;
valueName.resize(maxValueNameSize);
valueData.resize(maxValueDataSize);
for (DWORD pass = 0; pass < 2; ++pass)
{
DWORD valueNameSize = maxValueNameSize;
DWORD valueDataSize = maxValueDataSize;
DWORD index = 0;
DWORD type = 0;
while (true)
{
DWORD result = RegEnumValueW(key.get(), index, valueName.data(), &valueNameSize, nullptr, &type, valueData.data(), &valueDataSize);
if (result != ERROR_SUCCESS)
{
break;
}
valueName.resize(valueNameSize);
valueData.resize(valueDataSize);
if (valueNameSize)
{
std::wstring data;
if (pass == 0 && (type == REG_SZ) && valueDataSize >= sizeof(wchar_t))
{
data = {
reinterpret_cast<wchar_t*>(valueData.data()), valueData.size() / sizeof(wchar_t)
};
}
else if (pass == 1 && (type == REG_EXPAND_SZ) && valueDataSize >= sizeof(wchar_t))
{
data = {
reinterpret_cast<wchar_t*>(valueData.data()), valueData.size() / sizeof(wchar_t)
};
data = expand_environment_strings(data.data());
}
// Because Registry data may or may not be null terminated... check if we've managed
// to store an extra null in the wstring by telling it to create itself from pointer and size.
// If we did, pull it off.
if (!data.empty())
{
if (data.back() == L'\0')
{
data = data.substr(0, data.size() - 1);
}
}
if (!data.empty())
{
if (is_path_var(valueName))
{
concat_var(valueName, std::wstring{ data });
}
else
{
set_user_environment_var(valueName, std::wstring{ data });
}
}
}
valueName.resize(maxValueNameSize);
valueData.resize(maxValueDataSize);
valueNameSize = maxValueNameSize;
valueDataSize = maxValueDataSize;
index++;
}
index = 0;
}
}
}
}
std::wstring expand_environment_strings(std::wstring input)
{
// TODO: this should be replacing from ourselves, not from the OS
return wil::ExpandEnvironmentStringsW<std::wstring, 256>(input.data());
}
void set_user_environment_var(std::wstring var, std::wstring value)
{
value = expand_environment_strings(value);
value = check_for_temp(value);
save_to_map(var, value);
}
void concat_var(std::wstring var, std::wstring value)
{
// I wanted contains() but this isn't C++20... yet.
if (find(var) != end())
{
std::wstring existing = at(var);
// If it doesn't already trail with a ;, add one.
// Otherwise, just take advantage of the one it has.
if (existing.back() != L';')
{
existing.append(L";");
}
existing.append(value);
save_to_map(var, existing);
}
else
{
save_to_map(var, value);
}
}
void save_to_map(std::wstring var, std::wstring value)
{
if (!var.empty() && !value.empty())
{
insert_or_assign(var, value);
}
}
static constexpr std::wstring_view temp{ L"temp" };
static constexpr std::wstring_view tmp{ L"tmp" };
std::wstring check_for_temp(std::wstring_view input)
{
if (!_wcsicmp(input.data(), temp.data()) ||
!_wcsicmp(input.data(), tmp.data()))
{
return til::details::wiltmp::GetShortPathNameW<std::wstring, 256>(input.data());
}
else
{
return std::wstring{ input };
}
}
static constexpr std::wstring_view path{ L"Path" };
static constexpr std::wstring_view libpath{ L"LibPath" };
static constexpr std::wstring_view os2libpath{ L"Os2LibPath" };
bool is_path_var(std::wstring_view input)
{
return !_wcsicmp(input.data(), path.data()) || !_wcsicmp(input.data(), libpath.data()) || !_wcsicmp(input.data(), os2libpath.data());
}
void parse(wchar_t* block)
{
for (wchar_t const* lastCh{ block }; *lastCh != '\0'; ++lastCh)
{
// Copy current entry into temporary map.
const size_t cchEntry{ ::wcslen(lastCh) };
const std::wstring_view entry{ lastCh, cchEntry };
// Every entry is of the form "name=value\0".
const auto pos = entry.find_first_of(L"=", 0, 1);
THROW_HR_IF(E_UNEXPECTED, pos == std::wstring::npos);
std::wstring name{ entry.substr(0, pos) }; // portion before '='
std::wstring value{ entry.substr(pos + 1) }; // portion after '='
// Don't replace entries that already exist.
try_emplace(std::move(name), std::move(value));
lastCh += cchEntry;
}
}
public:
env()
{
}
env(wchar_t* block)
{
parse(block);
}
void regenerate()
{
// Generally replicates the behavior of shell32!RegenerateUserEnvironment
get(std::wstring{ til::details::vars::system_root });
get(std::wstring{ til::details::vars::system_drive });
get(std::wstring{ til::details::vars::all_users_profile });
get(std::wstring{ til::details::vars::public_var });
get(std::wstring{ til::details::vars::program_data });
get_computer_name();
get_user_name_and_domain();
get(std::wstring{ til::details::vars::user_dns_domain });
get(std::wstring{ til::details::vars::home_drive });
get(std::wstring{ til::details::vars::home_share });
get(std::wstring{ til::details::vars::home_path });
get(std::wstring{ til::details::vars::user_profile });
get(std::wstring{ til::details::vars::app_data });
get(std::wstring{ til::details::vars::local_app_data });
get_program_files();
get_vars_from_registry(HKEY_LOCAL_MACHINE, til::details::vars::reg::system_env_var_root);
// not processing autoexec.bat
get_vars_from_registry(HKEY_CURRENT_USER, til::details::vars::reg::user_env_var_root);
get_vars_from_registry(HKEY_CURRENT_USER, til::details::vars::reg::user_volatile_env_var_root);
get_vars_from_registry(HKEY_CURRENT_USER, fmt::format(til::details::vars::reg::user_volatile_session_env_var_root_pattern, NtCurrentTeb()->ProcessEnvironmentBlock->SessionId));
}
std::wstring to_string()
{
std::wstring result;
for (const auto& [name, value] : *this)
{
result += name;
result += L"=";
result += value;
result.append(L"\0", 1); // Override string's natural propensity to stop at \0
}
result.append(L"\0", 1);
return result;
}
// TODO: should we be a bunch of goofs and make a "watcher" here that sets up its own
// quiet little HWND and message pump watching for the WM_SETTINGCHANGE?
};
};

View File

@@ -0,0 +1,66 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "til/env.h"
using namespace WEX::Common;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
BOOL APIENTRY RegenerateUserEnvironment(void** pNewEnv, BOOL bSetCurrentEnv);
class EnvTests
{
TEST_CLASS(EnvTests);
TEST_METHOD(Construct)
{
til::env environment;
}
TEST_METHOD(Generate)
{
// Be sneaky and pull the method that @eryksun told us about to see if we've effectively recreated it.
wil::unique_hmodule shell32{ LoadLibraryW(L"shell32.dll") };
auto func = GetProcAddressByFunctionDeclaration(shell32.get(), RegenerateUserEnvironment);
// Use a WIL pointer to free it on scope exit.
wil::unique_environstrings_ptr newEnv;
VERIFY_WIN32_BOOL_SUCCEEDED(func((void**)&newEnv, FALSE));
// Parse the string into our environment table.
til::env expected{ newEnv.get() };
// Set up an empty table and tell it to generate the environment with a similar algorithm.
til::env actual;
actual.regenerate();
VERIFY_ARE_EQUAL(expected.size(), actual.size());
for (const auto& [expectedKey, expectedValue] : expected)
{
VERIFY_IS_TRUE(actual.find(expectedKey) != actual.end());
VERIFY_ARE_EQUAL(expectedValue, actual[expectedKey]);
}
}
TEST_METHOD(ToString)
{
til::env environment;
environment.insert_or_assign(L"A", L"Apple");
environment.insert_or_assign(L"B", L"Banana");
environment.insert_or_assign(L"C", L"Cassowary");
wchar_t expectedArray[] = L"A=Apple\0B=Banana\0C=Cassowary\0";
const std::wstring expected{ expectedArray, ARRAYSIZE(expectedArray) };
const auto& actual = environment.to_string();
VERIFY_ARE_EQUAL(expected.size(), actual.size());
for (size_t i = 0; i < expected.size(); ++i)
{
VERIFY_ARE_EQUAL(expected[i], actual[i]);
}
}
};

View File

@@ -19,6 +19,7 @@
<ClCompile Include="CoalesceTests.cpp" />
<ClCompile Include="ColorTests.cpp" />
<ClCompile Include="EnumSetTests.cpp" />
<ClCompile Include="EnvTests.cpp" />
<ClCompile Include="HashTests.cpp" />
<ClCompile Include="MathTests.cpp" />
<ClCompile Include="mutex.cpp" />
@@ -73,8 +74,14 @@
<AdditionalIncludeDirectories>..;$(SolutionDir)src\inc;$(SolutionDir)src\inc\test;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
</ItemDefinitionGroup>
<ItemGroup>
<!-- subsume fmt, one of our dependencies, into contypes. -->
<ProjectReference Include="..\..\dep\fmt\fmt.vcxproj">
<Project>{6bae5851-50d5-4934-8d5e-30361a8a40f3}</Project>
</ProjectReference>
</ItemGroup>
<!-- Careful reordering these. Some default props (contained in these files) are order sensitive. -->
<Import Project="$(SolutionDir)src\common.build.post.props" />
<Import Project="$(SolutionDir)src\common.build.tests.props" />
<Import Project="$(SolutionDir)src\common.nugetversions.targets" />
</Project>
</Project>

View File

@@ -26,6 +26,7 @@
<ClCompile Include="string.cpp" />
<ClCompile Include="throttled_func.cpp" />
<ClCompile Include="u8u16convertTests.cpp" />
<ClCompile Include="EnvTests.cpp" />
<ClCompile Include="UnicodeTests.cpp" />
</ItemGroup>
<ItemGroup>
@@ -120,4 +121,4 @@
<UniqueIdentifier>{7cf29ba4-d33d-4c3b-82e3-ab73e5a79685}</UniqueIdentifier>
</Filter>
</ItemGroup>
</Project>
</Project>

View File

@@ -34,12 +34,12 @@ Function Test-MicrosoftPerson($email) {
Function Generate-Thanks($Entry) {
# We don't need to thank ourselves for doing our jobs
If ($Entry.Microsoft) {
If ($_.Microsoft) {
""
} ElseIf (-Not [string]::IsNullOrEmpty($Entry.PossibleUsername)) {
" (thanks @{0}!)" -f $Entry.PossibleUsername
} ElseIf (-Not [string]::IsNullOrEmpty($_.PossibleUsername)) {
" (thanks @{0}!)" -f $_.PossibleUsername
} Else {
" (thanks @<{0}>!)" -f $Entry.Email
" (thanks @<{0}>!)" -f $_.Email
}
}
@@ -54,7 +54,6 @@ Function Get-PossibleUserName($email) {
$Entries = @()
$i = 0
ForEach ($RevisionRange in $RevisionRanges) {
# --pretty=format notes:
# - %an: author name
@@ -71,23 +70,15 @@ ForEach ($RevisionRange in $RevisionRanges) {
Subject = $_.Subject;
Microsoft = (Test-MicrosoftPerson $_.Email);
PossibleUsername = (Get-PossibleUserName $_.Email);
RevRangeID = $i;
} }
$i++
}
For($c = 0; $c -Lt $i; $c++) {
" " + ("|" * $c) + "/ " + $RevisionRanges[$c]
}
$Unique = $Entries | Group-Object Subject
$Unique = $Entries | Group-Object Subject | %{ $_.Group[0] | Add-Member Count $_.Count -Force -PassThru }
$Unique | % {
$en = $_.Group[0]
$revSpec = (" " * $i)
$_.Group | % {
$revSpec = $revSpec.remove($_.RevRangeID, 1).insert($_.RevRangeID, "X")
$c = ""
If ($_.Count -Gt 1) {
$c = "[{0}] " -f $_.Count
}
$c = "[{0}] " -f $revSpec
"* {0}{1}{2}" -f ($c, $en.Subject, (Generate-Thanks $en))
"* {0}{1}{2}" -f ($c, $_.Subject, (Generate-Thanks $_))
}