Merge remote-tracking branch 'origin/main' into dev/migrie/oop/ragnarok
This commit is contained in:
commit
b421ee6ca0
12
.github/actions/spelling/allow/allow.txt
vendored
12
.github/actions/spelling/allow/allow.txt
vendored
|
@ -1,12 +1,14 @@
|
|||
admins
|
||||
apc
|
||||
Apc
|
||||
bsd
|
||||
calt
|
||||
ccmp
|
||||
changelog
|
||||
cybersecurity
|
||||
Apc
|
||||
clickable
|
||||
clig
|
||||
copyable
|
||||
cybersecurity
|
||||
dalet
|
||||
dcs
|
||||
Dcs
|
||||
|
@ -34,17 +36,17 @@ It'd
|
|||
kje
|
||||
liga
|
||||
lje
|
||||
locl
|
||||
lorem
|
||||
Llast
|
||||
Lmid
|
||||
locl
|
||||
lorem
|
||||
Lorigin
|
||||
maxed
|
||||
mkmk
|
||||
mnt
|
||||
mru
|
||||
noreply
|
||||
nje
|
||||
noreply
|
||||
ogonek
|
||||
ok'd
|
||||
overlined
|
||||
|
|
7
.github/actions/spelling/allow/apis.txt
vendored
7
.github/actions/spelling/allow/apis.txt
vendored
|
@ -1,5 +1,7 @@
|
|||
ACCEPTFILES
|
||||
ACCESSDENIED
|
||||
acl
|
||||
aclapi
|
||||
alignas
|
||||
alignof
|
||||
APPLYTOSUBMENUS
|
||||
|
@ -21,6 +23,7 @@ commandlinetoargv
|
|||
cstdint
|
||||
CXICON
|
||||
CYICON
|
||||
Dacl
|
||||
dataobject
|
||||
dcomp
|
||||
DERR
|
||||
|
@ -117,15 +120,19 @@ OSVERSIONINFOEXW
|
|||
otms
|
||||
OUTLINETEXTMETRICW
|
||||
overridable
|
||||
PACL
|
||||
PAGESCROLL
|
||||
PEXPLICIT
|
||||
PICKFOLDERS
|
||||
pmr
|
||||
ptstr
|
||||
rcx
|
||||
REGCLS
|
||||
RETURNCMD
|
||||
rfind
|
||||
roundf
|
||||
RSHIFT
|
||||
SACL
|
||||
schandle
|
||||
semver
|
||||
serializer
|
||||
|
|
1
.github/actions/spelling/allow/names.txt
vendored
1
.github/actions/spelling/allow/names.txt
vendored
|
@ -9,6 +9,7 @@ Diviness
|
|||
dsafa
|
||||
duhowett
|
||||
ekg
|
||||
eryksun
|
||||
ethanschoonover
|
||||
Firefox
|
||||
Gatta
|
||||
|
|
1
.github/actions/spelling/excludes.txt
vendored
1
.github/actions/spelling/excludes.txt
vendored
|
@ -61,6 +61,7 @@ SUMS$
|
|||
^src/host/runft\.bat$
|
||||
^src/host/runut\.bat$
|
||||
^src/interactivity/onecore/BgfxEngine\.
|
||||
^src/renderer/atlas/
|
||||
^src/renderer/wddmcon/WddmConRenderer\.
|
||||
^src/terminal/adapter/ut_adapter/run\.bat$
|
||||
^src/terminal/parser/delfuzzpayload\.bat$
|
||||
|
|
|
@ -400,6 +400,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WindowsTerminal.UIA.Tests",
|
|||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "api-ms-win-core-synch-l1-2-0", "src\api-ms-win-core-synch-l1-2-0\api-ms-win-core-synch-l1-2-0.vcxproj", "{9CF74355-F018-4C19-81AD-9DC6B7F2C6F5}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RendererAtlas", "src\renderer\atlas\atlas.vcxproj", "{8222900C-8B6C-452A-91AC-BE95DB04B95F}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
AuditMode|Any CPU = AuditMode|Any CPU
|
||||
|
@ -3339,6 +3341,46 @@ Global
|
|||
{9CF74355-F018-4C19-81AD-9DC6B7F2C6F5}.Release|x64.Build.0 = Release|x64
|
||||
{9CF74355-F018-4C19-81AD-9DC6B7F2C6F5}.Release|x86.ActiveCfg = Release|Win32
|
||||
{9CF74355-F018-4C19-81AD-9DC6B7F2C6F5}.Release|x86.Build.0 = Release|Win32
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.AuditMode|ARM.ActiveCfg = AuditMode|Win32
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.AuditMode|ARM64.Build.0 = AuditMode|ARM64
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.AuditMode|DotNet_x64Test.ActiveCfg = AuditMode|Win32
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.AuditMode|DotNet_x86Test.ActiveCfg = AuditMode|Win32
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.AuditMode|x64.ActiveCfg = AuditMode|x64
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.AuditMode|x64.Build.0 = AuditMode|x64
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.AuditMode|x86.ActiveCfg = AuditMode|Win32
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.AuditMode|x86.Build.0 = AuditMode|Win32
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Debug|Any CPU.ActiveCfg = Debug|Win32
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Debug|ARM.ActiveCfg = Debug|Win32
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Debug|DotNet_x64Test.ActiveCfg = Debug|Win32
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Debug|DotNet_x86Test.ActiveCfg = Debug|Win32
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Debug|x64.Build.0 = Debug|x64
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Debug|x86.Build.0 = Debug|Win32
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Fuzzing|ARM64.Build.0 = Fuzzing|ARM64
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Fuzzing|DotNet_x64Test.ActiveCfg = Fuzzing|Win32
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Fuzzing|DotNet_x86Test.ActiveCfg = Fuzzing|Win32
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Fuzzing|x64.ActiveCfg = Fuzzing|x64
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Fuzzing|x64.Build.0 = Fuzzing|x64
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Fuzzing|x86.Build.0 = Fuzzing|Win32
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Release|Any CPU.ActiveCfg = Release|Win32
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Release|ARM.ActiveCfg = Release|Win32
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Release|DotNet_x64Test.ActiveCfg = Release|Win32
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Release|DotNet_x86Test.ActiveCfg = Release|Win32
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Release|x64.ActiveCfg = Release|x64
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Release|x64.Build.0 = Release|x64
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Release|x86.ActiveCfg = Release|Win32
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F}.Release|x86.Build.0 = Release|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@ -3438,6 +3480,7 @@ Global
|
|||
{C323DAEE-B307-4C7B-ACE5-7293CBEFCB5B} = {BDB237B6-1D1D-400F-84CC-40A58FA59C8E}
|
||||
{F19DACD5-0C6E-40DC-B6E4-767A3200542C} = {BDB237B6-1D1D-400F-84CC-40A58FA59C8E}
|
||||
{9CF74355-F018-4C19-81AD-9DC6B7F2C6F5} = {89CDCC5C-9F53-4054-97A4-639D99F169CD}
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F} = {05500DEF-2294-41E3-AF9A-24E580B82836}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {3140B1B7-C8EE-43D1-A772-D82A7061A271}
|
||||
|
|
|
@ -29,6 +29,9 @@
|
|||
<ProjectReference Include="$(SolutionDir)src\types\lib\types.vcxproj">
|
||||
<Project>{18D09A24-8240-42D6-8CB6-236EEE820263}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="$(SolutionDir)src\renderer\atlas\atlas.vcxproj">
|
||||
<Project>{8222900C-8B6C-452A-91AC-BE95DB04B95F}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="$(SolutionDir)src\renderer\base\lib\base.vcxproj">
|
||||
<Project>{af0a096a-8b3a-4949-81ef-7df8f0fee91f}</Project>
|
||||
</ProjectReference>
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
#include <WtExeUtils.h>
|
||||
#include <wil/token_helpers.h >
|
||||
|
||||
#include "../../types/inc/utils.hpp"
|
||||
|
||||
using namespace winrt::Windows::ApplicationModel;
|
||||
using namespace winrt::Windows::ApplicationModel::DataTransfer;
|
||||
using namespace winrt::Windows::UI::Xaml;
|
||||
|
@ -131,38 +133,6 @@ static Documents::Run _BuildErrorRun(const winrt::hstring& text, const ResourceD
|
|||
return textRun;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Returns whether the user is either a member of the Administrators group or
|
||||
// is currently elevated.
|
||||
// - This will return **FALSE** if the user has UAC disabled entirely, because
|
||||
// there's no separation of power between the user and an admin in that case.
|
||||
// Return Value:
|
||||
// - true if the user is an administrator
|
||||
static bool _isUserAdmin() noexcept
|
||||
try
|
||||
{
|
||||
wil::unique_handle processToken{ GetCurrentProcessToken() };
|
||||
const auto elevationType = wil::get_token_information<TOKEN_ELEVATION_TYPE>(processToken.get());
|
||||
const auto elevationState = wil::get_token_information<TOKEN_ELEVATION>(processToken.get());
|
||||
if (elevationType == TokenElevationTypeDefault && elevationState.TokenIsElevated)
|
||||
{
|
||||
// In this case, the user has UAC entirely disabled. This is sort of
|
||||
// weird, we treat this like the user isn't an admin at all. There's no
|
||||
// separation of powers, so the things we normally want to gate on
|
||||
// "having special powers" doesn't apply.
|
||||
//
|
||||
// See GH#7754, GH#11096
|
||||
return false;
|
||||
}
|
||||
|
||||
return wil::test_token_membership(nullptr, SECURITY_NT_AUTHORITY, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_CAUGHT_EXCEPTION();
|
||||
return false;
|
||||
}
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
// Function Description:
|
||||
|
@ -214,7 +184,7 @@ namespace winrt::TerminalApp::implementation
|
|||
// The TerminalPage has to be constructed during our construction, to
|
||||
// make sure that there's a terminal page for callers of
|
||||
// SetTitleBarContent
|
||||
_isElevated = _isUserAdmin();
|
||||
_isElevated = ::Microsoft::Console::Utils::IsElevated();
|
||||
_root = winrt::make_self<TerminalPage>();
|
||||
|
||||
_reloadSettings = std::make_shared<ThrottledFuncTrailing<>>(winrt::Windows::System::DispatcherQueue::GetForCurrentThread(), std::chrono::milliseconds(100), [weakSelf = get_weak()]() {
|
||||
|
@ -910,8 +880,6 @@ namespace winrt::TerminalApp::implementation
|
|||
void AppLogic::_RegisterSettingsChange()
|
||||
{
|
||||
const std::filesystem::path settingsPath{ std::wstring_view{ CascadiaSettings::SettingsPath() } };
|
||||
const std::filesystem::path statePath{ std::wstring_view{ ApplicationState::SharedInstance().FilePath() } };
|
||||
|
||||
_reader.create(
|
||||
settingsPath.parent_path().c_str(),
|
||||
false,
|
||||
|
@ -920,14 +888,29 @@ namespace winrt::TerminalApp::implementation
|
|||
// editors, who will write a temp file, then rename it to be the
|
||||
// actual file you wrote. So listen for that too.
|
||||
wil::FolderChangeEvents::FileName | wil::FolderChangeEvents::LastWriteTime,
|
||||
[this, settingsBasename = settingsPath.filename(), stateBasename = statePath.filename()](wil::FolderChangeEvent, PCWSTR fileModified) {
|
||||
const auto modifiedBasename = std::filesystem::path{ fileModified }.filename();
|
||||
[this, settingsBasename = settingsPath.filename()](wil::FolderChangeEvent, PCWSTR fileModified) {
|
||||
// DO NOT create a static reference to ApplicationState::SharedInstance here.
|
||||
//
|
||||
// ApplicationState::SharedInstance already caches its own
|
||||
// static ref. If _we_ keep a static ref to the member in
|
||||
// AppState, then our reference will keep ApplicationState alive
|
||||
// after the `ActionToStringMap` gets cleaned up. Then, when we
|
||||
// try to persist the actions in the window state, we won't be
|
||||
// able to. We'll try to look up the action and the map just
|
||||
// won't exist. We'll explode, even though the Terminal is
|
||||
// tearing down anyways. So we'll just die, but still invoke
|
||||
// WinDBG's post-mortem debugger, who won't be able to attach to
|
||||
// the process that's already exiting.
|
||||
//
|
||||
// So DON'T ~give a mouse a cookie~ take a static ref here.
|
||||
|
||||
const winrt::hstring modifiedBasename{ std::filesystem::path{ fileModified }.filename().c_str() };
|
||||
|
||||
if (modifiedBasename == settingsBasename)
|
||||
{
|
||||
_reloadSettings->Run();
|
||||
}
|
||||
else if (modifiedBasename == stateBasename)
|
||||
else if (ApplicationState::SharedInstance().IsStatePath(modifiedBasename))
|
||||
{
|
||||
_reloadState();
|
||||
}
|
||||
|
|
|
@ -300,10 +300,7 @@ namespace winrt::TerminalApp::implementation
|
|||
// - true if the ApplicationState should be used.
|
||||
bool TerminalPage::ShouldUsePersistedLayout(CascadiaSettings& settings) const
|
||||
{
|
||||
// GH#5000 Until there is a separate state file for elevated sessions we should just not
|
||||
// save at all while in an elevated window.
|
||||
return Feature_PersistedWindowLayout::IsEnabled() &&
|
||||
!IsElevated() &&
|
||||
settings.GlobalSettings().FirstWindowPreference() == FirstWindowPreference::PersistedWindowLayout;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,16 +3,18 @@
|
|||
|
||||
#include "pch.h"
|
||||
#include "ControlCore.h"
|
||||
#include <argb.h>
|
||||
|
||||
#include <DefaultSettings.h>
|
||||
#include <unicode.hpp>
|
||||
#include <Utf16Parser.hpp>
|
||||
#include <Utils.h>
|
||||
#include <WinUser.h>
|
||||
#include <LibraryResources.h>
|
||||
|
||||
#include "EventArgs.h"
|
||||
#include "../../types/inc/GlyphWidth.hpp"
|
||||
#include "../../types/inc/Utils.hpp"
|
||||
#include "../../buffer/out/search.h"
|
||||
#include "../../renderer/atlas/AtlasEngine.h"
|
||||
#include "../../renderer/dx/DxRenderer.hpp"
|
||||
|
||||
#include "ControlCore.g.cpp"
|
||||
|
||||
|
@ -204,6 +206,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||
const double actualHeight,
|
||||
const double compositionScale)
|
||||
{
|
||||
assert(_settings);
|
||||
|
||||
_panelWidth = actualWidth;
|
||||
_panelHeight = actualHeight;
|
||||
_compositionScale = compositionScale;
|
||||
|
@ -224,10 +228,16 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||
return false;
|
||||
}
|
||||
|
||||
// Set up the DX Engine
|
||||
auto dxEngine = std::make_unique<::Microsoft::Console::Render::DxEngine>();
|
||||
_renderer->AddRenderEngine(dxEngine.get());
|
||||
_renderEngine = std::move(dxEngine);
|
||||
if (Feature_AtlasEngine::IsEnabled() && _settings->UseAtlasEngine())
|
||||
{
|
||||
_renderEngine = std::make_unique<::Microsoft::Console::Render::AtlasEngine>();
|
||||
}
|
||||
else
|
||||
{
|
||||
_renderEngine = std::make_unique<::Microsoft::Console::Render::DxEngine>();
|
||||
}
|
||||
|
||||
_renderer->AddRenderEngine(_renderEngine.get());
|
||||
|
||||
// Initialize our font with the renderer
|
||||
// We don't have to care about DPI. We'll get a change message immediately if it's not 96
|
||||
|
@ -273,7 +283,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||
_renderEngine->SetSoftwareRendering(_settings->SoftwareRendering());
|
||||
_renderEngine->SetIntenseIsBold(_settings->IntenseIsBold());
|
||||
|
||||
_updateAntiAliasingMode(_renderEngine.get());
|
||||
_updateAntiAliasingMode();
|
||||
|
||||
// GH#5098: Inform the engine of the opacity of the default text background.
|
||||
// GH#11315: Always do this, even if they don't have acrylic on.
|
||||
|
@ -629,7 +639,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||
// Inform the renderer of our opacity
|
||||
_renderEngine->EnableTransparentBackground(_isBackgroundTransparent());
|
||||
|
||||
_updateAntiAliasingMode(_renderEngine.get());
|
||||
_updateAntiAliasingMode();
|
||||
|
||||
// Refresh our font with the renderer
|
||||
const auto actualFontOldSize = _actualFont.GetSize();
|
||||
|
@ -663,22 +673,24 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||
}
|
||||
}
|
||||
|
||||
void ControlCore::_updateAntiAliasingMode(::Microsoft::Console::Render::DxEngine* const dxEngine)
|
||||
void ControlCore::_updateAntiAliasingMode()
|
||||
{
|
||||
D2D1_TEXT_ANTIALIAS_MODE mode;
|
||||
// Update DxEngine's AntialiasingMode
|
||||
switch (_settings->AntialiasingMode())
|
||||
{
|
||||
case TextAntialiasingMode::Cleartype:
|
||||
dxEngine->SetAntialiasingMode(D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE);
|
||||
mode = D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE;
|
||||
break;
|
||||
case TextAntialiasingMode::Aliased:
|
||||
dxEngine->SetAntialiasingMode(D2D1_TEXT_ANTIALIAS_MODE_ALIASED);
|
||||
mode = D2D1_TEXT_ANTIALIAS_MODE_ALIASED;
|
||||
break;
|
||||
case TextAntialiasingMode::Grayscale:
|
||||
default:
|
||||
dxEngine->SetAntialiasingMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE);
|
||||
mode = D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE;
|
||||
break;
|
||||
}
|
||||
|
||||
_renderEngine->SetAntialiasingMode(mode);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
@ -1703,6 +1715,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||
|
||||
bool ControlCore::_isBackgroundTransparent()
|
||||
{
|
||||
// TODO! I had:
|
||||
return Opacity() < 1.0f || UseAcrylic();
|
||||
|
||||
// But the Atlas Renderer PR changed this to:
|
||||
//
|
||||
// const auto backgroundIsOpaque = _settings.Opacity() == 1.0 && _settings.BackgroundImage().empty();\
|
||||
//
|
||||
// Only one of these is right, right?
|
||||
// The background image thing might be a
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,12 +15,9 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "EventArgs.h"
|
||||
#include "ControlCore.g.h"
|
||||
#include "ControlSettings.h"
|
||||
#include "../../renderer/base/Renderer.hpp"
|
||||
#include "../../renderer/dx/DxRenderer.hpp"
|
||||
#include "../../renderer/uia/UiaRenderer.hpp"
|
||||
#include "../../cascadia/TerminalCore/Terminal.hpp"
|
||||
#include "../buffer/out/search.h"
|
||||
#include "cppwinrt_utils.h"
|
||||
|
@ -212,7 +209,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||
// As _renderer has a dependency on _renderEngine (through a raw pointer)
|
||||
// we must ensure the _renderer is deallocated first.
|
||||
// (C++ class members are destroyed in reverse order.)
|
||||
std::unique_ptr<::Microsoft::Console::Render::DxEngine> _renderEngine{ nullptr };
|
||||
std::unique_ptr<::Microsoft::Console::Render::IRenderEngine> _renderEngine{ nullptr };
|
||||
std::unique_ptr<::Microsoft::Console::Render::Renderer> _renderer{ nullptr };
|
||||
|
||||
FontInfoDesired _desiredFont;
|
||||
|
@ -270,7 +267,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||
#pragma endregion
|
||||
|
||||
void _raiseReadOnlyWarning();
|
||||
void _updateAntiAliasingMode(::Microsoft::Console::Render::DxEngine* const dxEngine);
|
||||
void _updateAntiAliasingMode();
|
||||
void _connectionOutputHandler(const hstring& hstr);
|
||||
void _updateHoveredCell(const std::optional<til::point> terminalPosition);
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include "cppwinrt_utils.h"
|
||||
|
||||
#include "ControlCore.h"
|
||||
#include "../../renderer/uia/UiaRenderer.hpp"
|
||||
|
||||
namespace ControlUnitTests
|
||||
{
|
||||
|
|
|
@ -33,6 +33,8 @@ namespace Microsoft.Terminal.Control
|
|||
Boolean UseAcrylic { get; };
|
||||
ScrollbarState ScrollState { get; };
|
||||
|
||||
Boolean UseAtlasEngine { get; };
|
||||
|
||||
String FontFace { get; };
|
||||
Int32 FontSize { get; };
|
||||
Windows.UI.Text.FontWeight FontWeight { get; };
|
||||
|
|
|
@ -3,17 +3,16 @@
|
|||
|
||||
#include "pch.h"
|
||||
#include "TermControl.h"
|
||||
#include <argb.h>
|
||||
#include <DefaultSettings.h>
|
||||
|
||||
#include <unicode.hpp>
|
||||
#include <Utf16Parser.hpp>
|
||||
#include <Utils.h>
|
||||
#include <LibraryResources.h>
|
||||
|
||||
#include "TermControlAutomationPeer.h"
|
||||
#include "../../types/inc/GlyphWidth.hpp"
|
||||
#include "../../types/inc/Utils.hpp"
|
||||
#include "../../renderer/atlas/AtlasEngine.h"
|
||||
|
||||
#include "TermControl.g.cpp"
|
||||
#include "TermControlAutomationPeer.h"
|
||||
|
||||
using namespace ::Microsoft::Console::Types;
|
||||
using namespace ::Microsoft::Console::VirtualTerminal;
|
||||
|
@ -1853,13 +1852,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||
|
||||
const winrt::Windows::Foundation::Size initialSize{ cols, rows };
|
||||
|
||||
return GetProposedDimensions(initialSize,
|
||||
settings.FontSize(),
|
||||
settings.FontWeight(),
|
||||
settings.FontFace(),
|
||||
settings.ScrollState(),
|
||||
settings.Padding(),
|
||||
dpi);
|
||||
return GetProposedDimensions(settings, dpi, initialSize);
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
|
@ -1880,16 +1873,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||
// caller knows what monitor the control is about to appear on.
|
||||
// Return Value:
|
||||
// - a size containing the requested dimensions in pixels.
|
||||
winrt::Windows::Foundation::Size TermControl::GetProposedDimensions(const winrt::Windows::Foundation::Size& initialSizeInChars,
|
||||
const int32_t& fontHeight,
|
||||
const winrt::Windows::UI::Text::FontWeight& fontWeight,
|
||||
const winrt::hstring& fontFace,
|
||||
const ScrollbarState& scrollState,
|
||||
const winrt::hstring& padding,
|
||||
const uint32_t dpi)
|
||||
winrt::Windows::Foundation::Size TermControl::GetProposedDimensions(IControlSettings const& settings, const uint32_t dpi, const winrt::Windows::Foundation::Size& initialSizeInChars)
|
||||
{
|
||||
const auto cols = ::base::saturated_cast<int>(initialSizeInChars.Width);
|
||||
const auto rows = ::base::saturated_cast<int>(initialSizeInChars.Height);
|
||||
const auto fontSize = settings.FontSize();
|
||||
const auto fontWeight = settings.FontWeight();
|
||||
const auto fontFace = settings.FontFace();
|
||||
const auto scrollState = settings.ScrollState();
|
||||
const auto padding = settings.Padding();
|
||||
|
||||
// Initialize our font information.
|
||||
// The font width doesn't terribly matter, we'll only be using the
|
||||
|
@ -1898,28 +1890,39 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||
// The family is only used to determine if the font is truetype or
|
||||
// not, but DX doesn't use that info at all.
|
||||
// The Codepage is additionally not actually used by the DX engine at all.
|
||||
FontInfo actualFont = { fontFace, 0, fontWeight.Weight, { 0, gsl::narrow_cast<short>(fontHeight) }, CP_UTF8, false };
|
||||
FontInfo actualFont = { fontFace, 0, fontWeight.Weight, { 0, gsl::narrow_cast<short>(fontSize) }, CP_UTF8, false };
|
||||
FontInfoDesired desiredFont = { actualFont };
|
||||
|
||||
// Create a DX engine and initialize it with our font and DPI. We'll
|
||||
// then use it to measure how much space the requested rows and columns
|
||||
// will take up.
|
||||
// TODO: MSFT:21254947 - use a static function to do this instead of
|
||||
// instantiating a DxEngine
|
||||
// instantiating a DxEngine/AtlasEngine.
|
||||
// GH#10211 - UNDER NO CIRCUMSTANCE should this fail. If it does, the
|
||||
// whole app will crash instantaneously on launch, which is no good.
|
||||
auto dxEngine = std::make_unique<::Microsoft::Console::Render::DxEngine>();
|
||||
LOG_IF_FAILED(dxEngine->UpdateDpi(dpi));
|
||||
LOG_IF_FAILED(dxEngine->UpdateFont(desiredFont, actualFont));
|
||||
double scale;
|
||||
if (Feature_AtlasEngine::IsEnabled() && settings.UseAtlasEngine())
|
||||
{
|
||||
auto engine = std::make_unique<::Microsoft::Console::Render::AtlasEngine>();
|
||||
LOG_IF_FAILED(engine->UpdateDpi(dpi));
|
||||
LOG_IF_FAILED(engine->UpdateFont(desiredFont, actualFont));
|
||||
scale = engine->GetScaling();
|
||||
}
|
||||
else
|
||||
{
|
||||
auto engine = std::make_unique<::Microsoft::Console::Render::DxEngine>();
|
||||
LOG_IF_FAILED(engine->UpdateDpi(dpi));
|
||||
LOG_IF_FAILED(engine->UpdateFont(desiredFont, actualFont));
|
||||
scale = engine->GetScaling();
|
||||
}
|
||||
|
||||
const auto scale = dxEngine->GetScaling();
|
||||
const auto fontSize = actualFont.GetSize();
|
||||
const auto actualFontSize = actualFont.GetSize();
|
||||
|
||||
// UWP XAML scrollbars aren't guaranteed to be the same size as the
|
||||
// ComCtl scrollbars, but it's certainly close enough.
|
||||
const auto scrollbarSize = GetSystemMetricsForDpi(SM_CXVSCROLL, dpi);
|
||||
|
||||
double width = cols * fontSize.X;
|
||||
double width = cols * actualFontSize.X;
|
||||
|
||||
// Reserve additional space if scrollbar is intended to be visible
|
||||
if (scrollState == ScrollbarState::Visible)
|
||||
|
@ -1927,7 +1930,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||
width += scrollbarSize;
|
||||
}
|
||||
|
||||
double height = rows * fontSize.Y;
|
||||
double height = rows * actualFontSize.Y;
|
||||
const auto thickness = ParseThicknessFromPadding(padding);
|
||||
// GH#2061 - make sure to account for the size the padding _will be_ scaled to
|
||||
width += scale * (thickness.Left + thickness.Right);
|
||||
|
@ -1987,14 +1990,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||
const winrt::Windows::Foundation::Size minSize{ 1, 1 };
|
||||
const double scaleFactor = DisplayInformation::GetForCurrentView().RawPixelsPerViewPixel();
|
||||
const auto dpi = ::base::saturated_cast<uint32_t>(USER_DEFAULT_SCREEN_DPI * scaleFactor);
|
||||
auto settings{ _core.Settings() };
|
||||
return GetProposedDimensions(minSize,
|
||||
settings.FontSize(),
|
||||
settings.FontWeight(),
|
||||
settings.FontFace(),
|
||||
settings.ScrollState(),
|
||||
settings.Padding(),
|
||||
dpi);
|
||||
|
||||
return GetProposedDimensions(_core.Settings(), dpi, minSize);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -92,13 +92,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||
const Windows::UI::Xaml::Thickness GetPadding();
|
||||
|
||||
static Windows::Foundation::Size GetProposedDimensions(IControlSettings const& settings, const uint32_t dpi);
|
||||
static Windows::Foundation::Size GetProposedDimensions(const winrt::Windows::Foundation::Size& initialSizeInChars,
|
||||
const int32_t& fontSize,
|
||||
const winrt::Windows::UI::Text::FontWeight& fontWeight,
|
||||
const winrt::hstring& fontFace,
|
||||
const ScrollbarState& scrollState,
|
||||
const winrt::hstring& padding,
|
||||
const uint32_t dpi);
|
||||
static Windows::Foundation::Size GetProposedDimensions(IControlSettings const& settings, const uint32_t dpi, const winrt::Windows::Foundation::Size& initialSizeInChars);
|
||||
|
||||
void BellLightOn();
|
||||
|
||||
|
|
|
@ -147,6 +147,7 @@
|
|||
<ProjectReference Include="..\..\types\lib\types.vcxproj" />
|
||||
<ProjectReference Include="..\..\buffer\out\lib\bufferout.vcxproj" />
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\renderer\base\lib\base.vcxproj" />
|
||||
<ProjectReference Include="..\..\renderer\atlas\atlas.vcxproj" />
|
||||
<ProjectReference Include="..\..\renderer\dx\lib\dx.vcxproj" />
|
||||
<ProjectReference Include="..\..\renderer\uia\lib\uia.vcxproj" />
|
||||
<ProjectReference Include="..\..\terminal\parser\lib\parser.vcxproj" />
|
||||
|
|
|
@ -37,6 +37,9 @@
|
|||
<ProjectReference Include="$(OpenConsoleDir)src\terminal\parser\lib\parser.vcxproj">
|
||||
<Project>{3ae13314-1939-4dfa-9c14-38ca0834050c}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\renderer\atlas\atlas.vcxproj">
|
||||
<Project>{8222900C-8B6C-452A-91AC-BE95DB04B95F}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\renderer\dx\lib\dx.vcxproj">
|
||||
<Project>{48d21369-3d7b-4431-9967-24e81292cf62}</Project>
|
||||
</ProjectReference>
|
||||
|
|
|
@ -246,13 +246,9 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
|||
return _profile.HasUnfocusedAppearance();
|
||||
}
|
||||
|
||||
bool ProfileViewModel::EditableUnfocusedAppearance()
|
||||
bool ProfileViewModel::EditableUnfocusedAppearance() const noexcept
|
||||
{
|
||||
if constexpr (Feature_EditableUnfocusedAppearance::IsEnabled())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return Feature_EditableUnfocusedAppearance::IsEnabled();
|
||||
}
|
||||
|
||||
bool ProfileViewModel::ShowUnfocusedAppearance()
|
||||
|
@ -286,6 +282,11 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
|||
return _unfocusedAppearanceViewModel;
|
||||
}
|
||||
|
||||
bool ProfileViewModel::AtlasEngineAvailable() const noexcept
|
||||
{
|
||||
return Feature_AtlasEngine::IsEnabled();
|
||||
}
|
||||
|
||||
bool ProfileViewModel::UseParentProcessDirectory()
|
||||
{
|
||||
return StartingDirectory().empty();
|
||||
|
|
|
@ -61,12 +61,12 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
|||
Editor::AppearanceViewModel DefaultAppearance();
|
||||
Editor::AppearanceViewModel UnfocusedAppearance();
|
||||
bool HasUnfocusedAppearance();
|
||||
bool EditableUnfocusedAppearance();
|
||||
bool EditableUnfocusedAppearance() const noexcept;
|
||||
bool ShowUnfocusedAppearance();
|
||||
|
||||
void CreateUnfocusedAppearance(const Windows::Foundation::Collections::IMapView<hstring, Model::ColorScheme>& schemes,
|
||||
const IHostedInWindow& windowRoot);
|
||||
void DeleteUnfocusedAppearance();
|
||||
bool AtlasEngineAvailable() const noexcept;
|
||||
|
||||
WINRT_PROPERTY(bool, IsBaseLayer, false);
|
||||
|
||||
|
@ -95,6 +95,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
|||
OBSERVABLE_PROJECTED_SETTING(_profile, SnapOnInput);
|
||||
OBSERVABLE_PROJECTED_SETTING(_profile, AltGrAliasing);
|
||||
OBSERVABLE_PROJECTED_SETTING(_profile, BellStyle);
|
||||
OBSERVABLE_PROJECTED_SETTING(_profile, UseAtlasEngine);
|
||||
|
||||
private:
|
||||
Model::Profile _profile;
|
||||
|
|
|
@ -32,6 +32,7 @@ namespace Microsoft.Terminal.Settings.Editor
|
|||
Boolean EditableUnfocusedAppearance { get; };
|
||||
Boolean ShowUnfocusedAppearance { get; };
|
||||
AppearanceViewModel UnfocusedAppearance { get; };
|
||||
Boolean AtlasEngineAvailable { get; };
|
||||
|
||||
void CreateUnfocusedAppearance(Windows.Foundation.Collections.IMapView<String, Microsoft.Terminal.Settings.Model.ColorScheme> Schemes, IHostedInWindow WindowRoot);
|
||||
void DeleteUnfocusedAppearance();
|
||||
|
@ -61,6 +62,7 @@ namespace Microsoft.Terminal.Settings.Editor
|
|||
OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, SnapOnInput);
|
||||
OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, AltGrAliasing);
|
||||
OBSERVABLE_PROJECTED_PROFILE_SETTING(Microsoft.Terminal.Settings.Model.BellStyle, BellStyle);
|
||||
OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, UseAtlasEngine);
|
||||
}
|
||||
|
||||
runtimeclass DeleteProfileEventArgs
|
||||
|
|
|
@ -319,7 +319,7 @@
|
|||
</StackPanel>
|
||||
<StackPanel>
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Visibility="{x:Bind State.Profile.EditableUnfocusedAppearance, Mode=OneWay}">
|
||||
Visibility="{x:Bind State.Profile.EditableUnfocusedAppearance}">
|
||||
<TextBlock x:Uid="Profile_UnfocusedAppearanceTextBlock"
|
||||
Style="{StaticResource TitleTextBlockStyle}" />
|
||||
<Button x:Uid="Profile_CreateUnfocusedAppearanceButton"
|
||||
|
@ -477,6 +477,15 @@
|
|||
IsChecked="{x:Bind IsBellStyleFlagSet(4), BindBack=SetBellStyleTaskbar, Mode=TwoWay}" />
|
||||
</StackPanel>
|
||||
</local:SettingContainer>
|
||||
|
||||
<!-- AtlasEngine -->
|
||||
<local:SettingContainer x:Uid="Profile_UseAtlasEngine"
|
||||
ClearSettingValue="{x:Bind State.Profile.ClearUseAtlasEngine}"
|
||||
HasSettingValue="{x:Bind State.Profile.HasUseAtlasEngine, Mode=OneWay}"
|
||||
SettingOverrideSource="{x:Bind State.Profile.UseAtlasEngineOverrideSource, Mode=OneWay}"
|
||||
Visibility="{x:Bind State.Profile.AtlasEngineAvailable}">
|
||||
<ToggleSwitch IsOn="{x:Bind State.Profile.UseAtlasEngine, Mode=TwoWay}" />
|
||||
</local:SettingContainer>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</PivotItem>
|
||||
|
|
|
@ -927,6 +927,10 @@
|
|||
<value>Controls what happens when the application emits a BEL character.</value>
|
||||
<comment>A description for what the "bell style" setting does. Presented near "Profile_BellStyle".{Locked="BEL"}</comment>
|
||||
</data>
|
||||
<data name="Profile_UseAtlasEngine.Header" xml:space="preserve">
|
||||
<value>Enable experimental text rendering engine</value>
|
||||
<comment>An option to enable an experimental text rendering engine</comment>
|
||||
</data>
|
||||
<data name="Profile_BellStyleAudible.Content" xml:space="preserve">
|
||||
<value>Audible</value>
|
||||
<comment>An option to choose from for the "bell style" setting. When selected, an audible cue is used to notify the user.</comment>
|
||||
|
|
|
@ -9,8 +9,11 @@
|
|||
#include "ActionAndArgs.h"
|
||||
#include "JsonUtils.h"
|
||||
#include "FileUtils.h"
|
||||
#include "../../types/inc/utils.hpp"
|
||||
|
||||
static constexpr std::wstring_view stateFileName{ L"state.json" };
|
||||
static constexpr std::wstring_view elevatedStateFileName{ L"elevated-state.json" };
|
||||
|
||||
static constexpr std::string_view TabLayoutKey{ "tabLayout" };
|
||||
static constexpr std::string_view InitialPositionKey{ "initialPosition" };
|
||||
static constexpr std::string_view InitialSizeKey{ "initialSize" };
|
||||
|
@ -85,15 +88,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
return trait.FromJson(root);
|
||||
}
|
||||
|
||||
// Returns the application-global ApplicationState object.
|
||||
Microsoft::Terminal::Settings::Model::ApplicationState ApplicationState::SharedInstance()
|
||||
{
|
||||
static auto state = winrt::make_self<ApplicationState>(GetBaseSettingsPath() / stateFileName);
|
||||
return *state;
|
||||
}
|
||||
|
||||
ApplicationState::ApplicationState(std::filesystem::path path) noexcept :
|
||||
_path{ std::move(path) },
|
||||
ApplicationState::ApplicationState(const std::filesystem::path& stateRoot) noexcept :
|
||||
_sharedPath{ stateRoot / stateFileName },
|
||||
_elevatedPath{ stateRoot / elevatedStateFileName },
|
||||
_throttler{ std::chrono::seconds(1), [this]() { _write(); } }
|
||||
{
|
||||
_read();
|
||||
|
@ -102,9 +99,21 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
// The destructor ensures that the last write is flushed to disk before returning.
|
||||
ApplicationState::~ApplicationState()
|
||||
{
|
||||
TraceLoggingWrite(g_hSettingsModelProvider,
|
||||
"ApplicationState_Dtor_Start",
|
||||
TraceLoggingDescription("Event at the start of the ApplicationState destructor"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
|
||||
// This will ensure that we not just cancel the last outstanding timer,
|
||||
// but instead force it to run as soon as possible and wait for it to complete.
|
||||
_throttler.flush();
|
||||
|
||||
TraceLoggingWrite(g_hSettingsModelProvider,
|
||||
"ApplicationState_Dtor_End",
|
||||
TraceLoggingDescription("Event at the end of the ApplicationState destructor"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
}
|
||||
|
||||
// Re-read the state.json from disk.
|
||||
|
@ -113,34 +122,13 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
_read();
|
||||
}
|
||||
|
||||
// Returns the state.json path on the disk.
|
||||
winrt::hstring ApplicationState::FilePath() const noexcept
|
||||
bool ApplicationState::IsStatePath(const winrt::hstring& filename)
|
||||
{
|
||||
return winrt::hstring{ _path.wstring() };
|
||||
static const auto sharedPath{ _sharedPath.filename() };
|
||||
static const auto elevatedPath{ _elevatedPath.filename() };
|
||||
return filename == sharedPath || filename == elevatedPath;
|
||||
}
|
||||
|
||||
// Generate all getter/setters
|
||||
#define MTSM_APPLICATION_STATE_GEN(type, name, key, ...) \
|
||||
type ApplicationState::name() const noexcept \
|
||||
{ \
|
||||
const auto state = _state.lock_shared(); \
|
||||
const auto& value = state->name; \
|
||||
return value ? *value : type{ __VA_ARGS__ }; \
|
||||
} \
|
||||
\
|
||||
void ApplicationState::name(const type& value) noexcept \
|
||||
{ \
|
||||
{ \
|
||||
auto state = _state.lock(); \
|
||||
state->name.emplace(value); \
|
||||
state->name##Changed = true; \
|
||||
} \
|
||||
\
|
||||
_throttler(); \
|
||||
}
|
||||
MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN)
|
||||
#undef MTSM_APPLICATION_STATE_GEN
|
||||
|
||||
// Method Description:
|
||||
// - See GH#11119. Removes all of the data in this ApplicationState object
|
||||
// and resets it to the defaults. This will delete the state file! That's
|
||||
|
@ -156,57 +144,58 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
void ApplicationState::Reset() noexcept
|
||||
try
|
||||
{
|
||||
LOG_LAST_ERROR_IF(!DeleteFile(_path.c_str()));
|
||||
LOG_LAST_ERROR_IF(!DeleteFile(_sharedPath.c_str()));
|
||||
LOG_LAST_ERROR_IF(!DeleteFile(_elevatedPath.c_str()));
|
||||
*_state.lock() = {};
|
||||
}
|
||||
CATCH_LOG()
|
||||
|
||||
Json::Value ApplicationState::_getRoot(const locked_hfile& file) const noexcept
|
||||
{
|
||||
Json::Value root;
|
||||
try
|
||||
{
|
||||
const auto data = ReadUTF8FileLocked(file);
|
||||
if (data.empty())
|
||||
{
|
||||
return root;
|
||||
}
|
||||
|
||||
std::string errs;
|
||||
std::unique_ptr<Json::CharReader> reader{ Json::CharReaderBuilder::CharReaderBuilder().newCharReader() };
|
||||
|
||||
if (!reader->parse(data.data(), data.data() + data.size(), &root, &errs))
|
||||
{
|
||||
throw winrt::hresult_error(WEB_E_INVALID_JSON_STRING, winrt::to_hstring(errs));
|
||||
}
|
||||
}
|
||||
CATCH_LOG()
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
// Deserializes the state.json at _path into this ApplicationState.
|
||||
// Deserializes the state.json and user-state (or elevated-state if
|
||||
// elevated) into this ApplicationState.
|
||||
// * ANY errors during app state will result in the creation of a new empty state.
|
||||
// * ANY errors during runtime will result in changes being partially ignored.
|
||||
void ApplicationState::_read() const noexcept
|
||||
try
|
||||
{
|
||||
auto state = _state.lock();
|
||||
const auto file = OpenFileReadSharedLocked(_path);
|
||||
std::string errs;
|
||||
std::unique_ptr<Json::CharReader> reader{ Json::CharReaderBuilder::CharReaderBuilder().newCharReader() };
|
||||
|
||||
auto root = _getRoot(file);
|
||||
// GetValueForKey() comes in two variants:
|
||||
// * take a std::optional<T> reference
|
||||
// * return std::optional<T> by value
|
||||
// At the time of writing the former version skips missing fields in the json,
|
||||
// but we want to explicitly clear state fields that were removed from state.json.
|
||||
#define MTSM_APPLICATION_STATE_GEN(type, name, key, ...) \
|
||||
if (!state->name##Changed) \
|
||||
{ \
|
||||
state->name = JsonUtils::GetValueForKey<std::optional<type>>(root, key); \
|
||||
}
|
||||
MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN)
|
||||
#undef MTSM_APPLICATION_STATE_GEN
|
||||
// First get shared state out of `state.json`.
|
||||
const auto sharedData = _readSharedContents().value_or(std::string{});
|
||||
if (!sharedData.empty())
|
||||
{
|
||||
Json::Value root;
|
||||
if (!reader->parse(sharedData.data(), sharedData.data() + sharedData.size(), &root, &errs))
|
||||
{
|
||||
throw winrt::hresult_error(WEB_E_INVALID_JSON_STRING, winrt::to_hstring(errs));
|
||||
}
|
||||
|
||||
// - If we're elevated, we want to only load the Shared properties
|
||||
// from state.json. We'll then load the Local props from
|
||||
// `elevated-state.json`
|
||||
// - If we're unelevated, then load _everything_ from state.json.
|
||||
if (::Microsoft::Console::Utils::IsElevated())
|
||||
{
|
||||
// Only load shared properties if we're elevated
|
||||
FromJson(root, FileSource::Shared);
|
||||
|
||||
// Then, try and get anything in elevated-state
|
||||
if (const auto localData{ _readLocalContents().value_or(std::string{}) }; !localData.empty())
|
||||
{
|
||||
Json::Value root;
|
||||
if (!reader->parse(localData.data(), localData.data() + localData.size(), &root, &errs))
|
||||
{
|
||||
throw winrt::hresult_error(WEB_E_INVALID_JSON_STRING, winrt::to_hstring(errs));
|
||||
}
|
||||
FromJson(root, FileSource::Local);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we're unelevated, then load everything.
|
||||
FromJson(root, FileSource::Shared | FileSource::Local);
|
||||
}
|
||||
}
|
||||
}
|
||||
CATCH_LOG()
|
||||
|
||||
|
@ -214,29 +203,191 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
// * Errors are only logged.
|
||||
// * _state->_writeScheduled is set to false, signaling our
|
||||
// setters that _synchronize() needs to be called again.
|
||||
void ApplicationState::_write() noexcept
|
||||
void ApplicationState::_write() const noexcept
|
||||
try
|
||||
{
|
||||
// re-read the state so that we can only update the properties that were changed.
|
||||
Json::Value root{};
|
||||
Json::StreamWriterBuilder wbuilder;
|
||||
|
||||
// When we're elevated, we've got to be tricky. We don't want to write
|
||||
// our window state, allowed commandlines, and other Local properties
|
||||
// into the shared `state.json`. But, if we only serialize the Shared
|
||||
// properties to a json blob, then we'll omit windowState entirely,
|
||||
// _removing_ the window state of the unelevated instance. Oh no!
|
||||
//
|
||||
// So, to be tricky, we'll first _load_ the shared state to a json blob.
|
||||
// We'll then serialize our view of the shared properties on top of that
|
||||
// blob. Then we'll write that blob back to the file. This will
|
||||
// round-trip the Local properties for the unelevated instances
|
||||
// untouched in state.json
|
||||
//
|
||||
// After that's done, we'll write our Local properties into
|
||||
// elevated-state.json.
|
||||
if (::Microsoft::Console::Utils::IsElevated())
|
||||
{
|
||||
auto state = _state.lock();
|
||||
const auto file = OpenFileRWExclusiveLocked(_path);
|
||||
root = _getRoot(file);
|
||||
std::string errs;
|
||||
std::unique_ptr<Json::CharReader> reader{ Json::CharReaderBuilder::CharReaderBuilder().newCharReader() };
|
||||
Json::Value root;
|
||||
|
||||
#define MTSM_APPLICATION_STATE_GEN(type, name, key, ...) \
|
||||
if (state->name##Changed) \
|
||||
{ \
|
||||
JsonUtils::SetValueForKey(root, key, state->name); \
|
||||
state->name##Changed = false; \
|
||||
}
|
||||
MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN)
|
||||
#undef MTSM_APPLICATION_STATE_GEN
|
||||
// First load the contents of state.json into a json blob. This will
|
||||
// contain the Shared properties and the unelevated instance's Local
|
||||
// properties.
|
||||
const auto sharedData = _readSharedContents().value_or(std::string{});
|
||||
if (!sharedData.empty())
|
||||
{
|
||||
if (!reader->parse(sharedData.data(), sharedData.data() + sharedData.size(), &root, &errs))
|
||||
{
|
||||
throw winrt::hresult_error(WEB_E_INVALID_JSON_STRING, winrt::to_hstring(errs));
|
||||
}
|
||||
}
|
||||
// Layer our shared properties on top of the blob from state.json,
|
||||
// and write it back out.
|
||||
_writeSharedContents(Json::writeString(wbuilder, _toJsonWithBlob(root, FileSource::Shared)));
|
||||
|
||||
Json::StreamWriterBuilder wbuilder;
|
||||
const auto content = Json::writeString(wbuilder, root);
|
||||
WriteUTF8FileLocked(file, content);
|
||||
// Finally, write our Local properties back to elevated-state.json
|
||||
_writeLocalContents(Json::writeString(wbuilder, ToJson(FileSource::Local)));
|
||||
}
|
||||
else
|
||||
{
|
||||
// We're unelevated, this is easy. Just write everything back out.
|
||||
_writeLocalContents(Json::writeString(wbuilder, ToJson(FileSource::Local | FileSource::Shared)));
|
||||
}
|
||||
}
|
||||
CATCH_LOG()
|
||||
|
||||
// Returns the application-global ApplicationState object.
|
||||
Microsoft::Terminal::Settings::Model::ApplicationState ApplicationState::SharedInstance()
|
||||
{
|
||||
std::filesystem::path root{ GetBaseSettingsPath() };
|
||||
static auto state = winrt::make_self<ApplicationState>(root);
|
||||
return *state;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Loads data from the given json blob. Will only read the data that's in
|
||||
// the specified parseSource - so if we're reading the Local state file,
|
||||
// we won't destroy previously parsed Shared data.
|
||||
// - READ: there's no layering for app state.
|
||||
void ApplicationState::FromJson(const Json::Value& root, FileSource parseSource) const noexcept
|
||||
{
|
||||
auto state = _state.lock();
|
||||
// GetValueForKey() comes in two variants:
|
||||
// * take a std::optional<T> reference
|
||||
// * return std::optional<T> by value
|
||||
// At the time of writing the former version skips missing fields in the json,
|
||||
// but we want to explicitly clear state fields that were removed from state.json.
|
||||
//
|
||||
// GH#11222: We only load properties that are of the same type (Local or
|
||||
// Shared) which we requested. If we didn't want to load this type of
|
||||
// property, just skip it.
|
||||
#define MTSM_APPLICATION_STATE_GEN(source, type, name, key, ...) \
|
||||
if (WI_IsFlagSet(parseSource, source)) \
|
||||
state->name = JsonUtils::GetValueForKey<std::optional<type>>(root, key);
|
||||
|
||||
MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN)
|
||||
#undef MTSM_APPLICATION_STATE_GEN
|
||||
}
|
||||
|
||||
Json::Value ApplicationState::ToJson(FileSource parseSource) const noexcept
|
||||
{
|
||||
Json::Value root{ Json::objectValue };
|
||||
return _toJsonWithBlob(root, parseSource);
|
||||
}
|
||||
|
||||
Json::Value ApplicationState::_toJsonWithBlob(Json::Value& root, FileSource parseSource) const noexcept
|
||||
{
|
||||
{
|
||||
auto state = _state.lock_shared();
|
||||
|
||||
// GH#11222: We only write properties that are of the same type (Local
|
||||
// or Shared) which we requested. If we didn't want to serialize this
|
||||
// type of property, just skip it.
|
||||
#define MTSM_APPLICATION_STATE_GEN(source, type, name, key, ...) \
|
||||
if (WI_IsFlagSet(parseSource, source)) \
|
||||
JsonUtils::SetValueForKey(root, key, state->name);
|
||||
|
||||
MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN)
|
||||
#undef MTSM_APPLICATION_STATE_GEN
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
// Generate all getter/setters
|
||||
#define MTSM_APPLICATION_STATE_GEN(source, type, name, key, ...) \
|
||||
type ApplicationState::name() const noexcept \
|
||||
{ \
|
||||
const auto state = _state.lock_shared(); \
|
||||
const auto& value = state->name; \
|
||||
return value ? *value : type{ __VA_ARGS__ }; \
|
||||
} \
|
||||
\
|
||||
void ApplicationState::name(const type& value) noexcept \
|
||||
{ \
|
||||
{ \
|
||||
auto state = _state.lock(); \
|
||||
state->name.emplace(value); \
|
||||
} \
|
||||
\
|
||||
_throttler(); \
|
||||
}
|
||||
MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN)
|
||||
#undef MTSM_APPLICATION_STATE_GEN
|
||||
|
||||
// Method Description:
|
||||
// - Read the contents of our "shared" state - state that should be shared
|
||||
// for elevated and unelevated instances. This is things like the list of
|
||||
// generated profiles, the command palette commandlines.
|
||||
std::optional<std::string> ApplicationState::_readSharedContents() const
|
||||
{
|
||||
return ReadUTF8FileIfExists(_sharedPath);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Read the contents of our "local" state - state that should be kept in
|
||||
// separate files for elevated and unelevated instances. This is things
|
||||
// like the persisted window state, and the approved commandlines (though,
|
||||
// those don't matter when unelevated).
|
||||
// - When elevated, this will DELETE `elevated-state.json` if it has bad
|
||||
// permissions, so we don't potentially read malicious data.
|
||||
std::optional<std::string> ApplicationState::_readLocalContents() const
|
||||
{
|
||||
return ::Microsoft::Console::Utils::IsElevated() ?
|
||||
ReadUTF8FileIfExists(_elevatedPath, true) :
|
||||
ReadUTF8FileIfExists(_sharedPath, false);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Write the contents of our "shared" state - state that should be shared
|
||||
// for elevated and unelevated instances. This will atomically write to
|
||||
// `state.json`
|
||||
void ApplicationState::_writeSharedContents(const std::string_view content) const
|
||||
{
|
||||
WriteUTF8FileAtomic(_sharedPath, content);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Write the contents of our "local" state - state that should be kept in
|
||||
// separate files for elevated and unelevated instances. When elevated,
|
||||
// this will write to `elevated-state.json`, and when unelevated, this
|
||||
// will atomically write to `user-state.json`
|
||||
void ApplicationState::_writeLocalContents(const std::string_view content) const
|
||||
{
|
||||
if (::Microsoft::Console::Utils::IsElevated())
|
||||
{
|
||||
// DON'T use WriteUTF8FileAtomic, which will write to a temporary file
|
||||
// then rename that file to the final filename. That actually lets us
|
||||
// overwrite the elevate file's contents even when unelevated, because
|
||||
// we're effectively deleting the original file, then renaming a
|
||||
// different file in it's place.
|
||||
//
|
||||
// We're not worried about someone else doing that though, if they do
|
||||
// that with the wrong permissions, then we'll just ignore the file and
|
||||
// start over.
|
||||
WriteUTF8File(_elevatedPath, content, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteUTF8FileAtomic(_sharedPath, content);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,21 +16,30 @@ Abstract:
|
|||
#include "WindowLayout.g.h"
|
||||
|
||||
#include <inc/cppwinrt_utils.h>
|
||||
#include <til/mutex.h>
|
||||
#include <til/throttled_func.h>
|
||||
#include "FileUtils.h"
|
||||
#include <JsonUtils.h>
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
// If a property is Shared, then it'll be stored in `state.json`, and used
|
||||
// in both elevated and unelevated instances of the Terminal. If a property
|
||||
// is marked Local, then it will have separate values for elevated and
|
||||
// unelevated instances.
|
||||
enum FileSource : int
|
||||
{
|
||||
Shared = 0x1,
|
||||
Local = 0x2
|
||||
};
|
||||
DEFINE_ENUM_FLAG_OPERATORS(FileSource);
|
||||
|
||||
// This macro generates all getters and setters for ApplicationState.
|
||||
// It provides X with the following arguments:
|
||||
// (type, function name, JSON key, ...variadic construction arguments)
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
#define MTSM_APPLICATION_STATE_FIELDS(X) \
|
||||
X(std::unordered_set<winrt::guid>, GeneratedProfiles, "generatedProfiles") \
|
||||
X(Windows::Foundation::Collections::IVector<Model::WindowLayout>, PersistedWindowLayouts, "persistedWindowLayouts") \
|
||||
X(Windows::Foundation::Collections::IVector<hstring>, RecentCommands, "recentCommands") \
|
||||
X(Windows::Foundation::Collections::IVector<winrt::Microsoft::Terminal::Settings::Model::InfoBarMessage>, DismissedMessages, "dismissedMessages")
|
||||
// (source, type, function name, JSON key, ...variadic construction arguments)
|
||||
#define MTSM_APPLICATION_STATE_FIELDS(X) \
|
||||
X(FileSource::Shared, std::unordered_set<winrt::guid>, GeneratedProfiles, "generatedProfiles") \
|
||||
X(FileSource::Local, Windows::Foundation::Collections::IVector<Model::WindowLayout>, PersistedWindowLayouts, "persistedWindowLayouts") \
|
||||
X(FileSource::Shared, Windows::Foundation::Collections::IVector<hstring>, RecentCommands, "recentCommands") \
|
||||
X(FileSource::Shared, Windows::Foundation::Collections::IVector<winrt::Microsoft::Terminal::Settings::Model::InfoBarMessage>, DismissedMessages, "dismissedMessages") \
|
||||
X(FileSource::Local, Windows::Foundation::Collections::IVector<hstring>, AllowedCommandlines, "allowedCommandlines")
|
||||
|
||||
struct WindowLayout : WindowLayoutT<WindowLayout>
|
||||
{
|
||||
|
@ -44,23 +53,26 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
friend ::Microsoft::Terminal::Settings::Model::JsonUtils::ConversionTrait<Model::WindowLayout>;
|
||||
};
|
||||
|
||||
struct ApplicationState : ApplicationStateT<ApplicationState>
|
||||
struct ApplicationState : public ApplicationStateT<ApplicationState>
|
||||
{
|
||||
static Microsoft::Terminal::Settings::Model::ApplicationState SharedInstance();
|
||||
|
||||
ApplicationState(std::filesystem::path path) noexcept;
|
||||
ApplicationState(const std::filesystem::path& stateRoot) noexcept;
|
||||
~ApplicationState();
|
||||
|
||||
// Methods
|
||||
void Reload() const noexcept;
|
||||
void Reset() noexcept;
|
||||
|
||||
void FromJson(const Json::Value& root, FileSource parseSource) const noexcept;
|
||||
Json::Value ToJson(FileSource parseSource) const noexcept;
|
||||
|
||||
// General getters/setters
|
||||
winrt::hstring FilePath() const noexcept;
|
||||
bool IsStatePath(const winrt::hstring& filename);
|
||||
|
||||
// State getters/setters
|
||||
#define MTSM_APPLICATION_STATE_GEN(type, name, key, ...) \
|
||||
type name() const noexcept; \
|
||||
#define MTSM_APPLICATION_STATE_GEN(source, type, name, key, ...) \
|
||||
type name() const noexcept; \
|
||||
void name(const type& value) noexcept;
|
||||
MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN)
|
||||
#undef MTSM_APPLICATION_STATE_GEN
|
||||
|
@ -68,21 +80,24 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
private:
|
||||
struct state_t
|
||||
{
|
||||
#define MTSM_APPLICATION_STATE_GEN(type, name, key, ...) \
|
||||
std::optional<type> name{ __VA_ARGS__ }; \
|
||||
bool name##Changed = false;
|
||||
|
||||
#define MTSM_APPLICATION_STATE_GEN(source, type, name, key, ...) std::optional<type> name{ __VA_ARGS__ };
|
||||
MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN)
|
||||
#undef MTSM_APPLICATION_STATE_GEN
|
||||
};
|
||||
til::shared_mutex<state_t> _state;
|
||||
std::filesystem::path _sharedPath;
|
||||
std::filesystem::path _elevatedPath;
|
||||
til::throttled_func_trailing<> _throttler;
|
||||
|
||||
Json::Value _getRoot(const winrt::Microsoft::Terminal::Settings::Model::locked_hfile& file) const noexcept;
|
||||
void _write() noexcept;
|
||||
void _write() const noexcept;
|
||||
void _read() const noexcept;
|
||||
|
||||
std::filesystem::path _path;
|
||||
til::shared_mutex<state_t> _state;
|
||||
til::throttled_func_trailing<> _throttler;
|
||||
Json::Value _toJsonWithBlob(Json::Value& root, FileSource parseSource) const noexcept;
|
||||
|
||||
std::optional<std::string> _readSharedContents() const;
|
||||
void _writeSharedContents(const std::string_view content) const;
|
||||
std::optional<std::string> _readLocalContents() const;
|
||||
void _writeLocalContents(const std::string_view content) const;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -30,12 +30,15 @@ namespace Microsoft.Terminal.Settings.Model
|
|||
void Reload();
|
||||
void Reset();
|
||||
|
||||
String FilePath { get; };
|
||||
Boolean IsStatePath(String filename);
|
||||
|
||||
Windows.Foundation.Collections.IVector<WindowLayout> PersistedWindowLayouts { get; set; };
|
||||
|
||||
Windows.Foundation.Collections.IVector<String> RecentCommands { get; set; };
|
||||
|
||||
Windows.Foundation.Collections.IVector<InfoBarMessage> DismissedMessages { get; set; };
|
||||
|
||||
Windows.Foundation.Collections.IVector<String> AllowedCommandlines { get; set; };
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,10 @@
|
|||
#include <shlobj.h>
|
||||
#include <WtExeUtils.h>
|
||||
|
||||
#include <aclapi.h>
|
||||
#include <sddl.h>
|
||||
#include <wil/token_helpers.h>
|
||||
|
||||
static constexpr std::string_view Utf8Bom{ u8"\uFEFF" };
|
||||
static constexpr std::wstring_view UnpackagedSettingsFolderName{ L"Microsoft\\Windows Terminal\\" };
|
||||
|
||||
|
@ -39,86 +43,44 @@ namespace winrt::Microsoft::Terminal::Settings::Model
|
|||
return baseSettingsPath;
|
||||
}
|
||||
|
||||
locked_hfile OpenFileReadSharedLocked(const std::filesystem::path& path)
|
||||
// Function Description:
|
||||
// - Checks the permissions on this file, to make sure it can only be opened
|
||||
// for writing by admins. We will be checking to see if the file is owned
|
||||
// by the Builtin\Administrators group. If it's not, then it was likely
|
||||
// tampered with.
|
||||
// Arguments:
|
||||
// - handle: a HANDLE to the file to check
|
||||
// Return Value:
|
||||
// - true if it had the expected permissions. False otherwise.
|
||||
static bool _isOwnedByAdministrators(const HANDLE& handle)
|
||||
{
|
||||
wil::unique_hfile file{ CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr) };
|
||||
THROW_LAST_ERROR_IF(!file);
|
||||
// just lock the entire file
|
||||
OVERLAPPED sOverlapped;
|
||||
sOverlapped.Offset = 0;
|
||||
sOverlapped.OffsetHigh = 0;
|
||||
// Shared lock
|
||||
THROW_LAST_ERROR_IF(!LockFileEx(file.get(),
|
||||
0, // lock shared, wait to return until lock is obtained
|
||||
0, // reserved, does nothing
|
||||
INT_MAX, // lock INT_MAX bytes
|
||||
0, // higher-order bytes, if our state file is greater than 2GB I guess this will be a problem
|
||||
&sOverlapped));
|
||||
return { std::move(file), sOverlapped };
|
||||
// If the file is owned by the administrators group, trust the
|
||||
// administrators instead of checking the DACL permissions. It's simpler
|
||||
// and more flexible.
|
||||
|
||||
wil::unique_hlocal_security_descriptor sd;
|
||||
PSID psidOwner{ nullptr };
|
||||
// The psidOwner pointer references the security descriptor, so it
|
||||
// doesn't have to be freed separate from sd.
|
||||
const auto status = GetSecurityInfo(handle,
|
||||
SE_FILE_OBJECT,
|
||||
OWNER_SECURITY_INFORMATION,
|
||||
&psidOwner,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
wil::out_param_ptr<PSECURITY_DESCRIPTOR*>(sd));
|
||||
THROW_IF_WIN32_ERROR(status);
|
||||
|
||||
wil::unique_any_psid psidAdmins{ nullptr };
|
||||
THROW_IF_WIN32_BOOL_FALSE(
|
||||
ConvertStringSidToSidW(L"BA", wil::out_param_ptr<PSID*>(psidAdmins)));
|
||||
|
||||
return EqualSid(psidOwner, psidAdmins.get());
|
||||
}
|
||||
|
||||
locked_hfile OpenFileRWExclusiveLocked(const std::filesystem::path& path)
|
||||
{
|
||||
wil::unique_hfile file{ CreateFileW(path.c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr) };
|
||||
THROW_LAST_ERROR_IF(!file);
|
||||
// just lock the entire file
|
||||
OVERLAPPED sOverlapped;
|
||||
sOverlapped.Offset = 0;
|
||||
sOverlapped.OffsetHigh = 0;
|
||||
// Shared lock
|
||||
THROW_LAST_ERROR_IF(!LockFileEx(file.get(),
|
||||
LOCKFILE_EXCLUSIVE_LOCK, // lock exclusive, wait to return until lock is obtained
|
||||
0, // reserved, does nothing
|
||||
INT_MAX, // lock INT_MAX bytes
|
||||
0, // higher-order bytes, if our state file is greater than 2GB I guess this will be a problem
|
||||
&sOverlapped));
|
||||
return { std::move(file), sOverlapped };
|
||||
}
|
||||
|
||||
std::string ReadUTF8FileLocked(const locked_hfile& file)
|
||||
{
|
||||
const auto fileSize = GetFileSize(file.get(), nullptr);
|
||||
THROW_LAST_ERROR_IF(fileSize == INVALID_FILE_SIZE);
|
||||
|
||||
// By making our buffer just slightly larger we can detect if
|
||||
// the file size changed and we've failed to read the full file.
|
||||
std::string buffer(static_cast<size_t>(fileSize) + 1, '\0');
|
||||
DWORD bytesRead = 0;
|
||||
THROW_IF_WIN32_BOOL_FALSE(ReadFile(file.get(), buffer.data(), gsl::narrow<DWORD>(buffer.size()), &bytesRead, nullptr));
|
||||
|
||||
// As mentioned before our buffer was allocated oversized.
|
||||
buffer.resize(bytesRead);
|
||||
|
||||
if (til::starts_with(buffer, Utf8Bom))
|
||||
{
|
||||
// Yeah this memmove()s the entire content.
|
||||
// But I don't really want to deal with UTF8 BOMs any more than necessary,
|
||||
// as basically not a single editor writes a BOM for UTF8.
|
||||
buffer.erase(0, Utf8Bom.size());
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void WriteUTF8FileLocked(const locked_hfile& file, const std::string_view& content)
|
||||
{
|
||||
// truncate the file because we want to overwrite it
|
||||
SetFilePointer(file.get(), 0, nullptr, FILE_BEGIN);
|
||||
THROW_IF_WIN32_BOOL_FALSE(SetEndOfFile(file.get()));
|
||||
|
||||
const auto fileSize = gsl::narrow<DWORD>(content.size());
|
||||
DWORD bytesWritten = 0;
|
||||
THROW_IF_WIN32_BOOL_FALSE(WriteFile(file.get(), content.data(), fileSize, &bytesWritten, nullptr));
|
||||
|
||||
if (bytesWritten != fileSize)
|
||||
{
|
||||
THROW_WIN32_MSG(ERROR_WRITE_FAULT, "failed to write whole file");
|
||||
}
|
||||
}
|
||||
|
||||
// Tries to read a file somewhat atomically without locking it.
|
||||
// Strips the UTF8 BOM if it exists.
|
||||
std::string ReadUTF8File(const std::filesystem::path& path)
|
||||
std::string ReadUTF8File(const std::filesystem::path& path, const bool elevatedOnly)
|
||||
{
|
||||
// From some casual observations we can determine that:
|
||||
// * ReadFile() always returns the requested amount of data (unless the file is smaller)
|
||||
|
@ -126,9 +88,40 @@ namespace winrt::Microsoft::Terminal::Settings::Model
|
|||
// -> Lets add a retry-loop just in case, to not fail if the file size changed while reading.
|
||||
for (int i = 0; i < 3; ++i)
|
||||
{
|
||||
wil::unique_hfile file{ CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr) };
|
||||
wil::unique_hfile file{ CreateFileW(path.c_str(),
|
||||
GENERIC_READ,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
||||
nullptr,
|
||||
OPEN_EXISTING,
|
||||
FILE_ATTRIBUTE_NORMAL,
|
||||
nullptr) };
|
||||
THROW_LAST_ERROR_IF(!file);
|
||||
|
||||
// Open the file _first_, then check if it has the right
|
||||
// permissions. This prevents a "Time-of-check to time-of-use"
|
||||
// vulnerability where a malicious exe could delete the file and
|
||||
// replace it between us checking the permissions, and reading the
|
||||
// contents. We've got a handle to the file now, which means we're
|
||||
// going to read the contents of that instance of the file
|
||||
// regardless. If someone replaces the file on us before we get to
|
||||
// the GetSecurityInfo call below, then only the subsequent call to
|
||||
// ReadUTF8File will notice it.
|
||||
if (elevatedOnly)
|
||||
{
|
||||
const bool hadExpectedPermissions{ _isOwnedByAdministrators(file.get()) };
|
||||
if (!hadExpectedPermissions)
|
||||
{
|
||||
// Close the handle
|
||||
file.reset();
|
||||
|
||||
// delete the file. It's been compromised.
|
||||
LOG_LAST_ERROR_IF(!DeleteFile(path.c_str()));
|
||||
|
||||
// Exit early, because obviously there's nothing to read from the deleted file.
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
const auto fileSize = GetFileSize(file.get(), nullptr);
|
||||
THROW_LAST_ERROR_IF(fileSize == INVALID_FILE_SIZE);
|
||||
|
||||
|
@ -166,11 +159,11 @@ namespace winrt::Microsoft::Terminal::Settings::Model
|
|||
}
|
||||
|
||||
// Same as ReadUTF8File, but returns an empty optional, if the file couldn't be opened.
|
||||
std::optional<std::string> ReadUTF8FileIfExists(const std::filesystem::path& path)
|
||||
std::optional<std::string> ReadUTF8FileIfExists(const std::filesystem::path& path, const bool elevatedOnly)
|
||||
{
|
||||
try
|
||||
{
|
||||
return { ReadUTF8File(path) };
|
||||
return { ReadUTF8File(path, elevatedOnly) };
|
||||
}
|
||||
catch (const wil::ResultException& exception)
|
||||
{
|
||||
|
@ -183,9 +176,70 @@ namespace winrt::Microsoft::Terminal::Settings::Model
|
|||
}
|
||||
}
|
||||
|
||||
void WriteUTF8File(const std::filesystem::path& path, const std::string_view& content)
|
||||
void WriteUTF8File(const std::filesystem::path& path,
|
||||
const std::string_view& content,
|
||||
const bool elevatedOnly)
|
||||
{
|
||||
wil::unique_hfile file{ CreateFileW(path.c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr) };
|
||||
SECURITY_ATTRIBUTES sa;
|
||||
// stash the security descriptor here, so it will stay in context until
|
||||
// after the call to CreateFile. If it gets cleaned up before that, then
|
||||
// CreateFile will fail
|
||||
wil::unique_hlocal_security_descriptor sd;
|
||||
if (elevatedOnly)
|
||||
{
|
||||
// Initialize the security descriptor so only admins can write the
|
||||
// file. We'll initialize the SECURITY_DESCRIPTOR with a
|
||||
// single entry (ACE) -- a mandatory label (i.e. a
|
||||
// LABEL_SECURITY_INFORMATION) that sets the file integrity level to
|
||||
// "high", with a no-write-up policy.
|
||||
//
|
||||
// When accessed from a security context at a lower integrity level,
|
||||
// the no-write-up policy filters out rights that aren't in the
|
||||
// object type's generic read and execute set (for the file type,
|
||||
// that's FILE_GENERIC_READ | FILE_GENERIC_EXECUTE).
|
||||
//
|
||||
// Another option we considered here was manually setting the ACLs
|
||||
// on this file such that Builtin\Admins could read&write the file,
|
||||
// and all users could only read.
|
||||
//
|
||||
// Big thanks to @eryksun in GH#11222 for helping with this. This
|
||||
// alternative method was chosen because it's considerably simpler.
|
||||
|
||||
// The required security descriptor can be created easily from the
|
||||
// SDDL string: "S:(ML;;NW;;;HI)"
|
||||
// (i.e. SACL:mandatory label;;no write up;;;high integrity level)
|
||||
unsigned long cb;
|
||||
THROW_IF_WIN32_BOOL_FALSE(
|
||||
ConvertStringSecurityDescriptorToSecurityDescriptor(L"S:(ML;;NW;;;HI)",
|
||||
SDDL_REVISION_1,
|
||||
wil::out_param_ptr<PSECURITY_DESCRIPTOR*>(sd),
|
||||
&cb));
|
||||
|
||||
// Initialize a security attributes structure.
|
||||
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
|
||||
sa.lpSecurityDescriptor = sd.get();
|
||||
sa.bInheritHandle = false;
|
||||
|
||||
// If we're running in an elevated context, when this file is
|
||||
// created, it will automatically be owned by
|
||||
// Builtin\Administrators, which will pass the above
|
||||
// _isOwnedByAdministrators check.
|
||||
//
|
||||
// Programs running in an elevated context will be free to write the
|
||||
// file, and unelevated processes will be able to read the file. An
|
||||
// unelevated process could always delete the file and rename a new
|
||||
// file in it's place (a la the way `vim.exe` saves files), but if
|
||||
// they do that, the new file _won't_ be owned by Administrators,
|
||||
// failing the above check.
|
||||
}
|
||||
|
||||
wil::unique_hfile file{ CreateFileW(path.c_str(),
|
||||
GENERIC_WRITE,
|
||||
FILE_SHARE_READ | FILE_SHARE_DELETE,
|
||||
elevatedOnly ? &sa : nullptr,
|
||||
CREATE_ALWAYS,
|
||||
FILE_ATTRIBUTE_NORMAL,
|
||||
nullptr) };
|
||||
THROW_LAST_ERROR_IF(!file);
|
||||
|
||||
const auto fileSize = gsl::narrow<DWORD>(content.size());
|
||||
|
@ -198,7 +252,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model
|
|||
}
|
||||
}
|
||||
|
||||
void WriteUTF8FileAtomic(const std::filesystem::path& path, const std::string_view& content)
|
||||
void WriteUTF8FileAtomic(const std::filesystem::path& path,
|
||||
const std::string_view& content)
|
||||
{
|
||||
// GH#10787: rename() will replace symbolic links themselves and not the path they point at.
|
||||
// It's thus important that we first resolve them before generating temporary path.
|
||||
|
|
|
@ -1,41 +1,11 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model
|
||||
{
|
||||
// I couldn't find a wil helper for this so I made it myself
|
||||
class locked_hfile
|
||||
{
|
||||
public:
|
||||
wil::unique_hfile file;
|
||||
OVERLAPPED lockedRegion;
|
||||
|
||||
~locked_hfile()
|
||||
{
|
||||
if (file)
|
||||
{
|
||||
// Need to unlock the file before it is closed
|
||||
UnlockFileEx(file.get(), 0, INT_MAX, 0, &lockedRegion);
|
||||
}
|
||||
}
|
||||
|
||||
HANDLE get() const noexcept
|
||||
{
|
||||
return file.get();
|
||||
}
|
||||
};
|
||||
|
||||
std::filesystem::path GetBaseSettingsPath();
|
||||
|
||||
locked_hfile OpenFileReadSharedLocked(const std::filesystem::path& path);
|
||||
locked_hfile OpenFileRWExclusiveLocked(const std::filesystem::path& path);
|
||||
std::string ReadUTF8FileLocked(const locked_hfile& file);
|
||||
void WriteUTF8FileLocked(const locked_hfile& file, const std::string_view& content);
|
||||
|
||||
std::string ReadUTF8File(const std::filesystem::path& path);
|
||||
std::optional<std::string> ReadUTF8FileIfExists(const std::filesystem::path& path);
|
||||
void WriteUTF8File(const std::filesystem::path& path, const std::string_view& content);
|
||||
std::string ReadUTF8File(const std::filesystem::path& path, const bool elevatedOnly = false);
|
||||
std::optional<std::string> ReadUTF8FileIfExists(const std::filesystem::path& path, const bool elevatedOnly = false);
|
||||
void WriteUTF8File(const std::filesystem::path& path, const std::string_view& content, const bool elevatedOnly = false);
|
||||
void WriteUTF8FileAtomic(const std::filesystem::path& path, const std::string_view& content);
|
||||
}
|
||||
|
|
|
@ -382,7 +382,6 @@ namespace Microsoft::Terminal::Settings::Model::JsonUtils
|
|||
};
|
||||
|
||||
template<typename T>
|
||||
|
||||
struct ConversionTrait<std::unordered_map<std::string, T>>
|
||||
{
|
||||
std::unordered_map<std::string, T> FromJson(const Json::Value& json) const
|
||||
|
|
|
@ -73,7 +73,8 @@ Author(s):
|
|||
X(hstring, Icon, "icon", L"\uE756") \
|
||||
X(CloseOnExitMode, CloseOnExit, "closeOnExit", CloseOnExitMode::Graceful) \
|
||||
X(hstring, TabTitle, "tabTitle") \
|
||||
X(Model::BellStyle, BellStyle, "bellStyle", BellStyle::Audible)
|
||||
X(Model::BellStyle, BellStyle, "bellStyle", BellStyle::Audible) \
|
||||
X(bool, UseAtlasEngine, "experimental.useAtlasEngine", false)
|
||||
|
||||
#define MTSM_FONT_SETTINGS(X) \
|
||||
X(hstring, FontFace, "face", DEFAULT_FONT_FACE) \
|
||||
|
|
|
@ -79,5 +79,6 @@ namespace Microsoft.Terminal.Settings.Model
|
|||
INHERITABLE_PROFILE_SETTING(Boolean, SnapOnInput);
|
||||
INHERITABLE_PROFILE_SETTING(Boolean, AltGrAliasing);
|
||||
INHERITABLE_PROFILE_SETTING(BellStyle, BellStyle);
|
||||
INHERITABLE_PROFILE_SETTING(Boolean, UseAtlasEngine);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -245,6 +245,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
_SuppressApplicationTitle = profile.SuppressApplicationTitle();
|
||||
}
|
||||
|
||||
_UseAtlasEngine = profile.UseAtlasEngine();
|
||||
_ScrollState = profile.ScrollState();
|
||||
|
||||
_AntialiasingMode = profile.AntialiasingMode();
|
||||
|
|
|
@ -141,6 +141,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
INHERITABLE_SETTING(Model::TerminalSettings, hstring, EnvironmentVariables);
|
||||
|
||||
INHERITABLE_SETTING(Model::TerminalSettings, Microsoft::Terminal::Control::ScrollbarState, ScrollState, Microsoft::Terminal::Control::ScrollbarState::Visible);
|
||||
INHERITABLE_SETTING(Model::TerminalSettings, bool, UseAtlasEngine, false);
|
||||
|
||||
INHERITABLE_SETTING(Model::TerminalSettings, Microsoft::Terminal::Control::TextAntialiasingMode, AntialiasingMode, Microsoft::Terminal::Control::TextAntialiasingMode::Grayscale);
|
||||
|
||||
|
|
|
@ -53,3 +53,6 @@ TRACELOGGING_DECLARE_PROVIDER(g_hSettingsModelProvider);
|
|||
|
||||
// Manually include til after we include Windows.Foundation to give it winrt superpowers
|
||||
#include "til.h"
|
||||
|
||||
#include <til/mutex.h>
|
||||
#include <til/throttled_func.h>
|
||||
|
|
|
@ -75,6 +75,7 @@ namespace ControlUnitTests
|
|||
WINRT_PROPERTY(winrt::hstring, EnvironmentVariables);
|
||||
|
||||
WINRT_PROPERTY(winrt::Microsoft::Terminal::Control::ScrollbarState, ScrollState, winrt::Microsoft::Terminal::Control::ScrollbarState::Visible);
|
||||
WINRT_PROPERTY(bool, UseAtlasEngine, false);
|
||||
|
||||
WINRT_PROPERTY(winrt::Microsoft::Terminal::Control::TextAntialiasingMode, AntialiasingMode, winrt::Microsoft::Terminal::Control::TextAntialiasingMode::Grayscale);
|
||||
|
||||
|
|
|
@ -845,8 +845,30 @@ winrt::Windows::Foundation::IAsyncAction AppHost::_SaveWindowLayouts()
|
|||
|
||||
if (_logic.ShouldUsePersistedLayout())
|
||||
{
|
||||
const auto layoutJsons = _windowManager.GetAllWindowLayouts();
|
||||
_logic.SaveWindowLayoutJsons(layoutJsons);
|
||||
try
|
||||
{
|
||||
TraceLoggingWrite(g_hWindowsTerminalProvider,
|
||||
"AppHost_SaveWindowLayouts_Collect",
|
||||
TraceLoggingDescription("Logged when collecting window state"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
const auto layoutJsons = _windowManager.GetAllWindowLayouts();
|
||||
TraceLoggingWrite(g_hWindowsTerminalProvider,
|
||||
"AppHost_SaveWindowLayouts_Save",
|
||||
TraceLoggingDescription("Logged when writing window state"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
_logic.SaveWindowLayoutJsons(layoutJsons);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_CAUGHT_EXCEPTION();
|
||||
TraceLoggingWrite(g_hWindowsTerminalProvider,
|
||||
"AppHost_SaveWindowLayouts_Failed",
|
||||
TraceLoggingDescription("An error occurred when collecting or writing window state"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
}
|
||||
}
|
||||
|
||||
co_return;
|
||||
|
@ -867,6 +889,12 @@ winrt::fire_and_forget AppHost::_SaveWindowLayoutsRepeat()
|
|||
// per 10 seconds, if a save is requested by another source simultaneously.
|
||||
if (_getWindowLayoutThrottler.has_value())
|
||||
{
|
||||
TraceLoggingWrite(g_hWindowsTerminalProvider,
|
||||
"AppHost_requestGetLayout",
|
||||
TraceLoggingDescription("Logged when triggering a throttled write of the window state"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
|
||||
_getWindowLayoutThrottler.value()();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,4 +66,5 @@
|
|||
X(winrt::Microsoft::Terminal::Control::TextAntialiasingMode, AntialiasingMode, winrt::Microsoft::Terminal::Control::TextAntialiasingMode::Grayscale) \
|
||||
X(bool, ForceFullRepaintRendering, false) \
|
||||
X(bool, SoftwareRendering, false) \
|
||||
X(bool, ForceVTInput, false)
|
||||
X(bool, ForceVTInput, false) \
|
||||
X(bool, UseAtlasEngine, false)
|
||||
|
|
|
@ -79,4 +79,14 @@
|
|||
<!-- This feature will not ship to Stable until it is complete. -->
|
||||
<alwaysDisabledReleaseTokens />
|
||||
</feature>
|
||||
|
||||
<feature>
|
||||
<name>Feature_AtlasEngine</name>
|
||||
<description>If enabled, AtlasEngine and the experimental.useAtlasEngine setting are compiled into the project</description>
|
||||
<stage>AlwaysEnabled</stage>
|
||||
<alwaysDisabledBrandingTokens>
|
||||
<brandingToken>Release</brandingToken>
|
||||
<brandingToken>WindowsInbox</brandingToken>
|
||||
</alwaysDisabledBrandingTokens>
|
||||
</feature>
|
||||
</featureStaging>
|
||||
|
|
|
@ -43,6 +43,9 @@
|
|||
<ProjectReference Include="..\..\renderer\base\lib\base.vcxproj">
|
||||
<Project>{af0a096a-8b3a-4949-81ef-7df8f0fee91f}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\renderer\atlas\atlas.vcxproj">
|
||||
<Project>{8222900C-8B6C-452A-91AC-BE95DB04B95F}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\renderer\dx\lib\dx.vcxproj">
|
||||
<Project>{48d21369-3d7b-4431-9967-24e81292cf62}</Project>
|
||||
</ProjectReference>
|
||||
|
|
|
@ -38,6 +38,9 @@
|
|||
<ProjectReference Include="..\..\renderer\base\lib\base.vcxproj">
|
||||
<Project>{af0a096a-8b3a-4949-81ef-7df8f0fee91f}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\renderer\atlas\atlas.vcxproj">
|
||||
<Project>{8222900C-8B6C-452A-91AC-BE95DB04B95F}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\renderer\dx\lib\dx.vcxproj">
|
||||
<Project>{48d21369-3d7b-4431-9967-24e81292cf62}</Project>
|
||||
</ProjectReference>
|
||||
|
@ -71,6 +74,7 @@
|
|||
<AdditionalIncludeDirectories>..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>winmm.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<SubSystem>Console</SubSystem>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
|
|
|
@ -2320,9 +2320,6 @@ void ReadStringWithReadConsoleInputAHelper(HANDLE hIn, PCSTR pszExpectedText, si
|
|||
|
||||
while (cchRead < cchExpectedText)
|
||||
{
|
||||
// expected read is either the size of the buffer or the number of characters remaining, whichever is smaller.
|
||||
DWORD const dwReadExpected = (DWORD)std::min(cbBuffer, cchExpectedText - cchRead);
|
||||
|
||||
DWORD dwRead;
|
||||
if (!VERIFY_WIN32_BOOL_SUCCEEDED(ReadConsoleInputA(hIn, irRead, (DWORD)cbBuffer, &dwRead), L"Attempt to read input into buffer."))
|
||||
{
|
||||
|
|
|
@ -22,10 +22,7 @@ Revision History:
|
|||
#include "ConsoleArguments.hpp"
|
||||
#include "ApiRoutines.h"
|
||||
|
||||
#include "../renderer/inc/IRenderData.hpp"
|
||||
#include "../renderer/inc/IRenderEngine.hpp"
|
||||
#include "../renderer/inc/IRenderer.hpp"
|
||||
#include "../renderer/inc/IFontDefaultList.hpp"
|
||||
#include "../renderer/base/Renderer.hpp"
|
||||
|
||||
#include "../server/DeviceComm.h"
|
||||
#include "../server/ConDrvDeviceComm.h"
|
||||
|
@ -62,7 +59,7 @@ public:
|
|||
|
||||
std::vector<wchar_t> WordDelimiters;
|
||||
|
||||
Microsoft::Console::Render::IRenderer* pRender;
|
||||
Microsoft::Console::Render::Renderer* pRender;
|
||||
|
||||
Microsoft::Console::Render::IFontDefaultList* pFontDefaultList;
|
||||
|
||||
|
|
|
@ -58,7 +58,7 @@ Settings::Settings() :
|
|||
_fInterceptCopyPaste(0),
|
||||
_DefaultForeground(INVALID_COLOR),
|
||||
_DefaultBackground(INVALID_COLOR),
|
||||
_fUseDx(false),
|
||||
_fUseDx(UseDx::Disabled),
|
||||
_fCopyColor(false)
|
||||
{
|
||||
_dwScreenBufferSize.X = 80;
|
||||
|
@ -820,12 +820,9 @@ void Settings::SetTerminalScrolling(const bool terminalScrollingEnabled) noexcep
|
|||
_TerminalScrolling = terminalScrollingEnabled;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Determines whether our primary renderer should be DirectX or GDI.
|
||||
// - This is based on user preference and velocity hold back state.
|
||||
// Return Value:
|
||||
// - True means use DirectX renderer. False means use GDI renderer.
|
||||
bool Settings::GetUseDx() const noexcept
|
||||
// Determines whether our primary renderer should be DirectX or GDI.
|
||||
// This is based on user preference and velocity hold back state.
|
||||
UseDx Settings::GetUseDx() const noexcept
|
||||
{
|
||||
return _fUseDx;
|
||||
}
|
||||
|
|
|
@ -26,6 +26,13 @@ constexpr unsigned short MIN_WINDOW_OPACITY = 0x4D; // 0x4D is approximately 30%
|
|||
#include "ConsoleArguments.hpp"
|
||||
#include "../inc/conattrs.hpp"
|
||||
|
||||
enum class UseDx : DWORD
|
||||
{
|
||||
Disabled = 0,
|
||||
DxEngine,
|
||||
AtlasEngine,
|
||||
};
|
||||
|
||||
class Settings
|
||||
{
|
||||
public:
|
||||
|
@ -188,7 +195,7 @@ public:
|
|||
bool IsTerminalScrolling() const noexcept;
|
||||
void SetTerminalScrolling(const bool terminalScrollingEnabled) noexcept;
|
||||
|
||||
bool GetUseDx() const noexcept;
|
||||
UseDx GetUseDx() const noexcept;
|
||||
bool GetCopyColor() const noexcept;
|
||||
|
||||
private:
|
||||
|
@ -232,7 +239,7 @@ private:
|
|||
bool _fAutoReturnOnNewline;
|
||||
bool _fRenderGridWorldwide;
|
||||
bool _fScreenReversed;
|
||||
bool _fUseDx;
|
||||
UseDx _fUseDx;
|
||||
bool _fCopyColor;
|
||||
|
||||
std::array<COLORREF, XTERM_COLOR_TABLE_SIZE> _colorTable;
|
||||
|
|
|
@ -52,6 +52,9 @@
|
|||
<ProjectReference Include="..\..\internal\internal.vcxproj">
|
||||
<Project>{ef3e32a7-5ff6-42b4-b6e2-96cd7d033f00}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\renderer\atlas\atlas.vcxproj">
|
||||
<Project>{8222900C-8B6C-452A-91AC-BE95DB04B95F}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\renderer\dx\lib\dx.vcxproj">
|
||||
<Project>{48d21369-3d7b-4431-9967-24e81292cf62}</Project>
|
||||
</ProjectReference>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<RootNamespace>win32</RootNamespace>
|
||||
<ProjectName>InteractivityWin32</ProjectName>
|
||||
<TargetName>ConInteractivityWin32Lib</TargetName>
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(SolutionDir)src\common.build.pre.props" />
|
||||
<ItemDefinitionGroup>
|
||||
|
@ -61,6 +61,9 @@
|
|||
<ClInclude Include="..\windowUiaProvider.hpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\renderer\atlas\atlas.vcxproj">
|
||||
<Project>{8222900C-8B6C-452A-91AC-BE95DB04B95F}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\..\renderer\dx\lib\dx.vcxproj">
|
||||
<Project>{48d21369-3d7b-4431-9967-24e81292cf62}</Project>
|
||||
</ProjectReference>
|
||||
|
|
|
@ -22,6 +22,9 @@
|
|||
<ProjectReference Include="..\..\..\internal\internal.vcxproj">
|
||||
<Project>{ef3e32a7-5ff6-42b4-b6e2-96cd7d033f00}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\..\renderer\atlas\atlas.vcxproj">
|
||||
<Project>{8222900C-8B6C-452A-91AC-BE95DB04B95F}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\..\renderer\dx\lib\dx.vcxproj">
|
||||
<Project>{48d21369-3d7b-4431-9967-24e81292cf62}</Project>
|
||||
</ProjectReference>
|
||||
|
@ -76,4 +79,4 @@
|
|||
<!-- 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" />
|
||||
</Project>
|
||||
</Project>
|
||||
|
|
|
@ -735,7 +735,6 @@ class UiaTextRangeTests
|
|||
TEST_METHOD(CanMoveByCharacter)
|
||||
{
|
||||
const SHORT lastColumnIndex = _pScreenInfo->GetBufferSize().RightInclusive();
|
||||
const SHORT bottomRow = gsl::narrow<SHORT>(_pTextBuffer->TotalRowCount() - 1);
|
||||
|
||||
// GH#6986: This is used as the "end of the buffer" to help screen readers run faster
|
||||
// instead of parsing through thousands of empty lines of text.
|
||||
|
@ -824,7 +823,6 @@ class UiaTextRangeTests
|
|||
TEST_METHOD(CanMoveByLine)
|
||||
{
|
||||
const SHORT lastColumnIndex = _pScreenInfo->GetBufferSize().Width() - 1;
|
||||
const SHORT bottomRow = gsl::narrow<SHORT>(_pTextBuffer->TotalRowCount() - 1);
|
||||
|
||||
// GH#6986: This is used as the "end of the buffer" to help screen readers run faster
|
||||
// instead of parsing through thousands of empty lines of text.
|
||||
|
@ -913,7 +911,6 @@ class UiaTextRangeTests
|
|||
TEST_METHOD(CanMoveEndpointByUnitCharacter)
|
||||
{
|
||||
const SHORT lastColumnIndex = _pScreenInfo->GetBufferSize().Width() - 1;
|
||||
const SHORT bottomRow = static_cast<SHORT>(_pTextBuffer->TotalRowCount() - 1);
|
||||
|
||||
// GH#6986: This is used as the "end of the buffer" to help screen readers run faster
|
||||
// instead of parsing through thousands of empty lines of text.
|
||||
|
@ -1197,7 +1194,6 @@ class UiaTextRangeTests
|
|||
|
||||
TEST_METHOD(CanMoveEndpointByUnitDocument)
|
||||
{
|
||||
const SHORT lastColumnIndex = _pScreenInfo->GetBufferSize().Width() - 1;
|
||||
const SHORT bottomRow = gsl::narrow<SHORT>(_pTextBuffer->TotalRowCount() - 1);
|
||||
|
||||
// GH#6986: This is used as the "end of the buffer" to help screen readers run faster
|
||||
|
|
|
@ -9,17 +9,14 @@
|
|||
#include "window.hpp"
|
||||
#include "windowio.hpp"
|
||||
#include "windowdpiapi.hpp"
|
||||
#include "windowmetrics.hpp"
|
||||
#include "WindowMetrics.hpp"
|
||||
|
||||
#include "../../inc/conint.h"
|
||||
|
||||
#include "../../host/globals.h"
|
||||
#include "../../host/dbcs.h"
|
||||
#include "../../host/getset.h"
|
||||
#include "../../host/misc.h"
|
||||
#include "../../host/_output.h"
|
||||
#include "../../host/output.h"
|
||||
#include "../../host/renderData.hpp"
|
||||
#include "../../host/scrolling.hpp"
|
||||
#include "../../host/srvinit.h"
|
||||
#include "../../host/stream.h"
|
||||
|
@ -29,6 +26,9 @@
|
|||
#include "../../renderer/base/renderer.hpp"
|
||||
#include "../../renderer/gdi/gdirenderer.hpp"
|
||||
|
||||
#if TIL_FEATURE_ATLASENGINE_ENABLED
|
||||
#include "../../renderer/atlas/AtlasEngine.h"
|
||||
#endif
|
||||
#if TIL_FEATURE_CONHOSTDXENGINE_ENABLED
|
||||
#include "../../renderer/dx/DxRenderer.hpp"
|
||||
#endif
|
||||
|
@ -209,16 +209,20 @@ void Window::_UpdateSystemMetrics() const
|
|||
// Ensure we have appropriate system metrics before we start constructing the window.
|
||||
_UpdateSystemMetrics();
|
||||
|
||||
const bool useDx = pSettings->GetUseDx();
|
||||
const auto useDx = pSettings->GetUseDx();
|
||||
GdiEngine* pGdiEngine = nullptr;
|
||||
#if TIL_FEATURE_CONHOSTDXENGINE_ENABLED
|
||||
[[maybe_unused]] DxEngine* pDxEngine = nullptr;
|
||||
DxEngine* pDxEngine = nullptr;
|
||||
#endif
|
||||
#if TIL_FEATURE_ATLASENGINE_ENABLED
|
||||
AtlasEngine* pAtlasEngine = nullptr;
|
||||
#endif
|
||||
try
|
||||
{
|
||||
#if TIL_FEATURE_CONHOSTDXENGINE_ENABLED
|
||||
if (useDx)
|
||||
switch (useDx)
|
||||
{
|
||||
#if TIL_FEATURE_CONHOSTDXENGINE_ENABLED
|
||||
case UseDx::DxEngine:
|
||||
pDxEngine = new DxEngine();
|
||||
// TODO: MSFT:21255595 make this less gross
|
||||
// Manually set the Dx Engine to Hwnd mode. When we're trying to
|
||||
|
@ -227,12 +231,18 @@ void Window::_UpdateSystemMetrics() const
|
|||
// math in the hwnd mode, not the Composition mode.
|
||||
THROW_IF_FAILED(pDxEngine->SetHwnd(nullptr));
|
||||
g.pRender->AddRenderEngine(pDxEngine);
|
||||
}
|
||||
else
|
||||
break;
|
||||
#endif
|
||||
{
|
||||
#if TIL_FEATURE_ATLASENGINE_ENABLED
|
||||
case UseDx::AtlasEngine:
|
||||
pAtlasEngine = new AtlasEngine();
|
||||
g.pRender->AddRenderEngine(pAtlasEngine);
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
pGdiEngine = new GdiEngine();
|
||||
g.pRender->AddRenderEngine(pGdiEngine);
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
|
@ -324,7 +334,7 @@ void Window::_UpdateSystemMetrics() const
|
|||
_hWnd = hWnd;
|
||||
|
||||
#if TIL_FEATURE_CONHOSTDXENGINE_ENABLED
|
||||
if (useDx)
|
||||
if (pDxEngine)
|
||||
{
|
||||
status = NTSTATUS_FROM_WIN32(HRESULT_CODE((pDxEngine->SetHwnd(hWnd))));
|
||||
|
||||
|
@ -334,6 +344,13 @@ void Window::_UpdateSystemMetrics() const
|
|||
}
|
||||
}
|
||||
else
|
||||
#endif
|
||||
#if TIL_FEATURE_ATLASENGINE_ENABLED
|
||||
if (pAtlasEngine)
|
||||
{
|
||||
status = NTSTATUS_FROM_WIN32(HRESULT_CODE((pAtlasEngine->SetHwnd(hWnd))));
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
status = NTSTATUS_FROM_WIN32(HRESULT_CODE((pGdiEngine->SetHwnd(hWnd))));
|
||||
|
|
|
@ -63,7 +63,7 @@ const RegistrySerialization::_RegPropertyMap RegistrySerialization::s_PropertyMa
|
|||
{ _RegPropertyType::Dword, CONSOLE_REGISTRY_DEFAULTFOREGROUND, SET_FIELD_AND_SIZE(_DefaultForeground) },
|
||||
{ _RegPropertyType::Dword, CONSOLE_REGISTRY_DEFAULTBACKGROUND, SET_FIELD_AND_SIZE(_DefaultBackground) },
|
||||
{ _RegPropertyType::Boolean, CONSOLE_REGISTRY_TERMINALSCROLLING, SET_FIELD_AND_SIZE(_TerminalScrolling) },
|
||||
{ _RegPropertyType::Boolean, CONSOLE_REGISTRY_USEDX, SET_FIELD_AND_SIZE(_fUseDx) },
|
||||
{ _RegPropertyType::Dword, CONSOLE_REGISTRY_USEDX, SET_FIELD_AND_SIZE(_fUseDx) },
|
||||
{ _RegPropertyType::Boolean, CONSOLE_REGISTRY_COPYCOLOR, SET_FIELD_AND_SIZE(_fCopyColor) }
|
||||
|
||||
};
|
||||
|
@ -251,7 +251,8 @@ NTSTATUS RegistrySerialization::s_OpenKey(_In_opt_ HKEY const hKey, _In_ PCWSTR
|
|||
[[nodiscard]]
|
||||
NTSTATUS RegistrySerialization::s_DeleteValue(const HKEY hKey, _In_ PCWSTR const pwszValueName)
|
||||
{
|
||||
return NTSTATUS_FROM_WIN32(RegDeleteKeyValueW(hKey, nullptr, pwszValueName));
|
||||
const auto result = RegDeleteKeyValueW(hKey, nullptr, pwszValueName);
|
||||
return result == ERROR_FILE_NOT_FOUND ? S_OK : NTSTATUS_FROM_WIN32(result);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
|
|
605
src/renderer/atlas/AtlasEngine.api.cpp
Normal file
605
src/renderer/atlas/AtlasEngine.api.cpp
Normal file
|
@ -0,0 +1,605 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "AtlasEngine.h"
|
||||
|
||||
// #### NOTE ####
|
||||
// If you see any code in here that contains "_r." you might be seeing a race condition.
|
||||
// The AtlasEngine::Present() method is called on a background thread without any locks,
|
||||
// while any of the API methods (like AtlasEngine::Invalidate) might be called concurrently.
|
||||
// The usage of _r fields is unsafe as those are accessed and written to by the Present() method.
|
||||
|
||||
#pragma warning(disable : 4100) // '...': unreferenced formal parameter
|
||||
// Disable a bunch of warnings which get in the way of writing performant code.
|
||||
#pragma warning(disable : 26429) // Symbol 'data' is never tested for nullness, it can be marked as not_null (f.23).
|
||||
#pragma warning(disable : 26446) // Prefer to use gsl::at() instead of unchecked subscript operator (bounds.4).
|
||||
#pragma warning(disable : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1).
|
||||
#pragma warning(disable : 26482) // Only index into arrays using constant expressions (bounds.2).
|
||||
|
||||
using namespace Microsoft::Console::Render;
|
||||
|
||||
// Like gsl::narrow but returns a HRESULT.
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 26472) // Don't use a static_cast for arithmetic conversions. Use brace initialization, gsl::narrow_cast or gsl::narrow (type.1).
|
||||
template<typename T, typename U>
|
||||
constexpr HRESULT api_narrow(U val, T& out) noexcept
|
||||
{
|
||||
out = static_cast<T>(val);
|
||||
return static_cast<U>(out) != val || (std::is_signed_v<T> != std::is_signed_v<U> && out < T{} != val < U{}) ? HRESULT_FROM_WIN32(ERROR_ARITHMETIC_OVERFLOW) : S_OK;
|
||||
}
|
||||
#pragma warning(pop)
|
||||
|
||||
template<typename T, typename U>
|
||||
constexpr HRESULT vec2_narrow(U x, U y, AtlasEngine::vec2<T>& out) noexcept
|
||||
{
|
||||
return api_narrow(x, out.x) | api_narrow(y, out.y);
|
||||
}
|
||||
|
||||
#pragma region IRenderEngine
|
||||
|
||||
[[nodiscard]] HRESULT AtlasEngine::Invalidate(const SMALL_RECT* const psrRegion) noexcept
|
||||
{
|
||||
//assert(psrRegion->Top < psrRegion->Bottom && psrRegion->Top >= 0 && psrRegion->Bottom <= _api.cellCount.y);
|
||||
|
||||
// BeginPaint() protects against invalid out of bounds numbers.
|
||||
_api.invalidatedRows.x = std::min(_api.invalidatedRows.x, gsl::narrow_cast<u16>(psrRegion->Top));
|
||||
_api.invalidatedRows.y = std::max(_api.invalidatedRows.y, gsl::narrow_cast<u16>(psrRegion->Bottom));
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
[[nodiscard]] HRESULT AtlasEngine::InvalidateCursor(const SMALL_RECT* const psrRegion) noexcept
|
||||
{
|
||||
//assert(psrRegion->Left <= psrRegion->Right && psrRegion->Left >= 0 && psrRegion->Right <= _api.cellCount.x);
|
||||
//assert(psrRegion->Top <= psrRegion->Bottom && psrRegion->Top >= 0 && psrRegion->Bottom <= _api.cellCount.y);
|
||||
|
||||
const auto left = gsl::narrow_cast<u16>(psrRegion->Left);
|
||||
const auto top = gsl::narrow_cast<u16>(psrRegion->Top);
|
||||
const auto right = gsl::narrow_cast<u16>(psrRegion->Right);
|
||||
const auto bottom = gsl::narrow_cast<u16>(psrRegion->Bottom);
|
||||
|
||||
// BeginPaint() protects against invalid out of bounds numbers.
|
||||
_api.invalidatedCursorArea.left = std::min(_api.invalidatedCursorArea.left, left);
|
||||
_api.invalidatedCursorArea.top = std::min(_api.invalidatedCursorArea.top, top);
|
||||
_api.invalidatedCursorArea.right = std::max(_api.invalidatedCursorArea.right, right);
|
||||
_api.invalidatedCursorArea.bottom = std::max(_api.invalidatedCursorArea.bottom, bottom);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
[[nodiscard]] HRESULT AtlasEngine::InvalidateSystem(const RECT* const prcDirtyClient) noexcept
|
||||
{
|
||||
const auto top = prcDirtyClient->top / _api.fontMetrics.cellSize.y;
|
||||
const auto bottom = prcDirtyClient->bottom / _api.fontMetrics.cellSize.y;
|
||||
|
||||
// BeginPaint() protects against invalid out of bounds numbers.
|
||||
SMALL_RECT rect;
|
||||
rect.Top = gsl::narrow_cast<SHORT>(top);
|
||||
rect.Bottom = gsl::narrow_cast<SHORT>(bottom);
|
||||
return Invalidate(&rect);
|
||||
}
|
||||
|
||||
[[nodiscard]] HRESULT AtlasEngine::InvalidateSelection(const std::vector<SMALL_RECT>& rectangles) noexcept
|
||||
{
|
||||
for (const auto& rect : rectangles)
|
||||
{
|
||||
// BeginPaint() protects against invalid out of bounds numbers.
|
||||
// TODO: rect can contain invalid out of bounds coordinates when the selection is being
|
||||
// dragged outside of the viewport (and the window begins scrolling automatically).
|
||||
_api.invalidatedRows.x = gsl::narrow_cast<u16>(std::min<int>(_api.invalidatedRows.x, std::max<int>(0, rect.Top)));
|
||||
_api.invalidatedRows.y = gsl::narrow_cast<u16>(std::max<int>(_api.invalidatedRows.y, std::max<int>(0, rect.Bottom)));
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
[[nodiscard]] HRESULT AtlasEngine::InvalidateScroll(const COORD* const pcoordDelta) noexcept
|
||||
{
|
||||
const auto delta = pcoordDelta->Y;
|
||||
if (delta == 0)
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
_api.scrollOffset = gsl::narrow_cast<i16>(clamp<int>(_api.scrollOffset + delta, i16min, i16max));
|
||||
|
||||
// InvalidateScroll() is a "synchronous" API. Any Invalidate()s after
|
||||
// a InvalidateScroll() refer to the new viewport after the scroll.
|
||||
// --> We need to shift the current invalidation rectangles as well.
|
||||
|
||||
_api.invalidatedCursorArea.top = gsl::narrow_cast<u16>(clamp<int>(_api.invalidatedCursorArea.top + delta, u16min, u16max));
|
||||
_api.invalidatedCursorArea.bottom = gsl::narrow_cast<u16>(clamp<int>(_api.invalidatedCursorArea.bottom + delta, u16min, u16max));
|
||||
|
||||
if (delta < 0)
|
||||
{
|
||||
_api.invalidatedRows.x = gsl::narrow_cast<u16>(clamp<int>(_api.invalidatedRows.x + delta, u16min, u16max));
|
||||
_api.invalidatedRows.y = _api.cellCount.y;
|
||||
}
|
||||
else
|
||||
{
|
||||
_api.invalidatedRows.x = 0;
|
||||
_api.invalidatedRows.y = gsl::narrow_cast<u16>(clamp<int>(_api.invalidatedRows.y + delta, u16min, u16max));
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
[[nodiscard]] HRESULT AtlasEngine::InvalidateAll() noexcept
|
||||
{
|
||||
_api.invalidatedRows = invalidatedRowsAll;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
[[nodiscard]] HRESULT AtlasEngine::InvalidateCircling(_Out_ bool* const pForcePaint) noexcept
|
||||
{
|
||||
RETURN_HR_IF_NULL(E_INVALIDARG, pForcePaint);
|
||||
*pForcePaint = false;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
[[nodiscard]] HRESULT AtlasEngine::InvalidateTitle(const std::wstring_view proposedTitle) noexcept
|
||||
{
|
||||
WI_SetFlag(_api.invalidations, ApiInvalidations::Title);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
[[nodiscard]] HRESULT AtlasEngine::UpdateFont(const FontInfoDesired& fontInfoDesired, _Out_ FontInfo& fontInfo) noexcept
|
||||
{
|
||||
return UpdateFont(fontInfoDesired, fontInfo, {}, {});
|
||||
}
|
||||
|
||||
[[nodiscard]] HRESULT AtlasEngine::UpdateSoftFont(const gsl::span<const uint16_t> bitPattern, const SIZE cellSize, const size_t centeringHint) noexcept
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
[[nodiscard]] HRESULT AtlasEngine::UpdateDpi(const int dpi) noexcept
|
||||
{
|
||||
u16 newDPI;
|
||||
RETURN_IF_FAILED(api_narrow(dpi, newDPI));
|
||||
|
||||
if (_api.dpi != newDPI)
|
||||
{
|
||||
_api.dpi = newDPI;
|
||||
WI_SetFlag(_api.invalidations, ApiInvalidations::Font);
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
[[nodiscard]] HRESULT AtlasEngine::UpdateViewport(const SMALL_RECT srNewViewport) noexcept
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
[[nodiscard]] HRESULT AtlasEngine::GetProposedFont(const FontInfoDesired& fontInfoDesired, _Out_ FontInfo& fontInfo, const int dpi) noexcept
|
||||
try
|
||||
{
|
||||
// One day I'm going to implement GDI for AtlasEngine...
|
||||
// Until then this code is work in progress.
|
||||
#if 0
|
||||
wil::unique_hfont hfont;
|
||||
|
||||
// This block of code (for GDI fonts) is unfinished.
|
||||
if (fontInfoDesired.IsDefaultRasterFont())
|
||||
{
|
||||
hfont.reset(static_cast<HFONT>(GetStockObject(OEM_FIXED_FONT)));
|
||||
RETURN_HR_IF(E_FAIL, !hfont);
|
||||
}
|
||||
else if (requestedFaceName == DEFAULT_RASTER_FONT_FACENAME)
|
||||
{
|
||||
// GDI Windows Font Mapping reference:
|
||||
// https://msdn.microsoft.com/en-us/library/ms969909.aspx
|
||||
|
||||
LOGFONTW lf;
|
||||
lf.lfHeight = -MulDiv(requestedSize.Y, dpi, 72);
|
||||
lf.lfWidth = 0;
|
||||
lf.lfEscapement = 0;
|
||||
lf.lfOrientation = 0;
|
||||
lf.lfWeight = requestedWeight;
|
||||
lf.lfItalic = FALSE;
|
||||
lf.lfUnderline = FALSE;
|
||||
lf.lfStrikeOut = FALSE;
|
||||
lf.lfCharSet = OEM_CHARSET;
|
||||
lf.lfOutPrecision = OUT_RASTER_PRECIS;
|
||||
lf.lfClipPrecision = CLIP_DEFAULT_PRECIS;
|
||||
lf.lfQuality = PROOF_QUALITY; // disables scaling for rasterized fonts
|
||||
lf.lfPitchAndFamily = FIXED_PITCH | FF_MODERN;
|
||||
// .size() only includes regular characters, but we also want to copy the trailing \0, so +1 it is.
|
||||
memcpy(&lf.lfFaceName[0], &DEFAULT_RASTER_FONT_FACENAME[0], sizeof(DEFAULT_RASTER_FONT_FACENAME));
|
||||
|
||||
hfont.reset(CreateFontIndirectW(&lf));
|
||||
RETURN_HR_IF(E_FAIL, !hfont);
|
||||
}
|
||||
|
||||
if (hfont)
|
||||
{
|
||||
// wil::unique_any_t's constructor says: "should not be WI_NOEXCEPT (may forward to a throwing constructor)".
|
||||
// The constructor we use by default doesn't throw.
|
||||
#pragma warning(suppress : 26447) // The function is declared 'noexcept' but calls function '...' which may throw exceptions (f.6).
|
||||
wil::unique_hdc hdc{ CreateCompatibleDC(nullptr) };
|
||||
RETURN_HR_IF(E_FAIL, !hdc);
|
||||
|
||||
DeleteObject(SelectObject(hdc.get(), hfont.get()));
|
||||
|
||||
SIZE sz;
|
||||
RETURN_HR_IF(E_FAIL, !GetTextExtentPoint32W(hdc.get(), L"M", 1, &sz));
|
||||
resultingCellSize.X = gsl::narrow<SHORT>(sz.cx);
|
||||
resultingCellSize.Y = gsl::narrow<SHORT>(sz.cy);
|
||||
}
|
||||
#endif
|
||||
|
||||
_resolveFontMetrics(fontInfoDesired, fontInfo);
|
||||
return S_OK;
|
||||
}
|
||||
CATCH_RETURN()
|
||||
|
||||
[[nodiscard]] HRESULT AtlasEngine::GetDirtyArea(gsl::span<const til::rectangle>& area) noexcept
|
||||
{
|
||||
area = gsl::span{ &_api.dirtyRect, 1 };
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
[[nodiscard]] HRESULT AtlasEngine::GetFontSize(_Out_ COORD* const pFontSize) noexcept
|
||||
{
|
||||
RETURN_HR_IF_NULL(E_INVALIDARG, pFontSize);
|
||||
pFontSize->X = gsl::narrow_cast<SHORT>(_api.fontMetrics.cellSize.x);
|
||||
pFontSize->Y = gsl::narrow_cast<SHORT>(_api.fontMetrics.cellSize.y);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
[[nodiscard]] HRESULT AtlasEngine::IsGlyphWideByFont(const std::wstring_view glyph, _Out_ bool* const pResult) noexcept
|
||||
{
|
||||
RETURN_HR_IF_NULL(E_INVALIDARG, pResult);
|
||||
|
||||
wil::com_ptr<IDWriteTextLayout> textLayout;
|
||||
RETURN_IF_FAILED(_sr.dwriteFactory->CreateTextLayout(glyph.data(), gsl::narrow_cast<uint32_t>(glyph.size()), _getTextFormat(false, false), FLT_MAX, FLT_MAX, textLayout.addressof()));
|
||||
|
||||
DWRITE_TEXT_METRICS metrics;
|
||||
RETURN_IF_FAILED(textLayout->GetMetrics(&metrics));
|
||||
|
||||
*pResult = static_cast<unsigned int>(std::ceil(metrics.width)) > _api.fontMetrics.cellSize.x;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
[[nodiscard]] HRESULT AtlasEngine::UpdateTitle(const std::wstring_view newTitle) noexcept
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region DxRenderer
|
||||
|
||||
HRESULT AtlasEngine::Enable() noexcept
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool AtlasEngine::GetRetroTerminalEffect() const noexcept
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] float AtlasEngine::GetScaling() const noexcept
|
||||
{
|
||||
return static_cast<float>(_api.dpi) / static_cast<float>(USER_DEFAULT_SCREEN_DPI);
|
||||
}
|
||||
|
||||
[[nodiscard]] HANDLE AtlasEngine::GetSwapChainHandle()
|
||||
{
|
||||
if (WI_IsFlagSet(_api.invalidations, ApiInvalidations::Device))
|
||||
{
|
||||
_createResources();
|
||||
WI_ClearFlag(_api.invalidations, ApiInvalidations::Device);
|
||||
}
|
||||
|
||||
return _api.swapChainHandle.get();
|
||||
}
|
||||
|
||||
[[nodiscard]] Microsoft::Console::Types::Viewport AtlasEngine::GetViewportInCharacters(const Types::Viewport& viewInPixels) const noexcept
|
||||
{
|
||||
assert(_api.fontMetrics.cellSize.x != 0);
|
||||
assert(_api.fontMetrics.cellSize.y != 0);
|
||||
return Types::Viewport::FromDimensions(viewInPixels.Origin(), COORD{ gsl::narrow_cast<short>(viewInPixels.Width() / _api.fontMetrics.cellSize.x), gsl::narrow_cast<short>(viewInPixels.Height() / _api.fontMetrics.cellSize.y) });
|
||||
}
|
||||
|
||||
[[nodiscard]] Microsoft::Console::Types::Viewport AtlasEngine::GetViewportInPixels(const Types::Viewport& viewInCharacters) const noexcept
|
||||
{
|
||||
assert(_api.fontMetrics.cellSize.x != 0);
|
||||
assert(_api.fontMetrics.cellSize.y != 0);
|
||||
return Types::Viewport::FromDimensions(viewInCharacters.Origin(), COORD{ gsl::narrow_cast<short>(viewInCharacters.Width() * _api.fontMetrics.cellSize.x), gsl::narrow_cast<short>(viewInCharacters.Height() * _api.fontMetrics.cellSize.y) });
|
||||
}
|
||||
|
||||
void AtlasEngine::SetAntialiasingMode(const D2D1_TEXT_ANTIALIAS_MODE antialiasingMode) noexcept
|
||||
{
|
||||
const auto mode = gsl::narrow_cast<u16>(antialiasingMode);
|
||||
if (_api.antialiasingMode != mode)
|
||||
{
|
||||
_api.antialiasingMode = mode;
|
||||
WI_SetFlag(_api.invalidations, ApiInvalidations::Font);
|
||||
}
|
||||
}
|
||||
|
||||
void AtlasEngine::SetCallback(std::function<void()> pfn) noexcept
|
||||
{
|
||||
_api.swapChainChangedCallback = std::move(pfn);
|
||||
}
|
||||
|
||||
void AtlasEngine::EnableTransparentBackground(const bool isTransparent) noexcept
|
||||
{
|
||||
const auto mixin = !isTransparent ? 0xff000000 : 0x00000000;
|
||||
if (_api.backgroundOpaqueMixin != mixin)
|
||||
{
|
||||
_api.backgroundOpaqueMixin = mixin;
|
||||
WI_SetFlag(_api.invalidations, ApiInvalidations::SwapChain);
|
||||
}
|
||||
}
|
||||
|
||||
void AtlasEngine::SetForceFullRepaintRendering(bool enable) noexcept
|
||||
{
|
||||
}
|
||||
|
||||
[[nodiscard]] HRESULT AtlasEngine::SetHwnd(const HWND hwnd) noexcept
|
||||
{
|
||||
if (_api.hwnd != hwnd)
|
||||
{
|
||||
_api.hwnd = hwnd;
|
||||
WI_SetFlag(_api.invalidations, ApiInvalidations::SwapChain);
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
void AtlasEngine::SetPixelShaderPath(std::wstring_view value) noexcept
|
||||
{
|
||||
}
|
||||
|
||||
void AtlasEngine::SetRetroTerminalEffect(bool enable) noexcept
|
||||
{
|
||||
}
|
||||
|
||||
void AtlasEngine::SetSelectionBackground(const COLORREF color, const float alpha) noexcept
|
||||
{
|
||||
const u32 selectionColor = (color & 0xffffff) | gsl::narrow_cast<u32>(std::lroundf(alpha * 255.0f)) << 24;
|
||||
if (_api.selectionColor != selectionColor)
|
||||
{
|
||||
_api.selectionColor = selectionColor;
|
||||
WI_SetFlag(_api.invalidations, ApiInvalidations::Settings);
|
||||
}
|
||||
}
|
||||
|
||||
void AtlasEngine::SetSoftwareRendering(bool enable) noexcept
|
||||
{
|
||||
}
|
||||
|
||||
void AtlasEngine::SetIntenseIsBold(bool enable) noexcept
|
||||
{
|
||||
}
|
||||
|
||||
void AtlasEngine::SetWarningCallback(std::function<void(HRESULT)> pfn) noexcept
|
||||
{
|
||||
_api.warningCallback = std::move(pfn);
|
||||
}
|
||||
|
||||
[[nodiscard]] HRESULT AtlasEngine::SetWindowSize(const SIZE pixels) noexcept
|
||||
{
|
||||
u16x2 newSize;
|
||||
RETURN_IF_FAILED(vec2_narrow(pixels.cx, pixels.cy, newSize));
|
||||
|
||||
// At the time of writing:
|
||||
// When Win+D is pressed, `TriggerRedrawCursor` is called and a render pass is initiated.
|
||||
// As conhost is in the background, GetClientRect will return {0,0} and we'll get called with {0,0}.
|
||||
// This isn't a valid value for _api.sizeInPixel and would crash _recreateSizeDependentResources().
|
||||
if (_api.sizeInPixel != newSize && newSize != u16x2{})
|
||||
{
|
||||
_api.sizeInPixel = newSize;
|
||||
_api.cellCount = _api.sizeInPixel / _api.fontMetrics.cellSize;
|
||||
WI_SetFlag(_api.invalidations, ApiInvalidations::Size);
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
void AtlasEngine::ToggleShaderEffects() noexcept
|
||||
{
|
||||
}
|
||||
|
||||
[[nodiscard]] HRESULT AtlasEngine::UpdateFont(const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, const std::unordered_map<std::wstring_view, uint32_t>& features, const std::unordered_map<std::wstring_view, float>& axes) noexcept
|
||||
try
|
||||
{
|
||||
std::vector<DWRITE_FONT_FEATURE> fontFeatures;
|
||||
if (!features.empty())
|
||||
{
|
||||
fontFeatures.reserve(features.size() + 3);
|
||||
|
||||
// All of these features are enabled by default by DirectWrite.
|
||||
// If you want to (and can) peek into the source of DirectWrite
|
||||
// you can look for the "GenericDefaultGsubFeatures" and "GenericDefaultGposFeatures" arrays.
|
||||
// Gsub is for GetGlyphs() and Gpos for GetGlyphPlacements().
|
||||
//
|
||||
// GH#10774: Apparently specifying all of the features is just redundant.
|
||||
fontFeatures.emplace_back(DWRITE_FONT_FEATURE{ DWRITE_FONT_FEATURE_TAG_STANDARD_LIGATURES, 1 });
|
||||
fontFeatures.emplace_back(DWRITE_FONT_FEATURE{ DWRITE_FONT_FEATURE_TAG_CONTEXTUAL_LIGATURES, 1 });
|
||||
fontFeatures.emplace_back(DWRITE_FONT_FEATURE{ DWRITE_FONT_FEATURE_TAG_CONTEXTUAL_ALTERNATES, 1 });
|
||||
|
||||
for (const auto& p : features)
|
||||
{
|
||||
if (p.first.size() == 4)
|
||||
{
|
||||
const auto s = p.first.data();
|
||||
switch (const auto tag = DWRITE_MAKE_FONT_FEATURE_TAG(s[0], s[1], s[2], s[3]))
|
||||
{
|
||||
case DWRITE_FONT_FEATURE_TAG_STANDARD_LIGATURES:
|
||||
fontFeatures[0].parameter = p.second;
|
||||
break;
|
||||
case DWRITE_FONT_FEATURE_TAG_CONTEXTUAL_LIGATURES:
|
||||
fontFeatures[1].parameter = p.second;
|
||||
break;
|
||||
case DWRITE_FONT_FEATURE_TAG_CONTEXTUAL_ALTERNATES:
|
||||
fontFeatures[2].parameter = p.second;
|
||||
break;
|
||||
default:
|
||||
fontFeatures.emplace_back(DWRITE_FONT_FEATURE{ tag, p.second });
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<DWRITE_FONT_AXIS_VALUE> fontAxisValues;
|
||||
if (!axes.empty())
|
||||
{
|
||||
fontAxisValues.reserve(axes.size() + 3);
|
||||
|
||||
// AtlasEngine::_recreateFontDependentResources() relies on these fields to
|
||||
// exist in this particular order in order to create appropriate default axes.
|
||||
fontAxisValues.emplace_back(DWRITE_FONT_AXIS_VALUE{ DWRITE_FONT_AXIS_TAG_WEIGHT, -1.0f });
|
||||
fontAxisValues.emplace_back(DWRITE_FONT_AXIS_VALUE{ DWRITE_FONT_AXIS_TAG_ITALIC, -1.0f });
|
||||
fontAxisValues.emplace_back(DWRITE_FONT_AXIS_VALUE{ DWRITE_FONT_AXIS_TAG_SLANT, -1.0f });
|
||||
|
||||
for (const auto& p : axes)
|
||||
{
|
||||
if (p.first.size() == 4)
|
||||
{
|
||||
const auto s = p.first.data();
|
||||
switch (const auto tag = DWRITE_MAKE_FONT_AXIS_TAG(s[0], s[1], s[2], s[3]))
|
||||
{
|
||||
case DWRITE_FONT_AXIS_TAG_WEIGHT:
|
||||
fontAxisValues[0].value = p.second;
|
||||
break;
|
||||
case DWRITE_FONT_AXIS_TAG_ITALIC:
|
||||
fontAxisValues[1].value = p.second;
|
||||
break;
|
||||
case DWRITE_FONT_AXIS_TAG_SLANT:
|
||||
fontAxisValues[2].value = p.second;
|
||||
break;
|
||||
default:
|
||||
fontAxisValues.emplace_back(DWRITE_FONT_AXIS_VALUE{ tag, p.second });
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const auto previousCellSize = _api.fontMetrics.cellSize;
|
||||
_resolveFontMetrics(fontInfoDesired, fontInfo, &_api.fontMetrics);
|
||||
_api.fontFeatures = std::move(fontFeatures);
|
||||
_api.fontAxisValues = std::move(fontAxisValues);
|
||||
|
||||
WI_SetFlag(_api.invalidations, ApiInvalidations::Font);
|
||||
|
||||
if (previousCellSize != _api.fontMetrics.cellSize)
|
||||
{
|
||||
_api.cellCount = _api.sizeInPixel / _api.fontMetrics.cellSize;
|
||||
WI_SetFlag(_api.invalidations, ApiInvalidations::Size);
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
CATCH_RETURN()
|
||||
|
||||
void AtlasEngine::UpdateHyperlinkHoveredId(const uint16_t hoveredId) noexcept
|
||||
{
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
void AtlasEngine::_resolveFontMetrics(const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, FontMetrics* fontMetrics) const
|
||||
{
|
||||
auto requestedFaceName = fontInfoDesired.GetFaceName().c_str();
|
||||
const auto requestedFamily = fontInfoDesired.GetFamily();
|
||||
auto requestedWeight = fontInfoDesired.GetWeight();
|
||||
auto requestedSize = fontInfoDesired.GetEngineSize();
|
||||
|
||||
if (!requestedFaceName)
|
||||
{
|
||||
requestedFaceName = L"Consolas";
|
||||
}
|
||||
if (!requestedSize.Y)
|
||||
{
|
||||
requestedSize = { 0, 12 };
|
||||
}
|
||||
if (!requestedWeight)
|
||||
{
|
||||
requestedWeight = DWRITE_FONT_WEIGHT_NORMAL;
|
||||
}
|
||||
|
||||
wil::com_ptr<IDWriteFontCollection> systemFontCollection;
|
||||
THROW_IF_FAILED(_sr.dwriteFactory->GetSystemFontCollection(systemFontCollection.addressof(), false));
|
||||
|
||||
u32 index = 0;
|
||||
BOOL exists = false;
|
||||
THROW_IF_FAILED(systemFontCollection->FindFamilyName(requestedFaceName, &index, &exists));
|
||||
THROW_HR_IF(DWRITE_E_NOFONT, !exists);
|
||||
|
||||
wil::com_ptr<IDWriteFontFamily> fontFamily;
|
||||
THROW_IF_FAILED(systemFontCollection->GetFontFamily(index, fontFamily.addressof()));
|
||||
|
||||
wil::com_ptr<IDWriteFont> font;
|
||||
THROW_IF_FAILED(fontFamily->GetFirstMatchingFont(static_cast<DWRITE_FONT_WEIGHT>(requestedWeight), DWRITE_FONT_STRETCH_NORMAL, DWRITE_FONT_STYLE_NORMAL, font.addressof()));
|
||||
|
||||
wil::com_ptr<IDWriteFontFace> fontFace;
|
||||
THROW_IF_FAILED(font->CreateFontFace(fontFace.addressof()));
|
||||
|
||||
DWRITE_FONT_METRICS metrics;
|
||||
fontFace->GetMetrics(&metrics);
|
||||
|
||||
// According to Wikipedia:
|
||||
// > One em was traditionally defined as the width of the capital 'M' in the current typeface and point size,
|
||||
// > because the 'M' was commonly cast the full-width of the square blocks [...] which are used in printing presses.
|
||||
// Even today M is often the widest character in a font that supports ASCII.
|
||||
// In the future a more robust solution could be written, until then this simple solution works for most cases.
|
||||
static constexpr u32 codePoint = L'M';
|
||||
u16 glyphIndex;
|
||||
THROW_IF_FAILED(fontFace->GetGlyphIndicesW(&codePoint, 1, &glyphIndex));
|
||||
|
||||
DWRITE_GLYPH_METRICS glyphMetrics;
|
||||
THROW_IF_FAILED(fontFace->GetDesignGlyphMetrics(&glyphIndex, 1, &glyphMetrics));
|
||||
|
||||
// Point sizes are commonly treated at a 72 DPI scale
|
||||
// (including by OpenType), whereas DirectWrite uses 96 DPI.
|
||||
// Since we want the height in px we multiply by the display's DPI.
|
||||
const auto fontSizeInPx = std::ceil(requestedSize.Y / 72.0 * _api.dpi);
|
||||
|
||||
const auto designUnitsPerPx = fontSizeInPx / static_cast<double>(metrics.designUnitsPerEm);
|
||||
const auto ascentInPx = static_cast<double>(metrics.ascent) * designUnitsPerPx;
|
||||
const auto descentInPx = static_cast<double>(metrics.descent) * designUnitsPerPx;
|
||||
const auto lineGapInPx = static_cast<double>(metrics.lineGap) * designUnitsPerPx;
|
||||
const auto advanceWidthInPx = static_cast<double>(glyphMetrics.advanceWidth) * designUnitsPerPx;
|
||||
|
||||
const auto halfGapInPx = lineGapInPx / 2.0;
|
||||
const auto baseline = std::ceil(ascentInPx + halfGapInPx);
|
||||
const auto cellWidth = gsl::narrow<u16>(std::ceil(advanceWidthInPx));
|
||||
const auto cellHeight = gsl::narrow<u16>(std::ceil(baseline + descentInPx + halfGapInPx));
|
||||
|
||||
{
|
||||
COORD resultingCellSize;
|
||||
resultingCellSize.X = gsl::narrow<SHORT>(cellWidth);
|
||||
resultingCellSize.Y = gsl::narrow<SHORT>(cellHeight);
|
||||
fontInfo.SetFromEngine(requestedFaceName, requestedFamily, requestedWeight, false, resultingCellSize, requestedSize);
|
||||
}
|
||||
|
||||
if (fontMetrics)
|
||||
{
|
||||
const auto underlineOffsetInPx = static_cast<double>(-metrics.underlinePosition) * designUnitsPerPx;
|
||||
const auto underlineThicknessInPx = static_cast<double>(metrics.underlineThickness) * designUnitsPerPx;
|
||||
const auto strikethroughOffsetInPx = static_cast<double>(-metrics.strikethroughPosition) * designUnitsPerPx;
|
||||
const auto strikethroughThicknessInPx = static_cast<double>(metrics.strikethroughThickness) * designUnitsPerPx;
|
||||
const auto lineThickness = gsl::narrow<u16>(std::round(std::min(underlineThicknessInPx, strikethroughThicknessInPx)));
|
||||
const auto underlinePos = gsl::narrow<u16>(std::round(baseline + underlineOffsetInPx - lineThickness / 2.0));
|
||||
const auto strikethroughPos = gsl::narrow<u16>(std::round(baseline + strikethroughOffsetInPx - lineThickness / 2.0));
|
||||
|
||||
auto fontName = wil::make_process_heap_string(requestedFaceName);
|
||||
const auto fontWeight = gsl::narrow<u16>(requestedWeight);
|
||||
|
||||
// NOTE: From this point onward no early returns or throwing code should exist,
|
||||
// as we might cause _api to be in an inconsistent state otherwise.
|
||||
|
||||
fontMetrics->fontName = std::move(fontName);
|
||||
fontMetrics->fontSizeInDIP = static_cast<float>(fontSizeInPx / static_cast<double>(_api.dpi) * 96.0);
|
||||
fontMetrics->baselineInDIP = static_cast<float>(baseline / static_cast<double>(_api.dpi) * 96.0);
|
||||
fontMetrics->cellSize = { cellWidth, cellHeight };
|
||||
fontMetrics->fontWeight = fontWeight;
|
||||
fontMetrics->underlinePos = underlinePos;
|
||||
fontMetrics->strikethroughPos = strikethroughPos;
|
||||
fontMetrics->lineThickness = lineThickness;
|
||||
}
|
||||
}
|
1458
src/renderer/atlas/AtlasEngine.cpp
Normal file
1458
src/renderer/atlas/AtlasEngine.cpp
Normal file
File diff suppressed because it is too large
Load diff
761
src/renderer/atlas/AtlasEngine.h
Normal file
761
src/renderer/atlas/AtlasEngine.h
Normal file
|
@ -0,0 +1,761 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <d2d1.h>
|
||||
#include <d3d11_1.h>
|
||||
#include <dwrite_3.h>
|
||||
|
||||
#include "../../renderer/inc/IRenderEngine.hpp"
|
||||
|
||||
namespace Microsoft::Console::Render
|
||||
{
|
||||
class AtlasEngine final : public IRenderEngine
|
||||
{
|
||||
public:
|
||||
explicit AtlasEngine();
|
||||
|
||||
AtlasEngine(const AtlasEngine&) = delete;
|
||||
AtlasEngine& operator=(const AtlasEngine&) = delete;
|
||||
|
||||
// IRenderEngine
|
||||
[[nodiscard]] HRESULT StartPaint() noexcept override;
|
||||
[[nodiscard]] HRESULT EndPaint() noexcept override;
|
||||
[[nodiscard]] bool RequiresContinuousRedraw() noexcept override;
|
||||
void WaitUntilCanRender() noexcept override;
|
||||
[[nodiscard]] HRESULT Present() noexcept override;
|
||||
[[nodiscard]] HRESULT PrepareForTeardown(_Out_ bool* pForcePaint) noexcept override;
|
||||
[[nodiscard]] HRESULT ScrollFrame() noexcept override;
|
||||
[[nodiscard]] HRESULT Invalidate(const SMALL_RECT* psrRegion) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateCursor(const SMALL_RECT* psrRegion) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateSystem(const RECT* prcDirtyClient) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateSelection(const std::vector<SMALL_RECT>& rectangles) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateScroll(const COORD* pcoordDelta) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateAll() noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateCircling(_Out_ bool* pForcePaint) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateTitle(std::wstring_view proposedTitle) noexcept override;
|
||||
[[nodiscard]] HRESULT PrepareRenderInfo(const RenderFrameInfo& info) noexcept override;
|
||||
[[nodiscard]] HRESULT ResetLineTransform() noexcept override;
|
||||
[[nodiscard]] HRESULT PrepareLineTransform(LineRendition lineRendition, size_t targetRow, size_t viewportLeft) noexcept override;
|
||||
[[nodiscard]] HRESULT PaintBackground() noexcept override;
|
||||
[[nodiscard]] HRESULT PaintBufferLine(gsl::span<const Cluster> clusters, COORD coord, bool fTrimLeft, bool lineWrapped) noexcept override;
|
||||
[[nodiscard]] HRESULT PaintBufferGridLines(GridLineSet lines, COLORREF color, size_t cchLine, COORD coordTarget) noexcept override;
|
||||
[[nodiscard]] HRESULT PaintSelection(SMALL_RECT rect) noexcept override;
|
||||
[[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override;
|
||||
[[nodiscard]] HRESULT UpdateDrawingBrushes(const TextAttribute& textAttributes, gsl::not_null<IRenderData*> pData, bool usingSoftFont, bool isSettingDefaultBrushes) noexcept override;
|
||||
[[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& FontInfoDesired, _Out_ FontInfo& FontInfo) noexcept override;
|
||||
[[nodiscard]] HRESULT UpdateSoftFont(gsl::span<const uint16_t> bitPattern, SIZE cellSize, size_t centeringHint) noexcept override;
|
||||
[[nodiscard]] HRESULT UpdateDpi(int iDpi) noexcept override;
|
||||
[[nodiscard]] HRESULT UpdateViewport(SMALL_RECT srNewViewport) noexcept override;
|
||||
[[nodiscard]] HRESULT GetProposedFont(const FontInfoDesired& FontInfoDesired, _Out_ FontInfo& FontInfo, int iDpi) noexcept override;
|
||||
[[nodiscard]] HRESULT GetDirtyArea(gsl::span<const til::rectangle>& area) noexcept override;
|
||||
[[nodiscard]] HRESULT GetFontSize(_Out_ COORD* pFontSize) noexcept override;
|
||||
[[nodiscard]] HRESULT IsGlyphWideByFont(std::wstring_view glyph, _Out_ bool* pResult) noexcept override;
|
||||
[[nodiscard]] HRESULT UpdateTitle(std::wstring_view newTitle) noexcept override;
|
||||
|
||||
// DxRenderer - getter
|
||||
HRESULT Enable() noexcept override;
|
||||
[[nodiscard]] bool GetRetroTerminalEffect() const noexcept override;
|
||||
[[nodiscard]] float GetScaling() const noexcept override;
|
||||
[[nodiscard]] HANDLE GetSwapChainHandle() override;
|
||||
[[nodiscard]] Types::Viewport GetViewportInCharacters(const Types::Viewport& viewInPixels) const noexcept override;
|
||||
[[nodiscard]] Types::Viewport GetViewportInPixels(const Types::Viewport& viewInCharacters) const noexcept override;
|
||||
// DxRenderer - setter
|
||||
void SetAntialiasingMode(D2D1_TEXT_ANTIALIAS_MODE antialiasingMode) noexcept override;
|
||||
void SetCallback(std::function<void()> pfn) noexcept override;
|
||||
void EnableTransparentBackground(const bool isTransparent) noexcept override;
|
||||
void SetForceFullRepaintRendering(bool enable) noexcept override;
|
||||
[[nodiscard]] HRESULT SetHwnd(HWND hwnd) noexcept override;
|
||||
void SetPixelShaderPath(std::wstring_view value) noexcept override;
|
||||
void SetRetroTerminalEffect(bool enable) noexcept override;
|
||||
void SetSelectionBackground(COLORREF color, float alpha = 0.5f) noexcept override;
|
||||
void SetSoftwareRendering(bool enable) noexcept override;
|
||||
void SetIntenseIsBold(bool enable) noexcept override;
|
||||
void SetWarningCallback(std::function<void(HRESULT)> pfn) noexcept override;
|
||||
[[nodiscard]] HRESULT SetWindowSize(SIZE pixels) noexcept override;
|
||||
void ToggleShaderEffects() noexcept override;
|
||||
[[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& pfiFontInfoDesired, FontInfo& fiFontInfo, const std::unordered_map<std::wstring_view, uint32_t>& features, const std::unordered_map<std::wstring_view, float>& axes) noexcept override;
|
||||
void UpdateHyperlinkHoveredId(uint16_t hoveredId) noexcept override;
|
||||
|
||||
// Some helper classes for the implementation.
|
||||
// public because I don't want to sprinkle the code with friends.
|
||||
public:
|
||||
#define ATLAS_POD_OPS(type) \
|
||||
constexpr bool operator==(const type& rhs) const noexcept \
|
||||
{ \
|
||||
return __builtin_memcmp(this, &rhs, sizeof(rhs)) == 0; \
|
||||
} \
|
||||
\
|
||||
constexpr bool operator!=(const type& rhs) const noexcept \
|
||||
{ \
|
||||
return __builtin_memcmp(this, &rhs, sizeof(rhs)) != 0; \
|
||||
}
|
||||
|
||||
#define ATLAS_FLAG_OPS(type, underlying) \
|
||||
friend constexpr type operator~(type v) noexcept { return static_cast<type>(~static_cast<underlying>(v)); } \
|
||||
friend constexpr type operator|(type lhs, type rhs) noexcept { return static_cast<type>(static_cast<underlying>(lhs) | static_cast<underlying>(rhs)); } \
|
||||
friend constexpr type operator&(type lhs, type rhs) noexcept { return static_cast<type>(static_cast<underlying>(lhs) & static_cast<underlying>(rhs)); } \
|
||||
friend constexpr void operator|=(type& lhs, type rhs) noexcept { lhs = lhs | rhs; } \
|
||||
friend constexpr void operator&=(type& lhs, type rhs) noexcept { lhs = lhs & rhs; }
|
||||
|
||||
template<typename T>
|
||||
struct vec2
|
||||
{
|
||||
T x{};
|
||||
T y{};
|
||||
|
||||
ATLAS_POD_OPS(vec2)
|
||||
|
||||
constexpr vec2 operator/(const vec2& rhs) noexcept
|
||||
{
|
||||
assert(rhs.x != 0 && rhs.y != 0);
|
||||
return { gsl::narrow_cast<T>(x / rhs.x), gsl::narrow_cast<T>(y / rhs.y) };
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct vec4
|
||||
{
|
||||
T x{};
|
||||
T y{};
|
||||
T z{};
|
||||
T w{};
|
||||
|
||||
ATLAS_POD_OPS(vec4)
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct rect
|
||||
{
|
||||
T left{};
|
||||
T top{};
|
||||
T right{};
|
||||
T bottom{};
|
||||
|
||||
ATLAS_POD_OPS(rect)
|
||||
|
||||
constexpr bool non_empty() noexcept
|
||||
{
|
||||
return (left < right) & (top < bottom);
|
||||
}
|
||||
};
|
||||
|
||||
using u8 = uint8_t;
|
||||
|
||||
using u16 = uint16_t;
|
||||
using u16x2 = vec2<u16>;
|
||||
using u16r = rect<u16>;
|
||||
|
||||
using i16 = int16_t;
|
||||
|
||||
using u32 = uint32_t;
|
||||
using u32x2 = vec2<u32>;
|
||||
|
||||
using i32 = int32_t;
|
||||
|
||||
using f32 = float;
|
||||
using f32x2 = vec2<f32>;
|
||||
using f32x4 = vec4<f32>;
|
||||
|
||||
struct TextAnalyzerResult
|
||||
{
|
||||
u32 textPosition = 0;
|
||||
u32 textLength = 0;
|
||||
|
||||
// These 2 fields represent DWRITE_SCRIPT_ANALYSIS.
|
||||
// Not using DWRITE_SCRIPT_ANALYSIS drops the struct size from 20 down to 12 bytes.
|
||||
u16 script = 0;
|
||||
u8 shapes = 0;
|
||||
|
||||
u8 bidiLevel = 0;
|
||||
};
|
||||
|
||||
private:
|
||||
template<typename T, size_t Alignment = alignof(T)>
|
||||
struct Buffer
|
||||
{
|
||||
constexpr Buffer() noexcept = default;
|
||||
|
||||
explicit Buffer(size_t size) :
|
||||
_data{ allocate(size) },
|
||||
_size{ size }
|
||||
{
|
||||
}
|
||||
|
||||
Buffer(const T* data, size_t size) :
|
||||
_data{ allocate(size) },
|
||||
_size{ size }
|
||||
{
|
||||
static_assert(std::is_trivially_copyable_v<T>);
|
||||
memcpy(_data, data, size * sizeof(T));
|
||||
}
|
||||
|
||||
~Buffer()
|
||||
{
|
||||
deallocate(_data);
|
||||
}
|
||||
|
||||
Buffer(Buffer&& other) noexcept :
|
||||
_data{ std::exchange(other._data, nullptr) },
|
||||
_size{ std::exchange(other._size, 0) }
|
||||
{
|
||||
}
|
||||
|
||||
#pragma warning(suppress : 26432) // If you define or delete any default operation in the type '...', define or delete them all (c.21).
|
||||
Buffer& operator=(Buffer&& other) noexcept
|
||||
{
|
||||
deallocate(_data);
|
||||
_data = std::exchange(other._data, nullptr);
|
||||
_size = std::exchange(other._size, 0);
|
||||
return *this;
|
||||
}
|
||||
|
||||
explicit operator bool() const noexcept
|
||||
{
|
||||
return _data != nullptr;
|
||||
}
|
||||
|
||||
T& operator[](size_t index) noexcept
|
||||
{
|
||||
assert(index < _size);
|
||||
return _data[index];
|
||||
}
|
||||
|
||||
const T& operator[](size_t index) const noexcept
|
||||
{
|
||||
assert(index < _size);
|
||||
return _data[index];
|
||||
}
|
||||
|
||||
T* data() noexcept
|
||||
{
|
||||
return _data;
|
||||
}
|
||||
|
||||
const T* data() const noexcept
|
||||
{
|
||||
return _data;
|
||||
}
|
||||
|
||||
size_t size() const noexcept
|
||||
{
|
||||
return _size;
|
||||
}
|
||||
|
||||
private:
|
||||
// These two functions don't need to use scoped objects or standard allocators,
|
||||
// since this class is in fact an scoped allocator object itself.
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 26402) // Return a scoped object instead of a heap-allocated if it has a move constructor (r.3).
|
||||
#pragma warning(disable : 26409) // Avoid calling new and delete explicitly, use std::make_unique<T> instead (r.11).
|
||||
static T* allocate(size_t size)
|
||||
{
|
||||
if constexpr (Alignment <= __STDCPP_DEFAULT_NEW_ALIGNMENT__)
|
||||
{
|
||||
return static_cast<T*>(::operator new(size * sizeof(T)));
|
||||
}
|
||||
else
|
||||
{
|
||||
return static_cast<T*>(::operator new(size * sizeof(T), static_cast<std::align_val_t>(Alignment)));
|
||||
}
|
||||
}
|
||||
|
||||
static void deallocate(T* data) noexcept
|
||||
{
|
||||
if constexpr (Alignment <= __STDCPP_DEFAULT_NEW_ALIGNMENT__)
|
||||
{
|
||||
::operator delete(data);
|
||||
}
|
||||
else
|
||||
{
|
||||
::operator delete(data, static_cast<std::align_val_t>(Alignment));
|
||||
}
|
||||
}
|
||||
#pragma warning(pop)
|
||||
|
||||
T* _data = nullptr;
|
||||
size_t _size = 0;
|
||||
};
|
||||
|
||||
// This structure works similar to how std::string works:
|
||||
// You can think of a std::string as a structure consisting of:
|
||||
// char* data;
|
||||
// size_t size;
|
||||
// size_t capacity;
|
||||
// where data is some backing memory allocated on the heap.
|
||||
//
|
||||
// But std::string employs an optimization called "small string optimization" (SSO).
|
||||
// To simplify things it could be explained as:
|
||||
// If the string capacity is small, then the characters are stored inside the "data"
|
||||
// pointer and you make sure to set the lowest bit in the pointer one way or another.
|
||||
// Heap allocations are always aligned by at least 4-8 bytes on any platform.
|
||||
// If the address of the "data" pointer is not even you know data is stored inline.
|
||||
template<typename T>
|
||||
union SmallObjectOptimizer
|
||||
{
|
||||
static_assert(std::is_trivially_copyable_v<T>);
|
||||
static_assert(std::has_unique_object_representations_v<T>);
|
||||
|
||||
T* allocated = nullptr;
|
||||
T inlined;
|
||||
|
||||
constexpr SmallObjectOptimizer() = default;
|
||||
|
||||
SmallObjectOptimizer(const SmallObjectOptimizer& other)
|
||||
{
|
||||
const auto otherData = other.data();
|
||||
const auto otherSize = other.size();
|
||||
const auto data = initialize(otherSize);
|
||||
memcpy(data, otherData, otherSize);
|
||||
}
|
||||
|
||||
SmallObjectOptimizer& operator=(const SmallObjectOptimizer& other)
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
delete this;
|
||||
new (this) SmallObjectOptimizer(other);
|
||||
}
|
||||
return &this;
|
||||
}
|
||||
|
||||
SmallObjectOptimizer(SmallObjectOptimizer&& other) noexcept
|
||||
{
|
||||
memcpy(this, &other, std::max(sizeof(allocated), sizeof(inlined)));
|
||||
other.allocated = nullptr;
|
||||
}
|
||||
|
||||
SmallObjectOptimizer& operator=(SmallObjectOptimizer&& other) noexcept
|
||||
{
|
||||
return *new (this) SmallObjectOptimizer(other);
|
||||
}
|
||||
|
||||
~SmallObjectOptimizer()
|
||||
{
|
||||
if (!is_inline())
|
||||
{
|
||||
#pragma warning(suppress : 26408) // Avoid malloc() and free(), prefer the nothrow version of new with delete (r.10).
|
||||
free(allocated);
|
||||
}
|
||||
}
|
||||
|
||||
T* initialize(size_t byteSize)
|
||||
{
|
||||
if (would_inline(byteSize))
|
||||
{
|
||||
return &inlined;
|
||||
}
|
||||
|
||||
#pragma warning(suppress : 26408) // Avoid malloc() and free(), prefer the nothrow version of new with delete (r.10).
|
||||
allocated = THROW_IF_NULL_ALLOC(static_cast<T*>(malloc(byteSize)));
|
||||
return allocated;
|
||||
}
|
||||
|
||||
constexpr bool would_inline(size_t byteSize) const noexcept
|
||||
{
|
||||
return byteSize <= sizeof(T);
|
||||
}
|
||||
|
||||
bool is_inline() const noexcept
|
||||
{
|
||||
return (__builtin_bit_cast(uintptr_t, allocated) & 1) != 0;
|
||||
}
|
||||
|
||||
const T* data() const noexcept
|
||||
{
|
||||
return is_inline() ? &inlined : allocated;
|
||||
}
|
||||
|
||||
size_t size() const noexcept
|
||||
{
|
||||
return is_inline() ? sizeof(inlined) : _msize(allocated);
|
||||
}
|
||||
};
|
||||
|
||||
struct FontMetrics
|
||||
{
|
||||
wil::unique_process_heap_string fontName;
|
||||
float baselineInDIP = 0.0f;
|
||||
float fontSizeInDIP = 0.0f;
|
||||
u16x2 cellSize;
|
||||
u16 fontWeight = 0;
|
||||
u16 underlinePos = 0;
|
||||
u16 strikethroughPos = 0;
|
||||
u16 lineThickness = 0;
|
||||
};
|
||||
|
||||
// These flags are shared with shader_ps.hlsl.
|
||||
// If you change this be sure to copy it over to shader_ps.hlsl.
|
||||
//
|
||||
// clang-format off
|
||||
enum class CellFlags : u32
|
||||
{
|
||||
None = 0x00000000,
|
||||
Inlined = 0x00000001,
|
||||
|
||||
ColoredGlyph = 0x00000002,
|
||||
ThinFont = 0x00000004,
|
||||
|
||||
Cursor = 0x00000008,
|
||||
Selected = 0x00000010,
|
||||
|
||||
BorderLeft = 0x00000020,
|
||||
BorderTop = 0x00000040,
|
||||
BorderRight = 0x00000080,
|
||||
BorderBottom = 0x00000100,
|
||||
Underline = 0x00000200,
|
||||
UnderlineDotted = 0x00000400,
|
||||
UnderlineDouble = 0x00000800,
|
||||
Strikethrough = 0x00001000,
|
||||
};
|
||||
// clang-format on
|
||||
ATLAS_FLAG_OPS(CellFlags, u32)
|
||||
|
||||
// This structure is shared with the GPU shader and needs to follow certain alignment rules.
|
||||
// You can generally assume that only u32 or types of that alignment are allowed.
|
||||
struct Cell
|
||||
{
|
||||
alignas(u32) u16x2 tileIndex;
|
||||
alignas(u32) CellFlags flags = CellFlags::None;
|
||||
u32x2 color;
|
||||
};
|
||||
|
||||
struct AtlasKeyAttributes
|
||||
{
|
||||
u16 inlined : 1;
|
||||
u16 bold : 1;
|
||||
u16 italic : 1;
|
||||
u16 cellCount : 13;
|
||||
|
||||
ATLAS_POD_OPS(AtlasKeyAttributes)
|
||||
};
|
||||
|
||||
struct AtlasKeyData
|
||||
{
|
||||
AtlasKeyAttributes attributes;
|
||||
u16 charCount;
|
||||
wchar_t chars[14];
|
||||
};
|
||||
|
||||
struct AtlasKey
|
||||
{
|
||||
AtlasKey(AtlasKeyAttributes attributes, u16 charCount, const wchar_t* chars)
|
||||
{
|
||||
const auto size = dataSize(charCount);
|
||||
const auto data = _data.initialize(size);
|
||||
attributes.inlined = _data.would_inline(size);
|
||||
data->attributes = attributes;
|
||||
data->charCount = charCount;
|
||||
memcpy(&data->chars[0], chars, static_cast<size_t>(charCount) * sizeof(AtlasKeyData::chars[0]));
|
||||
}
|
||||
|
||||
const AtlasKeyData* data() const noexcept
|
||||
{
|
||||
return _data.data();
|
||||
}
|
||||
|
||||
size_t hash() const noexcept
|
||||
{
|
||||
const auto d = data();
|
||||
#pragma warning(suppress : 26490) // Don't use reinterpret_cast (type.1).
|
||||
return std::_Fnv1a_append_bytes(std::_FNV_offset_basis, reinterpret_cast<const u8*>(d), dataSize(d->charCount));
|
||||
}
|
||||
|
||||
bool operator==(const AtlasKey& rhs) const noexcept
|
||||
{
|
||||
const auto a = data();
|
||||
const auto b = rhs.data();
|
||||
return a->charCount == b->charCount && memcmp(a, b, dataSize(a->charCount)) == 0;
|
||||
}
|
||||
|
||||
private:
|
||||
SmallObjectOptimizer<AtlasKeyData> _data;
|
||||
|
||||
static constexpr size_t dataSize(u16 charCount) noexcept
|
||||
{
|
||||
// This returns the actual byte size of a AtlasKeyData struct for the given charCount.
|
||||
// The `wchar_t chars[2]` is only a buffer for the inlined variant after
|
||||
// all and the actual charCount can be smaller or larger. Due to this we
|
||||
// remove the size of the `chars` array and add it's true length on top.
|
||||
return sizeof(AtlasKeyData) - sizeof(AtlasKeyData::chars) + static_cast<size_t>(charCount) * sizeof(AtlasKeyData::chars[0]);
|
||||
}
|
||||
};
|
||||
|
||||
struct AtlasKeyHasher
|
||||
{
|
||||
size_t operator()(const AtlasKey& key) const noexcept
|
||||
{
|
||||
return key.hash();
|
||||
}
|
||||
};
|
||||
|
||||
struct AtlasValueData
|
||||
{
|
||||
CellFlags flags = CellFlags::None;
|
||||
u16x2 coords[7];
|
||||
};
|
||||
|
||||
struct AtlasValue
|
||||
{
|
||||
constexpr AtlasValue() = default;
|
||||
|
||||
u16x2* initialize(CellFlags flags, u16 cellCount)
|
||||
{
|
||||
const auto size = dataSize(cellCount);
|
||||
const auto data = _data.initialize(size);
|
||||
WI_SetFlagIf(flags, CellFlags::Inlined, _data.would_inline(size));
|
||||
data->flags = flags;
|
||||
return &data->coords[0];
|
||||
}
|
||||
|
||||
const AtlasValueData* data() const noexcept
|
||||
{
|
||||
return _data.data();
|
||||
}
|
||||
|
||||
private:
|
||||
SmallObjectOptimizer<AtlasValueData> _data;
|
||||
|
||||
static constexpr size_t dataSize(u16 coordCount) noexcept
|
||||
{
|
||||
return sizeof(AtlasValueData) - sizeof(AtlasValueData::coords) + static_cast<size_t>(coordCount) * sizeof(AtlasValueData::coords[0]);
|
||||
}
|
||||
};
|
||||
|
||||
struct AtlasQueueItem
|
||||
{
|
||||
const AtlasKey* key;
|
||||
const AtlasValue* value;
|
||||
float scale;
|
||||
};
|
||||
|
||||
struct CachedCursorOptions
|
||||
{
|
||||
u32 cursorColor = INVALID_COLOR;
|
||||
u16 cursorType = gsl::narrow_cast<u16>(CursorType::Legacy);
|
||||
u8 heightPercentage = 20;
|
||||
|
||||
ATLAS_POD_OPS(CachedCursorOptions)
|
||||
};
|
||||
|
||||
struct BufferLineMetadata
|
||||
{
|
||||
u32x2 colors;
|
||||
CellFlags flags = CellFlags::None;
|
||||
};
|
||||
|
||||
// NOTE: D3D constant buffers sizes must be a multiple of 16 bytes.
|
||||
struct alignas(16) ConstBuffer
|
||||
{
|
||||
// WARNING: Modify this carefully after understanding how HLSL struct packing works.
|
||||
// The gist is:
|
||||
// * Minimum alignment is 4 bytes (like `#pragma pack 4`)
|
||||
// * Members cannot straddle 16 byte boundaries
|
||||
// This means a structure like {u32; u32; u32; u32x2} would require
|
||||
// padding so that it is {u32; u32; u32; <4 byte padding>; u32x2}.
|
||||
alignas(sizeof(f32x4)) f32x4 viewport;
|
||||
alignas(sizeof(f32x4)) f32x4 gammaRatios;
|
||||
alignas(sizeof(f32)) f32 grayscaleEnhancedContrast = 0;
|
||||
alignas(sizeof(u32)) u32 cellCountX = 0;
|
||||
alignas(sizeof(u32x2)) u32x2 cellSize;
|
||||
alignas(sizeof(u32x2)) u32x2 underlinePos;
|
||||
alignas(sizeof(u32x2)) u32x2 strikethroughPos;
|
||||
alignas(sizeof(u32)) u32 backgroundColor = 0;
|
||||
alignas(sizeof(u32)) u32 cursorColor = 0;
|
||||
alignas(sizeof(u32)) u32 selectionColor = 0;
|
||||
#pragma warning(suppress : 4324) // 'ConstBuffer': structure was padded due to alignment specifier
|
||||
};
|
||||
|
||||
// Handled in BeginPaint()
|
||||
enum class ApiInvalidations : u8
|
||||
{
|
||||
None = 0,
|
||||
Title = 1 << 0,
|
||||
Device = 1 << 1,
|
||||
SwapChain = 1 << 2,
|
||||
Size = 1 << 3,
|
||||
Font = 1 << 4,
|
||||
Settings = 1 << 5,
|
||||
};
|
||||
ATLAS_FLAG_OPS(ApiInvalidations, u8)
|
||||
|
||||
// Handled in Present()
|
||||
enum class RenderInvalidations : u8
|
||||
{
|
||||
None = 0,
|
||||
Cursor = 1 << 0,
|
||||
ConstBuffer = 1 << 1,
|
||||
};
|
||||
ATLAS_FLAG_OPS(RenderInvalidations, u8)
|
||||
|
||||
// MSVC STL (version 22000) implements std::clamp<T>(T, T, T) in terms of the generic
|
||||
// std::clamp<T, Predicate>(T, T, T, Predicate) with std::less{} as the argument,
|
||||
// which introduces branching. While not perfect, this is still better than std::clamp.
|
||||
template<typename T>
|
||||
static constexpr T clamp(T val, T min, T max)
|
||||
{
|
||||
return std::max(min, std::min(max, val));
|
||||
}
|
||||
|
||||
// AtlasEngine.cpp
|
||||
[[nodiscard]] HRESULT _handleException(const wil::ResultException& exception) noexcept;
|
||||
__declspec(noinline) void _createResources();
|
||||
void _releaseSwapChain();
|
||||
__declspec(noinline) void _createSwapChain();
|
||||
__declspec(noinline) void _recreateSizeDependentResources();
|
||||
__declspec(noinline) void _recreateFontDependentResources();
|
||||
IDWriteTextFormat* _getTextFormat(bool bold, bool italic) const noexcept;
|
||||
const Buffer<DWRITE_FONT_AXIS_VALUE>& _getTextFormatAxis(bool bold, bool italic) const noexcept;
|
||||
Cell* _getCell(u16 x, u16 y) noexcept;
|
||||
void _setCellFlags(SMALL_RECT coords, CellFlags mask, CellFlags bits) noexcept;
|
||||
u16x2 _allocateAtlasTile() noexcept;
|
||||
void _flushBufferLine();
|
||||
void _emplaceGlyph(IDWriteFontFace* fontFace, float scale, size_t bufferPos1, size_t bufferPos2);
|
||||
|
||||
// AtlasEngine.api.cpp
|
||||
void _resolveFontMetrics(const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, FontMetrics* fontMetrics = nullptr) const;
|
||||
|
||||
// AtlasEngine.r.cpp
|
||||
void _setShaderResources() const;
|
||||
static f32x4 _getGammaRatios(float gamma) noexcept;
|
||||
void _updateConstantBuffer() const noexcept;
|
||||
void _adjustAtlasSize();
|
||||
void _reserveScratchpadSize(u16 minWidth);
|
||||
void _processGlyphQueue();
|
||||
void _drawGlyph(const AtlasQueueItem& item) const;
|
||||
void _drawCursor();
|
||||
void _copyScratchpadTile(uint32_t scratchpadIndex, u16x2 target, uint32_t copyFlags = 0) const noexcept;
|
||||
|
||||
static constexpr bool debugGlyphGenerationPerformance = false;
|
||||
static constexpr bool debugGeneralPerformance = false || debugGlyphGenerationPerformance;
|
||||
static constexpr bool continuousRedraw = false || debugGeneralPerformance;
|
||||
|
||||
static constexpr u16 u16min = 0x0000;
|
||||
static constexpr u16 u16max = 0xffff;
|
||||
static constexpr i16 i16min = -0x8000;
|
||||
static constexpr i16 i16max = 0x7fff;
|
||||
static constexpr u16r invalidatedAreaNone = { u16max, u16max, u16min, u16min };
|
||||
static constexpr u16x2 invalidatedRowsNone{ u16max, u16min };
|
||||
static constexpr u16x2 invalidatedRowsAll{ u16min, u16max };
|
||||
|
||||
struct StaticResources
|
||||
{
|
||||
wil::com_ptr<ID2D1Factory> d2dFactory;
|
||||
wil::com_ptr<IDWriteFactory1> dwriteFactory;
|
||||
wil::com_ptr<IDWriteFontFallback> systemFontFallback;
|
||||
wil::com_ptr<IDWriteTextAnalyzer1> textAnalyzer;
|
||||
bool isWindows10OrGreater = true;
|
||||
|
||||
#ifndef NDEBUG
|
||||
wil::unique_folder_change_reader_nothrow sourceCodeWatcher;
|
||||
std::atomic<int64_t> sourceCodeInvalidationTime{ INT64_MAX };
|
||||
#endif
|
||||
} _sr;
|
||||
|
||||
struct Resources
|
||||
{
|
||||
// D3D resources
|
||||
wil::com_ptr<ID3D11Device> device;
|
||||
wil::com_ptr<ID3D11DeviceContext1> deviceContext;
|
||||
wil::com_ptr<IDXGISwapChain1> swapChain;
|
||||
wil::unique_handle frameLatencyWaitableObject;
|
||||
wil::com_ptr<ID3D11RenderTargetView> renderTargetView;
|
||||
wil::com_ptr<ID3D11VertexShader> vertexShader;
|
||||
wil::com_ptr<ID3D11PixelShader> pixelShader;
|
||||
wil::com_ptr<ID3D11Buffer> constantBuffer;
|
||||
wil::com_ptr<ID3D11Buffer> cellBuffer;
|
||||
wil::com_ptr<ID3D11ShaderResourceView> cellView;
|
||||
|
||||
// D2D resources
|
||||
wil::com_ptr<ID3D11Texture2D> atlasBuffer;
|
||||
wil::com_ptr<ID3D11ShaderResourceView> atlasView;
|
||||
wil::com_ptr<ID3D11Texture2D> atlasScratchpad;
|
||||
wil::com_ptr<ID2D1RenderTarget> d2dRenderTarget;
|
||||
wil::com_ptr<ID2D1Brush> brush;
|
||||
wil::com_ptr<IDWriteTextFormat> textFormats[2][2];
|
||||
Buffer<DWRITE_FONT_AXIS_VALUE> textFormatAxes[2][2];
|
||||
wil::com_ptr<IDWriteTypography> typography;
|
||||
|
||||
Buffer<Cell, 32> cells; // invalidated by ApiInvalidations::Size
|
||||
f32x2 cellSizeDIP; // invalidated by ApiInvalidations::Font, caches _api.cellSize but in DIP
|
||||
u16x2 cellSize; // invalidated by ApiInvalidations::Font, caches _api.cellSize
|
||||
u16x2 cellCount; // invalidated by ApiInvalidations::Font|Size, caches _api.cellCount
|
||||
u16 underlinePos = 0;
|
||||
u16 strikethroughPos = 0;
|
||||
u16 lineThickness = 0;
|
||||
u16 dpi = USER_DEFAULT_SCREEN_DPI; // invalidated by ApiInvalidations::Font, caches _api.dpi
|
||||
u16 maxEncounteredCellCount = 0;
|
||||
u16 scratchpadCellWidth = 0;
|
||||
u16x2 atlasSizeInPixelLimit; // invalidated by ApiInvalidations::Font
|
||||
u16x2 atlasSizeInPixel; // invalidated by ApiInvalidations::Font
|
||||
u16x2 atlasPosition;
|
||||
std::unordered_map<AtlasKey, AtlasValue, AtlasKeyHasher> glyphs;
|
||||
std::vector<AtlasQueueItem> glyphQueue;
|
||||
|
||||
f32 gamma = 0;
|
||||
f32 grayscaleEnhancedContrast = 0;
|
||||
u32 backgroundColor = 0xff000000;
|
||||
u32 selectionColor = 0x7fffffff;
|
||||
|
||||
CachedCursorOptions cursorOptions;
|
||||
RenderInvalidations invalidations = RenderInvalidations::None;
|
||||
|
||||
#ifndef NDEBUG
|
||||
// See documentation for IDXGISwapChain2::GetFrameLatencyWaitableObject method:
|
||||
// > For every frame it renders, the app should wait on this handle before starting any rendering operations.
|
||||
// > Note that this requirement includes the first frame the app renders with the swap chain.
|
||||
bool frameLatencyWaitableObjectUsed = false;
|
||||
#endif
|
||||
} _r;
|
||||
|
||||
struct ApiState
|
||||
{
|
||||
// This structure is loosely sorted in chunks from "very often accessed together"
|
||||
// to seldom accessed and/or usually not together.
|
||||
|
||||
std::vector<wchar_t> bufferLine;
|
||||
std::vector<u16> bufferLineColumn;
|
||||
Buffer<BufferLineMetadata> bufferLineMetadata;
|
||||
std::vector<TextAnalyzerResult> analysisResults;
|
||||
Buffer<u16> clusterMap;
|
||||
Buffer<DWRITE_SHAPING_TEXT_PROPERTIES> textProps;
|
||||
Buffer<u16> glyphIndices;
|
||||
Buffer<DWRITE_SHAPING_GLYPH_PROPERTIES> glyphProps;
|
||||
std::vector<DWRITE_FONT_FEATURE> fontFeatures; // changes are flagged as ApiInvalidations::Font|Size
|
||||
std::vector<DWRITE_FONT_AXIS_VALUE> fontAxisValues; // changes are flagged as ApiInvalidations::Font|Size
|
||||
FontMetrics fontMetrics; // changes are flagged as ApiInvalidations::Font|Size
|
||||
|
||||
u16x2 cellCount; // caches `sizeInPixel / cellSize`
|
||||
u16x2 sizeInPixel; // changes are flagged as ApiInvalidations::Size
|
||||
|
||||
// UpdateDrawingBrushes()
|
||||
u32 backgroundOpaqueMixin = 0xff000000; // changes are flagged as ApiInvalidations::Device
|
||||
u32x2 currentColor;
|
||||
AtlasKeyAttributes attributes{};
|
||||
u16 currentRow = 0;
|
||||
CellFlags flags = CellFlags::None;
|
||||
// SetSelectionBackground()
|
||||
u32 selectionColor = 0x7fffffff;
|
||||
|
||||
// dirtyRect is a computed value based on invalidatedRows.
|
||||
til::rectangle dirtyRect;
|
||||
// These "invalidation" fields are reset in EndPaint()
|
||||
u16r invalidatedCursorArea = invalidatedAreaNone;
|
||||
u16x2 invalidatedRows = invalidatedRowsNone; // x is treated as "top" and y as "bottom"
|
||||
i16 scrollOffset = 0;
|
||||
|
||||
std::function<void(HRESULT)> warningCallback;
|
||||
std::function<void()> swapChainChangedCallback;
|
||||
wil::unique_handle swapChainHandle;
|
||||
HWND hwnd = nullptr;
|
||||
u16 dpi = USER_DEFAULT_SCREEN_DPI; // changes are flagged as ApiInvalidations::Font|Size
|
||||
u16 antialiasingMode = D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE; // changes are flagged as ApiInvalidations::Font
|
||||
|
||||
ApiInvalidations invalidations = ApiInvalidations::Device;
|
||||
} _api;
|
||||
|
||||
#undef ATLAS_POD_OPS
|
||||
#undef ATLAS_FLAG_OPS
|
||||
};
|
||||
}
|
460
src/renderer/atlas/AtlasEngine.r.cpp
Normal file
460
src/renderer/atlas/AtlasEngine.r.cpp
Normal file
|
@ -0,0 +1,460 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "AtlasEngine.h"
|
||||
|
||||
// #### NOTE ####
|
||||
// If you see any code in here that contains "_api." you might be seeing a race condition.
|
||||
// The AtlasEngine::Present() method is called on a background thread without any locks,
|
||||
// while any of the API methods (like AtlasEngine::Invalidate) might be called concurrently.
|
||||
// The usage of the _r field is safe as its members are in practice
|
||||
// only ever written to by the caller of Present() (the "Renderer" class).
|
||||
// The _api fields on the other hand are concurrently written to by others.
|
||||
|
||||
#pragma warning(disable : 4100) // '...': unreferenced formal parameter
|
||||
// Disable a bunch of warnings which get in the way of writing performant code.
|
||||
#pragma warning(disable : 26429) // Symbol 'data' is never tested for nullness, it can be marked as not_null (f.23).
|
||||
#pragma warning(disable : 26446) // Prefer to use gsl::at() instead of unchecked subscript operator (bounds.4).
|
||||
#pragma warning(disable : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1).
|
||||
#pragma warning(disable : 26482) // Only index into arrays using constant expressions (bounds.2).
|
||||
|
||||
using namespace Microsoft::Console::Render;
|
||||
|
||||
#pragma region IRenderEngine
|
||||
|
||||
// Present() is called without the console buffer lock being held.
|
||||
// --> Put as much in here as possible.
|
||||
[[nodiscard]] HRESULT AtlasEngine::Present() noexcept
|
||||
try
|
||||
{
|
||||
_adjustAtlasSize();
|
||||
_reserveScratchpadSize(_r.maxEncounteredCellCount);
|
||||
_processGlyphQueue();
|
||||
|
||||
if (WI_IsFlagSet(_r.invalidations, RenderInvalidations::Cursor))
|
||||
{
|
||||
_drawCursor();
|
||||
WI_ClearFlag(_r.invalidations, RenderInvalidations::Cursor);
|
||||
}
|
||||
|
||||
// The values the constant buffer depends on are potentially updated after BeginPaint().
|
||||
if (WI_IsFlagSet(_r.invalidations, RenderInvalidations::ConstBuffer))
|
||||
{
|
||||
_updateConstantBuffer();
|
||||
WI_ClearFlag(_r.invalidations, RenderInvalidations::ConstBuffer);
|
||||
}
|
||||
|
||||
{
|
||||
#pragma warning(suppress : 26494) // Variable 'mapped' is uninitialized. Always initialize an object (type.5).
|
||||
D3D11_MAPPED_SUBRESOURCE mapped;
|
||||
THROW_IF_FAILED(_r.deviceContext->Map(_r.cellBuffer.get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped));
|
||||
assert(mapped.RowPitch >= _r.cells.size() * sizeof(Cell));
|
||||
memcpy(mapped.pData, _r.cells.data(), _r.cells.size() * sizeof(Cell));
|
||||
_r.deviceContext->Unmap(_r.cellBuffer.get(), 0);
|
||||
}
|
||||
|
||||
// After Present calls, the back buffer needs to explicitly be
|
||||
// re-bound to the D3D11 immediate context before it can be used again.
|
||||
_r.deviceContext->OMSetRenderTargets(1, _r.renderTargetView.addressof(), nullptr);
|
||||
_r.deviceContext->Draw(3, 0);
|
||||
|
||||
// See documentation for IDXGISwapChain2::GetFrameLatencyWaitableObject method:
|
||||
// > For every frame it renders, the app should wait on this handle before starting any rendering operations.
|
||||
// > Note that this requirement includes the first frame the app renders with the swap chain.
|
||||
assert(_r.frameLatencyWaitableObjectUsed);
|
||||
|
||||
// > IDXGISwapChain::Present: Partial Presentation (using a dirty rects or scroll) is not supported
|
||||
// > for SwapChains created with DXGI_SWAP_EFFECT_DISCARD or DXGI_SWAP_EFFECT_FLIP_DISCARD.
|
||||
// ---> No need to call IDXGISwapChain1::Present1.
|
||||
// TODO: Would IDXGISwapChain1::Present1 and its dirty rects have benefits for remote desktop?
|
||||
THROW_IF_FAILED(_r.swapChain->Present(1, 0));
|
||||
|
||||
// On some GPUs with tile based deferred rendering (TBDR) architectures, binding
|
||||
// RenderTargets that already have contents in them (from previous rendering) incurs a
|
||||
// cost for having to copy the RenderTarget contents back into tile memory for rendering.
|
||||
//
|
||||
// On Windows 10 with DXGI_SWAP_EFFECT_FLIP_DISCARD we get this for free.
|
||||
if (!_sr.isWindows10OrGreater)
|
||||
{
|
||||
_r.deviceContext->DiscardView(_r.renderTargetView.get());
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
catch (const wil::ResultException& exception)
|
||||
{
|
||||
return _handleException(exception);
|
||||
}
|
||||
CATCH_RETURN()
|
||||
|
||||
#pragma endregion
|
||||
|
||||
void AtlasEngine::_setShaderResources() const
|
||||
{
|
||||
_r.deviceContext->VSSetShader(_r.vertexShader.get(), nullptr, 0);
|
||||
_r.deviceContext->PSSetShader(_r.pixelShader.get(), nullptr, 0);
|
||||
|
||||
// Our vertex shader uses a trick from Bill Bilodeau published in
|
||||
// "Vertex Shader Tricks" at GDC14 to draw a fullscreen triangle
|
||||
// without vertex/index buffers. This prepares our context for this.
|
||||
_r.deviceContext->IASetVertexBuffers(0, 0, nullptr, nullptr, nullptr);
|
||||
_r.deviceContext->IASetIndexBuffer(nullptr, DXGI_FORMAT_UNKNOWN, 0);
|
||||
_r.deviceContext->IASetInputLayout(nullptr);
|
||||
_r.deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
|
||||
|
||||
_r.deviceContext->PSSetConstantBuffers(0, 1, _r.constantBuffer.addressof());
|
||||
|
||||
const std::array resources{ _r.cellView.get(), _r.atlasView.get() };
|
||||
_r.deviceContext->PSSetShaderResources(0, gsl::narrow_cast<UINT>(resources.size()), resources.data());
|
||||
}
|
||||
|
||||
AtlasEngine::f32x4 AtlasEngine::_getGammaRatios(float gamma) noexcept
|
||||
{
|
||||
static constexpr f32x4 gammaIncorrectTargetRatios[13]{
|
||||
{ 0.0000f / 4.f, 0.0000f / 4.f, 0.0000f / 4.f, 0.0000f / 4.f }, // gamma = 1.0
|
||||
{ 0.0166f / 4.f, -0.0807f / 4.f, 0.2227f / 4.f, -0.0751f / 4.f }, // gamma = 1.1
|
||||
{ 0.0350f / 4.f, -0.1760f / 4.f, 0.4325f / 4.f, -0.1370f / 4.f }, // gamma = 1.2
|
||||
{ 0.0543f / 4.f, -0.2821f / 4.f, 0.6302f / 4.f, -0.1876f / 4.f }, // gamma = 1.3
|
||||
{ 0.0739f / 4.f, -0.3963f / 4.f, 0.8167f / 4.f, -0.2287f / 4.f }, // gamma = 1.4
|
||||
{ 0.0933f / 4.f, -0.5161f / 4.f, 0.9926f / 4.f, -0.2616f / 4.f }, // gamma = 1.5
|
||||
{ 0.1121f / 4.f, -0.6395f / 4.f, 1.1588f / 4.f, -0.2877f / 4.f }, // gamma = 1.6
|
||||
{ 0.1300f / 4.f, -0.7649f / 4.f, 1.3159f / 4.f, -0.3080f / 4.f }, // gamma = 1.7
|
||||
{ 0.1469f / 4.f, -0.8911f / 4.f, 1.4644f / 4.f, -0.3234f / 4.f }, // gamma = 1.8
|
||||
{ 0.1627f / 4.f, -1.0170f / 4.f, 1.6051f / 4.f, -0.3347f / 4.f }, // gamma = 1.9
|
||||
{ 0.1773f / 4.f, -1.1420f / 4.f, 1.7385f / 4.f, -0.3426f / 4.f }, // gamma = 2.0
|
||||
{ 0.1908f / 4.f, -1.2652f / 4.f, 1.8650f / 4.f, -0.3476f / 4.f }, // gamma = 2.1
|
||||
{ 0.2031f / 4.f, -1.3864f / 4.f, 1.9851f / 4.f, -0.3501f / 4.f }, // gamma = 2.2
|
||||
};
|
||||
static constexpr auto norm13 = static_cast<float>(static_cast<double>(0x10000) / (255 * 255) * 4);
|
||||
static constexpr auto norm24 = static_cast<float>(static_cast<double>(0x100) / (255) * 4);
|
||||
|
||||
gamma = clamp(gamma, 1.0f, 2.2f);
|
||||
|
||||
const size_t index = gsl::narrow_cast<size_t>(std::round((gamma - 1.0f) / 1.2f * 12.0f));
|
||||
const auto& ratios = gammaIncorrectTargetRatios[index];
|
||||
return { norm13 * ratios.x, norm24 * ratios.y, norm13 * ratios.z, norm24 * ratios.w };
|
||||
}
|
||||
|
||||
void AtlasEngine::_updateConstantBuffer() const noexcept
|
||||
{
|
||||
ConstBuffer data;
|
||||
data.viewport.x = 0;
|
||||
data.viewport.y = 0;
|
||||
data.viewport.z = static_cast<float>(_r.cellCount.x * _r.cellSize.x);
|
||||
data.viewport.w = static_cast<float>(_r.cellCount.y * _r.cellSize.y);
|
||||
data.gammaRatios = _getGammaRatios(_r.gamma);
|
||||
data.grayscaleEnhancedContrast = _r.grayscaleEnhancedContrast;
|
||||
data.cellCountX = _r.cellCount.x;
|
||||
data.cellSize.x = _r.cellSize.x;
|
||||
data.cellSize.y = _r.cellSize.y;
|
||||
data.underlinePos.x = _r.underlinePos;
|
||||
data.underlinePos.y = _r.underlinePos + _r.lineThickness;
|
||||
data.strikethroughPos.x = _r.strikethroughPos;
|
||||
data.strikethroughPos.y = _r.strikethroughPos + _r.lineThickness;
|
||||
data.backgroundColor = _r.backgroundColor;
|
||||
data.cursorColor = _r.cursorOptions.cursorColor;
|
||||
data.selectionColor = _r.selectionColor;
|
||||
#pragma warning(suppress : 26447) // The function is declared 'noexcept' but calls function '...' which may throw exceptions (f.6).
|
||||
_r.deviceContext->UpdateSubresource(_r.constantBuffer.get(), 0, nullptr, &data, 0, 0);
|
||||
}
|
||||
|
||||
void AtlasEngine::_adjustAtlasSize()
|
||||
{
|
||||
if (_r.atlasPosition.y < _r.atlasSizeInPixel.y && _r.atlasPosition.x < _r.atlasSizeInPixel.x)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const u32 limitX = _r.atlasSizeInPixelLimit.x;
|
||||
const u32 limitY = _r.atlasSizeInPixelLimit.y;
|
||||
const u32 posX = _r.atlasPosition.x;
|
||||
const u32 posY = _r.atlasPosition.y;
|
||||
const u32 cellX = _r.cellSize.x;
|
||||
const u32 cellY = _r.cellSize.y;
|
||||
const auto perCellArea = cellX * cellY;
|
||||
|
||||
// The texture atlas is filled like this:
|
||||
// x →
|
||||
// y +--------------+
|
||||
// ↓ |XXXXXXXXXXXXXX|
|
||||
// |XXXXXXXXXXXXXX|
|
||||
// |XXXXX↖ |
|
||||
// | | |
|
||||
// +------|-------+
|
||||
// This is where _r.atlasPosition points at.
|
||||
//
|
||||
// Each X is a glyph texture tile that's occupied.
|
||||
// We can compute the area of pixels consumed by adding the first
|
||||
// two lines of X (rectangular) together with the last line of X.
|
||||
const auto currentArea = posY * limitX + posX * cellY;
|
||||
// minArea reserves enough room for 64 cells in all cases (mainly during startup).
|
||||
const auto minArea = 64 * perCellArea;
|
||||
auto newArea = std::max(minArea, currentArea);
|
||||
|
||||
// I want the texture to grow exponentially similar to std::vector, as this
|
||||
// ensures we don't need to resize the texture again right after having done.
|
||||
// This rounds newArea up to the next power of 2.
|
||||
unsigned long int index;
|
||||
_BitScanReverse(&index, newArea); // newArea can't be 0
|
||||
newArea = u32{ 1 } << (index + 1);
|
||||
|
||||
const auto pixelPerRow = limitX * cellY;
|
||||
// newArea might be just large enough that it spans N full rows of cells and one additional row
|
||||
// just barely. This algorithm rounds up newArea to the _next_ multiple of cellY.
|
||||
const auto wantedHeight = (newArea + pixelPerRow - 1) / pixelPerRow * cellY;
|
||||
// The atlas might either be a N rows of full width (xLimit) or just one
|
||||
// row (where wantedHeight == cellY) that doesn't quite fill it's maximum width yet.
|
||||
const auto wantedWidth = wantedHeight != cellY ? limitX : newArea / perCellArea * cellX;
|
||||
|
||||
// We know that limitX/limitY were u16 originally, and thus it's safe to narrow_cast it back.
|
||||
const auto height = gsl::narrow_cast<u16>(std::min(limitY, wantedHeight));
|
||||
const auto width = gsl::narrow_cast<u16>(std::min(limitX, wantedWidth));
|
||||
|
||||
assert(width != 0);
|
||||
assert(height != 0);
|
||||
|
||||
wil::com_ptr<ID3D11Texture2D> atlasBuffer;
|
||||
wil::com_ptr<ID3D11ShaderResourceView> atlasView;
|
||||
{
|
||||
D3D11_TEXTURE2D_DESC desc{};
|
||||
desc.Width = width;
|
||||
desc.Height = height;
|
||||
desc.MipLevels = 1;
|
||||
desc.ArraySize = 1;
|
||||
desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
|
||||
desc.SampleDesc = { 1, 0 };
|
||||
desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
|
||||
THROW_IF_FAILED(_r.device->CreateTexture2D(&desc, nullptr, atlasBuffer.addressof()));
|
||||
THROW_IF_FAILED(_r.device->CreateShaderResourceView(atlasBuffer.get(), nullptr, atlasView.addressof()));
|
||||
}
|
||||
|
||||
// If a _r.atlasBuffer already existed, we can copy its glyphs
|
||||
// over to the new texture without re-rendering everything.
|
||||
const auto copyFromExisting = _r.atlasSizeInPixel != u16x2{};
|
||||
if (copyFromExisting)
|
||||
{
|
||||
D3D11_BOX box;
|
||||
box.left = 0;
|
||||
box.top = 0;
|
||||
box.front = 0;
|
||||
box.right = _r.atlasSizeInPixel.x;
|
||||
box.bottom = _r.atlasSizeInPixel.y;
|
||||
box.back = 1;
|
||||
_r.deviceContext->CopySubresourceRegion1(atlasBuffer.get(), 0, 0, 0, 0, _r.atlasBuffer.get(), 0, &box, D3D11_COPY_NO_OVERWRITE);
|
||||
}
|
||||
|
||||
_r.atlasSizeInPixel = u16x2{ width, height };
|
||||
_r.atlasBuffer = std::move(atlasBuffer);
|
||||
_r.atlasView = std::move(atlasView);
|
||||
_setShaderResources();
|
||||
|
||||
WI_SetFlagIf(_r.invalidations, RenderInvalidations::Cursor, !copyFromExisting);
|
||||
}
|
||||
|
||||
void AtlasEngine::_reserveScratchpadSize(u16 minWidth)
|
||||
{
|
||||
if (minWidth <= _r.scratchpadCellWidth)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// The new size is the greater of ... cells wide:
|
||||
// * 2
|
||||
// * minWidth
|
||||
// * current size * 1.5
|
||||
const auto newWidth = std::max<UINT>(std::max<UINT>(2, minWidth), _r.scratchpadCellWidth + (_r.scratchpadCellWidth >> 1));
|
||||
|
||||
_r.d2dRenderTarget.reset();
|
||||
_r.atlasScratchpad.reset();
|
||||
|
||||
{
|
||||
D3D11_TEXTURE2D_DESC desc{};
|
||||
desc.Width = _r.cellSize.x * newWidth;
|
||||
desc.Height = _r.cellSize.y;
|
||||
desc.MipLevels = 1;
|
||||
desc.ArraySize = 1;
|
||||
desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
|
||||
desc.SampleDesc = { 1, 0 };
|
||||
desc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET;
|
||||
THROW_IF_FAILED(_r.device->CreateTexture2D(&desc, nullptr, _r.atlasScratchpad.put()));
|
||||
}
|
||||
{
|
||||
const auto surface = _r.atlasScratchpad.query<IDXGISurface>();
|
||||
|
||||
wil::com_ptr<IDWriteRenderingParams1> defaultParams;
|
||||
THROW_IF_FAILED(_sr.dwriteFactory->CreateRenderingParams(reinterpret_cast<IDWriteRenderingParams**>(defaultParams.addressof())));
|
||||
wil::com_ptr<IDWriteRenderingParams1> renderingParams;
|
||||
THROW_IF_FAILED(_sr.dwriteFactory->CreateCustomRenderingParams(1.0f, 0.0f, 0.0f, defaultParams->GetClearTypeLevel(), defaultParams->GetPixelGeometry(), defaultParams->GetRenderingMode(), renderingParams.addressof()));
|
||||
|
||||
_r.gamma = defaultParams->GetGamma();
|
||||
_r.grayscaleEnhancedContrast = defaultParams->GetGrayscaleEnhancedContrast();
|
||||
|
||||
D2D1_RENDER_TARGET_PROPERTIES props{};
|
||||
props.type = D2D1_RENDER_TARGET_TYPE_DEFAULT;
|
||||
props.pixelFormat = { DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED };
|
||||
props.dpiX = static_cast<float>(_r.dpi);
|
||||
props.dpiY = static_cast<float>(_r.dpi);
|
||||
THROW_IF_FAILED(_sr.d2dFactory->CreateDxgiSurfaceRenderTarget(surface.get(), &props, _r.d2dRenderTarget.put()));
|
||||
|
||||
// We don't really use D2D for anything except DWrite, but it
|
||||
// can't hurt to ensure that everything it does is pixel aligned.
|
||||
_r.d2dRenderTarget->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED);
|
||||
// Ensure that D2D uses the exact same gamma as our shader uses.
|
||||
_r.d2dRenderTarget->SetTextRenderingParams(renderingParams.get());
|
||||
// We can't set the antialiasingMode here, as D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE
|
||||
// will force the alpha channel to be 0 for _all_ text.
|
||||
//_r.d2dRenderTarget->SetTextAntialiasMode(static_cast<D2D1_TEXT_ANTIALIAS_MODE>(_api.antialiasingMode));
|
||||
}
|
||||
{
|
||||
static constexpr D2D1_COLOR_F color{ 1, 1, 1, 1 };
|
||||
wil::com_ptr<ID2D1SolidColorBrush> brush;
|
||||
THROW_IF_FAILED(_r.d2dRenderTarget->CreateSolidColorBrush(&color, nullptr, brush.addressof()));
|
||||
_r.brush = brush.query<ID2D1Brush>();
|
||||
}
|
||||
|
||||
_r.scratchpadCellWidth = _r.maxEncounteredCellCount;
|
||||
WI_SetAllFlags(_r.invalidations, RenderInvalidations::ConstBuffer);
|
||||
}
|
||||
|
||||
void AtlasEngine::_processGlyphQueue()
|
||||
{
|
||||
if (_r.glyphQueue.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& pair : _r.glyphQueue)
|
||||
{
|
||||
_drawGlyph(pair);
|
||||
}
|
||||
|
||||
_r.glyphQueue.clear();
|
||||
}
|
||||
|
||||
void AtlasEngine::_drawGlyph(const AtlasQueueItem& item) const
|
||||
{
|
||||
const auto key = item.key->data();
|
||||
const auto value = item.value->data();
|
||||
const auto coords = &value->coords[0];
|
||||
const auto charsLength = key->charCount;
|
||||
const auto cells = static_cast<u32>(key->attributes.cellCount);
|
||||
const auto textFormat = _getTextFormat(key->attributes.bold, key->attributes.italic);
|
||||
|
||||
// See D2DFactory::DrawText
|
||||
wil::com_ptr<IDWriteTextLayout> textLayout;
|
||||
THROW_IF_FAILED(_sr.dwriteFactory->CreateTextLayout(&key->chars[0], charsLength, textFormat, cells * _r.cellSizeDIP.x, _r.cellSizeDIP.y, textLayout.addressof()));
|
||||
if (item.scale != 1.0f)
|
||||
{
|
||||
const auto f = textFormat->GetFontSize();
|
||||
textLayout->SetFontSize(f * item.scale, { 0, charsLength });
|
||||
}
|
||||
if (_r.typography)
|
||||
{
|
||||
textLayout->SetTypography(_r.typography.get(), { 0, charsLength });
|
||||
}
|
||||
|
||||
auto options = D2D1_DRAW_TEXT_OPTIONS_NONE;
|
||||
// D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT enables a bunch of internal machinery
|
||||
// which doesn't have to run if we know we can't use it anyways in the shader.
|
||||
WI_SetFlagIf(options, D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT, WI_IsFlagSet(value->flags, CellFlags::ColoredGlyph));
|
||||
|
||||
_r.d2dRenderTarget->BeginDraw();
|
||||
// We could call
|
||||
// _r.d2dRenderTarget->PushAxisAlignedClip(&rect, D2D1_ANTIALIAS_MODE_ALIASED);
|
||||
// now to reduce the surface that needs to be cleared, but this decreases
|
||||
// performance by 10% (tested using debugGlyphGenerationPerformance).
|
||||
_r.d2dRenderTarget->Clear();
|
||||
_r.d2dRenderTarget->DrawTextLayout({}, textLayout.get(), _r.brush.get(), options);
|
||||
THROW_IF_FAILED(_r.d2dRenderTarget->EndDraw());
|
||||
|
||||
for (uint32_t i = 0; i < cells; ++i)
|
||||
{
|
||||
// Specifying NO_OVERWRITE means that the system can assume that existing references to the surface that
|
||||
// may be in flight on the GPU will not be affected by the update, so the copy can proceed immediately
|
||||
// (avoiding either a batch flush or the system maintaining multiple copies of the resource behind the scenes).
|
||||
//
|
||||
// Since our shader only draws whatever is in the atlas, and since we don't replace glyph tiles that are in use,
|
||||
// we can safely (?) tell the GPU that we don't overwrite parts of our atlas that are in use.
|
||||
_copyScratchpadTile(i, coords[i], D3D11_COPY_NO_OVERWRITE);
|
||||
}
|
||||
}
|
||||
|
||||
void AtlasEngine::_drawCursor()
|
||||
{
|
||||
_reserveScratchpadSize(1);
|
||||
|
||||
// lineWidth is in D2D's DIPs. For instance if we have a 150-200% zoom scale we want to draw a 2px wide line.
|
||||
// At 150% scale lineWidth thus needs to be 1.33333... because at a zoom scale of 1.5 this results in a 2px wide line.
|
||||
const auto lineWidth = std::max(1.0f, static_cast<float>((_r.dpi + USER_DEFAULT_SCREEN_DPI / 2) / USER_DEFAULT_SCREEN_DPI * USER_DEFAULT_SCREEN_DPI) / static_cast<float>(_r.dpi));
|
||||
const auto cursorType = static_cast<CursorType>(_r.cursorOptions.cursorType);
|
||||
D2D1_RECT_F rect;
|
||||
rect.left = 0.0f;
|
||||
rect.top = 0.0f;
|
||||
rect.right = _r.cellSizeDIP.x;
|
||||
rect.bottom = _r.cellSizeDIP.y;
|
||||
|
||||
switch (cursorType)
|
||||
{
|
||||
case CursorType::Legacy:
|
||||
rect.top = _r.cellSizeDIP.y * static_cast<float>(100 - _r.cursorOptions.heightPercentage) / 100.0f;
|
||||
break;
|
||||
case CursorType::VerticalBar:
|
||||
rect.right = lineWidth;
|
||||
break;
|
||||
case CursorType::EmptyBox:
|
||||
{
|
||||
// EmptyBox is drawn as a line and unlike filled rectangles those are drawn centered on their
|
||||
// coordinates in such a way that the line border extends half the width to each side.
|
||||
// --> Our coordinates have to be 0.5 DIP off in order to draw a 2px line on a 200% scaling.
|
||||
const auto halfWidth = lineWidth / 2.0f;
|
||||
rect.left = halfWidth;
|
||||
rect.top = halfWidth;
|
||||
rect.right -= halfWidth;
|
||||
rect.bottom -= halfWidth;
|
||||
break;
|
||||
}
|
||||
case CursorType::Underscore:
|
||||
case CursorType::DoubleUnderscore:
|
||||
rect.top = _r.cellSizeDIP.y - lineWidth;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
_r.d2dRenderTarget->BeginDraw();
|
||||
_r.d2dRenderTarget->Clear();
|
||||
|
||||
if (cursorType == CursorType::EmptyBox)
|
||||
{
|
||||
_r.d2dRenderTarget->DrawRectangle(&rect, _r.brush.get(), lineWidth);
|
||||
}
|
||||
else
|
||||
{
|
||||
_r.d2dRenderTarget->FillRectangle(&rect, _r.brush.get());
|
||||
}
|
||||
|
||||
if (cursorType == CursorType::DoubleUnderscore)
|
||||
{
|
||||
rect.top -= 2.0f;
|
||||
rect.bottom -= 2.0f;
|
||||
_r.d2dRenderTarget->FillRectangle(&rect, _r.brush.get());
|
||||
}
|
||||
|
||||
THROW_IF_FAILED(_r.d2dRenderTarget->EndDraw());
|
||||
|
||||
_copyScratchpadTile(0, {});
|
||||
}
|
||||
|
||||
void AtlasEngine::_copyScratchpadTile(uint32_t scratchpadIndex, u16x2 target, uint32_t copyFlags) const noexcept
|
||||
{
|
||||
D3D11_BOX box;
|
||||
box.left = scratchpadIndex * _r.cellSize.x;
|
||||
box.top = 0;
|
||||
box.front = 0;
|
||||
box.right = box.left + _r.cellSize.x;
|
||||
box.bottom = _r.cellSize.y;
|
||||
box.back = 1;
|
||||
#pragma warning(suppress : 26447) // The function is declared 'noexcept' but calls function '...' which may throw exceptions (f.6).
|
||||
_r.deviceContext->CopySubresourceRegion1(_r.atlasBuffer.get(), 0, target.x, target.y, 0, _r.atlasScratchpad.get(), 0, &box, copyFlags);
|
||||
}
|
55
src/renderer/atlas/atlas.vcxproj
Normal file
55
src/renderer/atlas/atlas.vcxproj
Normal file
|
@ -0,0 +1,55 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<ProjectGuid>{8222900C-8B6C-452A-91AC-BE95DB04B95F}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>atlas</RootNamespace>
|
||||
<ProjectName>RendererAtlas</ProjectName>
|
||||
<TargetName>ConRenderAtlas</TargetName>
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(SolutionDir)src\common.build.pre.props" />
|
||||
<ItemGroup>
|
||||
<ClCompile Include="AtlasEngine.api.cpp" />
|
||||
<ClCompile Include="AtlasEngine.r.cpp" />
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="AtlasEngine.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="AtlasEngine.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<FxCompile Include="shader_ps.hlsl">
|
||||
<ShaderType>Pixel</ShaderType>
|
||||
<ShaderModel>4.1</ShaderModel>
|
||||
<AllResourcesBound>true</AllResourcesBound>
|
||||
<VariableName>shader_ps</VariableName>
|
||||
<ObjectFileOutput />
|
||||
<HeaderFileOutput>$(OutDir)$(ProjectName)\%(Filename).h</HeaderFileOutput>
|
||||
<TreatWarningAsError>true</TreatWarningAsError>
|
||||
<AdditionalOptions>/Zpc %(AdditionalOptions)</AdditionalOptions>
|
||||
<AdditionalOptions Condition="'$(Configuration)'=='Release'">/O3 /Qstrip_debug /Qstrip_reflect %(AdditionalOptions)</AdditionalOptions>
|
||||
</FxCompile>
|
||||
<FxCompile Include="shader_vs.hlsl">
|
||||
<ShaderType>Vertex</ShaderType>
|
||||
<ShaderModel>4.1</ShaderModel>
|
||||
<AllResourcesBound>true</AllResourcesBound>
|
||||
<VariableName>shader_vs</VariableName>
|
||||
<ObjectFileOutput />
|
||||
<HeaderFileOutput>$(OutDir)$(ProjectName)\%(Filename).h</HeaderFileOutput>
|
||||
<TreatWarningAsError>true</TreatWarningAsError>
|
||||
<AdditionalOptions>/Zpc %(AdditionalOptions)</AdditionalOptions>
|
||||
<AdditionalOptions Condition="'$(Configuration)'=='Release'">/O3 /Qstrip_debug /Qstrip_reflect %(AdditionalOptions)</AdditionalOptions>
|
||||
</FxCompile>
|
||||
</ItemGroup>
|
||||
<Import Project="$(SolutionDir)src\common.build.post.props" />
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
<AdditionalIncludeDirectories>$(OutDir)$(ProjectName)\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
</Project>
|
4
src/renderer/atlas/pch.cpp
Normal file
4
src/renderer/atlas/pch.cpp
Normal file
|
@ -0,0 +1,4 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
52
src/renderer/atlas/pch.h
Normal file
52
src/renderer/atlas/pch.h
Normal file
|
@ -0,0 +1,52 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
#define NOMINMAX
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
|
||||
#include <array>
|
||||
#include <iomanip>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include <d2d1.h>
|
||||
#include <d3d11_1.h>
|
||||
#include <d3dcompiler.h>
|
||||
#include <dwrite_3.h>
|
||||
#include <dcomp.h>
|
||||
#include <dxgi1_3.h>
|
||||
#include <dxgidebug.h>
|
||||
#include <VersionHelpers.h>
|
||||
|
||||
#include <gsl/gsl_util>
|
||||
#include <gsl/pointers>
|
||||
#include <gsl/span>
|
||||
#include <wil/com.h>
|
||||
#include <wil/filesystem.h>
|
||||
#include <wil/result_macros.h>
|
||||
#include <wil/stl.h>
|
||||
#include <wil/win32_helpers.h>
|
||||
|
||||
// Dynamic Bitset (optional dependency on LibPopCnt for perf at bit counting)
|
||||
// Variable-size compressed-storage header-only bit flag storage library.
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4702) // unreachable code
|
||||
#include <dynamic_bitset.hpp>
|
||||
#pragma warning(pop)
|
||||
|
||||
// Chromium Numerics (safe math)
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4100) // '...': unreferenced formal parameter
|
||||
#pragma warning(disable : 26812) // The enum type '...' is unscoped. Prefer 'enum class' over 'enum' (Enum.3).
|
||||
#include <base/numerics/safe_math.h>
|
||||
#pragma warning(pop)
|
||||
|
||||
#include <til.h>
|
||||
#include <til/bit.h>
|
182
src/renderer/atlas/shader_ps.hlsl
Normal file
182
src/renderer/atlas/shader_ps.hlsl
Normal file
|
@ -0,0 +1,182 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#define INVALID_COLOR 0xffffffff
|
||||
|
||||
// These flags are shared with AtlasEngine::CellFlags.
|
||||
//
|
||||
// clang-format off
|
||||
#define CellFlags_None 0x00000000
|
||||
#define CellFlags_Inlined 0x00000001
|
||||
|
||||
#define CellFlags_ColoredGlyph 0x00000002
|
||||
#define CellFlags_ThinFont 0x00000004
|
||||
|
||||
#define CellFlags_Cursor 0x00000008
|
||||
#define CellFlags_Selected 0x00000010
|
||||
|
||||
#define CellFlags_BorderLeft 0x00000020
|
||||
#define CellFlags_BorderTop 0x00000040
|
||||
#define CellFlags_BorderRight 0x00000080
|
||||
#define CellFlags_BorderBottom 0x00000100
|
||||
#define CellFlags_Underline 0x00000200
|
||||
#define CellFlags_UnderlineDotted 0x00000400
|
||||
#define CellFlags_UnderlineDouble 0x00000800
|
||||
#define CellFlags_Strikethrough 0x00001000
|
||||
// clang-format on
|
||||
|
||||
// According to Nvidia's "Understanding Structured Buffer Performance" guide
|
||||
// one should aim for structures with sizes divisible by 128 bits (16 bytes).
|
||||
// This prevents elements from spanning cache lines.
|
||||
struct Cell
|
||||
{
|
||||
uint glyphPos;
|
||||
uint flags;
|
||||
uint2 color; // x: foreground, y: background
|
||||
};
|
||||
|
||||
cbuffer ConstBuffer : register(b0)
|
||||
{
|
||||
float4 viewport;
|
||||
float4 gammaRatios;
|
||||
float grayscaleEnhancedContrast;
|
||||
uint cellCountX;
|
||||
uint2 cellSize;
|
||||
uint2 underlinePos;
|
||||
uint2 strikethroughPos;
|
||||
uint backgroundColor;
|
||||
uint cursorColor;
|
||||
uint selectionColor;
|
||||
};
|
||||
StructuredBuffer<Cell> cells : register(t0);
|
||||
Texture2D<float4> glyphs : register(t1);
|
||||
|
||||
float4 decodeRGBA(uint i)
|
||||
{
|
||||
uint r = i & 0xff;
|
||||
uint g = (i >> 8) & 0xff;
|
||||
uint b = (i >> 16) & 0xff;
|
||||
uint a = i >> 24;
|
||||
float4 c = float4(r, g, b, a) / 255.0f;
|
||||
// Convert to premultiplied alpha for simpler alpha blending.
|
||||
c.rgb *= c.a;
|
||||
return c;
|
||||
}
|
||||
|
||||
uint2 decodeU16x2(uint i)
|
||||
{
|
||||
return uint2(i & 0xffff, i >> 16);
|
||||
}
|
||||
|
||||
float4 alphaBlendPremultiplied(float4 bottom, float4 top)
|
||||
{
|
||||
float ia = 1 - top.a;
|
||||
return float4(bottom.rgb * ia + top.rgb, bottom.a * ia + top.a);
|
||||
}
|
||||
|
||||
float applyLightOnDarkContrastAdjustment(float3 color)
|
||||
{
|
||||
float lightness = 0.30f * color.r + 0.59f * color.g + 0.11f * color.b;
|
||||
float multiplier = saturate(4.0f * (0.75f - lightness));
|
||||
return grayscaleEnhancedContrast * multiplier;
|
||||
}
|
||||
|
||||
float calcColorIntensity(float3 color)
|
||||
{
|
||||
return (color.r + color.g + color.g + color.b) / 4.0f;
|
||||
}
|
||||
|
||||
float enhanceContrast(float alpha, float k)
|
||||
{
|
||||
return alpha * (k + 1.0f) / (alpha * k + 1.0f);
|
||||
}
|
||||
|
||||
float applyAlphaCorrection(float a, float f, float4 g)
|
||||
{
|
||||
return a + a * (1 - a) * ((g.x * f + g.y) * a + (g.z * f + g.w));
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
float4 main(float4 pos: SV_Position): SV_Target
|
||||
// clang-format on
|
||||
{
|
||||
if (any(pos.xy < viewport.xy) || any(pos.xy >= viewport.zw))
|
||||
{
|
||||
return decodeRGBA(backgroundColor);
|
||||
}
|
||||
|
||||
// If you want to write test a before/after change simultaneously
|
||||
// you can turn the image into a checkerboard by writing:
|
||||
// if ((uint(pos.x) ^ uint(pos.y)) / 4 & 1) { return float4(1, 0, 0, 1); }
|
||||
// This will generate a checkerboard of 4*4px red squares.
|
||||
// Of course you wouldn't just return a red color there, but instead
|
||||
// for instance run your new code and compare it with the old.
|
||||
|
||||
uint2 viewportPos = pos.xy - viewport.xy;
|
||||
uint2 cellIndex = viewportPos / cellSize;
|
||||
uint2 cellPos = viewportPos % cellSize;
|
||||
Cell cell = cells[cellIndex.y * cellCountX + cellIndex.x];
|
||||
|
||||
// Layer 0:
|
||||
// The cell's background color
|
||||
float4 color = decodeRGBA(cell.color.y);
|
||||
float4 fg = decodeRGBA(cell.color.x);
|
||||
|
||||
// Layer 1 (optional):
|
||||
// Colored cursors are drawn "in between" the background color and the text of a cell.
|
||||
if ((cell.flags & CellFlags_Cursor) && cursorColor != INVALID_COLOR)
|
||||
{
|
||||
// The cursor texture is stored at the top-left-most glyph cell.
|
||||
// Cursor pixels are either entirely transparent or opaque.
|
||||
// --> We can just use .a as a mask to flip cursor pixels on or off.
|
||||
color = alphaBlendPremultiplied(color, decodeRGBA(cursorColor) * glyphs[cellPos].a);
|
||||
}
|
||||
|
||||
// Layer 2:
|
||||
// Step 1: Underlines
|
||||
if ((cell.flags & CellFlags_Underline) && cellPos.y >= underlinePos.x && cellPos.y < underlinePos.y)
|
||||
{
|
||||
color = alphaBlendPremultiplied(color, fg);
|
||||
}
|
||||
if ((cell.flags & CellFlags_UnderlineDotted) && cellPos.y >= underlinePos.x && cellPos.y < underlinePos.y && (viewportPos.x / (underlinePos.y - underlinePos.x) & 1))
|
||||
{
|
||||
color = alphaBlendPremultiplied(color, fg);
|
||||
}
|
||||
// Step 2: The cell's glyph, potentially drawn in the foreground color
|
||||
{
|
||||
float4 glyph = glyphs[decodeU16x2(cell.glyphPos) + cellPos];
|
||||
|
||||
if (!(cell.flags & CellFlags_ColoredGlyph))
|
||||
{
|
||||
float contrastBoost = (cell.flags & CellFlags_ThinFont) == 0 ? 0.0f : 0.5f;
|
||||
float enhancedContrast = contrastBoost + applyLightOnDarkContrastAdjustment(fg.rgb);
|
||||
float intensity = calcColorIntensity(fg.rgb);
|
||||
float contrasted = enhanceContrast(glyph.a, enhancedContrast);
|
||||
float correctedAlpha = applyAlphaCorrection(contrasted, intensity, gammaRatios);
|
||||
glyph = fg * correctedAlpha;
|
||||
}
|
||||
|
||||
color = alphaBlendPremultiplied(color, glyph);
|
||||
}
|
||||
// Step 3: Lines, but not "under"lines
|
||||
if ((cell.flags & CellFlags_Strikethrough) && cellPos.y >= strikethroughPos.x && cellPos.y < strikethroughPos.y)
|
||||
{
|
||||
color = alphaBlendPremultiplied(color, fg);
|
||||
}
|
||||
|
||||
// Layer 3 (optional):
|
||||
// Uncolored cursors invert the cells color.
|
||||
if ((cell.flags & CellFlags_Cursor) && cursorColor == INVALID_COLOR)
|
||||
{
|
||||
color.rgb = abs(glyphs[cellPos].rgb - color.rgb);
|
||||
}
|
||||
|
||||
// Layer 4:
|
||||
// The current selection is drawn semi-transparent on top.
|
||||
if (cell.flags & CellFlags_Selected)
|
||||
{
|
||||
color = alphaBlendPremultiplied(color, decodeRGBA(selectionColor));
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
17
src/renderer/atlas/shader_vs.hlsl
Normal file
17
src/renderer/atlas/shader_vs.hlsl
Normal file
|
@ -0,0 +1,17 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
// clang-format off
|
||||
float4 main(uint id: SV_VERTEXID): SV_POSITION
|
||||
// clang-format on
|
||||
{
|
||||
// The algorithm below is a fast way to generate a full screen triangle,
|
||||
// published by Bill Bilodeau "Vertex Shader Tricks" at GDC14.
|
||||
// It covers the entire viewport and is faster for the GPU than a quad/rectangle.
|
||||
return float4(
|
||||
float(id / 2) * 4.0 - 1.0,
|
||||
float(id % 2) * 4.0 - 1.0,
|
||||
0.0,
|
||||
1.0
|
||||
);
|
||||
}
|
|
@ -74,5 +74,6 @@ HRESULT RenderEngineBase::PrepareLineTransform(const LineRendition /*lineRenditi
|
|||
// - Blocks until the engine is able to render without blocking.
|
||||
void RenderEngineBase::WaitUntilCanRender() noexcept
|
||||
{
|
||||
// do nothing by default
|
||||
// Throttle the render loop a bit by default (~60 FPS), improving throughput.
|
||||
Sleep(8);
|
||||
}
|
||||
|
|
|
@ -213,12 +213,6 @@ DWORD WINAPI RenderThread::_ThreadProc()
|
|||
LOG_IF_FAILED(_pRenderer->PaintFrame());
|
||||
|
||||
SetEvent(_hPaintCompletedEvent);
|
||||
|
||||
// extra check before we sleep since it's a "long" activity, relatively speaking.
|
||||
if (_fKeepRunning)
|
||||
{
|
||||
Sleep(s_FrameLimitMilliseconds);
|
||||
}
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
|
|
|
@ -37,8 +37,6 @@ namespace Microsoft::Console::Render
|
|||
static DWORD WINAPI s_ThreadProc(_In_ LPVOID lpParameter);
|
||||
DWORD WINAPI _ThreadProc();
|
||||
|
||||
static DWORD const s_FrameLimitMilliseconds = 8;
|
||||
|
||||
HANDLE _hThread;
|
||||
HANDLE _hEvent;
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
|
@ -404,9 +404,9 @@ CATCH_RETURN()
|
|||
std::vector<DWRITE_SHAPING_GLYPH_PROPERTIES> glyphProps(maxGlyphCount);
|
||||
|
||||
// Get the features to apply to the font
|
||||
auto features = _fontRenderData->DefaultFontFeatures();
|
||||
DWRITE_FONT_FEATURE* featureList = features.data();
|
||||
DWRITE_TYPOGRAPHIC_FEATURES typographicFeatures = { &featureList[0], gsl::narrow<uint32_t>(features.size()) };
|
||||
const auto& features = _fontRenderData->DefaultFontFeatures();
|
||||
#pragma warning(suppress : 26492) // Don't use const_cast to cast away const or volatile (type.3).
|
||||
DWRITE_TYPOGRAPHIC_FEATURES typographicFeatures = { const_cast<DWRITE_FONT_FEATURE*>(features.data()), gsl::narrow<uint32_t>(features.size()) };
|
||||
DWRITE_TYPOGRAPHIC_FEATURES const* typographicFeaturesPointer = &typographicFeatures;
|
||||
const uint32_t fontFeatureLengths[] = { textLength };
|
||||
|
||||
|
|
|
@ -487,6 +487,7 @@ CATCH_RETURN()
|
|||
{
|
||||
// Color glyph rendering sourced from https://github.com/Microsoft/Windows-universal-samples/tree/master/Samples/DWriteColorGlyph
|
||||
|
||||
#pragma warning(suppress : 26429) // Symbol 'drawingContext' is never tested for nullness, it can be marked as not_null (f.23).
|
||||
DrawingContext* drawingContext = static_cast<DrawingContext*>(clientDrawingContext);
|
||||
|
||||
// Since we've delegated the drawing of the background of the text into this function, the origin passed in isn't actually the baseline.
|
||||
|
|
|
@ -244,10 +244,11 @@ bool DxEngine::_HasTerminalEffects() const noexcept
|
|||
// Arguments:
|
||||
// Return Value:
|
||||
// - Void
|
||||
void DxEngine::ToggleShaderEffects()
|
||||
void DxEngine::ToggleShaderEffects() noexcept
|
||||
{
|
||||
_terminalEffectsEnabled = !_terminalEffectsEnabled;
|
||||
_recreateDeviceRequested = true;
|
||||
#pragma warning(suppress : 26447) // The function is declared 'noexcept' but calls function 'Log_IfFailed()' which may throw exceptions (f.6).
|
||||
LOG_IF_FAILED(InvalidateAll());
|
||||
}
|
||||
|
||||
|
@ -969,14 +970,14 @@ try
|
|||
}
|
||||
CATCH_RETURN();
|
||||
|
||||
void DxEngine::SetCallback(std::function<void()> pfn)
|
||||
void DxEngine::SetCallback(std::function<void()> pfn) noexcept
|
||||
{
|
||||
_pfn = pfn;
|
||||
_pfn = std::move(pfn);
|
||||
}
|
||||
|
||||
void DxEngine::SetWarningCallback(std::function<void(const HRESULT)> pfn)
|
||||
void DxEngine::SetWarningCallback(std::function<void(const HRESULT)> pfn) noexcept
|
||||
{
|
||||
_pfnWarningCallback = pfn;
|
||||
_pfnWarningCallback = std::move(pfn);
|
||||
}
|
||||
|
||||
bool DxEngine::GetRetroTerminalEffect() const noexcept
|
||||
|
@ -1046,11 +1047,12 @@ try
|
|||
}
|
||||
CATCH_LOG()
|
||||
|
||||
HANDLE DxEngine::GetSwapChainHandle()
|
||||
HANDLE DxEngine::GetSwapChainHandle() noexcept
|
||||
{
|
||||
if (!_swapChainHandle)
|
||||
{
|
||||
THROW_IF_FAILED(_CreateDeviceResources(true));
|
||||
#pragma warning(suppress : 26447) // The function is declared 'noexcept' but calls function 'Log_IfFailed()' which may throw exceptions (f.6).
|
||||
LOG_IF_FAILED(_CreateDeviceResources(true));
|
||||
}
|
||||
|
||||
return _swapChainHandle.get();
|
||||
|
@ -1498,18 +1500,13 @@ CATCH_RETURN()
|
|||
// - See https://docs.microsoft.com/en-us/windows/uwp/gaming/reduce-latency-with-dxgi-1-3-swap-chains.
|
||||
void DxEngine::WaitUntilCanRender() noexcept
|
||||
{
|
||||
if (!_swapChainFrameLatencyWaitableObject)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Throttle the DxEngine a bit down to ~60 FPS.
|
||||
// This improves throughput for rendering complex or colored text.
|
||||
Sleep(8);
|
||||
|
||||
const auto ret = WaitForSingleObjectEx(
|
||||
_swapChainFrameLatencyWaitableObject.get(),
|
||||
1000, // 1 second timeout (shouldn't ever occur)
|
||||
true);
|
||||
if (ret != WAIT_OBJECT_0)
|
||||
if (_swapChainFrameLatencyWaitableObject)
|
||||
{
|
||||
LOG_WIN32_MSG(ret, "Waiting for swap chain frame latency waitable object returned error or timeout.");
|
||||
WaitForSingleObjectEx(_swapChainFrameLatencyWaitableObject.get(), 100, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2019,7 +2016,7 @@ try
|
|||
}
|
||||
CATCH_RETURN();
|
||||
|
||||
[[nodiscard]] Viewport DxEngine::GetViewportInCharacters(const Viewport& viewInPixels) noexcept
|
||||
[[nodiscard]] Viewport DxEngine::GetViewportInCharacters(const Viewport& viewInPixels) const noexcept
|
||||
{
|
||||
const short widthInChars = base::saturated_cast<short>(viewInPixels.Width() / _fontRenderData->GlyphCell().width());
|
||||
const short heightInChars = base::saturated_cast<short>(viewInPixels.Height() / _fontRenderData->GlyphCell().height());
|
||||
|
@ -2027,7 +2024,7 @@ CATCH_RETURN();
|
|||
return Viewport::FromDimensions(viewInPixels.Origin(), { widthInChars, heightInChars });
|
||||
}
|
||||
|
||||
[[nodiscard]] Viewport DxEngine::GetViewportInPixels(const Viewport& viewInCharacters) noexcept
|
||||
[[nodiscard]] Viewport DxEngine::GetViewportInPixels(const Viewport& viewInCharacters) const noexcept
|
||||
{
|
||||
const short widthInPixels = base::saturated_cast<short>(viewInCharacters.Width() * _fontRenderData->GlyphCell().width());
|
||||
const short heightInPixels = base::saturated_cast<short>(viewInCharacters.Height() * _fontRenderData->GlyphCell().height());
|
||||
|
|
|
@ -49,28 +49,28 @@ namespace Microsoft::Console::Render
|
|||
// Used to release device resources so that another instance of
|
||||
// conhost can render to the screen (i.e. only one DirectX
|
||||
// application may control the screen at a time.)
|
||||
[[nodiscard]] HRESULT Enable() noexcept;
|
||||
[[nodiscard]] HRESULT Enable() noexcept override;
|
||||
[[nodiscard]] HRESULT Disable() noexcept;
|
||||
|
||||
[[nodiscard]] HRESULT SetHwnd(const HWND hwnd) noexcept;
|
||||
[[nodiscard]] HRESULT SetHwnd(const HWND hwnd) noexcept override;
|
||||
|
||||
[[nodiscard]] HRESULT SetWindowSize(const SIZE pixels) noexcept;
|
||||
[[nodiscard]] HRESULT SetWindowSize(const SIZE pixels) noexcept override;
|
||||
|
||||
void SetCallback(std::function<void()> pfn);
|
||||
void SetWarningCallback(std::function<void(const HRESULT)> pfn);
|
||||
void SetCallback(std::function<void()> pfn) noexcept override;
|
||||
void SetWarningCallback(std::function<void(const HRESULT)> pfn) noexcept override;
|
||||
|
||||
void ToggleShaderEffects();
|
||||
void ToggleShaderEffects() noexcept override;
|
||||
|
||||
bool GetRetroTerminalEffect() const noexcept;
|
||||
void SetRetroTerminalEffect(bool enable) noexcept;
|
||||
bool GetRetroTerminalEffect() const noexcept override;
|
||||
void SetRetroTerminalEffect(bool enable) noexcept override;
|
||||
|
||||
void SetPixelShaderPath(std::wstring_view value) noexcept;
|
||||
void SetPixelShaderPath(std::wstring_view value) noexcept override;
|
||||
|
||||
void SetForceFullRepaintRendering(bool enable) noexcept;
|
||||
void SetForceFullRepaintRendering(bool enable) noexcept override;
|
||||
|
||||
void SetSoftwareRendering(bool enable) noexcept;
|
||||
void SetSoftwareRendering(bool enable) noexcept override;
|
||||
|
||||
HANDLE GetSwapChainHandle();
|
||||
HANDLE GetSwapChainHandle() noexcept override;
|
||||
|
||||
// IRenderEngine Members
|
||||
[[nodiscard]] HRESULT Invalidate(const SMALL_RECT* const psrRegion) noexcept override;
|
||||
|
@ -110,7 +110,7 @@ namespace Microsoft::Console::Render
|
|||
const bool usingSoftFont,
|
||||
const bool isSettingDefaultBrushes) noexcept override;
|
||||
[[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& fiFontInfoDesired, FontInfo& fiFontInfo) noexcept override;
|
||||
[[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& fiFontInfoDesired, FontInfo& fiFontInfo, const std::unordered_map<std::wstring_view, uint32_t>& features, const std::unordered_map<std::wstring_view, float>& axes) noexcept;
|
||||
[[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& fiFontInfoDesired, FontInfo& fiFontInfo, const std::unordered_map<std::wstring_view, uint32_t>& features, const std::unordered_map<std::wstring_view, float>& axes) noexcept override;
|
||||
[[nodiscard]] HRESULT UpdateDpi(int const iDpi) noexcept override;
|
||||
[[nodiscard]] HRESULT UpdateViewport(const SMALL_RECT srNewViewport) noexcept override;
|
||||
|
||||
|
@ -121,17 +121,17 @@ namespace Microsoft::Console::Render
|
|||
[[nodiscard]] HRESULT GetFontSize(_Out_ COORD* const pFontSize) noexcept override;
|
||||
[[nodiscard]] HRESULT IsGlyphWideByFont(const std::wstring_view glyph, _Out_ bool* const pResult) noexcept override;
|
||||
|
||||
[[nodiscard]] ::Microsoft::Console::Types::Viewport GetViewportInCharacters(const ::Microsoft::Console::Types::Viewport& viewInPixels) noexcept;
|
||||
[[nodiscard]] ::Microsoft::Console::Types::Viewport GetViewportInPixels(const ::Microsoft::Console::Types::Viewport& viewInCharacters) noexcept;
|
||||
[[nodiscard]] ::Microsoft::Console::Types::Viewport GetViewportInCharacters(const ::Microsoft::Console::Types::Viewport& viewInPixels) const noexcept override;
|
||||
[[nodiscard]] ::Microsoft::Console::Types::Viewport GetViewportInPixels(const ::Microsoft::Console::Types::Viewport& viewInCharacters) const noexcept override;
|
||||
|
||||
float GetScaling() const noexcept;
|
||||
float GetScaling() const noexcept override;
|
||||
|
||||
void SetSelectionBackground(const COLORREF color, const float alpha = 0.5f) noexcept;
|
||||
void SetAntialiasingMode(const D2D1_TEXT_ANTIALIAS_MODE antialiasingMode) noexcept;
|
||||
void EnableTransparentBackground(const bool isTransparent) noexcept;
|
||||
void SetIntenseIsBold(const bool opacity) noexcept;
|
||||
void SetSelectionBackground(const COLORREF color, const float alpha = 0.5f) noexcept override;
|
||||
void SetAntialiasingMode(const D2D1_TEXT_ANTIALIAS_MODE antialiasingMode) noexcept override;
|
||||
void EnableTransparentBackground(const bool isTransparent) noexcept override;
|
||||
void SetIntenseIsBold(const bool opacity) noexcept override;
|
||||
|
||||
void UpdateHyperlinkHoveredId(const uint16_t hoveredId) noexcept;
|
||||
void UpdateHyperlinkHoveredId(const uint16_t hoveredId) noexcept override;
|
||||
|
||||
protected:
|
||||
[[nodiscard]] HRESULT _DoUpdateTitle(_In_ const std::wstring_view newTitle) noexcept override;
|
||||
|
|
|
@ -14,12 +14,16 @@ Author(s):
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <d2d1.h>
|
||||
|
||||
#include "CursorOptions.h"
|
||||
#include "Cluster.hpp"
|
||||
#include "FontInfoDesired.hpp"
|
||||
#include "IRenderData.hpp"
|
||||
#include "../../buffer/out/LineRendition.hpp"
|
||||
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4100) // '...': unreferenced formal parameter
|
||||
namespace Microsoft::Console::Render
|
||||
{
|
||||
struct RenderFrameInfo
|
||||
|
@ -44,78 +48,75 @@ namespace Microsoft::Console::Render
|
|||
};
|
||||
using GridLineSet = til::enumset<GridLines>;
|
||||
|
||||
virtual ~IRenderEngine() = 0;
|
||||
#pragma warning(suppress : 26432) // If you define or delete any default operation in the type '...', define or delete them all (c.21).
|
||||
virtual ~IRenderEngine()
|
||||
{
|
||||
}
|
||||
|
||||
protected:
|
||||
IRenderEngine() = default;
|
||||
IRenderEngine(const IRenderEngine&) = default;
|
||||
IRenderEngine(IRenderEngine&&) = default;
|
||||
IRenderEngine& operator=(const IRenderEngine&) = default;
|
||||
IRenderEngine& operator=(IRenderEngine&&) = default;
|
||||
|
||||
public:
|
||||
[[nodiscard]] virtual HRESULT StartPaint() noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT EndPaint() noexcept = 0;
|
||||
|
||||
[[nodiscard]] virtual bool RequiresContinuousRedraw() noexcept = 0;
|
||||
virtual void WaitUntilCanRender() noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT Present() noexcept = 0;
|
||||
|
||||
[[nodiscard]] virtual HRESULT PrepareForTeardown(_Out_ bool* const pForcePaint) noexcept = 0;
|
||||
|
||||
[[nodiscard]] virtual HRESULT PrepareForTeardown(_Out_ bool* pForcePaint) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT ScrollFrame() noexcept = 0;
|
||||
|
||||
[[nodiscard]] virtual HRESULT Invalidate(const SMALL_RECT* const psrRegion) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT InvalidateCursor(const SMALL_RECT* const psrRegion) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT InvalidateSystem(const RECT* const prcDirtyClient) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT Invalidate(const SMALL_RECT* psrRegion) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT InvalidateCursor(const SMALL_RECT* psrRegion) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT InvalidateSystem(const RECT* prcDirtyClient) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT InvalidateSelection(const std::vector<SMALL_RECT>& rectangles) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT InvalidateScroll(const COORD* const pcoordDelta) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT InvalidateScroll(const COORD* pcoordDelta) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT InvalidateAll() noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT InvalidateCircling(_Out_ bool* const pForcePaint) noexcept = 0;
|
||||
|
||||
[[nodiscard]] virtual HRESULT InvalidateTitle(const std::wstring_view proposedTitle) noexcept = 0;
|
||||
|
||||
[[nodiscard]] virtual HRESULT InvalidateCircling(_Out_ bool* pForcePaint) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT InvalidateTitle(std::wstring_view proposedTitle) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT PrepareRenderInfo(const RenderFrameInfo& info) noexcept = 0;
|
||||
|
||||
[[nodiscard]] virtual HRESULT ResetLineTransform() noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT PrepareLineTransform(const LineRendition lineRendition,
|
||||
const size_t targetRow,
|
||||
const size_t viewportLeft) noexcept = 0;
|
||||
|
||||
[[nodiscard]] virtual HRESULT PrepareLineTransform(LineRendition lineRendition, size_t targetRow, size_t viewportLeft) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT PaintBackground() noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT PaintBufferLine(gsl::span<const Cluster> const clusters,
|
||||
const COORD coord,
|
||||
const bool fTrimLeft,
|
||||
const bool lineWrapped) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT PaintBufferGridLines(const GridLineSet lines,
|
||||
const COLORREF color,
|
||||
const size_t cchLine,
|
||||
const COORD coordTarget) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT PaintSelection(const SMALL_RECT rect) noexcept = 0;
|
||||
|
||||
[[nodiscard]] virtual HRESULT PaintBufferLine(gsl::span<const Cluster> clusters, COORD coord, bool fTrimLeft, bool lineWrapped) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT PaintBufferGridLines(GridLineSet lines, COLORREF color, size_t cchLine, COORD coordTarget) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT PaintSelection(SMALL_RECT rect) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT PaintCursor(const CursorOptions& options) noexcept = 0;
|
||||
|
||||
[[nodiscard]] virtual HRESULT UpdateDrawingBrushes(const TextAttribute& textAttributes,
|
||||
const gsl::not_null<IRenderData*> pData,
|
||||
const bool usingSoftFont,
|
||||
const bool isSettingDefaultBrushes) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT UpdateFont(const FontInfoDesired& FontInfoDesired,
|
||||
_Out_ FontInfo& FontInfo) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT UpdateSoftFont(const gsl::span<const uint16_t> bitPattern,
|
||||
const SIZE cellSize,
|
||||
const size_t centeringHint) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT UpdateDpi(const int iDpi) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT UpdateViewport(const SMALL_RECT srNewViewport) noexcept = 0;
|
||||
|
||||
[[nodiscard]] virtual HRESULT GetProposedFont(const FontInfoDesired& FontInfoDesired,
|
||||
_Out_ FontInfo& FontInfo,
|
||||
const int iDpi) noexcept = 0;
|
||||
|
||||
[[nodiscard]] virtual HRESULT UpdateDrawingBrushes(const TextAttribute& textAttributes, gsl::not_null<IRenderData*> pData, bool usingSoftFont, bool isSettingDefaultBrushes) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT UpdateFont(const FontInfoDesired& FontInfoDesired, _Out_ FontInfo& FontInfo) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT UpdateSoftFont(gsl::span<const uint16_t> bitPattern, SIZE cellSize, size_t centeringHint) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT UpdateDpi(int iDpi) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT UpdateViewport(SMALL_RECT srNewViewport) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT GetProposedFont(const FontInfoDesired& FontInfoDesired, _Out_ FontInfo& FontInfo, int iDpi) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT GetDirtyArea(gsl::span<const til::rectangle>& area) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT GetFontSize(_Out_ COORD* const pFontSize) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT IsGlyphWideByFont(const std::wstring_view glyph, _Out_ bool* const pResult) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT UpdateTitle(const std::wstring_view newTitle) noexcept = 0;
|
||||
};
|
||||
[[nodiscard]] virtual HRESULT GetFontSize(_Out_ COORD* pFontSize) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT IsGlyphWideByFont(std::wstring_view glyph, _Out_ bool* pResult) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT UpdateTitle(std::wstring_view newTitle) noexcept = 0;
|
||||
|
||||
inline Microsoft::Console::Render::IRenderEngine::~IRenderEngine() {}
|
||||
// The following functions used to be specific to the DxRenderer and they should
|
||||
// be abstracted away and integrated into the above or simply get removed.
|
||||
|
||||
// DxRenderer - getter
|
||||
virtual HRESULT Enable() noexcept { return S_OK; }
|
||||
virtual [[nodiscard]] bool GetRetroTerminalEffect() const noexcept { return false; }
|
||||
virtual [[nodiscard]] float GetScaling() const noexcept { return 1; }
|
||||
#pragma warning(suppress : 26440) // Function '...' can be declared 'noexcept' (f.6).
|
||||
virtual [[nodiscard]] HANDLE GetSwapChainHandle()
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
virtual [[nodiscard]] Types::Viewport GetViewportInCharacters(const Types::Viewport& viewInPixels) const noexcept { return Types::Viewport::Empty(); }
|
||||
virtual [[nodiscard]] Types::Viewport GetViewportInPixels(const Types::Viewport& viewInCharacters) const noexcept { return Types::Viewport::Empty(); }
|
||||
// DxRenderer - setter
|
||||
virtual void SetAntialiasingMode(const D2D1_TEXT_ANTIALIAS_MODE antialiasingMode) noexcept {}
|
||||
virtual void SetCallback(std::function<void()> pfn) noexcept {}
|
||||
virtual void EnableTransparentBackground(const bool isTransparent) noexcept {}
|
||||
virtual void SetForceFullRepaintRendering(bool enable) noexcept {}
|
||||
virtual [[nodiscard]] HRESULT SetHwnd(const HWND hwnd) noexcept { return E_NOTIMPL; }
|
||||
virtual void SetPixelShaderPath(std::wstring_view value) noexcept {}
|
||||
virtual void SetRetroTerminalEffect(bool enable) noexcept {}
|
||||
virtual void SetSelectionBackground(const COLORREF color, const float alpha = 0.5f) noexcept {}
|
||||
virtual void SetSoftwareRendering(bool enable) noexcept {}
|
||||
virtual void SetIntenseIsBold(bool enable) noexcept {}
|
||||
virtual void SetWarningCallback(std::function<void(HRESULT)> pfn) noexcept {}
|
||||
virtual [[nodiscard]] HRESULT SetWindowSize(const SIZE pixels) noexcept { return E_NOTIMPL; }
|
||||
virtual void ToggleShaderEffects() noexcept {}
|
||||
virtual [[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& pfiFontInfoDesired, FontInfo& fiFontInfo, const std::unordered_map<std::wstring_view, uint32_t>& features, const std::unordered_map<std::wstring_view, float>& axes) noexcept { return E_NOTIMPL; }
|
||||
virtual void UpdateHyperlinkHoveredId(const uint16_t hoveredId) noexcept {}
|
||||
};
|
||||
}
|
||||
#pragma warning(pop)
|
||||
|
|
|
@ -261,6 +261,13 @@ CATCH_RETURN();
|
|||
return S_OK;
|
||||
}
|
||||
|
||||
// RenderEngineBase defines a WaitUntilCanRender() that sleeps for 8ms to throttle rendering.
|
||||
// But UiaEngine is never the only engine running. Overriding this function prevents
|
||||
// us from sleeping 16ms per frame, when the other engine also sleeps for 8ms.
|
||||
void UiaEngine::WaitUntilCanRender() noexcept
|
||||
{
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Used to perform longer running presentation steps outside the lock so the
|
||||
// other threads can continue.
|
||||
|
|
|
@ -30,18 +30,16 @@ namespace Microsoft::Console::Render
|
|||
// Only one UiaEngine may present information at a time.
|
||||
// This ensures that an automation client isn't overwhelmed
|
||||
// by events when there are multiple TermControls
|
||||
[[nodiscard]] HRESULT Enable() noexcept;
|
||||
[[nodiscard]] HRESULT Enable() noexcept override;
|
||||
[[nodiscard]] HRESULT Disable() noexcept;
|
||||
|
||||
// IRenderEngine Members
|
||||
[[nodiscard]] HRESULT StartPaint() noexcept override;
|
||||
[[nodiscard]] HRESULT EndPaint() noexcept override;
|
||||
void WaitUntilCanRender() noexcept override;
|
||||
[[nodiscard]] HRESULT Present() noexcept override;
|
||||
|
||||
[[nodiscard]] HRESULT PrepareForTeardown(_Out_ bool* const pForcePaint) noexcept override;
|
||||
|
||||
[[nodiscard]] HRESULT ScrollFrame() noexcept override;
|
||||
|
||||
[[nodiscard]] HRESULT Invalidate(const SMALL_RECT* const psrRegion) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateCursor(const SMALL_RECT* const psrRegion) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateSystem(const RECT* const prcDirtyClient) noexcept override;
|
||||
|
@ -49,27 +47,16 @@ namespace Microsoft::Console::Render
|
|||
[[nodiscard]] HRESULT InvalidateScroll(const COORD* const pcoordDelta) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateAll() noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateCircling(_Out_ bool* const pForcePaint) noexcept override;
|
||||
|
||||
[[nodiscard]] HRESULT PaintBackground() noexcept override;
|
||||
[[nodiscard]] HRESULT PaintBufferLine(gsl::span<const Cluster> const clusters,
|
||||
COORD const coord,
|
||||
bool const fTrimLeft,
|
||||
const bool lineWrapped) noexcept override;
|
||||
[[nodiscard]] HRESULT PaintBufferGridLines(GridLineSet const lines, COLORREF const color, size_t const cchLine, COORD const coordTarget) noexcept override;
|
||||
[[nodiscard]] HRESULT PaintBufferLine(gsl::span<const Cluster> const clusters, const COORD coord, const bool fTrimLeft, const bool lineWrapped) noexcept override;
|
||||
[[nodiscard]] HRESULT PaintBufferGridLines(const GridLineSet lines, const COLORREF color, const size_t cchLine, const COORD coordTarget) noexcept override;
|
||||
[[nodiscard]] HRESULT PaintSelection(const SMALL_RECT rect) noexcept override;
|
||||
|
||||
[[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override;
|
||||
|
||||
[[nodiscard]] HRESULT UpdateDrawingBrushes(const TextAttribute& textAttributes,
|
||||
const gsl::not_null<IRenderData*> pData,
|
||||
const bool usingSoftFont,
|
||||
const bool isSettingDefaultBrushes) noexcept override;
|
||||
[[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& fiFontInfoDesired, FontInfo& fiFontInfo) noexcept override;
|
||||
[[nodiscard]] HRESULT UpdateDpi(int const iDpi) noexcept override;
|
||||
[[nodiscard]] HRESULT UpdateDrawingBrushes(const TextAttribute& textAttributes, const gsl::not_null<IRenderData*> pData, const bool usingSoftFont, const bool isSettingDefaultBrushes) noexcept override;
|
||||
[[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& FontInfoDesired, _Out_ FontInfo& FontInfo) noexcept override;
|
||||
[[nodiscard]] HRESULT UpdateDpi(const int iDpi) noexcept override;
|
||||
[[nodiscard]] HRESULT UpdateViewport(const SMALL_RECT srNewViewport) noexcept override;
|
||||
|
||||
[[nodiscard]] HRESULT GetProposedFont(const FontInfoDesired& fiFontInfoDesired, FontInfo& fiFontInfo, int const iDpi) noexcept override;
|
||||
|
||||
[[nodiscard]] HRESULT GetProposedFont(const FontInfoDesired& FontInfoDesired, _Out_ FontInfo& FontInfo, const int iDpi) noexcept override;
|
||||
[[nodiscard]] HRESULT GetDirtyArea(gsl::span<const til::rectangle>& area) noexcept override;
|
||||
[[nodiscard]] HRESULT GetFontSize(_Out_ COORD* const pFontSize) noexcept override;
|
||||
[[nodiscard]] HRESULT IsGlyphWideByFont(const std::wstring_view glyph, _Out_ bool* const pResult) noexcept override;
|
||||
|
|
|
@ -43,70 +43,41 @@ namespace Microsoft::Console::Render
|
|||
VtEngine(_In_ wil::unique_hfile hPipe,
|
||||
const Microsoft::Console::Types::Viewport initialViewport);
|
||||
|
||||
virtual ~VtEngine() override = default;
|
||||
|
||||
// IRenderEngine
|
||||
[[nodiscard]] HRESULT StartPaint() noexcept override;
|
||||
[[nodiscard]] HRESULT EndPaint() noexcept override;
|
||||
[[nodiscard]] HRESULT Present() noexcept override;
|
||||
[[nodiscard]] HRESULT PrepareForTeardown(_Out_ bool* pForcePaint) noexcept override;
|
||||
[[nodiscard]] HRESULT Invalidate(const SMALL_RECT* psrRegion) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateCursor(const SMALL_RECT* psrRegion) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateSystem(const RECT* prcDirtyClient) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateSelection(const std::vector<SMALL_RECT>& rectangles) noexcept override;
|
||||
[[nodiscard]] virtual HRESULT InvalidateScroll(const COORD* const pcoordDelta) noexcept = 0;
|
||||
[[nodiscard]] HRESULT InvalidateSystem(const RECT* const prcDirtyClient) noexcept override;
|
||||
[[nodiscard]] HRESULT Invalidate(const SMALL_RECT* const psrRegion) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateCursor(const SMALL_RECT* const psrRegion) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateAll() noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateCircling(_Out_ bool* const pForcePaint) noexcept override;
|
||||
[[nodiscard]] HRESULT PrepareForTeardown(_Out_ bool* const pForcePaint) noexcept override;
|
||||
|
||||
[[nodiscard]] virtual HRESULT StartPaint() noexcept override;
|
||||
[[nodiscard]] virtual HRESULT EndPaint() noexcept override;
|
||||
[[nodiscard]] virtual HRESULT Present() noexcept override;
|
||||
|
||||
[[nodiscard]] virtual HRESULT ScrollFrame() noexcept = 0;
|
||||
|
||||
[[nodiscard]] HRESULT InvalidateCircling(_Out_ bool* pForcePaint) noexcept override;
|
||||
[[nodiscard]] HRESULT PaintBackground() noexcept override;
|
||||
[[nodiscard]] virtual HRESULT PaintBufferLine(gsl::span<const Cluster> const clusters,
|
||||
const COORD coord,
|
||||
const bool trimLeft,
|
||||
const bool lineWrapped) noexcept override;
|
||||
[[nodiscard]] HRESULT PaintBufferGridLines(const GridLineSet lines,
|
||||
const COLORREF color,
|
||||
const size_t cchLine,
|
||||
const COORD coordTarget) noexcept override;
|
||||
[[nodiscard]] HRESULT PaintSelection(const SMALL_RECT rect) noexcept override;
|
||||
|
||||
[[nodiscard]] virtual HRESULT PaintCursor(const CursorOptions& options) noexcept override;
|
||||
|
||||
[[nodiscard]] virtual HRESULT UpdateDrawingBrushes(const TextAttribute& textAttributes,
|
||||
const gsl::not_null<IRenderData*> pData,
|
||||
const bool usingSoftFont,
|
||||
const bool isSettingDefaultBrushes) noexcept = 0;
|
||||
[[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& pfiFontInfoDesired,
|
||||
_Out_ FontInfo& pfiFontInfo) noexcept override;
|
||||
[[nodiscard]] HRESULT UpdateDpi(const int iDpi) noexcept override;
|
||||
[[nodiscard]] HRESULT UpdateViewport(const SMALL_RECT srNewViewport) noexcept override;
|
||||
|
||||
[[nodiscard]] HRESULT GetProposedFont(const FontInfoDesired& FontDesired,
|
||||
_Out_ FontInfo& Font,
|
||||
const int iDpi) noexcept override;
|
||||
|
||||
[[nodiscard]] HRESULT PaintBufferLine(gsl::span<const Cluster> clusters, COORD coord, bool fTrimLeft, bool lineWrapped) noexcept override;
|
||||
[[nodiscard]] HRESULT PaintBufferGridLines(GridLineSet lines, COLORREF color, size_t cchLine, COORD coordTarget) noexcept override;
|
||||
[[nodiscard]] HRESULT PaintSelection(SMALL_RECT rect) noexcept override;
|
||||
[[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override;
|
||||
[[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& FontInfoDesired, _Out_ FontInfo& FontInfo) noexcept override;
|
||||
[[nodiscard]] HRESULT UpdateDpi(int iDpi) noexcept override;
|
||||
[[nodiscard]] HRESULT UpdateViewport(SMALL_RECT srNewViewport) noexcept override;
|
||||
[[nodiscard]] HRESULT GetProposedFont(const FontInfoDesired& FontInfoDesired, _Out_ FontInfo& FontInfo, int iDpi) noexcept override;
|
||||
[[nodiscard]] HRESULT GetDirtyArea(gsl::span<const til::rectangle>& area) noexcept override;
|
||||
[[nodiscard]] HRESULT GetFontSize(_Out_ COORD* const pFontSize) noexcept override;
|
||||
[[nodiscard]] HRESULT IsGlyphWideByFont(const std::wstring_view glyph, _Out_ bool* const pResult) noexcept override;
|
||||
[[nodiscard]] HRESULT GetFontSize(_Out_ COORD* pFontSize) noexcept override;
|
||||
[[nodiscard]] HRESULT IsGlyphWideByFont(std::wstring_view glyph, _Out_ bool* pResult) noexcept override;
|
||||
|
||||
// VtEngine
|
||||
[[nodiscard]] HRESULT SuppressResizeRepaint() noexcept;
|
||||
|
||||
[[nodiscard]] HRESULT RequestCursor() noexcept;
|
||||
[[nodiscard]] HRESULT InheritCursor(const COORD coordCursor) noexcept;
|
||||
|
||||
[[nodiscard]] HRESULT WriteTerminalUtf8(const std::string_view str) noexcept;
|
||||
|
||||
[[nodiscard]] virtual HRESULT WriteTerminalW(const std::wstring_view str) noexcept = 0;
|
||||
|
||||
void SetTerminalOwner(Microsoft::Console::ITerminalOwner* const terminalOwner);
|
||||
void BeginResizeRequest();
|
||||
void EndResizeRequest();
|
||||
|
||||
void SetResizeQuirk(const bool resizeQuirk);
|
||||
|
||||
[[nodiscard]] virtual HRESULT ManuallyClearScrollback() noexcept;
|
||||
|
||||
[[nodiscard]] HRESULT RequestWin32Input() noexcept;
|
||||
|
||||
protected:
|
||||
|
|
|
@ -19,7 +19,7 @@ namespace Microsoft::Console::Render
|
|||
// Used to release device resources so that another instance of
|
||||
// conhost can render to the screen (i.e. only one DirectX
|
||||
// application may control the screen at a time.)
|
||||
[[nodiscard]] HRESULT Enable() noexcept;
|
||||
[[nodiscard]] HRESULT Enable() noexcept override;
|
||||
[[nodiscard]] HRESULT Disable() noexcept;
|
||||
|
||||
RECT GetDisplaySize();
|
||||
|
|
|
@ -94,4 +94,5 @@ namespace Microsoft::Console::Utils
|
|||
|
||||
GUID CreateV5Uuid(const GUID& namespaceGuid, const gsl::span<const gsl::byte> name);
|
||||
|
||||
bool IsElevated();
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
#include "inc/utils.hpp"
|
||||
#include "inc/colorTable.hpp"
|
||||
|
||||
#include <wil/token_helpers.h>
|
||||
|
||||
using namespace Microsoft::Console;
|
||||
|
||||
// Routine Description:
|
||||
|
@ -559,3 +561,33 @@ GUID Utils::CreateV5Uuid(const GUID& namespaceGuid, const gsl::span<const gsl::b
|
|||
::memcpy_s(&newGuid, sizeof(GUID), buffer.data(), sizeof(GUID));
|
||||
return EndianSwap(newGuid);
|
||||
}
|
||||
|
||||
bool Utils::IsElevated()
|
||||
{
|
||||
static bool isElevated = []() {
|
||||
try
|
||||
{
|
||||
wil::unique_handle processToken{ GetCurrentProcessToken() };
|
||||
const auto elevationType = wil::get_token_information<TOKEN_ELEVATION_TYPE>(processToken.get());
|
||||
const auto elevationState = wil::get_token_information<TOKEN_ELEVATION>(processToken.get());
|
||||
if (elevationType == TokenElevationTypeDefault && elevationState.TokenIsElevated)
|
||||
{
|
||||
// In this case, the user has UAC entirely disabled. This is sort of
|
||||
// weird, we treat this like the user isn't an admin at all. There's no
|
||||
// separation of powers, so the things we normally want to gate on
|
||||
// "having special powers" doesn't apply.
|
||||
//
|
||||
// See GH#7754, GH#11096
|
||||
return false;
|
||||
}
|
||||
|
||||
return wil::test_token_membership(nullptr, SECURITY_NT_AUTHORITY, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_CAUGHT_EXCEPTION();
|
||||
return false;
|
||||
}
|
||||
}();
|
||||
return isElevated;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue