mirror of
https://github.com/microsoft/terminal.git
synced 2026-04-07 23:01:09 +00:00
Compare commits
1 Commits
dev/miniks
...
dev/duhowe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2188e570f2 |
@@ -17,11 +17,7 @@
|
||||
"/src/winconpty/",
|
||||
"/.nuget/",
|
||||
"/.github/",
|
||||
"/samples/",
|
||||
"/res/terminal/",
|
||||
"/doc/specs/",
|
||||
"/doc/cascadia/",
|
||||
"/doc/user-docs/"
|
||||
"/samples/"
|
||||
],
|
||||
"SuffixFilters": [
|
||||
".dbb",
|
||||
|
||||
@@ -41,13 +41,17 @@ same window.
|
||||
|
||||
#### `split-pane`
|
||||
|
||||
`split-pane [-H]|[-V] [terminal_parameters]`
|
||||
`split-pane [--target,-t target-pane] [-H]|[-V] [terminal_parameters]`
|
||||
|
||||
Creates a new pane in the currently focused tab by splitting the given pane
|
||||
vertically or horizontally.
|
||||
|
||||
**Parameters**:
|
||||
|
||||
* `--target,-t target-pane`: Creates a new split in the given `target-pane`.
|
||||
Each pane has a unique index (per-tab) which can be used to identify them.
|
||||
These indicies are assigned in the order the panes were created. If omitted,
|
||||
defaults to the index of the currently focused pane.
|
||||
* `-H`, `-V`: Used to indicate which direction to split the pane. `-V` is
|
||||
"vertically" (think `[|]`), and `-H` is "horizontally" (think `[-]`). If
|
||||
omitted, defaults to "auto", which splits the current pane in whatever the
|
||||
|
||||
@@ -85,6 +85,6 @@ For an introduction to the various settings, see [Using Json Settings](UsingJson
|
||||
(ref [https://twitter.com/r_keith_hill/status/1142871145852440576](https://twitter.com/r_keith_hill/status/1142871145852440576))
|
||||
|
||||
2. Terminal zoom can be changed by holding <kbd>Ctrl</kbd> and scrolling with mouse.
|
||||
3. Background opacity can be changed by holding <kbd>Ctrl</kbd>+<kbd>Shift</kbd> and scrolling with mouse. Note that acrylic transparency is limited by the OS only to focused windows.
|
||||
3. If `useAcrylic` is enabled in profiles.json, background opacity can be changed by holding <kbd>Ctrl</kbd>+<kbd>Shift</kbd> and scrolling with mouse. Note that acrylic transparency is limited by the OS only to focused windows.
|
||||
4. Open Windows Terminal in current directory by typing `wt -d .` in the address bar.
|
||||
5. Please add more Tips and Tricks.
|
||||
|
||||
104
src/TerminalPerf.wprp
Normal file
104
src/TerminalPerf.wprp
Normal file
@@ -0,0 +1,104 @@
|
||||
<WindowsPerformanceRecorder Version="1.0" Comments="Test" Company="Microsoft Corporation" Copyright="Microsoft Corporation">
|
||||
<Profiles>
|
||||
<SystemCollector Id="SystemCollector_ConsolePerfSystemCollectorInFileLarge">
|
||||
<BufferSize Value="1024"/>
|
||||
<Buffers Value="100"/>
|
||||
</SystemCollector>
|
||||
<!-- Leave this system provider alone. It's for environmental statistics by the plugin only -->
|
||||
<SystemProvider Id="SystemProvider_Winperf_Environment_Light">
|
||||
<Keywords Operation="Add">
|
||||
<Keyword Value="CpuConfig"/>
|
||||
</Keywords>
|
||||
</SystemProvider>
|
||||
<!-- This one is our system provider to customize as we see fit. -->
|
||||
<SystemProvider Id="SystemProvider_ConsolePerf_Verbose" Base="SystemProvider_Base">
|
||||
<Keywords Operation="Add">
|
||||
<!-- Add additional kernel system collection here if we decide we need it for the regions of interest file -->
|
||||
<Keyword Value="CSwitch"/>
|
||||
<Keyword Value="MemoryInfo"/>
|
||||
<Keyword Value="MemoryInfoWS"/>
|
||||
<Keyword Value="VirtualAllocation"/>
|
||||
</Keywords>
|
||||
</SystemProvider>
|
||||
<!-- Then add all the event providers we might possibly need. The first batch up here is boilerplate ones -->
|
||||
<EventProvider Id="EventProvider-Microsoft-Windows-WinPerf" Name="7dba59f2-a1ff-40f0-b9f1-34b7531e9580"/>
|
||||
<EventProvider Id="EventProvider-Microsoft.Windows.WinPerf" Name="BE6F04EA-3488-4543-8082-24843EAEC303"/>
|
||||
<EventProvider Id="EventProvider-Census" Name="262CDE7A-5C84-46CF-9420-94963791EF69"/>
|
||||
<!-- These providers are relevant to specific Regions of Interest -->
|
||||
<EventProvider Id="EventProvider-Microsoft-Windows-Kernel-Memory" Name="Microsoft-Windows-Kernel-Memory"/>
|
||||
<EventProvider Id="EventProvider_Microsoft-Windows-Kernel-Power" Name="331c3b3a-2005-44c2-ac5e-77220c37d6b4" NonPagedMemory="true">
|
||||
<Keywords>
|
||||
<Keyword Value="0x8000000000000000" />
|
||||
</Keywords>
|
||||
<CaptureStateOnSave>
|
||||
<Keyword Value="0x80"/>
|
||||
</CaptureStateOnSave>
|
||||
</EventProvider>
|
||||
<!-- Add console providers here -->
|
||||
<EventProvider Id="EventProvider-Microsoft.Windows.Console.Launcher" Name="770aa552-671a-5e97-579b-151709ec0dbd"/>
|
||||
<EventProvider Id="EventProvider-Microsoft.Windows.Console.Host" Name="fe1ff234-1f09-50a8-d38d-c44fab43e818"/>
|
||||
<EventProvider Id="EventProvider-Microsoft.Windows.Console.Server" Name="1A541C01-589A-496E-85A7-A9E02170166D"/>
|
||||
<EventProvider Id="EventProvider-Microsoft.Windows.Console.VirtualTerminal.Parser" Name="c9ba2a84-d3ca-5e19-2bd6-776a0910cb9d"/>
|
||||
<EventProvider Id="EventProvider-Microsoft.Windows.Console.Render.VtEngine" Name="c9ba2a95-d3ca-5e19-2bd6-776a0910cb9d"/>
|
||||
<EventProvider Id="EventProvider-Microsoft.Windows.Terminal.App" Name="24a1622f-7da7-5c77-3303-d850bd1ab2ed"/>
|
||||
<EventProvider Id="EventProvider-Microsoft.Windows.Terminal.Control" Name="28c82e50-57af-5a86-c25b-e39cd990032b"/>
|
||||
<EventProvider Id="EventProvider-Microsoft.Windows.Terminal.Connection" Name="e912fe7b-eeb6-52a5-c628-abe388e5f792"/>
|
||||
<EventProvider Id="EventProvider-Microsoft.Windows.Terminal.Win32Host" Name="56c06166-2e2e-5f4d-7ff3-74f4b78c87d6"/>
|
||||
<!-- Now define some profiles. We'll call them by ID when collecting. Also, the Base is where it is inheriting from and is a .wprpi file built... -->
|
||||
<!-- ... into WPR automatically. Go look in the WPR install directory or in the documentation to find it. -->
|
||||
<Profile Id="ConsolePerfProfile.Verbose.File" Base="GeneralProfile.Light.File" LoggingMode="File" Name="ConsolePerfProfile" DetailLevel="Verbose" Description="Console Performance default profile">
|
||||
<Collectors Operation="Add">
|
||||
<SystemCollectorId Value="SystemCollector_ConsolePerfSystemCollectorInFileLarge">
|
||||
<!-- You don't have to change anything here when system keywords are added above. -->
|
||||
<!-- Kernel collection is a bit more unified than user collection of events. -->
|
||||
<SystemProviderId Value="SystemProvider_ConsolePerf_Verbose"/>
|
||||
</SystemCollectorId>
|
||||
<EventCollectorId Value="EventCollector_WPREventCollectorInFile">
|
||||
<EventProviders Operation="Add">
|
||||
<!-- Anything we need to collect for our perf test has to be referenced again here. -->
|
||||
<EventProviderId Value="EventProvider-Microsoft-Windows-WinPerf"/>
|
||||
<EventProviderId Value="EventProvider-Microsoft.Windows.WinPerf"/>
|
||||
<EventProviderId Value="EventProvider-Microsoft-Windows-Kernel-Memory"/>
|
||||
<EventProviderId Value="EventProvider-Microsoft.Windows.Console.Launcher"/>
|
||||
<EventProviderId Value="EventProvider-Microsoft.Windows.Console.Host"/>
|
||||
<EventProviderId Value="EventProvider-Microsoft.Windows.Console.Server"/>
|
||||
<EventProviderId Value="EventProvider-Microsoft.Windows.Console.VirtualTerminal.Parser"/>
|
||||
<EventProviderId Value="EventProvider-Microsoft.Windows.Console.Render.VtEngine"/>
|
||||
<EventProviderId Value="EventProvider-Microsoft.Windows.Terminal.App"/>
|
||||
<EventProviderId Value="EventProvider-Microsoft.Windows.Terminal.Control"/>
|
||||
<EventProviderId Value="EventProvider-Microsoft.Windows.Terminal.Connection"/>
|
||||
<EventProviderId Value="EventProvider-Microsoft.Windows.Terminal.Win32Host"/>
|
||||
</EventProviders>
|
||||
</EventCollectorId>
|
||||
</Collectors>
|
||||
</Profile>
|
||||
<!-- Leave everything below here alone. They're needed for WinPerf to collect environmental census information -->
|
||||
<Profile Id="WinPerfEnvironment.Light.File" Base="" LoggingMode="File" Name="WinPerfEnvironment" DetailLevel="Light" Description="WinPerf environment profile">
|
||||
<Collectors Operation="Add">
|
||||
<SystemCollectorId Value="SystemCollector_WPRSystemCollectorInFile">
|
||||
<SystemProviderId Value="SystemProvider_Winperf_Environment_Light" />
|
||||
</SystemCollectorId>
|
||||
<EventCollectorId Value="EventCollector_WPREventCollectorInFile">
|
||||
<EventProviders Operation="Add">
|
||||
<EventProviderId Value="EventProvider-Census"/>
|
||||
<EventProviderId Value="EventProvider_Microsoft-Windows-Kernel-Power"/>
|
||||
<EventProviderId Value="EventProvider-Microsoft-Windows-WinPerf"/>
|
||||
<EventProviderId Value="EventProvider-Microsoft.Windows.WinPerf"/>
|
||||
</EventProviders>
|
||||
</EventCollectorId>
|
||||
</Collectors>
|
||||
</Profile>
|
||||
<Profile Id="WinPerfEnvironment.Light.Memory" Base="WinPerfEnvironment.Light.File" LoggingMode="Memory" Name="WinPerfEnvironment" DetailLevel="Light" Description="WinPerf environment profile">
|
||||
<Collectors Operation="Add">
|
||||
<SystemCollectorId Value="SystemCollector_WPRSystemCollectorInFile">
|
||||
<BufferSize Value="1024"/>
|
||||
<Buffers Value="5" PercentageOfTotalMemory="true"/>
|
||||
</SystemCollectorId>
|
||||
<EventCollectorId Value="EventCollector_WPREventCollectorInFile">
|
||||
<BufferSize Value="1024"/>
|
||||
<Buffers Value="3" PercentageOfTotalMemory="true"/>
|
||||
</EventCollectorId>
|
||||
</Collectors>
|
||||
</Profile>
|
||||
</Profiles>
|
||||
</WindowsPerformanceRecorder>
|
||||
@@ -155,6 +155,11 @@ void TextAttribute::SetColor(const COLORREF rgbColor, const bool fIsForeground)
|
||||
}
|
||||
}
|
||||
|
||||
bool TextAttribute::_IsReverseVideo() const noexcept
|
||||
{
|
||||
return WI_IsFlagSet(_wAttrLegacy, COMMON_LVB_REVERSE_VIDEO);
|
||||
}
|
||||
|
||||
bool TextAttribute::IsLeadingByte() const noexcept
|
||||
{
|
||||
return WI_IsFlagSet(_wAttrLegacy, COMMON_LVB_LEADING_BYTE);
|
||||
|
||||
@@ -163,41 +163,12 @@ public:
|
||||
return _foreground.IsRgb() || _background.IsRgb();
|
||||
}
|
||||
|
||||
// This returns whether this attribute, if printed directly next to another attribute, for the space
|
||||
// character, would look identical to the other one.
|
||||
constexpr bool HasIdenticalVisualRepresentationForBlankSpace(const TextAttribute& other, const bool inverted = false) const noexcept
|
||||
{
|
||||
// sneaky-sneaky: I'm using xor here
|
||||
// inverted is whether there's a global invert; Reverse is a local one.
|
||||
// global ^ local == true : the background attribute is actually the visible foreground, so we care about the foregrounds being identical
|
||||
// global ^ local == false: the foreground attribute is the visible foreground, so we care about the backgrounds being identical
|
||||
const auto checkForeground = (inverted != _IsReverseVideo());
|
||||
return !IsAnyGridLineEnabled() && // grid lines have a visual representation
|
||||
// crossed out, doubly and singly underlined have a visual representation
|
||||
WI_AreAllFlagsClear(_extendedAttrs, ExtendedAttributes::CrossedOut | ExtendedAttributes::DoublyUnderlined | ExtendedAttributes::Underlined) &&
|
||||
// all other attributes do not have a visual representation
|
||||
(_wAttrLegacy & META_ATTRS) == (other._wAttrLegacy & META_ATTRS) &&
|
||||
((checkForeground && _foreground == other._foreground) ||
|
||||
(!checkForeground && _background == other._background)) &&
|
||||
_extendedAttrs == other._extendedAttrs;
|
||||
}
|
||||
|
||||
constexpr bool IsAnyGridLineEnabled() const noexcept
|
||||
{
|
||||
return WI_IsAnyFlagSet(_wAttrLegacy, COMMON_LVB_GRID_HORIZONTAL | COMMON_LVB_GRID_LVERTICAL | COMMON_LVB_GRID_RVERTICAL | COMMON_LVB_UNDERSCORE);
|
||||
}
|
||||
|
||||
private:
|
||||
COLORREF _GetRgbForeground(std::basic_string_view<COLORREF> colorTable,
|
||||
COLORREF defaultColor) const noexcept;
|
||||
COLORREF _GetRgbBackground(std::basic_string_view<COLORREF> colorTable,
|
||||
COLORREF defaultColor) const noexcept;
|
||||
|
||||
constexpr bool _IsReverseVideo() const noexcept
|
||||
{
|
||||
return WI_IsFlagSet(_wAttrLegacy, COMMON_LVB_REVERSE_VIDEO);
|
||||
}
|
||||
|
||||
bool _IsReverseVideo() const noexcept;
|
||||
void _SetBoldness(const bool isBold) noexcept;
|
||||
|
||||
WORD _wAttrLegacy;
|
||||
|
||||
@@ -573,21 +573,27 @@ bool TextBuffer::IncrementCircularBuffer(const bool inVtMode)
|
||||
}
|
||||
|
||||
//Routine Description:
|
||||
// - Retrieves the position of the last non-space character in the given
|
||||
// viewport
|
||||
// - By default, we search the entire buffer to find the last non-space
|
||||
// character.
|
||||
// - If we know the last character is within the given viewport (so we don't
|
||||
// need to check the entire buffer), we can provide a value in viewOptional
|
||||
// that we'll use to search for the last character in.
|
||||
// - Retrieves the position of the last non-space character on the final line of the text buffer.
|
||||
// - By default, we search the entire buffer to find the last non-space character
|
||||
//Arguments:
|
||||
// - <none>
|
||||
//Return Value:
|
||||
// - Coordinate position in screen coordinates (offset coordinates, not array index coordinates).
|
||||
COORD TextBuffer::GetLastNonSpaceCharacter() const
|
||||
{
|
||||
return GetLastNonSpaceCharacter(GetSize());
|
||||
}
|
||||
|
||||
//Routine Description:
|
||||
// - Retrieves the position of the last non-space character in the given viewport
|
||||
// - This is basically an optimized version of GetLastNonSpaceCharacter(), and can be called when
|
||||
// - we know the last character is within the given viewport (so we don't need to check the entire buffer)
|
||||
//Arguments:
|
||||
// - The viewport
|
||||
//Return value:
|
||||
// - Coordinate position (relative to the text buffer)
|
||||
COORD TextBuffer::GetLastNonSpaceCharacter(std::optional<const Microsoft::Console::Types::Viewport> viewOptional) const
|
||||
COORD TextBuffer::GetLastNonSpaceCharacter(const Microsoft::Console::Types::Viewport viewport) const
|
||||
{
|
||||
const auto viewport = viewOptional.has_value() ? viewOptional.value() : GetSize();
|
||||
|
||||
COORD coordEndOfText = { 0 };
|
||||
// Search the given viewport by starting at the bottom.
|
||||
coordEndOfText.Y = viewport.BottomInclusive();
|
||||
@@ -1866,18 +1872,9 @@ std::string TextBuffer::GenRTF(const TextAndColor& rows, const int fontHeightPoi
|
||||
// Arguments:
|
||||
// - oldBuffer - the text buffer to copy the contents FROM
|
||||
// - newBuffer - the text buffer to copy the contents TO
|
||||
// - lastCharacterViewport - Optional. If the caller knows that the last
|
||||
// nonspace character is in a particular Viewport, the caller can provide this
|
||||
// parameter as an optimization, as opposed to searching the entire buffer.
|
||||
// - positionInfo - Optional. The caller can provide a pair of rows in this
|
||||
// parameter and we'll calculate the position of the _end_ of those rows in
|
||||
// the new buffer. The rows's new value is placed back into this parameter.
|
||||
// Return Value:
|
||||
// - S_OK if we successfully copied the contents to the new buffer, otherwise an appropriate HRESULT.
|
||||
HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
|
||||
TextBuffer& newBuffer,
|
||||
const std::optional<Viewport> lastCharacterViewport,
|
||||
std::optional<std::reference_wrapper<PositionInformation>> positionInfo)
|
||||
HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer, TextBuffer& newBuffer)
|
||||
{
|
||||
Cursor& oldCursor = oldBuffer.GetCursor();
|
||||
Cursor& newCursor = newBuffer.GetCursor();
|
||||
@@ -1889,15 +1886,14 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
|
||||
// place the new cursor back on the equivalent character in
|
||||
// the new buffer.
|
||||
const COORD cOldCursorPos = oldCursor.GetPosition();
|
||||
const COORD cOldLastChar = oldBuffer.GetLastNonSpaceCharacter(lastCharacterViewport);
|
||||
const COORD cOldLastChar = oldBuffer.GetLastNonSpaceCharacter();
|
||||
|
||||
const short cOldRowsTotal = cOldLastChar.Y + 1;
|
||||
const short cOldColsTotal = oldBuffer.GetSize().Width();
|
||||
short const cOldRowsTotal = cOldLastChar.Y + 1;
|
||||
short const cOldColsTotal = oldBuffer.GetSize().Width();
|
||||
|
||||
COORD cNewCursorPos = { 0 };
|
||||
bool fFoundCursorPos = false;
|
||||
bool foundOldMutable = false;
|
||||
bool foundOldVisible = false;
|
||||
|
||||
HRESULT hr = S_OK;
|
||||
// Loop through all the rows of the old buffer and reprint them into the new buffer
|
||||
for (short iOldRow = 0; iOldRow < cOldRowsTotal; iOldRow++)
|
||||
@@ -1957,31 +1953,6 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
|
||||
}
|
||||
CATCH_RETURN();
|
||||
}
|
||||
|
||||
// If we found the old row that the caller was interested in, set the
|
||||
// out value of that parameter to the cursor's current Y position (the
|
||||
// new location of the _end_ of that row in the buffer).
|
||||
if (positionInfo.has_value())
|
||||
{
|
||||
if (!foundOldMutable)
|
||||
{
|
||||
if (iOldRow >= positionInfo.value().get().mutableViewportTop)
|
||||
{
|
||||
positionInfo.value().get().mutableViewportTop = newCursor.GetPosition().Y;
|
||||
foundOldMutable = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundOldVisible)
|
||||
{
|
||||
if (iOldRow >= positionInfo.value().get().visibleViewportTop)
|
||||
{
|
||||
positionInfo.value().get().visibleViewportTop = newCursor.GetPosition().Y;
|
||||
foundOldVisible = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
// If we didn't have a full row to copy, insert a new
|
||||
|
||||
@@ -103,7 +103,8 @@ public:
|
||||
// Scroll needs access to this to quickly rotate around the buffer.
|
||||
bool IncrementCircularBuffer(const bool inVtMode = false);
|
||||
|
||||
COORD GetLastNonSpaceCharacter(std::optional<const Microsoft::Console::Types::Viewport> viewOptional = std::nullopt) const;
|
||||
COORD GetLastNonSpaceCharacter() const;
|
||||
COORD GetLastNonSpaceCharacter(const Microsoft::Console::Types::Viewport viewport) const;
|
||||
|
||||
Cursor& GetCursor() noexcept;
|
||||
const Cursor& GetCursor() const noexcept;
|
||||
@@ -161,16 +162,7 @@ public:
|
||||
const std::wstring_view fontFaceName,
|
||||
const COLORREF backgroundColor);
|
||||
|
||||
struct PositionInformation
|
||||
{
|
||||
short mutableViewportTop{ 0 };
|
||||
short visibleViewportTop{ 0 };
|
||||
};
|
||||
|
||||
static HRESULT Reflow(TextBuffer& oldBuffer,
|
||||
TextBuffer& newBuffer,
|
||||
const std::optional<Microsoft::Console::Types::Viewport> lastCharacterViewport,
|
||||
std::optional<std::reference_wrapper<PositionInformation>> positionInfo);
|
||||
static HRESULT Reflow(TextBuffer& oldBuffer, TextBuffer& newBuffer);
|
||||
|
||||
private:
|
||||
std::deque<ROW> _storage;
|
||||
|
||||
@@ -527,13 +527,13 @@ void _stdcall TerminalBlinkCursor(void* terminal)
|
||||
return;
|
||||
}
|
||||
|
||||
publicTerminal->_terminal->SetCursorOn(!publicTerminal->_terminal->IsCursorOn());
|
||||
publicTerminal->_terminal->SetCursorVisible(!publicTerminal->_terminal->IsCursorVisible());
|
||||
}
|
||||
|
||||
void _stdcall TerminalSetCursorVisible(void* terminal, const bool visible)
|
||||
{
|
||||
const auto publicTerminal = static_cast<const HwndTerminal*>(terminal);
|
||||
publicTerminal->_terminal->SetCursorOn(visible);
|
||||
publicTerminal->_terminal->SetCursorVisible(visible);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
|
||||
@@ -115,27 +115,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.
|
||||
// Return Value:
|
||||
// - true if the user is an administrator
|
||||
static bool _isUserAdmin() noexcept
|
||||
try
|
||||
{
|
||||
SID_IDENTIFIER_AUTHORITY ntAuthority{ SECURITY_NT_AUTHORITY };
|
||||
wil::unique_sid adminGroupSid{};
|
||||
THROW_IF_WIN32_BOOL_FALSE(AllocateAndInitializeSid(&ntAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &adminGroupSid));
|
||||
BOOL b;
|
||||
THROW_IF_WIN32_BOOL_FALSE(CheckTokenMembership(NULL, adminGroupSid.get(), &b));
|
||||
return !!b;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_CAUGHT_EXCEPTION();
|
||||
return false;
|
||||
}
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
AppLogic::AppLogic() :
|
||||
@@ -152,7 +131,6 @@ 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();
|
||||
_root = winrt::make_self<TerminalPage>();
|
||||
}
|
||||
|
||||
@@ -167,17 +145,6 @@ namespace winrt::TerminalApp::implementation
|
||||
return _isUwp;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Called around the codebase to discover if Terminal is running elevated
|
||||
// Arguments:
|
||||
// - <none> - reports internal state
|
||||
// Return Value:
|
||||
// - True if elevated, false otherwise.
|
||||
bool AppLogic::IsElevated() const noexcept
|
||||
{
|
||||
return _isElevated;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Called by UWP context invoker to let us know that we may have to change some of our behaviors
|
||||
// for being a UWP
|
||||
@@ -400,44 +367,11 @@ namespace winrt::TerminalApp::implementation
|
||||
// Use the default profile to determine how big of a window we need.
|
||||
const auto [_, settings] = _settings->BuildSettings(nullptr);
|
||||
|
||||
auto proposedSize = TermControl::GetProposedDimensions(settings, dpi);
|
||||
|
||||
const float scale = static_cast<float>(dpi) / static_cast<float>(USER_DEFAULT_SCREEN_DPI);
|
||||
|
||||
// GH#2061 - If the global setting "Always show tab bar" is
|
||||
// TODO MSFT:21150597 - If the global setting "Always show tab bar" is
|
||||
// set or if "Show tabs in title bar" is set, then we'll need to add
|
||||
// the height of the tab bar here.
|
||||
if (_settings->GlobalSettings().GetShowTabsInTitlebar())
|
||||
{
|
||||
// If we're showing the tabs in the titlebar, we need to use a
|
||||
// TitlebarContol here to calculate how much space to reserve.
|
||||
//
|
||||
// We'll create a fake TitlebarControl, and we'll propose an
|
||||
// available size to it with Measure(). After Measure() is called,
|
||||
// the TitlebarControl's DesiredSize will contain the _unscaled_
|
||||
// size that the titlebar would like to use. We'll use that as part
|
||||
// of the height calculation here.
|
||||
auto titlebar = TitlebarControl{ static_cast<uint64_t>(0) };
|
||||
titlebar.Measure({ SHRT_MAX, SHRT_MAX });
|
||||
proposedSize.Y += (titlebar.DesiredSize().Height) * scale;
|
||||
}
|
||||
else if (_settings->GlobalSettings().GetAlwaysShowTabs())
|
||||
{
|
||||
// Otherwise, let's use a TabRowControl to calculate how much extra
|
||||
// space we'll need.
|
||||
//
|
||||
// Similarly to above, we'll measure it with an arbitrarily large
|
||||
// available space, to make sure we get all the space it wants.
|
||||
auto tabControl = TabRowControl();
|
||||
tabControl.Measure({ SHRT_MAX, SHRT_MAX });
|
||||
|
||||
// For whatever reason, there's about 6px of unaccounted-for space
|
||||
// in the application. I couldn't tell you where these 6px are
|
||||
// coming from, but they need to be included in this math.
|
||||
proposedSize.Y += (tabControl.DesiredSize().Height + 6) * scale;
|
||||
}
|
||||
|
||||
return proposedSize;
|
||||
return TermControl::GetProposedDimensions(settings, dpi);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
||||
@@ -21,7 +21,6 @@ namespace winrt::TerminalApp::implementation
|
||||
void Create();
|
||||
bool IsUwp() const noexcept;
|
||||
void RunAsUwp();
|
||||
bool IsElevated() const noexcept;
|
||||
void LoadSettings();
|
||||
[[nodiscard]] std::shared_ptr<::TerminalApp::CascadiaSettings> GetSettings() const noexcept;
|
||||
|
||||
@@ -48,7 +47,6 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
private:
|
||||
bool _isUwp{ false };
|
||||
bool _isElevated{ false };
|
||||
|
||||
// If you add controls here, but forget to null them either here or in
|
||||
// the ctor, you're going to have a bad time. It'll mysteriously fail to
|
||||
|
||||
@@ -25,7 +25,6 @@ namespace TerminalApp
|
||||
|
||||
Boolean IsUwp();
|
||||
void RunAsUwp();
|
||||
Boolean IsElevated();
|
||||
|
||||
Int32 SetStartupCommandline(String[] commands);
|
||||
String EarlyExitMessage { get; };
|
||||
|
||||
@@ -63,14 +63,6 @@ namespace winrt::TerminalApp::implementation
|
||||
_tabView = _tabRow.TabView();
|
||||
_rearranging = false;
|
||||
|
||||
// GH#3581 - There's a platform limitation that causes us to crash when we rearrange tabs.
|
||||
// Xaml tries to send a drag visual (to wit: a screenshot) to the drag hosting process,
|
||||
// but that process is running at a different IL than us.
|
||||
// For now, we're disabling elevated drag.
|
||||
const auto isElevated = ::winrt::Windows::UI::Xaml::Application::Current().as<::winrt::TerminalApp::App>().Logic().IsElevated();
|
||||
_tabView.CanReorderTabs(!isElevated);
|
||||
_tabView.CanDragTabs(!isElevated);
|
||||
|
||||
_tabView.TabDragStarting([weakThis{ get_weak() }](auto&& /*o*/, auto&& /*a*/) {
|
||||
if (auto page{ weakThis.get() })
|
||||
{
|
||||
@@ -519,6 +511,7 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
// Create a connection based on the values in our settings object.
|
||||
const auto connection = _CreateConnectionFromSettings(profileGuid, settings);
|
||||
|
||||
TermControl term{ settings, connection };
|
||||
|
||||
// Add the new tab to the list of our tabs.
|
||||
|
||||
@@ -201,7 +201,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
try
|
||||
{
|
||||
const COORD dimensions{ gsl::narrow_cast<SHORT>(_initialCols), gsl::narrow_cast<SHORT>(_initialRows) };
|
||||
THROW_IF_FAILED(_CreatePseudoConsoleAndPipes(dimensions, PSEUDOCONSOLE_RESIZE_QUIRK, &_inPipe, &_outPipe, &_hPC));
|
||||
THROW_IF_FAILED(_CreatePseudoConsoleAndPipes(dimensions, 0, &_inPipe, &_outPipe, &_hPC));
|
||||
THROW_IF_FAILED(_LaunchAttachedClient());
|
||||
|
||||
_startTime = std::chrono::high_resolution_clock::now();
|
||||
@@ -359,10 +359,6 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
|
||||
DWORD ConptyConnection::_OutputThread()
|
||||
{
|
||||
// Keep us alive until the output thread terminates; the destructor
|
||||
// won't wait for us, and the known exit points _do_.
|
||||
auto strongThis{ get_strong() };
|
||||
|
||||
// process the data of the output pipe in a loop
|
||||
while (true)
|
||||
{
|
||||
|
||||
@@ -27,7 +27,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
auto manager = Core::CoreTextServicesManager::GetForCurrentView();
|
||||
_editContext = manager.CreateEditContext();
|
||||
|
||||
// InputPane is manually shown inside of TermControl.
|
||||
// sets the Input Pane display policy to Manual for now so that it can manually show the
|
||||
// software keyboard when the control gains focus and dismiss it when the control loses focus.
|
||||
// TODO GitHub #3639: Should Input Pane display policy be Automatic
|
||||
_editContext.InputPaneDisplayPolicy(Core::CoreTextInputPaneDisplayPolicy::Manual);
|
||||
|
||||
// set the input scope to Text because this control is for any text.
|
||||
|
||||
@@ -21,8 +21,6 @@ using namespace winrt::Windows::UI::Xaml;
|
||||
using namespace winrt::Windows::UI::Xaml::Input;
|
||||
using namespace winrt::Windows::UI::Xaml::Automation::Peers;
|
||||
using namespace winrt::Windows::UI::Core;
|
||||
using namespace winrt::Windows::UI::ViewManagement;
|
||||
using namespace winrt::Windows::UI::Input;
|
||||
using namespace winrt::Windows::System;
|
||||
using namespace winrt::Microsoft::Terminal::Settings;
|
||||
using namespace winrt::Windows::ApplicationModel::DataTransfer;
|
||||
@@ -67,11 +65,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
_actualFont{ DEFAULT_FONT_FACE, 0, 10, { 0, DEFAULT_FONT_SIZE }, CP_UTF8, false },
|
||||
_touchAnchor{ std::nullopt },
|
||||
_cursorTimer{},
|
||||
_lastMouseClickTimestamp{},
|
||||
_lastMouseClick{},
|
||||
_lastMouseClickPos{},
|
||||
_searchBox{ nullptr },
|
||||
_focusRaisedClickPos{ std::nullopt },
|
||||
_clickDrag{ false }
|
||||
_unfocusedClickPos{ std::nullopt },
|
||||
_isClickDragSelection{ false }
|
||||
{
|
||||
_EnsureStaticInitialization();
|
||||
InitializeComponent();
|
||||
@@ -795,7 +793,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
{
|
||||
// Manually show the cursor when a key is pressed. Restarting
|
||||
// the timer prevents flickering.
|
||||
_terminal->SetCursorOn(true);
|
||||
_terminal->SetCursorVisible(true);
|
||||
_cursorTimer.value().Start();
|
||||
}
|
||||
|
||||
@@ -813,72 +811,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
e.Handled(true);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Send this particular mouse event to the terminal.
|
||||
// See Terminal::SendMouseEvent for more information.
|
||||
// Arguments:
|
||||
// - point: the PointerPoint object representing a mouse event from our XAML input handler
|
||||
bool TermControl::_TrySendMouseEvent(Windows::UI::Input::PointerPoint const& point)
|
||||
{
|
||||
const auto props = point.Properties();
|
||||
|
||||
// Get the terminal position relative to the viewport
|
||||
const auto terminalPosition = _GetTerminalPosition(point.Position());
|
||||
|
||||
// Which mouse button changed state (and how)
|
||||
unsigned int uiButton{};
|
||||
switch (props.PointerUpdateKind())
|
||||
{
|
||||
case PointerUpdateKind::LeftButtonPressed:
|
||||
uiButton = WM_LBUTTONDOWN;
|
||||
break;
|
||||
case PointerUpdateKind::LeftButtonReleased:
|
||||
uiButton = WM_LBUTTONUP;
|
||||
break;
|
||||
case PointerUpdateKind::MiddleButtonPressed:
|
||||
uiButton = WM_MBUTTONDOWN;
|
||||
break;
|
||||
case PointerUpdateKind::MiddleButtonReleased:
|
||||
uiButton = WM_MBUTTONUP;
|
||||
break;
|
||||
case PointerUpdateKind::RightButtonPressed:
|
||||
uiButton = WM_RBUTTONDOWN;
|
||||
break;
|
||||
case PointerUpdateKind::RightButtonReleased:
|
||||
uiButton = WM_RBUTTONUP;
|
||||
break;
|
||||
default:
|
||||
uiButton = WM_MOUSEMOVE;
|
||||
}
|
||||
|
||||
// Mouse wheel data
|
||||
const short sWheelDelta = ::base::saturated_cast<short>(props.MouseWheelDelta());
|
||||
if (sWheelDelta != 0 && !props.IsHorizontalMouseWheel())
|
||||
{
|
||||
// if we have a mouse wheel delta and it wasn't a horizontal wheel motion
|
||||
uiButton = WM_MOUSEWHEEL;
|
||||
}
|
||||
|
||||
const auto modifiers = _GetPressedModifierKeys();
|
||||
return _terminal->SendMouseEvent(terminalPosition, uiButton, modifiers, sWheelDelta);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Checks if we can send vt mouse input.
|
||||
// Arguments:
|
||||
// - point: the PointerPoint object representing a mouse event from our XAML input handler
|
||||
bool TermControl::_CanSendVTMouseInput()
|
||||
{
|
||||
// If the user is holding down Shift, suppress mouse events
|
||||
// TODO GH#4875: disable/customize this functionality
|
||||
const auto modifiers = _GetPressedModifierKeys();
|
||||
if (modifiers.IsShiftPressed())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return _terminal->IsTrackingMouseInput();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - handle a mouse click event. Begin selection process.
|
||||
// Arguments:
|
||||
@@ -892,18 +824,16 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
const auto ptr = args.Pointer();
|
||||
const auto point = args.GetCurrentPoint(*this);
|
||||
|
||||
// We also TryShow in GotFocusHandler, but this call is specifically
|
||||
// for the case where the Terminal is in focus but the user closed the
|
||||
// on-screen keyboard. This lets the user simply tap on the terminal
|
||||
// again to bring it up.
|
||||
InputPane::GetForCurrentView().TryShow();
|
||||
|
||||
if (!_focused)
|
||||
{
|
||||
Focus(FocusState::Pointer);
|
||||
// Save the click position that brought this control into focus
|
||||
// in case the user wants to perform a click-drag selection.
|
||||
_focusRaisedClickPos = point.Position();
|
||||
|
||||
// Save the click position here when the terminal does not have focus
|
||||
// because they might be performing a click-drag selection. Since we
|
||||
// only want to start the selection when the user moves the pointer with
|
||||
// the left mouse button held down, the PointerMovedHandler will use
|
||||
// this saved position to set the SelectionAnchor.
|
||||
_unfocusedClickPos = point.Position();
|
||||
}
|
||||
|
||||
if (ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Mouse || ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Pen)
|
||||
@@ -914,17 +844,13 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
const auto altEnabled = WI_IsFlagSet(modifiers, static_cast<uint32_t>(VirtualKeyModifiers::Menu));
|
||||
const auto shiftEnabled = WI_IsFlagSet(modifiers, static_cast<uint32_t>(VirtualKeyModifiers::Shift));
|
||||
|
||||
if (_CanSendVTMouseInput())
|
||||
{
|
||||
_TrySendMouseEvent(point);
|
||||
args.Handled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (point.Properties().IsLeftButtonPressed())
|
||||
{
|
||||
// A single left click from out of focus should only focus.
|
||||
if (_focusRaisedClickPos)
|
||||
// _unfocusedClickPos having a value signifies to us that
|
||||
// the user clicked on an unfocused terminal. We don't want
|
||||
// a single left click from out of focus to start a selection,
|
||||
// so we return fast here.
|
||||
if (_unfocusedClickPos)
|
||||
{
|
||||
args.Handled(true);
|
||||
return;
|
||||
@@ -959,17 +885,18 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
}
|
||||
else
|
||||
{
|
||||
// save location before rendering
|
||||
_terminal->SetSelectionAnchor(terminalPosition);
|
||||
}
|
||||
|
||||
_lastMouseClickTimestamp = point.Timestamp();
|
||||
_lastMouseClick = point.Timestamp();
|
||||
_lastMouseClickPos = cursorPosition;
|
||||
}
|
||||
_renderer->TriggerSelection();
|
||||
}
|
||||
else if (point.Properties().IsRightButtonPressed())
|
||||
{
|
||||
// CopyOnSelect right click always pastes
|
||||
// copyOnSelect causes right-click to always paste
|
||||
if (_terminal->IsCopyOnSelectActive() || !_terminal->IsSelectionActive())
|
||||
{
|
||||
PasteTextFromClipboard();
|
||||
@@ -1001,30 +928,19 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
const auto ptr = args.Pointer();
|
||||
const auto point = args.GetCurrentPoint(*this);
|
||||
|
||||
if (!_focused)
|
||||
{
|
||||
args.Handled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Mouse || ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Pen)
|
||||
{
|
||||
if (_CanSendVTMouseInput())
|
||||
{
|
||||
_TrySendMouseEvent(point);
|
||||
args.Handled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (point.Properties().IsLeftButtonPressed())
|
||||
{
|
||||
_clickDrag = true;
|
||||
_isClickDragSelection = true;
|
||||
|
||||
// PointerPressedHandler doesn't set the SelectionAnchor when the click was
|
||||
// from out of focus, so PointerMoved has to set it.
|
||||
if (_focusRaisedClickPos)
|
||||
// If this does not have a value, it means that PointerPressedHandler already
|
||||
// set the SelectionAnchor. If it does have a value, that means the user is
|
||||
// performing a click-drag selection on an unfocused terminal, so
|
||||
// a SelectionAnchor isn't set yet. We'll have to set it here.
|
||||
if (_unfocusedClickPos)
|
||||
{
|
||||
_terminal->SetSelectionAnchor(_GetTerminalPosition(*_focusRaisedClickPos));
|
||||
_terminal->SetSelectionAnchor(_GetTerminalPosition(*_unfocusedClickPos));
|
||||
}
|
||||
|
||||
const auto cursorPosition = point.Position();
|
||||
@@ -1095,33 +1011,22 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
void TermControl::_PointerReleasedHandler(Windows::Foundation::IInspectable const& sender,
|
||||
Input::PointerRoutedEventArgs const& args)
|
||||
{
|
||||
const auto ptr = args.Pointer();
|
||||
const auto point = args.GetCurrentPoint(*this);
|
||||
|
||||
_ReleasePointerCapture(sender, args);
|
||||
|
||||
const auto ptr = args.Pointer();
|
||||
|
||||
if (ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Mouse || ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Pen)
|
||||
{
|
||||
if (_CanSendVTMouseInput())
|
||||
{
|
||||
_TrySendMouseEvent(point);
|
||||
args.Handled(true);
|
||||
return;
|
||||
}
|
||||
const auto modifiers = static_cast<uint32_t>(args.KeyModifiers());
|
||||
// static_cast to a uint32_t because we can't use the WI_IsFlagSet
|
||||
// macro directly with a VirtualKeyModifiers
|
||||
const auto shiftEnabled = WI_IsFlagSet(modifiers, static_cast<uint32_t>(VirtualKeyModifiers::Shift));
|
||||
|
||||
// Only a left click release when copy on select is active should perform a copy.
|
||||
// Right clicks and middle clicks should not need to do anything when released.
|
||||
if (_terminal->IsCopyOnSelectActive() && point.Properties().PointerUpdateKind() == Windows::UI::Input::PointerUpdateKind::LeftButtonReleased)
|
||||
if (_terminal->IsCopyOnSelectActive())
|
||||
{
|
||||
const auto modifiers = static_cast<uint32_t>(args.KeyModifiers());
|
||||
// static_cast to a uint32_t because we can't use the WI_IsFlagSet
|
||||
// macro directly with a VirtualKeyModifiers
|
||||
const auto shiftEnabled = WI_IsFlagSet(modifiers, static_cast<uint32_t>(VirtualKeyModifiers::Shift));
|
||||
|
||||
// In a Copy on Select scenario,
|
||||
// All left click drags should copy,
|
||||
// All left clicks on a focused control should copy if a selection is active.
|
||||
if (_clickDrag || !_focusRaisedClickPos)
|
||||
// If the terminal was in focus, copy to clipboard.
|
||||
// If the terminal was unfocused AND a click-drag selection happened, copy to clipboard.
|
||||
if (!_unfocusedClickPos || (_unfocusedClickPos && _isClickDragSelection))
|
||||
{
|
||||
CopySelectionToClipboard(shiftEnabled);
|
||||
}
|
||||
@@ -1132,8 +1037,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
_touchAnchor = std::nullopt;
|
||||
}
|
||||
|
||||
_focusRaisedClickPos = std::nullopt;
|
||||
_clickDrag = false;
|
||||
_unfocusedClickPos = std::nullopt;
|
||||
_isClickDragSelection = false;
|
||||
|
||||
_TryStopAutoScroll(ptr.PointerId());
|
||||
|
||||
@@ -1150,14 +1055,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
Input::PointerRoutedEventArgs const& args)
|
||||
{
|
||||
const auto point = args.GetCurrentPoint(*this);
|
||||
|
||||
if (_CanSendVTMouseInput())
|
||||
{
|
||||
_TrySendMouseEvent(point);
|
||||
args.Handled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto delta = point.Properties().MouseWheelDelta();
|
||||
|
||||
// Get the state of the Ctrl & Shift keys
|
||||
@@ -1197,24 +1094,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
{
|
||||
auto acrylicBrush = RootGrid().Background().as<Media::AcrylicBrush>();
|
||||
acrylicBrush.TintOpacity(acrylicBrush.TintOpacity() + effectiveDelta);
|
||||
if (acrylicBrush.TintOpacity() == 1.0)
|
||||
{
|
||||
_settings.UseAcrylic(false);
|
||||
_InitializeBackgroundBrush();
|
||||
uint32_t bg = _settings.DefaultBackground();
|
||||
_BackgroundColorChanged(bg);
|
||||
}
|
||||
}
|
||||
CATCH_LOG();
|
||||
}
|
||||
else if (mouseDelta < 0)
|
||||
{
|
||||
_settings.UseAcrylic(true);
|
||||
|
||||
//Setting initial opacity set to 1 to ensure smooth transition to acrylic during mouse scroll
|
||||
_settings.TintOpacity(1.0);
|
||||
_InitializeBackgroundBrush();
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -1424,8 +1306,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
|
||||
_focused = true;
|
||||
|
||||
InputPane::GetForCurrentView().TryShow();
|
||||
|
||||
// If the searchbox is focused, we don't want TSFInputControl to think
|
||||
// it has focus so it doesn't intercept IME input. We also don't want the
|
||||
// terminal's cursor to start blinking. So, we'll just return quickly here.
|
||||
@@ -1447,7 +1327,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
if (_cursorTimer.has_value())
|
||||
{
|
||||
// When the terminal focuses, show the cursor immediately
|
||||
_terminal->SetCursorOn(true);
|
||||
_terminal->SetCursorVisible(true);
|
||||
_cursorTimer.value().Start();
|
||||
}
|
||||
_rowsToScroll = _settings.RowsToScroll();
|
||||
@@ -1479,7 +1359,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
if (_cursorTimer.has_value())
|
||||
{
|
||||
_cursorTimer.value().Stop();
|
||||
_terminal->SetCursorOn(false);
|
||||
_terminal->SetCursorVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1619,7 +1499,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
{
|
||||
return;
|
||||
}
|
||||
_terminal->SetCursorOn(!_terminal->IsCursorOn());
|
||||
_terminal->SetCursorVisible(!_terminal->IsCursorVisible());
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -2230,7 +2110,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
{
|
||||
// if click occurred at a different location or past the multiClickTimer...
|
||||
Timestamp delta;
|
||||
THROW_IF_FAILED(UInt64Sub(clickTime, _lastMouseClickTimestamp, &delta));
|
||||
THROW_IF_FAILED(UInt64Sub(clickTime, _lastMouseClick, &delta));
|
||||
if (clickPos != _lastMouseClickPos || delta > _multiClickTimer)
|
||||
{
|
||||
// exit early. This is a single click.
|
||||
|
||||
@@ -155,12 +155,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// imported from WinUser
|
||||
// Used for PointerPoint.Timestamp Property (https://docs.microsoft.com/en-us/uwp/api/windows.ui.input.pointerpoint.timestamp#Windows_UI_Input_PointerPoint_Timestamp)
|
||||
Timestamp _multiClickTimer;
|
||||
Timestamp _lastMouseClickTimestamp;
|
||||
Timestamp _lastMouseClick;
|
||||
unsigned int _multiClickCounter;
|
||||
std::optional<winrt::Windows::Foundation::Point> _lastMouseClickPos;
|
||||
|
||||
std::optional<winrt::Windows::Foundation::Point> _focusRaisedClickPos;
|
||||
bool _clickDrag;
|
||||
std::optional<winrt::Windows::Foundation::Point> _unfocusedClickPos;
|
||||
bool _isClickDragSelection;
|
||||
|
||||
winrt::Windows::UI::Xaml::Controls::SwapChainPanel::LayoutUpdated_revoker _layoutUpdatedRevoker;
|
||||
|
||||
@@ -211,8 +210,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
|
||||
::Microsoft::Terminal::Core::ControlKeyStates _GetPressedModifierKeys() const;
|
||||
bool _TrySendKeyEvent(const WORD vkey, const WORD scanCode, ::Microsoft::Terminal::Core::ControlKeyStates modifiers);
|
||||
bool _TrySendMouseEvent(Windows::UI::Input::PointerPoint const& point);
|
||||
bool _CanSendVTMouseInput();
|
||||
|
||||
const COORD _GetTerminalPosition(winrt::Windows::Foundation::Point cursorPosition);
|
||||
const unsigned int _NumberOfClicks(winrt::Windows::Foundation::Point clickPos, Timestamp clickTime);
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
#include <winrt/Windows.Graphics.Display.h>
|
||||
#include <winrt/windows.ui.core.h>
|
||||
#include <winrt/Windows.ui.input.h>
|
||||
#include <winrt/Windows.UI.ViewManagement.h>
|
||||
#include <winrt/Windows.UI.Xaml.h>
|
||||
#include <winrt/Windows.UI.Xaml.Automation.Peers.h>
|
||||
#include <winrt/Windows.UI.Text.Core.h>
|
||||
|
||||
@@ -28,9 +28,7 @@ namespace Microsoft::Terminal::Core
|
||||
|
||||
virtual bool SetCursorPosition(short x, short y) noexcept = 0;
|
||||
virtual COORD GetCursorPosition() noexcept = 0;
|
||||
virtual bool SetCursorVisibility(const bool visible) noexcept = 0;
|
||||
virtual bool CursorLineFeed(const bool withReturn) noexcept = 0;
|
||||
virtual bool EnableCursorBlinking(const bool enable) noexcept = 0;
|
||||
|
||||
virtual bool DeleteCharacter(const size_t count) noexcept = 0;
|
||||
virtual bool InsertCharacter(const size_t count) noexcept = 0;
|
||||
@@ -47,17 +45,6 @@ namespace Microsoft::Terminal::Core
|
||||
virtual bool SetDefaultForeground(const DWORD color) noexcept = 0;
|
||||
virtual bool SetDefaultBackground(const DWORD color) noexcept = 0;
|
||||
|
||||
virtual bool SetCursorKeysMode(const bool applicationMode) noexcept = 0;
|
||||
virtual bool SetKeypadMode(const bool applicationMode) noexcept = 0;
|
||||
virtual bool EnableVT200MouseMode(const bool enabled) noexcept = 0;
|
||||
virtual bool EnableUTF8ExtendedMouseMode(const bool enabled) noexcept = 0;
|
||||
virtual bool EnableSGRExtendedMouseMode(const bool enabled) noexcept = 0;
|
||||
virtual bool EnableButtonEventMouseMode(const bool enabled) noexcept = 0;
|
||||
virtual bool EnableAnyEventMouseMode(const bool enabled) noexcept = 0;
|
||||
virtual bool EnableAlternateScrollMode(const bool enabled) noexcept = 0;
|
||||
|
||||
virtual bool IsVtInputEnabled() const = 0;
|
||||
|
||||
protected:
|
||||
ITerminalApi() = default;
|
||||
};
|
||||
|
||||
@@ -17,7 +17,6 @@ namespace Microsoft::Terminal::Core
|
||||
ITerminalInput& operator=(ITerminalInput&&) = default;
|
||||
|
||||
virtual bool SendKeyEvent(const WORD vkey, const WORD scanCode, const ControlKeyStates states) = 0;
|
||||
virtual bool SendMouseEvent(const COORD viewportPos, const unsigned int uiButton, const ControlKeyStates states, const short wheelDelta) = 0;
|
||||
virtual bool SendCharEvent(const wchar_t ch) = 0;
|
||||
|
||||
// void SendMouseEvent(uint row, uint col, KeyModifiers modifiers);
|
||||
|
||||
@@ -173,173 +173,25 @@ void Terminal::UpdateSettings(winrt::Microsoft::Terminal::Settings::ICoreSetting
|
||||
{
|
||||
return S_FALSE;
|
||||
}
|
||||
const auto dx = viewportSize.X - oldDimensions.X;
|
||||
|
||||
const auto oldTop = _mutableViewport.Top();
|
||||
|
||||
const short newBufferHeight = viewportSize.Y + _scrollbackLines;
|
||||
COORD bufferSize{ viewportSize.X, newBufferHeight };
|
||||
RETURN_IF_FAILED(_buffer->ResizeTraditional(bufferSize));
|
||||
|
||||
// Save cursor's relative height versus the viewport
|
||||
const short sCursorHeightInViewportBefore = _buffer->GetCursor().GetPosition().Y - _mutableViewport.Top();
|
||||
|
||||
// This will be used to determine where the viewport should be in the new buffer.
|
||||
const short oldViewportTop = _mutableViewport.Top();
|
||||
short newViewportTop = oldViewportTop;
|
||||
short newVisibleTop = ::base::saturated_cast<short>(_VisibleStartIndex());
|
||||
|
||||
// If the original buffer had _no_ scroll offset, then we should be at the
|
||||
// bottom in the new buffer as well. Track that case now.
|
||||
const bool originalOffsetWasZero = _scrollOffset == 0;
|
||||
|
||||
// First allocate a new text buffer to take the place of the current one.
|
||||
std::unique_ptr<TextBuffer> newTextBuffer;
|
||||
try
|
||||
{
|
||||
newTextBuffer = std::make_unique<TextBuffer>(bufferSize,
|
||||
_buffer->GetCurrentAttributes(),
|
||||
0, // temporarily set size to 0 so it won't render.
|
||||
_buffer->GetRenderTarget());
|
||||
|
||||
// Build a PositionInformation to track the position of both the top of
|
||||
// the mutable viewport and the top of the visible viewport in the new
|
||||
// buffer.
|
||||
// * the new value of mutableViewportTop will be used to figure out
|
||||
// where we should place the mutable viewport in the new buffer. This
|
||||
// requires a bit of trickiness to remain consistent with conpty's
|
||||
// buffer (as seen below).
|
||||
// * the new value of visibleViewportTop will be used to calculate the
|
||||
// new scrollOffsett in the new buffer, so that the visible lines on
|
||||
// the screen remain roughly the same.
|
||||
TextBuffer::PositionInformation oldRows{ 0 };
|
||||
oldRows.mutableViewportTop = oldViewportTop;
|
||||
oldRows.visibleViewportTop = newVisibleTop;
|
||||
|
||||
const std::optional<short> oldViewStart{ oldViewportTop };
|
||||
RETURN_IF_FAILED(TextBuffer::Reflow(*_buffer.get(),
|
||||
*newTextBuffer.get(),
|
||||
_mutableViewport,
|
||||
{ oldRows }));
|
||||
|
||||
newViewportTop = oldRows.mutableViewportTop;
|
||||
newVisibleTop = oldRows.visibleViewportTop;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
|
||||
// Conpty resizes a little oddly - if the height decreased, and there were
|
||||
// blank lines at the bottom, those lines will get trimmed. If there's not
|
||||
// blank lines, then the top will get "shifted down", moving the top line
|
||||
// into scrollback. See GH#3490 for more details.
|
||||
//
|
||||
// If the final position in the buffer is on the bottom row of the new
|
||||
// viewport, then we're going to need to move the top down. Otherwise, move
|
||||
// the bottom up.
|
||||
//
|
||||
// There are also important things to consider with line wrapping.
|
||||
// * If a line in scrollback wrapped that didn't previously, we'll need to
|
||||
// make sure to have the new viewport down another line. This will cause
|
||||
// our top to move down.
|
||||
// * If a line _in the viewport_ wrapped that didn't previously, then the
|
||||
// conpty buffer will also have that wrapped line, and will move the
|
||||
// cursor & text down a line in response. This causes our bottom to move
|
||||
// down.
|
||||
//
|
||||
// We're going to use a combo of both these things to calculate where the
|
||||
// new viewport should be. To keep in sync with conpty, we'll need to make
|
||||
// sure that any lines that entered the scrollback _stay in scrollback_. We
|
||||
// do that by taking the max of
|
||||
// * Where the old top line in the viewport exists in the new buffer (as
|
||||
// calculated by TextBuffer::Reflow)
|
||||
// * Where the bottom of the text in the new buffer is (and using that to
|
||||
// calculate another proposed top location).
|
||||
|
||||
const COORD newCursorPos = newTextBuffer->GetCursor().GetPosition();
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 26496) // cpp core checks wants this const, but it's assigned immediately below...
|
||||
COORD newLastChar = newCursorPos;
|
||||
try
|
||||
{
|
||||
newLastChar = newTextBuffer->GetLastNonSpaceCharacter();
|
||||
}
|
||||
CATCH_LOG();
|
||||
#pragma warning(pop)
|
||||
|
||||
const auto maxRow = std::max(newLastChar.Y, newCursorPos.Y);
|
||||
|
||||
const short proposedTopFromLastLine = ::base::saturated_cast<short>(maxRow - viewportSize.Y + 1);
|
||||
const short proposedTopFromScrollback = newViewportTop;
|
||||
|
||||
short proposedTop = std::max(proposedTopFromLastLine,
|
||||
proposedTopFromScrollback);
|
||||
|
||||
// If we're using the new location of the old top line to place the
|
||||
// viewport, we might need to make an adjustment to it.
|
||||
//
|
||||
// We're using the last cell of the line to calculate where the top line is
|
||||
// in the new buffer. If that line wrapped, then all the lines below it
|
||||
// shifted down in the buffer. If there's space for all those lines in the
|
||||
// conpty buffer, then the originally unwrapped top line will _still_ be in
|
||||
// the buffer. In that case, don't stick to the _end_ of the old top line,
|
||||
// instead stick to the _start_, which is one line up.
|
||||
//
|
||||
// We can know if there's space in the conpty buffer by checking if the
|
||||
// maxRow (the highest row we've written text to) is above the viewport from
|
||||
// this proposed top position.
|
||||
if (proposedTop == proposedTopFromScrollback)
|
||||
{
|
||||
const auto proposedViewFromTop = Viewport::FromDimensions({ 0, proposedTopFromScrollback }, viewportSize);
|
||||
if (maxRow < proposedViewFromTop.BottomInclusive())
|
||||
{
|
||||
if (dx < 0 && proposedTop > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
auto row = newTextBuffer->GetRowByOffset(::base::saturated_cast<short>(proposedTop - 1));
|
||||
if (row.GetCharRow().WasWrapForced())
|
||||
{
|
||||
proposedTop--;
|
||||
}
|
||||
}
|
||||
CATCH_LOG();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the new bottom would be higher than the last row of text, then we
|
||||
// definitely want to use the last row of text to determine where the
|
||||
// viewport should be.
|
||||
const auto proposedViewFromTop = Viewport::FromDimensions({ 0, proposedTopFromScrollback }, viewportSize);
|
||||
if (maxRow > proposedViewFromTop.BottomInclusive())
|
||||
{
|
||||
proposedTop = proposedTopFromLastLine;
|
||||
}
|
||||
|
||||
// Make sure the proposed viewport is within the bounds of the buffer.
|
||||
// First make sure the top is >=0
|
||||
proposedTop = std::max(static_cast<short>(0), proposedTop);
|
||||
|
||||
// If the new bottom would be below the bottom of the buffer, then slide the
|
||||
// top up so that we'll still fit within the buffer.
|
||||
auto proposedTop = oldTop;
|
||||
const auto newView = Viewport::FromDimensions({ 0, proposedTop }, viewportSize);
|
||||
const auto proposedBottom = newView.BottomExclusive();
|
||||
// If the new bottom would be below the bottom of the buffer, then slide the
|
||||
// top up so that we'll still fit within the buffer.
|
||||
if (proposedBottom > bufferSize.Y)
|
||||
{
|
||||
proposedTop = ::base::saturated_cast<short>(proposedTop - (proposedBottom - bufferSize.Y));
|
||||
proposedTop -= (proposedBottom - bufferSize.Y);
|
||||
}
|
||||
|
||||
_mutableViewport = Viewport::FromDimensions({ 0, proposedTop }, viewportSize);
|
||||
|
||||
_buffer.swap(newTextBuffer);
|
||||
|
||||
// GH#3494: Maintain scrollbar position during resize
|
||||
// Make sure that we don't scroll past the mutableViewport at the bottom of the buffer
|
||||
newVisibleTop = std::min(newVisibleTop, _mutableViewport.Top());
|
||||
// Make sure we don't scroll past the top of the scrollback
|
||||
newVisibleTop = std::max<short>(newVisibleTop, 0);
|
||||
|
||||
// If the old scrolloffset was 0, then we weren't scrolled back at all
|
||||
// before, and shouldn't be now either.
|
||||
_scrollOffset = originalOffsetWasZero ? 0 : _mutableViewport.Top() - newVisibleTop;
|
||||
_scrollOffset = 0;
|
||||
_NotifyScrollEvent();
|
||||
|
||||
return S_OK;
|
||||
@@ -370,17 +222,6 @@ void Terminal::TrySnapOnInput()
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Relays if we are tracking mouse input
|
||||
// Parameters:
|
||||
// - <none>
|
||||
// Return value:
|
||||
// - true, if we are tracking mouse input. False, otherwise
|
||||
bool Terminal::IsTrackingMouseInput() const noexcept
|
||||
{
|
||||
return _terminalInput->IsTrackingMouseInput();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Send this particular key event to the terminal. The terminal will translate
|
||||
// the key and the modifiers pressed into the appropriate VT sequence for that
|
||||
@@ -443,32 +284,6 @@ bool Terminal::SendKeyEvent(const WORD vkey, const WORD scanCode, const ControlK
|
||||
return translated && manuallyHandled;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Send this particular mouse event to the terminal. The terminal will translate
|
||||
// the button and the modifiers pressed into the appropriate VT sequence for that
|
||||
// mouse event. If we do translate the key, we'll return true. In that case, the
|
||||
// event should NOT be processed any further. If we return false, the event
|
||||
// was NOT translated, and we should instead use the event normally
|
||||
// Arguments:
|
||||
// - viewportPos: the position of the mouse event relative to the viewport origin.
|
||||
// - uiButton: the WM mouse button event code
|
||||
// - states: The Microsoft::Terminal::Core::ControlKeyStates representing the modifier key states.
|
||||
// - wheelDelta: the amount that the scroll wheel changed (should be 0 unless button is a WM_MOUSE*WHEEL)
|
||||
// Return Value:
|
||||
// - true if we translated the key event, and it should not be processed any further.
|
||||
// - false if we did not translate the key, and it should be processed into a character.
|
||||
bool Terminal::SendMouseEvent(const COORD viewportPos, const unsigned int uiButton, const ControlKeyStates states, const short wheelDelta)
|
||||
{
|
||||
// viewportPos must be within the dimensions of the viewport
|
||||
const auto viewportDimensions = _mutableViewport.Dimensions();
|
||||
if (viewportPos.X < 0 || viewportPos.X >= viewportDimensions.X || viewportPos.Y < 0 || viewportPos.Y >= viewportDimensions.Y)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return _terminalInput->HandleMouse(viewportPos, uiButton, GET_KEYSTATE_WPARAM(states.Value()), wheelDelta);
|
||||
}
|
||||
|
||||
bool Terminal::SendCharEvent(const wchar_t ch)
|
||||
{
|
||||
return _terminalInput->HandleChar(ch);
|
||||
@@ -641,15 +456,18 @@ void Terminal::_WriteBuffer(const std::wstring_view& stringView)
|
||||
// Try the character again.
|
||||
i--;
|
||||
|
||||
// If we write the last cell of the row here, TextBuffer::Write will
|
||||
// mark this line as wrapped for us. If the next character we
|
||||
// process is a newline, the Terminal::CursorLineFeed will unmark
|
||||
// this line as wrapped.
|
||||
// Mark the line we're currently on as wrapped
|
||||
|
||||
// TODO: GH#780 - This should really be a _deferred_ newline. If
|
||||
// the next character to come in is a newline or a cursor
|
||||
// movement or anything, then we should _not_ wrap this line
|
||||
// here.
|
||||
//
|
||||
// This is more WriteCharsLegacy2ElectricBoogaloo work. I'm
|
||||
// leaving it like this for now - it'll break for lines that
|
||||
// _exactly_ wrap, but we can't re-wrap lines now anyways, so it
|
||||
// doesn't matter.
|
||||
_buffer->GetRowByOffset(cursorPosBefore.Y).GetCharRow().SetWrapForced(true);
|
||||
}
|
||||
|
||||
_AdjustCursorPosition(proposedCursorPosition);
|
||||
@@ -769,16 +587,13 @@ try
|
||||
CATCH_LOG()
|
||||
|
||||
// Method Description:
|
||||
// - Sets the cursor to be currently on. On/Off is tracked independently of
|
||||
// cursor visibility (hidden/visible). On/off is controlled by the cursor
|
||||
// blinker. Visibility is usually controlled by the client application. If the
|
||||
// cursor is hidden, then the cursor will remain hidden. If the cursor is
|
||||
// Visible, then it will immediately become visible.
|
||||
// - Sets the visibility of the text cursor.
|
||||
// Arguments:
|
||||
// - isVisible: whether the cursor should be visible
|
||||
void Terminal::SetCursorOn(const bool isOn) noexcept
|
||||
void Terminal::SetCursorVisible(const bool isVisible) noexcept
|
||||
{
|
||||
_buffer->GetCursor().SetIsOn(isOn);
|
||||
auto& cursor = _buffer->GetCursor();
|
||||
cursor.SetIsVisible(isVisible);
|
||||
}
|
||||
|
||||
bool Terminal::IsCursorBlinkingAllowed() const noexcept
|
||||
|
||||
@@ -33,7 +33,6 @@ namespace Microsoft::Terminal::Core
|
||||
namespace TerminalCoreUnitTests
|
||||
{
|
||||
class TerminalBufferTests;
|
||||
class TerminalApiTest;
|
||||
class ConptyRoundtripTests;
|
||||
};
|
||||
#endif
|
||||
@@ -85,8 +84,6 @@ public:
|
||||
bool ReverseText(bool reversed) noexcept override;
|
||||
bool SetCursorPosition(short x, short y) noexcept override;
|
||||
COORD GetCursorPosition() noexcept override;
|
||||
bool SetCursorVisibility(const bool visible) noexcept override;
|
||||
bool EnableCursorBlinking(const bool enable) noexcept override;
|
||||
bool CursorLineFeed(const bool withReturn) noexcept override;
|
||||
bool DeleteCharacter(const size_t count) noexcept override;
|
||||
bool InsertCharacter(const size_t count) noexcept override;
|
||||
@@ -98,23 +95,11 @@ public:
|
||||
bool SetCursorStyle(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::CursorStyle cursorStyle) noexcept override;
|
||||
bool SetDefaultForeground(const COLORREF color) noexcept override;
|
||||
bool SetDefaultBackground(const COLORREF color) noexcept override;
|
||||
|
||||
bool SetCursorKeysMode(const bool applicationMode) noexcept override;
|
||||
bool SetKeypadMode(const bool applicationMode) noexcept override;
|
||||
bool EnableVT200MouseMode(const bool enabled) noexcept override;
|
||||
bool EnableUTF8ExtendedMouseMode(const bool enabled) noexcept override;
|
||||
bool EnableSGRExtendedMouseMode(const bool enabled) noexcept override;
|
||||
bool EnableButtonEventMouseMode(const bool enabled) noexcept override;
|
||||
bool EnableAnyEventMouseMode(const bool enabled) noexcept override;
|
||||
bool EnableAlternateScrollMode(const bool enabled) noexcept override;
|
||||
|
||||
bool IsVtInputEnabled() const noexcept override;
|
||||
#pragma endregion
|
||||
|
||||
#pragma region ITerminalInput
|
||||
// These methods are defined in Terminal.cpp
|
||||
bool SendKeyEvent(const WORD vkey, const WORD scanCode, const Microsoft::Terminal::Core::ControlKeyStates states) override;
|
||||
bool SendMouseEvent(const COORD viewportPos, const unsigned int uiButton, const ControlKeyStates states, const short wheelDelta) override;
|
||||
bool SendCharEvent(const wchar_t ch) override;
|
||||
|
||||
[[nodiscard]] HRESULT UserResize(const COORD viewportSize) noexcept override;
|
||||
@@ -122,7 +107,6 @@ public:
|
||||
int GetScrollOffset() noexcept override;
|
||||
|
||||
void TrySnapOnInput() override;
|
||||
bool IsTrackingMouseInput() const noexcept;
|
||||
#pragma endregion
|
||||
|
||||
#pragma region IBaseData(base to IRenderData and IUiaData)
|
||||
@@ -148,7 +132,6 @@ public:
|
||||
CursorType GetCursorStyle() const noexcept override;
|
||||
COLORREF GetCursorColor() const noexcept override;
|
||||
bool IsCursorDoubleWidth() const noexcept override;
|
||||
bool IsScreenReversed() const noexcept override;
|
||||
const std::vector<Microsoft::Console::Render::RenderOverlay> GetOverlays() const noexcept override;
|
||||
const bool IsGridLineDrawingAllowed() noexcept override;
|
||||
#pragma endregion
|
||||
@@ -169,7 +152,7 @@ public:
|
||||
void SetScrollPositionChangedCallback(std::function<void(const int, const int, const int)> pfn) noexcept;
|
||||
void SetBackgroundCallback(std::function<void(const uint32_t)> pfn) noexcept;
|
||||
|
||||
void SetCursorOn(const bool isOn) noexcept;
|
||||
void SetCursorVisible(const bool isVisible) noexcept;
|
||||
bool IsCursorBlinkingAllowed() const noexcept;
|
||||
|
||||
#pragma region TextSelection
|
||||
@@ -279,7 +262,6 @@ private:
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
friend class TerminalCoreUnitTests::TerminalBufferTests;
|
||||
friend class TerminalCoreUnitTests::TerminalApiTest;
|
||||
friend class TerminalCoreUnitTests::ConptyRoundtripTests;
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -142,11 +142,6 @@ bool Terminal::CursorLineFeed(const bool withReturn) noexcept
|
||||
try
|
||||
{
|
||||
auto cursorPos = _buffer->GetCursor().GetPosition();
|
||||
|
||||
// since we explicitly just moved down a row, clear the wrap status on the
|
||||
// row we just came from
|
||||
_buffer->GetRowByOffset(cursorPos.Y).GetCharRow().SetWrapForced(false);
|
||||
|
||||
cursorPos.Y++;
|
||||
if (withReturn)
|
||||
{
|
||||
@@ -519,77 +514,3 @@ try
|
||||
return true;
|
||||
}
|
||||
CATCH_LOG_RETURN_FALSE()
|
||||
|
||||
bool Terminal::SetCursorKeysMode(const bool applicationMode) noexcept
|
||||
{
|
||||
_terminalInput->ChangeCursorKeysMode(applicationMode);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Terminal::SetKeypadMode(const bool applicationMode) noexcept
|
||||
{
|
||||
_terminalInput->ChangeKeypadMode(applicationMode);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Terminal::EnableVT200MouseMode(const bool enabled) noexcept
|
||||
{
|
||||
_terminalInput->EnableDefaultTracking(enabled);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Terminal::EnableUTF8ExtendedMouseMode(const bool enabled) noexcept
|
||||
{
|
||||
_terminalInput->SetUtf8ExtendedMode(enabled);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Terminal::EnableSGRExtendedMouseMode(const bool enabled) noexcept
|
||||
{
|
||||
_terminalInput->SetSGRExtendedMode(enabled);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Terminal::EnableButtonEventMouseMode(const bool enabled) noexcept
|
||||
{
|
||||
_terminalInput->EnableButtonEventTracking(enabled);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Terminal::EnableAnyEventMouseMode(const bool enabled) noexcept
|
||||
{
|
||||
_terminalInput->EnableAnyEventTracking(enabled);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Terminal::EnableAlternateScrollMode(const bool enabled) noexcept
|
||||
{
|
||||
_terminalInput->EnableAlternateScroll(enabled);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Terminal::IsVtInputEnabled() const noexcept
|
||||
{
|
||||
// We should never be getting this call in Terminal.
|
||||
FAIL_FAST();
|
||||
}
|
||||
|
||||
bool Terminal::SetCursorVisibility(const bool visible) noexcept
|
||||
{
|
||||
_buffer->GetCursor().SetIsVisible(visible);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Terminal::EnableCursorBlinking(const bool enable) noexcept
|
||||
{
|
||||
_buffer->GetCursor().SetBlinkingAllowed(enable);
|
||||
|
||||
// GH#2642 - From what we've gathered from other terminals, when blinking is
|
||||
// disabled, the cursor should remain On always, and have the visibility
|
||||
// controlled by the IsVisible property. So when you do a printf "\e[?12l"
|
||||
// to disable blinking, the cursor stays stuck On. At this point, only the
|
||||
// cursor visibility property controls whether the user can see it or not.
|
||||
// (Yes, the cursor can be On and NOT Visible)
|
||||
_buffer->GetCursor().SetIsOn(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -47,16 +47,6 @@ try
|
||||
}
|
||||
CATCH_LOG_RETURN_FALSE()
|
||||
|
||||
bool TerminalDispatch::CursorVisibility(const bool isVisible) noexcept
|
||||
{
|
||||
return _terminalApi.SetCursorVisibility(isVisible);
|
||||
}
|
||||
|
||||
bool TerminalDispatch::EnableCursorBlinking(const bool enable) noexcept
|
||||
{
|
||||
return _terminalApi.EnableCursorBlinking(enable);
|
||||
}
|
||||
|
||||
bool TerminalDispatch::CursorForward(const size_t distance) noexcept
|
||||
try
|
||||
{
|
||||
@@ -223,279 +213,3 @@ try
|
||||
return _terminalApi.EraseInDisplay(eraseType);
|
||||
}
|
||||
CATCH_LOG_RETURN_FALSE()
|
||||
|
||||
// - DECKPAM, DECKPNM - Sets the keypad input mode to either Application mode or Numeric mode (true, false respectively)
|
||||
// Arguments:
|
||||
// - applicationMode - set to true to enable Application Mode Input, false for Numeric Mode Input.
|
||||
// Return Value:
|
||||
// - True if handled successfully. False otherwise.
|
||||
bool TerminalDispatch::SetKeypadMode(const bool fApplicationMode) noexcept
|
||||
{
|
||||
_terminalApi.SetKeypadMode(fApplicationMode);
|
||||
return true;
|
||||
}
|
||||
|
||||
// - DECCKM - Sets the cursor keys input mode to either Application mode or Normal mode (true, false respectively)
|
||||
// Arguments:
|
||||
// - applicationMode - set to true to enable Application Mode Input, false for Normal Mode Input.
|
||||
// Return Value:
|
||||
// - True if handled successfully. False otherwise.
|
||||
bool TerminalDispatch::SetCursorKeysMode(const bool applicationMode) noexcept
|
||||
{
|
||||
_terminalApi.SetCursorKeysMode(applicationMode);
|
||||
return true;
|
||||
}
|
||||
|
||||
//Routine Description:
|
||||
// Enable VT200 Mouse Mode - Enables/disables the mouse input handler in default tracking mode.
|
||||
//Arguments:
|
||||
// - enabled - true to enable, false to disable.
|
||||
// Return value:
|
||||
// True if handled successfully. False otherwise.
|
||||
bool TerminalDispatch::EnableVT200MouseMode(const bool enabled) noexcept
|
||||
{
|
||||
_terminalApi.EnableVT200MouseMode(enabled);
|
||||
return true;
|
||||
}
|
||||
|
||||
//Routine Description:
|
||||
// Enable UTF-8 Extended Encoding - this changes the encoding scheme for sequences
|
||||
// emitted by the mouse input handler. Does not enable/disable mouse mode on its own.
|
||||
//Arguments:
|
||||
// - enabled - true to enable, false to disable.
|
||||
// Return value:
|
||||
// True if handled successfully. False otherwise.
|
||||
bool TerminalDispatch::EnableUTF8ExtendedMouseMode(const bool enabled) noexcept
|
||||
{
|
||||
_terminalApi.EnableUTF8ExtendedMouseMode(enabled);
|
||||
return true;
|
||||
}
|
||||
|
||||
//Routine Description:
|
||||
// Enable SGR Extended Encoding - this changes the encoding scheme for sequences
|
||||
// emitted by the mouse input handler. Does not enable/disable mouse mode on its own.
|
||||
//Arguments:
|
||||
// - enabled - true to enable, false to disable.
|
||||
// Return value:
|
||||
// True if handled successfully. False otherwise.
|
||||
bool TerminalDispatch::EnableSGRExtendedMouseMode(const bool enabled) noexcept
|
||||
{
|
||||
_terminalApi.EnableSGRExtendedMouseMode(enabled);
|
||||
return true;
|
||||
}
|
||||
|
||||
//Routine Description:
|
||||
// Enable Button Event mode - send mouse move events WITH A BUTTON PRESSED to the input.
|
||||
//Arguments:
|
||||
// - enabled - true to enable, false to disable.
|
||||
// Return value:
|
||||
// True if handled successfully. False otherwise.
|
||||
bool TerminalDispatch::EnableButtonEventMouseMode(const bool enabled) noexcept
|
||||
{
|
||||
_terminalApi.EnableButtonEventMouseMode(enabled);
|
||||
return true;
|
||||
}
|
||||
|
||||
//Routine Description:
|
||||
// Enable Any Event mode - send all mouse events to the input.
|
||||
|
||||
//Arguments:
|
||||
// - enabled - true to enable, false to disable.
|
||||
// Return value:
|
||||
// True if handled successfully. False otherwise.
|
||||
bool TerminalDispatch::EnableAnyEventMouseMode(const bool enabled) noexcept
|
||||
{
|
||||
_terminalApi.EnableAnyEventMouseMode(enabled);
|
||||
return true;
|
||||
}
|
||||
|
||||
//Routine Description:
|
||||
// Enable Alternate Scroll Mode - When in the Alt Buffer, send CUP and CUD on
|
||||
// scroll up/down events instead of the usual sequences
|
||||
//Arguments:
|
||||
// - enabled - true to enable, false to disable.
|
||||
// Return value:
|
||||
// True if handled successfully. False otherwise.
|
||||
bool TerminalDispatch::EnableAlternateScroll(const bool enabled) noexcept
|
||||
{
|
||||
_terminalApi.EnableAlternateScrollMode(enabled);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TerminalDispatch::SetPrivateModes(const std::basic_string_view<DispatchTypes::PrivateModeParams> params) noexcept
|
||||
{
|
||||
return _SetResetPrivateModes(params, true);
|
||||
}
|
||||
|
||||
bool TerminalDispatch::ResetPrivateModes(const std::basic_string_view<DispatchTypes::PrivateModeParams> params) noexcept
|
||||
{
|
||||
return _SetResetPrivateModes(params, false);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Generalized handler for the setting/resetting of DECSET/DECRST parameters.
|
||||
// All params in the rgParams will attempt to be executed, even if one
|
||||
// fails, to allow us to successfully re/set params that are chained with
|
||||
// params we don't yet support.
|
||||
// Arguments:
|
||||
// - params - array of params to set/reset
|
||||
// - enable - True for set, false for unset.
|
||||
// Return Value:
|
||||
// - True if ALL params were handled successfully. False otherwise.
|
||||
bool TerminalDispatch::_SetResetPrivateModes(const std::basic_string_view<DispatchTypes::PrivateModeParams> params, const bool enable) noexcept
|
||||
{
|
||||
// because the user might chain together params we don't support with params we DO support, execute all
|
||||
// params in the sequence, and only return failure if we failed at least one of them
|
||||
size_t failures = 0;
|
||||
for (const auto& p : params)
|
||||
{
|
||||
failures += _PrivateModeParamsHelper(p, enable) ? 0 : 1; // increment the number of failures if we fail.
|
||||
}
|
||||
return failures == 0;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Support routine for routing private mode parameters to be set/reset as flags
|
||||
// Arguments:
|
||||
// - params - array of params to set/reset
|
||||
// - enable - True for set, false for unset.
|
||||
// Return Value:
|
||||
// - True if handled successfully. False otherwise.
|
||||
bool TerminalDispatch::_PrivateModeParamsHelper(const DispatchTypes::PrivateModeParams param, const bool enable) noexcept
|
||||
{
|
||||
bool success = false;
|
||||
switch (param)
|
||||
{
|
||||
case DispatchTypes::PrivateModeParams::DECCKM_CursorKeysMode:
|
||||
// set - Enable Application Mode, reset - Normal mode
|
||||
success = SetCursorKeysMode(enable);
|
||||
break;
|
||||
case DispatchTypes::PrivateModeParams::VT200_MOUSE_MODE:
|
||||
success = EnableVT200MouseMode(enable);
|
||||
break;
|
||||
case DispatchTypes::PrivateModeParams::BUTTON_EVENT_MOUSE_MODE:
|
||||
success = EnableButtonEventMouseMode(enable);
|
||||
break;
|
||||
case DispatchTypes::PrivateModeParams::ANY_EVENT_MOUSE_MODE:
|
||||
success = EnableAnyEventMouseMode(enable);
|
||||
break;
|
||||
case DispatchTypes::PrivateModeParams::UTF8_EXTENDED_MODE:
|
||||
success = EnableUTF8ExtendedMouseMode(enable);
|
||||
break;
|
||||
case DispatchTypes::PrivateModeParams::SGR_EXTENDED_MODE:
|
||||
success = EnableSGRExtendedMouseMode(enable);
|
||||
break;
|
||||
case DispatchTypes::PrivateModeParams::ALTERNATE_SCROLL:
|
||||
success = EnableAlternateScroll(enable);
|
||||
break;
|
||||
case DispatchTypes::PrivateModeParams::DECTCEM_TextCursorEnableMode:
|
||||
success = CursorVisibility(enable);
|
||||
break;
|
||||
case DispatchTypes::PrivateModeParams::ATT610_StartCursorBlink:
|
||||
success = EnableCursorBlinking(enable);
|
||||
break;
|
||||
default:
|
||||
// If no functions to call, overall dispatch was a failure.
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
bool TerminalDispatch::SoftReset() noexcept
|
||||
{
|
||||
// TODO:GH#1883 much of this method is not yet implemented in the Terminal,
|
||||
// because the Terminal _doesn't need to_ yet. The terminal is only ever
|
||||
// connected to conpty, so it doesn't implement most of these things that
|
||||
// Hard/Soft Reset would reset. As those things are implemented, they should
|
||||
|
||||
// also get cleared here.
|
||||
//
|
||||
// This code is left here (from its original form in conhost) as a reminder
|
||||
// of what needs to be done.
|
||||
|
||||
bool success = CursorVisibility(true); // Cursor enabled.
|
||||
// if (success)
|
||||
// {
|
||||
// success = SetOriginMode(false); // Absolute cursor addressing.
|
||||
// }
|
||||
// if (success)
|
||||
// {
|
||||
// success = SetAutoWrapMode(true); // Wrap at end of line.
|
||||
// }
|
||||
if (success)
|
||||
{
|
||||
success = SetCursorKeysMode(false); // Normal characters.
|
||||
}
|
||||
if (success)
|
||||
{
|
||||
success = SetKeypadMode(false); // Numeric characters.
|
||||
}
|
||||
// if (success)
|
||||
// {
|
||||
// // Top margin = 1; bottom margin = page length.
|
||||
// success = _DoSetTopBottomScrollingMargins(0, 0);
|
||||
// }
|
||||
// if (success)
|
||||
// {
|
||||
// success = DesignateCharset(DispatchTypes::VTCharacterSets::USASCII); // Default Charset
|
||||
// }
|
||||
if (success)
|
||||
{
|
||||
const auto opt = DispatchTypes::GraphicsOptions::Off;
|
||||
success = SetGraphicsRendition({ &opt, 1 }); // Normal rendition.
|
||||
}
|
||||
// if (success)
|
||||
// {
|
||||
// // Reset the saved cursor state.
|
||||
// // Note that XTerm only resets the main buffer state, but that
|
||||
// // seems likely to be a bug. Most other terminals reset both.
|
||||
// _savedCursorState.at(0) = {}; // Main buffer
|
||||
// _savedCursorState.at(1) = {}; // Alt buffer
|
||||
// }
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool TerminalDispatch::HardReset() noexcept
|
||||
{
|
||||
// TODO:GH#1883 much of this method is not yet implemented in the Terminal,
|
||||
// because the Terminal _doesn't need to_ yet. The terminal is only ever
|
||||
// connected to conpty, so it doesn't implement most of these things that
|
||||
// Hard/Soft Reset would reset. As those things ar implemented, they should
|
||||
// also get cleared here.
|
||||
//
|
||||
// This code is left here (from its original form in conhost) as a reminder
|
||||
// of what needs to be done.
|
||||
|
||||
// Sets the SGR state to normal - this must be done before EraseInDisplay
|
||||
// to ensure that it clears with the default background color.
|
||||
bool success = SoftReset();
|
||||
|
||||
// Clears the screen - Needs to be done in two operations.
|
||||
if (success)
|
||||
{
|
||||
success = EraseInDisplay(DispatchTypes::EraseType::All);
|
||||
}
|
||||
if (success)
|
||||
{
|
||||
success = EraseInDisplay(DispatchTypes::EraseType::Scrollback);
|
||||
}
|
||||
|
||||
// // Set the DECSCNM screen mode back to normal.
|
||||
// if (success)
|
||||
// {
|
||||
// success = SetScreenMode(false);
|
||||
// }
|
||||
|
||||
// Cursor to 1,1 - the Soft Reset guarantees this is absolute
|
||||
if (success)
|
||||
{
|
||||
success = CursorPosition(1, 1);
|
||||
}
|
||||
|
||||
// // delete all current tab stops and reapply
|
||||
// _pConApi->PrivateSetDefaultTabStops();
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
@@ -18,9 +18,6 @@ public:
|
||||
bool CursorPosition(const size_t line,
|
||||
const size_t column) noexcept override; // CUP
|
||||
|
||||
bool CursorVisibility(const bool isVisible) noexcept override; // DECTCEM
|
||||
bool EnableCursorBlinking(const bool enable) noexcept override; // ATT610
|
||||
|
||||
bool CursorForward(const size_t distance) noexcept override;
|
||||
bool CursorBackward(const size_t distance) noexcept override;
|
||||
bool CursorUp(const size_t distance) noexcept override;
|
||||
@@ -41,22 +38,6 @@ public:
|
||||
bool InsertCharacter(const size_t count) noexcept override;
|
||||
bool EraseInDisplay(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::EraseType eraseType) noexcept override;
|
||||
|
||||
bool SetCursorKeysMode(const bool applicationMode) noexcept override; // DECCKM
|
||||
bool SetKeypadMode(const bool applicationMode) noexcept override; // DECKPAM, DECKPNM
|
||||
|
||||
bool SoftReset() noexcept override; // DECSTR
|
||||
bool HardReset() noexcept override; // RIS
|
||||
|
||||
bool EnableVT200MouseMode(const bool enabled) noexcept override; // ?1000
|
||||
bool EnableUTF8ExtendedMouseMode(const bool enabled) noexcept override; // ?1005
|
||||
bool EnableSGRExtendedMouseMode(const bool enabled) noexcept override; // ?1006
|
||||
bool EnableButtonEventMouseMode(const bool enabled) noexcept override; // ?1002
|
||||
bool EnableAnyEventMouseMode(const bool enabled) noexcept override; // ?1003
|
||||
bool EnableAlternateScroll(const bool enabled) noexcept override; // ?1007
|
||||
|
||||
bool SetPrivateModes(const std::basic_string_view<::Microsoft::Console::VirtualTerminal::DispatchTypes::PrivateModeParams> /*params*/) noexcept override; // DECSET
|
||||
bool ResetPrivateModes(const std::basic_string_view<::Microsoft::Console::VirtualTerminal::DispatchTypes::PrivateModeParams> /*params*/) noexcept override; // DECRST
|
||||
|
||||
private:
|
||||
::Microsoft::Terminal::Core::ITerminalApi& _terminalApi;
|
||||
|
||||
@@ -65,7 +46,4 @@ private:
|
||||
bool _SetBoldColorHelper(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::GraphicsOptions opt) noexcept;
|
||||
bool _SetDefaultColorHelper(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::GraphicsOptions opt) noexcept;
|
||||
void _SetGraphicsOptionHelper(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::GraphicsOptions opt) noexcept;
|
||||
|
||||
bool _SetResetPrivateModes(const std::basic_string_view<::Microsoft::Console::VirtualTerminal::DispatchTypes::PrivateModeParams> params, const bool enable) noexcept;
|
||||
bool _PrivateModeParamsHelper(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::PrivateModeParams param, const bool enable) noexcept;
|
||||
};
|
||||
|
||||
@@ -204,13 +204,3 @@ void Terminal::UnlockConsole() noexcept
|
||||
{
|
||||
_readWriteLock.unlock_shared();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Returns whether the screen is inverted;
|
||||
// This state is not currently known to Terminal.
|
||||
// Return Value:
|
||||
// - false.
|
||||
bool Terminal::IsScreenReversed() const noexcept
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -115,9 +115,6 @@ class TerminalCoreUnitTests::ConptyRoundtripTests final
|
||||
auto pfn = std::bind(&ConptyRoundtripTests::_writeCallback, this, std::placeholders::_1, std::placeholders::_2);
|
||||
_pVtRenderEngine->SetTestCallback(pfn);
|
||||
|
||||
// Enable the resize quirk, as the Terminal is going to be reacting as if it's enabled.
|
||||
_pVtRenderEngine->SetResizeQuirk(true);
|
||||
|
||||
// Configure the OutputStateMachine's _pfnFlushToTerminal
|
||||
// Use OutputStateMachineEngine::SetTerminalConnection
|
||||
g.pRender->AddRenderEngine(_pVtRenderEngine.get());
|
||||
@@ -158,10 +155,6 @@ class TerminalCoreUnitTests::ConptyRoundtripTests final
|
||||
|
||||
TEST_METHOD(PassthroughClearScrollback);
|
||||
|
||||
TEST_METHOD(PassthroughHardReset);
|
||||
|
||||
TEST_METHOD(PassthroughCursorShapeImmediately);
|
||||
|
||||
TEST_METHOD(TestWrappingALongString);
|
||||
TEST_METHOD(TestAdvancedWrapping);
|
||||
TEST_METHOD(TestExactWrappingWithoutSpaces);
|
||||
@@ -169,8 +162,6 @@ class TerminalCoreUnitTests::ConptyRoundtripTests final
|
||||
|
||||
TEST_METHOD(MoveCursorAtEOL);
|
||||
|
||||
TEST_METHOD(TestResizeHeight);
|
||||
|
||||
private:
|
||||
bool _writeCallback(const char* const pch, size_t const cch);
|
||||
void _flushFirstFrame();
|
||||
@@ -511,15 +502,21 @@ void ConptyRoundtripTests::TestExactWrappingWithoutSpaces()
|
||||
hostSm.ProcessString(L"\n");
|
||||
hostSm.ProcessString(L"1234567890");
|
||||
|
||||
auto verifyBuffer = [&](const TextBuffer& tb) {
|
||||
auto verifyBuffer = [&](const TextBuffer& tb, const bool isTerminal) {
|
||||
auto& cursor = tb.GetCursor();
|
||||
// Verify the cursor wrapped to the second line
|
||||
VERIFY_ARE_EQUAL(1, cursor.GetPosition().Y);
|
||||
VERIFY_ARE_EQUAL(10, cursor.GetPosition().X);
|
||||
|
||||
// TODO: GH#780 - In the Terminal, neither line should be wrapped.
|
||||
// Unfortunately, until WriteCharsLegacy2ElectricBoogaloo is complete,
|
||||
// the Terminal will still treat the first line as wrapped. When #780 is
|
||||
// implemented, these tests will fail, and should again expect the first
|
||||
// line to not be wrapped.
|
||||
|
||||
// Verify that we marked the 0th row as _not wrapped_
|
||||
const auto& row0 = tb.GetRowByOffset(0);
|
||||
VERIFY_IS_FALSE(row0.GetCharRow().WasWrapForced());
|
||||
VERIFY_ARE_EQUAL(isTerminal, row0.GetCharRow().WasWrapForced());
|
||||
|
||||
const auto& row1 = tb.GetRowByOffset(1);
|
||||
VERIFY_IS_FALSE(row1.GetCharRow().WasWrapForced());
|
||||
@@ -528,7 +525,7 @@ void ConptyRoundtripTests::TestExactWrappingWithoutSpaces()
|
||||
TestUtils::VerifyExpectedString(tb, L"1234567890", { 0, 1 });
|
||||
};
|
||||
|
||||
verifyBuffer(hostTb);
|
||||
verifyBuffer(hostTb, false);
|
||||
|
||||
// First write the first 80 characters from the string
|
||||
expectedOutput.push_back(R"(!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnop)");
|
||||
@@ -541,7 +538,7 @@ void ConptyRoundtripTests::TestExactWrappingWithoutSpaces()
|
||||
expectedOutput.push_back("\x1b[K");
|
||||
VERIFY_SUCCEEDED(renderer.PaintFrame());
|
||||
|
||||
verifyBuffer(termTb);
|
||||
verifyBuffer(termTb, true);
|
||||
}
|
||||
|
||||
void ConptyRoundtripTests::TestExactWrappingWithSpaces()
|
||||
@@ -553,7 +550,6 @@ void ConptyRoundtripTests::TestExactWrappingWithSpaces()
|
||||
auto& gci = g.getConsoleInformation();
|
||||
auto& si = gci.GetActiveOutputBuffer();
|
||||
auto& hostSm = si.GetStateMachine();
|
||||
|
||||
auto& hostTb = si.GetTextBuffer();
|
||||
auto& termTb = *term->_buffer;
|
||||
const auto initialTermView = term->GetViewport();
|
||||
@@ -575,15 +571,21 @@ void ConptyRoundtripTests::TestExactWrappingWithSpaces()
|
||||
hostSm.ProcessString(L" ");
|
||||
hostSm.ProcessString(L"1234567890");
|
||||
|
||||
auto verifyBuffer = [&](const TextBuffer& tb) {
|
||||
auto verifyBuffer = [&](const TextBuffer& tb, const bool isTerminal) {
|
||||
auto& cursor = tb.GetCursor();
|
||||
// Verify the cursor wrapped to the second line
|
||||
VERIFY_ARE_EQUAL(1, cursor.GetPosition().Y);
|
||||
VERIFY_ARE_EQUAL(20, cursor.GetPosition().X);
|
||||
|
||||
// TODO: GH#780 - In the Terminal, neither line should be wrapped.
|
||||
// Unfortunately, until WriteCharsLegacy2ElectricBoogaloo is complete,
|
||||
// the Terminal will still treat the first line as wrapped. When #780 is
|
||||
// implemented, these tests will fail, and should again expect the first
|
||||
// line to not be wrapped.
|
||||
|
||||
// Verify that we marked the 0th row as _not wrapped_
|
||||
const auto& row0 = tb.GetRowByOffset(0);
|
||||
VERIFY_IS_FALSE(row0.GetCharRow().WasWrapForced());
|
||||
VERIFY_ARE_EQUAL(isTerminal, row0.GetCharRow().WasWrapForced());
|
||||
|
||||
const auto& row1 = tb.GetRowByOffset(1);
|
||||
VERIFY_IS_FALSE(row1.GetCharRow().WasWrapForced());
|
||||
@@ -592,7 +594,7 @@ void ConptyRoundtripTests::TestExactWrappingWithSpaces()
|
||||
TestUtils::VerifyExpectedString(tb, L" 1234567890", { 0, 1 });
|
||||
};
|
||||
|
||||
verifyBuffer(hostTb);
|
||||
verifyBuffer(hostTb, false);
|
||||
|
||||
// First write the first 80 characters from the string
|
||||
expectedOutput.push_back(R"(!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnop)");
|
||||
@@ -605,7 +607,7 @@ void ConptyRoundtripTests::TestExactWrappingWithSpaces()
|
||||
expectedOutput.push_back("\x1b[K");
|
||||
VERIFY_SUCCEEDED(renderer.PaintFrame());
|
||||
|
||||
verifyBuffer(termTb);
|
||||
verifyBuffer(termTb, true);
|
||||
}
|
||||
|
||||
void ConptyRoundtripTests::MoveCursorAtEOL()
|
||||
@@ -618,7 +620,6 @@ void ConptyRoundtripTests::MoveCursorAtEOL()
|
||||
auto& gci = g.getConsoleInformation();
|
||||
auto& si = gci.GetActiveOutputBuffer();
|
||||
auto& hostSm = si.GetStateMachine();
|
||||
|
||||
auto& hostTb = si.GetTextBuffer();
|
||||
auto& termTb = *term->_buffer;
|
||||
_flushFirstFrame();
|
||||
@@ -672,263 +673,10 @@ void ConptyRoundtripTests::MoveCursorAtEOL()
|
||||
verifyData1(termTb);
|
||||
}
|
||||
|
||||
void ConptyRoundtripTests::TestResizeHeight()
|
||||
{
|
||||
// This test class is _60_ tests to ensure that resizing the terminal works
|
||||
// with conpty correctly. There's a lot of min/maxing in expressions here,
|
||||
// to account for the sheer number of cases here, and that we have to handle
|
||||
// both resizing larger and smaller all in one test.
|
||||
|
||||
BEGIN_TEST_METHOD_PROPERTIES()
|
||||
TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method")
|
||||
TEST_METHOD_PROPERTY(L"Data:dx", L"{-1, 0, 1}")
|
||||
TEST_METHOD_PROPERTY(L"Data:dy", L"{-10, -1, 0, 1, 10}")
|
||||
TEST_METHOD_PROPERTY(L"Data:printedRows", L"{1, 10, 50, 200}")
|
||||
END_TEST_METHOD_PROPERTIES()
|
||||
int dx, dy;
|
||||
int printedRows;
|
||||
VERIFY_SUCCEEDED(TestData::TryGetValue(L"dx", dx), L"change in width of buffer");
|
||||
VERIFY_SUCCEEDED(TestData::TryGetValue(L"dy", dy), L"change in height of buffer");
|
||||
VERIFY_SUCCEEDED(TestData::TryGetValue(L"printedRows", printedRows), L"Number of rows of text to print");
|
||||
|
||||
_checkConptyOutput = false;
|
||||
|
||||
auto& g = ServiceLocator::LocateGlobals();
|
||||
auto& renderer = *g.pRender;
|
||||
auto& gci = g.getConsoleInformation();
|
||||
auto& si = gci.GetActiveOutputBuffer();
|
||||
auto& hostSm = si.GetStateMachine();
|
||||
auto* hostTb = &si.GetTextBuffer();
|
||||
auto* termTb = term->_buffer.get();
|
||||
const auto initialHostView = si.GetViewport();
|
||||
const auto initialTermView = term->GetViewport();
|
||||
const auto initialTerminalBufferHeight = term->GetTextBuffer().GetSize().Height();
|
||||
|
||||
VERIFY_ARE_EQUAL(0, initialHostView.Top());
|
||||
VERIFY_ARE_EQUAL(TerminalViewHeight, initialHostView.BottomExclusive());
|
||||
VERIFY_ARE_EQUAL(0, initialTermView.Top());
|
||||
VERIFY_ARE_EQUAL(TerminalViewHeight, initialTermView.BottomExclusive());
|
||||
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Print %d lines of output, which will scroll the viewport", printedRows));
|
||||
|
||||
for (auto i = 0; i < printedRows; i++)
|
||||
{
|
||||
// This looks insane, but this expression is carefully crafted to give
|
||||
// us only printable characters, starting with `!` (0n33).
|
||||
// Similar statements are used elsewhere throughout this test.
|
||||
auto wstr = std::wstring(1, static_cast<wchar_t>((i) % 93) + 33);
|
||||
hostSm.ProcessString(wstr);
|
||||
hostSm.ProcessString(L"\r\n");
|
||||
}
|
||||
|
||||
// Conpty doesn't have a scrollback, it's view's origin is always 0,0
|
||||
const auto secondHostView = si.GetViewport();
|
||||
VERIFY_ARE_EQUAL(0, secondHostView.Top());
|
||||
VERIFY_ARE_EQUAL(TerminalViewHeight, secondHostView.BottomExclusive());
|
||||
|
||||
VERIFY_SUCCEEDED(renderer.PaintFrame());
|
||||
|
||||
const auto secondTermView = term->GetViewport();
|
||||
// If we've printed more lines than the height of the buffer, then we're
|
||||
// expecting the viewport to have moved down. Otherwise, the terminal's
|
||||
// viewport will stay at 0,0.
|
||||
const auto expectedTerminalViewBottom = std::max(std::min(::base::saturated_cast<short>(printedRows + 1),
|
||||
term->GetBufferHeight()),
|
||||
term->GetViewport().Height());
|
||||
|
||||
VERIFY_ARE_EQUAL(expectedTerminalViewBottom, secondTermView.BottomExclusive());
|
||||
VERIFY_ARE_EQUAL(expectedTerminalViewBottom - initialTermView.Height(), secondTermView.Top());
|
||||
|
||||
auto verifyTermData = [&expectedTerminalViewBottom, &printedRows, this, &initialTerminalBufferHeight](TextBuffer& termTb, const int resizeDy = 0) {
|
||||
// Some number of lines of text were lost from the scrollback. The
|
||||
// number of lines lost will be determined by whichever of the initial
|
||||
// or current buffer is smaller.
|
||||
const auto numLostRows = std::max(0,
|
||||
printedRows - std::min(term->GetTextBuffer().GetSize().Height(), initialTerminalBufferHeight) + 1);
|
||||
|
||||
const auto rowsWithText = std::min(::base::saturated_cast<short>(printedRows),
|
||||
expectedTerminalViewBottom) -
|
||||
1 + std::min(resizeDy, 0);
|
||||
|
||||
for (short row = 0; row < rowsWithText; row++)
|
||||
{
|
||||
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
|
||||
auto iter = termTb.GetCellDataAt({ 0, row });
|
||||
const wchar_t expectedChar = static_cast<wchar_t>((row + numLostRows) % 93) + 33;
|
||||
|
||||
auto expectedString = std::wstring(1, expectedChar);
|
||||
|
||||
if (iter->Chars() != expectedString)
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(L"row [%d] was mismatched", row));
|
||||
}
|
||||
VERIFY_ARE_EQUAL(expectedString, (iter++)->Chars());
|
||||
VERIFY_ARE_EQUAL(L" ", (iter)->Chars());
|
||||
}
|
||||
};
|
||||
auto verifyHostData = [&si, &initialHostView, &printedRows](TextBuffer& hostTb, const int resizeDy = 0) {
|
||||
const auto hostView = si.GetViewport();
|
||||
|
||||
// In the host, there are two regions we're interested in:
|
||||
|
||||
// 1. the first section of the buffer with the output in it. Before
|
||||
// we're resized, this will be filled with one character on each row.
|
||||
// 2. The second area below the first that's empty (filled with spaces).
|
||||
// Initially, this is only one row.
|
||||
// After we resize, different things will happen.
|
||||
// * If we decrease the height of the buffer, the characters in the
|
||||
// buffer will all move _up_ the same number of rows. We'll want to
|
||||
// only check the first initialView+dy rows for characters.
|
||||
// * If we increase the height, rows will be added at the bottom. We'll
|
||||
// want to check the initial viewport height for the original
|
||||
// characters, but then we'll want to look for more blank rows at the
|
||||
// bottom. The characters in the initial viewport won't have moved.
|
||||
|
||||
const short originalViewHeight = ::base::saturated_cast<short>(resizeDy < 0 ?
|
||||
initialHostView.Height() + resizeDy :
|
||||
initialHostView.Height());
|
||||
const auto rowsWithText = std::min(originalViewHeight - 1, printedRows);
|
||||
const bool scrolled = printedRows > initialHostView.Height();
|
||||
// The last row of the viewport should be empty
|
||||
// The second last row will have '0'+50
|
||||
// The third last row will have '0'+49
|
||||
// ...
|
||||
// The <height> last row will have '0'+(50-height+1)
|
||||
const auto firstChar = static_cast<wchar_t>(scrolled ?
|
||||
(printedRows - originalViewHeight + 1) :
|
||||
0);
|
||||
|
||||
short row = 0;
|
||||
// Don't include the last row of the viewport in this check, since it'll
|
||||
// be blank. We'll check it in the below loop.
|
||||
for (; row < rowsWithText; row++)
|
||||
{
|
||||
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
|
||||
auto iter = hostTb.GetCellDataAt({ 0, row });
|
||||
|
||||
const auto expectedChar = static_cast<wchar_t>(((firstChar + row) % 93) + 33);
|
||||
auto expectedString = std::wstring(1, static_cast<wchar_t>(expectedChar));
|
||||
|
||||
if (iter->Chars() != expectedString)
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(L"row [%d] was mismatched", row));
|
||||
}
|
||||
VERIFY_ARE_EQUAL(expectedString, (iter++)->Chars(), NoThrowString().Format(L"%s", expectedString.data()));
|
||||
VERIFY_ARE_EQUAL(L" ", (iter)->Chars());
|
||||
}
|
||||
|
||||
// Check that the remaining rows in the viewport are empty.
|
||||
for (; row < hostView.Height(); row++)
|
||||
{
|
||||
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
|
||||
auto iter = hostTb.GetCellDataAt({ 0, row });
|
||||
VERIFY_ARE_EQUAL(L" ", (iter)->Chars());
|
||||
}
|
||||
};
|
||||
|
||||
verifyHostData(*hostTb);
|
||||
verifyTermData(*termTb);
|
||||
|
||||
const COORD newViewportSize{
|
||||
::base::saturated_cast<short>(TerminalViewWidth + dx),
|
||||
::base::saturated_cast<short>(TerminalViewHeight + dy)
|
||||
};
|
||||
|
||||
Log::Comment(NoThrowString().Format(L"Resize the Terminal and conpty here"));
|
||||
auto resizeResult = term->UserResize(newViewportSize);
|
||||
VERIFY_SUCCEEDED(resizeResult);
|
||||
_resizeConpty(newViewportSize.X, newViewportSize.Y);
|
||||
|
||||
// After we resize, make sure to get the new textBuffers
|
||||
hostTb = &si.GetTextBuffer();
|
||||
termTb = term->_buffer.get();
|
||||
|
||||
// Conpty's doesn't have a scrollback, it's view's origin is always 0,0
|
||||
const auto thirdHostView = si.GetViewport();
|
||||
VERIFY_ARE_EQUAL(0, thirdHostView.Top());
|
||||
VERIFY_ARE_EQUAL(newViewportSize.Y, thirdHostView.BottomExclusive());
|
||||
|
||||
// The Terminal should be stuck to the top of the viewport, unless dy<0,
|
||||
// rows=50. In that set of cases, we _didn't_ pin the top of the Terminal to
|
||||
// the old top, we actually shifted it down (because the output was at the
|
||||
// bottom of the window, not empty lines).
|
||||
const auto thirdTermView = term->GetViewport();
|
||||
if (dy < 0 && (printedRows > initialTermView.Height() && printedRows < initialTerminalBufferHeight))
|
||||
{
|
||||
VERIFY_ARE_EQUAL(secondTermView.Top() - dy, thirdTermView.Top());
|
||||
VERIFY_ARE_EQUAL(expectedTerminalViewBottom, thirdTermView.BottomExclusive());
|
||||
}
|
||||
else
|
||||
{
|
||||
VERIFY_ARE_EQUAL(secondTermView.Top(), thirdTermView.Top());
|
||||
VERIFY_ARE_EQUAL(expectedTerminalViewBottom + dy, thirdTermView.BottomExclusive());
|
||||
}
|
||||
|
||||
verifyHostData(*hostTb, dy);
|
||||
// Note that at this point, nothing should have changed with the Terminal.
|
||||
verifyTermData(*termTb, dy);
|
||||
|
||||
Log::Comment(NoThrowString().Format(L"Paint a frame to update the Terminal"));
|
||||
VERIFY_SUCCEEDED(renderer.PaintFrame());
|
||||
|
||||
// Conpty's doesn't have a scrollback, it's view's origin is always 0,0
|
||||
const auto fourthHostView = si.GetViewport();
|
||||
VERIFY_ARE_EQUAL(0, fourthHostView.Top());
|
||||
VERIFY_ARE_EQUAL(newViewportSize.Y, fourthHostView.BottomExclusive());
|
||||
|
||||
// The Terminal should be stuck to the top of the viewport, unless dy<0,
|
||||
// rows=50. In that set of cases, we _didn't_ pin the top of the Terminal to
|
||||
// the old top, we actually shifted it down (because the output was at the
|
||||
// bottom of the window, not empty lines).
|
||||
const auto fourthTermView = term->GetViewport();
|
||||
if (dy < 0 && (printedRows > initialTermView.Height() && printedRows < initialTerminalBufferHeight))
|
||||
{
|
||||
VERIFY_ARE_EQUAL(secondTermView.Top() - dy, thirdTermView.Top());
|
||||
VERIFY_ARE_EQUAL(expectedTerminalViewBottom, thirdTermView.BottomExclusive());
|
||||
}
|
||||
else
|
||||
{
|
||||
VERIFY_ARE_EQUAL(secondTermView.Top(), thirdTermView.Top());
|
||||
VERIFY_ARE_EQUAL(expectedTerminalViewBottom + dy, thirdTermView.BottomExclusive());
|
||||
}
|
||||
verifyHostData(*hostTb, dy);
|
||||
verifyTermData(*termTb, dy);
|
||||
}
|
||||
|
||||
void ConptyRoundtripTests::PassthroughCursorShapeImmediately()
|
||||
{
|
||||
// This is a test for GH#4106, and more indirectly, GH #2011.
|
||||
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Change the cursor shape with VT. This should immediately be flushed to the Terminal."));
|
||||
VERIFY_IS_NOT_NULL(_pVtRenderEngine.get());
|
||||
|
||||
auto& g = ServiceLocator::LocateGlobals();
|
||||
auto& gci = g.getConsoleInformation();
|
||||
auto& si = gci.GetActiveOutputBuffer();
|
||||
auto& hostSm = si.GetStateMachine();
|
||||
auto& hostTb = si.GetTextBuffer();
|
||||
auto& termTb = *term->_buffer;
|
||||
|
||||
_flushFirstFrame();
|
||||
|
||||
_logConpty = true;
|
||||
|
||||
VERIFY_ARE_NOT_EQUAL(CursorType::VerticalBar, hostTb.GetCursor().GetType());
|
||||
VERIFY_ARE_NOT_EQUAL(CursorType::VerticalBar, termTb.GetCursor().GetType());
|
||||
|
||||
expectedOutput.push_back("\x1b[5 q");
|
||||
hostSm.ProcessString(L"\x1b[5 q");
|
||||
|
||||
VERIFY_ARE_EQUAL(CursorType::VerticalBar, hostTb.GetCursor().GetType());
|
||||
VERIFY_ARE_EQUAL(CursorType::VerticalBar, termTb.GetCursor().GetType());
|
||||
}
|
||||
|
||||
void ConptyRoundtripTests::PassthroughClearScrollback()
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Write more lines of output than there are lines in the viewport. Clear the scrollback with ^[[3J"));
|
||||
L"Write more lines of outout. We should use \r\n to move the cursor"));
|
||||
VERIFY_IS_NOT_NULL(_pVtRenderEngine.get());
|
||||
|
||||
auto& g = ServiceLocator::LocateGlobals();
|
||||
@@ -936,7 +684,6 @@ void ConptyRoundtripTests::PassthroughClearScrollback()
|
||||
auto& gci = g.getConsoleInformation();
|
||||
auto& si = gci.GetActiveOutputBuffer();
|
||||
auto& hostSm = si.GetStateMachine();
|
||||
|
||||
auto& termTb = *term->_buffer;
|
||||
|
||||
_flushFirstFrame();
|
||||
@@ -987,7 +734,7 @@ void ConptyRoundtripTests::PassthroughClearScrollback()
|
||||
const auto termSecondView = term->GetViewport();
|
||||
VERIFY_ARE_EQUAL(0, termSecondView.Top());
|
||||
|
||||
// Verify the top of the Terminal viewport contains the contents of the old viewport
|
||||
// Verify the top of the Terminal veiwoprt contains the contents of the old viewport
|
||||
for (short y = 0; y < termSecondView.BottomInclusive(); y++)
|
||||
{
|
||||
TestUtils::VerifyExpectedString(termTb, L"X ", { 0, y });
|
||||
@@ -999,70 +746,3 @@ void ConptyRoundtripTests::PassthroughClearScrollback()
|
||||
TestUtils::VerifyExpectedString(termTb, std::wstring(TerminalViewWidth, L' '), { 0, y });
|
||||
}
|
||||
}
|
||||
|
||||
void ConptyRoundtripTests::PassthroughHardReset()
|
||||
{
|
||||
// This test is highly similar to PassthroughClearScrollback.
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Write more lines of output than there are lines in the viewport. Clear everything with ^[c"));
|
||||
VERIFY_IS_NOT_NULL(_pVtRenderEngine.get());
|
||||
|
||||
auto& g = ServiceLocator::LocateGlobals();
|
||||
auto& renderer = *g.pRender;
|
||||
auto& gci = g.getConsoleInformation();
|
||||
auto& si = gci.GetActiveOutputBuffer();
|
||||
auto& hostSm = si.GetStateMachine();
|
||||
|
||||
auto& termTb = *term->_buffer;
|
||||
|
||||
_flushFirstFrame();
|
||||
|
||||
_logConpty = true;
|
||||
|
||||
const auto hostView = si.GetViewport();
|
||||
const auto end = 2 * hostView.Height();
|
||||
for (auto i = 0; i < end; i++)
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(L"Writing line %d/%d", i, end));
|
||||
expectedOutput.push_back("X");
|
||||
if (i < hostView.BottomInclusive())
|
||||
{
|
||||
expectedOutput.push_back("\r\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
// After we hit the bottom of the viewport, the newlines come in
|
||||
// separated for whatever reason.
|
||||
|
||||
expectedOutput.push_back("\r");
|
||||
expectedOutput.push_back("\n");
|
||||
expectedOutput.push_back("");
|
||||
}
|
||||
|
||||
hostSm.ProcessString(L"X\n");
|
||||
|
||||
VERIFY_SUCCEEDED(renderer.PaintFrame());
|
||||
}
|
||||
|
||||
VERIFY_SUCCEEDED(renderer.PaintFrame());
|
||||
|
||||
// Verify that we've printed height*2 lines of X's to the Terminal
|
||||
const auto termFirstView = term->GetViewport();
|
||||
for (short y = 0; y < 2 * termFirstView.Height(); y++)
|
||||
{
|
||||
TestUtils::VerifyExpectedString(termTb, L"X ", { 0, y });
|
||||
}
|
||||
|
||||
// Write a Hard Reset VT sequence to the host, it should come through to the Terminal
|
||||
expectedOutput.push_back("\033c");
|
||||
hostSm.ProcessString(L"\033c");
|
||||
|
||||
const auto termSecondView = term->GetViewport();
|
||||
VERIFY_ARE_EQUAL(0, termSecondView.Top());
|
||||
|
||||
// Verify everything has been cleared out
|
||||
for (short y = 0; y < termFirstView.BottomInclusive(); y++)
|
||||
{
|
||||
TestUtils::VerifyExpectedString(termTb, std::wstring(TerminalViewWidth, L' '), { 0, y });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,187 +24,90 @@ namespace TerminalCoreUnitTests
|
||||
{
|
||||
TEST_CLASS(TerminalApiTest);
|
||||
|
||||
TEST_METHOD(SetColorTableEntry);
|
||||
TEST_METHOD(SetColorTableEntry)
|
||||
{
|
||||
Terminal term;
|
||||
DummyRenderTarget emptyRT;
|
||||
term.Create({ 100, 100 }, 0, emptyRT);
|
||||
|
||||
TEST_METHOD(CursorVisibility);
|
||||
TEST_METHOD(CursorVisibilityViaStateMachine);
|
||||
auto settings = winrt::make<MockTermSettings>(100, 100, 100);
|
||||
term.UpdateSettings(settings);
|
||||
|
||||
VERIFY_IS_TRUE(term.SetColorTableEntry(0, 100));
|
||||
VERIFY_IS_TRUE(term.SetColorTableEntry(128, 100));
|
||||
VERIFY_IS_TRUE(term.SetColorTableEntry(255, 100));
|
||||
|
||||
VERIFY_IS_FALSE(term.SetColorTableEntry(256, 100));
|
||||
VERIFY_IS_FALSE(term.SetColorTableEntry(512, 100));
|
||||
}
|
||||
|
||||
// Terminal::_WriteBuffer used to enter infinite loops under certain conditions.
|
||||
// This test ensures that Terminal::_WriteBuffer doesn't get stuck when
|
||||
// PrintString() is called with more code units than the buffer width.
|
||||
TEST_METHOD(PrintStringOfSurrogatePairs);
|
||||
TEST_METHOD(PrintStringOfSurrogatePairs)
|
||||
{
|
||||
DummyRenderTarget renderTarget;
|
||||
Terminal term;
|
||||
term.Create({ 100, 100 }, 3, renderTarget);
|
||||
|
||||
std::wstring text;
|
||||
text.reserve(600);
|
||||
|
||||
for (size_t i = 0; i < 100; ++i)
|
||||
{
|
||||
text.append(L"𐐌𐐜𐐬");
|
||||
}
|
||||
|
||||
struct Baton
|
||||
{
|
||||
HANDLE done;
|
||||
std::wstring text;
|
||||
Terminal* pTerm;
|
||||
} baton{
|
||||
CreateEventW(nullptr, TRUE, FALSE, L"done signal"),
|
||||
text,
|
||||
&term,
|
||||
};
|
||||
|
||||
Log::Comment(L"Launching thread to write data.");
|
||||
const auto thread = CreateThread(
|
||||
nullptr,
|
||||
0,
|
||||
[](LPVOID data) -> DWORD {
|
||||
const Baton& baton = *reinterpret_cast<Baton*>(data);
|
||||
Log::Comment(L"Writing data.");
|
||||
baton.pTerm->PrintString(baton.text);
|
||||
Log::Comment(L"Setting event.");
|
||||
SetEvent(baton.done);
|
||||
return 0;
|
||||
},
|
||||
(LPVOID)&baton,
|
||||
0,
|
||||
nullptr);
|
||||
|
||||
Log::Comment(L"Waiting for the write.");
|
||||
switch (WaitForSingleObject(baton.done, 2000))
|
||||
{
|
||||
case WAIT_OBJECT_0:
|
||||
Log::Comment(L"Didn't get stuck. Success.");
|
||||
break;
|
||||
case WAIT_TIMEOUT:
|
||||
Log::Comment(L"Wait timed out. It got stuck.");
|
||||
Log::Result(WEX::Logging::TestResults::Failed);
|
||||
break;
|
||||
case WAIT_FAILED:
|
||||
Log::Comment(L"Wait failed for some reason. We didn't expect this.");
|
||||
Log::Result(WEX::Logging::TestResults::Failed);
|
||||
break;
|
||||
default:
|
||||
Log::Comment(L"Wait return code that no one expected. Fail.");
|
||||
Log::Result(WEX::Logging::TestResults::Failed);
|
||||
break;
|
||||
}
|
||||
|
||||
TerminateThread(thread, 0);
|
||||
CloseHandle(baton.done);
|
||||
return;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
using namespace TerminalCoreUnitTests;
|
||||
|
||||
void TerminalApiTest::SetColorTableEntry()
|
||||
{
|
||||
Terminal term;
|
||||
DummyRenderTarget emptyRT;
|
||||
term.Create({ 100, 100 }, 0, emptyRT);
|
||||
|
||||
auto settings = winrt::make<MockTermSettings>(100, 100, 100);
|
||||
term.UpdateSettings(settings);
|
||||
|
||||
VERIFY_IS_TRUE(term.SetColorTableEntry(0, 100));
|
||||
VERIFY_IS_TRUE(term.SetColorTableEntry(128, 100));
|
||||
VERIFY_IS_TRUE(term.SetColorTableEntry(255, 100));
|
||||
|
||||
VERIFY_IS_FALSE(term.SetColorTableEntry(256, 100));
|
||||
VERIFY_IS_FALSE(term.SetColorTableEntry(512, 100));
|
||||
}
|
||||
|
||||
// Terminal::_WriteBuffer used to enter infinite loops under certain conditions.
|
||||
// This test ensures that Terminal::_WriteBuffer doesn't get stuck when
|
||||
// PrintString() is called with more code units than the buffer width.
|
||||
void TerminalApiTest::PrintStringOfSurrogatePairs()
|
||||
{
|
||||
DummyRenderTarget renderTarget;
|
||||
Terminal term;
|
||||
term.Create({ 100, 100 }, 3, renderTarget);
|
||||
|
||||
std::wstring text;
|
||||
text.reserve(600);
|
||||
|
||||
for (size_t i = 0; i < 100; ++i)
|
||||
{
|
||||
text.append(L"𐐌𐐜𐐬");
|
||||
}
|
||||
|
||||
struct Baton
|
||||
{
|
||||
HANDLE done;
|
||||
std::wstring text;
|
||||
Terminal* pTerm;
|
||||
} baton{
|
||||
CreateEventW(nullptr, TRUE, FALSE, L"done signal"),
|
||||
text,
|
||||
&term,
|
||||
};
|
||||
|
||||
Log::Comment(L"Launching thread to write data.");
|
||||
const auto thread = CreateThread(
|
||||
nullptr,
|
||||
0,
|
||||
[](LPVOID data) -> DWORD {
|
||||
const Baton& baton = *reinterpret_cast<Baton*>(data);
|
||||
Log::Comment(L"Writing data.");
|
||||
baton.pTerm->PrintString(baton.text);
|
||||
Log::Comment(L"Setting event.");
|
||||
SetEvent(baton.done);
|
||||
return 0;
|
||||
},
|
||||
(LPVOID)&baton,
|
||||
0,
|
||||
nullptr);
|
||||
|
||||
Log::Comment(L"Waiting for the write.");
|
||||
switch (WaitForSingleObject(baton.done, 2000))
|
||||
{
|
||||
case WAIT_OBJECT_0:
|
||||
Log::Comment(L"Didn't get stuck. Success.");
|
||||
break;
|
||||
case WAIT_TIMEOUT:
|
||||
Log::Comment(L"Wait timed out. It got stuck.");
|
||||
Log::Result(WEX::Logging::TestResults::Failed);
|
||||
break;
|
||||
case WAIT_FAILED:
|
||||
Log::Comment(L"Wait failed for some reason. We didn't expect this.");
|
||||
Log::Result(WEX::Logging::TestResults::Failed);
|
||||
break;
|
||||
default:
|
||||
Log::Comment(L"Wait return code that no one expected. Fail.");
|
||||
Log::Result(WEX::Logging::TestResults::Failed);
|
||||
break;
|
||||
}
|
||||
|
||||
TerminateThread(thread, 0);
|
||||
CloseHandle(baton.done);
|
||||
return;
|
||||
}
|
||||
|
||||
void TerminalApiTest::CursorVisibility()
|
||||
{
|
||||
// GH#3093 - Cursor Visibility and On states shouldn't affect each other
|
||||
Terminal term;
|
||||
DummyRenderTarget emptyRT;
|
||||
term.Create({ 100, 100 }, 0, emptyRT);
|
||||
|
||||
VERIFY_IS_TRUE(term._buffer->GetCursor().IsVisible());
|
||||
VERIFY_IS_TRUE(term._buffer->GetCursor().IsOn());
|
||||
VERIFY_IS_TRUE(term._buffer->GetCursor().IsBlinkingAllowed());
|
||||
|
||||
term.SetCursorOn(false);
|
||||
VERIFY_IS_TRUE(term._buffer->GetCursor().IsVisible());
|
||||
VERIFY_IS_FALSE(term._buffer->GetCursor().IsOn());
|
||||
VERIFY_IS_TRUE(term._buffer->GetCursor().IsBlinkingAllowed());
|
||||
|
||||
term.SetCursorOn(true);
|
||||
VERIFY_IS_TRUE(term._buffer->GetCursor().IsVisible());
|
||||
VERIFY_IS_TRUE(term._buffer->GetCursor().IsOn());
|
||||
VERIFY_IS_TRUE(term._buffer->GetCursor().IsBlinkingAllowed());
|
||||
|
||||
term.SetCursorVisibility(false);
|
||||
VERIFY_IS_FALSE(term._buffer->GetCursor().IsVisible());
|
||||
VERIFY_IS_TRUE(term._buffer->GetCursor().IsOn());
|
||||
VERIFY_IS_TRUE(term._buffer->GetCursor().IsBlinkingAllowed());
|
||||
|
||||
term.SetCursorOn(false);
|
||||
VERIFY_IS_FALSE(term._buffer->GetCursor().IsVisible());
|
||||
VERIFY_IS_FALSE(term._buffer->GetCursor().IsOn());
|
||||
VERIFY_IS_TRUE(term._buffer->GetCursor().IsBlinkingAllowed());
|
||||
}
|
||||
|
||||
void TerminalApiTest::CursorVisibilityViaStateMachine()
|
||||
{
|
||||
// This is a nearly literal copy-paste of ScreenBufferTests::TestCursorIsOn, adapted for the Terminal
|
||||
Terminal term;
|
||||
DummyRenderTarget emptyRT;
|
||||
term.Create({ 100, 100 }, 0, emptyRT);
|
||||
|
||||
auto& tbi = *(term._buffer);
|
||||
auto& stateMachine = *(term._stateMachine);
|
||||
auto& cursor = tbi.GetCursor();
|
||||
|
||||
stateMachine.ProcessString(L"Hello World");
|
||||
VERIFY_IS_TRUE(cursor.IsOn());
|
||||
VERIFY_IS_TRUE(cursor.IsBlinkingAllowed());
|
||||
VERIFY_IS_TRUE(cursor.IsVisible());
|
||||
|
||||
stateMachine.ProcessString(L"\x1b[?12l");
|
||||
VERIFY_IS_TRUE(cursor.IsOn());
|
||||
VERIFY_IS_FALSE(cursor.IsBlinkingAllowed());
|
||||
VERIFY_IS_TRUE(cursor.IsVisible());
|
||||
|
||||
stateMachine.ProcessString(L"\x1b[?12h");
|
||||
VERIFY_IS_TRUE(cursor.IsOn());
|
||||
VERIFY_IS_TRUE(cursor.IsBlinkingAllowed());
|
||||
VERIFY_IS_TRUE(cursor.IsVisible());
|
||||
|
||||
cursor.SetIsOn(false);
|
||||
stateMachine.ProcessString(L"\x1b[?12l");
|
||||
VERIFY_IS_TRUE(cursor.IsOn());
|
||||
VERIFY_IS_FALSE(cursor.IsBlinkingAllowed());
|
||||
VERIFY_IS_TRUE(cursor.IsVisible());
|
||||
|
||||
stateMachine.ProcessString(L"\x1b[?12h");
|
||||
VERIFY_IS_TRUE(cursor.IsOn());
|
||||
VERIFY_IS_TRUE(cursor.IsBlinkingAllowed());
|
||||
VERIFY_IS_TRUE(cursor.IsVisible());
|
||||
|
||||
stateMachine.ProcessString(L"\x1b[?25l");
|
||||
VERIFY_IS_TRUE(cursor.IsOn());
|
||||
VERIFY_IS_TRUE(cursor.IsBlinkingAllowed());
|
||||
VERIFY_IS_FALSE(cursor.IsVisible());
|
||||
|
||||
stateMachine.ProcessString(L"\x1b[?25h");
|
||||
VERIFY_IS_TRUE(cursor.IsOn());
|
||||
VERIFY_IS_TRUE(cursor.IsBlinkingAllowed());
|
||||
VERIFY_IS_TRUE(cursor.IsVisible());
|
||||
|
||||
stateMachine.ProcessString(L"\x1b[?12;25l");
|
||||
VERIFY_IS_TRUE(cursor.IsOn());
|
||||
VERIFY_IS_FALSE(cursor.IsBlinkingAllowed());
|
||||
VERIFY_IS_FALSE(cursor.IsVisible());
|
||||
}
|
||||
|
||||
@@ -19,8 +19,6 @@ using namespace winrt::Windows::Foundation::Numerics;
|
||||
using namespace ::Microsoft::Console;
|
||||
using namespace ::Microsoft::Console::Types;
|
||||
|
||||
static constexpr int AutohideTaskbarSize = 2;
|
||||
|
||||
NonClientIslandWindow::NonClientIslandWindow(const ElementTheme& requestedTheme) noexcept :
|
||||
IslandWindow{},
|
||||
_backgroundBrushColor{ RGB(0, 0, 0) },
|
||||
@@ -334,8 +332,6 @@ int NonClientIslandWindow::_GetResizeHandleHeight() const noexcept
|
||||
// default frame.
|
||||
const auto originalTop = params->rgrc[0].top;
|
||||
|
||||
const auto originalSize = params->rgrc[0];
|
||||
|
||||
// apply the default frame
|
||||
auto ret = DefWindowProc(_window.get(), WM_NCCALCSIZE, wParam, lParam);
|
||||
if (ret != 0)
|
||||
@@ -343,17 +339,19 @@ int NonClientIslandWindow::_GetResizeHandleHeight() const noexcept
|
||||
return ret;
|
||||
}
|
||||
|
||||
auto newSize = params->rgrc[0];
|
||||
// Re-apply the original top from before the size of the default frame was applied.
|
||||
newSize.top = originalTop;
|
||||
// When we're fullscreen, we have the WS_POPUP size so we don't have to
|
||||
// worry about borders so the default frame will be fine.
|
||||
if (_fullscreen)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto newTop = originalTop;
|
||||
|
||||
// WM_NCCALCSIZE is called before WM_SIZE
|
||||
_UpdateMaximizedState();
|
||||
|
||||
// We don't need this correction when we're fullscreen. We will have the
|
||||
// WS_POPUP size, so we don't have to worry about borders, and the default
|
||||
// frame will be fine.
|
||||
if (_isMaximized && !_fullscreen)
|
||||
if (_isMaximized)
|
||||
{
|
||||
// When a window is maximized, its size is actually a little bit more
|
||||
// than the monitor's work area. The window is positioned and sized in
|
||||
@@ -361,74 +359,11 @@ int NonClientIslandWindow::_GetResizeHandleHeight() const noexcept
|
||||
// then the window is clipped to the monitor so that the resize handle
|
||||
// do not appear because you don't need them (because you can't resize
|
||||
// a window when it's maximized unless you restore it).
|
||||
newSize.top += _GetResizeHandleHeight();
|
||||
newTop += _GetResizeHandleHeight();
|
||||
}
|
||||
|
||||
// GH#1438 - Attempt to detect if there's an autohide taskbar, and if there
|
||||
// is, reduce our size a bit on the side with the taskbar, so the user can
|
||||
// still mouse-over the taskbar to reveal it.
|
||||
HMONITOR hMon = MonitorFromWindow(_window.get(), MONITOR_DEFAULTTONULL);
|
||||
if (hMon && (_isMaximized || _fullscreen))
|
||||
{
|
||||
MONITORINFO monInfo{ 0 };
|
||||
monInfo.cbSize = sizeof(MONITORINFO);
|
||||
GetMonitorInfo(hMon, &monInfo);
|
||||
|
||||
// First, check if we have an auto-hide taskbar at all:
|
||||
APPBARDATA autohide{ 0 };
|
||||
autohide.cbSize = sizeof(autohide);
|
||||
UINT state = (UINT)SHAppBarMessage(ABM_GETSTATE, &autohide);
|
||||
if (WI_IsFlagSet(state, ABS_AUTOHIDE))
|
||||
{
|
||||
// This helper can be used to determine if there's a auto-hide
|
||||
// taskbar on the given edge of the monitor we're currently on.
|
||||
auto hasAutohideTaskbar = [&monInfo](const UINT edge) -> bool {
|
||||
APPBARDATA data{ 0 };
|
||||
data.cbSize = sizeof(data);
|
||||
data.uEdge = edge;
|
||||
data.rc = monInfo.rcMonitor;
|
||||
HWND hTaskbar = (HWND)SHAppBarMessage(ABM_GETAUTOHIDEBAREX, &data);
|
||||
return hTaskbar != nullptr;
|
||||
};
|
||||
|
||||
const bool onTop = hasAutohideTaskbar(ABE_TOP);
|
||||
const bool onBottom = hasAutohideTaskbar(ABE_BOTTOM);
|
||||
const bool onLeft = hasAutohideTaskbar(ABE_LEFT);
|
||||
const bool onRight = hasAutohideTaskbar(ABE_RIGHT);
|
||||
|
||||
// If there's a taskbar on any side of the monitor, reduce our size
|
||||
// a little bit on that edge.
|
||||
//
|
||||
// Note to future code archeologists:
|
||||
// This doesn't seem to work for fullscreen on the primary display.
|
||||
// However, testing a bunch of other apps with fullscreen modes
|
||||
// and an auto-hiding taskbar has shown that _none_ of them
|
||||
// reveal the taskbar from fullscreen mode. This includes Edge,
|
||||
// Firefox, Chrome, Sublime Text, Powerpoint - none seemed to
|
||||
// support this.
|
||||
//
|
||||
// This does however work fine for maximized.
|
||||
if (onTop)
|
||||
{
|
||||
// Peculiarly, when we're fullscreen,
|
||||
newSize.top += AutohideTaskbarSize;
|
||||
}
|
||||
if (onBottom)
|
||||
{
|
||||
newSize.bottom -= AutohideTaskbarSize;
|
||||
}
|
||||
if (onLeft)
|
||||
{
|
||||
newSize.left += AutohideTaskbarSize;
|
||||
}
|
||||
if (onRight)
|
||||
{
|
||||
newSize.right -= AutohideTaskbarSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
params->rgrc[0] = newSize;
|
||||
// only modify the top of the frame to remove the title bar
|
||||
params->rgrc[0].top = newTop;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -492,8 +427,6 @@ SIZE NonClientIslandWindow::GetTotalNonClientExclusiveSize(UINT dpi) const noexc
|
||||
|
||||
islandFrame.top = -topBorderVisibleHeight;
|
||||
|
||||
// If we have a titlebar, this is being called after we've initialized, and
|
||||
// we can just ask that titlebar how big it wants to be.
|
||||
const auto titleBarHeight = _titlebar ? static_cast<LONG>(_titlebar.ActualHeight()) : 0;
|
||||
|
||||
return {
|
||||
@@ -718,11 +651,6 @@ void NonClientIslandWindow::_SetIsFullscreen(const bool fullscreenEnabled)
|
||||
{
|
||||
IslandWindow::_SetIsFullscreen(fullscreenEnabled);
|
||||
_titlebar.Visibility(!fullscreenEnabled ? Visibility::Visible : Visibility::Collapsed);
|
||||
// GH#4224 - When the auto-hide taskbar setting is enabled, then we don't
|
||||
// always get another window message to trigger us to remove the drag bar.
|
||||
// So, make sure to update the size of the drag region here, so that it
|
||||
// _definitely_ goes away.
|
||||
_UpdateIslandRegion();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
||||
@@ -18,7 +18,6 @@ const std::wstring_view ConsoleArguments::FILEPATH_LEADER_PREFIX = L"\\??\\";
|
||||
const std::wstring_view ConsoleArguments::WIDTH_ARG = L"--width";
|
||||
const std::wstring_view ConsoleArguments::HEIGHT_ARG = L"--height";
|
||||
const std::wstring_view ConsoleArguments::INHERIT_CURSOR_ARG = L"--inheritcursor";
|
||||
const std::wstring_view ConsoleArguments::RESIZE_QUIRK = L"--resizeQuirk";
|
||||
const std::wstring_view ConsoleArguments::FEATURE_ARG = L"--feature";
|
||||
const std::wstring_view ConsoleArguments::FEATURE_PTY_ARG = L"pty";
|
||||
|
||||
@@ -480,12 +479,6 @@ void ConsoleArguments::s_ConsumeArg(_Inout_ std::vector<std::wstring>& args, _In
|
||||
s_ConsumeArg(args, i);
|
||||
hr = S_OK;
|
||||
}
|
||||
else if (arg == RESIZE_QUIRK)
|
||||
{
|
||||
_resizeQuirk = true;
|
||||
s_ConsumeArg(args, i);
|
||||
hr = S_OK;
|
||||
}
|
||||
else if (arg == CLIENT_COMMANDLINE_ARG)
|
||||
{
|
||||
// Everything after this is the explicit commandline
|
||||
@@ -618,10 +611,6 @@ bool ConsoleArguments::GetInheritCursor() const
|
||||
{
|
||||
return _inheritCursor;
|
||||
}
|
||||
bool ConsoleArguments::IsResizeQuirkEnabled() const
|
||||
{
|
||||
return _resizeQuirk;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Tell us to use a different size than the one parsed as the size of the
|
||||
|
||||
@@ -50,7 +50,6 @@ public:
|
||||
short GetWidth() const;
|
||||
short GetHeight() const;
|
||||
bool GetInheritCursor() const;
|
||||
bool IsResizeQuirkEnabled() const;
|
||||
|
||||
void SetExpectedSize(COORD dimensions) noexcept;
|
||||
|
||||
@@ -69,7 +68,6 @@ public:
|
||||
static const std::wstring_view WIDTH_ARG;
|
||||
static const std::wstring_view HEIGHT_ARG;
|
||||
static const std::wstring_view INHERIT_CURSOR_ARG;
|
||||
static const std::wstring_view RESIZE_QUIRK;
|
||||
static const std::wstring_view FEATURE_ARG;
|
||||
static const std::wstring_view FEATURE_PTY_ARG;
|
||||
|
||||
@@ -102,7 +100,6 @@ private:
|
||||
_serverHandle(serverHandle),
|
||||
_signalHandle(signalHandle),
|
||||
_inheritCursor(inheritCursor),
|
||||
_resizeQuirk(false),
|
||||
_receivedEarlySizeChange{ false },
|
||||
_originalWidth{ -1 },
|
||||
_originalHeight{ -1 }
|
||||
@@ -130,7 +127,6 @@ private:
|
||||
DWORD _serverHandle;
|
||||
DWORD _signalHandle;
|
||||
bool _inheritCursor;
|
||||
bool _resizeQuirk{ false };
|
||||
|
||||
bool _receivedEarlySizeChange;
|
||||
short _originalWidth;
|
||||
|
||||
@@ -43,13 +43,7 @@ VtInputThread::VtInputThread(_In_ wil::unique_hfile hPipe,
|
||||
|
||||
auto engine = std::make_unique<InputStateMachineEngine>(std::move(dispatch), inheritCursor);
|
||||
|
||||
auto engineRef = engine.get();
|
||||
|
||||
_pInputStateMachine = std::make_unique<StateMachine>(std::move(engine));
|
||||
|
||||
// we need this callback to be able to flush an unknown input sequence to the app
|
||||
auto flushCallback = std::bind(&StateMachine::FlushToTerminal, _pInputStateMachine.get());
|
||||
engineRef->SetFlushToInputQueueCallback(flushCallback);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
||||
@@ -73,7 +73,6 @@ VtIo::VtIo() :
|
||||
[[nodiscard]] HRESULT VtIo::Initialize(const ConsoleArguments* const pArgs)
|
||||
{
|
||||
_lookingForCursorPosition = pArgs->GetInheritCursor();
|
||||
_resizeQuirk = pArgs->IsResizeQuirkEnabled();
|
||||
|
||||
// If we were already given VT handles, set up the VT IO engine to use those.
|
||||
if (pArgs->InConptyMode())
|
||||
@@ -193,7 +192,6 @@ VtIo::VtIo() :
|
||||
if (_pVtRenderEngine)
|
||||
{
|
||||
_pVtRenderEngine->SetTerminalOwner(this);
|
||||
_pVtRenderEngine->SetResizeQuirk(_resizeQuirk);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -448,19 +446,3 @@ void VtIo::EnableConptyModeForTests()
|
||||
_objectsCreated = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Method Description:
|
||||
// - Returns true if the Resize Quirk is enabled. This changes the behavior of
|
||||
// conpty to _not_ InvalidateAll the entire viewport on a resize operation.
|
||||
// This is used by the Windows Terminal, because it is prepared to be
|
||||
// connected to a conpty, and handles it's own buffer specifically for a
|
||||
// conpty scenario.
|
||||
// - See also: GH#3490, #4354, #4741
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - true iff we were started with the `--resizeQuirk` flag enabled.
|
||||
bool VtIo::IsResizeQuirkEnabled() const
|
||||
{
|
||||
return _resizeQuirk;
|
||||
}
|
||||
|
||||
@@ -43,8 +43,6 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
void EnableConptyModeForTests();
|
||||
#endif
|
||||
|
||||
bool IsResizeQuirkEnabled() const;
|
||||
|
||||
private:
|
||||
// After CreateIoHandlers is called, these will be invalid.
|
||||
wil::unique_hfile _hInput;
|
||||
@@ -59,8 +57,6 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
bool _lookingForCursorPosition;
|
||||
std::mutex _shutdownLock;
|
||||
|
||||
bool _resizeQuirk{ false };
|
||||
|
||||
std::unique_ptr<Microsoft::Console::Render::VtEngine> _pVtRenderEngine;
|
||||
std::unique_ptr<Microsoft::Console::VtInputThread> _pVtInputThread;
|
||||
std::unique_ptr<Microsoft::Console::PtySignalInputThread> _pPtySignalInputThread;
|
||||
|
||||
@@ -35,6 +35,7 @@ CONSOLE_INFORMATION::CONSOLE_INFORMATION() :
|
||||
// OutputCPInfo initialized below
|
||||
_cookedReadData(nullptr),
|
||||
ConsoleIme{},
|
||||
terminalMouseInput(HandleTerminalKeyEventCallback),
|
||||
_vtIo(),
|
||||
_blinker{},
|
||||
renderData{}
|
||||
@@ -179,6 +180,18 @@ void CONSOLE_INFORMATION::SetCookedReadData(COOKED_READ_DATA* readData) noexcept
|
||||
_cookedReadData = readData;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Handler for inserting key sequences into the buffer when the terminal emulation layer
|
||||
// has determined a key can be converted appropriately into a sequence of inputs
|
||||
// Arguments:
|
||||
// - events - the input events to write to the input buffer
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void CONSOLE_INFORMATION::HandleTerminalKeyEventCallback(_Inout_ std::deque<std::unique_ptr<IInputEvent>>& events)
|
||||
{
|
||||
ServiceLocator::LocateGlobals().getConsoleInformation().pInputBuffer->Write(events);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Return the active screen buffer of the console.
|
||||
// Arguments:
|
||||
|
||||
@@ -759,16 +759,16 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont
|
||||
// if we're headless, not so much. However, GetMaxWindowSizeInCharacters
|
||||
// will only return the buffer size, so we can't use that to clip the arg here.
|
||||
// So only clip the requested size if we're not headless
|
||||
if (g.getConsoleInformation().IsInVtIoMode())
|
||||
{
|
||||
// SetViewportRect doesn't cause the buffer to resize. Manually resize the buffer.
|
||||
RETURN_IF_NTSTATUS_FAILED(context.ResizeScreenBuffer(Viewport::FromInclusive(Window).Dimensions(), false));
|
||||
}
|
||||
if (!g.IsHeadless())
|
||||
{
|
||||
COORD const coordMax = context.GetMaxWindowSizeInCharacters();
|
||||
RETURN_HR_IF(E_INVALIDARG, (NewWindowSize.X > coordMax.X || NewWindowSize.Y > coordMax.Y));
|
||||
}
|
||||
else if (g.getConsoleInformation().IsInVtIoMode())
|
||||
{
|
||||
// SetViewportRect doesn't cause the buffer to resize. Manually resize the buffer.
|
||||
RETURN_IF_NTSTATUS_FAILED(context.ResizeScreenBuffer(Viewport::FromInclusive(Window).Dimensions(), false));
|
||||
}
|
||||
|
||||
// Even if it's the same size, we need to post an update in case the scroll bars need to go away.
|
||||
context.SetViewport(Viewport::FromInclusive(Window), true);
|
||||
@@ -776,15 +776,7 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont
|
||||
{
|
||||
// TODO: MSFT: 9574827 - shouldn't we be looking at or at least logging the failure codes here? (Or making them non-void?)
|
||||
context.PostUpdateWindowSize();
|
||||
|
||||
// Use WriteToScreen to invalidate the viewport with the renderer.
|
||||
// GH#3490 - If we're in conpty mode, don't invalidate the entire
|
||||
// viewport. In conpty mode, the VtEngine will later decide what
|
||||
// part of the buffer actually needs to be re-sent to the terminal.
|
||||
if (!(g.getConsoleInformation().IsInVtIoMode() && g.getConsoleInformation().GetVtIo()->IsResizeQuirkEnabled()))
|
||||
{
|
||||
WriteToScreen(context, context.GetViewport());
|
||||
}
|
||||
WriteToScreen(context, context.GetViewport());
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
@@ -1624,7 +1616,7 @@ void DoSrvPrivateTabClear(const bool fClearAll)
|
||||
void DoSrvPrivateEnableVT200MouseMode(const bool fEnable)
|
||||
{
|
||||
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
gci.GetActiveInputBuffer()->GetTerminalInput().EnableDefaultTracking(fEnable);
|
||||
gci.terminalMouseInput.EnableDefaultTracking(fEnable);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
@@ -1636,7 +1628,7 @@ void DoSrvPrivateEnableVT200MouseMode(const bool fEnable)
|
||||
void DoSrvPrivateEnableUTF8ExtendedMouseMode(const bool fEnable)
|
||||
{
|
||||
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
gci.GetActiveInputBuffer()->GetTerminalInput().SetUtf8ExtendedMode(fEnable);
|
||||
gci.terminalMouseInput.SetUtf8ExtendedMode(fEnable);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
@@ -1648,7 +1640,7 @@ void DoSrvPrivateEnableUTF8ExtendedMouseMode(const bool fEnable)
|
||||
void DoSrvPrivateEnableSGRExtendedMouseMode(const bool fEnable)
|
||||
{
|
||||
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
gci.GetActiveInputBuffer()->GetTerminalInput().SetSGRExtendedMode(fEnable);
|
||||
gci.terminalMouseInput.SetSGRExtendedMode(fEnable);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
@@ -1660,7 +1652,7 @@ void DoSrvPrivateEnableSGRExtendedMouseMode(const bool fEnable)
|
||||
void DoSrvPrivateEnableButtonEventMouseMode(const bool fEnable)
|
||||
{
|
||||
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
gci.GetActiveInputBuffer()->GetTerminalInput().EnableButtonEventTracking(fEnable);
|
||||
gci.terminalMouseInput.EnableButtonEventTracking(fEnable);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
@@ -1672,7 +1664,7 @@ void DoSrvPrivateEnableButtonEventMouseMode(const bool fEnable)
|
||||
void DoSrvPrivateEnableAnyEventMouseMode(const bool fEnable)
|
||||
{
|
||||
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
gci.GetActiveInputBuffer()->GetTerminalInput().EnableAnyEventTracking(fEnable);
|
||||
gci.terminalMouseInput.EnableAnyEventTracking(fEnable);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
@@ -1684,7 +1676,7 @@ void DoSrvPrivateEnableAnyEventMouseMode(const bool fEnable)
|
||||
void DoSrvPrivateEnableAlternateScroll(const bool fEnable)
|
||||
{
|
||||
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
gci.GetActiveInputBuffer()->GetTerminalInput().EnableAlternateScroll(fEnable);
|
||||
gci.terminalMouseInput.EnableAlternateScroll(fEnable);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
|
||||
@@ -466,8 +466,6 @@ size_t InputBuffer::Prepend(_Inout_ std::deque<std::unique_ptr<IInputEvent>>& in
|
||||
{
|
||||
try
|
||||
{
|
||||
_vtInputShouldSuppress = true;
|
||||
auto resetVtInputSupress = wil::scope_exit([&]() { _vtInputShouldSuppress = false; });
|
||||
_HandleConsoleSuspensionEvents(inEvents);
|
||||
if (inEvents.empty())
|
||||
{
|
||||
@@ -558,8 +556,6 @@ size_t InputBuffer::Write(_Inout_ std::deque<std::unique_ptr<IInputEvent>>& inEv
|
||||
{
|
||||
try
|
||||
{
|
||||
_vtInputShouldSuppress = true;
|
||||
auto resetVtInputSuppress = wil::scope_exit([&]() { _vtInputShouldSuppress = false; });
|
||||
_HandleConsoleSuspensionEvents(inEvents);
|
||||
if (inEvents.empty())
|
||||
{
|
||||
@@ -845,7 +841,8 @@ bool InputBuffer::IsInVirtualTerminalInputMode() const
|
||||
// - Handler for inserting key sequences into the buffer when the terminal emulation layer
|
||||
// has determined a key can be converted appropriately into a sequence of inputs
|
||||
// Arguments:
|
||||
// - inEvents - Series of input records to insert into the buffer
|
||||
// - rgInput - Series of input records to insert into the buffer
|
||||
// - cInput - Length of input records array
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void InputBuffer::_HandleTerminalInputCallback(std::deque<std::unique_ptr<IInputEvent>>& inEvents)
|
||||
@@ -859,12 +856,6 @@ void InputBuffer::_HandleTerminalInputCallback(std::deque<std::unique_ptr<IInput
|
||||
inEvents.pop_front();
|
||||
_storage.push_back(std::move(inEvent));
|
||||
}
|
||||
|
||||
if (!_vtInputShouldSuppress)
|
||||
{
|
||||
ServiceLocator::LocateGlobals().hInputEvent.SetEvent();
|
||||
WakeUpReadersWaitingForData();
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
|
||||
@@ -82,12 +82,6 @@ private:
|
||||
std::unique_ptr<IInputEvent> _writePartialByteSequence;
|
||||
Microsoft::Console::VirtualTerminal::TerminalInput _termInput;
|
||||
|
||||
// This flag is used in _HandleTerminalInputCallback
|
||||
// If the InputBuffer leads to a _HandleTerminalInputCallback call,
|
||||
// we should suppress the wakeup functions.
|
||||
// Otherwise, we should be calling them.
|
||||
bool _vtInputShouldSuppress{ false };
|
||||
|
||||
void _ReadBuffer(_Out_ std::deque<std::unique_ptr<IInputEvent>>& outEvents,
|
||||
const size_t readCount,
|
||||
_Out_ size_t& eventsRead,
|
||||
|
||||
@@ -769,17 +769,14 @@ bool ConhostInternalGetSet::SetCursorColor(const COLORREF cursorColor)
|
||||
|
||||
// Routine Description:
|
||||
// - Connects the IsConsolePty call directly into our Driver Message servicing call inside Conhost.exe
|
||||
// - NOTE: This ONE method behaves differently! The rest of the methods on this
|
||||
// interface return true if successful. This one just returns the result.
|
||||
// Arguments:
|
||||
// - isPty: receives the bool indicating whether or not we're in pty mode.
|
||||
// Return Value:
|
||||
// - true if we're in pty mode.
|
||||
bool ConhostInternalGetSet::IsConsolePty() const
|
||||
// - true if successful (see DoSrvIsConsolePty). false otherwise.
|
||||
bool ConhostInternalGetSet::IsConsolePty(bool& isPty) const
|
||||
{
|
||||
bool isPty = false;
|
||||
DoSrvIsConsolePty(isPty);
|
||||
return isPty;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ConhostInternalGetSet::DeleteLines(const size_t count)
|
||||
@@ -894,16 +891,3 @@ bool ConhostInternalGetSet::PrivateScrollRegion(const SMALL_RECT scrollRect,
|
||||
destinationOrigin,
|
||||
standardFillAttrs));
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Checks if the InputBuffer is willing to accept VT Input directly
|
||||
// PrivateIsVtInputEnabled is an internal-only "API" call that the vt commands can execute,
|
||||
// but it is not represented as a function call on our public API surface.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return value:
|
||||
// - true if enabled (see IsInVirtualTerminalInputMode). false otherwise.
|
||||
bool ConhostInternalGetSet::PrivateIsVtInputEnabled() const
|
||||
{
|
||||
return _io.GetActiveInputBuffer()->IsInVirtualTerminalInputMode();
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@ public:
|
||||
|
||||
bool GetConsoleOutputCP(unsigned int& codepage) override;
|
||||
|
||||
bool IsConsolePty() const override;
|
||||
bool IsConsolePty(bool& isPty) const override;
|
||||
|
||||
bool DeleteLines(const size_t count) override;
|
||||
bool InsertLines(const size_t count) override;
|
||||
@@ -166,8 +166,6 @@ public:
|
||||
const COORD destinationOrigin,
|
||||
const bool standardFillAttrs) noexcept override;
|
||||
|
||||
bool PrivateIsVtInputEnabled() const override;
|
||||
|
||||
private:
|
||||
Microsoft::Console::IIoProvider& _io;
|
||||
};
|
||||
|
||||
@@ -445,16 +445,4 @@ void RenderData::ColorSelection(const COORD coordSelectionStart, const COORD coo
|
||||
{
|
||||
Selection::Instance().ColorSelection(coordSelectionStart, coordSelectionEnd, attr);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Returns true if the screen is globally inverted
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - true if the screen is globally inverted
|
||||
bool RenderData::IsScreenReversed() const noexcept
|
||||
{
|
||||
const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
return gci.IsScreenReversed();
|
||||
}
|
||||
#pragma endregion
|
||||
|
||||
@@ -49,8 +49,6 @@ public:
|
||||
COLORREF GetCursorColor() const noexcept override;
|
||||
bool IsCursorDoubleWidth() const noexcept override;
|
||||
|
||||
bool IsScreenReversed() const noexcept override;
|
||||
|
||||
const std::vector<Microsoft::Console::Render::RenderOverlay> GetOverlays() const noexcept override;
|
||||
|
||||
const bool IsGridLineDrawingAllowed() noexcept override;
|
||||
|
||||
@@ -1414,7 +1414,7 @@ bool SCREEN_INFORMATION::IsMaximizedY() const
|
||||
// Save cursor's relative height versus the viewport
|
||||
SHORT const sCursorHeightInViewportBefore = _textBuffer->GetCursor().GetPosition().Y - _viewport.Top();
|
||||
|
||||
HRESULT hr = TextBuffer::Reflow(*_textBuffer.get(), *newTextBuffer.get(), std::nullopt, std::nullopt);
|
||||
HRESULT hr = TextBuffer::Reflow(*_textBuffer.get(), *newTextBuffer.get());
|
||||
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
@@ -1890,7 +1890,7 @@ const SCREEN_INFORMATION& SCREEN_INFORMATION::GetMainBuffer() const
|
||||
ScreenBufferSizeChange(psiNewAltBuffer->GetBufferSize().Dimensions());
|
||||
|
||||
// Tell the VT MouseInput handler that we're in the Alt buffer now
|
||||
gci.GetActiveInputBuffer()->GetTerminalInput().UseAlternateScreenBuffer();
|
||||
gci.terminalMouseInput.UseAlternateScreenBuffer();
|
||||
}
|
||||
return Status;
|
||||
}
|
||||
@@ -1924,7 +1924,7 @@ void SCREEN_INFORMATION::UseMainScreenBuffer()
|
||||
// deleting the alt buffer will give the GetSet back to its main
|
||||
|
||||
// Tell the VT MouseInput handler that we're in the main buffer now
|
||||
gci.GetActiveInputBuffer()->GetTerminalInput().UseMainScreenBuffer();
|
||||
gci.terminalMouseInput.UseMainScreenBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2176,13 +2176,8 @@ void SCREEN_INFORMATION::SetDefaultAttributes(const TextAttribute& attributes,
|
||||
commandLine.UpdatePopups(attributes, popupAttributes, oldPrimaryAttributes, oldPopupAttributes);
|
||||
}
|
||||
|
||||
// Force repaint of entire viewport, unless we're in conpty mode. In that
|
||||
// case, we don't really need to force a redraw of the entire screen just
|
||||
// because the text attributes changed.
|
||||
if (!(gci.IsInVtIoMode()))
|
||||
{
|
||||
GetRenderTarget().TriggerRedrawAll();
|
||||
}
|
||||
// force repaint of entire viewport
|
||||
GetRenderTarget().TriggerRedrawAll();
|
||||
|
||||
gci.ConsoleIme.RefreshAreaAttributes();
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ Revision History:
|
||||
#include "settings.hpp"
|
||||
|
||||
#include "conimeinfo.h"
|
||||
#include "..\terminal\adapter\MouseInput.hpp"
|
||||
#include "VtIo.hpp"
|
||||
#include "CursorBlinker.hpp"
|
||||
|
||||
@@ -106,6 +107,8 @@ public:
|
||||
|
||||
ConsoleImeInfo ConsoleIme;
|
||||
|
||||
Microsoft::Console::VirtualTerminal::MouseInput terminalMouseInput;
|
||||
|
||||
void LockConsole();
|
||||
bool TryLockConsole();
|
||||
void UnlockConsole();
|
||||
@@ -114,6 +117,8 @@ public:
|
||||
|
||||
Microsoft::Console::VirtualTerminal::VtIo* GetVtIo();
|
||||
|
||||
static void HandleTerminalKeyEventCallback(_Inout_ std::deque<std::unique_ptr<IInputEvent>>& events);
|
||||
|
||||
SCREEN_INFORMATION& GetActiveOutputBuffer() override;
|
||||
const SCREEN_INFORMATION& GetActiveOutputBuffer() const override;
|
||||
bool HasActiveOutputBuffer() const;
|
||||
|
||||
@@ -113,8 +113,6 @@ class Microsoft::Console::Render::VtRendererTest
|
||||
TEST_METHOD(WinTelnetTestColors);
|
||||
TEST_METHOD(WinTelnetTestCursor);
|
||||
|
||||
TEST_METHOD(FormattedString);
|
||||
|
||||
TEST_METHOD(TestWrapping);
|
||||
|
||||
TEST_METHOD(TestResize);
|
||||
@@ -262,6 +260,7 @@ void VtRendererTest::Xterm256TestInvalidate()
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Make sure that invalidating all invalidates the whole viewport."));
|
||||
VERIFY_SUCCEEDED(engine->InvalidateAll());
|
||||
qExpectedInput.push_back("\x1b[2J");
|
||||
TestPaint(*engine, [&]() {
|
||||
VERIFY_ARE_EQUAL(view, engine->_invalidRect);
|
||||
});
|
||||
@@ -729,6 +728,7 @@ void VtRendererTest::XtermTestInvalidate()
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Make sure that invalidating all invalidates the whole viewport."));
|
||||
VERIFY_SUCCEEDED(engine->InvalidateAll());
|
||||
qExpectedInput.push_back("\x1b[2J");
|
||||
TestPaint(*engine, [&]() {
|
||||
VERIFY_ARE_EQUAL(view, engine->_invalidRect);
|
||||
});
|
||||
@@ -1472,35 +1472,3 @@ void VtRendererTest::TestCursorVisibility()
|
||||
VERIFY_IS_FALSE(engine->_nextCursorIsVisible);
|
||||
VERIFY_IS_FALSE(engine->_needToDisableCursor);
|
||||
}
|
||||
|
||||
void VtRendererTest::FormattedString()
|
||||
{
|
||||
// This test works with a static cache variable that
|
||||
// can be affected by other tests
|
||||
BEGIN_TEST_METHOD_PROPERTIES()
|
||||
TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method")
|
||||
END_TEST_METHOD_PROPERTIES();
|
||||
|
||||
static const std::string format("\x1b[%dm");
|
||||
const auto value = 12;
|
||||
|
||||
Viewport view = SetUpViewport();
|
||||
wil::unique_hfile hFile = wil::unique_hfile(INVALID_HANDLE_VALUE);
|
||||
auto engine = std::make_unique<Xterm256Engine>(std::move(hFile), p, view, g_ColorTable, static_cast<WORD>(COLOR_TABLE_SIZE));
|
||||
auto pfn = std::bind(&VtRendererTest::WriteCallback, this, std::placeholders::_1, std::placeholders::_2);
|
||||
engine->SetTestCallback(pfn);
|
||||
|
||||
Log::Comment(L"1.) Write it once. It should resize itself.");
|
||||
qExpectedInput.push_back("\x1b[12m");
|
||||
VERIFY_SUCCEEDED(engine->_WriteFormattedString(&format, value));
|
||||
|
||||
Log::Comment(L"2.) Write the same thing again, should be fine.");
|
||||
qExpectedInput.push_back("\x1b[12m");
|
||||
VERIFY_SUCCEEDED(engine->_WriteFormattedString(&format, value));
|
||||
|
||||
Log::Comment(L"3.) Now write something huge. Should resize itself and still be fine.");
|
||||
static const std::string bigFormat("\x1b[28;3;%d;%d;%dm");
|
||||
const auto bigValue = 500;
|
||||
qExpectedInput.push_back("\x1b[28;3;500;500;500m");
|
||||
VERIFY_SUCCEEDED(engine->_WriteFormattedString(&bigFormat, bigValue, bigValue, bigValue));
|
||||
}
|
||||
|
||||
@@ -86,9 +86,7 @@
|
||||
#include <wrl.h>
|
||||
|
||||
// TIL - Terminal Implementation Library
|
||||
#ifndef BLOCK_TIL // Certain projects may want to include TIL manually to gain superpowers
|
||||
#include "til.h"
|
||||
#endif
|
||||
|
||||
#pragma warning(pop)
|
||||
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define PSEUDOCONSOLE_RESIZE_QUIRK (2u)
|
||||
|
||||
HRESULT WINAPI ConptyCreatePseudoConsole(COORD size, HANDLE hInput, HANDLE hOutput, DWORD dwFlags, HPCON* phPC);
|
||||
|
||||
HRESULT WINAPI ConptyResizePseudoConsole(HPCON hPC, COORD size);
|
||||
|
||||
@@ -4,15 +4,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "til/at.h"
|
||||
#include "til/color.h"
|
||||
#include "til/some.h"
|
||||
#include "til/point.h"
|
||||
#include "til/size.h"
|
||||
#include "til/rectangle.h"
|
||||
#include "til/u8u16convert.h"
|
||||
|
||||
//#include "til/operators.h"
|
||||
|
||||
namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
||||
{
|
||||
}
|
||||
|
||||
@@ -1,347 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include "size.h"
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
class BitmapTests;
|
||||
#endif
|
||||
|
||||
namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
||||
{
|
||||
class const_bitterator // Bit Iterator. Bitterator.
|
||||
{
|
||||
public:
|
||||
const_bitterator(const std::vector<bool>& values, size_t pos) :
|
||||
_map(values),
|
||||
_pos(pos)
|
||||
{
|
||||
}
|
||||
|
||||
const_bitterator operator+(const ptrdiff_t movement)
|
||||
{
|
||||
auto copy = *this;
|
||||
copy += movement;
|
||||
return copy;
|
||||
}
|
||||
|
||||
const_bitterator operator-(const ptrdiff_t movement)
|
||||
{
|
||||
auto copy = *this;
|
||||
copy -= movement;
|
||||
return copy;
|
||||
}
|
||||
|
||||
const_bitterator& operator++()
|
||||
{
|
||||
++_pos;
|
||||
return (*this);
|
||||
}
|
||||
|
||||
const_bitterator& operator--()
|
||||
{
|
||||
--_pos;
|
||||
return (*this);
|
||||
}
|
||||
|
||||
const_bitterator& operator+=(const ptrdiff_t& movement)
|
||||
{
|
||||
_pos += movement;
|
||||
return (*this);
|
||||
}
|
||||
|
||||
const_bitterator& operator-=(const ptrdiff_t& movement)
|
||||
{
|
||||
_pos -= movement;
|
||||
return (*this);
|
||||
}
|
||||
|
||||
bool operator==(const const_bitterator& other) const
|
||||
{
|
||||
return _pos == other._pos && _map == other._map;
|
||||
}
|
||||
|
||||
bool operator!=(const const_bitterator& other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
bool operator<(const const_bitterator& other) const
|
||||
{
|
||||
return _pos < other._pos;
|
||||
}
|
||||
|
||||
bool operator>(const const_bitterator& other) const
|
||||
{
|
||||
return _pos > other._pos;
|
||||
}
|
||||
|
||||
bool operator*() const
|
||||
{
|
||||
return _map[_pos];
|
||||
}
|
||||
|
||||
private:
|
||||
size_t _pos;
|
||||
const std::vector<bool>& _map;
|
||||
};
|
||||
|
||||
class const_runerator // Run Iterator. Runerator.
|
||||
{
|
||||
public:
|
||||
const_runerator(const std::vector<bool>& values, til::size sz, size_t pos) :
|
||||
_values(values),
|
||||
_size(sz),
|
||||
_pos(pos)
|
||||
{
|
||||
_calculateArea();
|
||||
}
|
||||
|
||||
const_runerator& operator++()
|
||||
{
|
||||
_pos = _nextPos;
|
||||
_calculateArea();
|
||||
return (*this);
|
||||
}
|
||||
|
||||
bool operator==(const const_runerator& other) const
|
||||
{
|
||||
return _pos == other._pos && _values == other._values;
|
||||
}
|
||||
|
||||
bool operator!=(const const_runerator& other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
bool operator<(const const_runerator& other) const
|
||||
{
|
||||
return _pos < other._pos;
|
||||
}
|
||||
|
||||
bool operator>(const const_runerator& other) const
|
||||
{
|
||||
return _pos > other._pos;
|
||||
}
|
||||
|
||||
til::rectangle operator*() const
|
||||
{
|
||||
return _run;
|
||||
}
|
||||
|
||||
private:
|
||||
const std::vector<bool>& _values;
|
||||
const til::size _size;
|
||||
size_t _pos;
|
||||
size_t _nextPos;
|
||||
til::rectangle _run;
|
||||
|
||||
til::point _indexToPoint(size_t index)
|
||||
{
|
||||
return til::point{ (ptrdiff_t)index % _size.width(), (ptrdiff_t)index / _size.width() };
|
||||
}
|
||||
|
||||
void _calculateArea()
|
||||
{
|
||||
const size_t end = (size_t)_size.area();
|
||||
|
||||
_nextPos = _pos;
|
||||
|
||||
while (_nextPos < end && !_values.at(_nextPos))
|
||||
{
|
||||
++_nextPos;
|
||||
}
|
||||
|
||||
if (_nextPos < end)
|
||||
{
|
||||
// pos is now at the first on bit.
|
||||
const auto runStart = _indexToPoint(_nextPos);
|
||||
const size_t rowEndIndex = (size_t)((runStart.y() + 1) * _size.width());
|
||||
|
||||
ptrdiff_t runLength = 0;
|
||||
|
||||
do
|
||||
{
|
||||
++_nextPos;
|
||||
++runLength;
|
||||
} while (_nextPos < end && _nextPos < rowEndIndex && _values.at(_nextPos));
|
||||
|
||||
_run = til::rectangle{ runStart, til::size{ runLength, static_cast<ptrdiff_t>(1) } };
|
||||
}
|
||||
else
|
||||
{
|
||||
_pos = _nextPos;
|
||||
_run = til::rectangle{};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class bitmap
|
||||
{
|
||||
public:
|
||||
using const_iterator = const const_bitterator;
|
||||
|
||||
bitmap() :
|
||||
bitmap(0, 0)
|
||||
{
|
||||
}
|
||||
|
||||
bitmap(size_t width, size_t height) :
|
||||
bitmap(til::size{ width, height })
|
||||
{
|
||||
}
|
||||
|
||||
bitmap(til::size sz) :
|
||||
_size(sz),
|
||||
_bits(sz.area(), true),
|
||||
_empty(false)
|
||||
{
|
||||
}
|
||||
|
||||
const_iterator begin() const
|
||||
{
|
||||
return const_bitterator(_bits, 0);
|
||||
}
|
||||
|
||||
const_iterator end() const
|
||||
{
|
||||
return const_bitterator(_bits, _size.area());
|
||||
}
|
||||
|
||||
const_iterator begin_row(size_t row) const
|
||||
{
|
||||
return const_bitterator(_bits, row * _size.width());
|
||||
}
|
||||
|
||||
const_iterator end_row(size_t row) const
|
||||
{
|
||||
return const_bitterator(_bits, (row + 1) * _size.width());
|
||||
}
|
||||
|
||||
const_runerator begin_runs() const
|
||||
{
|
||||
return const_runerator(_bits, _size, 0);
|
||||
}
|
||||
|
||||
const_runerator end_runs() const
|
||||
{
|
||||
return const_runerator(_bits, _size, _size.area());
|
||||
}
|
||||
|
||||
void set(til::point pt)
|
||||
{
|
||||
_bits[pt.y() * _size.width() + pt.x()] = true;
|
||||
_empty = false;
|
||||
}
|
||||
|
||||
void reset(til::point pt)
|
||||
{
|
||||
_bits[pt.y() * _size.width() + pt.x()] = false;
|
||||
}
|
||||
|
||||
void set(til::rectangle rc)
|
||||
{
|
||||
for (auto pt : rc)
|
||||
{
|
||||
set(pt);
|
||||
}
|
||||
}
|
||||
|
||||
void reset(til::rectangle rc)
|
||||
{
|
||||
for (auto pt : rc)
|
||||
{
|
||||
reset(pt);
|
||||
}
|
||||
}
|
||||
|
||||
void set_all()
|
||||
{
|
||||
// .clear() then .resize(_size(), true) throws an assert (unsupported operation)
|
||||
// .assign(_size(), true) throws an assert (unsupported operation)
|
||||
|
||||
set(til::rectangle{ til::point{ 0, 0 }, _size });
|
||||
}
|
||||
|
||||
void reset_all()
|
||||
{
|
||||
// .clear() then .resize(_size(), false) throws an assert (unsupported operation)
|
||||
// .assign(_size(), false) throws an assert (unsupported operation)
|
||||
reset(til::rectangle{ til::point{ 0, 0 }, _size });
|
||||
_empty = true;
|
||||
}
|
||||
|
||||
void resize(til::size size)
|
||||
{
|
||||
// Don't resize if it's not different as we mark the whole thing dirty on resize.
|
||||
// TODO: marking it dirty might not be necessary or we should be smart about it
|
||||
// (mark none of it dirty on resize down, mark just the edges on up?)
|
||||
if (_size != size)
|
||||
{
|
||||
_size = size;
|
||||
// .resize(_size(), true) throws an assert (unsupported operation)
|
||||
_bits = std::vector<bool>(_size.area(), true);
|
||||
}
|
||||
}
|
||||
|
||||
void resize(size_t width, size_t height)
|
||||
{
|
||||
resize(til::size{ width, height });
|
||||
}
|
||||
|
||||
constexpr bool empty() const
|
||||
{
|
||||
return _empty;
|
||||
}
|
||||
|
||||
const til::size& size() const
|
||||
{
|
||||
return _size;
|
||||
}
|
||||
|
||||
operator bool() const noexcept
|
||||
{
|
||||
return !_bits.empty();
|
||||
}
|
||||
|
||||
bitmap operator+(const point& pt) const
|
||||
{
|
||||
auto temp = *this;
|
||||
return temp += pt;
|
||||
}
|
||||
|
||||
bitmap& operator+=(const point& pt)
|
||||
{
|
||||
// early return if nothing to do.
|
||||
if (pt.x() == 0 && pt.y() == 0)
|
||||
{
|
||||
return (*this);
|
||||
}
|
||||
|
||||
// If we're told to shift the whole thing by an entire width or height,
|
||||
// the effect is to just clear the whole bitmap.
|
||||
if (pt.x() >= _size.width() || pt.y() >= _size.height())
|
||||
{
|
||||
reset_all();
|
||||
return (*this);
|
||||
}
|
||||
|
||||
// TODO: any way to reconcile this with walk directions from scrolling apis?
|
||||
// TODO: actually implement translation.
|
||||
|
||||
return (*this);
|
||||
}
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
friend class ::BitmapTests;
|
||||
#endif
|
||||
|
||||
private:
|
||||
bool _empty;
|
||||
til::size _size;
|
||||
std::vector<bool> _bits;
|
||||
};
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
class BitteratorTests;
|
||||
#endif
|
||||
|
||||
namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
||||
{
|
||||
};
|
||||
@@ -1,117 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
||||
{
|
||||
// color is a universal integral 8bpp RGBA (0-255) color type implicitly convertible to/from
|
||||
// a number of other color types.
|
||||
#pragma warning(push)
|
||||
// we can't depend on GSL here (some libraries use BLOCK_GSL), so we use static_cast for explicit narrowing
|
||||
#pragma warning(disable : 26472)
|
||||
struct color
|
||||
{
|
||||
uint8_t r, g, b, a;
|
||||
|
||||
constexpr color() noexcept :
|
||||
r{ 0 },
|
||||
g{ 0 },
|
||||
b{ 0 },
|
||||
a{ 0 } {}
|
||||
|
||||
constexpr color(uint8_t _r, uint8_t _g, uint8_t _b) noexcept :
|
||||
r{ _r },
|
||||
g{ _g },
|
||||
b{ _b },
|
||||
a{ 255 } {}
|
||||
|
||||
constexpr color(uint8_t _r, uint8_t _g, uint8_t _b, uint8_t _a) noexcept :
|
||||
r{ _r },
|
||||
g{ _g },
|
||||
b{ _b },
|
||||
a{ _a } {}
|
||||
|
||||
constexpr color(const color&) = default;
|
||||
constexpr color(color&&) = default;
|
||||
color& operator=(const color&) = default;
|
||||
color& operator=(color&&) = default;
|
||||
~color() = default;
|
||||
|
||||
#ifdef _WINDEF_
|
||||
constexpr color(COLORREF c) :
|
||||
r{ static_cast<uint8_t>(c & 0xFF) },
|
||||
g{ static_cast<uint8_t>((c & 0xFF00) >> 8) },
|
||||
b{ static_cast<uint8_t>((c & 0xFF0000) >> 16) },
|
||||
a{ 255 }
|
||||
{
|
||||
}
|
||||
|
||||
operator COLORREF() const noexcept
|
||||
{
|
||||
return static_cast<COLORREF>(r) | (static_cast<COLORREF>(g) << 8) | (static_cast<COLORREF>(b) << 16);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Method Description:
|
||||
// - Converting constructor for any other color structure type containing integral R, G, B, A (case sensitive.)
|
||||
// Notes:
|
||||
// - This and all below conversions make use of std::enable_if and a default parameter to disambiguate themselves.
|
||||
// enable_if will result in an <error-type> if the constraint within it is not met, which will make this
|
||||
// template ill-formed. Because SFINAE, ill-formed templated members "disappear" instead of causing an error.
|
||||
template<typename TOther>
|
||||
constexpr color(const TOther& other, std::enable_if_t<std::is_integral_v<decltype(std::declval<TOther>().R)> && std::is_integral_v<decltype(std::declval<TOther>().A)>, int> /*sentinel*/ = 0) :
|
||||
r{ static_cast<uint8_t>(other.R) },
|
||||
g{ static_cast<uint8_t>(other.G) },
|
||||
b{ static_cast<uint8_t>(other.B) },
|
||||
a{ static_cast<uint8_t>(other.A) }
|
||||
{
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Converting constructor for any other color structure type containing integral r, g, b, a (case sensitive.)
|
||||
template<typename TOther>
|
||||
constexpr color(const TOther& other, std::enable_if_t<std::is_integral_v<decltype(std::declval<TOther>().r)> && std::is_integral_v<decltype(std::declval<TOther>().a)>, int> /*sentinel*/ = 0) :
|
||||
r{ static_cast<uint8_t>(other.r) },
|
||||
g{ static_cast<uint8_t>(other.g) },
|
||||
b{ static_cast<uint8_t>(other.b) },
|
||||
a{ static_cast<uint8_t>(other.a) }
|
||||
{
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Converting constructor for any other color structure type containing floating-point R, G, B, A (case sensitive.)
|
||||
template<typename TOther>
|
||||
constexpr color(const TOther& other, std::enable_if_t<std::is_floating_point_v<decltype(std::declval<TOther>().R)> && std::is_floating_point_v<decltype(std::declval<TOther>().A)>, float> /*sentinel*/ = 1.0f) :
|
||||
r{ static_cast<uint8_t>(other.R * 255.0f) },
|
||||
g{ static_cast<uint8_t>(other.G * 255.0f) },
|
||||
b{ static_cast<uint8_t>(other.B * 255.0f) },
|
||||
a{ static_cast<uint8_t>(other.A * 255.0f) }
|
||||
{
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Converting constructor for any other color structure type containing floating-point r, g, b, a (case sensitive.)
|
||||
template<typename TOther>
|
||||
constexpr color(const TOther& other, std::enable_if_t<std::is_floating_point_v<decltype(std::declval<TOther>().r)> && std::is_floating_point_v<decltype(std::declval<TOther>().a)>, float> /*sentinel*/ = 1.0f) :
|
||||
r{ static_cast<uint8_t>(other.r * 255.0f) },
|
||||
g{ static_cast<uint8_t>(other.g * 255.0f) },
|
||||
b{ static_cast<uint8_t>(other.b * 255.0f) },
|
||||
a{ static_cast<uint8_t>(other.a * 255.0f) }
|
||||
{
|
||||
}
|
||||
|
||||
#ifdef D3DCOLORVALUE_DEFINED
|
||||
constexpr operator D3DCOLORVALUE() const
|
||||
{
|
||||
return D3DCOLORVALUE{ r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f };
|
||||
}
|
||||
#endif
|
||||
|
||||
constexpr bool operator==(const til::color& other) const
|
||||
{
|
||||
return r == other.r && g == other.g && b == other.b && a == other.a;
|
||||
}
|
||||
};
|
||||
#pragma warning(pop)
|
||||
}
|
||||
@@ -1,256 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rectangle.h"
|
||||
#include "size.h"
|
||||
#include "bitmap.h"
|
||||
|
||||
#define _TIL_INLINEPREFIX __declspec(noinline) inline
|
||||
|
||||
namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
||||
{
|
||||
// RECTANGLE VS SIZE
|
||||
// ADD will grow the total area of the rectangle. The sign is the direction to grow.
|
||||
_TIL_INLINEPREFIX rectangle operator+(const rectangle& lhs, const size& rhs)
|
||||
{
|
||||
// Fetch the pieces of the rectangle.
|
||||
auto l = lhs.left();
|
||||
auto r = lhs.right();
|
||||
auto t = lhs.top();
|
||||
auto b = lhs.bottom();
|
||||
|
||||
// Fetch the scale factors we're using.
|
||||
const auto width = rhs.width();
|
||||
const auto height = rhs.height();
|
||||
|
||||
// Since this is the add operation versus a size, the result
|
||||
// should grow the total rectangle area.
|
||||
// The sign determines which edge of the rectangle moves.
|
||||
// We use the magnitude as how far to move.
|
||||
if (width > 0)
|
||||
{
|
||||
// Adding the positive makes the rectangle "grow"
|
||||
// because right stretches outward (to the right).
|
||||
//
|
||||
// Example with adding width 3...
|
||||
// |-- x = origin
|
||||
// V
|
||||
// x---------| x------------|
|
||||
// | | | |
|
||||
// | | | |
|
||||
// |---------| |------------|
|
||||
// BEFORE AFTER
|
||||
THROW_HR_IF(E_ABORT, !base::CheckAdd(r, width).AssignIfValid(&r));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Adding the negative makes the rectangle "grow"
|
||||
// because left stretches outward (to the left).
|
||||
//
|
||||
// Example with adding width -3...
|
||||
// |-- x = origin
|
||||
// V
|
||||
// x---------| |--x---------|
|
||||
// | | | |
|
||||
// | | | |
|
||||
// |---------| |------------|
|
||||
// BEFORE AFTER
|
||||
THROW_HR_IF(E_ABORT, !base::CheckAdd(l, width).AssignIfValid(&l));
|
||||
}
|
||||
|
||||
if (height > 0)
|
||||
{
|
||||
// Adding the positive makes the rectangle "grow"
|
||||
// because bottom stretches outward (to the down).
|
||||
//
|
||||
// Example with adding height 2...
|
||||
// |-- x = origin
|
||||
// V
|
||||
// x---------| x---------|
|
||||
// | | | |
|
||||
// | | | |
|
||||
// |---------| | |
|
||||
// | |
|
||||
// |---------|
|
||||
// BEFORE AFTER
|
||||
THROW_HR_IF(E_ABORT, !base::CheckAdd(b, height).AssignIfValid(&b));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Adding the negative makes the rectangle "grow"
|
||||
// because top stretches outward (to the up).
|
||||
//
|
||||
// Example with adding height -2...
|
||||
// |-- x = origin
|
||||
// |
|
||||
// | |---------|
|
||||
// V | |
|
||||
// x---------| x |
|
||||
// | | | |
|
||||
// | | | |
|
||||
// |---------| |---------|
|
||||
// BEFORE AFTER
|
||||
THROW_HR_IF(E_ABORT, !base::CheckAdd(t, height).AssignIfValid(&t));
|
||||
}
|
||||
|
||||
return rectangle{ til::point{ l, t }, til::point{ r, b } };
|
||||
}
|
||||
|
||||
_TIL_INLINEPREFIX rectangle& operator+=(rectangle& lhs, const size& rhs)
|
||||
{
|
||||
lhs = lhs + rhs;
|
||||
return lhs;
|
||||
}
|
||||
|
||||
// SUB will shrink the total area of the rectangle. The sign is the direction to shrink.
|
||||
_TIL_INLINEPREFIX rectangle operator-(const rectangle& lhs, const size& rhs)
|
||||
{
|
||||
// Fetch the pieces of the rectangle.
|
||||
auto l = lhs.left();
|
||||
auto r = lhs.right();
|
||||
auto t = lhs.top();
|
||||
auto b = lhs.bottom();
|
||||
|
||||
// Fetch the scale factors we're using.
|
||||
const auto width = rhs.width();
|
||||
const auto height = rhs.height();
|
||||
|
||||
// Since this is the subtract operation versus a size, the result
|
||||
// should shrink the total rectangle area.
|
||||
// The sign determines which edge of the rectangle moves.
|
||||
// We use the magnitude as how far to move.
|
||||
if (width > 0)
|
||||
{
|
||||
// Subtracting the positive makes the rectangle "shrink"
|
||||
// because right pulls inward (to the left).
|
||||
//
|
||||
// Example with subtracting width 3...
|
||||
// |-- x = origin
|
||||
// V
|
||||
// x---------| x------|
|
||||
// | | | |
|
||||
// | | | |
|
||||
// |---------| |------|
|
||||
// BEFORE AFTER
|
||||
THROW_HR_IF(E_ABORT, !base::CheckSub(r, width).AssignIfValid(&r));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Subtracting the negative makes the rectangle "shrink"
|
||||
// because left pulls inward (to the right).
|
||||
//
|
||||
// Example with subtracting width -3...
|
||||
// |-- x = origin
|
||||
// V
|
||||
// x---------| x |------|
|
||||
// | | | |
|
||||
// | | | |
|
||||
// |---------| |------|
|
||||
// BEFORE AFTER
|
||||
THROW_HR_IF(E_ABORT, !base::CheckSub(l, width).AssignIfValid(&l));
|
||||
}
|
||||
|
||||
if (height > 0)
|
||||
{
|
||||
// Subtracting the positive makes the rectangle "shrink"
|
||||
// because bottom pulls inward (to the up).
|
||||
//
|
||||
// Example with subtracting height 2...
|
||||
// |-- x = origin
|
||||
// V
|
||||
// x---------| x---------|
|
||||
// | | |---------|
|
||||
// | |
|
||||
// |---------|
|
||||
// BEFORE AFTER
|
||||
THROW_HR_IF(E_ABORT, !base::CheckSub(b, height).AssignIfValid(&b));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Subtracting the positive makes the rectangle "shrink"
|
||||
// because top pulls inward (to the down).
|
||||
//
|
||||
// Example with subtracting height -2...
|
||||
// |-- x = origin
|
||||
// V
|
||||
// x---------| x
|
||||
// | |
|
||||
// | | |---------|
|
||||
// |---------| |---------|
|
||||
// BEFORE AFTER
|
||||
THROW_HR_IF(E_ABORT, !base::CheckSub(t, height).AssignIfValid(&t));
|
||||
}
|
||||
|
||||
return rectangle{ til::point{ l, t }, til::point{ r, b } };
|
||||
}
|
||||
|
||||
_TIL_INLINEPREFIX rectangle& operator-=(rectangle& lhs, const size& rhs)
|
||||
{
|
||||
lhs = lhs - rhs;
|
||||
return lhs;
|
||||
}
|
||||
|
||||
// MUL will scale the entire rectangle by the size L/R * WIDTH and T/B * HEIGHT.
|
||||
_TIL_INLINEPREFIX rectangle operator*(const rectangle& lhs, const size& rhs)
|
||||
{
|
||||
ptrdiff_t l;
|
||||
THROW_HR_IF(E_ABORT, !(base::MakeCheckedNum(lhs.left()) * rhs.width()).AssignIfValid(&l));
|
||||
|
||||
ptrdiff_t t;
|
||||
THROW_HR_IF(E_ABORT, !(base::MakeCheckedNum(lhs.top()) * rhs.height()).AssignIfValid(&t));
|
||||
|
||||
ptrdiff_t r;
|
||||
THROW_HR_IF(E_ABORT, !(base::MakeCheckedNum(lhs.right()) * rhs.width()).AssignIfValid(&r));
|
||||
|
||||
ptrdiff_t b;
|
||||
THROW_HR_IF(E_ABORT, !(base::MakeCheckedNum(lhs.bottom()) * rhs.height()).AssignIfValid(&b));
|
||||
|
||||
return til::rectangle{ l, t, r, b };
|
||||
}
|
||||
|
||||
// POINT VS SIZE
|
||||
// This is a convenience and will take X vs WIDTH and Y vs HEIGHT.
|
||||
_TIL_INLINEPREFIX point operator+(const point& lhs, const size& rhs)
|
||||
{
|
||||
return lhs + til::point{ rhs.width(), rhs.height() };
|
||||
}
|
||||
|
||||
_TIL_INLINEPREFIX point operator-(const point& lhs, const size& rhs)
|
||||
{
|
||||
return lhs - til::point{ rhs.width(), rhs.height() };
|
||||
}
|
||||
|
||||
_TIL_INLINEPREFIX point operator*(const point& lhs, const size& rhs)
|
||||
{
|
||||
return lhs * til::point{ rhs.width(), rhs.height() };
|
||||
}
|
||||
|
||||
_TIL_INLINEPREFIX point operator/(const point& lhs, const size& rhs)
|
||||
{
|
||||
return lhs / til::point{ rhs.width(), rhs.height() };
|
||||
}
|
||||
|
||||
// SIZE VS POINT
|
||||
// This is a convenience and will take WIDTH vs X and HEIGHT vs Y.
|
||||
_TIL_INLINEPREFIX size operator+(const size& lhs, const point& rhs)
|
||||
{
|
||||
return lhs + til::size(rhs.x(), rhs.y());
|
||||
}
|
||||
|
||||
_TIL_INLINEPREFIX size operator-(const size& lhs, const point& rhs)
|
||||
{
|
||||
return lhs - til::size(rhs.x(), rhs.y());
|
||||
}
|
||||
|
||||
_TIL_INLINEPREFIX size operator*(const size& lhs, const point& rhs)
|
||||
{
|
||||
return lhs * til::size(rhs.x(), rhs.y());
|
||||
}
|
||||
|
||||
_TIL_INLINEPREFIX size operator/(const size& lhs, const point& rhs)
|
||||
{
|
||||
return lhs / til::size(rhs.x(), rhs.y());
|
||||
}
|
||||
}
|
||||
@@ -1,248 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
class PointTests;
|
||||
#endif
|
||||
|
||||
namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
||||
{
|
||||
class point
|
||||
{
|
||||
public:
|
||||
constexpr point() noexcept :
|
||||
point(0, 0)
|
||||
{
|
||||
}
|
||||
|
||||
// On 64-bit processors, int and ptrdiff_t are different fundamental types.
|
||||
// On 32-bit processors, they're the same which makes this a double-definition
|
||||
// with the `ptrdiff_t` one below.
|
||||
#if defined(_M_AMD64) || defined(_M_ARM64)
|
||||
constexpr point(int x, int y) noexcept :
|
||||
point(static_cast<ptrdiff_t>(x), static_cast<ptrdiff_t>(y))
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
point(size_t x, size_t y)
|
||||
{
|
||||
THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(x).AssignIfValid(&_x));
|
||||
THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(y).AssignIfValid(&_y));
|
||||
}
|
||||
|
||||
constexpr point(ptrdiff_t x, ptrdiff_t y) noexcept :
|
||||
_x(x),
|
||||
_y(y)
|
||||
{
|
||||
}
|
||||
|
||||
// This template will convert to point from anything that has an X and a Y field that appear convertible to an integer value
|
||||
template<typename TOther>
|
||||
constexpr point(const TOther& other, std::enable_if_t<std::is_integral_v<decltype(std::declval<TOther>().X)> && std::is_integral_v<decltype(std::declval<TOther>().Y)>, int> /*sentinel*/ = 0) :
|
||||
point(static_cast<ptrdiff_t>(other.X), static_cast<ptrdiff_t>(other.Y))
|
||||
{
|
||||
}
|
||||
|
||||
// This template will convert to point from anything that has a x and a y field that appear convertible to an integer value
|
||||
template<typename TOther>
|
||||
constexpr point(const TOther& other, std::enable_if_t<std::is_integral_v<decltype(std::declval<TOther>().x)> && std::is_integral_v<decltype(std::declval<TOther>().y)>, int> /*sentinel*/ = 0) :
|
||||
point(static_cast<ptrdiff_t>(other.x), static_cast<ptrdiff_t>(other.y))
|
||||
{
|
||||
}
|
||||
|
||||
constexpr bool operator==(const point& other) const noexcept
|
||||
{
|
||||
return _x == other._x &&
|
||||
_y == other._y;
|
||||
}
|
||||
|
||||
constexpr bool operator!=(const point& other) const noexcept
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
constexpr operator bool() const noexcept
|
||||
{
|
||||
return _x != 0 || _y != 0;
|
||||
}
|
||||
|
||||
constexpr bool operator<(const point& other) const noexcept
|
||||
{
|
||||
if (_y < other._y)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (_y > other._y)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return _x < other._x;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr bool operator>(const point& other) const noexcept
|
||||
{
|
||||
if (_y > other._y)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (_y < other._y)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return _x > other._x;
|
||||
}
|
||||
}
|
||||
|
||||
point operator+(const point& other) const
|
||||
{
|
||||
ptrdiff_t x;
|
||||
THROW_HR_IF(E_ABORT, !base::CheckAdd(_x, other._x).AssignIfValid(&x));
|
||||
|
||||
ptrdiff_t y;
|
||||
THROW_HR_IF(E_ABORT, !base::CheckAdd(_y, other._y).AssignIfValid(&y));
|
||||
|
||||
return point{ x, y };
|
||||
}
|
||||
|
||||
point operator-(const point& other) const
|
||||
{
|
||||
ptrdiff_t x;
|
||||
THROW_HR_IF(E_ABORT, !base::CheckSub(_x, other._x).AssignIfValid(&x));
|
||||
|
||||
ptrdiff_t y;
|
||||
THROW_HR_IF(E_ABORT, !base::CheckSub(_y, other._y).AssignIfValid(&y));
|
||||
|
||||
return point{ x, y };
|
||||
}
|
||||
|
||||
point operator*(const point& other) const
|
||||
{
|
||||
ptrdiff_t x;
|
||||
THROW_HR_IF(E_ABORT, !base::CheckMul(_x, other._x).AssignIfValid(&x));
|
||||
|
||||
ptrdiff_t y;
|
||||
THROW_HR_IF(E_ABORT, !base::CheckMul(_y, other._y).AssignIfValid(&y));
|
||||
|
||||
return point{ x, y };
|
||||
}
|
||||
|
||||
point operator/(const point& other) const
|
||||
{
|
||||
ptrdiff_t x;
|
||||
THROW_HR_IF(E_ABORT, !base::CheckDiv(_x, other._x).AssignIfValid(&x));
|
||||
|
||||
ptrdiff_t y;
|
||||
THROW_HR_IF(E_ABORT, !base::CheckDiv(_y, other._y).AssignIfValid(&y));
|
||||
|
||||
return point{ x, y };
|
||||
}
|
||||
|
||||
constexpr ptrdiff_t x() const noexcept
|
||||
{
|
||||
return _x;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T x() const
|
||||
{
|
||||
T ret;
|
||||
THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(x()).AssignIfValid(&ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
constexpr ptrdiff_t y() const noexcept
|
||||
{
|
||||
return _y;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T y() const
|
||||
{
|
||||
T ret;
|
||||
THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(y()).AssignIfValid(&ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef _WINCONTYPES_
|
||||
operator COORD() const
|
||||
{
|
||||
COORD ret;
|
||||
THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(_x).AssignIfValid(&ret.X));
|
||||
THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(_y).AssignIfValid(&ret.Y));
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _WINDEF_
|
||||
operator POINT() const
|
||||
{
|
||||
POINT ret;
|
||||
THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(_x).AssignIfValid(&ret.x));
|
||||
THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(_y).AssignIfValid(&ret.y));
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef DCOMMON_H_INCLUDED
|
||||
constexpr operator D2D1_POINT_2F() const noexcept
|
||||
{
|
||||
return D2D1_POINT_2F{ gsl::narrow_cast<float>(_x), gsl::narrow_cast<float>(_y) };
|
||||
}
|
||||
#endif
|
||||
|
||||
protected:
|
||||
ptrdiff_t _x;
|
||||
ptrdiff_t _y;
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
friend class ::PointTests;
|
||||
#endif
|
||||
};
|
||||
}
|
||||
|
||||
#ifdef __WEX_COMMON_H__
|
||||
namespace WEX::TestExecution
|
||||
{
|
||||
template<>
|
||||
class VerifyOutputTraits<::til::point>
|
||||
{
|
||||
public:
|
||||
static WEX::Common::NoThrowString ToString(const ::til::point& point)
|
||||
{
|
||||
return WEX::Common::NoThrowString().Format(L"(X:%td, Y:%td)", point.x(), point.y());
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
class VerifyCompareTraits<::til::point, ::til::point>
|
||||
{
|
||||
public:
|
||||
static bool AreEqual(const ::til::point& expected, const ::til::point& actual) noexcept
|
||||
{
|
||||
return expected == actual;
|
||||
}
|
||||
|
||||
static bool AreSame(const ::til::point& expected, const ::til::point& actual) noexcept
|
||||
{
|
||||
return &expected == &actual;
|
||||
}
|
||||
|
||||
static bool IsLessThan(const ::til::point& expectedLess, const ::til::point& expectedGreater) = delete;
|
||||
|
||||
static bool IsGreaterThan(const ::til::point& expectedGreater, const ::til::point& expectedLess) = delete;
|
||||
|
||||
static bool IsNull(const ::til::point& object) noexcept
|
||||
{
|
||||
return object == til::point{};
|
||||
}
|
||||
};
|
||||
};
|
||||
#endif
|
||||
@@ -1,505 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "point.h"
|
||||
#include "size.h"
|
||||
#include "some.h"
|
||||
|
||||
#include "recterator.h"
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
class RectangleTests;
|
||||
#endif
|
||||
|
||||
namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
||||
{
|
||||
class rectangle
|
||||
{
|
||||
public:
|
||||
using const_iterator = recterator;
|
||||
|
||||
constexpr rectangle() noexcept :
|
||||
rectangle(til::point{ 0, 0 }, til::point{ 0, 0 })
|
||||
{
|
||||
}
|
||||
|
||||
// On 64-bit processors, int and ptrdiff_t are different fundamental types.
|
||||
// On 32-bit processors, they're the same which makes this a double-definition
|
||||
// with the `ptrdiff_t` one below.
|
||||
#if defined(_M_AMD64) || defined(_M_ARM64)
|
||||
constexpr rectangle(int left, int top, int right, int bottom) noexcept :
|
||||
rectangle(til::point{ left, top }, til::point{ right, bottom })
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
rectangle(size_t left, size_t top, size_t right, size_t bottom) :
|
||||
rectangle(til::point{ left, top }, til::point{ right, bottom })
|
||||
{
|
||||
}
|
||||
|
||||
constexpr rectangle(ptrdiff_t left, ptrdiff_t top, ptrdiff_t right, ptrdiff_t bottom) noexcept :
|
||||
rectangle(til::point{ left, top }, til::point{ right, bottom })
|
||||
{
|
||||
}
|
||||
|
||||
// Creates a 1x1 rectangle with the given top-left corner.
|
||||
rectangle(til::point topLeft) :
|
||||
_topLeft(topLeft)
|
||||
{
|
||||
_bottomRight = _topLeft + til::point{ 1, 1 };
|
||||
}
|
||||
|
||||
// Creates a rectangle where you specify the top-left corner (included)
|
||||
// and the bottom-right corner (excluded)
|
||||
constexpr rectangle(til::point topLeft, til::point bottomRight) noexcept :
|
||||
_topLeft(topLeft),
|
||||
_bottomRight(bottomRight)
|
||||
{
|
||||
}
|
||||
|
||||
// Creates a rectangle with the given size where the top-left corner
|
||||
// is set to 0,0.
|
||||
constexpr rectangle(til::size size) noexcept :
|
||||
_topLeft(til::point{ 0, 0 }),
|
||||
_bottomRight(til::point{ size.width(), size.height() })
|
||||
{
|
||||
}
|
||||
|
||||
// Creates a rectangle at the given top-left corner point X,Y that extends
|
||||
// down (+Y direction) and right (+X direction) for the given size.
|
||||
rectangle(til::point topLeft, til::size size) :
|
||||
_topLeft(topLeft),
|
||||
_bottomRight(topLeft + til::point{ size.width(), size.height() })
|
||||
{
|
||||
}
|
||||
|
||||
#ifdef _WINCONTYPES_
|
||||
// This extra specialization exists for SMALL_RECT because it's the only rectangle in the world that we know of
|
||||
// with the bottom and right fields INCLUSIVE to the rectangle itself.
|
||||
// It will perform math on the way in to ensure that it is represented as EXCLUSIVE.
|
||||
rectangle(SMALL_RECT sr)
|
||||
{
|
||||
_topLeft = til::point{ static_cast<ptrdiff_t>(sr.Left), static_cast<ptrdiff_t>(sr.Top) };
|
||||
|
||||
_bottomRight = til::point{ static_cast<ptrdiff_t>(sr.Right), static_cast<ptrdiff_t>(sr.Bottom) } + til::point{ 1, 1 };
|
||||
}
|
||||
#endif
|
||||
|
||||
// This template will convert to rectangle from anything that has a Left, Top, Right, and Bottom field that appear convertible to an integer value
|
||||
template<typename TOther>
|
||||
constexpr rectangle(const TOther& other, std::enable_if_t<std::is_integral_v<decltype(std::declval<TOther>().Top)> && std::is_integral_v<decltype(std::declval<TOther>().Left)> && std::is_integral_v<decltype(std::declval<TOther>().Bottom)> && std::is_integral_v<decltype(std::declval<TOther>().Right)>, int> /*sentinel*/ = 0) :
|
||||
rectangle(til::point{ static_cast<ptrdiff_t>(other.Left), static_cast<ptrdiff_t>(other.Top) }, til::point{ static_cast<ptrdiff_t>(other.Right), static_cast<ptrdiff_t>(other.Bottom) })
|
||||
{
|
||||
}
|
||||
|
||||
// This template will convert to rectangle from anything that has a left, top, right, and bottom field that appear convertible to an integer value
|
||||
template<typename TOther>
|
||||
constexpr rectangle(const TOther& other, std::enable_if_t<std::is_integral_v<decltype(std::declval<TOther>().top)> && std::is_integral_v<decltype(std::declval<TOther>().left)> && std::is_integral_v<decltype(std::declval<TOther>().bottom)> && std::is_integral_v<decltype(std::declval<TOther>().right)>, int> /*sentinel*/ = 0) :
|
||||
rectangle(til::point{ static_cast<ptrdiff_t>(other.left), static_cast<ptrdiff_t>(other.top) }, til::point{ static_cast<ptrdiff_t>(other.right), static_cast<ptrdiff_t>(other.bottom) })
|
||||
{
|
||||
}
|
||||
|
||||
constexpr rectangle& operator=(const rectangle other) noexcept
|
||||
{
|
||||
_topLeft = other._topLeft;
|
||||
_bottomRight = other._bottomRight;
|
||||
return (*this);
|
||||
}
|
||||
|
||||
constexpr bool operator==(const rectangle& other) const noexcept
|
||||
{
|
||||
return _topLeft == other._topLeft &&
|
||||
_bottomRight == other._bottomRight;
|
||||
}
|
||||
|
||||
constexpr bool operator!=(const rectangle& other) const noexcept
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
constexpr operator bool() const noexcept
|
||||
{
|
||||
return _topLeft.x() < _bottomRight.x() &&
|
||||
_topLeft.y() < _bottomRight.y();
|
||||
}
|
||||
|
||||
const_iterator begin() const
|
||||
{
|
||||
return recterator(_topLeft, size());
|
||||
}
|
||||
|
||||
const_iterator end() const
|
||||
{
|
||||
return recterator(_topLeft, size(), { _topLeft.x(), _topLeft.y() + height() });
|
||||
}
|
||||
|
||||
// OR = union
|
||||
constexpr rectangle operator|(const rectangle& other) const noexcept
|
||||
{
|
||||
const auto thisEmpty = empty();
|
||||
const auto otherEmpty = other.empty();
|
||||
|
||||
// If both are empty, return empty rect.
|
||||
if (thisEmpty && otherEmpty)
|
||||
{
|
||||
return rectangle{};
|
||||
}
|
||||
|
||||
// If this is empty but not the other one, then give the other.
|
||||
if (thisEmpty)
|
||||
{
|
||||
return other;
|
||||
}
|
||||
|
||||
// If the other is empty but not this, give this.
|
||||
if (otherEmpty)
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
|
||||
// If we get here, they're both not empty. Do math.
|
||||
const auto l = std::min(left(), other.left());
|
||||
const auto t = std::min(top(), other.top());
|
||||
const auto r = std::max(right(), other.right());
|
||||
const auto b = std::max(bottom(), other.bottom());
|
||||
return rectangle{ l, t, r, b };
|
||||
}
|
||||
|
||||
// AND = intersect
|
||||
constexpr rectangle operator&(const rectangle& other) const noexcept
|
||||
{
|
||||
const auto l = std::max(left(), other.left());
|
||||
const auto r = std::min(right(), other.right());
|
||||
|
||||
// If the width dimension would be empty, give back empty rectangle.
|
||||
if (l >= r)
|
||||
{
|
||||
return rectangle{};
|
||||
}
|
||||
|
||||
const auto t = std::max(top(), other.top());
|
||||
const auto b = std::min(bottom(), other.bottom());
|
||||
|
||||
// If the height dimension would be empty, give back empty rectangle.
|
||||
if (t >= b)
|
||||
{
|
||||
return rectangle{};
|
||||
}
|
||||
|
||||
return rectangle{ l, t, r, b };
|
||||
}
|
||||
|
||||
// - = subtract
|
||||
some<rectangle, 4> operator-(const rectangle& other) const
|
||||
{
|
||||
some<rectangle, 4> result;
|
||||
|
||||
// We could have up to four rectangles describing the area resulting when you take removeMe out of main.
|
||||
// Find the intersection of the two so we know which bits of removeMe are actually applicable
|
||||
// to the original rectangle for subtraction purposes.
|
||||
const auto intersect = *this & other;
|
||||
|
||||
// If there's no intersect, there's nothing to remove.
|
||||
if (intersect.empty())
|
||||
{
|
||||
// Just put the original rectangle into the results and return early.
|
||||
result.push_back(*this);
|
||||
}
|
||||
// If the original rectangle matches the intersect, there is nothing to return.
|
||||
else if (*this != intersect)
|
||||
{
|
||||
// Generate our potential four viewports that represent the region of the original that falls outside of the remove area.
|
||||
// We will bias toward generating wide rectangles over tall rectangles (if possible) so that optimizations that apply
|
||||
// to manipulating an entire row at once can be realized by other parts of the console code. (i.e. Run Length Encoding)
|
||||
// In the following examples, the found remaining regions are represented by:
|
||||
// T = Top B = Bottom L = Left R = Right
|
||||
//
|
||||
// 4 Sides but Identical:
|
||||
// |-----------this-----------| |-----------this-----------|
|
||||
// | | | |
|
||||
// | | | |
|
||||
// | | | |
|
||||
// | | ======> | intersect | ======> early return of nothing
|
||||
// | | | |
|
||||
// | | | |
|
||||
// | | | |
|
||||
// |-----------other----------| |--------------------------|
|
||||
//
|
||||
// 4 Sides:
|
||||
// |-----------this-----------| |-----------this-----------| |--------------------------|
|
||||
// | | | | |TTTTTTTTTTTTTTTTTTTTTTTTTT|
|
||||
// | | | | |TTTTTTTTTTTTTTTTTTTTTTTTTT|
|
||||
// | |---------| | | |---------| | |LLLLLLLL|---------|RRRRRRR|
|
||||
// | |other | | ======> | |intersect| | ======> |LLLLLLLL| |RRRRRRR|
|
||||
// | |---------| | | |---------| | |LLLLLLLL|---------|RRRRRRR|
|
||||
// | | | | |BBBBBBBBBBBBBBBBBBBBBBBBBB|
|
||||
// | | | | |BBBBBBBBBBBBBBBBBBBBBBBBBB|
|
||||
// |--------------------------| |--------------------------| |--------------------------|
|
||||
//
|
||||
// 3 Sides:
|
||||
// |-----------this-----------| |-----------this-----------| |--------------------------|
|
||||
// | | | | |TTTTTTTTTTTTTTTTTTTTTTTTTT|
|
||||
// | | | | |TTTTTTTTTTTTTTTTTTTTTTTTTT|
|
||||
// | |--------------------| | |-----------------| |LLLLLLLL|-----------------|
|
||||
// | |other | ======> | |intersect | ======> |LLLLLLLL| |
|
||||
// | |--------------------| | |-----------------| |LLLLLLLL|-----------------|
|
||||
// | | | | |BBBBBBBBBBBBBBBBBBBBBBBBBB|
|
||||
// | | | | |BBBBBBBBBBBBBBBBBBBBBBBBBB|
|
||||
// |--------------------------| |--------------------------| |--------------------------|
|
||||
//
|
||||
// 2 Sides:
|
||||
// |-----------this-----------| |-----------this-----------| |--------------------------|
|
||||
// | | | | |TTTTTTTTTTTTTTTTTTTTTTTTTT|
|
||||
// | | | | |TTTTTTTTTTTTTTTTTTTTTTTTTT|
|
||||
// | |--------------------| | |-----------------| |LLLLLLLL|-----------------|
|
||||
// | |other | ======> | |intersect | ======> |LLLLLLLL| |
|
||||
// | | | | | | |LLLLLLLL| |
|
||||
// | | | | | | |LLLLLLLL| |
|
||||
// | | | | | | |LLLLLLLL| |
|
||||
// |--------| | |--------------------------| |--------------------------|
|
||||
// | |
|
||||
// |--------------------|
|
||||
//
|
||||
// 1 Side:
|
||||
// |-----------this-----------| |-----------this-----------| |--------------------------|
|
||||
// | | | | |TTTTTTTTTTTTTTTTTTTTTTTTTT|
|
||||
// | | | | |TTTTTTTTTTTTTTTTTTTTTTTTTT|
|
||||
// |-----------------------------| |--------------------------| |--------------------------|
|
||||
// | other | ======> | intersect | ======> | |
|
||||
// | | | | | |
|
||||
// | | | | | |
|
||||
// | | | | | |
|
||||
// | | |--------------------------| |--------------------------|
|
||||
// | |
|
||||
// |-----------------------------|
|
||||
//
|
||||
// 0 Sides:
|
||||
// |-----------this-----------| |-----------this-----------|
|
||||
// | | | |
|
||||
// | | | |
|
||||
// | | | |
|
||||
// | | ======> | | ======> early return of this
|
||||
// | | | |
|
||||
// | | | |
|
||||
// | | | |
|
||||
// |--------------------------| |--------------------------|
|
||||
//
|
||||
//
|
||||
// |---------------|
|
||||
// | other |
|
||||
// |---------------|
|
||||
|
||||
// We generate these rectangles by the original and intersect points, but some of them might be empty when the intersect
|
||||
// lines up with the edge of the original. That's OK. That just means that the subtraction didn't leave anything behind.
|
||||
// We will filter those out below when adding them to the result.
|
||||
const til::rectangle t{ left(), top(), right(), intersect.top() };
|
||||
const til::rectangle b{ left(), intersect.bottom(), right(), bottom() };
|
||||
const til::rectangle l{ left(), intersect.top(), intersect.left(), intersect.bottom() };
|
||||
const til::rectangle r{ intersect.right(), intersect.top(), right(), intersect.bottom() };
|
||||
|
||||
if (!t.empty())
|
||||
{
|
||||
result.push_back(t);
|
||||
}
|
||||
|
||||
if (!b.empty())
|
||||
{
|
||||
result.push_back(b);
|
||||
}
|
||||
|
||||
if (!l.empty())
|
||||
{
|
||||
result.push_back(l);
|
||||
}
|
||||
|
||||
if (!r.empty())
|
||||
{
|
||||
result.push_back(r);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
constexpr ptrdiff_t top() const noexcept
|
||||
{
|
||||
return _topLeft.y();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T top() const
|
||||
{
|
||||
T ret;
|
||||
THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(top()).AssignIfValid(&ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
constexpr ptrdiff_t bottom() const noexcept
|
||||
{
|
||||
return _bottomRight.y();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T bottom() const
|
||||
{
|
||||
T ret;
|
||||
THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(bottom()).AssignIfValid(&ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
constexpr ptrdiff_t left() const noexcept
|
||||
{
|
||||
return _topLeft.x();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T left() const
|
||||
{
|
||||
T ret;
|
||||
THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(left()).AssignIfValid(&ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
constexpr ptrdiff_t right() const noexcept
|
||||
{
|
||||
return _bottomRight.x();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T right() const
|
||||
{
|
||||
T ret;
|
||||
THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(right()).AssignIfValid(&ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
ptrdiff_t width() const
|
||||
{
|
||||
ptrdiff_t ret;
|
||||
THROW_HR_IF(E_ABORT, !::base::CheckSub(right(), left()).AssignIfValid(&ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T width() const
|
||||
{
|
||||
T ret;
|
||||
THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(width()).AssignIfValid(&ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
ptrdiff_t height() const
|
||||
{
|
||||
ptrdiff_t ret;
|
||||
THROW_HR_IF(E_ABORT, !::base::CheckSub(bottom(), top()).AssignIfValid(&ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T height() const
|
||||
{
|
||||
T ret;
|
||||
THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(height()).AssignIfValid(&ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
constexpr point origin() const noexcept
|
||||
{
|
||||
return _topLeft;
|
||||
}
|
||||
|
||||
size size() const
|
||||
{
|
||||
return til::size{ width(), height() };
|
||||
}
|
||||
|
||||
constexpr bool empty() const noexcept
|
||||
{
|
||||
return !operator bool();
|
||||
}
|
||||
|
||||
#ifdef _WINCONTYPES_
|
||||
// NOTE: This will convert back to INCLUSIVE on the way out because
|
||||
// that is generally how SMALL_RECTs are handled in console code and via the APIs.
|
||||
operator SMALL_RECT() const
|
||||
{
|
||||
SMALL_RECT ret;
|
||||
THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(left()).AssignIfValid(&ret.Left));
|
||||
THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(top()).AssignIfValid(&ret.Top));
|
||||
THROW_HR_IF(E_ABORT, !base::CheckSub(right(), 1).AssignIfValid(&ret.Right));
|
||||
THROW_HR_IF(E_ABORT, !base::CheckSub(bottom(), 1).AssignIfValid(&ret.Bottom));
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _WINDEF_
|
||||
operator RECT() const
|
||||
{
|
||||
RECT ret;
|
||||
THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(left()).AssignIfValid(&ret.left));
|
||||
THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(top()).AssignIfValid(&ret.top));
|
||||
THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(right()).AssignIfValid(&ret.right));
|
||||
THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(bottom()).AssignIfValid(&ret.bottom));
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef DCOMMON_H_INCLUDED
|
||||
constexpr operator D2D1_RECT_F() const noexcept
|
||||
{
|
||||
return D2D1_RECT_F{ gsl::narrow_cast<FLOAT>(left()), gsl::narrow_cast<FLOAT>(top()), gsl::narrow_cast<FLOAT>(right()), gsl::narrow_cast<FLOAT>(bottom()) };
|
||||
}
|
||||
#endif
|
||||
|
||||
protected:
|
||||
til::point _topLeft;
|
||||
til::point _bottomRight;
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
friend class ::RectangleTests;
|
||||
#endif
|
||||
};
|
||||
}
|
||||
|
||||
#ifdef __WEX_COMMON_H__
|
||||
namespace WEX::TestExecution
|
||||
{
|
||||
template<>
|
||||
class VerifyOutputTraits<::til::rectangle>
|
||||
{
|
||||
public:
|
||||
static WEX::Common::NoThrowString ToString(const ::til::rectangle& rect)
|
||||
{
|
||||
return WEX::Common::NoThrowString().Format(L"(L:%td, T:%td, R:%td, B:%td) [W:%td, H:%td]", rect.left(), rect.top(), rect.right(), rect.bottom(), rect.width(), rect.height());
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
class VerifyCompareTraits<::til::rectangle, ::til::rectangle>
|
||||
{
|
||||
public:
|
||||
static bool AreEqual(const ::til::rectangle& expected, const ::til::rectangle& actual) noexcept
|
||||
{
|
||||
return expected == actual;
|
||||
}
|
||||
|
||||
static bool AreSame(const ::til::rectangle& expected, const ::til::rectangle& actual) noexcept
|
||||
{
|
||||
return &expected == &actual;
|
||||
}
|
||||
|
||||
static bool IsLessThan(const ::til::rectangle& expectedLess, const ::til::rectangle& expectedGreater) = delete;
|
||||
|
||||
static bool IsGreaterThan(const ::til::rectangle& expectedGreater, const ::til::rectangle& expectedLess) = delete;
|
||||
|
||||
static bool IsNull(const ::til::rectangle& object) noexcept
|
||||
{
|
||||
return object == til::rectangle{};
|
||||
}
|
||||
};
|
||||
|
||||
};
|
||||
#endif
|
||||
@@ -1,119 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "point.h"
|
||||
#include "size.h"
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
class RecteratorTests;
|
||||
#endif
|
||||
|
||||
namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
||||
{
|
||||
class recterator
|
||||
{
|
||||
public:
|
||||
recterator(point topLeft, size size) :
|
||||
_topLeft(topLeft),
|
||||
_size(size),
|
||||
_current(topLeft)
|
||||
{
|
||||
}
|
||||
|
||||
recterator(point topLeft, size size, point start) :
|
||||
_topLeft(topLeft),
|
||||
_size(size),
|
||||
_current(start)
|
||||
{
|
||||
}
|
||||
|
||||
recterator& operator++()
|
||||
{
|
||||
if (_current.x() + 1 >= _topLeft.x() + _size.width())
|
||||
{
|
||||
_current = { _topLeft.x(), _current.y() + 1 };
|
||||
}
|
||||
else
|
||||
{
|
||||
_current = { _current.x() + 1, _current.y() };
|
||||
}
|
||||
|
||||
return (*this);
|
||||
}
|
||||
|
||||
bool operator==(const recterator& other) const
|
||||
{
|
||||
return _current == other._current &&
|
||||
_topLeft == other._topLeft &&
|
||||
_size == other._size;
|
||||
}
|
||||
|
||||
bool operator!=(const recterator& other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
bool operator<(const recterator& other) const
|
||||
{
|
||||
return _current < other._current;
|
||||
}
|
||||
|
||||
bool operator>(const recterator& other) const
|
||||
{
|
||||
return _current > other._current;
|
||||
}
|
||||
|
||||
point operator*() const
|
||||
{
|
||||
return _current;
|
||||
}
|
||||
|
||||
protected:
|
||||
point _current;
|
||||
const point _topLeft;
|
||||
const size _size;
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
friend class ::RecteratorTests;
|
||||
#endif
|
||||
};
|
||||
};
|
||||
|
||||
#ifdef __WEX_COMMON_H__
|
||||
namespace WEX::TestExecution
|
||||
{
|
||||
template<>
|
||||
class VerifyOutputTraits<::til::recterator>
|
||||
{
|
||||
public:
|
||||
static WEX::Common::NoThrowString ToString(const ::til::recterator& /*rect*/)
|
||||
{
|
||||
return WEX::Common::NoThrowString().Format(L"Yep that's a recterator.");
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
class VerifyCompareTraits<::til::recterator, ::til::recterator>
|
||||
{
|
||||
public:
|
||||
static bool AreEqual(const ::til::recterator& expected, const ::til::recterator& actual) noexcept
|
||||
{
|
||||
return expected == actual;
|
||||
}
|
||||
|
||||
static bool AreSame(const ::til::recterator& expected, const ::til::recterator& actual) noexcept
|
||||
{
|
||||
return &expected == &actual;
|
||||
}
|
||||
|
||||
static bool IsLessThan(const ::til::recterator& expectedLess, const ::til::recterator& expectedGreater) = delete;
|
||||
|
||||
static bool IsGreaterThan(const ::til::recterator& expectedGreater, const ::til::recterator& expectedLess) = delete;
|
||||
|
||||
static bool IsNull(const ::til::recterator& object) noexcept = delete;
|
||||
};
|
||||
|
||||
};
|
||||
#endif
|
||||
@@ -1,270 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
class SizeTests;
|
||||
#endif
|
||||
|
||||
namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
||||
{
|
||||
class size
|
||||
{
|
||||
public:
|
||||
constexpr size() noexcept :
|
||||
size(0, 0)
|
||||
{
|
||||
}
|
||||
|
||||
// On 64-bit processors, int and ptrdiff_t are different fundamental types.
|
||||
// On 32-bit processors, they're the same which makes this a double-definition
|
||||
// with the `ptrdiff_t` one below.
|
||||
#if defined(_M_AMD64) || defined(_M_ARM64)
|
||||
constexpr size(int width, int height) noexcept :
|
||||
size(static_cast<ptrdiff_t>(width), static_cast<ptrdiff_t>(height))
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
size(size_t width, size_t height)
|
||||
{
|
||||
THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(width).AssignIfValid(&_width));
|
||||
THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(height).AssignIfValid(&_height));
|
||||
}
|
||||
|
||||
constexpr size(ptrdiff_t width, ptrdiff_t height) noexcept :
|
||||
_width(width),
|
||||
_height(height)
|
||||
{
|
||||
}
|
||||
|
||||
// This template will convert to size from anything that has an X and a Y field that appear convertible to an integer value
|
||||
template<typename TOther>
|
||||
constexpr size(const TOther& other, std::enable_if_t<std::is_integral_v<decltype(std::declval<TOther>().X)> && std::is_integral_v<decltype(std::declval<TOther>().Y)>, int> /*sentinel*/ = 0) :
|
||||
size(static_cast<ptrdiff_t>(other.X), static_cast<ptrdiff_t>(other.Y))
|
||||
{
|
||||
}
|
||||
|
||||
// This template will convert to size from anything that has a cx and a cy field that appear convertible to an integer value
|
||||
template<typename TOther>
|
||||
constexpr size(const TOther& other, std::enable_if_t<std::is_integral_v<decltype(std::declval<TOther>().cx)> && std::is_integral_v<decltype(std::declval<TOther>().cy)>, int> /*sentinel*/ = 0) :
|
||||
size(static_cast<ptrdiff_t>(other.cx), static_cast<ptrdiff_t>(other.cy))
|
||||
{
|
||||
}
|
||||
|
||||
constexpr bool operator==(const size& other) const noexcept
|
||||
{
|
||||
return _width == other._width &&
|
||||
_height == other._height;
|
||||
}
|
||||
|
||||
constexpr bool operator!=(const size& other) const noexcept
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
constexpr operator bool() const noexcept
|
||||
{
|
||||
return _width != 0 || _height != 0;
|
||||
}
|
||||
|
||||
size operator+(const size& other) const
|
||||
{
|
||||
ptrdiff_t width;
|
||||
THROW_HR_IF(E_ABORT, !base::CheckAdd(_width, other._width).AssignIfValid(&width));
|
||||
|
||||
ptrdiff_t height;
|
||||
THROW_HR_IF(E_ABORT, !base::CheckAdd(_height, other._height).AssignIfValid(&height));
|
||||
|
||||
return size{ width, height };
|
||||
}
|
||||
|
||||
size operator-(const size& other) const
|
||||
{
|
||||
ptrdiff_t width;
|
||||
THROW_HR_IF(E_ABORT, !base::CheckSub(_width, other._width).AssignIfValid(&width));
|
||||
|
||||
ptrdiff_t height;
|
||||
THROW_HR_IF(E_ABORT, !base::CheckSub(_height, other._height).AssignIfValid(&height));
|
||||
|
||||
return size{ width, height };
|
||||
}
|
||||
|
||||
size operator*(const size& other) const
|
||||
{
|
||||
ptrdiff_t width;
|
||||
THROW_HR_IF(E_ABORT, !base::CheckMul(_width, other._width).AssignIfValid(&width));
|
||||
|
||||
ptrdiff_t height;
|
||||
THROW_HR_IF(E_ABORT, !base::CheckMul(_height, other._height).AssignIfValid(&height));
|
||||
|
||||
return size{ width, height };
|
||||
}
|
||||
|
||||
size operator/(const size& other) const
|
||||
{
|
||||
ptrdiff_t width;
|
||||
THROW_HR_IF(E_ABORT, !base::CheckDiv(_width, other._width).AssignIfValid(&width));
|
||||
|
||||
ptrdiff_t height;
|
||||
THROW_HR_IF(E_ABORT, !base::CheckDiv(_height, other._height).AssignIfValid(&height));
|
||||
|
||||
return size{ width, height };
|
||||
}
|
||||
|
||||
size divide_ceil(const size& other) const
|
||||
{
|
||||
// Divide normally to get the floor.
|
||||
const size floor = *this / other;
|
||||
|
||||
ptrdiff_t adjWidth = 0;
|
||||
ptrdiff_t adjHeight = 0;
|
||||
|
||||
// Check for width remainder, anything not 0.
|
||||
// If we multiply the floored number with the other, it will equal
|
||||
// the old width if there was no remainder.
|
||||
if (other._width * floor._width != _width)
|
||||
{
|
||||
// If there was any remainder,
|
||||
// Grow the magnitude by 1 in the
|
||||
// direction of the sign.
|
||||
if (floor.width() >= 0)
|
||||
{
|
||||
++adjWidth;
|
||||
}
|
||||
else
|
||||
{
|
||||
--adjWidth;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for height remainder, anything not 0.
|
||||
// If we multiply the floored number with the other, it will equal
|
||||
// the old width if there was no remainder.
|
||||
if (other._height * floor._height != _height)
|
||||
{
|
||||
// If there was any remainder,
|
||||
// Grow the magnitude by 1 in the
|
||||
// direction of the sign.
|
||||
if (_height >= 0)
|
||||
{
|
||||
++adjHeight;
|
||||
}
|
||||
else
|
||||
{
|
||||
--adjHeight;
|
||||
}
|
||||
}
|
||||
|
||||
return floor + size{ adjWidth, adjHeight };
|
||||
}
|
||||
|
||||
constexpr ptrdiff_t width() const noexcept
|
||||
{
|
||||
return _width;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T width() const
|
||||
{
|
||||
T ret;
|
||||
THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(width()).AssignIfValid(&ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
constexpr ptrdiff_t height() const noexcept
|
||||
{
|
||||
return _height;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T height() const
|
||||
{
|
||||
T ret;
|
||||
THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(height()).AssignIfValid(&ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
ptrdiff_t area() const
|
||||
{
|
||||
ptrdiff_t result;
|
||||
THROW_HR_IF(E_ABORT, !base::CheckMul(_width, _height).AssignIfValid(&result));
|
||||
return result;
|
||||
}
|
||||
|
||||
#ifdef _WINCONTYPES_
|
||||
operator COORD() const
|
||||
{
|
||||
COORD ret;
|
||||
THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(_width).AssignIfValid(&ret.X));
|
||||
THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(_height).AssignIfValid(&ret.Y));
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _WINDEF_
|
||||
operator SIZE() const
|
||||
{
|
||||
SIZE ret;
|
||||
THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(_width).AssignIfValid(&ret.cx));
|
||||
THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(_height).AssignIfValid(&ret.cy));
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef DCOMMON_H_INCLUDED
|
||||
constexpr operator D2D1_SIZE_F() const noexcept
|
||||
{
|
||||
return D2D1_SIZE_F{ gsl::narrow_cast<float>(_width), gsl::narrow_cast<float>(_height) };
|
||||
}
|
||||
#endif
|
||||
|
||||
protected:
|
||||
ptrdiff_t _width;
|
||||
ptrdiff_t _height;
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
friend class ::SizeTests;
|
||||
#endif
|
||||
};
|
||||
};
|
||||
|
||||
#ifdef __WEX_COMMON_H__
|
||||
namespace WEX::TestExecution
|
||||
{
|
||||
template<>
|
||||
class VerifyOutputTraits<::til::size>
|
||||
{
|
||||
public:
|
||||
static WEX::Common::NoThrowString ToString(const ::til::size& size)
|
||||
{
|
||||
return WEX::Common::NoThrowString().Format(L"[W:%td, H:%td]", size.width(), size.height());
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
class VerifyCompareTraits<::til::size, ::til::size>
|
||||
{
|
||||
public:
|
||||
static bool AreEqual(const ::til::size& expected, const ::til::size& actual) noexcept
|
||||
{
|
||||
return expected == actual;
|
||||
}
|
||||
|
||||
static bool AreSame(const ::til::size& expected, const ::til::size& actual) noexcept
|
||||
{
|
||||
return &expected == &actual;
|
||||
}
|
||||
|
||||
static bool IsLessThan(const ::til::size& expectedLess, const ::til::size& expectedGreater) = delete;
|
||||
|
||||
static bool IsGreaterThan(const ::til::size& expectedGreater, const ::til::size& expectedLess) = delete;
|
||||
|
||||
static bool IsNull(const ::til::size& object) noexcept
|
||||
{
|
||||
return object == til::size{};
|
||||
}
|
||||
};
|
||||
};
|
||||
#endif
|
||||
@@ -50,16 +50,6 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
||||
_used = init.size();
|
||||
}
|
||||
|
||||
constexpr bool operator==(const til::some<T, N>& other) const noexcept
|
||||
{
|
||||
return std::equal(cbegin(), cend(), other.cbegin(), other.cend());
|
||||
}
|
||||
|
||||
constexpr bool operator!=(const til::some<T, N>& other) const noexcept
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
void fill(const T& _Value)
|
||||
{
|
||||
_array.fill(_Value);
|
||||
@@ -192,51 +182,3 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#ifdef __WEX_COMMON_H__
|
||||
namespace WEX::TestExecution
|
||||
{
|
||||
template<class T, size_t N>
|
||||
class VerifyOutputTraits<::til::some<T, N>>
|
||||
{
|
||||
public:
|
||||
static WEX::Common::NoThrowString ToString(const ::til::some<T, N>& some)
|
||||
{
|
||||
auto str = WEX::Common::NoThrowString().Format(L"\r\nSome contains %d of max size %d:\r\nElements:\r\n", some.size(), some.max_size());
|
||||
|
||||
for (auto& item : some)
|
||||
{
|
||||
const auto itemStr = WEX::TestExecution::VerifyOutputTraits<T>::ToString(item);
|
||||
str.AppendFormat(L"\t- %ws\r\n", (const wchar_t*)itemStr);
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
};
|
||||
|
||||
template<class T, size_t N>
|
||||
class VerifyCompareTraits<::til::some<T, N>, ::til::some<T, N>>
|
||||
{
|
||||
public:
|
||||
static bool AreEqual(const ::til::some<T, N>& expected, const ::til::some<T, N>& actual) noexcept
|
||||
{
|
||||
return expected == actual;
|
||||
}
|
||||
|
||||
static bool AreSame(const ::til::some<T, N>& expected, const ::til::some<T, N>& actual) noexcept
|
||||
{
|
||||
return &expected == &actual;
|
||||
}
|
||||
|
||||
static bool IsLessThan(const ::til::some<T, N>& expectedLess, const ::til::some<T, N>& expectedGreater) = delete;
|
||||
|
||||
static bool IsGreaterThan(const ::til::some<T, N>& expectedGreater, const ::til::some<T, N>& expectedLess) = delete;
|
||||
|
||||
static bool IsNull(const ::til::some<T, N>& object) noexcept
|
||||
{
|
||||
return object == til::some<T, N>{};
|
||||
}
|
||||
};
|
||||
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -232,7 +232,7 @@ BgfxEngine::BgfxEngine(PVOID SharedViewBase, LONG DisplayHeight, LONG DisplayWid
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
std::vector<til::rectangle> BgfxEngine::GetDirtyArea()
|
||||
SMALL_RECT BgfxEngine::GetDirtyRectInChars()
|
||||
{
|
||||
SMALL_RECT r;
|
||||
r.Bottom = _displayHeight > 0 ? (SHORT)(_displayHeight - 1) : 0;
|
||||
@@ -240,7 +240,7 @@ std::vector<til::rectangle> BgfxEngine::GetDirtyArea()
|
||||
r.Left = 0;
|
||||
r.Right = _displayWidth > 0 ? (SHORT)(_displayWidth - 1) : 0;
|
||||
|
||||
return { r };
|
||||
return r;
|
||||
}
|
||||
|
||||
[[nodiscard]] HRESULT BgfxEngine::GetFontSize(_Out_ COORD* const pFontSize) noexcept
|
||||
|
||||
@@ -69,7 +69,7 @@ namespace Microsoft::Console::Render
|
||||
|
||||
[[nodiscard]] HRESULT GetProposedFont(const FontInfoDesired& fiFontInfoDesired, FontInfo& fiFontInfo, int const iDpi) noexcept override;
|
||||
|
||||
std::vector<til::rectangle> GetDirtyArea() override;
|
||||
SMALL_RECT GetDirtyRectInChars() override;
|
||||
[[nodiscard]] HRESULT GetFontSize(_Out_ COORD* const pFontSize) noexcept override;
|
||||
[[nodiscard]] HRESULT IsGlyphWideByFont(const std::wstring_view glyph, _Out_ bool* const pResult) noexcept override;
|
||||
|
||||
|
||||
@@ -123,7 +123,7 @@ bool HandleTerminalMouseEvent(const COORD cMousePosition,
|
||||
// Virtual terminal input mode
|
||||
if (IsInVirtualTerminalInputMode())
|
||||
{
|
||||
fWasHandled = gci.GetActiveInputBuffer()->GetTerminalInput().HandleMouse(cMousePosition, uiButton, sModifierKeystate, sWheelDelta);
|
||||
fWasHandled = gci.terminalMouseInput.HandleMouse(cMousePosition, uiButton, sModifierKeystate, sWheelDelta);
|
||||
}
|
||||
|
||||
return fWasHandled;
|
||||
|
||||
@@ -563,71 +563,58 @@ void Renderer::_PaintBufferOutput(_In_ IRenderEngine* const pEngine)
|
||||
|
||||
// This is effectively the number of cells on the visible screen that need to be redrawn.
|
||||
// The origin is always 0, 0 because it represents the screen itself, not the underlying buffer.
|
||||
const auto dirtyAreas = pEngine->GetDirtyArea();
|
||||
auto dirty = Viewport::FromInclusive(pEngine->GetDirtyRectInChars());
|
||||
|
||||
for (const auto dirtyRect : dirtyAreas)
|
||||
// Shift the origin of the dirty region to match the underlying buffer so we can
|
||||
// compare the two regions directly for intersection.
|
||||
dirty = Viewport::Offset(dirty, view.Origin());
|
||||
|
||||
// The intersection between what is dirty on the screen (in need of repaint)
|
||||
// and what is supposed to be visible on the screen (the viewport) is what
|
||||
// we need to walk through line-by-line and repaint onto the screen.
|
||||
const auto redraw = Viewport::Intersect(dirty, view);
|
||||
|
||||
// Shortcut: don't bother redrawing if the width is 0.
|
||||
if (redraw.Width() > 0)
|
||||
{
|
||||
auto dirty = Viewport::FromInclusive(dirtyRect);
|
||||
// Retrieve the text buffer so we can read information out of it.
|
||||
const auto& buffer = _pData->GetTextBuffer();
|
||||
|
||||
// Shift the origin of the dirty region to match the underlying buffer so we can
|
||||
// compare the two regions directly for intersection.
|
||||
dirty = Viewport::Offset(dirty, view.Origin());
|
||||
|
||||
// The intersection between what is dirty on the screen (in need of repaint)
|
||||
// and what is supposed to be visible on the screen (the viewport) is what
|
||||
// we need to walk through line-by-line and repaint onto the screen.
|
||||
const auto redraw = Viewport::Intersect(dirty, view);
|
||||
|
||||
// Shortcut: don't bother redrawing if the width is 0.
|
||||
if (redraw.Width() > 0)
|
||||
// Now walk through each row of text that we need to redraw.
|
||||
for (auto row = redraw.Top(); row < redraw.BottomExclusive(); row++)
|
||||
{
|
||||
// Retrieve the text buffer so we can read information out of it.
|
||||
const auto& buffer = _pData->GetTextBuffer();
|
||||
// Calculate the boundaries of a single line. This is from the left to right edge of the dirty
|
||||
// area in width and exactly 1 tall.
|
||||
const auto bufferLine = Viewport::FromDimensions({ redraw.Left(), row }, { redraw.Width(), 1 });
|
||||
|
||||
// Now walk through each row of text that we need to redraw.
|
||||
for (auto row = redraw.Top(); row < redraw.BottomExclusive(); row++)
|
||||
{
|
||||
// Calculate the boundaries of a single line. This is from the left to right edge of the dirty
|
||||
// area in width and exactly 1 tall.
|
||||
const auto bufferLine = Viewport::FromDimensions({ redraw.Left(), row }, { redraw.Width(), 1 });
|
||||
// Find where on the screen we should place this line information. This requires us to re-map
|
||||
// the buffer-based origin of the line back onto the screen-based origin of the line
|
||||
// For example, the screen might say we need to paint 1,1 because it is dirty but the viewport is actually looking
|
||||
// at 13,26 relative to the buffer.
|
||||
// This means that we need 14,27 out of the backing buffer to fill in the 1,1 cell of the screen.
|
||||
const auto screenLine = Viewport::Offset(bufferLine, -view.Origin());
|
||||
|
||||
// Find where on the screen we should place this line information. This requires us to re-map
|
||||
// the buffer-based origin of the line back onto the screen-based origin of the line
|
||||
// For example, the screen might say we need to paint 1,1 because it is dirty but the viewport is actually looking
|
||||
// at 13,26 relative to the buffer.
|
||||
// This means that we need 14,27 out of the backing buffer to fill in the 1,1 cell of the screen.
|
||||
const auto screenLine = Viewport::Offset(bufferLine, -view.Origin());
|
||||
// Retrieve the cell information iterator limited to just this line we want to redraw.
|
||||
auto it = buffer.GetCellDataAt(bufferLine.Origin(), bufferLine);
|
||||
|
||||
// Retrieve the cell information iterator limited to just this line we want to redraw.
|
||||
auto it = buffer.GetCellDataAt(bufferLine.Origin(), bufferLine);
|
||||
// Calculate if two things are true:
|
||||
// 1. this row wrapped
|
||||
// 2. We're painting the last col of the row.
|
||||
// In that case, set lineWrapped=true for the _PaintBufferOutputHelper call.
|
||||
const auto lineWrapped = (buffer.GetRowByOffset(bufferLine.Origin().Y).GetCharRow().WasWrapForced()) &&
|
||||
(bufferLine.RightExclusive() == buffer.GetSize().Width());
|
||||
|
||||
// Calculate if two things are true:
|
||||
// 1. this row wrapped
|
||||
// 2. We're painting the last col of the row.
|
||||
// In that case, set lineWrapped=true for the _PaintBufferOutputHelper call.
|
||||
const auto lineWrapped = (buffer.GetRowByOffset(bufferLine.Origin().Y).GetCharRow().WasWrapForced()) &&
|
||||
(bufferLine.RightExclusive() == buffer.GetSize().Width());
|
||||
|
||||
// Ask the helper to paint through this specific line.
|
||||
_PaintBufferOutputHelper(pEngine, it, screenLine.Origin(), lineWrapped);
|
||||
}
|
||||
// Ask the helper to paint through this specific line.
|
||||
_PaintBufferOutputHelper(pEngine, it, screenLine.Origin(), lineWrapped);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool _IsAllSpaces(const std::wstring_view v)
|
||||
{
|
||||
// first non-space char is not found (is npos)
|
||||
return v.find_first_not_of(L" ") == decltype(v)::npos;
|
||||
}
|
||||
|
||||
void Renderer::_PaintBufferOutputHelper(_In_ IRenderEngine* const pEngine,
|
||||
TextBufferCellIterator it,
|
||||
const COORD target,
|
||||
const bool lineWrapped)
|
||||
{
|
||||
auto globalInvert{ _pData->IsScreenReversed() };
|
||||
|
||||
// If we have valid data, let's figure out how to draw it.
|
||||
if (it)
|
||||
{
|
||||
@@ -674,14 +661,8 @@ void Renderer::_PaintBufferOutputHelper(_In_ IRenderEngine* const pEngine,
|
||||
{
|
||||
if (color != it->TextAttr())
|
||||
{
|
||||
auto newAttr{ it->TextAttr() };
|
||||
// foreground doesn't matter for runs of spaces (!)
|
||||
// if we trick it . . . we call Paint far fewer times for cmatrix
|
||||
if (!_IsAllSpaces(it->Chars()) || !newAttr.HasIdenticalVisualRepresentationForBlankSpace(color, globalInvert))
|
||||
{
|
||||
color = newAttr;
|
||||
break; // vend this run
|
||||
}
|
||||
color = it->TextAttr();
|
||||
break;
|
||||
}
|
||||
|
||||
// Walk through the text data and turn it into rendering clusters.
|
||||
@@ -803,35 +784,26 @@ void Renderer::_PaintCursor(_In_ IRenderEngine* const pEngine)
|
||||
{
|
||||
// Get cursor position in buffer
|
||||
COORD coordCursor = _pData->GetCursorPosition();
|
||||
|
||||
// GH#3166: Only draw the cursor if it's actually in the viewport. It
|
||||
// might be on the line that's in that partially visible row at the
|
||||
// bottom of the viewport, the space that's not quite a full line in
|
||||
// height. Since we don't draw that text, we shouldn't draw the cursor
|
||||
// there either.
|
||||
// Adjust cursor to viewport
|
||||
Viewport view = _pData->GetViewport();
|
||||
if (view.IsInBounds(coordCursor))
|
||||
{
|
||||
// Adjust cursor to viewport
|
||||
view.ConvertToOrigin(&coordCursor);
|
||||
view.ConvertToOrigin(&coordCursor);
|
||||
|
||||
COLORREF cursorColor = _pData->GetCursorColor();
|
||||
bool useColor = cursorColor != INVALID_COLOR;
|
||||
COLORREF cursorColor = _pData->GetCursorColor();
|
||||
bool useColor = cursorColor != INVALID_COLOR;
|
||||
|
||||
// Build up the cursor parameters including position, color, and drawing options
|
||||
IRenderEngine::CursorOptions options;
|
||||
options.coordCursor = coordCursor;
|
||||
options.ulCursorHeightPercent = _pData->GetCursorHeight();
|
||||
options.cursorPixelWidth = _pData->GetCursorPixelWidth();
|
||||
options.fIsDoubleWidth = _pData->IsCursorDoubleWidth();
|
||||
options.cursorType = _pData->GetCursorStyle();
|
||||
options.fUseColor = useColor;
|
||||
options.cursorColor = cursorColor;
|
||||
options.isOn = _pData->IsCursorOn();
|
||||
// Build up the cursor parameters including position, color, and drawing options
|
||||
IRenderEngine::CursorOptions options;
|
||||
options.coordCursor = coordCursor;
|
||||
options.ulCursorHeightPercent = _pData->GetCursorHeight();
|
||||
options.cursorPixelWidth = _pData->GetCursorPixelWidth();
|
||||
options.fIsDoubleWidth = _pData->IsCursorDoubleWidth();
|
||||
options.cursorType = _pData->GetCursorStyle();
|
||||
options.fUseColor = useColor;
|
||||
options.cursorColor = cursorColor;
|
||||
options.isOn = _pData->IsCursorOn();
|
||||
|
||||
// Draw it within the viewport
|
||||
LOG_IF_FAILED(pEngine->PaintCursor(options));
|
||||
}
|
||||
// Draw it within the viewport
|
||||
LOG_IF_FAILED(pEngine->PaintCursor(options));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -862,27 +834,24 @@ void Renderer::_PaintOverlay(IRenderEngine& engine,
|
||||
// Set it up in a Viewport helper structure and trim it the IME viewport to be within the full console viewport.
|
||||
Viewport viewConv = Viewport::FromInclusive(srCaView);
|
||||
|
||||
for (auto rect : engine.GetDirtyArea())
|
||||
SMALL_RECT srDirty = engine.GetDirtyRectInChars();
|
||||
|
||||
// Dirty is an inclusive rectangle, but oddly enough the IME was an exclusive one, so correct it.
|
||||
srDirty.Bottom++;
|
||||
srDirty.Right++;
|
||||
|
||||
if (viewConv.TrimToViewport(&srDirty))
|
||||
{
|
||||
SMALL_RECT srDirty = rect;
|
||||
Viewport viewDirty = Viewport::FromInclusive(srDirty);
|
||||
|
||||
// Dirty is an inclusive rectangle, but oddly enough the IME was an exclusive one, so correct it.
|
||||
srDirty.Bottom++;
|
||||
srDirty.Right++;
|
||||
|
||||
if (viewConv.TrimToViewport(&srDirty))
|
||||
for (SHORT iRow = viewDirty.Top(); iRow < viewDirty.BottomInclusive(); iRow++)
|
||||
{
|
||||
Viewport viewDirty = Viewport::FromInclusive(srDirty);
|
||||
const COORD target{ viewDirty.Left(), iRow };
|
||||
const auto source = target - overlay.origin;
|
||||
|
||||
for (SHORT iRow = viewDirty.Top(); iRow < viewDirty.BottomInclusive(); iRow++)
|
||||
{
|
||||
const COORD target{ viewDirty.Left(), iRow };
|
||||
const auto source = target - overlay.origin;
|
||||
auto it = overlay.buffer.GetCellLineDataAt(source);
|
||||
|
||||
auto it = overlay.buffer.GetCellLineDataAt(source);
|
||||
|
||||
_PaintBufferOutputHelper(&engine, it, target, false);
|
||||
}
|
||||
_PaintBufferOutputHelper(&engine, it, target, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -921,19 +890,16 @@ void Renderer::_PaintSelection(_In_ IRenderEngine* const pEngine)
|
||||
{
|
||||
try
|
||||
{
|
||||
auto dirtyAreas = pEngine->GetDirtyArea();
|
||||
SMALL_RECT srDirty = pEngine->GetDirtyRectInChars();
|
||||
Viewport dirtyView = Viewport::FromInclusive(srDirty);
|
||||
|
||||
// Get selection rectangles
|
||||
const auto rectangles = _GetSelectionRects();
|
||||
for (auto rect : rectangles)
|
||||
{
|
||||
for (auto dirtyRect : dirtyAreas)
|
||||
if (dirtyView.TrimToViewport(&rect))
|
||||
{
|
||||
Viewport dirtyView = Viewport::FromInclusive(dirtyRect);
|
||||
if (dirtyView.TrimToViewport(&rect))
|
||||
{
|
||||
LOG_IF_FAILED(pEngine->PaintSelection(rect));
|
||||
}
|
||||
LOG_IF_FAILED(pEngine->PaintSelection(rect));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -639,12 +639,8 @@ try
|
||||
_glyphAdvances.cbegin() + clusterGlyphBegin + clusterGlyphLength,
|
||||
0.0f);
|
||||
|
||||
// With certain font faces at certain sizes, the advances seem to be slightly more than
|
||||
// the pixel grid; Cascadia Code at 13pt (though, 200% scale) had an advance of 10.000001.
|
||||
// We don't want anything sub one hundredth of a cell to make us break up runs, because
|
||||
// doing so results in suboptimal rendering.
|
||||
// If what we expect is bigger than what we have... pad it out.
|
||||
if ((advanceExpected - advanceActual) > 0.001f)
|
||||
if (advanceExpected > advanceActual)
|
||||
{
|
||||
// Get the amount of space we have leftover.
|
||||
const auto diff = advanceExpected - advanceActual;
|
||||
@@ -661,7 +657,7 @@ try
|
||||
_glyphAdvances.at(static_cast<size_t>(clusterGlyphBegin) + clusterGlyphLength - 1) += diff;
|
||||
}
|
||||
// If what we expect is smaller than what we have... rescale the font size to get a smaller glyph to fit.
|
||||
else if ((advanceExpected - advanceActual) < -0.001f)
|
||||
else if (advanceExpected < advanceActual)
|
||||
{
|
||||
const auto scaleProposed = advanceExpected / advanceActual;
|
||||
|
||||
|
||||
@@ -65,27 +65,26 @@ using namespace Microsoft::Console::Types;
|
||||
// TODO GH 2683: The default constructor should not throw.
|
||||
DxEngine::DxEngine() :
|
||||
RenderEngineBase(),
|
||||
_invalidMap{},
|
||||
/*_isInvalidUsed{ false },
|
||||
_invalidRect{ 0 },*/
|
||||
_isInvalidUsed{ false },
|
||||
_invalidRect{ 0 },
|
||||
_invalidScroll{ 0 },
|
||||
_presentParams{ 0 },
|
||||
_presentReady{ false },
|
||||
_presentScroll{ 0 },
|
||||
/*_presentDirty{ 0 },*/
|
||||
_presentDirty{ 0 },
|
||||
_presentOffset{ 0 },
|
||||
_isEnabled{ false },
|
||||
_isPainting{ false },
|
||||
_displaySizePixels{},
|
||||
_displaySizePixels{ 0 },
|
||||
_foregroundColor{ 0 },
|
||||
_backgroundColor{ 0 },
|
||||
_selectionBackground{},
|
||||
_glyphCell{ 1, 1 },
|
||||
_glyphCell{ 0 },
|
||||
_haveDeviceResources{ false },
|
||||
_retroTerminalEffects{ false },
|
||||
_antialiasingMode{ D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE },
|
||||
_hwndTarget{ static_cast<HWND>(INVALID_HANDLE_VALUE) },
|
||||
_sizeTarget{},
|
||||
_sizeTarget{ 0 },
|
||||
_dpi{ USER_DEFAULT_SCREEN_DPI },
|
||||
_scale{ 1.0f },
|
||||
_chainMode{ SwapChainMode::ForComposition },
|
||||
@@ -239,8 +238,8 @@ HRESULT DxEngine::_SetupTerminalEffects()
|
||||
|
||||
// Setup the viewport.
|
||||
D3D11_VIEWPORT vp;
|
||||
vp.Width = _displaySizePixels.width<FLOAT>();
|
||||
vp.Height = _displaySizePixels.height<FLOAT>();
|
||||
vp.Width = static_cast<FLOAT>(_displaySizePixels.cx);
|
||||
vp.Height = static_cast<FLOAT>(_displaySizePixels.cy);
|
||||
vp.MinDepth = 0.0f;
|
||||
vp.MaxDepth = 1.0f;
|
||||
vp.TopLeftX = 0;
|
||||
@@ -410,8 +409,6 @@ void DxEngine::_ComputePixelShaderSettings() noexcept
|
||||
|
||||
_displaySizePixels = _GetClientSize();
|
||||
|
||||
_invalidMap.resize(_displaySizePixels / _glyphCell);
|
||||
|
||||
if (createSwapChain)
|
||||
{
|
||||
DXGI_SWAP_CHAIN_DESC1 SwapChainDesc = { 0 };
|
||||
@@ -430,15 +427,11 @@ void DxEngine::_ComputePixelShaderSettings() noexcept
|
||||
case SwapChainMode::ForHwnd:
|
||||
{
|
||||
// use the HWND's dimensions for the swap chain dimensions.
|
||||
til::rectangle clientRect;
|
||||
{
|
||||
RECT rect = { 0 };
|
||||
RETURN_IF_WIN32_BOOL_FALSE(GetClientRect(_hwndTarget, &rect));
|
||||
clientRect = rect;
|
||||
}
|
||||
RECT rect = { 0 };
|
||||
RETURN_IF_WIN32_BOOL_FALSE(GetClientRect(_hwndTarget, &rect));
|
||||
|
||||
SwapChainDesc.Width = clientRect.width<UINT>();
|
||||
SwapChainDesc.Height = clientRect.height<UINT>();
|
||||
SwapChainDesc.Width = rect.right - rect.left;
|
||||
SwapChainDesc.Height = rect.bottom - rect.top;
|
||||
|
||||
// We can't do alpha for HWNDs. Set to ignore. It will fail otherwise.
|
||||
SwapChainDesc.AlphaMode = DXGI_ALPHA_MODE_IGNORE;
|
||||
@@ -464,8 +457,8 @@ void DxEngine::_ComputePixelShaderSettings() noexcept
|
||||
case SwapChainMode::ForComposition:
|
||||
{
|
||||
// Use the given target size for compositions.
|
||||
SwapChainDesc.Width = _displaySizePixels.width<UINT>();
|
||||
SwapChainDesc.Height = _displaySizePixels.height<UINT>();
|
||||
SwapChainDesc.Width = _displaySizePixels.cx;
|
||||
SwapChainDesc.Height = _displaySizePixels.cy;
|
||||
|
||||
// We're doing advanced composition pretty much for the purpose of pretty alpha, so turn it on.
|
||||
SwapChainDesc.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED;
|
||||
@@ -635,8 +628,8 @@ void DxEngine::_ReleaseDeviceResources() noexcept
|
||||
return _dwriteFactory->CreateTextLayout(string,
|
||||
gsl::narrow<UINT32>(stringLength),
|
||||
_dwriteTextFormat.Get(),
|
||||
_displaySizePixels.width<float>(),
|
||||
_glyphCell.height() != 0 ? _glyphCell.height<float>() : _displaySizePixels.height<float>(),
|
||||
gsl::narrow<float>(_displaySizePixels.cx),
|
||||
_glyphCell.cy != 0 ? _glyphCell.cy : gsl::narrow<float>(_displaySizePixels.cy),
|
||||
ppTextLayout);
|
||||
}
|
||||
|
||||
@@ -693,11 +686,7 @@ Microsoft::WRL::ComPtr<IDXGISwapChain1> DxEngine::GetSwapChain()
|
||||
{
|
||||
RETURN_HR_IF_NULL(E_INVALIDARG, psrRegion);
|
||||
|
||||
SMALL_RECT inclusive = *psrRegion;
|
||||
inclusive.Right--;
|
||||
inclusive.Bottom--;
|
||||
|
||||
_InvalidOr(inclusive);
|
||||
_InvalidOr(*psrRegion);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
@@ -711,7 +700,7 @@ Microsoft::WRL::ComPtr<IDXGISwapChain1> DxEngine::GetSwapChain()
|
||||
{
|
||||
RETURN_HR_IF_NULL(E_INVALIDARG, pcoordCursor);
|
||||
|
||||
const SMALL_RECT sr = Microsoft::Console::Types::Viewport::FromCoord(*pcoordCursor).ToExclusive();
|
||||
const SMALL_RECT sr = Microsoft::Console::Types::Viewport::FromCoord(*pcoordCursor).ToInclusive();
|
||||
return Invalidate(&sr);
|
||||
}
|
||||
|
||||
@@ -759,42 +748,39 @@ Microsoft::WRL::ComPtr<IDXGISwapChain1> DxEngine::GetSwapChain()
|
||||
{
|
||||
try
|
||||
{
|
||||
/*til::point delta(*pcoordDelta);*/
|
||||
// TODO: do this.
|
||||
POINT delta = { 0 };
|
||||
delta.x = pcoordDelta->X * _glyphCell.cx;
|
||||
delta.y = pcoordDelta->Y * _glyphCell.cy;
|
||||
|
||||
//POINT delta = { 0 };
|
||||
//delta.x = pcoordDelta->X * _glyphCell.cx;
|
||||
//delta.y = pcoordDelta->Y * _glyphCell.cy;
|
||||
_InvalidOffset(delta);
|
||||
|
||||
//_InvalidOffset(delta);
|
||||
_invalidScroll.cx += delta.x;
|
||||
_invalidScroll.cy += delta.y;
|
||||
|
||||
//_invalidScroll.cx += delta.x;
|
||||
//_invalidScroll.cy += delta.y;
|
||||
// Add the revealed portion of the screen from the scroll to the invalid area.
|
||||
const RECT display = _GetDisplayRect();
|
||||
RECT reveal = display;
|
||||
|
||||
//// Add the revealed portion of the screen from the scroll to the invalid area.
|
||||
//const RECT display = _GetDisplayRect();
|
||||
//RECT reveal = display;
|
||||
// X delta first
|
||||
OffsetRect(&reveal, delta.x, 0);
|
||||
IntersectRect(&reveal, &reveal, &display);
|
||||
SubtractRect(&reveal, &display, &reveal);
|
||||
|
||||
//// X delta first
|
||||
//OffsetRect(&reveal, delta.x, 0);
|
||||
//IntersectRect(&reveal, &reveal, &display);
|
||||
//SubtractRect(&reveal, &display, &reveal);
|
||||
if (!IsRectEmpty(&reveal))
|
||||
{
|
||||
_InvalidOr(reveal);
|
||||
}
|
||||
|
||||
//if (!IsRectEmpty(&reveal))
|
||||
//{
|
||||
// _InvalidOr(reveal);
|
||||
//}
|
||||
// Y delta second (subtract rect won't work if you move both)
|
||||
reveal = display;
|
||||
OffsetRect(&reveal, 0, delta.y);
|
||||
IntersectRect(&reveal, &reveal, &display);
|
||||
SubtractRect(&reveal, &display, &reveal);
|
||||
|
||||
//// Y delta second (subtract rect won't work if you move both)
|
||||
//reveal = display;
|
||||
//OffsetRect(&reveal, 0, delta.y);
|
||||
//IntersectRect(&reveal, &reveal, &display);
|
||||
//SubtractRect(&reveal, &display, &reveal);
|
||||
|
||||
//if (!IsRectEmpty(&reveal))
|
||||
//{
|
||||
// _InvalidOr(reveal);
|
||||
//}
|
||||
if (!IsRectEmpty(&reveal))
|
||||
{
|
||||
_InvalidOr(reveal);
|
||||
}
|
||||
}
|
||||
CATCH_RETURN();
|
||||
}
|
||||
@@ -810,9 +796,8 @@ Microsoft::WRL::ComPtr<IDXGISwapChain1> DxEngine::GetSwapChain()
|
||||
// - S_OK
|
||||
[[nodiscard]] HRESULT DxEngine::InvalidateAll() noexcept
|
||||
{
|
||||
_invalidMap.set_all();
|
||||
/*const RECT screen = _GetDisplayRect();
|
||||
_InvalidOr(screen);*/
|
||||
const RECT screen = _GetDisplayRect();
|
||||
_InvalidOr(screen);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
@@ -837,7 +822,7 @@ Microsoft::WRL::ComPtr<IDXGISwapChain1> DxEngine::GetSwapChain()
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - X by Y area in pixels of the surface
|
||||
[[nodiscard]] til::size DxEngine::_GetClientSize() const noexcept
|
||||
[[nodiscard]] SIZE DxEngine::_GetClientSize() const noexcept
|
||||
{
|
||||
switch (_chainMode)
|
||||
{
|
||||
@@ -846,42 +831,39 @@ Microsoft::WRL::ComPtr<IDXGISwapChain1> DxEngine::GetSwapChain()
|
||||
RECT clientRect = { 0 };
|
||||
LOG_IF_WIN32_BOOL_FALSE(GetClientRect(_hwndTarget, &clientRect));
|
||||
|
||||
til::rectangle client{ clientRect };
|
||||
SIZE clientSize = { 0 };
|
||||
clientSize.cx = clientRect.right - clientRect.left;
|
||||
clientSize.cy = clientRect.bottom - clientRect.top;
|
||||
|
||||
return client.size();
|
||||
return clientSize;
|
||||
}
|
||||
case SwapChainMode::ForComposition:
|
||||
{
|
||||
try
|
||||
{
|
||||
// TODO: fix scale
|
||||
/*return _sizeTarget * _scale;*/
|
||||
return _sizeTarget;
|
||||
}
|
||||
CATCH_LOG();
|
||||
|
||||
return _sizeTarget;
|
||||
SIZE size = _sizeTarget;
|
||||
size.cx = static_cast<LONG>(size.cx * _scale);
|
||||
size.cy = static_cast<LONG>(size.cy * _scale);
|
||||
return size;
|
||||
}
|
||||
default:
|
||||
FAIL_FAST_HR(E_NOTIMPL);
|
||||
}
|
||||
}
|
||||
|
||||
//// Routine Description:
|
||||
//// - Helper to multiply all parameters of a rectangle by the font size
|
||||
//// to convert from characters to pixels.
|
||||
//// Arguments:
|
||||
//// - cellsToPixels - rectangle to update
|
||||
//// - fontSize - scaling factors
|
||||
//// Return Value:
|
||||
//// - <none> - Updates reference
|
||||
//void _ScaleByFont(RECT& cellsToPixels, SIZE fontSize) noexcept
|
||||
//{
|
||||
// cellsToPixels.left *= fontSize.cx;
|
||||
// cellsToPixels.right *= fontSize.cx;
|
||||
// cellsToPixels.top *= fontSize.cy;
|
||||
// cellsToPixels.bottom *= fontSize.cy;
|
||||
//}
|
||||
// Routine Description:
|
||||
// - Helper to multiply all parameters of a rectangle by the font size
|
||||
// to convert from characters to pixels.
|
||||
// Arguments:
|
||||
// - cellsToPixels - rectangle to update
|
||||
// - fontSize - scaling factors
|
||||
// Return Value:
|
||||
// - <none> - Updates reference
|
||||
void _ScaleByFont(RECT& cellsToPixels, SIZE fontSize) noexcept
|
||||
{
|
||||
cellsToPixels.left *= fontSize.cx;
|
||||
cellsToPixels.right *= fontSize.cx;
|
||||
cellsToPixels.top *= fontSize.cy;
|
||||
cellsToPixels.bottom *= fontSize.cy;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Retrieves a rectangle representation of the pixel size of the
|
||||
@@ -890,9 +872,9 @@ Microsoft::WRL::ComPtr<IDXGISwapChain1> DxEngine::GetSwapChain()
|
||||
// - <none>
|
||||
// Return Value;
|
||||
// - Origin-placed rectangle representing the pixel size of the surface
|
||||
[[nodiscard]] til::rectangle DxEngine::_GetDisplayRect() const noexcept
|
||||
[[nodiscard]] RECT DxEngine::_GetDisplayRect() const noexcept
|
||||
{
|
||||
return til::rectangle{ til::point{ 0, 0 }, _displaySizePixels };
|
||||
return { 0, 0, _displaySizePixels.cx, _displaySizePixels.cy };
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
@@ -905,25 +887,22 @@ Microsoft::WRL::ComPtr<IDXGISwapChain1> DxEngine::GetSwapChain()
|
||||
// - <none>
|
||||
void DxEngine::_InvalidOffset(POINT delta)
|
||||
{
|
||||
til::point pt{ delta };
|
||||
_invalidMap += pt;
|
||||
if (_isInvalidUsed)
|
||||
{
|
||||
// Copy the existing invalid rect
|
||||
RECT invalidNew = _invalidRect;
|
||||
|
||||
//if (_isInvalidUsed)
|
||||
//{
|
||||
// // Copy the existing invalid rect
|
||||
// RECT invalidNew = _invalidRect;
|
||||
// Offset it to the new position
|
||||
THROW_IF_WIN32_BOOL_FALSE(OffsetRect(&invalidNew, delta.x, delta.y));
|
||||
|
||||
// // Offset it to the new position
|
||||
// THROW_IF_WIN32_BOOL_FALSE(OffsetRect(&invalidNew, delta.x, delta.y));
|
||||
// Get the rect representing the display
|
||||
const RECT rectScreen = _GetDisplayRect();
|
||||
|
||||
// // Get the rect representing the display
|
||||
// const RECT rectScreen = _GetDisplayRect();
|
||||
// Ensure that the new invalid rectangle is still on the display
|
||||
IntersectRect(&invalidNew, &invalidNew, &rectScreen);
|
||||
|
||||
// // Ensure that the new invalid rectangle is still on the display
|
||||
// IntersectRect(&invalidNew, &invalidNew, &rectScreen);
|
||||
|
||||
// _invalidRect = invalidNew;
|
||||
//}
|
||||
_invalidRect = invalidNew;
|
||||
}
|
||||
}
|
||||
|
||||
// Routine description:
|
||||
@@ -935,22 +914,17 @@ void DxEngine::_InvalidOffset(POINT delta)
|
||||
// - <none>
|
||||
void DxEngine::_InvalidOr(SMALL_RECT sr) noexcept
|
||||
{
|
||||
if (_invalidMap)
|
||||
{
|
||||
_invalidMap.set(sr);
|
||||
}
|
||||
RECT region;
|
||||
region.left = sr.Left;
|
||||
region.top = sr.Top;
|
||||
region.right = sr.Right;
|
||||
region.bottom = sr.Bottom;
|
||||
_ScaleByFont(region, _glyphCell);
|
||||
|
||||
//RECT region;
|
||||
//region.left = sr.Left;
|
||||
//region.top = sr.Top;
|
||||
//region.right = sr.Right;
|
||||
//region.bottom = sr.Bottom;
|
||||
//_ScaleByFont(region, _glyphCell);
|
||||
region.right += _glyphCell.cx;
|
||||
region.bottom += _glyphCell.cy;
|
||||
|
||||
//region.right += _glyphCell.cx;
|
||||
//region.bottom += _glyphCell.cy;
|
||||
|
||||
//_InvalidOr(region);
|
||||
_InvalidOr(region);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
@@ -959,20 +933,20 @@ void DxEngine::_InvalidOr(SMALL_RECT sr) noexcept
|
||||
// - rc - Dirty pixel rectangle
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void DxEngine::_InvalidOr(RECT /*rc*/) noexcept
|
||||
void DxEngine::_InvalidOr(RECT rc) noexcept
|
||||
{
|
||||
//if (_isInvalidUsed)
|
||||
//{
|
||||
// UnionRect(&_invalidRect, &_invalidRect, &rc);
|
||||
if (_isInvalidUsed)
|
||||
{
|
||||
UnionRect(&_invalidRect, &_invalidRect, &rc);
|
||||
|
||||
// const RECT rcScreen = _GetDisplayRect();
|
||||
// IntersectRect(&_invalidRect, &_invalidRect, &rcScreen);
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
// _invalidRect = rc;
|
||||
// _isInvalidUsed = true;
|
||||
//}
|
||||
const RECT rcScreen = _GetDisplayRect();
|
||||
IntersectRect(&_invalidRect, &_invalidRect, &rcScreen);
|
||||
}
|
||||
else
|
||||
{
|
||||
_invalidRect = rc;
|
||||
_isInvalidUsed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
@@ -997,8 +971,25 @@ void DxEngine::_InvalidOr(RECT /*rc*/) noexcept
|
||||
// - Any DirectX error, a memory error, etc.
|
||||
[[nodiscard]] HRESULT DxEngine::StartPaint() noexcept
|
||||
{
|
||||
FAIL_FAST_IF_FAILED(InvalidateAll());
|
||||
RETURN_HR_IF(E_NOT_VALID_STATE, _isPainting); // invalid to start a paint while painting.
|
||||
|
||||
#pragma warning(suppress : 26477 26485 26494 26482 26446 26447) // We don't control TraceLoggingWrite
|
||||
TraceLoggingWrite(g_hDxRenderProvider,
|
||||
"Invalid",
|
||||
TraceLoggingInt32(_invalidRect.bottom - _invalidRect.top, "InvalidHeight"),
|
||||
TraceLoggingInt32((_invalidRect.bottom - _invalidRect.top) / _glyphCell.cy, "InvalidHeightChars"),
|
||||
TraceLoggingInt32(_invalidRect.right - _invalidRect.left, "InvalidWidth"),
|
||||
TraceLoggingInt32((_invalidRect.right - _invalidRect.left) / _glyphCell.cx, "InvalidWidthChars"),
|
||||
TraceLoggingInt32(_invalidRect.left, "InvalidX"),
|
||||
TraceLoggingInt32(_invalidRect.left / _glyphCell.cx, "InvalidXChars"),
|
||||
TraceLoggingInt32(_invalidRect.top, "InvalidY"),
|
||||
TraceLoggingInt32(_invalidRect.top / _glyphCell.cy, "InvalidYChars"),
|
||||
TraceLoggingInt32(_invalidScroll.cx, "ScrollWidth"),
|
||||
TraceLoggingInt32(_invalidScroll.cx / _glyphCell.cx, "ScrollWidthChars"),
|
||||
TraceLoggingInt32(_invalidScroll.cy, "ScrollHeight"),
|
||||
TraceLoggingInt32(_invalidScroll.cy / _glyphCell.cy, "ScrollHeightChars"));
|
||||
|
||||
if (_isEnabled)
|
||||
{
|
||||
try
|
||||
@@ -1008,7 +999,8 @@ void DxEngine::_InvalidOr(RECT /*rc*/) noexcept
|
||||
{
|
||||
RETURN_IF_FAILED(_CreateDeviceResources(true));
|
||||
}
|
||||
else if (_displaySizePixels != clientSize)
|
||||
else if (_displaySizePixels.cy != clientSize.cy ||
|
||||
_displaySizePixels.cx != clientSize.cx)
|
||||
{
|
||||
// OK, we're going to play a dangerous game here for the sake of optimizing resize
|
||||
// First, set up a complete clear of all device resources if something goes terribly wrong.
|
||||
@@ -1021,7 +1013,7 @@ void DxEngine::_InvalidOr(RECT /*rc*/) noexcept
|
||||
_d2dRenderTarget.Reset();
|
||||
|
||||
// Change the buffer size and recreate the render target (and surface)
|
||||
RETURN_IF_FAILED(_dxgiSwapChain->ResizeBuffers(2, clientSize.width<UINT>(), clientSize.height<UINT>(), DXGI_FORMAT_B8G8R8A8_UNORM, 0));
|
||||
RETURN_IF_FAILED(_dxgiSwapChain->ResizeBuffers(2, clientSize.cx, clientSize.cy, DXGI_FORMAT_B8G8R8A8_UNORM, 0));
|
||||
RETURN_IF_FAILED(_PrepareRenderTarget());
|
||||
|
||||
// OK we made it past the parts that can cause errors. We can release our failure handler.
|
||||
@@ -1029,32 +1021,10 @@ void DxEngine::_InvalidOr(RECT /*rc*/) noexcept
|
||||
|
||||
// And persist the new size.
|
||||
_displaySizePixels = clientSize;
|
||||
|
||||
_invalidMap.resize(_displaySizePixels / _glyphCell);
|
||||
}
|
||||
|
||||
_d2dRenderTarget->BeginDraw();
|
||||
_isPainting = true;
|
||||
|
||||
// Walk the map for dirty rectangles and store them up.
|
||||
// We're going to have to go over them multiple times, so don't spend all the iteration
|
||||
// work multiple times.
|
||||
for (auto it = _invalidMap.begin_runs(); it < _invalidMap.end_runs(); ++it)
|
||||
{
|
||||
auto rect = *it;
|
||||
|
||||
#pragma warning(suppress : 26477 26485 26494 26482 26446 26447) // We don't control TraceLoggingWrite
|
||||
TraceLoggingWrite(g_hDxRenderProvider,
|
||||
"Invalid",
|
||||
TraceLoggingInt32((LONG)rect.height(), "InvalidHeightChars"),
|
||||
TraceLoggingInt32((LONG)rect.width(), "InvalidWidthChars"),
|
||||
TraceLoggingInt32((LONG)rect.left(), "InvalidXChars"),
|
||||
TraceLoggingInt32((LONG)rect.top(), "InvalidYChars"),
|
||||
TraceLoggingInt32(_invalidScroll.cx, "ScrollWidthChars"),
|
||||
TraceLoggingInt32(_invalidScroll.cy, "ScrollHeightChars"));
|
||||
|
||||
_dirtyRects.push_back(rect);
|
||||
}
|
||||
}
|
||||
CATCH_RETURN();
|
||||
}
|
||||
@@ -1084,19 +1054,15 @@ void DxEngine::_InvalidOr(RECT /*rc*/) noexcept
|
||||
{
|
||||
if (_invalidScroll.cy != 0 || _invalidScroll.cx != 0)
|
||||
{
|
||||
// The scroll rect is the entire screen minus the revealed areas.
|
||||
// Get the entire screen into a rectangle.
|
||||
til::rectangle scrollArea = _GetDisplayRect();
|
||||
_presentDirty = _invalidRect;
|
||||
|
||||
// Reduce the size of the rectangle by the scroll
|
||||
scrollArea -= _invalidScroll;
|
||||
|
||||
_presentScroll = scrollArea;
|
||||
const RECT display = _GetDisplayRect();
|
||||
SubtractRect(&_presentScroll, &display, &_presentDirty);
|
||||
_presentOffset.x = _invalidScroll.cx;
|
||||
_presentOffset.y = _invalidScroll.cy;
|
||||
|
||||
_presentParams.DirtyRectsCount = gsl::narrow<UINT>(_dirtyRectRects.size());
|
||||
_presentParams.pDirtyRects = _dirtyRectRects.data();
|
||||
_presentParams.DirtyRectsCount = 1;
|
||||
_presentParams.pDirtyRects = &_presentDirty;
|
||||
|
||||
_presentParams.pScrollOffset = &_presentOffset;
|
||||
_presentParams.pScrollRect = &_presentScroll;
|
||||
@@ -1117,11 +1083,8 @@ void DxEngine::_InvalidOr(RECT /*rc*/) noexcept
|
||||
}
|
||||
}
|
||||
|
||||
_dirtyRects.clear();
|
||||
_invalidMap.reset_all();
|
||||
|
||||
/*_invalidRect = { 0 };
|
||||
_isInvalidUsed = false;*/
|
||||
_invalidRect = { 0 };
|
||||
_isInvalidUsed = false;
|
||||
|
||||
_invalidScroll = { 0 };
|
||||
|
||||
@@ -1198,8 +1161,7 @@ void DxEngine::_InvalidOr(RECT /*rc*/) noexcept
|
||||
RETURN_IF_FAILED(_CopyFrontToBack());
|
||||
_presentReady = false;
|
||||
|
||||
_dirtyRectRects.clear();
|
||||
/*_presentDirty = { 0 };*/
|
||||
_presentDirty = { 0 };
|
||||
_presentOffset = { 0 };
|
||||
_presentScroll = { 0 };
|
||||
_presentParams = { 0 };
|
||||
@@ -1222,22 +1184,27 @@ void DxEngine::_InvalidOr(RECT /*rc*/) noexcept
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - This paints in the back most layer of the frame with clear/nothing so it can
|
||||
// be transparent if it wants to be.
|
||||
// - This paints in the back most layer of the frame with the background color.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - S_OK
|
||||
[[nodiscard]] HRESULT DxEngine::PaintBackground() noexcept
|
||||
{
|
||||
const D2D1_COLOR_F nothing = { 0 }; // 0 alpha and color is black.
|
||||
for (const D2D1_RECT_F rect : _dirtyRects)
|
||||
switch (_chainMode)
|
||||
{
|
||||
_d2dRenderTarget->SetTransform(D2D1::Matrix3x2F::Scale(_glyphCell));
|
||||
_d2dRenderTarget->PushAxisAlignedClip(rect, D2D1_ANTIALIAS_MODE_ALIASED);
|
||||
case SwapChainMode::ForHwnd:
|
||||
_d2dRenderTarget->FillRectangle(D2D1::RectF(static_cast<float>(_invalidRect.left),
|
||||
static_cast<float>(_invalidRect.top),
|
||||
static_cast<float>(_invalidRect.right),
|
||||
static_cast<float>(_invalidRect.bottom)),
|
||||
_d2dBrushBackground.Get());
|
||||
break;
|
||||
case SwapChainMode::ForComposition:
|
||||
D2D1_COLOR_F nothing = { 0 };
|
||||
|
||||
_d2dRenderTarget->Clear(nothing);
|
||||
_d2dRenderTarget->PopAxisAlignedClip();
|
||||
_d2dRenderTarget->SetTransform(D2D1::Matrix3x2F::Identity());
|
||||
break;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
@@ -1258,10 +1225,10 @@ void DxEngine::_InvalidOr(RECT /*rc*/) noexcept
|
||||
{
|
||||
try
|
||||
{
|
||||
const til::point cellPoint{ coord };
|
||||
|
||||
// Calculate positioning of our origin.
|
||||
const D2D1_POINT_2F origin = til::point{ coord } * _glyphCell;
|
||||
D2D1_POINT_2F origin;
|
||||
origin.x = static_cast<float>(coord.X * _glyphCell.cx);
|
||||
origin.y = static_cast<float>(coord.Y * _glyphCell.cy);
|
||||
|
||||
// Create the text layout
|
||||
CustomTextLayout layout(_dwriteFactory.Get(),
|
||||
@@ -1269,7 +1236,7 @@ void DxEngine::_InvalidOr(RECT /*rc*/) noexcept
|
||||
_dwriteTextFormat.Get(),
|
||||
_dwriteFontFace.Get(),
|
||||
clusters,
|
||||
_glyphCell.width());
|
||||
_glyphCell.cx);
|
||||
|
||||
// Get the baseline for this font as that's where we draw from
|
||||
DWRITE_LINE_SPACING spacing;
|
||||
@@ -1281,7 +1248,7 @@ void DxEngine::_InvalidOr(RECT /*rc*/) noexcept
|
||||
_d2dBrushBackground.Get(),
|
||||
_dwriteFactory.Get(),
|
||||
spacing,
|
||||
_glyphCell,
|
||||
D2D1::SizeF(gsl::narrow<FLOAT>(_glyphCell.cx), gsl::narrow<FLOAT>(_glyphCell.cy)),
|
||||
D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT);
|
||||
|
||||
// Layout then render the text
|
||||
@@ -1382,18 +1349,25 @@ void DxEngine::_InvalidOr(RECT /*rc*/) noexcept
|
||||
// - rect - Rectangle to invert or highlight to make the selection area
|
||||
// Return Value:
|
||||
// - S_OK or relevant DirectX error.
|
||||
[[nodiscard]] HRESULT DxEngine::PaintSelection(SMALL_RECT rect) noexcept
|
||||
[[nodiscard]] HRESULT DxEngine::PaintSelection(const SMALL_RECT rect) noexcept
|
||||
{
|
||||
const auto existingColor = _d2dBrushForeground->GetColor();
|
||||
|
||||
_d2dBrushForeground->SetColor(_selectionBackground);
|
||||
const auto resetColorOnExit = wil::scope_exit([&]() noexcept { _d2dBrushForeground->SetColor(existingColor); });
|
||||
|
||||
rect.Bottom--;
|
||||
rect.Right--;
|
||||
RECT pixels;
|
||||
pixels.left = rect.Left * _glyphCell.cx;
|
||||
pixels.top = rect.Top * _glyphCell.cy;
|
||||
pixels.right = rect.Right * _glyphCell.cx;
|
||||
pixels.bottom = rect.Bottom * _glyphCell.cy;
|
||||
|
||||
D2D1_RECT_F draw = { 0 };
|
||||
draw.left = static_cast<float>(pixels.left);
|
||||
draw.top = static_cast<float>(pixels.top);
|
||||
draw.right = static_cast<float>(pixels.right);
|
||||
draw.bottom = static_cast<float>(pixels.bottom);
|
||||
|
||||
/* rect is SMALL_RECT */
|
||||
const D2D1_RECT_F draw = til::rectangle{ rect } * _glyphCell;
|
||||
_d2dRenderTarget->FillRectangle(draw, _d2dBrushForeground.Get());
|
||||
|
||||
return S_OK;
|
||||
@@ -1421,12 +1395,16 @@ enum class CursorPaintType
|
||||
return S_FALSE;
|
||||
}
|
||||
// Create rectangular block representing where the cursor can fill.
|
||||
D2D1_RECT_F rect = til::rectangle{ til::point{ options.coordCursor } } * _glyphCell;
|
||||
D2D1_RECT_F rect = { 0 };
|
||||
rect.left = static_cast<float>(options.coordCursor.X * _glyphCell.cx);
|
||||
rect.top = static_cast<float>(options.coordCursor.Y * _glyphCell.cy);
|
||||
rect.right = static_cast<float>(rect.left + _glyphCell.cx);
|
||||
rect.bottom = static_cast<float>(rect.top + _glyphCell.cy);
|
||||
|
||||
// If we're double-width, make it one extra glyph wider
|
||||
if (options.fIsDoubleWidth)
|
||||
{
|
||||
rect.right += _glyphCell.width();
|
||||
rect.right += _glyphCell.cx;
|
||||
}
|
||||
|
||||
CursorPaintType paintType = CursorPaintType::Fill;
|
||||
@@ -1438,7 +1416,7 @@ enum class CursorPaintType
|
||||
// Enforce min/max cursor height
|
||||
ULONG ulHeight = std::clamp(options.ulCursorHeightPercent, s_ulMinCursorHeightPercent, s_ulMaxCursorHeightPercent);
|
||||
|
||||
ulHeight = gsl::narrow<ULONG>((_glyphCell.height() * ulHeight) / 100);
|
||||
ulHeight = gsl::narrow<ULONG>((_glyphCell.cy * ulHeight) / 100);
|
||||
rect.top = rect.bottom - ulHeight;
|
||||
break;
|
||||
}
|
||||
@@ -1608,9 +1586,10 @@ CATCH_RETURN()
|
||||
|
||||
try
|
||||
{
|
||||
_glyphCell = fiFontInfo.GetSize();
|
||||
const auto size = fiFontInfo.GetSize();
|
||||
|
||||
_invalidMap.resize(_displaySizePixels / _glyphCell);
|
||||
_glyphCell.cx = size.X;
|
||||
_glyphCell.cy = size.Y;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
|
||||
@@ -1619,9 +1598,10 @@ CATCH_RETURN()
|
||||
|
||||
[[nodiscard]] Viewport DxEngine::GetViewportInCharacters(const Viewport& viewInPixels) noexcept
|
||||
{
|
||||
const auto cellSize = til::size{ viewInPixels.Dimensions() } / _glyphCell;
|
||||
const short widthInChars = gsl::narrow_cast<short>(viewInPixels.Width() / _glyphCell.cx);
|
||||
const short heightInChars = gsl::narrow_cast<short>(viewInPixels.Height() / _glyphCell.cy);
|
||||
|
||||
return Viewport::FromDimensions(viewInPixels.Origin(), cellSize);
|
||||
return Viewport::FromDimensions(viewInPixels.Origin(), { widthInChars, heightInChars });
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
@@ -1706,10 +1686,19 @@ float DxEngine::GetScaling() const noexcept
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - Rectangle describing dirty area in characters.
|
||||
// TODO: maybe this should be returning a ref... not a copy...
|
||||
[[nodiscard]] std::vector<til::rectangle> DxEngine::GetDirtyArea()
|
||||
[[nodiscard]] SMALL_RECT DxEngine::GetDirtyRectInChars() noexcept
|
||||
{
|
||||
return _dirtyRects;
|
||||
SMALL_RECT r;
|
||||
r.Top = gsl::narrow<SHORT>(floor(_invalidRect.top / _glyphCell.cy));
|
||||
r.Left = gsl::narrow<SHORT>(floor(_invalidRect.left / _glyphCell.cx));
|
||||
r.Bottom = gsl::narrow<SHORT>(floor(_invalidRect.bottom / _glyphCell.cy));
|
||||
r.Right = gsl::narrow<SHORT>(floor(_invalidRect.right / _glyphCell.cx));
|
||||
|
||||
// Exclusive to inclusive
|
||||
r.Bottom--;
|
||||
r.Right--;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
@@ -1721,7 +1710,7 @@ float DxEngine::GetScaling() const noexcept
|
||||
// - Nearest integer short x and y values for each cell.
|
||||
[[nodiscard]] COORD DxEngine::_GetFontSize() const noexcept
|
||||
{
|
||||
return _glyphCell;
|
||||
return { gsl::narrow<SHORT>(_glyphCell.cx), gsl::narrow<SHORT>(_glyphCell.cy) };
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
@@ -1757,7 +1746,7 @@ float DxEngine::GetScaling() const noexcept
|
||||
_dwriteTextFormat.Get(),
|
||||
_dwriteFontFace.Get(),
|
||||
{ &cluster, 1 },
|
||||
_glyphCell.width());
|
||||
_glyphCell.cx);
|
||||
|
||||
UINT32 columns = 0;
|
||||
RETURN_IF_FAILED(layout.GetColumns(&columns));
|
||||
@@ -1994,10 +1983,7 @@ float DxEngine::GetScaling() const noexcept
|
||||
DWRITE_FONT_STRETCH stretch = DWRITE_FONT_STRETCH_NORMAL;
|
||||
std::wstring localeName = _GetLocaleName();
|
||||
|
||||
// _ResolveFontFaceWithFallback overrides the last argument with the locale name of the font,
|
||||
// but we should use the system's locale to render the text.
|
||||
std::wstring fontLocaleName = localeName;
|
||||
const auto face = _ResolveFontFaceWithFallback(fontName, weight, stretch, style, fontLocaleName);
|
||||
const auto face = _ResolveFontFaceWithFallback(fontName, weight, stretch, style, localeName);
|
||||
|
||||
DWRITE_FONT_METRICS1 fontMetrics;
|
||||
face->GetMetrics(&fontMetrics);
|
||||
|
||||
@@ -27,9 +27,6 @@
|
||||
|
||||
#include <TraceLoggingProvider.h>
|
||||
|
||||
#include "til/bitmap.h"
|
||||
#include "til/operators.h"
|
||||
|
||||
TRACELOGGING_DECLARE_PROVIDER(g_hDxRenderProvider);
|
||||
|
||||
namespace Microsoft::Console::Render
|
||||
@@ -98,7 +95,7 @@ namespace Microsoft::Console::Render
|
||||
|
||||
[[nodiscard]] HRESULT GetProposedFont(const FontInfoDesired& fiFontInfoDesired, FontInfo& fiFontInfo, int const iDpi) noexcept override;
|
||||
|
||||
[[nodiscard]] std::vector<til::rectangle> GetDirtyArea() override;
|
||||
[[nodiscard]] SMALL_RECT GetDirtyRectInChars() 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;
|
||||
@@ -124,7 +121,7 @@ namespace Microsoft::Console::Render
|
||||
SwapChainMode _chainMode;
|
||||
|
||||
HWND _hwndTarget;
|
||||
til::size _sizeTarget;
|
||||
SIZE _sizeTarget;
|
||||
int _dpi;
|
||||
float _scale;
|
||||
|
||||
@@ -133,8 +130,8 @@ namespace Microsoft::Console::Render
|
||||
bool _isEnabled;
|
||||
bool _isPainting;
|
||||
|
||||
til::size _displaySizePixels;
|
||||
til::size _glyphCell;
|
||||
SIZE _displaySizePixels;
|
||||
SIZE _glyphCell;
|
||||
|
||||
D2D1_COLOR_F _defaultForegroundColor;
|
||||
D2D1_COLOR_F _defaultBackgroundColor;
|
||||
@@ -143,14 +140,10 @@ namespace Microsoft::Console::Render
|
||||
D2D1_COLOR_F _backgroundColor;
|
||||
D2D1_COLOR_F _selectionBackground;
|
||||
|
||||
[[nodiscard]] til::rectangle _GetDisplayRect() const noexcept;
|
||||
[[nodiscard]] RECT _GetDisplayRect() const noexcept;
|
||||
|
||||
til::bitmap _invalidMap;
|
||||
std::vector<til::rectangle> _dirtyRects;
|
||||
std::vector<RECT> _dirtyRectRects;
|
||||
|
||||
//bool _isInvalidUsed;
|
||||
//RECT _invalidRect;
|
||||
bool _isInvalidUsed;
|
||||
RECT _invalidRect;
|
||||
SIZE _invalidScroll;
|
||||
|
||||
void _InvalidOr(SMALL_RECT sr) noexcept;
|
||||
@@ -159,7 +152,7 @@ namespace Microsoft::Console::Render
|
||||
void _InvalidOffset(POINT pt);
|
||||
|
||||
bool _presentReady;
|
||||
/*RECT _presentDirty;*/
|
||||
RECT _presentDirty;
|
||||
RECT _presentScroll;
|
||||
POINT _presentOffset;
|
||||
DXGI_PRESENT_PARAMETERS _presentParams;
|
||||
@@ -253,7 +246,7 @@ namespace Microsoft::Console::Render
|
||||
|
||||
[[nodiscard]] COORD _GetFontSize() const noexcept;
|
||||
|
||||
[[nodiscard]] til::size _GetClientSize() const noexcept;
|
||||
[[nodiscard]] SIZE _GetClientSize() const noexcept;
|
||||
|
||||
[[nodiscard]] D2D1_COLOR_F _ColorFFromColorRef(const COLORREF color) noexcept;
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#pragma once
|
||||
|
||||
// This includes support libraries from the CRT, STL, WIL, and GSL
|
||||
#define BLOCK_TIL
|
||||
#include "LibraryIncludes.h"
|
||||
|
||||
#include <windows.h>
|
||||
@@ -35,7 +34,4 @@
|
||||
#include <dwrite_2.h>
|
||||
#include <dwrite_3.h>
|
||||
|
||||
// Include TIL after DX so we can use the DX conversions.
|
||||
#include "til.h"
|
||||
|
||||
#pragma hdrstop
|
||||
|
||||
@@ -68,7 +68,7 @@ namespace Microsoft::Console::Render
|
||||
_Out_ FontInfo& Font,
|
||||
const int iDpi) noexcept override;
|
||||
|
||||
[[nodiscard]] std::vector<til::rectangle> GetDirtyArea() override;
|
||||
SMALL_RECT GetDirtyRectInChars() override;
|
||||
[[nodiscard]] HRESULT GetFontSize(_Out_ COORD* const pFontSize) noexcept override;
|
||||
[[nodiscard]] HRESULT IsGlyphWideByFont(const std::wstring_view glyph, _Out_ bool* const pResult) noexcept override;
|
||||
|
||||
|
||||
@@ -16,14 +16,14 @@ using namespace Microsoft::Console::Render;
|
||||
// Return Value:
|
||||
// - The character dimensions of the current dirty area of the frame.
|
||||
// This is an Inclusive rect.
|
||||
std::vector<til::rectangle> GdiEngine::GetDirtyArea()
|
||||
SMALL_RECT GdiEngine::GetDirtyRectInChars()
|
||||
{
|
||||
RECT rc = _psInvalidData.rcPaint;
|
||||
|
||||
SMALL_RECT sr = { 0 };
|
||||
LOG_IF_FAILED(_ScaleByFont(&rc, &sr));
|
||||
|
||||
return { sr };
|
||||
return sr;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
|
||||
@@ -60,8 +60,6 @@ namespace Microsoft::Console::Render
|
||||
virtual COLORREF GetCursorColor() const noexcept = 0;
|
||||
virtual bool IsCursorDoubleWidth() const noexcept = 0;
|
||||
|
||||
virtual bool IsScreenReversed() const noexcept = 0;
|
||||
|
||||
virtual const std::vector<RenderOverlay> GetOverlays() const noexcept = 0;
|
||||
|
||||
virtual const bool IsGridLineDrawingAllowed() noexcept = 0;
|
||||
|
||||
@@ -14,7 +14,6 @@ Author(s):
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "til/rectangle.h"
|
||||
#include "../../inc/conattrs.hpp"
|
||||
#include "Cluster.hpp"
|
||||
#include "FontInfoDesired.hpp"
|
||||
@@ -118,7 +117,7 @@ namespace Microsoft::Console::Render
|
||||
_Out_ FontInfo& FontInfo,
|
||||
const int iDpi) noexcept = 0;
|
||||
|
||||
[[nodiscard]] virtual std::vector<til::rectangle> GetDirtyArea() = 0;
|
||||
virtual SMALL_RECT GetDirtyRectInChars() = 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& newTitle) noexcept = 0;
|
||||
|
||||
@@ -403,9 +403,9 @@ UiaEngine::UiaEngine(IUiaEventDispatcher* dispatcher) :
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - Rectangle describing dirty area in characters.
|
||||
[[nodiscard]] std::vector<til::rectangle> UiaEngine::GetDirtyArea()
|
||||
[[nodiscard]] SMALL_RECT UiaEngine::GetDirtyRectInChars() noexcept
|
||||
{
|
||||
return { Viewport::Empty().ToInclusive() };
|
||||
return Viewport::Empty().ToInclusive();
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
|
||||
@@ -71,7 +71,7 @@ namespace Microsoft::Console::Render
|
||||
|
||||
[[nodiscard]] HRESULT GetProposedFont(const FontInfoDesired& fiFontInfoDesired, FontInfo& fiFontInfo, int const iDpi) noexcept override;
|
||||
|
||||
[[nodiscard]] std::vector<til::rectangle> GetDirtyArea() override;
|
||||
[[nodiscard]] SMALL_RECT GetDirtyRectInChars() 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;
|
||||
|
||||
|
||||
@@ -82,6 +82,7 @@ using namespace Microsoft::Console::Render;
|
||||
[[nodiscard]] HRESULT VtEngine::_EraseCharacter(const short chars) noexcept
|
||||
{
|
||||
static const std::string format = "\x1b[%dX";
|
||||
|
||||
return _WriteFormattedString(&format, chars);
|
||||
}
|
||||
|
||||
@@ -94,6 +95,7 @@ using namespace Microsoft::Console::Render;
|
||||
[[nodiscard]] HRESULT VtEngine::_CursorForward(const short chars) noexcept
|
||||
{
|
||||
static const std::string format = "\x1b[%dC";
|
||||
|
||||
return _WriteFormattedString(&format, chars);
|
||||
}
|
||||
|
||||
|
||||
@@ -111,17 +111,5 @@ WinTelnetEngine::WinTelnetEngine(_In_ wil::unique_hfile hPipe,
|
||||
// - S_OK or suitable HRESULT error from either conversion or writing pipe.
|
||||
[[nodiscard]] HRESULT WinTelnetEngine::WriteTerminalW(_In_ const std::wstring_view wstr) noexcept
|
||||
{
|
||||
RETURN_IF_FAILED(VtEngine::_WriteTerminalAscii(wstr));
|
||||
// GH#4106, GH#2011 - WriteTerminalW is only ever called by the
|
||||
// StateMachine, when we've encountered a string we don't understand. When
|
||||
// this happens, we usually don't actually trigger another frame, but we
|
||||
// _do_ want this string to immediately be sent to the terminal. Since we
|
||||
// only flush our buffer on actual frames, this means that strings we've
|
||||
// decided to pass through would have gotten buffered here until the next
|
||||
// actual frame is triggered.
|
||||
//
|
||||
// To fix this, flush here, so this string is sent to the connected terminal
|
||||
// application.
|
||||
|
||||
return _Flush();
|
||||
return VtEngine::_WriteTerminalAscii(wstr);
|
||||
}
|
||||
|
||||
@@ -63,15 +63,16 @@ XtermEngine::XtermEngine(_In_ wil::unique_hfile hPipe,
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto dirty = GetDirtyArea();
|
||||
|
||||
// If we have 0 or 1 dirty pieces in the area, set as appropriate.
|
||||
Viewport dirtyView = dirty.empty() ? Viewport::Empty() : Viewport::FromInclusive(til::at(dirty, 0));
|
||||
|
||||
// If there's more than 1, union them all up with the 1 we already have.
|
||||
for (size_t i = 1; i < dirty.size(); ++i)
|
||||
const auto dirtyRect = GetDirtyRectInChars();
|
||||
const auto dirtyView = Viewport::FromInclusive(dirtyRect);
|
||||
if (!_resized && dirtyView == _lastViewport)
|
||||
{
|
||||
dirtyView = Viewport::Union(dirtyView, Viewport::FromInclusive(til::at(dirty, i)));
|
||||
// TODO: MSFT:21096414 - This is never actually hit. We set
|
||||
// _resized=true on every frame (see VtEngine::UpdateViewport).
|
||||
// Unfortunately, not always setting _resized is not a good enough
|
||||
// solution, see that work item for a description why.
|
||||
RETURN_IF_FAILED(_ClearScreen());
|
||||
_clearedAllThisFrame = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,10 +243,6 @@ XtermEngine::XtermEngine(_In_ wil::unique_hfile hPipe,
|
||||
_needToDisableCursor = true;
|
||||
hr = _CursorHome();
|
||||
}
|
||||
else if (_resized && _resizeQuirk)
|
||||
{
|
||||
hr = _CursorPosition(coord);
|
||||
}
|
||||
else if (coord.X == 0 && coord.Y == (_lastText.Y + 1))
|
||||
{
|
||||
// Down one line, at the start of the line.
|
||||
@@ -469,21 +466,9 @@ XtermEngine::XtermEngine(_In_ wil::unique_hfile hPipe,
|
||||
// - S_OK or suitable HRESULT error from either conversion or writing pipe.
|
||||
[[nodiscard]] HRESULT XtermEngine::WriteTerminalW(const std::wstring_view wstr) noexcept
|
||||
{
|
||||
RETURN_IF_FAILED(_fUseAsciiOnly ?
|
||||
VtEngine::_WriteTerminalAscii(wstr) :
|
||||
VtEngine::_WriteTerminalUtf8(wstr));
|
||||
// GH#4106, GH#2011 - WriteTerminalW is only ever called by the
|
||||
// StateMachine, when we've encountered a string we don't understand. When
|
||||
// this happens, we usually don't actually trigger another frame, but we
|
||||
// _do_ want this string to immediately be sent to the terminal. Since we
|
||||
// only flush our buffer on actual frames, this means that strings we've
|
||||
// decided to pass through would have gotten buffered here until the next
|
||||
// actual frame is triggered.
|
||||
//
|
||||
// To fix this, flush here, so this string is sent to the connected terminal
|
||||
// application.
|
||||
|
||||
return _Flush();
|
||||
return _fUseAsciiOnly ?
|
||||
VtEngine::_WriteTerminalAscii(wstr) :
|
||||
VtEngine::_WriteTerminalUtf8(wstr);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
||||
@@ -144,11 +144,6 @@ using namespace Microsoft::Console::Render;
|
||||
// - S_OK, else an appropriate HRESULT for failing to allocate or write.
|
||||
[[nodiscard]] HRESULT VtEngine::_InvalidCombine(const Viewport invalid) noexcept
|
||||
{
|
||||
if (_invalidMap)
|
||||
{
|
||||
_invalidMap.set(invalid.ToInclusive());
|
||||
}
|
||||
|
||||
if (!_fInvalidRectUsed)
|
||||
{
|
||||
_invalidRect = invalid;
|
||||
|
||||
@@ -17,9 +17,14 @@ using namespace Microsoft::Console::Types;
|
||||
// Return Value:
|
||||
// - The character dimensions of the current dirty area of the frame.
|
||||
// This is an Inclusive rect.
|
||||
std::vector<til::rectangle> VtEngine::GetDirtyArea()
|
||||
SMALL_RECT VtEngine::GetDirtyRectInChars()
|
||||
{
|
||||
return _invalidRects;
|
||||
SMALL_RECT dirty = _invalidRect.ToInclusive();
|
||||
if (dirty.Top < _virtualTop)
|
||||
{
|
||||
dirty.Top = _virtualTop;
|
||||
}
|
||||
return dirty;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
|
||||
@@ -27,7 +27,6 @@ using namespace Microsoft::Console::Types;
|
||||
|
||||
// If there's nothing to do, quick return
|
||||
bool somethingToDo = _fInvalidRectUsed ||
|
||||
!_invalidMap.empty() ||
|
||||
(_scrollDelta.X != 0 || _scrollDelta.Y != 0) ||
|
||||
_cursorMoved ||
|
||||
_titleChanged;
|
||||
@@ -35,14 +34,6 @@ using namespace Microsoft::Console::Types;
|
||||
_quickReturn = !somethingToDo;
|
||||
_trace.TraceStartPaint(_quickReturn, _fInvalidRectUsed, _invalidRect, _lastViewport, _scrollDelta, _cursorMoved);
|
||||
|
||||
if (somethingToDo)
|
||||
{
|
||||
for (auto it = _invalidMap.begin_runs(); it < _invalidMap.end_runs(); ++it)
|
||||
{
|
||||
_invalidRects.push_back(*it);
|
||||
}
|
||||
}
|
||||
|
||||
return _quickReturn ? S_FALSE : S_OK;
|
||||
}
|
||||
|
||||
@@ -59,8 +50,6 @@ using namespace Microsoft::Console::Types;
|
||||
{
|
||||
_trace.TraceEndPaint();
|
||||
|
||||
_invalidMap.reset_all();
|
||||
_invalidRects.clear();
|
||||
_invalidRect = Viewport::Empty();
|
||||
_fInvalidRectUsed = false;
|
||||
_scrollDelta = { 0 };
|
||||
|
||||
@@ -36,8 +36,6 @@ VtEngine::VtEngine(_In_ wil::unique_hfile pipe,
|
||||
_lastWasBold(false),
|
||||
_lastViewport(initialViewport),
|
||||
_invalidRect(Viewport::Empty()),
|
||||
_invalidMap(initialViewport.Dimensions()),
|
||||
_invalidRects(),
|
||||
_fInvalidRectUsed(false),
|
||||
_lastRealCursor({ 0 }),
|
||||
_lastText({ 0 }),
|
||||
@@ -189,66 +187,35 @@ VtEngine::VtEngine(_In_ wil::unique_hfile pipe,
|
||||
// - Helper for calling _Write with a string for formatting a sequence. Used
|
||||
// extensively by VtSequences.cpp
|
||||
// Arguments:
|
||||
// - pFormat: pointer to format string to write to the pipe
|
||||
// - pFormat: the pointer to the string to write to the pipe.
|
||||
// - ...: a va_list of args to format the string with.
|
||||
// Return Value:
|
||||
// - S_OK, E_INVALIDARG for a invalid format string, or suitable HRESULT error
|
||||
// from writing pipe.
|
||||
[[nodiscard]] HRESULT VtEngine::_WriteFormattedString(const std::string* const pFormat, ...) noexcept
|
||||
try
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, pFormat);
|
||||
|
||||
// NOTE: pFormat is a pointer because varargs refuses to operate with a ref in that position
|
||||
// NOTE: We're not using string_view because it doesn't guarantee null (which will be needed
|
||||
// later in the formatting method).
|
||||
|
||||
HRESULT hr = E_FAIL;
|
||||
va_list argList;
|
||||
va_start(argList, pFormat);
|
||||
|
||||
// We're going to hold onto our format string space across calls because
|
||||
// the VT renderer will be formatting a LOT of strings and alloc/freeing them
|
||||
// over and over is going to be way worse for perf than just holding some extra
|
||||
// memory for formatting purposes.
|
||||
// See _formatBuffer for its location.
|
||||
|
||||
// First, plow ahead using our pre-reserved string space.
|
||||
LPSTR destEnd = nullptr;
|
||||
size_t destRemaining = 0;
|
||||
if (SUCCEEDED(StringCchVPrintfExA(_formatBuffer.data(),
|
||||
_formatBuffer.size(),
|
||||
&destEnd,
|
||||
&destRemaining,
|
||||
STRSAFE_NO_TRUNCATION,
|
||||
pFormat->c_str(),
|
||||
args)))
|
||||
{
|
||||
return _Write({ _formatBuffer.data(), _formatBuffer.size() - destRemaining });
|
||||
}
|
||||
|
||||
// If we didn't succeed at filling/using the existing space, then
|
||||
// we're going to take the long way by counting the space required and resizing up to that
|
||||
// space and formatting.
|
||||
|
||||
const auto needed = _scprintf(pFormat->c_str(), args);
|
||||
int cchNeeded = _scprintf(pFormat->c_str(), argList);
|
||||
// -1 is the _scprintf error case https://msdn.microsoft.com/en-us/library/t32cf9tb.aspx
|
||||
if (needed > -1)
|
||||
if (cchNeeded > -1)
|
||||
{
|
||||
_formatBuffer.resize(static_cast<size_t>(needed) + 1);
|
||||
wistd::unique_ptr<char[]> psz = wil::make_unique_nothrow<char[]>(cchNeeded + 1);
|
||||
RETURN_IF_NULL_ALLOC(psz);
|
||||
|
||||
const auto written = _vsnprintf_s(_formatBuffer.data(), _formatBuffer.size(), needed, pFormat->c_str(), args);
|
||||
hr = _Write({ _formatBuffer.data(), gsl::narrow<size_t>(written) });
|
||||
int cchWritten = _vsnprintf_s(psz.get(), cchNeeded + 1, cchNeeded, pFormat->c_str(), argList);
|
||||
hr = _Write({ psz.get(), gsl::narrow<size_t>(cchWritten) });
|
||||
}
|
||||
else
|
||||
{
|
||||
hr = E_INVALIDARG;
|
||||
}
|
||||
|
||||
va_end(args);
|
||||
|
||||
va_end(argList);
|
||||
return hr;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
|
||||
// Method Description:
|
||||
// - This method will update the active font on the current device context
|
||||
@@ -295,14 +262,11 @@ CATCH_RETURN();
|
||||
|
||||
if ((oldView.Height() != newView.Height()) || (oldView.Width() != newView.Width()))
|
||||
{
|
||||
_invalidMap.resize(_lastViewport.Dimensions());
|
||||
|
||||
// Don't emit a resize event if we've requested it be suppressed
|
||||
if (!_suppressResizeRepaint)
|
||||
{
|
||||
hr = _ResizeWindow(newView.Width(), newView.Height());
|
||||
}
|
||||
_resized = true;
|
||||
}
|
||||
|
||||
// See MSFT:19408543
|
||||
@@ -314,50 +278,39 @@ CATCH_RETURN();
|
||||
// lead to the first _actual_ resize being suppressed.
|
||||
_suppressResizeRepaint = false;
|
||||
|
||||
if (_resizeQuirk)
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
// GH#3490 - When the viewport width changed, don't do anything extra here.
|
||||
// If the buffer had areas that were invalid due to the resize, then the
|
||||
// buffer will have triggered it's own invalidations for what it knows is
|
||||
// invalid. Previously, we'd invalidate everything if the width changed,
|
||||
// because we couldn't be sure if lines were reflowed.
|
||||
}
|
||||
else
|
||||
{
|
||||
if (SUCCEEDED(hr))
|
||||
// Viewport is smaller now - just update it all.
|
||||
if (oldView.Height() > newView.Height() || oldView.Width() > newView.Width())
|
||||
{
|
||||
// Viewport is smaller now - just update it all.
|
||||
if (oldView.Height() > newView.Height() || oldView.Width() > newView.Width())
|
||||
hr = InvalidateAll();
|
||||
}
|
||||
else
|
||||
{
|
||||
// At least one of the directions grew.
|
||||
// First try and add everything to the right of the old viewport,
|
||||
// then everything below where the old viewport ended.
|
||||
if (oldView.Width() < newView.Width())
|
||||
{
|
||||
hr = InvalidateAll();
|
||||
short left = oldView.RightExclusive();
|
||||
short top = 0;
|
||||
short right = newView.RightInclusive();
|
||||
short bottom = oldView.BottomInclusive();
|
||||
Viewport rightOfOldViewport = Viewport::FromInclusive({ left, top, right, bottom });
|
||||
hr = _InvalidCombine(rightOfOldViewport);
|
||||
}
|
||||
else
|
||||
if (SUCCEEDED(hr) && oldView.Height() < newView.Height())
|
||||
{
|
||||
// At least one of the directions grew.
|
||||
// First try and add everything to the right of the old viewport,
|
||||
// then everything below where the old viewport ended.
|
||||
if (oldView.Width() < newView.Width())
|
||||
{
|
||||
short left = oldView.RightExclusive();
|
||||
short top = 0;
|
||||
short right = newView.RightInclusive();
|
||||
short bottom = oldView.BottomInclusive();
|
||||
Viewport rightOfOldViewport = Viewport::FromInclusive({ left, top, right, bottom });
|
||||
hr = _InvalidCombine(rightOfOldViewport);
|
||||
}
|
||||
if (SUCCEEDED(hr) && oldView.Height() < newView.Height())
|
||||
{
|
||||
short left = 0;
|
||||
short top = oldView.BottomExclusive();
|
||||
short right = newView.RightInclusive();
|
||||
short bottom = newView.BottomInclusive();
|
||||
Viewport belowOldViewport = Viewport::FromInclusive({ left, top, right, bottom });
|
||||
hr = _InvalidCombine(belowOldViewport);
|
||||
}
|
||||
short left = 0;
|
||||
short top = oldView.BottomExclusive();
|
||||
short right = newView.RightInclusive();
|
||||
short bottom = newView.BottomInclusive();
|
||||
Viewport belowOldViewport = Viewport::FromInclusive({ left, top, right, bottom });
|
||||
hr = _InvalidCombine(belowOldViewport);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_resized = true;
|
||||
return hr;
|
||||
}
|
||||
|
||||
@@ -503,19 +456,3 @@ void VtEngine::EndResizeRequest()
|
||||
{
|
||||
_inResizeRequest = false;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Configure the renderer for the resize quirk. This changes the behavior of
|
||||
// conpty to _not_ InvalidateAll the entire viewport on a resize operation.
|
||||
// This is used by the Windows Terminal, because it is prepared to be
|
||||
// connected to a conpty, and handles it's own buffer specifically for a
|
||||
// conpty scenario.
|
||||
// - See also: GH#3490, #4354, #4741
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - true iff we were started with the `--resizeQuirk` flag enabled.
|
||||
void VtEngine::SetResizeQuirk(const bool resizeQuirk)
|
||||
{
|
||||
_resizeQuirk = resizeQuirk;
|
||||
}
|
||||
|
||||
@@ -228,20 +228,17 @@ void RenderTracing::TraceLastText(const COORD lastTextPos) const
|
||||
void RenderTracing::TraceMoveCursor(const COORD lastTextPos, const COORD cursor) const
|
||||
{
|
||||
#ifndef UNIT_TESTING
|
||||
if (TraceLoggingProviderEnabled(g_hConsoleVtRendererTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
|
||||
{
|
||||
const auto lastTextStr = _CoordToString(lastTextPos);
|
||||
const auto lastText = lastTextStr.c_str();
|
||||
const auto lastTextStr = _CoordToString(lastTextPos);
|
||||
const auto lastText = lastTextStr.c_str();
|
||||
|
||||
const auto cursorStr = _CoordToString(cursor);
|
||||
const auto cursorPos = cursorStr.c_str();
|
||||
const auto cursorStr = _CoordToString(cursor);
|
||||
const auto cursorPos = cursorStr.c_str();
|
||||
|
||||
TraceLoggingWrite(g_hConsoleVtRendererTraceProvider,
|
||||
"VtEngine_TraceMoveCursor",
|
||||
TraceLoggingString(lastText),
|
||||
TraceLoggingString(cursorPos),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
}
|
||||
TraceLoggingWrite(g_hConsoleVtRendererTraceProvider,
|
||||
"VtEngine_TraceMoveCursor",
|
||||
TraceLoggingString(lastText),
|
||||
TraceLoggingString(cursorPos),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
#else
|
||||
UNREFERENCED_PARAMETER(lastTextPos);
|
||||
UNREFERENCED_PARAMETER(cursor);
|
||||
@@ -251,14 +248,11 @@ void RenderTracing::TraceMoveCursor(const COORD lastTextPos, const COORD cursor)
|
||||
void RenderTracing::TraceWrapped() const
|
||||
{
|
||||
#ifndef UNIT_TESTING
|
||||
if (TraceLoggingProviderEnabled(g_hConsoleVtRendererTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
|
||||
{
|
||||
const auto* const msg = "Wrapped instead of \\r\\n";
|
||||
TraceLoggingWrite(g_hConsoleVtRendererTraceProvider,
|
||||
"VtEngine_TraceWrapped",
|
||||
TraceLoggingString(msg),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
}
|
||||
const auto* const msg = "Wrapped instead of \\r\\n";
|
||||
TraceLoggingWrite(g_hConsoleVtRendererTraceProvider,
|
||||
"VtEngine_TraceWrapped",
|
||||
TraceLoggingString(msg),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
#else
|
||||
#endif UNIT_TESTING
|
||||
}
|
||||
@@ -266,15 +260,12 @@ void RenderTracing::TraceWrapped() const
|
||||
void RenderTracing::TracePaintCursor(const COORD coordCursor) const
|
||||
{
|
||||
#ifndef UNIT_TESTING
|
||||
if (TraceLoggingProviderEnabled(g_hConsoleVtRendererTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
|
||||
{
|
||||
const auto cursorPosString = _CoordToString(coordCursor);
|
||||
const auto cursorPos = cursorPosString.c_str();
|
||||
TraceLoggingWrite(g_hConsoleVtRendererTraceProvider,
|
||||
"VtEngine_TracePaintCursor",
|
||||
TraceLoggingString(cursorPos),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
}
|
||||
const auto cursorPosString = _CoordToString(coordCursor);
|
||||
const auto cursorPos = cursorPosString.c_str();
|
||||
TraceLoggingWrite(g_hConsoleVtRendererTraceProvider,
|
||||
"VtEngine_TracePaintCursor",
|
||||
TraceLoggingString(cursorPos),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
#else
|
||||
UNREFERENCED_PARAMETER(coordCursor);
|
||||
#endif UNIT_TESTING
|
||||
|
||||
@@ -24,8 +24,6 @@ Author(s):
|
||||
#include <string>
|
||||
#include <functional>
|
||||
|
||||
#include "til/bitmap.h"
|
||||
|
||||
// fwdecl unittest classes
|
||||
#ifdef UNIT_TESTING
|
||||
namespace TerminalCoreUnitTests
|
||||
@@ -91,7 +89,7 @@ namespace Microsoft::Console::Render
|
||||
_Out_ FontInfo& Font,
|
||||
const int iDpi) noexcept override;
|
||||
|
||||
std::vector<til::rectangle> GetDirtyArea() override;
|
||||
SMALL_RECT GetDirtyRectInChars() override;
|
||||
[[nodiscard]] HRESULT GetFontSize(_Out_ COORD* const pFontSize) noexcept override;
|
||||
[[nodiscard]] HRESULT IsGlyphWideByFont(const std::wstring_view glyph, _Out_ bool* const pResult) noexcept override;
|
||||
|
||||
@@ -108,22 +106,16 @@ namespace Microsoft::Console::Render
|
||||
void BeginResizeRequest();
|
||||
void EndResizeRequest();
|
||||
|
||||
void SetResizeQuirk(const bool resizeQuirk);
|
||||
|
||||
protected:
|
||||
wil::unique_hfile _hFile;
|
||||
std::string _buffer;
|
||||
|
||||
std::string _formatBuffer;
|
||||
|
||||
const Microsoft::Console::IDefaultColorProvider& _colorProvider;
|
||||
|
||||
COLORREF _LastFG;
|
||||
COLORREF _LastBG;
|
||||
bool _lastWasBold;
|
||||
|
||||
til::bitmap _invalidMap;
|
||||
std::vector<til::rectangle> _invalidRects;
|
||||
Microsoft::Console::Types::Viewport _lastViewport;
|
||||
Microsoft::Console::Types::Viewport _invalidRect;
|
||||
|
||||
@@ -157,8 +149,6 @@ namespace Microsoft::Console::Render
|
||||
|
||||
bool _delayedEolWrap{ false };
|
||||
|
||||
bool _resizeQuirk{ false };
|
||||
|
||||
[[nodiscard]] HRESULT _Write(std::string_view const str) noexcept;
|
||||
[[nodiscard]] HRESULT _WriteFormattedString(const std::string* const pFormat, ...) noexcept;
|
||||
[[nodiscard]] HRESULT _Flush() noexcept;
|
||||
|
||||
@@ -355,7 +355,7 @@ bool WddmConEngine::IsInitialized()
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
std::vector<til::rectangle> WddmConEngine::GetDirtyArea()
|
||||
SMALL_RECT WddmConEngine::GetDirtyRectInChars()
|
||||
{
|
||||
SMALL_RECT r;
|
||||
r.Bottom = _displayHeight > 0 ? (SHORT)(_displayHeight - 1) : 0;
|
||||
@@ -363,7 +363,7 @@ std::vector<til::rectangle> WddmConEngine::GetDirtyArea()
|
||||
r.Left = 0;
|
||||
r.Right = _displayWidth > 0 ? (SHORT)(_displayWidth - 1) : 0;
|
||||
|
||||
return { r };
|
||||
return r;
|
||||
}
|
||||
|
||||
RECT WddmConEngine::GetDisplaySize()
|
||||
|
||||
@@ -61,7 +61,7 @@ namespace Microsoft::Console::Render
|
||||
|
||||
[[nodiscard]] HRESULT GetProposedFont(const FontInfoDesired& fiFontInfoDesired, FontInfo& fiFontInfo, int const iDpi) noexcept override;
|
||||
|
||||
std::vector<til::rectangle> GetDirtyArea() override;
|
||||
SMALL_RECT GetDirtyRectInChars() override;
|
||||
[[nodiscard]] HRESULT GetFontSize(_Out_ COORD* const pFontSize) noexcept override;
|
||||
[[nodiscard]] HRESULT IsGlyphWideByFont(const std::wstring_view glyph, _Out_ bool* const pResult) noexcept override;
|
||||
|
||||
|
||||
@@ -39,7 +39,5 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
|
||||
virtual bool MoveCursor(const size_t row,
|
||||
const size_t col) = 0;
|
||||
|
||||
virtual bool IsVtInputEnabled() const = 0;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -203,14 +203,3 @@ bool InteractDispatch::MoveCursor(const size_t row, const size_t col)
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Checks if the InputBuffer is willing to accept VT Input directly
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return value:
|
||||
// - true if enabled (see IsInVirtualTerminalInputMode). false otherwise.
|
||||
bool InteractDispatch::IsVtInputEnabled() const
|
||||
{
|
||||
return _pConApi->PrivateIsVtInputEnabled();
|
||||
}
|
||||
|
||||
@@ -32,8 +32,6 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
const std::basic_string_view<size_t> parameters) override; // DTTERM_WindowManipulation
|
||||
bool MoveCursor(const size_t row, const size_t col) override;
|
||||
|
||||
bool IsVtInputEnabled() const override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<ConGetSet> _pConApi;
|
||||
};
|
||||
|
||||
@@ -3,14 +3,13 @@
|
||||
|
||||
#include "precomp.h"
|
||||
#include <windows.h>
|
||||
#include "terminalInput.hpp"
|
||||
#include "MouseInput.hpp"
|
||||
|
||||
using namespace Microsoft::Console::VirtualTerminal;
|
||||
|
||||
#ifdef BUILD_ONECORE_INTERACTIVITY
|
||||
#include "..\..\interactivity\inc\VtApiRedirection.hpp"
|
||||
#endif
|
||||
static const int s_MaxDefaultCoordinate = 94;
|
||||
|
||||
// This magic flag is "documented" at https://msdn.microsoft.com/en-us/library/windows/desktop/ms646301(v=vs.85).aspx
|
||||
// "If the high-order bit is 1, the key is down; otherwise, it is up."
|
||||
@@ -20,6 +19,13 @@ static constexpr short KeyPressed{ gsl::narrow_cast<short>(0x8000) };
|
||||
static constexpr std::wstring_view CursorUpSequence{ L"\x1b[A" };
|
||||
static constexpr std::wstring_view CursorDownSequence{ L"\x1b[B" };
|
||||
|
||||
MouseInput::MouseInput(const WriteInputEvents pfnWriteEvents) noexcept :
|
||||
_pfnWriteEvents(pfnWriteEvents),
|
||||
_lastPos{ -1, -1 },
|
||||
_lastButton{ 0 }
|
||||
{
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Determines if the input windows message code describes a button event
|
||||
// (left, middle, right button and any of up, down or double click)
|
||||
@@ -87,32 +93,6 @@ static constexpr bool _isButtonDown(const unsigned int button) noexcept
|
||||
return isButtonDown;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Retrieves which mouse button is currently pressed. This is needed because
|
||||
// MOUSEMOVE events do not also tell us if any mouse buttons are pressed during the move.
|
||||
// Parameters:
|
||||
// <none>
|
||||
// Return value:
|
||||
// - a button corresponding to any pressed mouse buttons, else WM_LBUTTONUP if none are pressed.
|
||||
unsigned int TerminalInput::s_GetPressedButton() noexcept
|
||||
{
|
||||
// TODO GH#4869: Have the caller pass the mouse button state into HandleMouse
|
||||
unsigned int button = WM_LBUTTONUP; // Will be treated as a release, or no button pressed.
|
||||
if (WI_IsFlagSet(GetKeyState(VK_LBUTTON), KeyPressed))
|
||||
{
|
||||
button = WM_LBUTTONDOWN;
|
||||
}
|
||||
else if (WI_IsFlagSet(GetKeyState(VK_MBUTTON), KeyPressed))
|
||||
{
|
||||
button = WM_MBUTTONDOWN;
|
||||
}
|
||||
else if (WI_IsFlagSet(GetKeyState(VK_RBUTTON), KeyPressed))
|
||||
{
|
||||
button = WM_RBUTTONDOWN;
|
||||
}
|
||||
return button;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - translates the input windows mouse message into its equivalent X11 encoding.
|
||||
// X Button Encoding:
|
||||
@@ -264,17 +244,6 @@ static constexpr short _encodeDefaultCoordinate(const short sCoordinateValue) no
|
||||
return sCoordinateValue + 32;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Relays if we are tracking mouse input
|
||||
// Parameters:
|
||||
// - <none>
|
||||
// Return value:
|
||||
// - true, if we are tracking mouse input. False, otherwise
|
||||
bool TerminalInput::IsTrackingMouseInput() const noexcept
|
||||
{
|
||||
return (_mouseInputState.trackingMode != TrackingMode::None);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Attempt to handle the given mouse coordinates and windows button as a VT-style mouse event.
|
||||
// If the event should be transmitted in the selected mouse mode, then we'll try and
|
||||
@@ -286,10 +255,10 @@ bool TerminalInput::IsTrackingMouseInput() const noexcept
|
||||
// - delta - the amount that the scroll wheel changed (should be 0 unless button is a WM_MOUSE*WHEEL)
|
||||
// Return value:
|
||||
// - true if the event was handled and we should stop event propagation to the default window handler.
|
||||
bool TerminalInput::HandleMouse(const COORD position,
|
||||
const unsigned int button,
|
||||
const short modifierKeyState,
|
||||
const short delta)
|
||||
bool MouseInput::HandleMouse(const COORD position,
|
||||
const unsigned int button,
|
||||
const short modifierKeyState,
|
||||
const short delta)
|
||||
{
|
||||
bool success = false;
|
||||
if (_ShouldSendAlternateScroll(button, delta))
|
||||
@@ -298,20 +267,20 @@ bool TerminalInput::HandleMouse(const COORD position,
|
||||
}
|
||||
else
|
||||
{
|
||||
success = (_mouseInputState.trackingMode != TrackingMode::None);
|
||||
success = (_trackingMode != TrackingMode::None);
|
||||
if (success)
|
||||
{
|
||||
// isHover is only true for WM_MOUSEMOVE events
|
||||
const bool isHover = _isHoverMsg(button);
|
||||
const bool isButton = _isButtonMsg(button);
|
||||
|
||||
const bool sameCoord = (position.X == _mouseInputState.lastPos.X) &&
|
||||
(position.Y == _mouseInputState.lastPos.Y) &&
|
||||
(_mouseInputState.lastButton == button);
|
||||
const bool sameCoord = (position.X == _lastPos.X) &&
|
||||
(position.Y == _lastPos.Y) &&
|
||||
(_lastButton == button);
|
||||
|
||||
// If we have a WM_MOUSEMOVE, we need to know if any of the mouse
|
||||
// buttons are actually pressed. If they are,
|
||||
// _GetPressedButton will return the first pressed mouse button.
|
||||
// s_GetPressedButton will return the first pressed mouse button.
|
||||
// If it returns WM_LBUTTONUP, then we can assume that the mouse
|
||||
// moved without a button being pressed.
|
||||
const unsigned int realButton = isHover ? s_GetPressedButton() : button;
|
||||
@@ -322,14 +291,13 @@ bool TerminalInput::HandleMouse(const COORD position,
|
||||
// In AnyEvent, all coord change hovers are sent
|
||||
const bool physicalButtonPressed = realButton != WM_LBUTTONUP;
|
||||
|
||||
success = (isButton && _mouseInputState.trackingMode != TrackingMode::None) ||
|
||||
(isHover && _mouseInputState.trackingMode == TrackingMode::ButtonEvent && ((!sameCoord) && (physicalButtonPressed))) ||
|
||||
(isHover && _mouseInputState.trackingMode == TrackingMode::AnyEvent && !sameCoord);
|
||||
|
||||
success = (isButton && _trackingMode != TrackingMode::None) ||
|
||||
(isHover && _trackingMode == TrackingMode::ButtonEvent && ((!sameCoord) && (physicalButtonPressed))) ||
|
||||
(isHover && _trackingMode == TrackingMode::AnyEvent && !sameCoord);
|
||||
if (success)
|
||||
{
|
||||
std::wstring sequence;
|
||||
switch (_mouseInputState.extendedMode)
|
||||
switch (_extendedMode)
|
||||
{
|
||||
case ExtendedMode::None:
|
||||
sequence = _GenerateDefaultSequence(position,
|
||||
@@ -370,11 +338,11 @@ bool TerminalInput::HandleMouse(const COORD position,
|
||||
_SendInputSequence(sequence);
|
||||
success = true;
|
||||
}
|
||||
if (_mouseInputState.trackingMode == TrackingMode::ButtonEvent || _mouseInputState.trackingMode == TrackingMode::AnyEvent)
|
||||
if (_trackingMode == TrackingMode::ButtonEvent || _trackingMode == TrackingMode::AnyEvent)
|
||||
{
|
||||
_mouseInputState.lastPos.X = position.X;
|
||||
_mouseInputState.lastPos.Y = position.Y;
|
||||
_mouseInputState.lastButton = button;
|
||||
_lastPos.X = position.X;
|
||||
_lastPos.Y = position.Y;
|
||||
_lastButton = button;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -393,17 +361,17 @@ bool TerminalInput::HandleMouse(const COORD position,
|
||||
// - delta - the amount that the scroll wheel changed (should be 0 unless button is a WM_MOUSE*WHEEL)
|
||||
// Return value:
|
||||
// - The generated sequence. Will be empty if we couldn't generate.
|
||||
std::wstring TerminalInput::_GenerateDefaultSequence(const COORD position,
|
||||
const unsigned int button,
|
||||
const bool isHover,
|
||||
const short modifierKeyState,
|
||||
const short delta)
|
||||
std::wstring MouseInput::_GenerateDefaultSequence(const COORD position,
|
||||
const unsigned int button,
|
||||
const bool isHover,
|
||||
const short modifierKeyState,
|
||||
const short delta)
|
||||
{
|
||||
// In the default, non-extended encoding scheme, coordinates above 94 shouldn't be supported,
|
||||
// because (95+32+1)=128, which is not an ASCII character.
|
||||
// There are more details in _GenerateUtf8Sequence, but basically, we can't put anything above x80 into the input
|
||||
// stream without bash.exe trying to convert it into utf8, and generating extra bytes in the process.
|
||||
if (position.X <= s_MaxDefaultCoordinate && position.Y <= s_MaxDefaultCoordinate)
|
||||
if (position.X <= MouseInput::s_MaxDefaultCoordinate && position.Y <= MouseInput::s_MaxDefaultCoordinate)
|
||||
{
|
||||
const COORD vtCoords = _winToVTCoord(position);
|
||||
const short encodedX = _encodeDefaultCoordinate(vtCoords.X);
|
||||
@@ -430,11 +398,11 @@ std::wstring TerminalInput::_GenerateDefaultSequence(const COORD position,
|
||||
// - delta - the amount that the scroll wheel changed (should be 0 unless button is a WM_MOUSE*WHEEL)
|
||||
// Return value:
|
||||
// - The generated sequence. Will be empty if we couldn't generate.
|
||||
std::wstring TerminalInput::_GenerateUtf8Sequence(const COORD position,
|
||||
const unsigned int button,
|
||||
const bool isHover,
|
||||
const short modifierKeyState,
|
||||
const short delta)
|
||||
std::wstring MouseInput::_GenerateUtf8Sequence(const COORD position,
|
||||
const unsigned int button,
|
||||
const bool isHover,
|
||||
const short modifierKeyState,
|
||||
const short delta)
|
||||
{
|
||||
// So we have some complications here.
|
||||
// The windows input stream is typically encoded as UTF16.
|
||||
@@ -481,12 +449,12 @@ std::wstring TerminalInput::_GenerateUtf8Sequence(const COORD position,
|
||||
// Return value:
|
||||
// - true if we were able to successfully generate a sequence.
|
||||
// On success, caller is responsible for delete[]ing *ppwchSequence.
|
||||
std::wstring TerminalInput::_GenerateSGRSequence(const COORD position,
|
||||
const unsigned int button,
|
||||
const bool isDown,
|
||||
const bool isHover,
|
||||
const short modifierKeyState,
|
||||
const short delta)
|
||||
std::wstring MouseInput::_GenerateSGRSequence(const COORD position,
|
||||
const unsigned int button,
|
||||
const bool isDown,
|
||||
const bool isHover,
|
||||
const short modifierKeyState,
|
||||
const short delta)
|
||||
{
|
||||
// Format for SGR events is:
|
||||
// "\x1b[<%d;%d;%d;%c", xButton, x+1, y+1, fButtonDown? 'M' : 'm'
|
||||
@@ -497,6 +465,166 @@ std::wstring TerminalInput::_GenerateSGRSequence(const COORD position,
|
||||
return format;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Either enables or disables UTF-8 extended mode encoding. This *should* cause
|
||||
// the coordinates of a mouse event to be encoded as a UTF-8 byte stream, however, because windows' input is
|
||||
// typically UTF-16 encoded, it emits a UTF-16 stream.
|
||||
// Does NOT enable or disable mouse mode by itself. This matches the behavior I found in Ubuntu terminals.
|
||||
// Parameters:
|
||||
// - enable - either enable or disable.
|
||||
// Return value:
|
||||
// <none>
|
||||
void MouseInput::SetUtf8ExtendedMode(const bool enable) noexcept
|
||||
{
|
||||
_extendedMode = enable ? ExtendedMode::Utf8 : ExtendedMode::None;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Either enables or disables SGR extended mode encoding. This causes the
|
||||
// coordinates of a mouse event to be emitted in a human readable format,
|
||||
// eg, x,y=203,504 -> "^[[<B;203;504M". This way, applications don't need to worry about character encoding.
|
||||
// Does NOT enable or disable mouse mode by itself. This matches the behavior I found in Ubuntu terminals.
|
||||
// Parameters:
|
||||
// - enable - either enable or disable.
|
||||
// Return value:
|
||||
// <none>
|
||||
void MouseInput::SetSGRExtendedMode(const bool enable) noexcept
|
||||
{
|
||||
_extendedMode = enable ? ExtendedMode::Sgr : ExtendedMode::None;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Either enables or disables mouse mode handling. Leaves the extended mode alone,
|
||||
// so if we disable then re-enable mouse mode without toggling an extended mode, the mode will persist.
|
||||
// Parameters:
|
||||
// - enable - either enable or disable.
|
||||
// Return value:
|
||||
// <none>
|
||||
void MouseInput::EnableDefaultTracking(const bool enable) noexcept
|
||||
{
|
||||
_trackingMode = enable ? TrackingMode::Default : TrackingMode::None;
|
||||
_lastPos = { -1, -1 }; // Clear out the last saved mouse position & button.
|
||||
_lastButton = 0;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Either enables or disables ButtonEvent mouse handling. Button Event mode
|
||||
// sends additional sequences when a button is pressed and the mouse changes character cells.
|
||||
// Leaves the extended mode alone, so if we disable then re-enable mouse mode
|
||||
// without toggling an extended mode, the mode will persist.
|
||||
// Parameters:
|
||||
// - enable - either enable or disable.
|
||||
// Return value:
|
||||
// <none>
|
||||
void MouseInput::EnableButtonEventTracking(const bool enable) noexcept
|
||||
{
|
||||
_trackingMode = enable ? TrackingMode::ButtonEvent : TrackingMode::None;
|
||||
_lastPos = { -1, -1 }; // Clear out the last saved mouse position & button.
|
||||
_lastButton = 0;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Either enables or disables AnyEvent mouse handling. Any Event mode sends sequences
|
||||
// for any and every mouse event, regardless if a button is pressed or not.
|
||||
// Leaves the extended mode alone, so if we disable then re-enable mouse mode
|
||||
// without toggling an extended mode, the mode will persist.
|
||||
// Parameters:
|
||||
// - enable - either enable or disable.
|
||||
// Return value:
|
||||
// <none>
|
||||
void MouseInput::EnableAnyEventTracking(const bool enable) noexcept
|
||||
{
|
||||
_trackingMode = enable ? TrackingMode::AnyEvent : TrackingMode::None;
|
||||
_lastPos = { -1, -1 }; // Clear out the last saved mouse position & button.
|
||||
_lastButton = 0;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Sends the given sequence into the input callback specified by _pfnWriteEvents.
|
||||
// Typically, this inserts the characters into the input buffer as KeyDown KEY_EVENTs.
|
||||
// Parameters:
|
||||
// - sequence - sequence to send to _pfnWriteEvents
|
||||
// Return value:
|
||||
// <none>
|
||||
void MouseInput::_SendInputSequence(const std::wstring_view sequence) const noexcept
|
||||
{
|
||||
if (!sequence.empty())
|
||||
{
|
||||
std::deque<std::unique_ptr<IInputEvent>> events;
|
||||
try
|
||||
{
|
||||
for (const auto& wch : sequence)
|
||||
{
|
||||
events.push_back(std::make_unique<KeyEvent>(true, 1ui16, 0ui16, 0ui16, wch, 0));
|
||||
}
|
||||
|
||||
_pfnWriteEvents(events);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_HR(wil::ResultFromCaughtException());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Retrieves which mouse button is currently pressed. This is needed because
|
||||
// MOUSEMOVE events do not also tell us if any mouse buttons are pressed during the move.
|
||||
// Parameters:
|
||||
// <none>
|
||||
// Return value:
|
||||
// - a button corresponding to any pressed mouse buttons, else WM_LBUTTONUP if none are pressed.
|
||||
unsigned int MouseInput::s_GetPressedButton() noexcept
|
||||
{
|
||||
unsigned int button = WM_LBUTTONUP; // Will be treated as a release, or no button pressed.
|
||||
if (WI_IsFlagSet(GetKeyState(VK_LBUTTON), KeyPressed))
|
||||
{
|
||||
button = WM_LBUTTONDOWN;
|
||||
}
|
||||
else if (WI_IsFlagSet(GetKeyState(VK_MBUTTON), KeyPressed))
|
||||
{
|
||||
button = WM_MBUTTONDOWN;
|
||||
}
|
||||
else if (WI_IsFlagSet(GetKeyState(VK_RBUTTON), KeyPressed))
|
||||
{
|
||||
button = WM_RBUTTONDOWN;
|
||||
}
|
||||
return button;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Enables alternate scroll mode. This sends Cursor Up/down sequences when in the alternate buffer
|
||||
// Parameters:
|
||||
// - enable - either enable or disable.
|
||||
// Return value:
|
||||
// <none>
|
||||
void MouseInput::EnableAlternateScroll(const bool enable) noexcept
|
||||
{
|
||||
_alternateScroll = enable;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Notify the MouseInput handler that the screen buffer has been swapped to the alternate buffer
|
||||
// Parameters:
|
||||
// <none>
|
||||
// Return value:
|
||||
// <none>
|
||||
void MouseInput::UseAlternateScreenBuffer() noexcept
|
||||
{
|
||||
_inAlternateBuffer = true;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Notify the MouseInput handler that the screen buffer has been swapped to the alternate buffer
|
||||
// Parameters:
|
||||
// <none>
|
||||
// Return value:
|
||||
// <none>
|
||||
void MouseInput::UseMainScreenBuffer() noexcept
|
||||
{
|
||||
_inAlternateBuffer = false;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Returns true if we should translate the input event (button, sScrollDelta)
|
||||
// into an alternate scroll event instead of the default scroll event,
|
||||
@@ -506,10 +634,10 @@ std::wstring TerminalInput::_GenerateSGRSequence(const COORD position,
|
||||
// - delta: The scroll wheel delta of the input event
|
||||
// Return value:
|
||||
// True iff the alternate buffer is active and alternate scroll mode is enabled and the event is a mouse wheel event.
|
||||
bool TerminalInput::_ShouldSendAlternateScroll(const unsigned int button, const short delta) const noexcept
|
||||
bool MouseInput::_ShouldSendAlternateScroll(const unsigned int button, const short delta) const noexcept
|
||||
{
|
||||
return _mouseInputState.inAlternateBuffer &&
|
||||
_mouseInputState.alternateScroll &&
|
||||
return _inAlternateBuffer &&
|
||||
_alternateScroll &&
|
||||
(button == WM_MOUSEWHEEL || button == WM_MOUSEHWHEEL) && delta != 0;
|
||||
}
|
||||
|
||||
@@ -519,7 +647,7 @@ bool TerminalInput::_ShouldSendAlternateScroll(const unsigned int button, const
|
||||
// - delta: The scroll wheel delta of the input event
|
||||
// Return value:
|
||||
// True iff the input sequence was sent successfully.
|
||||
bool TerminalInput::_SendAlternateScroll(const short delta) const noexcept
|
||||
bool MouseInput::_SendAlternateScroll(const short delta) const noexcept
|
||||
{
|
||||
if (delta > 0)
|
||||
{
|
||||
100
src/terminal/adapter/MouseInput.hpp
Normal file
100
src/terminal/adapter/MouseInput.hpp
Normal file
@@ -0,0 +1,100 @@
|
||||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- MouseInput.hpp
|
||||
|
||||
Abstract:
|
||||
- This serves as an adapter between mouse input from a user and the virtual terminal sequences that are
|
||||
typically emitted by an xterm-compatible console.
|
||||
|
||||
Author(s):
|
||||
- Mike Griese (migrie) 01-Aug-2016
|
||||
--*/
|
||||
#pragma once
|
||||
|
||||
#include "../../types/inc/IInputEvent.hpp"
|
||||
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
|
||||
namespace Microsoft::Console::VirtualTerminal
|
||||
{
|
||||
typedef void (*WriteInputEvents)(_Inout_ std::deque<std::unique_ptr<IInputEvent>>& events);
|
||||
|
||||
class MouseInput sealed
|
||||
{
|
||||
public:
|
||||
MouseInput(const WriteInputEvents pfnWriteEvents) noexcept;
|
||||
|
||||
bool HandleMouse(const COORD position,
|
||||
const unsigned int button,
|
||||
const short modifierKeyState,
|
||||
const short delta);
|
||||
|
||||
void SetUtf8ExtendedMode(const bool enable) noexcept;
|
||||
void SetSGRExtendedMode(const bool enable) noexcept;
|
||||
|
||||
void EnableDefaultTracking(const bool enable) noexcept;
|
||||
void EnableButtonEventTracking(const bool enable) noexcept;
|
||||
void EnableAnyEventTracking(const bool enable) noexcept;
|
||||
|
||||
void EnableAlternateScroll(const bool enable) noexcept;
|
||||
void UseAlternateScreenBuffer() noexcept;
|
||||
void UseMainScreenBuffer() noexcept;
|
||||
|
||||
enum class ExtendedMode : unsigned int
|
||||
{
|
||||
None,
|
||||
Utf8,
|
||||
Sgr,
|
||||
Urxvt
|
||||
};
|
||||
|
||||
enum class TrackingMode : unsigned int
|
||||
{
|
||||
None,
|
||||
Default,
|
||||
ButtonEvent,
|
||||
AnyEvent
|
||||
};
|
||||
|
||||
private:
|
||||
static const int s_MaxDefaultCoordinate = 94;
|
||||
|
||||
WriteInputEvents _pfnWriteEvents;
|
||||
|
||||
ExtendedMode _extendedMode = ExtendedMode::None;
|
||||
TrackingMode _trackingMode = TrackingMode::None;
|
||||
|
||||
bool _alternateScroll = false;
|
||||
bool _inAlternateBuffer = false;
|
||||
|
||||
COORD _lastPos;
|
||||
unsigned int _lastButton;
|
||||
|
||||
void _SendInputSequence(const std::wstring_view sequence) const noexcept;
|
||||
static std::wstring _GenerateDefaultSequence(const COORD position,
|
||||
const unsigned int button,
|
||||
const bool isHover,
|
||||
const short modifierKeyState,
|
||||
const short delta);
|
||||
static std::wstring _GenerateUtf8Sequence(const COORD position,
|
||||
const unsigned int button,
|
||||
const bool isHover,
|
||||
const short modifierKeyState,
|
||||
const short delta);
|
||||
static std::wstring _GenerateSGRSequence(const COORD position,
|
||||
const unsigned int button,
|
||||
const bool isDown,
|
||||
const bool isHover,
|
||||
const short modifierKeyState,
|
||||
const short delta);
|
||||
|
||||
bool _ShouldSendAlternateScroll(const unsigned int button, const short delta) const noexcept;
|
||||
bool _SendAlternateScroll(const short delta) const noexcept;
|
||||
|
||||
static unsigned int s_GetPressedButton() noexcept;
|
||||
};
|
||||
}
|
||||
@@ -583,7 +583,8 @@ bool AdaptDispatch::EraseInDisplay(const DispatchTypes::EraseType eraseType)
|
||||
// make the state machine propogate this ED sequence to the connected
|
||||
// terminal application. While we're in conpty mode, we don't really
|
||||
// have a scrollback, but the attached terminal might.
|
||||
const bool isPty = _pConApi->IsConsolePty();
|
||||
bool isPty = false;
|
||||
_pConApi->IsConsolePty(isPty);
|
||||
return eraseScrollbackResult && (!isPty);
|
||||
}
|
||||
else if (eraseType == DispatchTypes::EraseType::All)
|
||||
@@ -1038,19 +1039,7 @@ bool AdaptDispatch::ResetPrivateModes(const std::basic_string_view<DispatchTypes
|
||||
// - True if handled successfully. False otherwise.
|
||||
bool AdaptDispatch::SetKeypadMode(const bool fApplicationMode)
|
||||
{
|
||||
bool success = true;
|
||||
success = _pConApi->PrivateSetKeypadMode(fApplicationMode);
|
||||
|
||||
// If we're a conpty, AND WE'RE IN VT INPUT MODE, always return false
|
||||
// The VT Input mode check is to work around ssh.exe v7.7, which uses VT
|
||||
// output, but not Input. Once the conpty supports these types of input,
|
||||
// this check can be removed. See GH#4911
|
||||
if (_pConApi->IsConsolePty() && _pConApi->PrivateIsVtInputEnabled())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return success;
|
||||
return _pConApi->PrivateSetKeypadMode(fApplicationMode);
|
||||
}
|
||||
|
||||
// - DECCKM - Sets the cursor keys input mode to either Application mode or Normal mode (true, false respectively)
|
||||
@@ -1060,19 +1049,7 @@ bool AdaptDispatch::SetKeypadMode(const bool fApplicationMode)
|
||||
// - True if handled successfully. False otherwise.
|
||||
bool AdaptDispatch::SetCursorKeysMode(const bool applicationMode)
|
||||
{
|
||||
bool success = true;
|
||||
success = _pConApi->PrivateSetCursorKeysMode(applicationMode);
|
||||
|
||||
// If we're a conpty, AND WE'RE IN VT INPUT MODE, always return false
|
||||
// The VT Input mode check is to work around ssh.exe v7.7, which uses VT
|
||||
// output, but not Input. Once the conpty supports these types of input,
|
||||
// this check can be removed. See GH#4911
|
||||
if (_pConApi->IsConsolePty() && _pConApi->PrivateIsVtInputEnabled())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return success;
|
||||
return _pConApi->PrivateSetCursorKeysMode(applicationMode);
|
||||
}
|
||||
|
||||
// - att610 - Enables or disables the cursor blinking.
|
||||
@@ -1462,8 +1439,6 @@ bool AdaptDispatch::DesignateCharset(const wchar_t wchCharset) noexcept
|
||||
// True if handled successfully. False otherwise.
|
||||
bool AdaptDispatch::SoftReset()
|
||||
{
|
||||
const bool isPty = _pConApi->IsConsolePty();
|
||||
|
||||
bool success = CursorVisibility(true); // Cursor enabled.
|
||||
if (success)
|
||||
{
|
||||
@@ -1477,15 +1452,11 @@ bool AdaptDispatch::SoftReset()
|
||||
{
|
||||
success = SetCursorKeysMode(false); // Normal characters.
|
||||
}
|
||||
// SetCursorKeysMode will return false if we're in conpty mode, as to
|
||||
// trigger a passthrough. If that's the case, just power through here.
|
||||
if (success || isPty)
|
||||
if (success)
|
||||
{
|
||||
success = SetKeypadMode(false); // Numeric characters.
|
||||
}
|
||||
// SetKeypadMode will return false if we're in conpty mode, as to trigger a
|
||||
// passthrough. If that's the case, just power through here.
|
||||
if (success || isPty)
|
||||
if (success)
|
||||
{
|
||||
// Top margin = 1; bottom margin = page length.
|
||||
success = _DoSetTopBottomScrollingMargins(0, 0);
|
||||
@@ -1565,7 +1536,9 @@ bool AdaptDispatch::HardReset()
|
||||
// make the state machine propogate this RIS sequence to the connected
|
||||
// terminal application. We've reset our state, but the connected terminal
|
||||
// might need to do more.
|
||||
if (_pConApi->IsConsolePty())
|
||||
bool isPty = false;
|
||||
_pConApi->IsConsolePty(isPty);
|
||||
if (isPty)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -1715,19 +1688,7 @@ bool AdaptDispatch::EnableDECCOLMSupport(const bool enabled) noexcept
|
||||
// True if handled successfully. False otherwise.
|
||||
bool AdaptDispatch::EnableVT200MouseMode(const bool enabled)
|
||||
{
|
||||
bool success = true;
|
||||
success = _pConApi->PrivateEnableVT200MouseMode(enabled);
|
||||
|
||||
// If we're a conpty, AND WE'RE IN VT INPUT MODE, always return false
|
||||
// The VT Input mode check is to work around ssh.exe v7.7, which uses VT
|
||||
// output, but not Input. Once the conpty supports these types of input,
|
||||
// this check can be removed. See GH#4911
|
||||
if (_pConApi->IsConsolePty() && _pConApi->PrivateIsVtInputEnabled())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return success;
|
||||
return _pConApi->PrivateEnableVT200MouseMode(enabled);
|
||||
}
|
||||
|
||||
//Routine Description:
|
||||
@@ -1739,19 +1700,7 @@ bool AdaptDispatch::EnableVT200MouseMode(const bool enabled)
|
||||
// True if handled successfully. False otherwise.
|
||||
bool AdaptDispatch::EnableUTF8ExtendedMouseMode(const bool enabled)
|
||||
{
|
||||
bool success = true;
|
||||
success = _pConApi->PrivateEnableUTF8ExtendedMouseMode(enabled);
|
||||
|
||||
// If we're a conpty, AND WE'RE IN VT INPUT MODE, always return false
|
||||
// The VT Input mode check is to work around ssh.exe v7.7, which uses VT
|
||||
// output, but not Input. Once the conpty supports these types of input,
|
||||
// this check can be removed. See GH#4911
|
||||
if (_pConApi->IsConsolePty() && _pConApi->PrivateIsVtInputEnabled())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return success;
|
||||
return _pConApi->PrivateEnableUTF8ExtendedMouseMode(enabled);
|
||||
}
|
||||
|
||||
//Routine Description:
|
||||
@@ -1763,19 +1712,7 @@ bool AdaptDispatch::EnableUTF8ExtendedMouseMode(const bool enabled)
|
||||
// True if handled successfully. False otherwise.
|
||||
bool AdaptDispatch::EnableSGRExtendedMouseMode(const bool enabled)
|
||||
{
|
||||
bool success = true;
|
||||
success = _pConApi->PrivateEnableSGRExtendedMouseMode(enabled);
|
||||
|
||||
// If we're a conpty, AND WE'RE IN VT INPUT MODE, always return false
|
||||
// The VT Input mode check is to work around ssh.exe v7.7, which uses VT
|
||||
// output, but not Input. Once the conpty supports these types of input,
|
||||
// this check can be removed. See GH#4911
|
||||
if (_pConApi->IsConsolePty() && _pConApi->PrivateIsVtInputEnabled())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return success;
|
||||
return _pConApi->PrivateEnableSGRExtendedMouseMode(enabled);
|
||||
}
|
||||
|
||||
//Routine Description:
|
||||
@@ -1786,19 +1723,7 @@ bool AdaptDispatch::EnableSGRExtendedMouseMode(const bool enabled)
|
||||
// True if handled successfully. False otherwise.
|
||||
bool AdaptDispatch::EnableButtonEventMouseMode(const bool enabled)
|
||||
{
|
||||
bool success = true;
|
||||
success = _pConApi->PrivateEnableButtonEventMouseMode(enabled);
|
||||
|
||||
// If we're a conpty, AND WE'RE IN VT INPUT MODE, always return false
|
||||
// The VT Input mode check is to work around ssh.exe v7.7, which uses VT
|
||||
// output, but not Input. Once the conpty supports these types of input,
|
||||
// this check can be removed. See GH#4911
|
||||
if (_pConApi->IsConsolePty() && _pConApi->PrivateIsVtInputEnabled())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return success;
|
||||
return _pConApi->PrivateEnableButtonEventMouseMode(enabled);
|
||||
}
|
||||
|
||||
//Routine Description:
|
||||
@@ -1810,19 +1735,7 @@ bool AdaptDispatch::EnableButtonEventMouseMode(const bool enabled)
|
||||
// True if handled successfully. False otherwise.
|
||||
bool AdaptDispatch::EnableAnyEventMouseMode(const bool enabled)
|
||||
{
|
||||
bool success = true;
|
||||
success = _pConApi->PrivateEnableAnyEventMouseMode(enabled);
|
||||
|
||||
// If we're a conpty, AND WE'RE IN VT INPUT MODE, always return false
|
||||
// The VT Input mode check is to work around ssh.exe v7.7, which uses VT
|
||||
// output, but not Input. Once the conpty supports these types of input,
|
||||
// this check can be removed. See GH#4911
|
||||
if (_pConApi->IsConsolePty() && _pConApi->PrivateIsVtInputEnabled())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return success;
|
||||
return _pConApi->PrivateEnableAnyEventMouseMode(enabled);
|
||||
}
|
||||
|
||||
//Routine Description:
|
||||
@@ -1834,19 +1747,7 @@ bool AdaptDispatch::EnableAnyEventMouseMode(const bool enabled)
|
||||
// True if handled successfully. False otherwise.
|
||||
bool AdaptDispatch::EnableAlternateScroll(const bool enabled)
|
||||
{
|
||||
bool success = true;
|
||||
success = _pConApi->PrivateEnableAlternateScroll(enabled);
|
||||
|
||||
// If we're a conpty, AND WE'RE IN VT INPUT MODE, always return false
|
||||
// The VT Input mode check is to work around ssh.exe v7.7, which uses VT
|
||||
// output, but not Input. Once the conpty supports these types of input,
|
||||
// this check can be removed. See GH#4911
|
||||
if (_pConApi->IsConsolePty() && _pConApi->PrivateIsVtInputEnabled())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return success;
|
||||
return _pConApi->PrivateEnableAlternateScroll(enabled);
|
||||
}
|
||||
|
||||
//Routine Description:
|
||||
@@ -1858,6 +1759,13 @@ bool AdaptDispatch::EnableAlternateScroll(const bool enabled)
|
||||
// True if handled successfully. False otherwise.
|
||||
bool AdaptDispatch::SetCursorStyle(const DispatchTypes::CursorStyle cursorStyle)
|
||||
{
|
||||
bool isPty = false;
|
||||
_pConApi->IsConsolePty(isPty);
|
||||
if (isPty)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
CursorType actualType = CursorType::Legacy;
|
||||
bool fEnableBlinking = false;
|
||||
|
||||
@@ -1898,13 +1806,6 @@ bool AdaptDispatch::SetCursorStyle(const DispatchTypes::CursorStyle cursorStyle)
|
||||
success = _pConApi->PrivateAllowCursorBlinking(fEnableBlinking);
|
||||
}
|
||||
|
||||
// If we're a conpty, always return false, so that this cursor state will be
|
||||
// sent to the connected terminal
|
||||
if (_pConApi->IsConsolePty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
@@ -1917,7 +1818,9 @@ bool AdaptDispatch::SetCursorStyle(const DispatchTypes::CursorStyle cursorStyle)
|
||||
// True if handled successfully. False otherwise.
|
||||
bool AdaptDispatch::SetCursorColor(const COLORREF cursorColor)
|
||||
{
|
||||
if (_pConApi->IsConsolePty())
|
||||
bool isPty = false;
|
||||
_pConApi->IsConsolePty(isPty);
|
||||
if (isPty)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -1945,7 +1848,9 @@ bool AdaptDispatch::SetColorTableEntry(const size_t tableIndex, const DWORD dwCo
|
||||
// value to the terminal. Still handle the sequence so apps that use
|
||||
// the API or VT to query the values of the color table still read the
|
||||
// correct color.
|
||||
if (_pConApi->IsConsolePty())
|
||||
bool isPty = false;
|
||||
_pConApi->IsConsolePty(isPty);
|
||||
if (isPty)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -1968,7 +1873,9 @@ bool Microsoft::Console::VirtualTerminal::AdaptDispatch::SetDefaultForeground(co
|
||||
// value to the terminal. Still handle the sequence so apps that use
|
||||
// the API or VT to query the values of the color table still read the
|
||||
// correct color.
|
||||
if (_pConApi->IsConsolePty())
|
||||
bool isPty = false;
|
||||
_pConApi->IsConsolePty(isPty);
|
||||
if (isPty)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -1991,7 +1898,9 @@ bool Microsoft::Console::VirtualTerminal::AdaptDispatch::SetDefaultBackground(co
|
||||
// value to the terminal. Still handle the sequence so apps that use
|
||||
// the API or VT to query the values of the color table still read the
|
||||
// correct color.
|
||||
if (_pConApi->IsConsolePty())
|
||||
bool isPty = false;
|
||||
_pConApi->IsConsolePty(isPty);
|
||||
if (isPty)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -35,8 +35,6 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
virtual bool SetConsoleCursorPosition(const COORD position) = 0;
|
||||
virtual bool SetConsoleTextAttribute(const WORD attr) = 0;
|
||||
|
||||
virtual bool PrivateIsVtInputEnabled() const = 0;
|
||||
|
||||
virtual bool PrivateSetLegacyAttributes(const WORD attr,
|
||||
const bool foreground,
|
||||
const bool background,
|
||||
@@ -98,7 +96,7 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
virtual bool GetConsoleOutputCP(unsigned int& codepage) = 0;
|
||||
|
||||
virtual bool PrivateSuppressResizeRepaint() = 0;
|
||||
virtual bool IsConsolePty() const = 0;
|
||||
virtual bool IsConsolePty(bool& isPty) const = 0;
|
||||
|
||||
virtual bool DeleteLines(const size_t count) = 0;
|
||||
virtual bool InsertLines(const size_t count) = 0;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
<ClCompile Include="..\DispatchCommon.cpp" />
|
||||
<ClCompile Include="..\InteractDispatch.cpp" />
|
||||
<ClCompile Include="..\adaptDispatchGraphics.cpp" />
|
||||
<ClCompile Include="..\MouseInput.cpp" />
|
||||
<ClCompile Include="..\telemetry.cpp" />
|
||||
<ClCompile Include="..\terminalOutput.cpp" />
|
||||
<ClCompile Include="..\tracing.cpp" />
|
||||
@@ -28,6 +29,7 @@
|
||||
<ClInclude Include="..\DispatchCommon.hpp" />
|
||||
<ClInclude Include="..\InteractDispatch.hpp" />
|
||||
<ClInclude Include="..\conGetSet.hpp" />
|
||||
<ClInclude Include="..\MouseInput.hpp" />
|
||||
<ClInclude Include="..\precomp.h" />
|
||||
<ClInclude Include="..\telemetry.hpp" />
|
||||
<ClInclude Include="..\terminalOutput.hpp" />
|
||||
|
||||
@@ -39,6 +39,9 @@
|
||||
<ClCompile Include="..\InteractDispatch.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\MouseInput.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\adaptDefaults.hpp">
|
||||
@@ -62,6 +65,9 @@
|
||||
<ClInclude Include="..\tracing.hpp">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\MouseInput.hpp">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\DispatchCommon.hpp">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
|
||||
@@ -34,6 +34,7 @@ SOURCES= \
|
||||
..\DispatchCommon.cpp \
|
||||
..\InteractDispatch.cpp \
|
||||
..\adaptDispatchGraphics.cpp \
|
||||
..\MouseInput.cpp \
|
||||
..\terminalOutput.cpp \
|
||||
..\telemetry.cpp \
|
||||
..\tracing.cpp \
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include <wextestclass.h>
|
||||
#include "..\..\inc\consoletaeftemplates.hpp"
|
||||
|
||||
#include "..\terminal\input\terminalInput.hpp"
|
||||
#include "MouseInput.hpp"
|
||||
|
||||
using namespace WEX::Common;
|
||||
using namespace WEX::Logging;
|
||||
@@ -280,7 +280,7 @@ public:
|
||||
|
||||
Log::Comment(L"Starting test...");
|
||||
|
||||
std::unique_ptr<TerminalInput> mouseInput = std::make_unique<TerminalInput>(s_MouseInputTestCallback);
|
||||
std::unique_ptr<MouseInput> mouseInput = std::make_unique<MouseInput>(s_MouseInputTestCallback);
|
||||
|
||||
unsigned int uiModifierKeystate = 0;
|
||||
VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"uiModifierKeystate", uiModifierKeystate));
|
||||
@@ -359,7 +359,7 @@ public:
|
||||
|
||||
Log::Comment(L"Starting test...");
|
||||
|
||||
std::unique_ptr<TerminalInput> mouseInput = std::make_unique<TerminalInput>(s_MouseInputTestCallback);
|
||||
std::unique_ptr<MouseInput> mouseInput = std::make_unique<MouseInput>(s_MouseInputTestCallback);
|
||||
|
||||
unsigned int uiModifierKeystate = 0;
|
||||
VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"uiModifierKeystate", uiModifierKeystate));
|
||||
@@ -442,7 +442,7 @@ public:
|
||||
|
||||
Log::Comment(L"Starting test...");
|
||||
|
||||
std::unique_ptr<TerminalInput> mouseInput = std::make_unique<TerminalInput>(s_MouseInputTestCallback);
|
||||
std::unique_ptr<MouseInput> mouseInput = std::make_unique<MouseInput>(s_MouseInputTestCallback);
|
||||
unsigned int uiModifierKeystate = 0;
|
||||
VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"uiModifierKeystate", uiModifierKeystate));
|
||||
short sModifierKeystate = (SHORT)uiModifierKeystate;
|
||||
@@ -520,7 +520,7 @@ public:
|
||||
|
||||
Log::Comment(L"Starting test...");
|
||||
|
||||
std::unique_ptr<TerminalInput> mouseInput = std::make_unique<TerminalInput>(s_MouseInputTestCallback);
|
||||
std::unique_ptr<MouseInput> mouseInput = std::make_unique<MouseInput>(s_MouseInputTestCallback);
|
||||
unsigned int uiModifierKeystate = 0;
|
||||
VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"uiModifierKeystate", uiModifierKeystate));
|
||||
short sModifierKeystate = (SHORT)uiModifierKeystate;
|
||||
|
||||
@@ -214,11 +214,6 @@ public:
|
||||
return _setConsoleTextAttributeResult;
|
||||
}
|
||||
|
||||
bool PrivateIsVtInputEnabled() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PrivateSetLegacyAttributes(const WORD attr, const bool foreground, const bool background, const bool meta) override
|
||||
{
|
||||
Log::Comment(L"PrivateSetLegacyAttributes MOCK called...");
|
||||
@@ -610,10 +605,14 @@ public:
|
||||
return _getConsoleOutputCPResult;
|
||||
}
|
||||
|
||||
bool IsConsolePty() const override
|
||||
bool IsConsolePty(bool& isPty) const override
|
||||
{
|
||||
Log::Comment(L"IsConsolePty MOCK called...");
|
||||
return _isPty;
|
||||
if (_isConsolePtyResult)
|
||||
{
|
||||
isPty = _isPty;
|
||||
}
|
||||
return _isConsolePtyResult;
|
||||
}
|
||||
|
||||
bool DeleteLines(const size_t /*count*/) override
|
||||
@@ -947,6 +946,7 @@ public:
|
||||
bool _setCursorColorResult = false;
|
||||
COLORREF _expectedCursorColor = 0;
|
||||
bool _getConsoleOutputCPResult = false;
|
||||
bool _isConsolePtyResult = false;
|
||||
bool _privateSetDefaultAttributesResult = false;
|
||||
bool _moveToBottomResult = false;
|
||||
|
||||
@@ -2265,6 +2265,7 @@ public:
|
||||
|
||||
// Test in pty mode - we should fail, but PrivateSetColorTableEntry should still be called
|
||||
_testGetSet->_isPty = true;
|
||||
_testGetSet->_isConsolePtyResult = true;
|
||||
|
||||
_testGetSet->_expectedColorTableIndex = 15; // Windows BRIGHT_WHITE
|
||||
VERIFY_IS_FALSE(_pDispatch.get()->SetColorTableEntry(15, testColor));
|
||||
|
||||
@@ -6,12 +6,10 @@
|
||||
<RootNamespace>adapter</RootNamespace>
|
||||
<ProjectName>TerminalInput</ProjectName>
|
||||
<TargetName>TerminalInput</TargetName>
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(SolutionDir)src\common.build.pre.props" />
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\mouseInput.cpp" />
|
||||
<ClCompile Include="..\mouseInputState.cpp" />
|
||||
<ClCompile Include="..\terminalInput.cpp" />
|
||||
<ClCompile Include="..\precomp.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
@@ -28,4 +26,4 @@
|
||||
</ItemGroup>
|
||||
<!-- Careful reordering these. Some default props (contained in these files) are order sensitive. -->
|
||||
<Import Project="$(SolutionDir)src\common.build.post.props" />
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
#include <windows.h>
|
||||
#include "terminalInput.hpp"
|
||||
|
||||
using namespace Microsoft::Console::VirtualTerminal;
|
||||
|
||||
// Routine Description:
|
||||
// - Either enables or disables UTF-8 extended mode encoding. This *should* cause
|
||||
// the coordinates of a mouse event to be encoded as a UTF-8 byte stream, however, because windows' input is
|
||||
// typically UTF-16 encoded, it emits a UTF-16 stream.
|
||||
// Does NOT enable or disable mouse mode by itself. This matches the behavior I found in Ubuntu terminals.
|
||||
// Parameters:
|
||||
// - enable - either enable or disable.
|
||||
// Return value:
|
||||
// <none>
|
||||
void TerminalInput::SetUtf8ExtendedMode(const bool enable) noexcept
|
||||
{
|
||||
_mouseInputState.extendedMode = enable ? ExtendedMode::Utf8 : ExtendedMode::None;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Either enables or disables SGR extended mode encoding. This causes the
|
||||
// coordinates of a mouse event to be emitted in a human readable format,
|
||||
// eg, x,y=203,504 -> "^[[<B;203;504M". This way, applications don't need to worry about character encoding.
|
||||
// Does NOT enable or disable mouse mode by itself. This matches the behavior I found in Ubuntu terminals.
|
||||
// Parameters:
|
||||
// - enable - either enable or disable.
|
||||
// Return value:
|
||||
// <none>
|
||||
void TerminalInput::SetSGRExtendedMode(const bool enable) noexcept
|
||||
{
|
||||
_mouseInputState.extendedMode = enable ? ExtendedMode::Sgr : ExtendedMode::None;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Either enables or disables mouse mode handling. Leaves the extended mode alone,
|
||||
// so if we disable then re-enable mouse mode without toggling an extended mode, the mode will persist.
|
||||
// Parameters:
|
||||
// - enable - either enable or disable.
|
||||
// Return value:
|
||||
// <none>
|
||||
void TerminalInput::EnableDefaultTracking(const bool enable) noexcept
|
||||
{
|
||||
_mouseInputState.trackingMode = enable ? TrackingMode::Default : TrackingMode::None;
|
||||
_mouseInputState.lastPos = { -1, -1 }; // Clear out the last saved mouse position & button.
|
||||
_mouseInputState.lastButton = 0;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Either enables or disables ButtonEvent mouse handling. Button Event mode
|
||||
// sends additional sequences when a button is pressed and the mouse changes character cells.
|
||||
// Leaves the extended mode alone, so if we disable then re-enable mouse mode
|
||||
// without toggling an extended mode, the mode will persist.
|
||||
// Parameters:
|
||||
// - enable - either enable or disable.
|
||||
// Return value:
|
||||
// <none>
|
||||
void TerminalInput::EnableButtonEventTracking(const bool enable) noexcept
|
||||
{
|
||||
_mouseInputState.trackingMode = enable ? TrackingMode::ButtonEvent : TrackingMode::None;
|
||||
_mouseInputState.lastPos = { -1, -1 }; // Clear out the last saved mouse position & button.
|
||||
_mouseInputState.lastButton = 0;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Either enables or disables AnyEvent mouse handling. Any Event mode sends sequences
|
||||
// for any and every mouse event, regardless if a button is pressed or not.
|
||||
// Leaves the extended mode alone, so if we disable then re-enable mouse mode
|
||||
// without toggling an extended mode, the mode will persist.
|
||||
// Parameters:
|
||||
// - enable - either enable or disable.
|
||||
// Return value:
|
||||
// <none>
|
||||
void TerminalInput::EnableAnyEventTracking(const bool enable) noexcept
|
||||
{
|
||||
_mouseInputState.trackingMode = enable ? TrackingMode::AnyEvent : TrackingMode::None;
|
||||
_mouseInputState.lastPos = { -1, -1 }; // Clear out the last saved mouse position & button.
|
||||
_mouseInputState.lastButton = 0;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Enables alternate scroll mode. This sends Cursor Up/down sequences when in the alternate buffer
|
||||
// Parameters:
|
||||
// - enable - either enable or disable.
|
||||
// Return value:
|
||||
// <none>
|
||||
void TerminalInput::EnableAlternateScroll(const bool enable) noexcept
|
||||
{
|
||||
_mouseInputState.alternateScroll = enable;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Notify the MouseInput handler that the screen buffer has been swapped to the alternate buffer
|
||||
// Parameters:
|
||||
// <none>
|
||||
// Return value:
|
||||
// <none>
|
||||
void TerminalInput::UseAlternateScreenBuffer() noexcept
|
||||
{
|
||||
_mouseInputState.inAlternateBuffer = true;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Notify the MouseInput handler that the screen buffer has been swapped to the alternate buffer
|
||||
// Parameters:
|
||||
// <none>
|
||||
// Return value:
|
||||
// <none>
|
||||
void TerminalInput::UseMainScreenBuffer() noexcept
|
||||
{
|
||||
_mouseInputState.inAlternateBuffer = false;
|
||||
}
|
||||
@@ -29,8 +29,6 @@ PRECOMPILED_INCLUDE = ..\precomp.h
|
||||
|
||||
SOURCES= \
|
||||
..\terminalInput.cpp \
|
||||
..\mouseInput.cpp \
|
||||
..\mouseInputState.cpp \
|
||||
|
||||
INCLUDES = \
|
||||
$(INCLUDES); \
|
||||
|
||||
@@ -367,7 +367,7 @@ bool TerminalInput::HandleKey(const IInputEvent* const pInEvent) const
|
||||
// On key presses, prepare to translate to VT compatible sequences
|
||||
if (pInEvent->EventType() == InputEventType::KeyEvent)
|
||||
{
|
||||
const auto senderFunc = [this](const std::wstring_view seq) noexcept { _SendInputSequence(seq); };
|
||||
const auto senderFunc = [this](const std::wstring_view seq) { _SendInputSequence(seq); };
|
||||
|
||||
auto keyEvent = *static_cast<const KeyEvent* const>(pInEvent);
|
||||
|
||||
@@ -543,7 +543,7 @@ void TerminalInput::_SendNullInputSequence(const DWORD controlKeyState) const
|
||||
}
|
||||
}
|
||||
|
||||
void TerminalInput::_SendInputSequence(const std::wstring_view sequence) const noexcept
|
||||
void TerminalInput::_SendInputSequence(const std::wstring_view sequence) const
|
||||
{
|
||||
if (!sequence.empty())
|
||||
{
|
||||
|
||||
@@ -38,30 +38,6 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
void ChangeKeypadMode(const bool applicationMode) noexcept;
|
||||
void ChangeCursorKeysMode(const bool applicationMode) noexcept;
|
||||
|
||||
#pragma region MouseInput
|
||||
// These methods are defined in mouseInput.cpp
|
||||
bool HandleMouse(const COORD position,
|
||||
const unsigned int button,
|
||||
const short modifierKeyState,
|
||||
const short delta);
|
||||
|
||||
bool IsTrackingMouseInput() const noexcept;
|
||||
#pragma endregion
|
||||
|
||||
#pragma region MouseInputState Management
|
||||
// These methods are defined in mouseInputState.cpp
|
||||
void SetUtf8ExtendedMode(const bool enable) noexcept;
|
||||
void SetSGRExtendedMode(const bool enable) noexcept;
|
||||
|
||||
void EnableDefaultTracking(const bool enable) noexcept;
|
||||
void EnableButtonEventTracking(const bool enable) noexcept;
|
||||
void EnableAnyEventTracking(const bool enable) noexcept;
|
||||
|
||||
void EnableAlternateScroll(const bool enable) noexcept;
|
||||
void UseAlternateScreenBuffer() noexcept;
|
||||
void UseMainScreenBuffer() noexcept;
|
||||
#pragma endregion
|
||||
|
||||
private:
|
||||
std::function<void(std::deque<std::unique_ptr<IInputEvent>>&)> _pfnWriteEvents;
|
||||
|
||||
@@ -72,62 +48,7 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
bool _cursorApplicationMode = false;
|
||||
|
||||
void _SendNullInputSequence(const DWORD dwControlKeyState) const;
|
||||
void _SendInputSequence(const std::wstring_view sequence) const noexcept;
|
||||
void _SendInputSequence(const std::wstring_view sequence) const;
|
||||
void _SendEscapedInputSequence(const wchar_t wch) const;
|
||||
|
||||
#pragma region MouseInputState Management
|
||||
// These methods are defined in mouseInputState.cpp
|
||||
enum class ExtendedMode : unsigned int
|
||||
{
|
||||
None,
|
||||
Utf8,
|
||||
Sgr,
|
||||
Urxvt
|
||||
};
|
||||
|
||||
enum class TrackingMode : unsigned int
|
||||
{
|
||||
None,
|
||||
Default,
|
||||
ButtonEvent,
|
||||
AnyEvent
|
||||
};
|
||||
|
||||
struct MouseInputState
|
||||
{
|
||||
ExtendedMode extendedMode{ ExtendedMode::None };
|
||||
TrackingMode trackingMode{ TrackingMode::None };
|
||||
bool alternateScroll{ false };
|
||||
bool inAlternateBuffer{ false };
|
||||
COORD lastPos{ -1, -1 };
|
||||
unsigned int lastButton{ 0 };
|
||||
};
|
||||
|
||||
MouseInputState _mouseInputState;
|
||||
#pragma endregion
|
||||
|
||||
#pragma region MouseInput
|
||||
static std::wstring _GenerateDefaultSequence(const COORD position,
|
||||
const unsigned int button,
|
||||
const bool isHover,
|
||||
const short modifierKeyState,
|
||||
const short delta);
|
||||
static std::wstring _GenerateUtf8Sequence(const COORD position,
|
||||
const unsigned int button,
|
||||
const bool isHover,
|
||||
const short modifierKeyState,
|
||||
const short delta);
|
||||
static std::wstring _GenerateSGRSequence(const COORD position,
|
||||
const unsigned int button,
|
||||
const bool isDown,
|
||||
const bool isHover,
|
||||
const short modifierKeyState,
|
||||
const short delta);
|
||||
|
||||
bool _ShouldSendAlternateScroll(const unsigned int button, const short delta) const noexcept;
|
||||
bool _SendAlternateScroll(const short delta) const noexcept;
|
||||
|
||||
static unsigned int s_GetPressedButton() noexcept;
|
||||
#pragma endregion
|
||||
};
|
||||
}
|
||||
|
||||
@@ -92,8 +92,7 @@ InputStateMachineEngine::InputStateMachineEngine(std::unique_ptr<IInteractDispat
|
||||
|
||||
InputStateMachineEngine::InputStateMachineEngine(std::unique_ptr<IInteractDispatch> pDispatch, const bool lookingForDSR) :
|
||||
_pDispatch(std::move(pDispatch)),
|
||||
_lookingForDSR(lookingForDSR),
|
||||
_pfnFlushToInputQueue(nullptr)
|
||||
_lookingForDSR(lookingForDSR)
|
||||
{
|
||||
THROW_HR_IF_NULL(E_INVALIDARG, _pDispatch.get());
|
||||
}
|
||||
@@ -257,27 +256,6 @@ bool InputStateMachineEngine::ActionPrintString(const std::wstring_view string)
|
||||
// - true iff we successfully dispatched the sequence.
|
||||
bool InputStateMachineEngine::ActionPassThroughString(const std::wstring_view string)
|
||||
{
|
||||
if (_pDispatch->IsVtInputEnabled())
|
||||
{
|
||||
// Synthesize string into key events that we'll write to the buffer
|
||||
// similar to TerminalInput::_SendInputSequence
|
||||
if (!string.empty())
|
||||
{
|
||||
try
|
||||
{
|
||||
std::deque<std::unique_ptr<IInputEvent>> inputEvents;
|
||||
for (const auto& wch : string)
|
||||
{
|
||||
inputEvents.push_back(std::make_unique<KeyEvent>(true, 1ui16, 0ui16, 0ui16, wch, 0));
|
||||
}
|
||||
return _pDispatch->WriteInput(inputEvents);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_HR(wil::ResultFromCaughtException());
|
||||
}
|
||||
}
|
||||
}
|
||||
return ActionPrintString(string);
|
||||
}
|
||||
|
||||
@@ -293,11 +271,6 @@ bool InputStateMachineEngine::ActionPassThroughString(const std::wstring_view st
|
||||
bool InputStateMachineEngine::ActionEscDispatch(const wchar_t wch,
|
||||
const std::basic_string_view<wchar_t> /*intermediates*/)
|
||||
{
|
||||
if (_pDispatch->IsVtInputEnabled() && _pfnFlushToInputQueue)
|
||||
{
|
||||
return _pfnFlushToInputQueue();
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
|
||||
// 0x7f is DEL, which we treat effectively the same as a ctrl character.
|
||||
@@ -336,11 +309,6 @@ bool InputStateMachineEngine::ActionCsiDispatch(const wchar_t wch,
|
||||
const std::basic_string_view<wchar_t> intermediates,
|
||||
const std::basic_string_view<size_t> parameters)
|
||||
{
|
||||
if (_pDispatch->IsVtInputEnabled() && _pfnFlushToInputQueue)
|
||||
{
|
||||
return _pfnFlushToInputQueue();
|
||||
}
|
||||
|
||||
DWORD modifierState = 0;
|
||||
short vkey = 0;
|
||||
unsigned int function = 0;
|
||||
@@ -472,11 +440,6 @@ bool InputStateMachineEngine::ActionCsiDispatch(const wchar_t wch,
|
||||
bool InputStateMachineEngine::ActionSs3Dispatch(const wchar_t wch,
|
||||
const std::basic_string_view<size_t> /*parameters*/)
|
||||
{
|
||||
if (_pDispatch->IsVtInputEnabled() && _pfnFlushToInputQueue)
|
||||
{
|
||||
return _pfnFlushToInputQueue();
|
||||
}
|
||||
|
||||
// Ss3 sequence keys aren't modified.
|
||||
// When F1-F4 *are* modified, they're sent as CSI sequences, not SS3's.
|
||||
const DWORD modifierState = 0;
|
||||
@@ -1080,22 +1043,6 @@ bool InputStateMachineEngine::DispatchIntermediatesFromEscape() const noexcept
|
||||
return true;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Sets us up for vt input passthrough.
|
||||
// We'll set a couple members, and if they aren't null, when we get a
|
||||
// sequence we don't understand, we'll pass it along to the app
|
||||
// instead of eating it ourselves.
|
||||
// Arguments:
|
||||
// - pfnFlushToInputQueue: This is a callback to the underlying state machine to
|
||||
// trigger it to call ActionPassThroughString with whatever sequence it's
|
||||
// currently processing.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void InputStateMachineEngine::SetFlushToInputQueueCallback(std::function<bool()> pfnFlushToInputQueue)
|
||||
{
|
||||
_pfnFlushToInputQueue = pfnFlushToInputQueue;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Retrieves the type of window manipulation operation from the parameter pool
|
||||
// stored during Param actions.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user