mirror of
https://github.com/microsoft/terminal.git
synced 2026-04-10 08:11:06 +00:00
Compare commits
5 Commits
dev/duhowe
...
dev/miniks
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5eb08ff804 | ||
|
|
65ada350ce | ||
|
|
3fb8a8be4b | ||
|
|
cd1c4b6b10 | ||
|
|
6cac0969f3 |
@@ -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!
|
||||
|
||||
@@ -60,4 +60,4 @@
|
||||
<AdditionalDependencies>Uiautomationcore.lib;onecoreuap.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -68,7 +68,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
|
||||
void WindowManager::SignalClose()
|
||||
{
|
||||
if (_monarch && _peasant)
|
||||
if (_monarch)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
class AppHost
|
||||
{
|
||||
public:
|
||||
AppHost();
|
||||
AppHost() noexcept;
|
||||
virtual ~AppHost();
|
||||
|
||||
void AppTitleChanged(const winrt::Windows::Foundation::IInspectable& sender, winrt::hstring newTitle);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
void DisplayErrorDialogBlockingAndReport(const HRESULT hr, const std::wstring_view message);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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.)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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() &&
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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
510
src/inc/til/env.h
Normal 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?
|
||||
};
|
||||
};
|
||||
66
src/til/ut_til/EnvTests.cpp
Normal file
66
src/til/ut_til/EnvTests.cpp
Normal 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]);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 $_))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user