mirror of
https://github.com/microsoft/terminal.git
synced 2025-12-10 00:48:23 -06:00
A minor ConPTY refactoring: Goodbye VtEngine Edition (#17510)
The idea is that we can translate Console API calls directly to VT at least as well as the current VtEngine setup can. For instance, a call to `SetConsoleCursorPosition` clearly translates directly to a `CUP` escape sequence. Effectively, instead of translating output asynchronously in the renderer thread, we'll do it synchronously right during the Console API call. Most importantly, the this means that any VT output that an application generates will now be given to the terminal unmodified. Aside from reducing our project's complexity quite a bit and opening the path towards various interesting work like sixels, Device Control Strings, buffer snapshotting, synchronized updates, and more, it also improves performance for mixed text output like enwik8.txt in conhost to 1.3-2x and in Windows Terminal via ConPTY to roughly 20x. This adds support for overlapped IO, because now that output cannot be "skipped" anymore (VtEngine worked like a renderer after all) it's become crucial to block conhost as little as possible. ⚠️ Intentionally unresolved changes/quirks: * To force a delayed EOL wrap to wrap, `WriteCharsLegacy` emits a `\r\n` if necessary. This breaks text reflow on window resize. We cannot emit ` \r` the way readline does it, because this would overwrite the first column in the next row with a whitespace. The alternative is to read back the affected cell from the buffer and emit that character and its attributes followed by a `\r`. I chose to not do that, because buffer read-back is lossy (= UCS2). Unless the window is resized, the difference is unnoticeable and historically, conhost had no support for buffer reflow anyway. * If `ENABLE_VIRTUAL_TERMINAL_PROCESSING` is set while `DISABLE_NEWLINE_AUTO_RETURN` is reset, we'll blindly replace all LF with CRLF. This may hypothetically break DCS sequences, but it's the only way to do this without parsing the given VT string and thus the only way we can achieve passthrough mode in the future. * `ENABLE_WRAP_AT_EOL_OUTPUT` is translated to `DECAWM`. Between Windows XP and Windows 11 21H2, `ENABLE_WRAP_AT_EOL_OUTPUT` being reset would cause the cursor position to reset to wherever a write started, _if_ the write, including expanded control chars, was less than 100 characters long. If it was longer than that, the cursor position would end up in an effectively random position. After lengthy research I believe that this is a bug introduced in Windows XP and that the original intention was for this mode to be equivalent to `DECAWM`. This is compounded by MSDN's description (emphasis mine): > If this mode is disabled, the **last character** in the row is > overwritten with any subsequent characters. ⚠️ Unresolved issues/quirks: * Focus/Unfocus events are injected into the output stream without checking whether the VT output is currently in a ground state. This may break whatever VT sequence is currently ongoing. This is an existing issue. * `VtIo::Writer::WriteInfos` should properly verify the width of each individual character. * Using `SetConsoleActiveScreenBuffer` destroys surrogate pairs and extended (VT) attributes. It could be translated to VT pages in the long term. * Similarly, `ScrollConsoleScreenBuffer` results in the same and could be translated to `DECCRA` and `DECFRA` in the near term. This is important because otherwise `vim` output may loose its extended attributes during scrolling. * Reflowing a long line until it wraps results in the cooked read prompt to be misaligned vertically. * `SCREEN_INFORMATION::s_RemoveScreenBuffer` should trigger a buffer switch similar to `SetConsoleActiveScreenBuffer`. * Translation of `COMMON_LVB_GRID_HORIZONTAL` to `SGR 53` was dropped and may be reintroduced alongside `UNDERSCORE` = `SGR 4`. * Move the `OSC 0 ; P t BEL` sequence to `WriteWindowTitle` and swap the `BEL` with the `ST` (`ESC \`). * PowerShell on Windows 10 ships with PSReadLine 2.0.0-beta2 which emits SGR 37/40 instead of 39/49. This results in black spaces when typing and there's no good way to fix that. * A test is missing that ensures that `FillConsoleOutputCharacterW` results in a `CSI n J` during the PowerShell shim. * A test is missing that ensures that `PtySignal::ClearBuffer` does not result in any VT being generated. Closes #262 Closes #1173 Closes #3016 Closes #4129 Closes #5228 Closes #8698 Closes #12336 Closes #15014 Closes #15888 Closes #16461 Closes #16911 Closes #17151 Closes #17313
This commit is contained in:
parent
a8582978af
commit
450eec48de
3
.github/actions/spelling/allow/apis.txt
vendored
3
.github/actions/spelling/allow/apis.txt
vendored
@ -65,8 +65,8 @@ GETTEXTLENGTH
|
||||
Hashtable
|
||||
HIGHCONTRASTON
|
||||
HIGHCONTRASTW
|
||||
hinternet
|
||||
HIGHQUALITYSCALE
|
||||
hinternet
|
||||
HINTERNET
|
||||
hotkeys
|
||||
href
|
||||
@ -155,6 +155,7 @@ NOTIFYBYPOS
|
||||
NOTIFYICON
|
||||
NOTIFYICONDATA
|
||||
ntprivapi
|
||||
NTSYSCALLAPI
|
||||
numr
|
||||
oaidl
|
||||
ocidl
|
||||
|
||||
7
.github/actions/spelling/expect/alphabet.txt
vendored
7
.github/actions/spelling/expect/alphabet.txt
vendored
@ -1,20 +1,15 @@
|
||||
AAAAA
|
||||
AAAAAAAAAAAAA
|
||||
AAAAAABBBBBBCCC
|
||||
AAAAABBBBBBCCC
|
||||
abcd
|
||||
abcd
|
||||
ABCDEFGHIJ
|
||||
abcdefghijk
|
||||
ABCDEFGHIJKLMNO
|
||||
abcdefghijklmnop
|
||||
ABCDEFGHIJKLMNOPQRS
|
||||
ABCDEFGHIJKLMNOPQRST
|
||||
ABCG
|
||||
ABE
|
||||
abf
|
||||
BBBBB
|
||||
BBBBBBBB
|
||||
BBBBBCCC
|
||||
BBBBCCCCC
|
||||
BBGGRR
|
||||
@ -29,10 +24,8 @@ QQQQQQQQQQABCDEFGHIJKLMNOPQRSTQQQQQQQQQ
|
||||
QQQQQQQQQQABCDEFGHIJKLMNOPQRSTQQQQQQQQQQ
|
||||
QQQQQQQQQQABCDEFGHIJPQRST
|
||||
QQQQQQQQQQABCDEFGHIJPQRSTQQQQQQQQQQ
|
||||
qrstuvwxyz
|
||||
qwerty
|
||||
qwertyuiopasdfg
|
||||
YYYYYYYDDDDDDDDDDD
|
||||
ZAAZZ
|
||||
ZABBZ
|
||||
ZBAZZ
|
||||
|
||||
25
.github/actions/spelling/expect/expect.txt
vendored
25
.github/actions/spelling/expect/expect.txt
vendored
@ -17,7 +17,6 @@ ADDALIAS
|
||||
ADDREF
|
||||
ADDSTRING
|
||||
ADDTOOL
|
||||
AFew
|
||||
AFill
|
||||
AFX
|
||||
AHelper
|
||||
@ -66,7 +65,6 @@ ARRAYSIZE
|
||||
ARROWKEYS
|
||||
asan
|
||||
ASBSET
|
||||
asdfghjkl
|
||||
ASetting
|
||||
ASingle
|
||||
ASYNCDONTCARE
|
||||
@ -125,6 +123,7 @@ BKCOLOR
|
||||
BKGND
|
||||
Bksp
|
||||
Blt
|
||||
blu
|
||||
BLUESCROLL
|
||||
bmi
|
||||
BODGY
|
||||
@ -145,7 +144,6 @@ buffersize
|
||||
buflen
|
||||
buildtransitive
|
||||
buildsystems
|
||||
burriter
|
||||
BValue
|
||||
bytebuffer
|
||||
cac
|
||||
@ -210,7 +208,6 @@ cmw
|
||||
CNL
|
||||
cnn
|
||||
Codeflow
|
||||
codenav
|
||||
codepages
|
||||
codepath
|
||||
coinit
|
||||
@ -362,6 +359,7 @@ DBGFONTS
|
||||
DBGOUTPUT
|
||||
dbh
|
||||
dblclk
|
||||
Dcd
|
||||
DColor
|
||||
DCOLORVALUE
|
||||
dcommon
|
||||
@ -379,7 +377,7 @@ DECALN
|
||||
DECANM
|
||||
DECARM
|
||||
DECAUPSS
|
||||
DECAWM
|
||||
decawm
|
||||
DECBI
|
||||
DECBKM
|
||||
DECCARA
|
||||
@ -421,6 +419,7 @@ DECPCCM
|
||||
DECPCTERM
|
||||
DECPS
|
||||
DECRARA
|
||||
decrc
|
||||
DECRC
|
||||
DECREQTPARM
|
||||
DECRLM
|
||||
@ -436,6 +435,7 @@ DECRSPS
|
||||
decrst
|
||||
DECSACE
|
||||
DECSASD
|
||||
decsc
|
||||
DECSC
|
||||
DECSCA
|
||||
DECSCNM
|
||||
@ -475,7 +475,6 @@ DEFPUSHBUTTON
|
||||
defterm
|
||||
DELAYLOAD
|
||||
DELETEONRELEASE
|
||||
Delt
|
||||
depersist
|
||||
deprioritized
|
||||
deserializers
|
||||
@ -556,7 +555,6 @@ Efast
|
||||
efghijklmn
|
||||
EHsc
|
||||
EINS
|
||||
EJO
|
||||
ELEMENTNOTAVAILABLE
|
||||
EMPTYBOX
|
||||
enabledelayedexpansion
|
||||
@ -626,7 +624,6 @@ FINDDOWN
|
||||
FINDREGEX
|
||||
FINDSTRINGEXACT
|
||||
FINDUP
|
||||
FIter
|
||||
FITZPATRICK
|
||||
FIXEDFILEINFO
|
||||
Flg
|
||||
@ -723,6 +720,7 @@ GETWHEELSCROLLLINES
|
||||
Gfun
|
||||
gfx
|
||||
GGI
|
||||
GHgh
|
||||
GHIJK
|
||||
GHIJKL
|
||||
gitcheckin
|
||||
@ -948,7 +946,6 @@ LCONTROL
|
||||
LCTRL
|
||||
lcx
|
||||
LEFTALIGN
|
||||
libpopcnt
|
||||
libsancov
|
||||
libtickit
|
||||
licate
|
||||
@ -1046,7 +1043,6 @@ MAPBITMAP
|
||||
MAPVIRTUALKEY
|
||||
MAPVK
|
||||
MAXDIMENSTRING
|
||||
maxing
|
||||
MAXSHORT
|
||||
maxval
|
||||
maxversiontested
|
||||
@ -1506,7 +1502,6 @@ REGSTR
|
||||
RELBINPATH
|
||||
remoting
|
||||
renamer
|
||||
renderengine
|
||||
rendersize
|
||||
reparented
|
||||
reparenting
|
||||
@ -1847,7 +1842,6 @@ Trd
|
||||
TREX
|
||||
triaged
|
||||
triaging
|
||||
Tribool
|
||||
TRIMZEROHEADINGS
|
||||
trx
|
||||
tsa
|
||||
@ -1941,7 +1935,6 @@ uxtheme
|
||||
Vanara
|
||||
vararg
|
||||
vclib
|
||||
vcprintf
|
||||
vcxitems
|
||||
vectorize
|
||||
VERCTRL
|
||||
@ -1985,7 +1978,6 @@ vtio
|
||||
vtmode
|
||||
vtpipeterm
|
||||
vtpt
|
||||
vtrenderer
|
||||
VTRGB
|
||||
VTRGBTo
|
||||
vtseq
|
||||
@ -2007,6 +1999,7 @@ wcswidth
|
||||
wddm
|
||||
wddmcon
|
||||
WDDMCONSOLECONTEXT
|
||||
WDK
|
||||
wdm
|
||||
webpage
|
||||
websites
|
||||
@ -2074,7 +2067,6 @@ Winperf
|
||||
WInplace
|
||||
winres
|
||||
winrt
|
||||
wintelnet
|
||||
winternl
|
||||
winuser
|
||||
winuserp
|
||||
@ -2175,7 +2167,6 @@ XTWINOPS
|
||||
xunit
|
||||
xutr
|
||||
XVIRTUALSCREEN
|
||||
XWalk
|
||||
yact
|
||||
YCast
|
||||
YCENTER
|
||||
@ -2184,7 +2175,6 @@ YLimit
|
||||
YPan
|
||||
YSubstantial
|
||||
YVIRTUALSCREEN
|
||||
YWalk
|
||||
Zab
|
||||
zabcd
|
||||
Zabcdefghijklmn
|
||||
@ -2192,6 +2182,5 @@ Zabcdefghijklmnopqrstuvwxyz
|
||||
ZCmd
|
||||
ZCtrl
|
||||
ZWJs
|
||||
zxcvbnm
|
||||
ZYXWVU
|
||||
ZYXWVUTd
|
||||
|
||||
65
NOTICE.md
65
NOTICE.md
@ -84,71 +84,6 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
```
|
||||
|
||||
## kimwalisch/libpopcnt
|
||||
|
||||
**Source**: [https://github.com/kimwalisch/libpopcnt](https://github.com/kimwalisch/libpopcnt)
|
||||
|
||||
### License
|
||||
|
||||
```
|
||||
BSD 2-Clause License
|
||||
|
||||
Copyright (c) 2016 - 2019, Kim Walisch
|
||||
Copyright (c) 2016 - 2019, Wojciech Muła
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
```
|
||||
|
||||
## dynamic_bitset
|
||||
|
||||
**Source**: [https://github.com/pinam45/dynamic_bitset](https://github.com/pinam45/dynamic_bitset)
|
||||
|
||||
### License
|
||||
|
||||
```
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Maxime Pinard
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
```
|
||||
|
||||
## \{fmt\}
|
||||
|
||||
**Source**: [https://github.com/fmtlib/fmt](https://github.com/fmtlib/fmt)
|
||||
|
||||
@ -131,7 +131,6 @@ EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "InteractivityWin32", "src\interactivity\win32\lib\win32.LIB.vcxproj", "{06EC74CB-9A12-429C-B551-8532EC964726}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{1C959542-BAC2-4E55-9A6D-13251914CBB9} = {1C959542-BAC2-4E55-9A6D-13251914CBB9}
|
||||
{990F2657-8580-4828-943F-5DD657D11842} = {990F2657-8580-4828-943F-5DD657D11842}
|
||||
{AF0A096A-8B3A-4949-81EF-7DF8F0FEE91F} = {AF0A096A-8B3A-4949-81EF-7DF8F0FEE91F}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
@ -140,14 +139,9 @@ EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "InteractivityBase", "src\interactivity\base\lib\InteractivityBase.vcxproj", "{06EC74CB-9A12-429C-B551-8562EC964846}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Interactivity.Win32.Tests.Unit", "src\interactivity\win32\ut_interactivity_win32\Interactivity.Win32.UnitTests.vcxproj", "{D3B92829-26CB-411A-BDA2-7F5DA3D25DD4}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{990F2657-8580-4828-943F-5DD657D11842} = {990F2657-8580-4828-943F-5DD657D11842}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CloseTest", "src\tools\closetest\CloseTest.vcxproj", "{C7A6A5D9-60BE-4AEB-A5F6-AFE352F86CBB}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RendererVt", "src\renderer\vt\lib\vt.vcxproj", "{990F2657-8580-4828-943F-5DD657D11842}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "VtPipeTerm", "src\tools\vtpipeterm\VtPipeTerm.vcxproj", "{814DBDDE-894E-4327-A6E1-740504850098}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B} = {9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B}
|
||||
@ -157,8 +151,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ConEchoKey", "src\tools\ech
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Types", "src\types\lib\types.vcxproj", "{18D09A24-8240-42D6-8CB6-236EEE820263}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RendererVt.unittest", "src\renderer\vt\ut_lib\vt.unittest.vcxproj", "{990F2657-8580-4828-943F-5DD657D11843}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "BufferOut", "src\buffer\out\lib\bufferout.vcxproj", "{0CF235BD-2DA0-407E-90EE-C467E8BBC714}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TerminalConnection", "src\cascadia\TerminalConnection\TerminalConnection.vcxproj", "{CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}"
|
||||
@ -1117,29 +1109,6 @@ Global
|
||||
{C7A6A5D9-60BE-4AEB-A5F6-AFE352F86CBB}.Release|x64.Build.0 = Release|x64
|
||||
{C7A6A5D9-60BE-4AEB-A5F6-AFE352F86CBB}.Release|x86.ActiveCfg = Release|Win32
|
||||
{C7A6A5D9-60BE-4AEB-A5F6-AFE352F86CBB}.Release|x86.Build.0 = Release|Win32
|
||||
{990F2657-8580-4828-943F-5DD657D11842}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32
|
||||
{990F2657-8580-4828-943F-5DD657D11842}.AuditMode|ARM64.ActiveCfg = Release|ARM64
|
||||
{990F2657-8580-4828-943F-5DD657D11842}.AuditMode|x64.ActiveCfg = Release|x64
|
||||
{990F2657-8580-4828-943F-5DD657D11842}.AuditMode|x86.ActiveCfg = Release|Win32
|
||||
{990F2657-8580-4828-943F-5DD657D11842}.Debug|Any CPU.ActiveCfg = Debug|Win32
|
||||
{990F2657-8580-4828-943F-5DD657D11842}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{990F2657-8580-4828-943F-5DD657D11842}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{990F2657-8580-4828-943F-5DD657D11842}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{990F2657-8580-4828-943F-5DD657D11842}.Debug|x64.Build.0 = Debug|x64
|
||||
{990F2657-8580-4828-943F-5DD657D11842}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{990F2657-8580-4828-943F-5DD657D11842}.Debug|x86.Build.0 = Debug|Win32
|
||||
{990F2657-8580-4828-943F-5DD657D11842}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32
|
||||
{990F2657-8580-4828-943F-5DD657D11842}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64
|
||||
{990F2657-8580-4828-943F-5DD657D11842}.Fuzzing|x64.ActiveCfg = Fuzzing|x64
|
||||
{990F2657-8580-4828-943F-5DD657D11842}.Fuzzing|x64.Build.0 = Fuzzing|x64
|
||||
{990F2657-8580-4828-943F-5DD657D11842}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32
|
||||
{990F2657-8580-4828-943F-5DD657D11842}.Release|Any CPU.ActiveCfg = Release|Win32
|
||||
{990F2657-8580-4828-943F-5DD657D11842}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{990F2657-8580-4828-943F-5DD657D11842}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{990F2657-8580-4828-943F-5DD657D11842}.Release|x64.ActiveCfg = Release|x64
|
||||
{990F2657-8580-4828-943F-5DD657D11842}.Release|x64.Build.0 = Release|x64
|
||||
{990F2657-8580-4828-943F-5DD657D11842}.Release|x86.ActiveCfg = Release|Win32
|
||||
{990F2657-8580-4828-943F-5DD657D11842}.Release|x86.Build.0 = Release|Win32
|
||||
{814DBDDE-894E-4327-A6E1-740504850098}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32
|
||||
{814DBDDE-894E-4327-A6E1-740504850098}.AuditMode|ARM64.ActiveCfg = Release|ARM64
|
||||
{814DBDDE-894E-4327-A6E1-740504850098}.AuditMode|x64.ActiveCfg = Release|x64
|
||||
@ -1210,28 +1179,6 @@ Global
|
||||
{18D09A24-8240-42D6-8CB6-236EEE820263}.Release|x64.Build.0 = Release|x64
|
||||
{18D09A24-8240-42D6-8CB6-236EEE820263}.Release|x86.ActiveCfg = Release|Win32
|
||||
{18D09A24-8240-42D6-8CB6-236EEE820263}.Release|x86.Build.0 = Release|Win32
|
||||
{990F2657-8580-4828-943F-5DD657D11843}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32
|
||||
{990F2657-8580-4828-943F-5DD657D11843}.AuditMode|ARM64.ActiveCfg = Release|ARM64
|
||||
{990F2657-8580-4828-943F-5DD657D11843}.AuditMode|x64.ActiveCfg = Release|x64
|
||||
{990F2657-8580-4828-943F-5DD657D11843}.AuditMode|x86.ActiveCfg = Release|Win32
|
||||
{990F2657-8580-4828-943F-5DD657D11843}.Debug|Any CPU.ActiveCfg = Debug|Win32
|
||||
{990F2657-8580-4828-943F-5DD657D11843}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{990F2657-8580-4828-943F-5DD657D11843}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{990F2657-8580-4828-943F-5DD657D11843}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{990F2657-8580-4828-943F-5DD657D11843}.Debug|x64.Build.0 = Debug|x64
|
||||
{990F2657-8580-4828-943F-5DD657D11843}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{990F2657-8580-4828-943F-5DD657D11843}.Debug|x86.Build.0 = Debug|Win32
|
||||
{990F2657-8580-4828-943F-5DD657D11843}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32
|
||||
{990F2657-8580-4828-943F-5DD657D11843}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64
|
||||
{990F2657-8580-4828-943F-5DD657D11843}.Fuzzing|x64.ActiveCfg = Fuzzing|x64
|
||||
{990F2657-8580-4828-943F-5DD657D11843}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32
|
||||
{990F2657-8580-4828-943F-5DD657D11843}.Release|Any CPU.ActiveCfg = Release|Win32
|
||||
{990F2657-8580-4828-943F-5DD657D11843}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{990F2657-8580-4828-943F-5DD657D11843}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{990F2657-8580-4828-943F-5DD657D11843}.Release|x64.ActiveCfg = Release|x64
|
||||
{990F2657-8580-4828-943F-5DD657D11843}.Release|x64.Build.0 = Release|x64
|
||||
{990F2657-8580-4828-943F-5DD657D11843}.Release|x86.ActiveCfg = Release|Win32
|
||||
{990F2657-8580-4828-943F-5DD657D11843}.Release|x86.Build.0 = Release|Win32
|
||||
{0CF235BD-2DA0-407E-90EE-C467E8BBC714}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32
|
||||
{0CF235BD-2DA0-407E-90EE-C467E8BBC714}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64
|
||||
{0CF235BD-2DA0-407E-90EE-C467E8BBC714}.AuditMode|ARM64.Build.0 = AuditMode|ARM64
|
||||
@ -2444,11 +2391,9 @@ Global
|
||||
{06EC74CB-9A12-429C-B551-8562EC964846} = {E8F24881-5E37-4362-B191-A3BA0ED7F4EB}
|
||||
{D3B92829-26CB-411A-BDA2-7F5DA3D25DD4} = {E8F24881-5E37-4362-B191-A3BA0ED7F4EB}
|
||||
{C7A6A5D9-60BE-4AEB-A5F6-AFE352F86CBB} = {A10C4720-DCA4-4640-9749-67F4314F527C}
|
||||
{990F2657-8580-4828-943F-5DD657D11842} = {05500DEF-2294-41E3-AF9A-24E580B82836}
|
||||
{814DBDDE-894E-4327-A6E1-740504850098} = {A10C4720-DCA4-4640-9749-67F4314F527C}
|
||||
{814CBEEE-894E-4327-A6E1-740504850098} = {A10C4720-DCA4-4640-9749-67F4314F527C}
|
||||
{18D09A24-8240-42D6-8CB6-236EEE820263} = {89CDCC5C-9F53-4054-97A4-639D99F169CD}
|
||||
{990F2657-8580-4828-943F-5DD657D11843} = {05500DEF-2294-41E3-AF9A-24E580B82836}
|
||||
{0CF235BD-2DA0-407E-90EE-C467E8BBC714} = {1E4A062E-293B-4817-B20D-BF16B979E350}
|
||||
{CA5CAD1A-C46D-4588-B1C0-40F31AE9100B} = {59840756-302F-44DF-AA47-441A9D673202}
|
||||
{CA5CAD1A-ABCD-429C-B551-8562EC954746} = {9921CA0A-320C-4460-8623-3A3196E7F4CB}
|
||||
|
||||
@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Maxime Pinard
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@ -1,17 +0,0 @@
|
||||
### Notes for Future Maintainers
|
||||
|
||||
This was originally imported by @miniksa in March 2020.
|
||||
|
||||
The provenance information (where it came from and which commit) is stored in the file `cgmanifest.json` in the same directory as this readme.
|
||||
Please update the provenance information in that file when ingesting an updated version of the dependent library.
|
||||
That provenance file is automatically read and inventoried by Microsoft systems to ensure compliance with appropriate governance standards.
|
||||
|
||||
## What should be done to update this in the future?
|
||||
|
||||
1. Go to pinam45/dynamic_bitset repository on GitHub.
|
||||
2. Take the entire contents of the include directory wholesale and drop it in the root directory here.
|
||||
3. Don't change anything about it.
|
||||
4. Validate that the license in the root of the repository didn't change and update it if so. It is sitting in the same directory as this readme.
|
||||
If it changed dramatically, ensure that it is still compatible with our license scheme. Also update the NOTICE file in the root of our repository to declare the third-party usage.
|
||||
5. Submit the pull.
|
||||
|
||||
@ -1,15 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/component-detection-manifest.json",
|
||||
"Registrations": [
|
||||
{
|
||||
"component": {
|
||||
"type": "git",
|
||||
"git": {
|
||||
"repositoryUrl": "https://github.com/pinam45/dynamic_bitset",
|
||||
"commitHash": "00f2d066ce9deebf28b006636150e5a882beb83f"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"Version": 1
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,26 +0,0 @@
|
||||
BSD 2-Clause License
|
||||
|
||||
Copyright (c) 2016 - 2019, Kim Walisch
|
||||
Copyright (c) 2016 - 2019, Wojciech Muła
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@ -1,17 +0,0 @@
|
||||
### Notes for Future Maintainers
|
||||
|
||||
This was originally imported by @miniksa in March 2020.
|
||||
|
||||
The provenance information (where it came from and which commit) is stored in the file `cgmanifest.json` in the same directory as this readme.
|
||||
Please update the provenance information in that file when ingesting an updated version of the dependent library.
|
||||
That provenance file is automatically read and inventoried by Microsoft systems to ensure compliance with appropriate governance standards.
|
||||
|
||||
## What should be done to update this in the future?
|
||||
|
||||
1. Go to kimwalisch/libpopcnt repository on GitHub.
|
||||
2. Take the `libpopcnt.h` file.
|
||||
3. Don't change anything about it.
|
||||
4. Validate that the `LICENSE` in the root of the repository didn't change and update it if so. It is sitting in the same directory as this readme.
|
||||
If it changed dramatically, ensure that it is still compatible with our license scheme. Also update the NOTICE file in the root of our repository to declare the third-party usage.
|
||||
5. Submit the pull.
|
||||
|
||||
@ -1,15 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/component-detection-manifest.json",
|
||||
"Registrations": [
|
||||
{
|
||||
"component": {
|
||||
"type": "git",
|
||||
"git": {
|
||||
"repositoryUrl": "https://github.com/kimwalisch/libpopcnt",
|
||||
"commitHash": "c49987e90e56191c399cab881ab87b5daecc9b8e"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"Version": 1
|
||||
}
|
||||
@ -1,798 +0,0 @@
|
||||
/*
|
||||
* libpopcnt.h - C/C++ library for counting the number of 1 bits (bit
|
||||
* population count) in an array as quickly as possible using
|
||||
* specialized CPU instructions i.e. POPCNT, AVX2, AVX512, NEON.
|
||||
*
|
||||
* Copyright (c) 2016 - 2020, Kim Walisch
|
||||
* Copyright (c) 2016 - 2018, Wojciech Muła
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef LIBPOPCNT_H
|
||||
#define LIBPOPCNT_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifndef __has_builtin
|
||||
#define __has_builtin(x) 0
|
||||
#endif
|
||||
|
||||
#ifndef __has_attribute
|
||||
#define __has_attribute(x) 0
|
||||
#endif
|
||||
|
||||
#ifdef __GNUC__
|
||||
#define GNUC_PREREQ(x, y) \
|
||||
(__GNUC__ > x || (__GNUC__ == x && __GNUC_MINOR__ >= y))
|
||||
#else
|
||||
#define GNUC_PREREQ(x, y) 0
|
||||
#endif
|
||||
|
||||
#ifdef __clang__
|
||||
#define CLANG_PREREQ(x, y) \
|
||||
(__clang_major__ > x || (__clang_major__ == x && __clang_minor__ >= y))
|
||||
#else
|
||||
#define CLANG_PREREQ(x, y) 0
|
||||
#endif
|
||||
|
||||
#if (_MSC_VER < 1900) && \
|
||||
!defined(__cplusplus)
|
||||
#define inline __inline
|
||||
#endif
|
||||
|
||||
#if (defined(__i386__) || \
|
||||
defined(__x86_64__) || \
|
||||
defined(_M_IX86) || \
|
||||
defined(_M_X64))
|
||||
#define X86_OR_X64
|
||||
#endif
|
||||
|
||||
#if GNUC_PREREQ(4, 2) || \
|
||||
__has_builtin(__builtin_popcount)
|
||||
#define HAVE_BUILTIN_POPCOUNT
|
||||
#endif
|
||||
|
||||
#if GNUC_PREREQ(4, 2) || \
|
||||
CLANG_PREREQ(3, 0)
|
||||
#define HAVE_ASM_POPCNT
|
||||
#endif
|
||||
|
||||
#if defined(X86_OR_X64) && \
|
||||
(defined(HAVE_ASM_POPCNT) || \
|
||||
defined(_MSC_VER))
|
||||
#define HAVE_POPCNT
|
||||
#endif
|
||||
|
||||
#if defined(X86_OR_X64) && \
|
||||
GNUC_PREREQ(4, 9)
|
||||
#define HAVE_AVX2
|
||||
#endif
|
||||
|
||||
#if defined(X86_OR_X64) && \
|
||||
GNUC_PREREQ(5, 0)
|
||||
#define HAVE_AVX512
|
||||
#endif
|
||||
|
||||
#if defined(X86_OR_X64) && !defined(_M_ARM64EC)
|
||||
/* MSVC compatible compilers (Windows) */
|
||||
#if defined(_MSC_VER)
|
||||
/* clang-cl (LLVM 10 from 2020) requires /arch:AVX2 or
|
||||
* /arch:AVX512 to enable vector instructions */
|
||||
#if defined(__clang__)
|
||||
#if defined(__AVX2__)
|
||||
#define HAVE_AVX2
|
||||
#endif
|
||||
#if defined(__AVX512__)
|
||||
#define HAVE_AVX2
|
||||
#define HAVE_AVX512
|
||||
#endif
|
||||
/* MSVC 2017 or later does not require
|
||||
* /arch:AVX2 or /arch:AVX512 */
|
||||
#elif _MSC_VER >= 1910
|
||||
#define HAVE_AVX2
|
||||
#define HAVE_AVX512
|
||||
#endif
|
||||
/* Clang (Unix-like OSes) */
|
||||
#elif CLANG_PREREQ(3, 8) && \
|
||||
__has_attribute(target) && \
|
||||
(!defined(__apple_build_version__) || __apple_build_version__ >= 8000000)
|
||||
#define HAVE_AVX2
|
||||
#define HAVE_AVX512
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Only enable CPUID runtime checks if this is really
|
||||
* needed. E.g. do not enable if user has compiled
|
||||
* using -march=native on a CPU that supports AVX512.
|
||||
*/
|
||||
#if defined(X86_OR_X64) && \
|
||||
(defined(__cplusplus) || \
|
||||
defined(_MSC_VER) || \
|
||||
(GNUC_PREREQ(4, 2) || \
|
||||
__has_builtin(__sync_val_compare_and_swap))) && \
|
||||
((defined(HAVE_AVX512) && !(defined(__AVX512__) || defined(__AVX512BW__))) || \
|
||||
(defined(HAVE_AVX2) && !defined(__AVX2__)) || \
|
||||
(defined(HAVE_POPCNT) && !defined(__POPCNT__)))
|
||||
#define HAVE_CPUID
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*
|
||||
* This uses fewer arithmetic operations than any other known
|
||||
* implementation on machines with fast multiplication.
|
||||
* It uses 12 arithmetic operations, one of which is a multiply.
|
||||
* http://en.wikipedia.org/wiki/Hamming_weight#Efficient_implementation
|
||||
*/
|
||||
static inline uint64_t popcount64(uint64_t x)
|
||||
{
|
||||
uint64_t m1 = 0x5555555555555555ll;
|
||||
uint64_t m2 = 0x3333333333333333ll;
|
||||
uint64_t m4 = 0x0F0F0F0F0F0F0F0Fll;
|
||||
uint64_t h01 = 0x0101010101010101ll;
|
||||
|
||||
x -= (x >> 1) & m1;
|
||||
x = (x & m2) + ((x >> 2) & m2);
|
||||
x = (x + (x >> 4)) & m4;
|
||||
|
||||
return (x * h01) >> 56;
|
||||
}
|
||||
|
||||
#if defined(HAVE_ASM_POPCNT) && \
|
||||
defined(__x86_64__)
|
||||
|
||||
static inline uint64_t popcnt64(uint64_t x)
|
||||
{
|
||||
__asm__ ("popcnt %1, %0" : "=r" (x) : "0" (x));
|
||||
return x;
|
||||
}
|
||||
|
||||
#elif defined(HAVE_ASM_POPCNT) && \
|
||||
defined(__i386__)
|
||||
|
||||
static inline uint32_t popcnt32(uint32_t x)
|
||||
{
|
||||
__asm__ ("popcnt %1, %0" : "=r" (x) : "0" (x));
|
||||
return x;
|
||||
}
|
||||
|
||||
static inline uint64_t popcnt64(uint64_t x)
|
||||
{
|
||||
return popcnt32((uint32_t) x) +
|
||||
popcnt32((uint32_t)(x >> 32));
|
||||
}
|
||||
|
||||
#elif defined(_MSC_VER) && \
|
||||
defined(_M_X64)
|
||||
|
||||
#include <nmmintrin.h>
|
||||
|
||||
static inline uint64_t popcnt64(uint64_t x)
|
||||
{
|
||||
return _mm_popcnt_u64(x);
|
||||
}
|
||||
|
||||
#elif defined(_MSC_VER) && \
|
||||
defined(_M_IX86)
|
||||
|
||||
#include <nmmintrin.h>
|
||||
|
||||
static inline uint64_t popcnt64(uint64_t x)
|
||||
{
|
||||
return _mm_popcnt_u32((uint32_t) x) +
|
||||
_mm_popcnt_u32((uint32_t)(x >> 32));
|
||||
}
|
||||
|
||||
/* non x86 CPUs */
|
||||
#elif defined(HAVE_BUILTIN_POPCOUNT)
|
||||
|
||||
static inline uint64_t popcnt64(uint64_t x)
|
||||
{
|
||||
return __builtin_popcountll(x);
|
||||
}
|
||||
|
||||
/* no hardware POPCNT,
|
||||
* use pure integer algorithm */
|
||||
#else
|
||||
|
||||
static inline uint64_t popcnt64(uint64_t x)
|
||||
{
|
||||
return popcount64(x);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_CPUID)
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#include <intrin.h>
|
||||
#include <immintrin.h>
|
||||
#endif
|
||||
|
||||
/* %ecx bit flags */
|
||||
#define bit_POPCNT (1 << 23)
|
||||
|
||||
/* %ebx bit flags */
|
||||
#define bit_AVX2 (1 << 5)
|
||||
#define bit_AVX512 (1 << 30)
|
||||
|
||||
/* xgetbv bit flags */
|
||||
#define XSTATE_SSE (1 << 1)
|
||||
#define XSTATE_YMM (1 << 2)
|
||||
#define XSTATE_ZMM (7 << 5)
|
||||
|
||||
static inline void run_cpuid(int eax, int ecx, int* abcd)
|
||||
{
|
||||
#if defined(_MSC_VER)
|
||||
__cpuidex(abcd, eax, ecx);
|
||||
#else
|
||||
int ebx = 0;
|
||||
int edx = 0;
|
||||
|
||||
#if defined(__i386__) && \
|
||||
defined(__PIC__)
|
||||
/* in case of PIC under 32-bit EBX cannot be clobbered */
|
||||
__asm__ ("movl %%ebx, %%edi;"
|
||||
"cpuid;"
|
||||
"xchgl %%ebx, %%edi;"
|
||||
: "=D" (ebx),
|
||||
"+a" (eax),
|
||||
"+c" (ecx),
|
||||
"=d" (edx));
|
||||
#else
|
||||
__asm__ ("cpuid;"
|
||||
: "+b" (ebx),
|
||||
"+a" (eax),
|
||||
"+c" (ecx),
|
||||
"=d" (edx));
|
||||
#endif
|
||||
|
||||
abcd[0] = eax;
|
||||
abcd[1] = ebx;
|
||||
abcd[2] = ecx;
|
||||
abcd[3] = edx;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(HAVE_AVX2) || \
|
||||
defined(HAVE_AVX512)
|
||||
|
||||
static inline int get_xcr0()
|
||||
{
|
||||
int xcr0;
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
xcr0 = (int) _xgetbv(0);
|
||||
#else
|
||||
__asm__ ("xgetbv" : "=a" (xcr0) : "c" (0) : "%edx" );
|
||||
#endif
|
||||
|
||||
return xcr0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static inline int get_cpuid()
|
||||
{
|
||||
int flags = 0;
|
||||
int abcd[4];
|
||||
|
||||
run_cpuid(1, 0, abcd);
|
||||
|
||||
if ((abcd[2] & bit_POPCNT) == bit_POPCNT)
|
||||
flags |= bit_POPCNT;
|
||||
|
||||
#if defined(HAVE_AVX2) || \
|
||||
defined(HAVE_AVX512)
|
||||
|
||||
int osxsave_mask = (1 << 27);
|
||||
|
||||
/* ensure OS supports extended processor state management */
|
||||
if ((abcd[2] & osxsave_mask) != osxsave_mask)
|
||||
return 0;
|
||||
|
||||
int ymm_mask = XSTATE_SSE | XSTATE_YMM;
|
||||
int zmm_mask = XSTATE_SSE | XSTATE_YMM | XSTATE_ZMM;
|
||||
|
||||
int xcr0 = get_xcr0();
|
||||
|
||||
if ((xcr0 & ymm_mask) == ymm_mask)
|
||||
{
|
||||
run_cpuid(7, 0, abcd);
|
||||
|
||||
if ((abcd[1] & bit_AVX2) == bit_AVX2)
|
||||
flags |= bit_AVX2;
|
||||
|
||||
if ((xcr0 & zmm_mask) == zmm_mask)
|
||||
{
|
||||
if ((abcd[1] & bit_AVX512) == bit_AVX512)
|
||||
flags |= bit_AVX512;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
#endif /* cpuid */
|
||||
|
||||
#if defined(HAVE_AVX2)
|
||||
|
||||
#include <immintrin.h>
|
||||
|
||||
#if !defined(_MSC_VER)
|
||||
__attribute__ ((target ("avx2")))
|
||||
#endif
|
||||
static inline void CSA256(__m256i* h, __m256i* l, __m256i a, __m256i b, __m256i c)
|
||||
{
|
||||
__m256i u = _mm256_xor_si256(a, b);
|
||||
*h = _mm256_or_si256(_mm256_and_si256(a, b), _mm256_and_si256(u, c));
|
||||
*l = _mm256_xor_si256(u, c);
|
||||
}
|
||||
|
||||
#if !defined(_MSC_VER)
|
||||
__attribute__ ((target ("avx2")))
|
||||
#endif
|
||||
static inline __m256i popcnt256(__m256i v)
|
||||
{
|
||||
__m256i lookup1 = _mm256_setr_epi8(
|
||||
4, 5, 5, 6, 5, 6, 6, 7,
|
||||
5, 6, 6, 7, 6, 7, 7, 8,
|
||||
4, 5, 5, 6, 5, 6, 6, 7,
|
||||
5, 6, 6, 7, 6, 7, 7, 8
|
||||
);
|
||||
|
||||
__m256i lookup2 = _mm256_setr_epi8(
|
||||
4, 3, 3, 2, 3, 2, 2, 1,
|
||||
3, 2, 2, 1, 2, 1, 1, 0,
|
||||
4, 3, 3, 2, 3, 2, 2, 1,
|
||||
3, 2, 2, 1, 2, 1, 1, 0
|
||||
);
|
||||
|
||||
__m256i low_mask = _mm256_set1_epi8(0x0f);
|
||||
__m256i lo = _mm256_and_si256(v, low_mask);
|
||||
__m256i hi = _mm256_and_si256(_mm256_srli_epi16(v, 4), low_mask);
|
||||
__m256i popcnt1 = _mm256_shuffle_epi8(lookup1, lo);
|
||||
__m256i popcnt2 = _mm256_shuffle_epi8(lookup2, hi);
|
||||
|
||||
return _mm256_sad_epu8(popcnt1, popcnt2);
|
||||
}
|
||||
|
||||
/*
|
||||
* AVX2 Harley-Seal popcount (4th iteration).
|
||||
* The algorithm is based on the paper "Faster Population Counts
|
||||
* using AVX2 Instructions" by Daniel Lemire, Nathan Kurz and
|
||||
* Wojciech Mula (23 Nov 2016).
|
||||
* @see https://arxiv.org/abs/1611.07612
|
||||
*/
|
||||
#if !defined(_MSC_VER)
|
||||
__attribute__ ((target ("avx2")))
|
||||
#endif
|
||||
static inline uint64_t popcnt_avx2(const __m256i* ptr, uint64_t size)
|
||||
{
|
||||
__m256i cnt = _mm256_setzero_si256();
|
||||
__m256i ones = _mm256_setzero_si256();
|
||||
__m256i twos = _mm256_setzero_si256();
|
||||
__m256i fours = _mm256_setzero_si256();
|
||||
__m256i eights = _mm256_setzero_si256();
|
||||
__m256i sixteens = _mm256_setzero_si256();
|
||||
__m256i twosA, twosB, foursA, foursB, eightsA, eightsB;
|
||||
|
||||
uint64_t i = 0;
|
||||
uint64_t limit = size - size % 16;
|
||||
uint64_t* cnt64;
|
||||
|
||||
for(; i < limit; i += 16)
|
||||
{
|
||||
CSA256(&twosA, &ones, ones, _mm256_loadu_si256(ptr + i + 0), _mm256_loadu_si256(ptr + i + 1));
|
||||
CSA256(&twosB, &ones, ones, _mm256_loadu_si256(ptr + i + 2), _mm256_loadu_si256(ptr + i + 3));
|
||||
CSA256(&foursA, &twos, twos, twosA, twosB);
|
||||
CSA256(&twosA, &ones, ones, _mm256_loadu_si256(ptr + i + 4), _mm256_loadu_si256(ptr + i + 5));
|
||||
CSA256(&twosB, &ones, ones, _mm256_loadu_si256(ptr + i + 6), _mm256_loadu_si256(ptr + i + 7));
|
||||
CSA256(&foursB, &twos, twos, twosA, twosB);
|
||||
CSA256(&eightsA, &fours, fours, foursA, foursB);
|
||||
CSA256(&twosA, &ones, ones, _mm256_loadu_si256(ptr + i + 8), _mm256_loadu_si256(ptr + i + 9));
|
||||
CSA256(&twosB, &ones, ones, _mm256_loadu_si256(ptr + i + 10), _mm256_loadu_si256(ptr + i + 11));
|
||||
CSA256(&foursA, &twos, twos, twosA, twosB);
|
||||
CSA256(&twosA, &ones, ones, _mm256_loadu_si256(ptr + i + 12), _mm256_loadu_si256(ptr + i + 13));
|
||||
CSA256(&twosB, &ones, ones, _mm256_loadu_si256(ptr + i + 14), _mm256_loadu_si256(ptr + i + 15));
|
||||
CSA256(&foursB, &twos, twos, twosA, twosB);
|
||||
CSA256(&eightsB, &fours, fours, foursA, foursB);
|
||||
CSA256(&sixteens, &eights, eights, eightsA, eightsB);
|
||||
|
||||
cnt = _mm256_add_epi64(cnt, popcnt256(sixteens));
|
||||
}
|
||||
|
||||
cnt = _mm256_slli_epi64(cnt, 4);
|
||||
cnt = _mm256_add_epi64(cnt, _mm256_slli_epi64(popcnt256(eights), 3));
|
||||
cnt = _mm256_add_epi64(cnt, _mm256_slli_epi64(popcnt256(fours), 2));
|
||||
cnt = _mm256_add_epi64(cnt, _mm256_slli_epi64(popcnt256(twos), 1));
|
||||
cnt = _mm256_add_epi64(cnt, popcnt256(ones));
|
||||
|
||||
for(; i < size; i++)
|
||||
cnt = _mm256_add_epi64(cnt, popcnt256(_mm256_loadu_si256(ptr + i)));
|
||||
|
||||
cnt64 = (uint64_t*) &cnt;
|
||||
|
||||
return cnt64[0] +
|
||||
cnt64[1] +
|
||||
cnt64[2] +
|
||||
cnt64[3];
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_AVX512)
|
||||
|
||||
#include <immintrin.h>
|
||||
|
||||
#if !defined(_MSC_VER)
|
||||
__attribute__ ((target ("avx512bw")))
|
||||
#endif
|
||||
static inline __m512i popcnt512(__m512i v)
|
||||
{
|
||||
__m512i m1 = _mm512_set1_epi8(0x55);
|
||||
__m512i m2 = _mm512_set1_epi8(0x33);
|
||||
__m512i m4 = _mm512_set1_epi8(0x0F);
|
||||
__m512i vm = _mm512_and_si512(_mm512_srli_epi16(v, 1), m1);
|
||||
__m512i t1 = _mm512_sub_epi8(v, vm);
|
||||
__m512i tm = _mm512_and_si512(t1, m2);
|
||||
__m512i tm2 = _mm512_and_si512(_mm512_srli_epi16(t1, 2), m2);
|
||||
__m512i t2 = _mm512_add_epi8(tm, tm2);
|
||||
__m512i tt = _mm512_add_epi8(t2, _mm512_srli_epi16(t2, 4));
|
||||
__m512i t3 = _mm512_and_si512(tt, m4);
|
||||
|
||||
return _mm512_sad_epu8(t3, _mm512_setzero_si512());
|
||||
}
|
||||
|
||||
#if !defined(_MSC_VER)
|
||||
__attribute__ ((target ("avx512bw")))
|
||||
#endif
|
||||
static inline void CSA512(__m512i* h, __m512i* l, __m512i a, __m512i b, __m512i c)
|
||||
{
|
||||
*l = _mm512_ternarylogic_epi32(c, b, a, 0x96);
|
||||
*h = _mm512_ternarylogic_epi32(c, b, a, 0xe8);
|
||||
}
|
||||
|
||||
/*
|
||||
* AVX512 Harley-Seal popcount (4th iteration).
|
||||
* The algorithm is based on the paper "Faster Population Counts
|
||||
* using AVX2 Instructions" by Daniel Lemire, Nathan Kurz and
|
||||
* Wojciech Mula (23 Nov 2016).
|
||||
* @see https://arxiv.org/abs/1611.07612
|
||||
*/
|
||||
#if !defined(_MSC_VER)
|
||||
__attribute__ ((target ("avx512bw")))
|
||||
#endif
|
||||
static inline uint64_t popcnt_avx512(const __m512i* ptr, const uint64_t size)
|
||||
{
|
||||
__m512i cnt = _mm512_setzero_si512();
|
||||
__m512i ones = _mm512_setzero_si512();
|
||||
__m512i twos = _mm512_setzero_si512();
|
||||
__m512i fours = _mm512_setzero_si512();
|
||||
__m512i eights = _mm512_setzero_si512();
|
||||
__m512i sixteens = _mm512_setzero_si512();
|
||||
__m512i twosA, twosB, foursA, foursB, eightsA, eightsB;
|
||||
|
||||
uint64_t i = 0;
|
||||
uint64_t limit = size - size % 16;
|
||||
uint64_t* cnt64;
|
||||
|
||||
for(; i < limit; i += 16)
|
||||
{
|
||||
CSA512(&twosA, &ones, ones, _mm512_loadu_si512(ptr + i + 0), _mm512_loadu_si512(ptr + i + 1));
|
||||
CSA512(&twosB, &ones, ones, _mm512_loadu_si512(ptr + i + 2), _mm512_loadu_si512(ptr + i + 3));
|
||||
CSA512(&foursA, &twos, twos, twosA, twosB);
|
||||
CSA512(&twosA, &ones, ones, _mm512_loadu_si512(ptr + i + 4), _mm512_loadu_si512(ptr + i + 5));
|
||||
CSA512(&twosB, &ones, ones, _mm512_loadu_si512(ptr + i + 6), _mm512_loadu_si512(ptr + i + 7));
|
||||
CSA512(&foursB, &twos, twos, twosA, twosB);
|
||||
CSA512(&eightsA, &fours, fours, foursA, foursB);
|
||||
CSA512(&twosA, &ones, ones, _mm512_loadu_si512(ptr + i + 8), _mm512_loadu_si512(ptr + i + 9));
|
||||
CSA512(&twosB, &ones, ones, _mm512_loadu_si512(ptr + i + 10), _mm512_loadu_si512(ptr + i + 11));
|
||||
CSA512(&foursA, &twos, twos, twosA, twosB);
|
||||
CSA512(&twosA, &ones, ones, _mm512_loadu_si512(ptr + i + 12), _mm512_loadu_si512(ptr + i + 13));
|
||||
CSA512(&twosB, &ones, ones, _mm512_loadu_si512(ptr + i + 14), _mm512_loadu_si512(ptr + i + 15));
|
||||
CSA512(&foursB, &twos, twos, twosA, twosB);
|
||||
CSA512(&eightsB, &fours, fours, foursA, foursB);
|
||||
CSA512(&sixteens, &eights, eights, eightsA, eightsB);
|
||||
|
||||
cnt = _mm512_add_epi64(cnt, popcnt512(sixteens));
|
||||
}
|
||||
|
||||
cnt = _mm512_slli_epi64(cnt, 4);
|
||||
cnt = _mm512_add_epi64(cnt, _mm512_slli_epi64(popcnt512(eights), 3));
|
||||
cnt = _mm512_add_epi64(cnt, _mm512_slli_epi64(popcnt512(fours), 2));
|
||||
cnt = _mm512_add_epi64(cnt, _mm512_slli_epi64(popcnt512(twos), 1));
|
||||
cnt = _mm512_add_epi64(cnt, popcnt512(ones));
|
||||
|
||||
for(; i < size; i++)
|
||||
cnt = _mm512_add_epi64(cnt, popcnt512(_mm512_loadu_si512(ptr + i)));
|
||||
|
||||
cnt64 = (uint64_t*) &cnt;
|
||||
|
||||
return cnt64[0] +
|
||||
cnt64[1] +
|
||||
cnt64[2] +
|
||||
cnt64[3] +
|
||||
cnt64[4] +
|
||||
cnt64[5] +
|
||||
cnt64[6] +
|
||||
cnt64[7];
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/* x86 CPUs */
|
||||
#if defined(X86_OR_X64)
|
||||
|
||||
/*
|
||||
* Count the number of 1 bits in the data array
|
||||
* @data: An array
|
||||
* @size: Size of data in bytes
|
||||
*/
|
||||
static inline uint64_t popcnt(const void* data, uint64_t size)
|
||||
{
|
||||
uint64_t i = 0;
|
||||
uint64_t cnt = 0;
|
||||
const uint8_t* ptr = (const uint8_t*) data;
|
||||
|
||||
/*
|
||||
* CPUID runtime checks are only enabled if this is needed.
|
||||
* E.g. CPUID is disabled when a user compiles his
|
||||
* code using -march=native on a CPU with AVX512.
|
||||
*/
|
||||
#if defined(HAVE_CPUID)
|
||||
#if defined(__cplusplus)
|
||||
/* C++11 thread-safe singleton */
|
||||
static const int cpuid = get_cpuid();
|
||||
#else
|
||||
static int cpuid_ = -1;
|
||||
int cpuid = cpuid_;
|
||||
if (cpuid == -1)
|
||||
{
|
||||
cpuid = get_cpuid();
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
_InterlockedCompareExchange(&cpuid_, cpuid, -1);
|
||||
#else
|
||||
__sync_val_compare_and_swap(&cpuid_, -1, cpuid);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_AVX512)
|
||||
#if defined(__AVX512__) || defined(__AVX512BW__)
|
||||
/* AVX512 requires arrays >= 1024 bytes */
|
||||
if (i + 1024 <= size)
|
||||
#else
|
||||
if ((cpuid & bit_AVX512) &&
|
||||
i + 1024 <= size)
|
||||
#endif
|
||||
{
|
||||
const __m512i* ptr512 = (const __m512i*)(ptr + i);
|
||||
cnt += popcnt_avx512(ptr512, (size - i) / 64);
|
||||
i = size - size % 64;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_AVX2)
|
||||
#if defined(__AVX2__)
|
||||
/* AVX2 requires arrays >= 512 bytes */
|
||||
if (i + 512 <= size)
|
||||
#else
|
||||
if ((cpuid & bit_AVX2) &&
|
||||
i + 512 <= size)
|
||||
#endif
|
||||
{
|
||||
const __m256i* ptr256 = (const __m256i*)(ptr + i);
|
||||
cnt += popcnt_avx2(ptr256, (size - i) / 32);
|
||||
i = size - size % 32;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_POPCNT)
|
||||
/*
|
||||
* The user has compiled without -mpopcnt.
|
||||
* Unfortunately the MSVC compiler does not have
|
||||
* a POPCNT macro so we cannot get rid of the
|
||||
* runtime check for MSVC.
|
||||
*/
|
||||
#if !defined(__POPCNT__)
|
||||
if (cpuid & bit_POPCNT)
|
||||
#endif
|
||||
{
|
||||
/* We use unaligned memory accesses here to improve performance */
|
||||
for (; i < size - size % 8; i += 8)
|
||||
cnt += popcnt64(*(const uint64_t*)(ptr + i));
|
||||
for (; i < size; i++)
|
||||
cnt += popcnt64(ptr[i]);
|
||||
|
||||
return cnt;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !defined(HAVE_POPCNT) || \
|
||||
!defined(__POPCNT__)
|
||||
/*
|
||||
* Pure integer popcount algorithm.
|
||||
* We use unaligned memory accesses here to improve performance.
|
||||
*/
|
||||
for (; i < size - size % 8; i += 8)
|
||||
cnt += popcount64(*(const uint64_t*)(ptr + i));
|
||||
|
||||
if (i < size)
|
||||
{
|
||||
uint64_t val = 0;
|
||||
size_t bytes = (size_t)(size - i);
|
||||
memcpy(&val, &ptr[i], bytes);
|
||||
cnt += popcount64(val);
|
||||
}
|
||||
|
||||
return cnt;
|
||||
#endif
|
||||
}
|
||||
|
||||
#elif defined(__ARM_NEON) || \
|
||||
defined(__aarch64__)
|
||||
|
||||
#include <arm_neon.h>
|
||||
|
||||
static inline uint64x2_t vpadalq(uint64x2_t sum, uint8x16_t t)
|
||||
{
|
||||
return vpadalq_u32(sum, vpaddlq_u16(vpaddlq_u8(t)));
|
||||
}
|
||||
|
||||
/*
|
||||
* Count the number of 1 bits in the data array
|
||||
* @data: An array
|
||||
* @size: Size of data in bytes
|
||||
*/
|
||||
static inline uint64_t popcnt(const void* data, uint64_t size)
|
||||
{
|
||||
uint64_t i = 0;
|
||||
uint64_t cnt = 0;
|
||||
uint64_t chunk_size = 64;
|
||||
const uint8_t* ptr = (const uint8_t*) data;
|
||||
|
||||
if (size >= chunk_size)
|
||||
{
|
||||
uint64_t iters = size / chunk_size;
|
||||
uint64x2_t sum = vcombine_u64(vcreate_u64(0), vcreate_u64(0));
|
||||
uint8x16_t zero = vcombine_u8(vcreate_u8(0), vcreate_u8(0));
|
||||
|
||||
do
|
||||
{
|
||||
uint8x16_t t0 = zero;
|
||||
uint8x16_t t1 = zero;
|
||||
uint8x16_t t2 = zero;
|
||||
uint8x16_t t3 = zero;
|
||||
|
||||
/*
|
||||
* After every 31 iterations we need to add the
|
||||
* temporary sums (t0, t1, t2, t3) to the total sum.
|
||||
* We must ensure that the temporary sums <= 255
|
||||
* and 31 * 8 bits = 248 which is OK.
|
||||
*/
|
||||
uint64_t limit = (i + 31 < iters) ? i + 31 : iters;
|
||||
|
||||
/* Each iteration processes 64 bytes */
|
||||
for (; i < limit; i++)
|
||||
{
|
||||
uint8x16x4_t input = vld4q_u8(ptr);
|
||||
ptr += chunk_size;
|
||||
|
||||
t0 = vaddq_u8(t0, vcntq_u8(input.val[0]));
|
||||
t1 = vaddq_u8(t1, vcntq_u8(input.val[1]));
|
||||
t2 = vaddq_u8(t2, vcntq_u8(input.val[2]));
|
||||
t3 = vaddq_u8(t3, vcntq_u8(input.val[3]));
|
||||
}
|
||||
|
||||
sum = vpadalq(sum, t0);
|
||||
sum = vpadalq(sum, t1);
|
||||
sum = vpadalq(sum, t2);
|
||||
sum = vpadalq(sum, t3);
|
||||
}
|
||||
while (i < iters);
|
||||
|
||||
i = 0;
|
||||
size %= chunk_size;
|
||||
|
||||
uint64_t tmp[2];
|
||||
vst1q_u64(tmp, sum);
|
||||
cnt += tmp[0];
|
||||
cnt += tmp[1];
|
||||
}
|
||||
|
||||
#if defined(__ARM_FEATURE_UNALIGNED)
|
||||
/* We use unaligned memory accesses here to improve performance */
|
||||
for (; i < size - size % 8; i += 8)
|
||||
cnt += popcnt64(*(const uint64_t*)(ptr + i));
|
||||
#else
|
||||
if (i + 8 <= size)
|
||||
{
|
||||
/* Align memory to an 8 byte boundary */
|
||||
for (; (uintptr_t)(ptr + i) % 8; i++)
|
||||
cnt += popcnt64(ptr[i]);
|
||||
for (; i < size - size % 8; i += 8)
|
||||
cnt += popcnt64(*(const uint64_t*)(ptr + i));
|
||||
}
|
||||
#endif
|
||||
|
||||
if (i < size)
|
||||
{
|
||||
uint64_t val = 0;
|
||||
size_t bytes = (size_t)(size - i);
|
||||
memcpy(&val, &ptr[i], bytes);
|
||||
cnt += popcount64(val);
|
||||
}
|
||||
|
||||
return cnt;
|
||||
}
|
||||
|
||||
/* all other CPUs */
|
||||
#else
|
||||
|
||||
/*
|
||||
* Count the number of 1 bits in the data array
|
||||
* @data: An array
|
||||
* @size: Size of data in bytes
|
||||
*/
|
||||
static inline uint64_t popcnt(const void* data, uint64_t size)
|
||||
{
|
||||
uint64_t i = 0;
|
||||
uint64_t cnt = 0;
|
||||
const uint8_t* ptr = (const uint8_t*) data;
|
||||
|
||||
if (size >= 8)
|
||||
{
|
||||
/*
|
||||
* Since we don't know whether this CPU architecture
|
||||
* supports unaligned memory accesses we align
|
||||
* memory to an 8 byte boundary.
|
||||
*/
|
||||
for (; (uintptr_t)(ptr + i) % 8; i++)
|
||||
cnt += popcnt64(ptr[i]);
|
||||
for (; i < size - size % 8; i += 8)
|
||||
cnt += popcnt64(*(const uint64_t*)(ptr + i));
|
||||
}
|
||||
|
||||
for (; i < size; i++)
|
||||
cnt += popcnt64(ptr[i]);
|
||||
|
||||
return cnt;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
||||
#endif /* LIBPOPCNT_H */
|
||||
@ -42,7 +42,6 @@
|
||||
<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.Console.UIA" Name="e7ebce59-2161-572d-b263-2f16a6afb9e5"/>
|
||||
<!-- 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. -->
|
||||
@ -66,7 +65,6 @@
|
||||
<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.Console.UIA"/>
|
||||
</EventProviders>
|
||||
</EventCollectorId>
|
||||
|
||||
@ -18,7 +18,6 @@
|
||||
<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.Console.UIA" Name="e7ebce59-2161-572d-b263-2f16a6afb9e5"/>
|
||||
|
||||
<!-- Profile for General Terminal logging -->
|
||||
|
||||
@ -716,13 +716,6 @@ OutputCellIterator TextBuffer::WriteLine(const OutputCellIterator givenIt,
|
||||
// - true if we successfully incremented the buffer.
|
||||
void TextBuffer::IncrementCircularBuffer(const TextAttribute& fillAttributes)
|
||||
{
|
||||
// FirstRow is at any given point in time the array index in the circular buffer that corresponds
|
||||
// to the logical position 0 in the window (cursor coordinates and all other coordinates).
|
||||
if (_isActiveBuffer && _renderer)
|
||||
{
|
||||
_renderer->TriggerFlush(true);
|
||||
}
|
||||
|
||||
// Prune hyperlinks to delete obsolete references
|
||||
_PruneHyperlinks();
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
#include "ConptyConnection.h"
|
||||
|
||||
#include <conpty-static.h>
|
||||
#include <winmeta.h>
|
||||
|
||||
#include "CTerminalHandoff.h"
|
||||
#include "LibraryResources.h"
|
||||
@ -31,29 +32,6 @@ static constexpr auto _errorFormat = L"{0} ({0:#010x})"sv;
|
||||
|
||||
namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
{
|
||||
// Function Description:
|
||||
// - creates some basic anonymous pipes and passes them to CreatePseudoConsole
|
||||
// Arguments:
|
||||
// - size: The size of the conpty to create, in characters.
|
||||
// - phInput: Receives the handle to the newly-created anonymous pipe for writing input to the conpty.
|
||||
// - phOutput: Receives the handle to the newly-created anonymous pipe for reading the output of the conpty.
|
||||
// - phPc: Receives a token value to identify this conpty
|
||||
#pragma warning(suppress : 26430) // This statement sufficiently checks the out parameters. Analyzer cannot find this.
|
||||
static HRESULT _CreatePseudoConsoleAndPipes(const COORD size, const DWORD dwFlags, HANDLE* phInput, HANDLE* phOutput, HPCON* phPC) noexcept
|
||||
{
|
||||
RETURN_HR_IF(E_INVALIDARG, phPC == nullptr || phInput == nullptr || phOutput == nullptr);
|
||||
|
||||
wil::unique_hfile outPipeOurSide, outPipePseudoConsoleSide;
|
||||
wil::unique_hfile inPipeOurSide, inPipePseudoConsoleSide;
|
||||
|
||||
RETURN_IF_WIN32_BOOL_FALSE(CreatePipe(&inPipePseudoConsoleSide, &inPipeOurSide, nullptr, 0));
|
||||
RETURN_IF_WIN32_BOOL_FALSE(CreatePipe(&outPipeOurSide, &outPipePseudoConsoleSide, nullptr, 0));
|
||||
RETURN_IF_FAILED(ConptyCreatePseudoConsole(size, inPipePseudoConsoleSide.get(), outPipePseudoConsoleSide.get(), dwFlags, phPC));
|
||||
*phInput = inPipeOurSide.release();
|
||||
*phOutput = outPipeOurSide.release();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - launches the client application attached to the new pseudoconsole
|
||||
HRESULT ConptyConnection::_LaunchAttachedClient() noexcept
|
||||
@ -174,8 +152,11 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
|
||||
// Who decided that?
|
||||
#pragma warning(suppress : 26455) // Default constructor should not throw. Declare it 'noexcept' (f.6).
|
||||
ConptyConnection::ConptyConnection()
|
||||
ConptyConnection::ConptyConnection() :
|
||||
_writeOverlappedEvent{ CreateEventExW(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, EVENT_ALL_ACCESS) }
|
||||
{
|
||||
THROW_LAST_ERROR_IF(!_writeOverlappedEvent);
|
||||
_writeOverlapped.hEvent = _writeOverlappedEvent.get();
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
@ -236,7 +217,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
_environment = settings.TryLookup(L"environment").try_as<Windows::Foundation::Collections::ValueSet>();
|
||||
_profileGuid = unbox_prop_or<winrt::guid>(settings, L"profileGuid", _profileGuid);
|
||||
|
||||
_flags = PSEUDOCONSOLE_RESIZE_QUIRK;
|
||||
_flags = 0;
|
||||
|
||||
// If we're using an existing buffer, we want the new connection
|
||||
// to reuse the existing cursor. When not setting this flag, the
|
||||
@ -310,10 +291,8 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
|
||||
_sessionId = Utils::CreateGuid();
|
||||
|
||||
wil::unique_hfile inPipePseudoConsoleSide;
|
||||
wil::unique_hfile outPipePseudoConsoleSide;
|
||||
THROW_IF_WIN32_BOOL_FALSE(CreatePipe(&inPipePseudoConsoleSide, &_inPipe, nullptr, 0));
|
||||
THROW_IF_WIN32_BOOL_FALSE(CreatePipe(&_outPipe, &outPipePseudoConsoleSide, nullptr, 0));
|
||||
auto pipe = Utils::CreateOverlappedPipe(PIPE_ACCESS_DUPLEX, 128 * 1024);
|
||||
auto pipeClientClone = duplicateHandle(pipe.client.get());
|
||||
|
||||
auto ownedSignal = duplicateHandle(signal);
|
||||
auto ownedReference = duplicateHandle(reference);
|
||||
@ -338,8 +317,9 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
}
|
||||
CATCH_LOG()
|
||||
|
||||
*in = inPipePseudoConsoleSide.release();
|
||||
*out = outPipePseudoConsoleSide.release();
|
||||
_pipe = std::move(pipe.server);
|
||||
*in = pipe.client.release();
|
||||
*out = pipeClientClone.release();
|
||||
}
|
||||
|
||||
winrt::hstring ConptyConnection::Commandline() const
|
||||
@ -366,9 +346,11 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
|
||||
// If we do not have pipes already, then this is a fresh connection... not an inbound one that is a received
|
||||
// handoff from an already-started PTY process.
|
||||
if (!_inPipe)
|
||||
if (!_pipe)
|
||||
{
|
||||
THROW_IF_FAILED(_CreatePseudoConsoleAndPipes(til::unwrap_coord_size(dimensions), _flags, &_inPipe, &_outPipe, &_hPC));
|
||||
auto pipe = Utils::CreateOverlappedPipe(PIPE_ACCESS_DUPLEX, 128 * 1024);
|
||||
THROW_IF_FAILED(ConptyCreatePseudoConsole(til::unwrap_coord_size(dimensions), pipe.client.get(), pipe.client.get(), _flags, &_hPC));
|
||||
_pipe = std::move(pipe.server);
|
||||
|
||||
if (_initialParentHwnd != 0)
|
||||
{
|
||||
@ -500,10 +482,44 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
return;
|
||||
}
|
||||
|
||||
// convert from UTF-16LE to UTF-8 as ConPty expects UTF-8
|
||||
// TODO GH#3378 reconcile and unify UTF-8 converters
|
||||
auto str = winrt::to_string(data);
|
||||
LOG_IF_WIN32_BOOL_FALSE(WriteFile(_inPipe.get(), str.c_str(), (DWORD)str.length(), nullptr, nullptr));
|
||||
// Ensure a linear and predictable write order, even across multiple threads.
|
||||
// A ticket lock is the perfect fit for this as it acts as first-come-first-serve.
|
||||
std::lock_guard guard{ _writeLock };
|
||||
|
||||
if (_writePending)
|
||||
{
|
||||
_writePending = false;
|
||||
|
||||
DWORD read;
|
||||
if (!GetOverlappedResult(_pipe.get(), &_writeOverlapped, &read, TRUE))
|
||||
{
|
||||
// Not much we can do when the wait fails. This will kill the connection.
|
||||
LOG_LAST_ERROR();
|
||||
_hPC.reset();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (FAILED_LOG(til::u16u8(data, _writeBuffer)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!WriteFile(_pipe.get(), _writeBuffer.data(), gsl::narrow_cast<DWORD>(_writeBuffer.length()), nullptr, &_writeOverlapped))
|
||||
{
|
||||
switch (const auto gle = GetLastError())
|
||||
{
|
||||
case ERROR_BROKEN_PIPE:
|
||||
_hPC.reset();
|
||||
break;
|
||||
case ERROR_IO_PENDING:
|
||||
_writePending = true;
|
||||
break;
|
||||
default:
|
||||
LOG_WIN32(gle);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConptyConnection::Resize(uint32_t rows, uint32_t columns)
|
||||
@ -562,23 +578,17 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
{
|
||||
_transitionToState(ConnectionState::Closing);
|
||||
|
||||
// .reset()ing either of these two will signal ConPTY to send out a CTRL_CLOSE_EVENT to all attached clients.
|
||||
// FYI: The other members of this class are concurrently read by the _hOutputThread
|
||||
// thread running in the background and so they're not safe to be .reset().
|
||||
// This will signal ConPTY to send out a CTRL_CLOSE_EVENT to all attached clients.
|
||||
// Once they're all disconnected it'll close its half of the pipes.
|
||||
_hPC.reset();
|
||||
_inPipe.reset();
|
||||
|
||||
if (_hOutputThread)
|
||||
{
|
||||
// Loop around `CancelSynchronousIo()` just in case the signal to shut down was missed.
|
||||
// This may happen if we called `CancelSynchronousIo()` while not being stuck
|
||||
// in `ReadFile()` and if OpenConsole refuses to exit in a timely manner.
|
||||
// Loop around `CancelIoEx()` just in case the signal to shut down was missed.
|
||||
for (;;)
|
||||
{
|
||||
// ConptyConnection::Close() blocks the UI thread, because `_TerminalOutputHandlers` might indirectly
|
||||
// reference UI objects like `ControlCore`. CancelSynchronousIo() allows us to have the background
|
||||
// thread exit as fast as possible by aborting any ongoing writes coming from OpenConsole.
|
||||
CancelSynchronousIo(_hOutputThread.get());
|
||||
// The output thread may be stuck waiting for the OVERLAPPED to be signaled.
|
||||
CancelIoEx(_pipe.get(), nullptr);
|
||||
|
||||
// Waiting for the output thread to exit ensures that all pending TerminalOutput.raise()
|
||||
// calls have returned and won't notify our caller (ControlCore) anymore. This ensures that
|
||||
@ -588,17 +598,15 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
LOG_LAST_ERROR();
|
||||
}
|
||||
}
|
||||
|
||||
// Now that the background thread is done, we can safely clean up the other system objects, without
|
||||
// race conditions, or fear of deadlocking ourselves (e.g. by calling CloseHandle() on _outPipe).
|
||||
_outPipe.reset();
|
||||
_hOutputThread.reset();
|
||||
_piClient.reset();
|
||||
_pipe.reset();
|
||||
|
||||
// The output thread should have already transitioned us to Closed.
|
||||
// This exists just in case there was no output thread.
|
||||
_transitionToState(ConnectionState::Closed);
|
||||
}
|
||||
CATCH_LOG()
|
||||
@ -645,72 +653,98 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
_LastConPtyClientDisconnected();
|
||||
});
|
||||
|
||||
const wil::unique_event overlappedEvent{ CreateEventExW(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, EVENT_ALL_ACCESS) };
|
||||
OVERLAPPED overlapped{ .hEvent = overlappedEvent.get() };
|
||||
bool overlappedPending = false;
|
||||
char buffer[128 * 1024];
|
||||
DWORD read = 0;
|
||||
|
||||
til::u8state u8State;
|
||||
std::wstring wstr;
|
||||
|
||||
// process the data of the output pipe in a loop
|
||||
while (true)
|
||||
// If we use overlapped IO We want to queue ReadFile() calls before processing the
|
||||
// string, because TerminalOutput.raise() may take a while (relatively speaking).
|
||||
// That's why the loop looks a little weird as it starts a read, processes the
|
||||
// previous string, and finally converts the previous read to the next string.
|
||||
for (;;)
|
||||
{
|
||||
const auto readFail{ !ReadFile(_outPipe.get(), &buffer[0], sizeof(buffer), &read, nullptr) };
|
||||
|
||||
// When we call CancelSynchronousIo() in Close() this is the branch that's taken and gets us out of here.
|
||||
if (_isStateAtOrBeyond(ConnectionState::Closing))
|
||||
// When we have a `wstr` that's ready for processing we must do so without blocking.
|
||||
// Otherwise, whatever the user typed will be delayed until the next IO operation.
|
||||
// With overlapped IO that's not a problem because the ReadFile() calls won't block.
|
||||
if (!ReadFile(_pipe.get(), &buffer[0], sizeof(buffer), &read, &overlapped))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (readFail) // reading failed (we must check this first, because read will also be 0.)
|
||||
{
|
||||
// EXIT POINT
|
||||
const auto lastError = GetLastError();
|
||||
if (lastError == ERROR_BROKEN_PIPE)
|
||||
if (GetLastError() != ERROR_IO_PENDING)
|
||||
{
|
||||
_LastConPtyClientDisconnected();
|
||||
return S_OK;
|
||||
break;
|
||||
}
|
||||
else
|
||||
overlappedPending = true;
|
||||
}
|
||||
|
||||
// wstr can be empty in two situations:
|
||||
// * The previous call to til::u8u16 failed.
|
||||
// * We're using overlapped IO, and it's the first iteration.
|
||||
if (!wstr.empty())
|
||||
{
|
||||
if (!_receivedFirstByte)
|
||||
{
|
||||
_indicateExitWithStatus(HRESULT_FROM_WIN32(lastError)); // print a message
|
||||
_transitionToState(ConnectionState::Failed);
|
||||
return gsl::narrow_cast<DWORD>(HRESULT_FROM_WIN32(lastError));
|
||||
}
|
||||
}
|
||||
|
||||
const auto result{ til::u8u16(std::string_view{ &buffer[0], read }, wstr, u8State) };
|
||||
if (FAILED(result))
|
||||
{
|
||||
// EXIT POINT
|
||||
_indicateExitWithStatus(result); // print a message
|
||||
_transitionToState(ConnectionState::Failed);
|
||||
return gsl::narrow_cast<DWORD>(result);
|
||||
}
|
||||
|
||||
if (wstr.empty())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!_receivedFirstByte)
|
||||
{
|
||||
const auto now = std::chrono::high_resolution_clock::now();
|
||||
const std::chrono::duration<double> delta = now - _startTime;
|
||||
const auto now = std::chrono::high_resolution_clock::now();
|
||||
const std::chrono::duration<double> delta = now - _startTime;
|
||||
|
||||
#pragma warning(suppress : 26477 26485 26494 26482 26446) // We don't control TraceLoggingWrite
|
||||
TraceLoggingWrite(g_hTerminalConnectionProvider,
|
||||
"ReceivedFirstByte",
|
||||
TraceLoggingDescription("An event emitted when the connection receives the first byte"),
|
||||
TraceLoggingGuid(_sessionId, "SessionGuid", "The WT_SESSION's GUID"),
|
||||
TraceLoggingFloat64(delta.count(), "Duration"),
|
||||
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
|
||||
TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));
|
||||
_receivedFirstByte = true;
|
||||
TraceLoggingWrite(g_hTerminalConnectionProvider,
|
||||
"ReceivedFirstByte",
|
||||
TraceLoggingDescription("An event emitted when the connection receives the first byte"),
|
||||
TraceLoggingGuid(_sessionId, "SessionGuid", "The WT_SESSION's GUID"),
|
||||
TraceLoggingFloat64(delta.count(), "Duration"),
|
||||
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
|
||||
TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));
|
||||
_receivedFirstByte = true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
TerminalOutput.raise(wstr);
|
||||
}
|
||||
CATCH_LOG();
|
||||
}
|
||||
|
||||
// Pass the output to our registered event handlers
|
||||
TerminalOutput.raise(wstr);
|
||||
// Here's the counterpart to the start of the loop. We processed whatever was in `wstr`,
|
||||
// so blocking synchronously on the pipe is now possible.
|
||||
// If we used overlapped IO, we need to wait for the ReadFile() to complete.
|
||||
// If we didn't, we can now safely block on our ReadFile() call.
|
||||
if (overlappedPending)
|
||||
{
|
||||
overlappedPending = false;
|
||||
if (FAILED(Utils::GetOverlappedResultSameThread(&overlapped, &read)))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// winsock2 (WSA) handles of the \Device\Afd type are transparently compatible with
|
||||
// ReadFile() and the WSARecv() documentations contains this important information:
|
||||
// > For byte streams, zero bytes having been read [..] indicates graceful closure and that no more bytes will ever be read.
|
||||
// --> Exit if we've read 0 bytes.
|
||||
if (read == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (_isStateAtOrBeyond(ConnectionState::Closing))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
TraceLoggingWrite(
|
||||
g_hTerminalConnectionProvider,
|
||||
"ReadFile",
|
||||
TraceLoggingCountedUtf8String(&buffer[0], read, "buffer"),
|
||||
TraceLoggingGuid(_sessionId, "session"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
|
||||
// If we hit a parsing error, eat it. It's bad utf-8, we can't do anything with it.
|
||||
FAILED_LOG(til::u8u16({ &buffer[0], gsl::narrow_cast<size_t>(read) }, wstr, u8State));
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
@ -5,9 +5,10 @@
|
||||
|
||||
#include "ConptyConnection.g.h"
|
||||
#include "BaseTerminalConnection.h"
|
||||
|
||||
#include "ITerminalHandoff.h"
|
||||
|
||||
#include <til/env.h>
|
||||
#include <til/ticket_lock.h>
|
||||
|
||||
namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
{
|
||||
@ -74,12 +75,17 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
bool _receivedFirstByte{ false };
|
||||
std::chrono::high_resolution_clock::time_point _startTime{};
|
||||
|
||||
wil::unique_hfile _inPipe; // The pipe for writing input to
|
||||
wil::unique_hfile _outPipe; // The pipe for reading output from
|
||||
wil::unique_hfile _pipe;
|
||||
wil::unique_handle _hOutputThread;
|
||||
wil::unique_process_information _piClient;
|
||||
wil::unique_any<HPCON, decltype(closePseudoConsoleAsync), closePseudoConsoleAsync> _hPC;
|
||||
|
||||
til::ticket_lock _writeLock;
|
||||
wil::unique_event _writeOverlappedEvent;
|
||||
OVERLAPPED _writeOverlapped{};
|
||||
std::string _writeBuffer;
|
||||
bool _writePending = false;
|
||||
|
||||
DWORD _flags{ 0 };
|
||||
|
||||
til::env _initialEnv{};
|
||||
|
||||
@ -52,14 +52,6 @@ void Terminal::Create(til::size viewportSize, til::CoordType scrollbackLines, Re
|
||||
auto dispatch = std::make_unique<AdaptDispatch>(*this, &renderer, _renderSettings, _terminalInput);
|
||||
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
||||
_stateMachine = std::make_unique<StateMachine>(std::move(engine));
|
||||
|
||||
// Until we have a true pass-through mode (GH#1173), the decision as to
|
||||
// whether C1 controls are interpreted or not is made at the conhost level.
|
||||
// If they are being filtered out, then we will simply never receive them.
|
||||
// But if they are being accepted by conhost, there's a chance they may get
|
||||
// passed through in some situations, so it's important that our state
|
||||
// machine is always prepared to accept them.
|
||||
_stateMachine->SetParserMode(StateMachine::Mode::AlwaysAcceptC1, true);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
||||
@ -47,7 +47,6 @@ namespace TerminalCoreUnitTests
|
||||
{
|
||||
class TerminalBufferTests;
|
||||
class TerminalApiTest;
|
||||
class ConptyRoundtripTests;
|
||||
class ScrollTest;
|
||||
};
|
||||
#endif
|
||||
@ -150,7 +149,6 @@ public:
|
||||
void UseAlternateScreenBuffer(const TextAttribute& attrs) override;
|
||||
void UseMainScreenBuffer() override;
|
||||
|
||||
bool IsConsolePty() const noexcept override;
|
||||
bool IsVtInputEnabled() const noexcept override;
|
||||
void NotifyAccessibilityChange(const til::rect& changedRect) noexcept override;
|
||||
void NotifyBufferRotation(const int delta) override;
|
||||
@ -480,7 +478,6 @@ private:
|
||||
#ifdef UNIT_TESTING
|
||||
friend class TerminalCoreUnitTests::TerminalBufferTests;
|
||||
friend class TerminalCoreUnitTests::TerminalApiTest;
|
||||
friend class TerminalCoreUnitTests::ConptyRoundtripTests;
|
||||
friend class TerminalCoreUnitTests::ScrollTest;
|
||||
#endif
|
||||
};
|
||||
|
||||
@ -310,11 +310,6 @@ void Terminal::ShowWindow(bool showOrHide)
|
||||
}
|
||||
}
|
||||
|
||||
bool Terminal::IsConsolePty() const noexcept
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Terminal::IsVtInputEnabled() const noexcept
|
||||
{
|
||||
return false;
|
||||
|
||||
@ -273,8 +273,6 @@ namespace ControlUnitTests
|
||||
// contents. ConPTY will handle the actual clearing of the buffer
|
||||
// contents. We can only ensure that the viewport moved when we did a
|
||||
// clear scrollback.
|
||||
//
|
||||
// The ConptyRoundtripTests test the actual clearing of the contents.
|
||||
}
|
||||
void ControlCoreTests::TestClearScreen()
|
||||
{
|
||||
@ -312,8 +310,6 @@ namespace ControlUnitTests
|
||||
// contents. ConPTY will handle the actual clearing of the buffer
|
||||
// contents. We can only ensure that the viewport moved when we did a
|
||||
// clear scrollback.
|
||||
//
|
||||
// The ConptyRoundtripTests test the actual clearing of the contents.
|
||||
}
|
||||
void ControlCoreTests::TestClearAll()
|
||||
{
|
||||
@ -351,8 +347,6 @@ namespace ControlUnitTests
|
||||
// contents. ConPTY will handle the actual clearing of the buffer
|
||||
// contents. We can only ensure that the viewport moved when we did a
|
||||
// clear scrollback.
|
||||
//
|
||||
// The ConptyRoundtripTests test the actual clearing of the contents.
|
||||
}
|
||||
|
||||
void ControlCoreTests::TestReadEntireBuffer()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -34,7 +34,6 @@ namespace
|
||||
HRESULT StartPaint() noexcept { return S_OK; }
|
||||
HRESULT EndPaint() noexcept { return S_OK; }
|
||||
HRESULT Present() noexcept { return S_OK; }
|
||||
HRESULT PrepareForTeardown(_Out_ bool* /*pForcePaint*/) noexcept { return S_OK; }
|
||||
HRESULT ScrollFrame() noexcept { return S_OK; }
|
||||
HRESULT Invalidate(const til::rect* /*psrRegion*/) noexcept { return S_OK; }
|
||||
HRESULT InvalidateCursor(const til::rect* /*psrRegion*/) noexcept { return S_OK; }
|
||||
|
||||
@ -23,7 +23,6 @@
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="TerminalApiTest.cpp" />
|
||||
<ClCompile Include="ConptyRoundtripTests.cpp" />
|
||||
<ClCompile Include="TerminalBufferTests.cpp" />
|
||||
<ClCompile Include="ScrollTest.cpp" />
|
||||
<ClCompile Include="TilWinRtHelpersTests.cpp" />
|
||||
@ -47,45 +46,6 @@
|
||||
<ProjectReference Include="..\TerminalCore\lib\TerminalCore-lib.vcxproj">
|
||||
<Project>{ca5cad1a-abcd-429c-b551-8562ec954746}</Project>
|
||||
</ProjectReference>
|
||||
|
||||
<!-- The following are all Console Host (host.lib) dependencies. We're
|
||||
including them for the ConptyRoundtripTests, which instantiate a console
|
||||
host, then user the output from conpty to dump directly into a Terminal,
|
||||
and make sure the buffer contents align. -->
|
||||
|
||||
<ProjectReference Include="..\..\renderer\vt\ut_lib\vt.unittest.vcxproj">
|
||||
<Project>{990F2657-8580-4828-943F-5DD657D11843}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\renderer\base\lib\base.vcxproj">
|
||||
<Project>{af0a096a-8b3a-4949-81ef-7df8f0fee91f}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="$(OpenConsoleDir)\src\host\ut_lib\host.unittest.vcxproj">
|
||||
<Project>{06ec74cb-9a12-429c-b551-8562ec954746}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\propslib\propslib.vcxproj">
|
||||
<Project>{345fd5a4-b32b-4f29-bd1c-b033bd2c35cc}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\interactivity\base\lib\InteractivityBase.vcxproj">
|
||||
<Project>{06ec74cb-9a12-429c-b551-8562ec964846}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\interactivity\win32\lib\win32.LIB.vcxproj">
|
||||
<Project>{06ec74cb-9a12-429c-b551-8532ec964726}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\tsf\tsf.vcxproj">
|
||||
<Project>{2fd12fbb-1ddb-46d8-b818-1023c624caca}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\server\lib\server.vcxproj">
|
||||
<Project>{18d09a24-8240-42d6-8cb6-236eee820262}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\terminal\adapter\lib\adapter.vcxproj">
|
||||
<Project>{dcf55140-ef6a-4736-a403-957e4f7430bb}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\internal\internal.vcxproj">
|
||||
<Project>{ef3e32a7-5ff6-42b4-b6e2-96cd7d033f00}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\renderer\gdi\lib\gdi.vcxproj">
|
||||
<Project>{1c959542-bac2-4e55-9a6d-13251914cbb9}</Project>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="MockTermSettings.h" />
|
||||
|
||||
@ -141,7 +141,7 @@
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PrecompiledHeaderFile>precomp.h</PrecompiledHeaderFile>
|
||||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)\src\inc;$(SolutionDir)\dep;$(SolutionDir)\dep\Console;$(SolutionDir)\dep\Win32K;$(SolutionDir)\oss\chromium;$(SolutionDir)\oss\dynamic_bitset;$(SolutionDir)\oss\interval_tree;$(SolutionDir)\oss\libpopcnt;$(SolutionDir)\oss\pcg\include;%(AdditionalIncludeDirectories);</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)\src\inc;$(SolutionDir)\dep;$(SolutionDir)\dep\Console;$(SolutionDir)\dep\Win32K;$(SolutionDir)\oss\chromium;$(SolutionDir)\oss\interval_tree;$(SolutionDir)\oss\pcg\include;%(AdditionalIncludeDirectories);</AdditionalIncludeDirectories>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<MinimalRebuild>false</MinimalRebuild>
|
||||
<RuntimeTypeInfo>false</RuntimeTypeInfo>
|
||||
|
||||
@ -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";
|
||||
const std::wstring_view ConsoleArguments::COM_SERVER_ARG = L"-Embedding";
|
||||
@ -495,12 +494,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 == GLYPH_WIDTH)
|
||||
{
|
||||
hr = s_GetArgumentValue(args, i, &_textMeasurement);
|
||||
@ -652,10 +645,6 @@ bool ConsoleArguments::GetInheritCursor() const
|
||||
{
|
||||
return _inheritCursor;
|
||||
}
|
||||
bool ConsoleArguments::IsResizeQuirkEnabled() const
|
||||
{
|
||||
return _resizeQuirk;
|
||||
}
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
// Method Description:
|
||||
|
||||
@ -46,7 +46,6 @@ public:
|
||||
|
||||
std::wstring GetOriginalCommandLine() const;
|
||||
std::wstring GetClientCommandline() const;
|
||||
std::wstring GetVtMode() const;
|
||||
const std::wstring& GetTextMeasurement() const;
|
||||
bool GetForceV1() const;
|
||||
bool GetForceNoHandoff() const;
|
||||
@ -54,7 +53,6 @@ public:
|
||||
short GetWidth() const;
|
||||
short GetHeight() const;
|
||||
bool GetInheritCursor() const;
|
||||
bool IsResizeQuirkEnabled() const;
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
void EnableConptyModeForTests();
|
||||
@ -72,7 +70,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;
|
||||
static const std::wstring_view COM_SERVER_ARG;
|
||||
@ -107,7 +104,6 @@ private:
|
||||
_serverHandle(serverHandle),
|
||||
_signalHandle(signalHandle),
|
||||
_inheritCursor(inheritCursor),
|
||||
_resizeQuirk(false),
|
||||
_runAsComServer{ runAsComServer }
|
||||
{
|
||||
}
|
||||
@ -135,7 +131,6 @@ private:
|
||||
DWORD _serverHandle;
|
||||
DWORD _signalHandle;
|
||||
bool _inheritCursor;
|
||||
bool _resizeQuirk{ false };
|
||||
|
||||
[[nodiscard]] HRESULT _GetClientCommandline(_Inout_ std::vector<std::wstring>& args,
|
||||
const size_t index,
|
||||
|
||||
@ -82,10 +82,8 @@ void CursorBlinker::TimerRoutine(SCREEN_INFORMATION& ScreenInfo) const noexcept
|
||||
auto& cursor = buffer.GetCursor();
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
auto* const pAccessibilityNotifier = ServiceLocator::LocateAccessibilityNotifier();
|
||||
const auto inConpty{ gci.IsInVtIoMode() };
|
||||
|
||||
// GH#2988: ConPTY can now be focused, but it doesn't need to do any of this work either.
|
||||
if (inConpty || !WI_IsFlagSet(gci.Flags, CONSOLE_HAS_FOCUS))
|
||||
if (!WI_IsFlagSet(gci.Flags, CONSOLE_HAS_FOCUS))
|
||||
{
|
||||
goto DoScroll;
|
||||
}
|
||||
@ -165,6 +163,12 @@ DoScroll:
|
||||
// guCaretBlinkTime is -1.
|
||||
void CursorBlinker::SetCaretTimer() const noexcept
|
||||
{
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
if (gci.IsInVtIoMode())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using filetime_duration = std::chrono::duration<int64_t, std::ratio<1, 10000000>>;
|
||||
static constexpr DWORD dwDefTimeout = 0x212;
|
||||
|
||||
|
||||
@ -179,11 +179,7 @@ void PtySignalInputThread::_DoResizeWindow(const ResizeWindowData& data)
|
||||
return;
|
||||
}
|
||||
|
||||
if (_api.ResizeWindow(data.sx, data.sy))
|
||||
{
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
THROW_IF_FAILED(gci.GetVtIo()->SuppressResizeRepaint());
|
||||
}
|
||||
_api.ResizeWindow(data.sx, data.sy);
|
||||
}
|
||||
|
||||
void PtySignalInputThread::_DoClearBuffer() const
|
||||
|
||||
@ -55,54 +55,112 @@ DWORD WINAPI VtInputThread::StaticVtInputThreadProc(_In_ LPVOID lpParameter)
|
||||
void VtInputThread::_InputThread()
|
||||
{
|
||||
const auto cleanup = wil::scope_exit([this]() {
|
||||
ServiceLocator::LocateGlobals().getConsoleInformation().GetVtIo()->CloseInput();
|
||||
_hFile.reset();
|
||||
ServiceLocator::LocateGlobals().getConsoleInformation().GetVtIo()->SendCloseEvent();
|
||||
});
|
||||
|
||||
OVERLAPPED* overlapped = nullptr;
|
||||
OVERLAPPED overlappedBuf{};
|
||||
wil::unique_event overlappedEvent;
|
||||
bool overlappedPending = false;
|
||||
char buffer[4096];
|
||||
DWORD dwRead = 0;
|
||||
DWORD read = 0;
|
||||
|
||||
til::u8state u8State;
|
||||
std::wstring wstr;
|
||||
|
||||
if (Utils::HandleWantsOverlappedIo(_hFile.get()))
|
||||
{
|
||||
overlappedEvent.reset(CreateEventExW(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, EVENT_ALL_ACCESS));
|
||||
if (overlappedEvent)
|
||||
{
|
||||
overlappedBuf.hEvent = overlappedEvent.get();
|
||||
overlapped = &overlappedBuf;
|
||||
}
|
||||
}
|
||||
|
||||
// If we use overlapped IO We want to queue ReadFile() calls before processing the
|
||||
// string, because LockConsole/ProcessString may take a while (relatively speaking).
|
||||
// That's why the loop looks a little weird as it starts a read, processes the
|
||||
// previous string, and finally converts the previous read to the next string.
|
||||
for (;;)
|
||||
{
|
||||
const auto ok = ReadFile(_hFile.get(), buffer, ARRAYSIZE(buffer), &dwRead, nullptr);
|
||||
// When we have a `wstr` that's ready for processing we must do so without blocking.
|
||||
// Otherwise, whatever the user typed will be delayed until the next IO operation.
|
||||
// With overlapped IO that's not a problem because the ReadFile() calls won't block.
|
||||
if (overlapped)
|
||||
{
|
||||
if (!ReadFile(_hFile.get(), &buffer[0], sizeof(buffer), &read, overlapped))
|
||||
{
|
||||
if (GetLastError() != ERROR_IO_PENDING)
|
||||
{
|
||||
break;
|
||||
}
|
||||
overlappedPending = true;
|
||||
}
|
||||
}
|
||||
|
||||
// The ReadFile() documentations calls out that:
|
||||
// > If the lpNumberOfBytesRead parameter is zero when ReadFile returns TRUE on a pipe, the other
|
||||
// > end of the pipe called the WriteFile function with nNumberOfBytesToWrite set to zero.
|
||||
// But I was unable to replicate any such behavior. I'm not sure it's true anymore.
|
||||
//
|
||||
// However, what the documentations fails to mention is that winsock2 (WSA) handles of the \Device\Afd type are
|
||||
// transparently compatible with ReadFile() and the WSARecv() documentations contains this important information:
|
||||
// wstr can be empty in two situations:
|
||||
// * The previous call to til::u8u16 failed.
|
||||
// * We're using overlapped IO, and it's the first iteration.
|
||||
if (!wstr.empty())
|
||||
{
|
||||
try
|
||||
{
|
||||
// Make sure to call the GLOBAL Lock/Unlock, not the gci's lock/unlock.
|
||||
// Only the global unlock attempts to dispatch ctrl events. If you use the
|
||||
// gci's unlock, when you press C-c, it won't be dispatched until the
|
||||
// next console API call. For something like `powershell sleep 60`,
|
||||
// that won't happen for 60s
|
||||
LockConsole();
|
||||
const auto unlock = wil::scope_exit([&] { UnlockConsole(); });
|
||||
|
||||
_pInputStateMachine->ProcessString(wstr);
|
||||
}
|
||||
CATCH_LOG();
|
||||
}
|
||||
|
||||
// Here's the counterpart to the start of the loop. We processed whatever was in `wstr`,
|
||||
// so blocking synchronously on the pipe is now possible.
|
||||
// If we used overlapped IO, we need to wait for the ReadFile() to complete.
|
||||
// If we didn't, we can now safely block on our ReadFile() call.
|
||||
if (overlapped)
|
||||
{
|
||||
if (overlappedPending)
|
||||
{
|
||||
overlappedPending = false;
|
||||
if (FAILED(Utils::GetOverlappedResultSameThread(overlapped, &read)))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!ReadFile(_hFile.get(), &buffer[0], sizeof(buffer), &read, overlapped))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// winsock2 (WSA) handles of the \Device\Afd type are transparently compatible with
|
||||
// ReadFile() and the WSARecv() documentations contains this important information:
|
||||
// > For byte streams, zero bytes having been read [..] indicates graceful closure and that no more bytes will ever be read.
|
||||
// In other words, for pipe HANDLE of unknown type you should consider `lpNumberOfBytesRead == 0` as an exit indicator.
|
||||
//
|
||||
// Here, `dwRead == 0` fixes a deadlock when exiting conhost while being in use by WSL whose hypervisor pipes are WSA.
|
||||
if (!ok || dwRead == 0)
|
||||
// --> Exit if we've read 0 bytes.
|
||||
if (read == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
TraceLoggingWrite(
|
||||
g_hConhostV2EventTraceProvider,
|
||||
"ConPTY ReadFile",
|
||||
TraceLoggingCountedUtf8String(&buffer[0], read, "buffer"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
|
||||
// If we hit a parsing error, eat it. It's bad utf-8, we can't do anything with it.
|
||||
if (FAILED_LOG(til::u8u16({ buffer, gsl::narrow_cast<size_t>(dwRead) }, wstr, u8State)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Make sure to call the GLOBAL Lock/Unlock, not the gci's lock/unlock.
|
||||
// Only the global unlock attempts to dispatch ctrl events. If you use the
|
||||
// gci's unlock, when you press C-c, it won't be dispatched until the
|
||||
// next console API call. For something like `powershell sleep 60`,
|
||||
// that won't happen for 60s
|
||||
LockConsole();
|
||||
const auto unlock = wil::scope_exit([&] { UnlockConsole(); });
|
||||
|
||||
_pInputStateMachine->ProcessString(wstr);
|
||||
}
|
||||
CATCH_LOG();
|
||||
FAILED_LOG(til::u8u16({ &buffer[0], gsl::narrow_cast<size_t>(read) }, wstr, u8State));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -4,11 +4,12 @@
|
||||
#include "precomp.h"
|
||||
#include "VtIo.hpp"
|
||||
|
||||
#include <til/unicode.h>
|
||||
|
||||
#include "handle.h" // LockConsole
|
||||
#include "output.h" // CloseConsoleProcessState
|
||||
#include "../interactivity/inc/ServiceLocator.hpp"
|
||||
#include "../renderer/base/renderer.hpp"
|
||||
#include "../renderer/vt/Xterm256Engine.hpp"
|
||||
#include "../types/inc/CodepointWidthDetector.hpp"
|
||||
#include "../types/inc/utils.hpp"
|
||||
|
||||
@ -19,16 +20,9 @@ using namespace Microsoft::Console::Types;
|
||||
using namespace Microsoft::Console::Utils;
|
||||
using namespace Microsoft::Console::Interactivity;
|
||||
|
||||
VtIo::VtIo() :
|
||||
_initialized(false),
|
||||
_lookingForCursorPosition(false)
|
||||
{
|
||||
}
|
||||
|
||||
[[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())
|
||||
@ -90,6 +84,16 @@ VtIo::VtIo() :
|
||||
_hOutput.reset(OutHandle);
|
||||
_hSignal.reset(SignalHandle);
|
||||
|
||||
if (Utils::HandleWantsOverlappedIo(_hOutput.get()))
|
||||
{
|
||||
_overlappedEvent.reset(CreateEventExW(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, EVENT_ALL_ACCESS));
|
||||
if (_overlappedEvent)
|
||||
{
|
||||
_overlappedBuf.hEvent = _overlappedEvent.get();
|
||||
_overlapped = &_overlappedBuf;
|
||||
}
|
||||
}
|
||||
|
||||
// The only way we're initialized is if the args said we're in conpty mode.
|
||||
// If the args say so, then at least one of in, out, or signal was specified
|
||||
_initialized = true;
|
||||
@ -97,7 +101,7 @@ VtIo::VtIo() :
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Create the VtRenderer and the VtInputThread for this console.
|
||||
// - Create the VtEngine and the VtInputThread for this console.
|
||||
// MUST BE DONE AFTER CONSOLE IS INITIALIZED, to make sure we've gotten the
|
||||
// buffer size from the attached client application.
|
||||
// Arguments:
|
||||
@ -112,11 +116,9 @@ VtIo::VtIo() :
|
||||
{
|
||||
return S_FALSE;
|
||||
}
|
||||
auto& globals = ServiceLocator::LocateGlobals();
|
||||
|
||||
const auto& gci = globals.getConsoleInformation();
|
||||
// SetWindowVisibility uses the console lock to protect access to _pVtRenderEngine.
|
||||
assert(gci.IsConsoleLocked());
|
||||
assert(ServiceLocator::LocateGlobals().getConsoleInformation().IsConsoleLocked());
|
||||
|
||||
try
|
||||
{
|
||||
@ -124,21 +126,6 @@ VtIo::VtIo() :
|
||||
{
|
||||
_pVtInputThread = std::make_unique<VtInputThread>(std::move(_hInput), _lookingForCursorPosition);
|
||||
}
|
||||
|
||||
if (IsValidHandle(_hOutput.get()))
|
||||
{
|
||||
auto initialViewport = Viewport::FromDimensions({ 0, 0 }, gci.GetWindowSize());
|
||||
|
||||
auto xterm256Engine = std::make_unique<Xterm256Engine>(std::move(_hOutput),
|
||||
initialViewport);
|
||||
_pVtRenderEngine = std::move(xterm256Engine);
|
||||
|
||||
if (_pVtRenderEngine)
|
||||
{
|
||||
_pVtRenderEngine->SetTerminalOwner(this);
|
||||
_pVtRenderEngine->SetResizeQuirk(_resizeQuirk);
|
||||
}
|
||||
}
|
||||
}
|
||||
CATCH_RETURN();
|
||||
|
||||
@ -167,48 +154,48 @@ bool VtIo::IsUsingVt() const
|
||||
{
|
||||
return S_FALSE;
|
||||
}
|
||||
auto& g = ServiceLocator::LocateGlobals();
|
||||
|
||||
if (_pVtRenderEngine)
|
||||
{
|
||||
try
|
||||
{
|
||||
g.pRender->AddRenderEngine(_pVtRenderEngine.get());
|
||||
g.getConsoleInformation().GetActiveOutputBuffer().SetTerminalConnection(_pVtRenderEngine.get());
|
||||
|
||||
// Force the whole window to be put together first.
|
||||
// We don't really need the handle, we just want to leverage the setup steps.
|
||||
ServiceLocator::LocatePseudoWindow();
|
||||
}
|
||||
CATCH_RETURN();
|
||||
}
|
||||
|
||||
if (_pVtInputThread)
|
||||
{
|
||||
LOG_IF_FAILED(_pVtInputThread->Start());
|
||||
}
|
||||
|
||||
// MSFT: 15813316
|
||||
// If the terminal application wants us to inherit the cursor position,
|
||||
// we're going to emit a VT sequence to ask for the cursor position, then
|
||||
// wait 3s until we get a response.
|
||||
// If we get a response, the InteractDispatch will call SetCursorPosition,
|
||||
// which will call to our VtIo::SetCursorPosition method.
|
||||
if (_lookingForCursorPosition && _pVtRenderEngine && _pVtInputThread)
|
||||
{
|
||||
_lookingForCursorPosition = false;
|
||||
LOG_IF_FAILED(_pVtRenderEngine->RequestCursor());
|
||||
auto writer = GetWriter();
|
||||
|
||||
// Allow the input thread to momentarily gain the console lock.
|
||||
const auto suspension = g.getConsoleInformation().SuspendLock();
|
||||
_pVtInputThread->WaitUntilDSR(3000);
|
||||
// GH#4999 - Send a sequence to the connected terminal to request
|
||||
// win32-input-mode from them. This will enable the connected terminal to
|
||||
// send us full INPUT_RECORDs as input. If the terminal doesn't understand
|
||||
// this sequence, it'll just ignore it.
|
||||
|
||||
writer.WriteUTF8(
|
||||
"\033[?1004h" // Focus Event Mode
|
||||
"\033[?9001h" // Win32 Input Mode
|
||||
);
|
||||
|
||||
// MSFT: 15813316
|
||||
// If the terminal application wants us to inherit the cursor position,
|
||||
// we're going to emit a VT sequence to ask for the cursor position, then
|
||||
// wait 1s until we get a response.
|
||||
// If we get a response, the InteractDispatch will call SetCursorPosition,
|
||||
// which will call to our VtIo::SetCursorPosition method.
|
||||
if (_lookingForCursorPosition)
|
||||
{
|
||||
writer.WriteUTF8("\x1b[6n"); // Cursor Position Report (DSR CPR)
|
||||
}
|
||||
|
||||
writer.Submit();
|
||||
}
|
||||
|
||||
// GH#4999 - Send a sequence to the connected terminal to request
|
||||
// win32-input-mode from them. This will enable the connected terminal to
|
||||
// send us full INPUT_RECORDs as input. If the terminal doesn't understand
|
||||
// this sequence, it'll just ignore it.
|
||||
LOG_IF_FAILED(_pVtRenderEngine->RequestWin32Input());
|
||||
if (_lookingForCursorPosition)
|
||||
{
|
||||
_lookingForCursorPosition = false;
|
||||
|
||||
// Allow the input thread to momentarily gain the console lock.
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
const auto suspension = gci.SuspendLock();
|
||||
_pVtInputThread->WaitUntilDSR(3000);
|
||||
}
|
||||
|
||||
if (_pPtySignalInputThread)
|
||||
{
|
||||
@ -247,25 +234,6 @@ void VtIo::CreatePseudoWindow()
|
||||
}
|
||||
}
|
||||
|
||||
void VtIo::SetWindowVisibility(bool showOrHide) noexcept
|
||||
{
|
||||
auto& gci = ::Microsoft::Console::Interactivity::ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
|
||||
gci.LockConsole();
|
||||
auto unlock = wil::scope_exit([&] { gci.UnlockConsole(); });
|
||||
|
||||
// ConsoleInputThreadProcWin32 calls VtIo::CreatePseudoWindow,
|
||||
// which calls CreateWindowExW, which causes a WM_SIZE message.
|
||||
// In short, this function might be called before _pVtRenderEngine exists.
|
||||
// See PtySignalInputThread::CreatePseudoWindow().
|
||||
if (!_pVtRenderEngine)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_IF_FAILED(_pVtRenderEngine->SetWindowVisibility(showOrHide));
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Create and start the signal thread. The signal thread can be created
|
||||
// independent of the i/o threads, and doesn't require a client first
|
||||
@ -302,69 +270,6 @@ void VtIo::SetWindowVisibility(bool showOrHide) noexcept
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Prevent the renderer from emitting output on the next resize. This prevents
|
||||
// the host from echoing a resize to the terminal that requested it.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - S_OK if the renderer successfully suppressed the next repaint, otherwise an
|
||||
// appropriate HRESULT indicating failure.
|
||||
[[nodiscard]] HRESULT VtIo::SuppressResizeRepaint()
|
||||
{
|
||||
auto hr = S_OK;
|
||||
if (_pVtRenderEngine)
|
||||
{
|
||||
hr = _pVtRenderEngine->SuppressResizeRepaint();
|
||||
}
|
||||
return hr;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Attempts to set the initial cursor position, if we're looking for it.
|
||||
// If we're not trying to inherit the cursor, does nothing.
|
||||
// Arguments:
|
||||
// - coordCursor: The initial position of the cursor.
|
||||
// Return Value:
|
||||
// - S_OK if we successfully inherited the cursor or did nothing, else an
|
||||
// appropriate HRESULT
|
||||
[[nodiscard]] HRESULT VtIo::SetCursorPosition(const til::point coordCursor)
|
||||
{
|
||||
auto hr = S_OK;
|
||||
if (_lookingForCursorPosition)
|
||||
{
|
||||
if (_pVtRenderEngine)
|
||||
{
|
||||
hr = _pVtRenderEngine->InheritCursor(coordCursor);
|
||||
}
|
||||
|
||||
_lookingForCursorPosition = false;
|
||||
}
|
||||
return hr;
|
||||
}
|
||||
|
||||
[[nodiscard]] HRESULT VtIo::SwitchScreenBuffer(const bool useAltBuffer)
|
||||
{
|
||||
auto hr = S_OK;
|
||||
if (_pVtRenderEngine)
|
||||
{
|
||||
hr = _pVtRenderEngine->SwitchScreenBuffer(useAltBuffer);
|
||||
}
|
||||
return hr;
|
||||
}
|
||||
|
||||
void VtIo::CloseInput()
|
||||
{
|
||||
_pVtInputThread = nullptr;
|
||||
SendCloseEvent();
|
||||
}
|
||||
|
||||
void VtIo::CloseOutput()
|
||||
{
|
||||
auto& g = ServiceLocator::LocateGlobals();
|
||||
g.getConsoleInformation().GetActiveOutputBuffer().SetTerminalConnection(nullptr);
|
||||
}
|
||||
|
||||
void VtIo::SendCloseEvent()
|
||||
{
|
||||
LockConsole();
|
||||
@ -379,73 +284,16 @@ void VtIo::SendCloseEvent()
|
||||
}
|
||||
}
|
||||
|
||||
// The name of this method is an analogy to TCP_CORK. It instructs
|
||||
// the VT renderer to stop flushing its buffer to the output pipe.
|
||||
// Don't forget to uncork it!
|
||||
void VtIo::CorkRenderer(bool corked) const noexcept
|
||||
// Returns true for C0 characters and C1 [single-character] CSI.
|
||||
// A copy of isActionableFromGround() from stateMachine.cpp.
|
||||
static constexpr bool IsControlCharacter(wchar_t wch) noexcept
|
||||
{
|
||||
_pVtRenderEngine->Cork(corked);
|
||||
}
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
// Method Description:
|
||||
// - This is a test helper method. It can be used to trick VtIo into responding
|
||||
// true to `IsUsingVt`, which will cause the console host to act in conpty
|
||||
// mode.
|
||||
// Arguments:
|
||||
// - vtRenderEngine: a VT renderer that our VtIo should use as the vt engine during these tests
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void VtIo::EnableConptyModeForTests(std::unique_ptr<Microsoft::Console::Render::VtEngine> vtRenderEngine, const bool resizeQuirk)
|
||||
{
|
||||
_initialized = true;
|
||||
_resizeQuirk = resizeQuirk;
|
||||
_pVtRenderEngine = std::move(vtRenderEngine);
|
||||
}
|
||||
#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 its 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;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Manually tell the renderer that it should emit a "Erase Scrollback"
|
||||
// sequence to the connected terminal. We need to do this in certain cases
|
||||
// that we've identified where we believe the client wanted the entire
|
||||
// terminal buffer cleared, not just the viewport. For more information, see
|
||||
// GH#3126.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - S_OK if we wrote the sequences successfully, otherwise an appropriate HRESULT
|
||||
[[nodiscard]] HRESULT VtIo::ManuallyClearScrollback() const noexcept
|
||||
{
|
||||
if (_pVtRenderEngine)
|
||||
{
|
||||
return _pVtRenderEngine->ManuallyClearScrollback();
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
[[nodiscard]] HRESULT VtIo::RequestMouseMode(bool enable) const noexcept
|
||||
{
|
||||
if (_pVtRenderEngine)
|
||||
{
|
||||
return _pVtRenderEngine->RequestMouseMode(enable);
|
||||
}
|
||||
return S_OK;
|
||||
// This is equivalent to:
|
||||
// return (wch <= 0x1f) || (wch >= 0x7f && wch <= 0x9f);
|
||||
// It's written like this to get MSVC to emit optimal assembly for findActionableFromGround.
|
||||
// It lacks the ability to turn boolean operators into binary operations and also happens
|
||||
// to fail to optimize the printable-ASCII range check into a subtraction & comparison.
|
||||
return (wch <= 0x1f) | (static_cast<wchar_t>(wch - 0x7f) <= 0x20);
|
||||
}
|
||||
|
||||
// Formats the given console attributes to their closest VT equivalent.
|
||||
@ -510,3 +358,411 @@ void VtIo::FormatAttributes(std::wstring& target, const TextAttribute& attribute
|
||||
|
||||
target.append(bufW, len);
|
||||
}
|
||||
|
||||
VtIo::Writer VtIo::GetWriter() noexcept
|
||||
{
|
||||
_corked += 1;
|
||||
return Writer{ this };
|
||||
}
|
||||
|
||||
VtIo::Writer::Writer(VtIo* io) noexcept :
|
||||
_io{ io }
|
||||
{
|
||||
}
|
||||
|
||||
VtIo::Writer::~Writer() noexcept
|
||||
{
|
||||
// If _io is non-null, then we didn't call Submit, e.g. because of an exception.
|
||||
// We need to avoid flushing the buffer in that case.
|
||||
if (_io)
|
||||
{
|
||||
_io->_writerTainted = true;
|
||||
_io->_uncork();
|
||||
}
|
||||
}
|
||||
|
||||
VtIo::Writer::Writer(Writer&& other) noexcept :
|
||||
_io{ std::exchange(other._io, nullptr) }
|
||||
{
|
||||
}
|
||||
|
||||
VtIo::Writer& VtIo::Writer::operator=(Writer&& other) noexcept
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
this->~Writer();
|
||||
_io = std::exchange(other._io, nullptr);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
VtIo::Writer::operator bool() const noexcept
|
||||
{
|
||||
return _io != nullptr;
|
||||
}
|
||||
|
||||
void VtIo::Writer::Submit()
|
||||
{
|
||||
const auto io = std::exchange(_io, nullptr);
|
||||
io->_uncork();
|
||||
}
|
||||
|
||||
void VtIo::_uncork()
|
||||
{
|
||||
_corked -= 1;
|
||||
if (_corked <= 0)
|
||||
{
|
||||
_flushNow();
|
||||
}
|
||||
}
|
||||
|
||||
void VtIo::_flushNow()
|
||||
{
|
||||
size_t minSize = 0;
|
||||
|
||||
if (_writerRestoreCursor)
|
||||
{
|
||||
minSize = 4;
|
||||
_writerRestoreCursor = false;
|
||||
_back.append("\x1b\x38"); // DECRC: DEC Restore Cursor (+ attributes)
|
||||
}
|
||||
|
||||
if (_overlappedPending)
|
||||
{
|
||||
_overlappedPending = false;
|
||||
|
||||
DWORD written;
|
||||
if (FAILED(Utils::GetOverlappedResultSameThread(_overlapped, &written)))
|
||||
{
|
||||
// Not much we can do here. Let's treat this like a ERROR_BROKEN_PIPE.
|
||||
_hOutput.reset();
|
||||
SendCloseEvent();
|
||||
}
|
||||
}
|
||||
|
||||
_front.clear();
|
||||
_front.swap(_back);
|
||||
|
||||
// If it's >128KiB large and twice as large as the previous buffer, free the memory.
|
||||
// This ensures that there's a pathway for shrinking the buffer from large sizes.
|
||||
if (const auto cap = _back.capacity(); cap > 128 * 1024 && cap / 2 > _front.size())
|
||||
{
|
||||
_back = std::string{};
|
||||
}
|
||||
|
||||
// We encountered an exception and shouldn't flush the broken pieces.
|
||||
if (_writerTainted)
|
||||
{
|
||||
_writerTainted = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// If _back (now _front) was empty, we can return early. If all _front contains is
|
||||
// DECSC/DECRC that was added by BackupCursor & us, we can also return early.
|
||||
if (_front.size() <= minSize)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// No point in calling WriteFile if we already encountered ERROR_BROKEN_PIPE.
|
||||
// We do this after the above, so that _back doesn't grow indefinitely.
|
||||
if (!_hOutput)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto write = gsl::narrow_cast<DWORD>(_front.size());
|
||||
|
||||
TraceLoggingWrite(
|
||||
g_hConhostV2EventTraceProvider,
|
||||
"ConPTY WriteFile",
|
||||
TraceLoggingCountedUtf8String(_front.data(), write, "buffer"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (WriteFile(_hOutput.get(), _front.data(), write, nullptr, _overlapped))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (const auto gle = GetLastError())
|
||||
{
|
||||
case ERROR_BROKEN_PIPE:
|
||||
_hOutput.reset();
|
||||
SendCloseEvent();
|
||||
return;
|
||||
case ERROR_IO_PENDING:
|
||||
_overlappedPending = true;
|
||||
return;
|
||||
default:
|
||||
LOG_WIN32(gle);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VtIo::Writer::BackupCursor() const
|
||||
{
|
||||
if (!_io->_writerRestoreCursor)
|
||||
{
|
||||
_io->_writerRestoreCursor = true;
|
||||
_io->_back.append("\x1b\x37"); // DECSC: DEC Save Cursor (+ attributes)
|
||||
}
|
||||
}
|
||||
|
||||
void VtIo::Writer::WriteUTF8(std::string_view str) const
|
||||
{
|
||||
_io->_back.append(str);
|
||||
}
|
||||
|
||||
void VtIo::Writer::WriteUTF16(std::wstring_view str) const
|
||||
{
|
||||
if (str.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto existingUTF8Len = _io->_back.size();
|
||||
const auto incomingUTF16Len = str.size();
|
||||
|
||||
// When converting from UTF-16 to UTF-8 the worst case is 3 bytes per UTF-16 code unit.
|
||||
const auto incomingUTF8Cap = incomingUTF16Len * 3;
|
||||
const auto totalUTF8Cap = existingUTF8Len + incomingUTF8Cap;
|
||||
|
||||
// Since WideCharToMultiByte() only supports `int` lengths, we check for an overflow past INT_MAX/3.
|
||||
// We also check for an overflow of totalUTF8Cap just to be sure.
|
||||
if (incomingUTF16Len > gsl::narrow_cast<size_t>(INT_MAX / 3) || totalUTF8Cap <= existingUTF8Len)
|
||||
{
|
||||
THROW_HR_MSG(E_INVALIDARG, "string too large");
|
||||
}
|
||||
|
||||
// NOTE: Throwing inside resize_and_overwrite invokes undefined behavior.
|
||||
_io->_back._Resize_and_overwrite(totalUTF8Cap, [&](char* buf, const size_t) noexcept {
|
||||
const auto len = WideCharToMultiByte(CP_UTF8, 0, str.data(), gsl::narrow_cast<int>(incomingUTF16Len), buf + existingUTF8Len, gsl::narrow_cast<int>(incomingUTF8Cap), nullptr, nullptr);
|
||||
return existingUTF8Len + std::max(0, len);
|
||||
});
|
||||
}
|
||||
|
||||
// When DISABLE_NEWLINE_AUTO_RETURN is not set (Bad! Don't do it!) we'll do newline translation for you.
|
||||
// That's the only difference of this function from WriteUTF16: It does LF -> CRLF translation.
|
||||
void VtIo::Writer::WriteUTF16TranslateCRLF(std::wstring_view str) const
|
||||
{
|
||||
const auto beg = str.begin();
|
||||
const auto end = str.end();
|
||||
auto begCopy = beg;
|
||||
auto endCopy = beg;
|
||||
|
||||
// Our goal is to prepend a \r in front of \n that don't already have one.
|
||||
// There's no point in replacing \n\n\n with \r\n\r\n\r\n, however. It's just fine to do \r\n\n\n.
|
||||
// After all we aren't a text file, we're a terminal, and \r\n and \n are identical if we're at the first column.
|
||||
for (;;)
|
||||
{
|
||||
// To do so, we'll first find the next LF and emit the unrelated text before it.
|
||||
endCopy = std::find(endCopy, end, L'\n');
|
||||
WriteUTF16({ begCopy, endCopy });
|
||||
begCopy = endCopy;
|
||||
|
||||
// Done? Great.
|
||||
if (begCopy == end)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// We only need to prepend a CR if the LF isn't already preceded by one.
|
||||
if (begCopy == beg || begCopy[-1] != L'\r')
|
||||
{
|
||||
_io->_back.push_back('\r');
|
||||
}
|
||||
|
||||
// Now extend the end of the next WriteUTF16 *past* this series of CRs and LFs.
|
||||
// We've just ensured that the LF is preceded by a CR, so we can skip all this safely.
|
||||
while (++endCopy != end && (*endCopy == L'\n' || *endCopy == L'\r'))
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Same as WriteUTF16, but replaces control characters with spaces.
|
||||
// We don't outright remove them because that would mess up the cursor position.
|
||||
// conhost traditionally assigned control chars a width of 1 when in the raw write mode.
|
||||
void VtIo::Writer::WriteUTF16StripControlChars(std::wstring_view str) const
|
||||
{
|
||||
auto it = str.data();
|
||||
const auto end = it + str.size();
|
||||
|
||||
// We can picture `str` as a repeated sequence of regular characters followed by control characters.
|
||||
while (it != end)
|
||||
{
|
||||
const auto begControlChars = FindActionableControlCharacter(it, end - it);
|
||||
|
||||
WriteUTF16({ it, begControlChars });
|
||||
|
||||
for (it = begControlChars; it != end && IsControlCharacter(*it); ++it)
|
||||
{
|
||||
WriteUCS2StripControlChars(*it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VtIo::Writer::WriteUCS2(wchar_t ch) const
|
||||
{
|
||||
char buf[4];
|
||||
size_t len = 0;
|
||||
|
||||
if (til::is_surrogate(ch))
|
||||
{
|
||||
ch = UNICODE_REPLACEMENT;
|
||||
}
|
||||
|
||||
if (ch <= 0x7f)
|
||||
{
|
||||
buf[len++] = static_cast<char>(ch);
|
||||
}
|
||||
else if (ch <= 0x7ff)
|
||||
{
|
||||
buf[len++] = static_cast<char>(0xc0 | (ch >> 6));
|
||||
buf[len++] = static_cast<char>(0x80 | (ch & 0x3f));
|
||||
}
|
||||
else
|
||||
{
|
||||
buf[len++] = static_cast<char>(0xe0 | (ch >> 12));
|
||||
buf[len++] = static_cast<char>(0x80 | ((ch >> 6) & 0x3f));
|
||||
buf[len++] = static_cast<char>(0x80 | (ch & 0x3f));
|
||||
}
|
||||
|
||||
_io->_back.append(buf, len);
|
||||
}
|
||||
|
||||
void VtIo::Writer::WriteUCS2StripControlChars(wchar_t ch) const
|
||||
{
|
||||
if (ch < 0x20)
|
||||
{
|
||||
static constexpr wchar_t lut[] = {
|
||||
// clang-format off
|
||||
L' ', L'☺', L'☻', L'♥', L'♦', L'♣', L'♠', L'•', L'◘', L'○', L'◙', L'♂', L'♀', L'♪', L'♫', L'☼',
|
||||
L'►', L'◄', L'↕', L'‼', L'¶', L'§', L'▬', L'↨', L'↑', L'↓', L'→', L'←', L'∟', L'↔', L'▲', L'▼',
|
||||
// clang-format on
|
||||
};
|
||||
ch = lut[ch];
|
||||
}
|
||||
else if (ch == 0x7F)
|
||||
{
|
||||
ch = L'⌂';
|
||||
}
|
||||
else if (ch > 0x7F && ch < 0xA0)
|
||||
{
|
||||
ch = L'?';
|
||||
}
|
||||
|
||||
WriteUCS2(ch);
|
||||
}
|
||||
|
||||
// CUP: Cursor Position
|
||||
void VtIo::Writer::WriteCUP(til::point position) const
|
||||
{
|
||||
fmt::format_to(std::back_inserter(_io->_back), FMT_COMPILE("\x1b[{};{}H"), position.y + 1, position.x + 1);
|
||||
}
|
||||
|
||||
// DECTCEM: Text Cursor Enable
|
||||
void VtIo::Writer::WriteDECTCEM(bool enabled) const
|
||||
{
|
||||
char buf[] = "\x1b[?25h";
|
||||
buf[std::size(buf) - 2] = enabled ? 'h' : 'l';
|
||||
_io->_back.append(&buf[0], std::size(buf) - 1);
|
||||
}
|
||||
|
||||
// SGR 1006: SGR Extended Mouse Mode
|
||||
void VtIo::Writer::WriteSGR1006(bool enabled) const
|
||||
{
|
||||
char buf[] = "\x1b[?1003;1006h";
|
||||
buf[std::size(buf) - 2] = enabled ? 'h' : 'l';
|
||||
_io->_back.append(&buf[0], std::size(buf) - 1);
|
||||
}
|
||||
|
||||
// DECAWM: Autowrap Mode
|
||||
void VtIo::Writer::WriteDECAWM(bool enabled) const
|
||||
{
|
||||
char buf[] = "\x1b[?7h";
|
||||
buf[std::size(buf) - 2] = enabled ? 'h' : 'l';
|
||||
_io->_back.append(&buf[0], std::size(buf) - 1);
|
||||
}
|
||||
|
||||
// ASB: Alternate Screen Buffer
|
||||
void VtIo::Writer::WriteASB(bool enabled) const
|
||||
{
|
||||
char buf[] = "\x1b[?1049h";
|
||||
buf[std::size(buf) - 2] = enabled ? 'h' : 'l';
|
||||
_io->_back.append(&buf[0], std::size(buf) - 1);
|
||||
}
|
||||
|
||||
void VtIo::Writer::WriteAttributes(const TextAttribute& attributes) const
|
||||
{
|
||||
FormatAttributes(_io->_back, attributes);
|
||||
}
|
||||
|
||||
void VtIo::Writer::WriteInfos(til::point target, std::span<const CHAR_INFO> infos) const
|
||||
{
|
||||
const auto beg = infos.begin();
|
||||
const auto end = infos.end();
|
||||
const auto last = end - 1;
|
||||
WORD attributes = 0xffff;
|
||||
|
||||
WriteCUP(target);
|
||||
|
||||
for (auto it = beg; it != end; ++it)
|
||||
{
|
||||
const auto& ci = *it;
|
||||
auto ch = ci.Char.UnicodeChar;
|
||||
auto wide = WI_IsAnyFlagSet(ci.Attributes, COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE);
|
||||
|
||||
if (wide)
|
||||
{
|
||||
if (WI_IsAnyFlagSet(ci.Attributes, COMMON_LVB_LEADING_BYTE))
|
||||
{
|
||||
if (it == last)
|
||||
{
|
||||
// The leading half of a wide glyph won't fit into the last remaining column.
|
||||
// --> Replace it with a space.
|
||||
ch = L' ';
|
||||
wide = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (it == beg)
|
||||
{
|
||||
// The trailing half of a wide glyph won't fit into the first column. It's incomplete.
|
||||
// --> Replace it with a space.
|
||||
ch = L' ';
|
||||
wide = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Trailing halves of glyphs are ignored within the run. We only emit the leading half.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (attributes != ci.Attributes)
|
||||
{
|
||||
attributes = ci.Attributes;
|
||||
WriteAttributes(TextAttribute{ attributes });
|
||||
}
|
||||
|
||||
int repeat = 1;
|
||||
if (wide && (til::is_surrogate(ch) || IsControlCharacter(ch)))
|
||||
{
|
||||
// Control characters, U+FFFD, etc. are narrow characters, so if the caller
|
||||
// asked for a wide glyph we need to repeat the replacement character twice.
|
||||
repeat++;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
WriteUCS2StripControlChars(ch);
|
||||
} while (--repeat);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,77 +3,101 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../renderer/vt/vtrenderer.hpp"
|
||||
#include "VtInputThread.hpp"
|
||||
#include "PtySignalInputThread.hpp"
|
||||
|
||||
class ConsoleArguments;
|
||||
|
||||
namespace Microsoft::Console::Render
|
||||
{
|
||||
class VtEngine;
|
||||
}
|
||||
|
||||
namespace Microsoft::Console::VirtualTerminal
|
||||
{
|
||||
class VtIo
|
||||
{
|
||||
public:
|
||||
struct Writer
|
||||
{
|
||||
Writer() = default;
|
||||
Writer(VtIo* io) noexcept;
|
||||
|
||||
~Writer() noexcept;
|
||||
|
||||
Writer(const Writer&) = delete;
|
||||
Writer& operator=(const Writer&) = delete;
|
||||
Writer(Writer&& other) noexcept;
|
||||
Writer& operator=(Writer&& other) noexcept;
|
||||
|
||||
explicit operator bool() const noexcept;
|
||||
|
||||
void Submit();
|
||||
|
||||
void BackupCursor() const;
|
||||
void WriteUTF8(std::string_view str) const;
|
||||
void WriteUTF16(std::wstring_view str) const;
|
||||
void WriteUTF16TranslateCRLF(std::wstring_view str) const;
|
||||
void WriteUTF16StripControlChars(std::wstring_view str) const;
|
||||
void WriteUCS2(wchar_t ch) const;
|
||||
void WriteUCS2StripControlChars(wchar_t ch) const;
|
||||
void WriteCUP(til::point position) const;
|
||||
void WriteDECTCEM(bool enabled) const;
|
||||
void WriteSGR1006(bool enabled) const;
|
||||
void WriteDECAWM(bool enabled) const;
|
||||
void WriteASB(bool enabled) const;
|
||||
void WriteAttributes(const TextAttribute& attributes) const;
|
||||
void WriteInfos(til::point target, std::span<const CHAR_INFO> infos) const;
|
||||
|
||||
private:
|
||||
VtIo* _io = nullptr;
|
||||
};
|
||||
|
||||
friend struct Writer;
|
||||
|
||||
static void FormatAttributes(std::string& target, const TextAttribute& attributes);
|
||||
static void FormatAttributes(std::wstring& target, const TextAttribute& attributes);
|
||||
|
||||
VtIo();
|
||||
|
||||
[[nodiscard]] HRESULT Initialize(const ConsoleArguments* const pArgs);
|
||||
|
||||
[[nodiscard]] HRESULT CreateAndStartSignalThread() noexcept;
|
||||
[[nodiscard]] HRESULT CreateIoHandlers() noexcept;
|
||||
|
||||
bool IsUsingVt() const;
|
||||
|
||||
[[nodiscard]] HRESULT StartIfNeeded();
|
||||
|
||||
[[nodiscard]] HRESULT SuppressResizeRepaint();
|
||||
[[nodiscard]] HRESULT SetCursorPosition(const til::point coordCursor);
|
||||
[[nodiscard]] HRESULT SwitchScreenBuffer(const bool useAltBuffer);
|
||||
void SendCloseEvent();
|
||||
|
||||
void CloseInput();
|
||||
void CloseOutput();
|
||||
|
||||
void CorkRenderer(bool corked) const noexcept;
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
void EnableConptyModeForTests(std::unique_ptr<Microsoft::Console::Render::VtEngine> vtRenderEngine, const bool resizeQuirk = false);
|
||||
#endif
|
||||
|
||||
bool IsResizeQuirkEnabled() const;
|
||||
|
||||
[[nodiscard]] HRESULT ManuallyClearScrollback() const noexcept;
|
||||
[[nodiscard]] HRESULT RequestMouseMode(bool enable) const noexcept;
|
||||
|
||||
void CreatePseudoWindow();
|
||||
void SetWindowVisibility(bool showOrHide) noexcept;
|
||||
Writer GetWriter() noexcept;
|
||||
|
||||
private:
|
||||
[[nodiscard]] HRESULT _Initialize(const HANDLE InHandle, const HANDLE OutHandle, _In_opt_ const HANDLE SignalHandle);
|
||||
|
||||
void _uncork();
|
||||
void _flushNow();
|
||||
|
||||
// After CreateIoHandlers is called, these will be invalid.
|
||||
wil::unique_hfile _hInput;
|
||||
wil::unique_hfile _hOutput;
|
||||
// After CreateAndStartSignalThread is called, this will be invalid.
|
||||
wil::unique_hfile _hSignal;
|
||||
|
||||
bool _initialized;
|
||||
|
||||
bool _lookingForCursorPosition;
|
||||
|
||||
bool _resizeQuirk{ false };
|
||||
bool _closeEventSent{ false };
|
||||
|
||||
std::unique_ptr<Microsoft::Console::Render::VtEngine> _pVtRenderEngine;
|
||||
std::unique_ptr<Microsoft::Console::VtInputThread> _pVtInputThread;
|
||||
std::unique_ptr<Microsoft::Console::PtySignalInputThread> _pPtySignalInputThread;
|
||||
|
||||
[[nodiscard]] HRESULT _Initialize(const HANDLE InHandle, const HANDLE OutHandle, _In_opt_ const HANDLE SignalHandle);
|
||||
// We use two buffers: A front and a back buffer. The front buffer is the one we're currently
|
||||
// sending to the terminal (it's being "presented" = it's on the "front" & "visible").
|
||||
// The back buffer is the one we're concurrently writing to.
|
||||
std::string _front;
|
||||
std::string _back;
|
||||
OVERLAPPED* _overlapped = nullptr;
|
||||
OVERLAPPED _overlappedBuf{};
|
||||
wil::unique_event _overlappedEvent;
|
||||
bool _overlappedPending = false;
|
||||
bool _writerRestoreCursor = false;
|
||||
bool _writerTainted = false;
|
||||
|
||||
bool _initialized = false;
|
||||
bool _lookingForCursorPosition = false;
|
||||
bool _closeEventSent = false;
|
||||
int _corked = 0;
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
friend class VtIoTests;
|
||||
|
||||
@ -74,6 +74,7 @@ static FillConsoleResult FillConsoleImpl(SCREEN_INFORMATION& screenInfo, FillCon
|
||||
LockConsole();
|
||||
const auto unlock = wil::scope_exit([&] { UnlockConsole(); });
|
||||
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
auto& screenBuffer = screenInfo.GetActiveBuffer();
|
||||
const auto bufferSize = screenBuffer.GetBufferSize();
|
||||
FillConsoleResult result;
|
||||
@ -83,6 +84,122 @@ static FillConsoleResult FillConsoleImpl(SCREEN_INFORMATION& screenInfo, FillCon
|
||||
return {};
|
||||
}
|
||||
|
||||
if (auto writer = gci.GetVtWriterForBuffer(&screenInfo))
|
||||
{
|
||||
writer.BackupCursor();
|
||||
|
||||
const auto h = bufferSize.Height();
|
||||
const auto w = bufferSize.Width();
|
||||
auto y = startingCoordinate.y;
|
||||
auto input = static_cast<const uint16_t*>(data);
|
||||
size_t inputPos = 0;
|
||||
til::small_vector<CHAR_INFO, 1024> infoBuffer;
|
||||
Viewport unused;
|
||||
|
||||
infoBuffer.resize(gsl::narrow_cast<size_t>(w));
|
||||
|
||||
while (y < h && inputPos < lengthToWrite)
|
||||
{
|
||||
const auto beg = y == startingCoordinate.y ? startingCoordinate.x : 0;
|
||||
const auto columnsAvailable = w - beg;
|
||||
til::CoordType columns = 0;
|
||||
|
||||
const auto readViewport = Viewport::FromInclusive({ beg, y, w - 1, y });
|
||||
THROW_IF_FAILED(ReadConsoleOutputWImplHelper(screenInfo, infoBuffer, readViewport, unused));
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case FillConsoleMode::WriteAttribute:
|
||||
for (; columns < columnsAvailable && inputPos < lengthToWrite; ++columns, ++inputPos)
|
||||
{
|
||||
infoBuffer[columns].Attributes = input[inputPos];
|
||||
}
|
||||
break;
|
||||
case FillConsoleMode::FillAttribute:
|
||||
for (const auto attr = input[0]; columns < columnsAvailable && inputPos < lengthToWrite; ++columns, ++inputPos)
|
||||
{
|
||||
infoBuffer[columns].Attributes = attr;
|
||||
}
|
||||
break;
|
||||
case FillConsoleMode::WriteCharacter:
|
||||
for (; columns < columnsAvailable && inputPos < lengthToWrite; ++inputPos)
|
||||
{
|
||||
const auto ch = input[inputPos];
|
||||
if (ch >= 0x80 && IsGlyphFullWidth(ch))
|
||||
{
|
||||
// If the wide glyph doesn't fit into the last column, pad it with whitespace.
|
||||
if ((columns + 1) >= columnsAvailable)
|
||||
{
|
||||
auto& lead = infoBuffer[columns++];
|
||||
lead.Char.UnicodeChar = L' ';
|
||||
lead.Attributes = lead.Attributes & ~(COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE);
|
||||
break;
|
||||
}
|
||||
|
||||
auto& lead = infoBuffer[columns++];
|
||||
lead.Char.UnicodeChar = ch;
|
||||
lead.Attributes = lead.Attributes & ~COMMON_LVB_TRAILING_BYTE | COMMON_LVB_LEADING_BYTE;
|
||||
|
||||
auto& trail = infoBuffer[columns++];
|
||||
trail.Char.UnicodeChar = ch;
|
||||
trail.Attributes = trail.Attributes & ~COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto& lead = infoBuffer[columns++];
|
||||
lead.Char.UnicodeChar = ch;
|
||||
lead.Attributes = lead.Attributes & ~(COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case FillConsoleMode::FillCharacter:
|
||||
// Identical to WriteCharacter above, but with the if() and for() swapped.
|
||||
if (const auto ch = input[0]; ch >= 0x80 && IsGlyphFullWidth(ch))
|
||||
{
|
||||
for (; columns < columnsAvailable && inputPos < lengthToWrite; ++inputPos)
|
||||
{
|
||||
// If the wide glyph doesn't fit into the last column, pad it with whitespace.
|
||||
if ((columns + 1) >= columnsAvailable)
|
||||
{
|
||||
auto& lead = infoBuffer[columns++];
|
||||
lead.Char.UnicodeChar = L' ';
|
||||
lead.Attributes = lead.Attributes & ~(COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE);
|
||||
break;
|
||||
}
|
||||
|
||||
auto& lead = infoBuffer[columns++];
|
||||
lead.Char.UnicodeChar = ch;
|
||||
lead.Attributes = lead.Attributes & ~COMMON_LVB_TRAILING_BYTE | COMMON_LVB_LEADING_BYTE;
|
||||
|
||||
auto& trail = infoBuffer[columns++];
|
||||
trail.Char.UnicodeChar = ch;
|
||||
trail.Attributes = trail.Attributes & ~COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (; columns < columnsAvailable && inputPos < lengthToWrite; ++inputPos)
|
||||
{
|
||||
auto& lead = infoBuffer[columns++];
|
||||
lead.Char.UnicodeChar = ch;
|
||||
lead.Attributes = lead.Attributes & ~(COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
const auto writeViewport = Viewport::FromInclusive({ beg, y, beg + columns - 1, y });
|
||||
THROW_IF_FAILED(WriteConsoleOutputWImplHelper(screenInfo, infoBuffer, w, writeViewport, unused));
|
||||
|
||||
y += 1;
|
||||
result.cellsModified += columns;
|
||||
}
|
||||
|
||||
result.lengthRead = inputPos;
|
||||
|
||||
writer.Submit();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Technically we could always pass `data` as `uint16_t*`, because `wchar_t` is guaranteed to be 16 bits large.
|
||||
// However, OutputCellIterator is terrifyingly unsafe code and so we don't do that.
|
||||
@ -261,13 +378,37 @@ static FillConsoleResult FillConsoleImpl(SCREEN_INFORMATION& screenInfo, FillCon
|
||||
size_t& cellsModified,
|
||||
const bool enablePowershellShim) noexcept
|
||||
{
|
||||
UNREFERENCED_PARAMETER(enablePowershellShim);
|
||||
|
||||
try
|
||||
{
|
||||
LockConsole();
|
||||
const auto unlock = wil::scope_exit([&] { UnlockConsole(); });
|
||||
|
||||
// GH#3126 - This is a shim for powershell's `Clear-Host` function. In
|
||||
// the vintage console, `Clear-Host` is supposed to clear the entire
|
||||
// buffer. In conpty however, there's no difference between the viewport
|
||||
// and the entirety of the buffer. We're going to see if this API call
|
||||
// exactly matched the way we expect powershell to call it. If it does,
|
||||
// then let's manually emit a ^[[3J to the connected terminal, so that
|
||||
// their entire buffer will be cleared as well.
|
||||
if (enablePowershellShim)
|
||||
{
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
if (const auto writer = gci.GetVtWriterForBuffer(&OutContext))
|
||||
{
|
||||
const auto currentBufferDimensions{ OutContext.GetBufferSize().Dimensions() };
|
||||
const auto wroteWholeBuffer = lengthToWrite == (currentBufferDimensions.area<size_t>());
|
||||
const auto startedAtOrigin = startingCoordinate == til::point{ 0, 0 };
|
||||
const auto wroteSpaces = attribute == (FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED);
|
||||
|
||||
if (wroteWholeBuffer && startedAtOrigin && wroteSpaces)
|
||||
{
|
||||
// PowerShell has previously called FillConsoleOutputCharacterW() which triggered a call to WriteClearScreen().
|
||||
cellsModified = lengthToWrite;
|
||||
return S_OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cellsModified = FillConsoleImpl(OutContext, FillConsoleMode::FillAttribute, &attribute, lengthToWrite, startingCoordinate).cellsModified;
|
||||
return S_OK;
|
||||
}
|
||||
@ -299,8 +440,6 @@ static FillConsoleResult FillConsoleImpl(SCREEN_INFORMATION& screenInfo, FillCon
|
||||
LockConsole();
|
||||
const auto unlock = wil::scope_exit([&] { UnlockConsole(); });
|
||||
|
||||
cellsModified = FillConsoleImpl(OutContext, FillConsoleMode::FillCharacter, &character, lengthToWrite, startingCoordinate).lengthRead;
|
||||
|
||||
// GH#3126 - This is a shim for powershell's `Clear-Host` function. In
|
||||
// the vintage console, `Clear-Host` is supposed to clear the entire
|
||||
// buffer. In conpty however, there's no difference between the viewport
|
||||
@ -311,7 +450,7 @@ static FillConsoleResult FillConsoleImpl(SCREEN_INFORMATION& screenInfo, FillCon
|
||||
if (enablePowershellShim)
|
||||
{
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
if (gci.IsInVtIoMode())
|
||||
if (auto writer = gci.GetVtWriterForBuffer(&OutContext))
|
||||
{
|
||||
const auto currentBufferDimensions{ OutContext.GetBufferSize().Dimensions() };
|
||||
const auto wroteWholeBuffer = lengthToWrite == (currentBufferDimensions.area<size_t>());
|
||||
@ -320,14 +459,15 @@ static FillConsoleResult FillConsoleImpl(SCREEN_INFORMATION& screenInfo, FillCon
|
||||
|
||||
if (wroteWholeBuffer && startedAtOrigin && wroteSpaces)
|
||||
{
|
||||
// It's important that we flush the renderer at this point so we don't
|
||||
// have any pending output rendered after the scrollback is cleared.
|
||||
ServiceLocator::LocateGlobals().pRender->TriggerFlush(false);
|
||||
return gci.GetVtIo()->ManuallyClearScrollback();
|
||||
WriteClearScreen(OutContext);
|
||||
writer.Submit();
|
||||
cellsModified = lengthToWrite;
|
||||
return S_OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cellsModified = FillConsoleImpl(OutContext, FillConsoleMode::FillCharacter, &character, lengthToWrite, startingCoordinate).lengthRead;
|
||||
return S_OK;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
|
||||
@ -14,6 +14,7 @@
|
||||
#include "dbcs.h"
|
||||
#include "handle.h"
|
||||
#include "misc.h"
|
||||
#include "VtIo.hpp"
|
||||
|
||||
#include "../types/inc/convert.hpp"
|
||||
#include "../types/inc/GlyphWidth.hpp"
|
||||
@ -21,12 +22,14 @@
|
||||
|
||||
#include "../interactivity/inc/ServiceLocator.hpp"
|
||||
|
||||
#pragma hdrstop
|
||||
using namespace Microsoft::Console::Types;
|
||||
using namespace Microsoft::Console::VirtualTerminal;
|
||||
using Microsoft::Console::Interactivity::ServiceLocator;
|
||||
using Microsoft::Console::VirtualTerminal::StateMachine;
|
||||
// Used by WriteCharsLegacy.
|
||||
#define IS_GLYPH_CHAR(wch) (((wch) >= L' ') && ((wch) != 0x007F))
|
||||
|
||||
constexpr bool controlCharPredicate(wchar_t wch)
|
||||
{
|
||||
return wch < L' ' || wch == 0x007F;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - This routine updates the cursor position. Its input is the non-special
|
||||
@ -109,11 +112,12 @@ static void AdjustCursorPosition(SCREEN_INFORMATION& screenInfo, _In_ til::point
|
||||
}
|
||||
|
||||
// As the name implies, this writes text without processing its control characters.
|
||||
void _writeCharsLegacyUnprocessed(SCREEN_INFORMATION& screenInfo, const std::wstring_view& text, til::CoordType* psScrollY)
|
||||
static bool _writeCharsLegacyUnprocessed(SCREEN_INFORMATION& screenInfo, const std::wstring_view& text, til::CoordType* psScrollY)
|
||||
{
|
||||
const auto wrapAtEOL = WI_IsFlagSet(screenInfo.OutputMode, ENABLE_WRAP_AT_EOL_OUTPUT);
|
||||
const auto hasAccessibilityEventing = screenInfo.HasAccessibilityEventing();
|
||||
auto& textBuffer = screenInfo.GetTextBuffer();
|
||||
bool wrapped = false;
|
||||
|
||||
RowWriteState state{
|
||||
.text = text,
|
||||
@ -127,8 +131,9 @@ void _writeCharsLegacyUnprocessed(SCREEN_INFORMATION& screenInfo, const std::wst
|
||||
state.columnBegin = cursorPosition.x;
|
||||
textBuffer.Replace(cursorPosition.y, textBuffer.GetCurrentAttributes(), state);
|
||||
cursorPosition.x = state.columnEnd;
|
||||
wrapped = wrapAtEOL && state.columnEnd >= state.columnLimit;
|
||||
|
||||
if (wrapAtEOL && state.columnEnd >= state.columnLimit)
|
||||
if (wrapped)
|
||||
{
|
||||
textBuffer.SetWrapForced(cursorPosition.y, true);
|
||||
}
|
||||
@ -140,6 +145,8 @@ void _writeCharsLegacyUnprocessed(SCREEN_INFORMATION& screenInfo, const std::wst
|
||||
|
||||
AdjustCursorPosition(screenInfo, cursorPosition, psScrollY);
|
||||
}
|
||||
|
||||
return wrapped;
|
||||
}
|
||||
|
||||
// This routine writes a string to the screen while handling control characters.
|
||||
@ -153,15 +160,16 @@ void WriteCharsLegacy(SCREEN_INFORMATION& screenInfo, const std::wstring_view& t
|
||||
const auto width = textBuffer.GetSize().Width();
|
||||
auto& cursor = textBuffer.GetCursor();
|
||||
const auto wrapAtEOL = WI_IsFlagSet(screenInfo.OutputMode, ENABLE_WRAP_AT_EOL_OUTPUT);
|
||||
auto it = text.begin();
|
||||
const auto beg = text.begin();
|
||||
const auto end = text.end();
|
||||
auto it = beg;
|
||||
|
||||
// In VT mode, when you have a 120-column terminal you can write 120 columns without the cursor wrapping.
|
||||
// Whenever the cursor is in that 120th column IsDelayedEOLWrap() will return true. I'm not sure why the VT parts
|
||||
// of the code base store this as a boolean. It's also unclear why we handle this here. The intention is likely
|
||||
// so that when we exit VT mode and receive a write a potentially stored delayed wrap would still be handled.
|
||||
// The way this code does it however isn't correct since it handles it like the old console APIs would and
|
||||
// so writing a newline while being delay wrapped will print 2 newlines.
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
auto writer = gci.GetVtWriterForBuffer(&screenInfo);
|
||||
|
||||
// If we enter this if condition, then someone wrote text in VT mode and now switched to non-VT mode.
|
||||
// Since the Console APIs don't support delayed EOL wrapping, we need to first put the cursor back
|
||||
// to a position that the Console APIs expect (= not delayed).
|
||||
if (cursor.IsDelayedEOLWrap() && wrapAtEOL)
|
||||
{
|
||||
auto pos = cursor.GetPosition();
|
||||
@ -172,6 +180,11 @@ void WriteCharsLegacy(SCREEN_INFORMATION& screenInfo, const std::wstring_view& t
|
||||
pos.x = 0;
|
||||
pos.y++;
|
||||
AdjustCursorPosition(screenInfo, pos, psScrollY);
|
||||
|
||||
if (writer)
|
||||
{
|
||||
writer.WriteUTF8("\r\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -179,79 +192,141 @@ void WriteCharsLegacy(SCREEN_INFORMATION& screenInfo, const std::wstring_view& t
|
||||
// If it's not set, we can just straight up give everything to WriteCharsLegacyUnprocessed.
|
||||
if (WI_IsFlagClear(screenInfo.OutputMode, ENABLE_PROCESSED_OUTPUT))
|
||||
{
|
||||
_writeCharsLegacyUnprocessed(screenInfo, { it, end }, psScrollY);
|
||||
it = end;
|
||||
const auto lastCharWrapped = _writeCharsLegacyUnprocessed(screenInfo, text, psScrollY);
|
||||
|
||||
if (writer)
|
||||
{
|
||||
// We're asked to produce VT output, but also to behave as if these control characters aren't control characters.
|
||||
// So, to make it work, we simply replace all the control characters with whitespace.
|
||||
writer.WriteUTF16StripControlChars(text);
|
||||
if (lastCharWrapped)
|
||||
{
|
||||
writer.WriteUTF8("\r\n");
|
||||
}
|
||||
writer.Submit();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
while (it != end)
|
||||
{
|
||||
const auto nextControlChar = std::find_if(it, end, [](const auto& wch) { return !IS_GLYPH_CHAR(wch); });
|
||||
const auto nextControlChar = std::find_if(it, end, controlCharPredicate);
|
||||
if (nextControlChar != it)
|
||||
{
|
||||
_writeCharsLegacyUnprocessed(screenInfo, { it, nextControlChar }, psScrollY);
|
||||
const std::wstring_view chunk{ it, nextControlChar };
|
||||
const auto lastCharWrapped = _writeCharsLegacyUnprocessed(screenInfo, chunk, psScrollY);
|
||||
it = nextControlChar;
|
||||
|
||||
if (writer)
|
||||
{
|
||||
writer.WriteUTF16(chunk);
|
||||
if (lastCharWrapped)
|
||||
{
|
||||
writer.WriteUTF8("\r\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (; it != end && !IS_GLYPH_CHAR(*it); ++it)
|
||||
if (it == end)
|
||||
{
|
||||
switch (*it)
|
||||
break;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
auto wch = *it;
|
||||
auto lastCharWrapped = false;
|
||||
|
||||
switch (wch)
|
||||
{
|
||||
case UNICODE_NULL:
|
||||
_writeCharsLegacyUnprocessed(screenInfo, { &tabSpaces[0], 1 }, psScrollY);
|
||||
continue;
|
||||
{
|
||||
lastCharWrapped = _writeCharsLegacyUnprocessed(screenInfo, { &tabSpaces[0], 1 }, psScrollY);
|
||||
wch = L' ';
|
||||
break;
|
||||
}
|
||||
case UNICODE_BELL:
|
||||
{
|
||||
std::ignore = screenInfo.SendNotifyBeep();
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
case UNICODE_BACKSPACE:
|
||||
{
|
||||
auto pos = cursor.GetPosition();
|
||||
pos.x = textBuffer.GetRowByOffset(pos.y).NavigateToPrevious(pos.x);
|
||||
AdjustCursorPosition(screenInfo, pos, psScrollY);
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
case UNICODE_TAB:
|
||||
{
|
||||
const auto pos = cursor.GetPosition();
|
||||
const auto remaining = width - pos.x;
|
||||
const auto tabCount = gsl::narrow_cast<size_t>(std::min(remaining, 8 - (pos.x & 7)));
|
||||
_writeCharsLegacyUnprocessed(screenInfo, { &tabSpaces[0], tabCount }, psScrollY);
|
||||
continue;
|
||||
lastCharWrapped = _writeCharsLegacyUnprocessed(screenInfo, { &tabSpaces[0], tabCount }, psScrollY);
|
||||
break;
|
||||
}
|
||||
case UNICODE_LINEFEED:
|
||||
{
|
||||
auto pos = cursor.GetPosition();
|
||||
if (WI_IsFlagClear(screenInfo.OutputMode, DISABLE_NEWLINE_AUTO_RETURN))
|
||||
if (WI_IsFlagClear(screenInfo.OutputMode, DISABLE_NEWLINE_AUTO_RETURN) && pos.x != 0)
|
||||
{
|
||||
pos.x = 0;
|
||||
// This causes the current \n to be replaced with a \r\n in the ConPTY VT output.
|
||||
wch = 0;
|
||||
lastCharWrapped = true;
|
||||
}
|
||||
|
||||
textBuffer.GetMutableRowByOffset(pos.y).SetWrapForced(false);
|
||||
pos.y = pos.y + 1;
|
||||
AdjustCursorPosition(screenInfo, pos, psScrollY);
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
case UNICODE_CARRIAGERETURN:
|
||||
{
|
||||
auto pos = cursor.GetPosition();
|
||||
pos.x = 0;
|
||||
AdjustCursorPosition(screenInfo, pos, psScrollY);
|
||||
continue;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// As a special favor to incompetent apps that attempt to display control chars,
|
||||
// convert to corresponding OEM Glyph Chars
|
||||
const auto cp = ServiceLocator::LocateGlobals().getConsoleInformation().OutputCP;
|
||||
const auto ch = gsl::narrow_cast<char>(*it);
|
||||
wchar_t wch = 0;
|
||||
const auto result = MultiByteToWideChar(cp, MB_USEGLYPHCHARS, &ch, 1, &wch, 1);
|
||||
if (result == 1)
|
||||
default:
|
||||
{
|
||||
_writeCharsLegacyUnprocessed(screenInfo, { &wch, 1 }, psScrollY);
|
||||
// As a special favor to incompetent apps that attempt to display control chars,
|
||||
// convert to corresponding OEM Glyph Chars
|
||||
const auto cp = ServiceLocator::LocateGlobals().getConsoleInformation().OutputCP;
|
||||
const auto ch = gsl::narrow_cast<char>(wch);
|
||||
const auto result = MultiByteToWideChar(cp, MB_USEGLYPHCHARS, &ch, 1, &wch, 1);
|
||||
if (result != 1)
|
||||
{
|
||||
wch = 0;
|
||||
}
|
||||
if (wch)
|
||||
{
|
||||
lastCharWrapped = _writeCharsLegacyUnprocessed(screenInfo, { &wch, 1 }, psScrollY);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (writer)
|
||||
{
|
||||
if (wch)
|
||||
{
|
||||
writer.WriteUCS2(wch);
|
||||
}
|
||||
if (lastCharWrapped)
|
||||
{
|
||||
writer.WriteUTF8("\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
++it;
|
||||
} while (it != end && controlCharPredicate(*it));
|
||||
}
|
||||
|
||||
if (writer)
|
||||
{
|
||||
writer.Submit();
|
||||
}
|
||||
}
|
||||
|
||||
@ -259,7 +334,58 @@ void WriteCharsLegacy(SCREEN_INFORMATION& screenInfo, const std::wstring_view& t
|
||||
// This wrapper around StateMachine exists so that we can add the necessary ConPTY transformations.
|
||||
void WriteCharsVT(SCREEN_INFORMATION& screenInfo, const std::wstring_view& str)
|
||||
{
|
||||
screenInfo.GetStateMachine().ProcessString(str);
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
auto& stateMachine = screenInfo.GetStateMachine();
|
||||
// When switch between the main and alt-buffer SCREEN_INFORMATION::GetActiveBuffer()
|
||||
// may change, so get the VtIo reference now, just in case.
|
||||
auto writer = gci.GetVtWriterForBuffer(&screenInfo);
|
||||
|
||||
stateMachine.ProcessString(str);
|
||||
|
||||
if (writer)
|
||||
{
|
||||
const auto& injections = stateMachine.GetInjections();
|
||||
size_t offset = 0;
|
||||
|
||||
const auto write = [&](size_t beg, size_t end) {
|
||||
const auto chunk = til::safe_slice_abs(str, beg, end);
|
||||
if (WI_IsFlagSet(screenInfo.OutputMode, DISABLE_NEWLINE_AUTO_RETURN))
|
||||
{
|
||||
writer.WriteUTF16(chunk);
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteUTF16TranslateCRLF(chunk);
|
||||
}
|
||||
};
|
||||
|
||||
for (const auto& injection : injections)
|
||||
{
|
||||
write(offset, injection.offset);
|
||||
offset = injection.offset;
|
||||
|
||||
static constexpr std::array<std::string_view, 2> mapping{ {
|
||||
{ "\x1b[?1004h\x1b[?9001h" }, // RIS: Focus Event Mode + Win32 Input Mode
|
||||
{ "\033[?1004h" } // DECSET_FOCUS: Focus Event Mode
|
||||
} };
|
||||
|
||||
writer.WriteUTF8(mapping[static_cast<size_t>(injection.type)]);
|
||||
}
|
||||
|
||||
write(offset, std::wstring_view::npos);
|
||||
writer.Submit();
|
||||
}
|
||||
}
|
||||
|
||||
// Erases all contents of the given screenInfo, including the current screen and scrollback.
|
||||
void WriteClearScreen(SCREEN_INFORMATION& screenInfo)
|
||||
{
|
||||
WriteCharsVT(
|
||||
screenInfo,
|
||||
L"\x1b[H" // CUP to home
|
||||
L"\x1b[2J" // Erase in Display: clear the screen
|
||||
L"\x1b[3J" // Erase in Display: clear the scrollback buffer
|
||||
);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
@ -284,7 +410,7 @@ void WriteCharsVT(SCREEN_INFORMATION& screenInfo, const std::wstring_view& str)
|
||||
std::unique_ptr<WriteData>& waiter)
|
||||
try
|
||||
{
|
||||
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
if (WI_IsAnyFlagSet(gci.Flags, (CONSOLE_SUSPENDED | CONSOLE_SELECTING | CONSOLE_SCROLLBAR_TRACKING)))
|
||||
{
|
||||
waiter = std::make_unique<WriteData>(screenInfo,
|
||||
@ -295,25 +421,16 @@ try
|
||||
return CONSOLE_STATUS_WAIT;
|
||||
}
|
||||
|
||||
const auto vtIo = ServiceLocator::LocateGlobals().getConsoleInformation().GetVtIo();
|
||||
const auto restoreVtQuirk = wil::scope_exit([&]() {
|
||||
if (requiresVtQuirk)
|
||||
{
|
||||
screenInfo.ResetIgnoreLegacyEquivalentVTAttributes();
|
||||
}
|
||||
if (vtIo->IsUsingVt())
|
||||
{
|
||||
vtIo->CorkRenderer(false);
|
||||
}
|
||||
});
|
||||
if (requiresVtQuirk)
|
||||
{
|
||||
screenInfo.SetIgnoreLegacyEquivalentVTAttributes();
|
||||
}
|
||||
if (vtIo->IsUsingVt())
|
||||
{
|
||||
vtIo->CorkRenderer(true);
|
||||
}
|
||||
|
||||
const std::wstring_view str{ pwchBuffer, *pcbBuffer / sizeof(WCHAR) };
|
||||
|
||||
@ -323,7 +440,7 @@ try
|
||||
}
|
||||
else
|
||||
{
|
||||
screenInfo.GetStateMachine().ProcessString(str);
|
||||
WriteCharsVT(screenInfo, str);
|
||||
}
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
|
||||
@ -21,6 +21,7 @@ Revision History:
|
||||
|
||||
void WriteCharsLegacy(SCREEN_INFORMATION& screenInfo, const std::wstring_view& str, til::CoordType* psScrollY);
|
||||
void WriteCharsVT(SCREEN_INFORMATION& screenInfo, const std::wstring_view& str);
|
||||
void WriteClearScreen(SCREEN_INFORMATION& screenInfo);
|
||||
|
||||
// NOTE: console lock must be held when calling this routine
|
||||
// String has been translated to unicode at this point.
|
||||
|
||||
@ -118,12 +118,22 @@ ErrorExit2:
|
||||
return Status;
|
||||
}
|
||||
|
||||
VtIo* CONSOLE_INFORMATION::GetVtIo()
|
||||
VtIo* CONSOLE_INFORMATION::GetVtIo() noexcept
|
||||
{
|
||||
return &_vtIo;
|
||||
}
|
||||
|
||||
bool CONSOLE_INFORMATION::IsInVtIoMode() const
|
||||
VtIo::Writer CONSOLE_INFORMATION::GetVtWriter() noexcept
|
||||
{
|
||||
return _vtIo.IsUsingVt() ? _vtIo.GetWriter() : VtIo::Writer{};
|
||||
}
|
||||
|
||||
VtIo::Writer CONSOLE_INFORMATION::GetVtWriterForBuffer(const SCREEN_INFORMATION* context) noexcept
|
||||
{
|
||||
return _vtIo.IsUsingVt() && (pCurrentScreenBuffer == context || pCurrentScreenBuffer == context->GetAltBuffer()) ? _vtIo.GetWriter() : VtIo::Writer{};
|
||||
}
|
||||
|
||||
bool CONSOLE_INFORMATION::IsInVtIoMode() const noexcept
|
||||
{
|
||||
return _vtIo.IsUsingVt();
|
||||
}
|
||||
@ -215,20 +225,6 @@ InputBuffer* const CONSOLE_INFORMATION::GetActiveInputBuffer() const
|
||||
void CONSOLE_INFORMATION::SetTitle(const std::wstring_view newTitle)
|
||||
{
|
||||
_Title = std::wstring{ newTitle.begin(), newTitle.end() };
|
||||
|
||||
// Sanitize the input if we're in pty mode. No control chars - this string
|
||||
// will get emitted back to the TTY in a VT sequence, and we don't want
|
||||
// to embed control characters in that string. Note that we can't use
|
||||
// IsInVtIoMode for this test, because the VT I/O thread won't have
|
||||
// been created when the title is first set during startup.
|
||||
if (ServiceLocator::LocateGlobals().launchArgs.InConptyMode())
|
||||
{
|
||||
_Title.erase(std::remove_if(_Title.begin(), _Title.end(), [](auto ch) {
|
||||
return ch < UNICODE_SPACE || (ch > UNICODE_DEL && ch < UNICODE_NBSP);
|
||||
}),
|
||||
_Title.end());
|
||||
}
|
||||
|
||||
_TitleAndPrefix = _Prefix + _Title;
|
||||
|
||||
auto* const pRender = ServiceLocator::LocateGlobals().pRender;
|
||||
|
||||
@ -598,6 +598,9 @@ CATCH_RETURN();
|
||||
return E_INVALIDARG;
|
||||
}
|
||||
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
auto writer = gci.GetVtWriterForBuffer(&context);
|
||||
|
||||
for (til::CoordType y = clippedRectangle.Top(); y <= clippedRectangle.BottomInclusive(); y++)
|
||||
{
|
||||
const auto charInfos = buffer.subspan(totalOffset, width);
|
||||
@ -606,6 +609,11 @@ CATCH_RETURN();
|
||||
// Make the iterator and write to the target position.
|
||||
storageBuffer.Write(OutputCellIterator(charInfos), target);
|
||||
|
||||
if (writer)
|
||||
{
|
||||
writer.WriteInfos(target, charInfos);
|
||||
}
|
||||
|
||||
totalOffset += bufferStride;
|
||||
}
|
||||
|
||||
@ -615,6 +623,11 @@ CATCH_RETURN();
|
||||
// Since we've managed to write part of the request, return the clamped part that we actually used.
|
||||
writtenRectangle = clippedRectangle;
|
||||
|
||||
if (writer)
|
||||
{
|
||||
writer.Submit();
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
@ -631,12 +644,23 @@ CATCH_RETURN();
|
||||
try
|
||||
{
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
auto writer = gci.GetVtWriterForBuffer(&context);
|
||||
|
||||
if (writer)
|
||||
{
|
||||
writer.BackupCursor();
|
||||
}
|
||||
|
||||
const auto codepage = gci.OutputCP;
|
||||
LOG_IF_FAILED(_ConvertCellsToWInplace(codepage, buffer, requestRectangle));
|
||||
|
||||
RETURN_IF_FAILED(WriteConsoleOutputWImplHelper(context, buffer, requestRectangle.Width(), requestRectangle, writtenRectangle));
|
||||
|
||||
if (writer)
|
||||
{
|
||||
writer.Submit();
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
@ -652,6 +676,14 @@ CATCH_RETURN();
|
||||
|
||||
try
|
||||
{
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
auto writer = gci.GetVtWriterForBuffer(&context);
|
||||
|
||||
if (writer)
|
||||
{
|
||||
writer.BackupCursor();
|
||||
}
|
||||
|
||||
if (!context.GetActiveBuffer().GetCurrentFont().IsTrueTypeFont())
|
||||
{
|
||||
// For compatibility reasons, we must maintain the behavior that munges the data if we are writing while a raster font is enabled.
|
||||
@ -664,6 +696,11 @@ CATCH_RETURN();
|
||||
RETURN_IF_FAILED(WriteConsoleOutputWImplHelper(context, buffer, requestRectangle.Width(), requestRectangle, writtenRectangle));
|
||||
}
|
||||
|
||||
if (writer)
|
||||
{
|
||||
writer.Submit();
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
|
||||
@ -50,9 +50,6 @@
|
||||
<ProjectReference Include="..\..\renderer\gdi\lib\gdi.vcxproj">
|
||||
<Project>{1c959542-bac2-4e55-9a6d-13251914cbb9}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\renderer\vt\lib\vt.vcxproj">
|
||||
<Project>{990f2657-8580-4828-943f-5dd657d11842}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\server\lib\server.vcxproj">
|
||||
<Project>{18d09a24-8240-42d6-8cb6-236eee820262}</Project>
|
||||
</ProjectReference>
|
||||
|
||||
@ -45,9 +45,6 @@
|
||||
<ProjectReference Include="..\..\renderer\gdi\lib\gdi.vcxproj">
|
||||
<Project>{1c959542-bac2-4e55-9a6d-13251914cbb9}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\renderer\vt\lib\vt.vcxproj">
|
||||
<Project>{990f2657-8580-4828-943f-5dd657d11842}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\server\lib\server.vcxproj">
|
||||
<Project>{18d09a24-8240-42d6-8cb6-236eee820262}</Project>
|
||||
</ProjectReference>
|
||||
|
||||
@ -2,23 +2,19 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
|
||||
#include "getset.h"
|
||||
|
||||
#include "_output.h"
|
||||
#include "_stream.h"
|
||||
#include "output.h"
|
||||
#include "dbcs.h"
|
||||
#include "ApiRoutines.h"
|
||||
#include "directio.h"
|
||||
#include "handle.h"
|
||||
#include "misc.h"
|
||||
#include "cmdline.h"
|
||||
|
||||
#include "../types/inc/convert.hpp"
|
||||
#include "../types/inc/viewport.hpp"
|
||||
|
||||
#include "ApiRoutines.h"
|
||||
#include "output.h"
|
||||
#include "_output.h"
|
||||
#include "_stream.h"
|
||||
|
||||
#include "../interactivity/inc/ServiceLocator.hpp"
|
||||
#include "../types/inc/convert.hpp"
|
||||
#include "../types/inc/viewport.hpp"
|
||||
|
||||
#pragma hdrstop
|
||||
|
||||
@ -367,19 +363,27 @@ void ApiRoutines::GetNumberOfConsoleMouseButtonsImpl(ULONG& buttons) noexcept
|
||||
WI_ClearFlag(gci.Flags, CONSOLE_USE_PRIVATE_FLAGS);
|
||||
}
|
||||
|
||||
if (gci.IsInVtIoMode())
|
||||
if (auto writer = gci.GetVtWriter())
|
||||
{
|
||||
auto oldMode = context.InputMode;
|
||||
auto newMode = mode;
|
||||
|
||||
// Mouse input should be received when mouse mode is on and quick edit mode is off
|
||||
// (for more information regarding the quirks of mouse mode and why/how it relates
|
||||
// to quick edit mode, see GH#9970)
|
||||
const auto newQuickEditMode{ WI_IsFlagSet(gci.Flags, CONSOLE_QUICK_EDIT_MODE) };
|
||||
const auto oldMouseMode{ !oldQuickEditMode && WI_IsFlagSet(context.InputMode, ENABLE_MOUSE_INPUT) };
|
||||
const auto newMouseMode{ !newQuickEditMode && WI_IsFlagSet(mode, ENABLE_MOUSE_INPUT) };
|
||||
WI_ClearFlagIf(oldMode, ENABLE_MOUSE_INPUT, oldQuickEditMode);
|
||||
WI_ClearFlagIf(newMode, ENABLE_MOUSE_INPUT, newQuickEditMode);
|
||||
|
||||
if (oldMouseMode != newMouseMode)
|
||||
if (const auto diff = oldMode ^ newMode)
|
||||
{
|
||||
LOG_IF_FAILED(gci.GetVtIo()->RequestMouseMode(newMouseMode));
|
||||
if (WI_IsFlagSet(diff, ENABLE_MOUSE_INPUT))
|
||||
{
|
||||
writer.WriteSGR1006(WI_IsFlagSet(newMode, ENABLE_MOUSE_INPUT));
|
||||
}
|
||||
}
|
||||
|
||||
writer.Submit();
|
||||
}
|
||||
|
||||
context.InputMode = mode;
|
||||
@ -416,7 +420,6 @@ void ApiRoutines::GetNumberOfConsoleMouseButtonsImpl(ULONG& buttons) noexcept
|
||||
{
|
||||
try
|
||||
{
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
LockConsole();
|
||||
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
||||
|
||||
@ -426,6 +429,7 @@ void ApiRoutines::GetNumberOfConsoleMouseButtonsImpl(ULONG& buttons) noexcept
|
||||
auto& screenInfo = context.GetActiveBuffer();
|
||||
const auto dwOldMode = screenInfo.OutputMode;
|
||||
const auto dwNewMode = mode;
|
||||
const auto diff = dwOldMode ^ dwNewMode;
|
||||
|
||||
screenInfo.OutputMode = dwNewMode;
|
||||
|
||||
@ -437,19 +441,26 @@ void ApiRoutines::GetNumberOfConsoleMouseButtonsImpl(ULONG& buttons) noexcept
|
||||
screenInfo.GetStateMachine().ResetState();
|
||||
}
|
||||
|
||||
// if we changed rendering modes then redraw the output buffer,
|
||||
// but only do this if we're not in conpty mode.
|
||||
if (!gci.IsInVtIoMode() &&
|
||||
(WI_IsFlagSet(dwNewMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING) != WI_IsFlagSet(dwOldMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING) ||
|
||||
WI_IsFlagSet(dwNewMode, ENABLE_LVB_GRID_WORLDWIDE) != WI_IsFlagSet(dwOldMode, ENABLE_LVB_GRID_WORLDWIDE)))
|
||||
// if we changed rendering modes then redraw the output buffer.
|
||||
if (WI_IsAnyFlagSet(diff, ENABLE_VIRTUAL_TERMINAL_PROCESSING | ENABLE_LVB_GRID_WORLDWIDE))
|
||||
{
|
||||
auto* pRender = ServiceLocator::LocateGlobals().pRender;
|
||||
if (pRender)
|
||||
if (const auto pRender = ServiceLocator::LocateGlobals().pRender)
|
||||
{
|
||||
pRender->TriggerRedrawAll();
|
||||
}
|
||||
}
|
||||
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
if (auto writer = gci.GetVtWriterForBuffer(&context))
|
||||
{
|
||||
if (WI_IsFlagSet(diff, ENABLE_WRAP_AT_EOL_OUTPUT))
|
||||
{
|
||||
writer.WriteDECAWM(WI_IsFlagSet(dwNewMode, ENABLE_WRAP_AT_EOL_OUTPUT));
|
||||
}
|
||||
|
||||
writer.Submit();
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
@ -466,6 +477,61 @@ void ApiRoutines::SetConsoleActiveScreenBufferImpl(SCREEN_INFORMATION& newContex
|
||||
LockConsole();
|
||||
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
||||
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
|
||||
if (&newContext.GetActiveBuffer() == &gci.GetActiveOutputBuffer())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (auto writer = gci.GetVtWriter())
|
||||
{
|
||||
const auto viewport = gci.GetActiveOutputBuffer().GetBufferSize();
|
||||
const auto size = viewport.Dimensions();
|
||||
const auto area = static_cast<size_t>(viewport.Width() * viewport.Height());
|
||||
|
||||
auto& main = newContext.GetMainBuffer();
|
||||
auto& alt = newContext.GetActiveBuffer();
|
||||
const auto hasAltBuffer = &alt != &main;
|
||||
|
||||
// TODO GH#5094: This could use xterm's XTWINOPS "\e[8;<height>;<width>t" escape sequence here.
|
||||
THROW_IF_NTSTATUS_FAILED(main.ResizeTraditional(size));
|
||||
main.SetViewportSize(&size);
|
||||
if (hasAltBuffer)
|
||||
{
|
||||
THROW_IF_NTSTATUS_FAILED(alt.ResizeTraditional(size));
|
||||
alt.SetViewportSize(&size);
|
||||
}
|
||||
|
||||
Viewport read;
|
||||
til::small_vector<CHAR_INFO, 1024> infos;
|
||||
infos.resize(area, CHAR_INFO{ L' ', FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED });
|
||||
|
||||
const auto dumpScreenInfo = [&](SCREEN_INFORMATION& screenInfo) {
|
||||
THROW_IF_FAILED(ReadConsoleOutputWImpl(screenInfo, infos, viewport, read));
|
||||
for (til::CoordType i = 0; i < size.height; i++)
|
||||
{
|
||||
writer.WriteInfos({ 0, i }, { infos.begin() + i * size.width, static_cast<size_t>(size.width) });
|
||||
}
|
||||
|
||||
writer.WriteCUP(screenInfo.GetTextBuffer().GetCursor().GetPosition());
|
||||
writer.WriteAttributes(screenInfo.GetAttributes());
|
||||
writer.WriteDECTCEM(screenInfo.GetTextBuffer().GetCursor().IsVisible());
|
||||
writer.WriteDECAWM(WI_IsFlagSet(screenInfo.OutputMode, ENABLE_WRAP_AT_EOL_OUTPUT));
|
||||
};
|
||||
|
||||
writer.WriteASB(false);
|
||||
dumpScreenInfo(main);
|
||||
|
||||
if (hasAltBuffer)
|
||||
{
|
||||
writer.WriteASB(true);
|
||||
dumpScreenInfo(alt);
|
||||
}
|
||||
|
||||
writer.Submit();
|
||||
}
|
||||
|
||||
SetActiveScreenBuffer(newContext.GetActiveBuffer());
|
||||
}
|
||||
CATCH_LOG();
|
||||
@ -565,6 +631,8 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont
|
||||
cursor.SetPosition(clampedCursorPosition);
|
||||
}
|
||||
|
||||
// TODO GH#5094: This could use xterm's XTWINOPS "\e[8;<height>;<width>t" escape sequence here.
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
@ -621,7 +689,7 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont
|
||||
// Only do this if we actually changed the value of the palette though -
|
||||
// this API gets called all the time to change all sorts of things, but
|
||||
// not necessarily the palette.
|
||||
if (changedOneTableEntry && !gci.IsInVtIoMode())
|
||||
if (changedOneTableEntry)
|
||||
{
|
||||
if (auto* pRender{ ServiceLocator::LocateGlobals().pRender })
|
||||
{
|
||||
@ -681,6 +749,8 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont
|
||||
cursor.SetPosition(clampedCursorPosition);
|
||||
}
|
||||
|
||||
// TODO GH#5094: This could use xterm's XTWINOPS "\e[8;<height>;<width>t" escape sequence here.
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
@ -713,7 +783,11 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont
|
||||
|
||||
// MSFT: 15813316 - Try to use this SetCursorPosition call to inherit the cursor position.
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
RETURN_IF_FAILED(gci.GetVtIo()->SetCursorPosition(position));
|
||||
if (auto writer = gci.GetVtWriterForBuffer(&context))
|
||||
{
|
||||
writer.WriteCUP(position);
|
||||
writer.Submit();
|
||||
}
|
||||
|
||||
RETURN_IF_NTSTATUS_FAILED(buffer.SetCursorPosition(position, true));
|
||||
|
||||
@ -789,6 +863,13 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont
|
||||
|
||||
context.SetCursorInformation(size, isVisible);
|
||||
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
if (auto writer = gci.GetVtWriterForBuffer(&context))
|
||||
{
|
||||
writer.WriteDECTCEM(isVisible);
|
||||
writer.Submit();
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
@ -854,14 +935,7 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont
|
||||
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;
|
||||
}
|
||||
@ -913,8 +987,8 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont
|
||||
const til::inclusive_rect& source,
|
||||
const til::point target,
|
||||
std::optional<til::inclusive_rect> clip,
|
||||
const wchar_t fillCharacter,
|
||||
const WORD fillAttribute,
|
||||
wchar_t fillCharacter,
|
||||
WORD fillAttribute,
|
||||
const bool enableCmdShim) noexcept
|
||||
{
|
||||
try
|
||||
@ -922,44 +996,73 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont
|
||||
LockConsole();
|
||||
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
||||
|
||||
auto& buffer = context.GetActiveBuffer();
|
||||
|
||||
TextAttribute useThisAttr(fillAttribute);
|
||||
ScrollRegion(buffer, source, clip, target, fillCharacter, useThisAttr);
|
||||
|
||||
auto hr = S_OK;
|
||||
|
||||
// GH#3126 - This is a shim for cmd's `cls` function. In the
|
||||
// legacy console, `cls` is supposed to clear the entire buffer. In
|
||||
// conpty however, there's no difference between the viewport and the
|
||||
// entirety of the buffer. We're going to see if this API call exactly
|
||||
// matched the way we expect cmd to call it. If it does, then
|
||||
// let's manually emit a ^[[3J to the connected terminal, so that their
|
||||
// entire buffer will be cleared as well.
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
if (enableCmdShim && gci.IsInVtIoMode())
|
||||
if (auto writer = gci.GetVtWriterForBuffer(&context))
|
||||
{
|
||||
const auto currentBufferDimensions = buffer.GetBufferSize().Dimensions();
|
||||
const auto sourceIsWholeBuffer = (source.top == 0) &&
|
||||
(source.left == 0) &&
|
||||
(source.right == currentBufferDimensions.width) &&
|
||||
(source.bottom == currentBufferDimensions.height);
|
||||
const auto targetIsNegativeBufferHeight = (target.x == 0) &&
|
||||
(target.y == -currentBufferDimensions.height);
|
||||
const auto noClipProvided = clip == std::nullopt;
|
||||
const auto fillIsBlank = (fillCharacter == UNICODE_SPACE) &&
|
||||
(fillAttribute == buffer.GetAttributes().GetLegacyAttributes());
|
||||
auto& buffer = context.GetActiveBuffer();
|
||||
|
||||
if (sourceIsWholeBuffer && targetIsNegativeBufferHeight && noClipProvided && fillIsBlank)
|
||||
// However, if the character is null and we were given a null attribute (represented as legacy 0),
|
||||
// then we'll just fill with spaces and whatever the buffer's default colors are.
|
||||
if (fillCharacter == UNICODE_NULL && fillAttribute == 0)
|
||||
{
|
||||
// It's important that we flush the renderer at this point so we don't
|
||||
// have any pending output rendered after the scrollback is cleared.
|
||||
ServiceLocator::LocateGlobals().pRender->TriggerFlush(false);
|
||||
hr = gci.GetVtIo()->ManuallyClearScrollback();
|
||||
fillCharacter = UNICODE_SPACE;
|
||||
fillAttribute = buffer.GetAttributes().GetLegacyAttributes();
|
||||
}
|
||||
|
||||
// GH#3126 - This is a shim for cmd's `cls` function. In the
|
||||
// legacy console, `cls` is supposed to clear the entire buffer. In
|
||||
// conpty however, there's no difference between the viewport and the
|
||||
// entirety of the buffer. We're going to see if this API call exactly
|
||||
// matched the way we expect cmd to call it. If it does, then
|
||||
// let's manually emit a Full Reset (RIS).
|
||||
const auto bufferSize = buffer.GetBufferSize();
|
||||
if (enableCmdShim &&
|
||||
source.left <= 0 && source.top <= 0 &&
|
||||
source.right >= bufferSize.RightInclusive() && source.bottom >= bufferSize.BottomInclusive() &&
|
||||
target.x == 0 && target.y <= -bufferSize.BottomExclusive() &&
|
||||
!clip &&
|
||||
fillCharacter == UNICODE_SPACE && fillAttribute == buffer.GetAttributes().GetLegacyAttributes())
|
||||
{
|
||||
WriteClearScreen(context);
|
||||
writer.Submit();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
const auto clipViewport = clip ? Viewport::FromInclusive(*clip) : bufferSize;
|
||||
const auto sourceViewport = Viewport::FromInclusive(source);
|
||||
Viewport readViewport;
|
||||
Viewport writtenViewport;
|
||||
|
||||
const auto w = std::max(0, sourceViewport.Width());
|
||||
const auto h = std::max(0, sourceViewport.Height());
|
||||
const auto a = static_cast<size_t>(w * h);
|
||||
if (a == 0)
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
til::small_vector<CHAR_INFO, 1024> backup;
|
||||
til::small_vector<CHAR_INFO, 1024> fill;
|
||||
|
||||
backup.resize(a, CHAR_INFO{ fillCharacter, fillAttribute });
|
||||
fill.resize(a, CHAR_INFO{ fillCharacter, fillAttribute });
|
||||
|
||||
writer.BackupCursor();
|
||||
|
||||
RETURN_IF_FAILED(ReadConsoleOutputWImplHelper(context, backup, sourceViewport, readViewport));
|
||||
RETURN_IF_FAILED(WriteConsoleOutputWImplHelper(context, fill, w, sourceViewport.Clamp(clipViewport), writtenViewport));
|
||||
RETURN_IF_FAILED(WriteConsoleOutputWImplHelper(context, backup, w, Viewport::FromDimensions(target, readViewport.Dimensions()).Clamp(clipViewport), writtenViewport));
|
||||
|
||||
writer.Submit();
|
||||
}
|
||||
else
|
||||
{
|
||||
auto& buffer = context.GetActiveBuffer();
|
||||
TextAttribute useThisAttr(fillAttribute);
|
||||
ScrollRegion(buffer, source, clip, target, fillCharacter, useThisAttr);
|
||||
}
|
||||
|
||||
return hr;
|
||||
return S_OK;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
}
|
||||
@ -984,6 +1087,13 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont
|
||||
const TextAttribute attr{ attribute };
|
||||
context.SetAttributes(attr);
|
||||
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
if (auto writer = gci.GetVtWriterForBuffer(&context))
|
||||
{
|
||||
writer.WriteAttributes(attr);
|
||||
writer.Submit();
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
@ -1103,7 +1213,6 @@ void ApiRoutines::GetConsoleWindowImpl(HWND& hwnd) noexcept
|
||||
LockConsole();
|
||||
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
||||
const IConsoleWindow* pWindow = ServiceLocator::LocateConsoleWindow();
|
||||
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
if (pWindow != nullptr)
|
||||
{
|
||||
hwnd = pWindow->GetWindowHandle();
|
||||
@ -1115,6 +1224,7 @@ void ApiRoutines::GetConsoleWindowImpl(HWND& hwnd) noexcept
|
||||
// doesn't actually do anything, but is a unique HWND to this
|
||||
// console, so that they know that this console is in fact a real
|
||||
// console window.
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
if (gci.IsInVtIoMode())
|
||||
{
|
||||
hwnd = ServiceLocator::LocatePseudoWindow();
|
||||
@ -1516,6 +1626,16 @@ void ApiRoutines::GetConsoleDisplayModeImpl(ULONG& flags) noexcept
|
||||
LockConsole();
|
||||
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
||||
|
||||
ServiceLocator::LocateGlobals().getConsoleInformation().SetTitle(title);
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
gci.SetTitle(title);
|
||||
|
||||
if (auto writer = gci.GetVtWriter())
|
||||
{
|
||||
writer.WriteUTF8("\x1b]0;");
|
||||
writer.WriteUTF16StripControlChars(title);
|
||||
writer.WriteUTF8("\x7");
|
||||
writer.Submit();
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
@ -25,19 +25,3 @@ bool Globals::IsHeadless() const
|
||||
{
|
||||
return launchArgs.IsHeadless();
|
||||
}
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
// Method Description:
|
||||
// - This is a test helper method. It can be used to trick us into responding
|
||||
// true to `IsHeadless`, which will cause the console host to act in conpty
|
||||
// mode.
|
||||
// Arguments:
|
||||
// - vtRenderEngine: a VT renderer that our VtIo should use as the vt engine during these tests
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Globals::EnableConptyModeForTests(std::unique_ptr<Microsoft::Console::Render::VtEngine> vtRenderEngine, const bool resizeQuirk)
|
||||
{
|
||||
launchArgs.EnableConptyModeForTests();
|
||||
getConsoleInformation().GetVtIo()->EnableConptyModeForTests(std::move(vtRenderEngine), resizeQuirk);
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -76,10 +76,6 @@ public:
|
||||
wil::unique_threadpool_wait handoffInboxConsoleExitWait;
|
||||
bool defaultTerminalMarkerCheckRequired = false;
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
void EnableConptyModeForTests(std::unique_ptr<Microsoft::Console::Render::VtEngine> vtRenderEngine, const bool resizeQuirk = false);
|
||||
#endif
|
||||
|
||||
private:
|
||||
CONSOLE_INFORMATION ciConsoleInformation;
|
||||
ApiRoutines defaultApiRoutines;
|
||||
|
||||
@ -15,7 +15,6 @@
|
||||
namespace Microsoft::Console::Render
|
||||
{
|
||||
class Renderer;
|
||||
class VtEngine;
|
||||
}
|
||||
|
||||
class InputBuffer final : public ConsoleObjectHeader
|
||||
|
||||
@ -33,6 +33,14 @@ ConhostInternalGetSet::ConhostInternalGetSet(_In_ IIoProvider& io) :
|
||||
// - <none>
|
||||
void ConhostInternalGetSet::ReturnResponse(const std::wstring_view response)
|
||||
{
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
|
||||
// ConPTY should not respond to requests. That's the job of the terminal.
|
||||
if (gci.IsInVtIoMode())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO GH#4954 During the input refactor we may want to add a "priority" input list
|
||||
// to make sure that "response" input is spooled directly into the application.
|
||||
// We switched this to an append (vs. a prepend) to fix GH#1637, a bug where two CPR
|
||||
@ -202,7 +210,7 @@ CursorType ConhostInternalGetSet::GetUserDefaultCursorStyle() const
|
||||
// - <none>
|
||||
void ConhostInternalGetSet::ShowWindow(bool showOrHide)
|
||||
{
|
||||
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
const auto hwnd = gci.IsInVtIoMode() ? ServiceLocator::LocatePseudoWindow() : ServiceLocator::LocateConsoleWindow()->GetWindowHandle();
|
||||
|
||||
// GH#13301 - When we send this ShowWindow message, if we send it to the
|
||||
@ -279,11 +287,17 @@ void ConhostInternalGetSet::SetWorkingDirectory(const std::wstring_view /*uri*/)
|
||||
// - true if successful. false otherwise.
|
||||
void ConhostInternalGetSet::PlayMidiNote(const int noteNumber, const int velocity, const std::chrono::microseconds duration)
|
||||
{
|
||||
const auto window = ServiceLocator::LocateConsoleWindow();
|
||||
if (!window)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Unlock the console, so the UI doesn't hang while we're busy.
|
||||
UnlockConsole();
|
||||
|
||||
// This call will block for the duration, unless shutdown early.
|
||||
const auto windowHandle = ServiceLocator::LocateConsoleWindow()->GetWindowHandle();
|
||||
const auto windowHandle = window->GetWindowHandle();
|
||||
auto& midiAudio = ServiceLocator::LocateGlobals().getConsoleInformation().GetMidiAudio();
|
||||
midiAudio.PlayNote(windowHandle, noteNumber, velocity, std::chrono::duration_cast<std::chrono::milliseconds>(duration));
|
||||
|
||||
@ -332,11 +346,9 @@ bool ConhostInternalGetSet::ResizeWindow(const til::CoordType sColumns, const ti
|
||||
}
|
||||
|
||||
// If the cursor row is now past the bottom of the viewport, we'll have to
|
||||
// move the viewport down to bring it back into view. However, we don't want
|
||||
// to do this in pty mode, because the conpty resize operation is dependent
|
||||
// on the viewport *not* being adjusted.
|
||||
// move the viewport down to bring it back into view.
|
||||
const auto cursorOverflow = csbiex.dwCursorPosition.Y - newViewport.BottomInclusive();
|
||||
if (cursorOverflow > 0 && !IsConsolePty())
|
||||
if (cursorOverflow > 0)
|
||||
{
|
||||
newViewport = Viewport::Offset(newViewport, { 0, cursorOverflow });
|
||||
}
|
||||
@ -353,17 +365,6 @@ bool ConhostInternalGetSet::ResizeWindow(const til::CoordType sColumns, const ti
|
||||
return true;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Checks if the console host is acting as a pty.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - true if we're in pty mode.
|
||||
bool ConhostInternalGetSet::IsConsolePty() const
|
||||
{
|
||||
return ServiceLocator::LocateGlobals().getConsoleInformation().IsInVtIoMode();
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Checks if the InputBuffer is willing to accept VT Input directly
|
||||
// IsVtInputEnabled is an internal-only "API" call that the vt commands can execute,
|
||||
|
||||
@ -62,7 +62,6 @@ public:
|
||||
void SetWorkingDirectory(const std::wstring_view uri) override;
|
||||
void PlayMidiNote(const int noteNumber, const int velocity, const std::chrono::microseconds duration) override;
|
||||
|
||||
bool IsConsolePty() const override;
|
||||
bool IsVtInputEnabled() const override;
|
||||
|
||||
void NotifyAccessibilityChange(const til::rect& changedRect) override;
|
||||
|
||||
@ -1200,20 +1200,7 @@ void SCREEN_INFORMATION::_InternalSetViewportSize(const til::size* const pcoordS
|
||||
_viewport = newViewport;
|
||||
Tracing::s_TraceWindowViewport(_viewport);
|
||||
|
||||
// In Conpty mode, call TriggerScroll here without params. By not providing
|
||||
// params, the renderer will make sure to update the VtEngine with the
|
||||
// updated viewport size. If we don't do this, the engine can get into a
|
||||
// torn state on this frame.
|
||||
//
|
||||
// Without this statement, the engine won't be told about the new view size
|
||||
// till the start of the next frame. If any other text gets output before
|
||||
// that frame starts, there's a very real chance that it'll cause errors as
|
||||
// the engine tries to invalidate those regions.
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
if (gci.IsInVtIoMode() && ServiceLocator::LocateGlobals().pRender)
|
||||
{
|
||||
ServiceLocator::LocateGlobals().pRender->TriggerScroll();
|
||||
}
|
||||
if (gci.HasPendingCookedRead())
|
||||
{
|
||||
gci.CookedReadData().RedrawAfterResize();
|
||||
@ -1761,6 +1748,11 @@ const SCREEN_INFORMATION& SCREEN_INFORMATION::GetMainBuffer() const
|
||||
return *this;
|
||||
}
|
||||
|
||||
const SCREEN_INFORMATION* SCREEN_INFORMATION::GetAltBuffer() const noexcept
|
||||
{
|
||||
return _psiAlternateBuffer;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Instantiates a new buffer to be used as an alternate buffer. This buffer
|
||||
// does not have a driver handle associated with it and shares a state
|
||||
@ -1901,15 +1893,6 @@ void SCREEN_INFORMATION::_handleDeferredResize(SCREEN_INFORMATION& siMain)
|
||||
s_RemoveScreenBuffer(psiOldAltBuffer); // this will also delete the old alt buffer
|
||||
}
|
||||
|
||||
// GH#381: When we switch into the alt buffer:
|
||||
// * flush the current frame, to clear out anything that we prepared for this buffer.
|
||||
// * Emit a ?1049h/l to the remote side, to let them know that we've switched buffers.
|
||||
if (gci.IsInVtIoMode() && ServiceLocator::LocateGlobals().pRender)
|
||||
{
|
||||
ServiceLocator::LocateGlobals().pRender->TriggerFlush(false);
|
||||
LOG_IF_FAILED(gci.GetVtIo()->SwitchScreenBuffer(true));
|
||||
}
|
||||
|
||||
::SetActiveScreenBuffer(*psiNewAltBuffer);
|
||||
|
||||
// Kind of a hack until we have proper signal channels: If the client app wants window size events, send one for
|
||||
@ -1937,15 +1920,6 @@ void SCREEN_INFORMATION::UseMainScreenBuffer()
|
||||
{
|
||||
_handleDeferredResize(*psiMain);
|
||||
|
||||
// GH#381: When we switch into the main buffer:
|
||||
// * flush the current frame, to clear out anything that we prepared for this buffer.
|
||||
// * Emit a ?1049h/l to the remote side, to let them know that we've switched buffers.
|
||||
if (gci.IsInVtIoMode() && ServiceLocator::LocateGlobals().pRender)
|
||||
{
|
||||
ServiceLocator::LocateGlobals().pRender->TriggerFlush(false);
|
||||
LOG_IF_FAILED(gci.GetVtIo()->SwitchScreenBuffer(false));
|
||||
}
|
||||
|
||||
::SetActiveScreenBuffer(*psiMain);
|
||||
psiMain->UpdateScrollBars(); // The alt had disabled scrollbars, re-enable them
|
||||
|
||||
@ -1994,7 +1968,7 @@ bool SCREEN_INFORMATION::_IsAltBuffer() const
|
||||
// - true iff this buffer has a main buffer.
|
||||
bool SCREEN_INFORMATION::_IsInPtyMode() const
|
||||
{
|
||||
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
return _IsAltBuffer() || gci.IsInVtIoMode();
|
||||
}
|
||||
|
||||
@ -2084,8 +2058,6 @@ void SCREEN_INFORMATION::SetPopupAttributes(const TextAttribute& popupAttributes
|
||||
void SCREEN_INFORMATION::SetDefaultAttributes(const TextAttribute& attributes,
|
||||
const TextAttribute& popupAttributes)
|
||||
{
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
|
||||
const auto oldPrimaryAttributes = GetAttributes();
|
||||
const auto oldPopupAttributes = GetPopupAttributes();
|
||||
|
||||
@ -2101,10 +2073,7 @@ void SCREEN_INFORMATION::SetDefaultAttributes(const TextAttribute& attributes,
|
||||
// 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()))
|
||||
{
|
||||
_textBuffer->TriggerRedrawAll();
|
||||
}
|
||||
_textBuffer->TriggerRedrawAll();
|
||||
|
||||
// If we're an alt buffer, also update our main buffer.
|
||||
if (_psiMainBuffer)
|
||||
@ -2200,32 +2169,6 @@ void SCREEN_INFORMATION::SetViewport(const Viewport& newViewport,
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Sets up the Output state machine to be in pty mode. Sequences it doesn't
|
||||
// understand will be written to the pTtyConnection passed in here.
|
||||
// Arguments:
|
||||
// - pTtyConnection: This is a TerminalOutputConnection that we can write the
|
||||
// sequence we didn't understand to.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void SCREEN_INFORMATION::SetTerminalConnection(_In_ VtEngine* const pTtyConnection)
|
||||
{
|
||||
auto& engine = reinterpret_cast<OutputStateMachineEngine&>(_stateMachine->Engine());
|
||||
if (pTtyConnection)
|
||||
{
|
||||
engine.SetTerminalConnection(pTtyConnection,
|
||||
[&stateMachine = *_stateMachine]() -> bool {
|
||||
ServiceLocator::LocateGlobals().pRender->NotifyPaintFrame();
|
||||
return stateMachine.FlushToTerminal();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
engine.SetTerminalConnection(nullptr,
|
||||
nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Writes cells to the output buffer at the cursor position.
|
||||
// Arguments:
|
||||
|
||||
@ -47,14 +47,6 @@ Revision History:
|
||||
#include "../types/inc/Viewport.hpp"
|
||||
class ConversionAreaInfo; // forward decl window. circular reference
|
||||
|
||||
// fwdecl unittest classes
|
||||
#ifdef UNIT_TESTING
|
||||
namespace TerminalCoreUnitTests
|
||||
{
|
||||
class ConptyRoundtripTests;
|
||||
};
|
||||
#endif
|
||||
|
||||
class SCREEN_INFORMATION : public ConsoleObjectHeader, public Microsoft::Console::IIoProvider
|
||||
{
|
||||
public:
|
||||
@ -199,7 +191,7 @@ public:
|
||||
|
||||
SCREEN_INFORMATION& GetMainBuffer();
|
||||
const SCREEN_INFORMATION& GetMainBuffer() const;
|
||||
|
||||
const SCREEN_INFORMATION* GetAltBuffer() const noexcept;
|
||||
SCREEN_INFORMATION& GetActiveBuffer();
|
||||
const SCREEN_INFORMATION& GetActiveBuffer() const;
|
||||
|
||||
@ -213,8 +205,6 @@ public:
|
||||
|
||||
[[nodiscard]] HRESULT ClearBuffer();
|
||||
|
||||
void SetTerminalConnection(_In_ Microsoft::Console::Render::VtEngine* const pTtyConnection);
|
||||
|
||||
void UpdateBottom();
|
||||
|
||||
FontInfo& GetCurrentFont() noexcept;
|
||||
@ -226,6 +216,9 @@ public:
|
||||
void SetIgnoreLegacyEquivalentVTAttributes() noexcept;
|
||||
void ResetIgnoreLegacyEquivalentVTAttributes() noexcept;
|
||||
|
||||
[[nodiscard]] NTSTATUS ResizeWithReflow(const til::size coordnewScreenSize);
|
||||
[[nodiscard]] NTSTATUS ResizeTraditional(const til::size coordNewScreenSize);
|
||||
|
||||
private:
|
||||
SCREEN_INFORMATION(_In_ Microsoft::Console::Interactivity::IWindowMetrics* pMetrics,
|
||||
_In_ Microsoft::Console::Interactivity::IAccessibilityNotifier* pNotifier,
|
||||
@ -249,9 +242,6 @@ private:
|
||||
_Out_ bool* const pfIsHorizontalVisible,
|
||||
_Out_ bool* const pfIsVerticalVisible);
|
||||
|
||||
[[nodiscard]] NTSTATUS ResizeWithReflow(const til::size coordnewScreenSize);
|
||||
[[nodiscard]] NTSTATUS ResizeTraditional(const til::size coordNewScreenSize);
|
||||
|
||||
[[nodiscard]] NTSTATUS _InitializeOutputStateMachine();
|
||||
void _FreeOutputStateMachine();
|
||||
|
||||
@ -297,7 +287,5 @@ private:
|
||||
friend class TextBufferIteratorTests;
|
||||
friend class ScreenBufferTests;
|
||||
friend class CommonState;
|
||||
friend class ConptyOutputTests;
|
||||
friend class TerminalCoreUnitTests::ConptyRoundtripTests;
|
||||
#endif
|
||||
};
|
||||
|
||||
@ -103,7 +103,10 @@ public:
|
||||
bool IsConsoleLocked() const noexcept;
|
||||
ULONG GetCSRecursionCount() const noexcept;
|
||||
|
||||
Microsoft::Console::VirtualTerminal::VtIo* GetVtIo();
|
||||
Microsoft::Console::VirtualTerminal::VtIo* GetVtIo() noexcept;
|
||||
Microsoft::Console::VirtualTerminal::VtIo::Writer GetVtWriter() noexcept;
|
||||
Microsoft::Console::VirtualTerminal::VtIo::Writer GetVtWriterForBuffer(const SCREEN_INFORMATION* context) noexcept;
|
||||
bool IsInVtIoMode() const noexcept;
|
||||
|
||||
SCREEN_INFORMATION& GetActiveOutputBuffer() override;
|
||||
const SCREEN_INFORMATION& GetActiveOutputBuffer() const override;
|
||||
@ -112,7 +115,6 @@ public:
|
||||
|
||||
InputBuffer* const GetActiveInputBuffer() const override;
|
||||
|
||||
bool IsInVtIoMode() const;
|
||||
bool HasPendingCookedRead() const noexcept;
|
||||
bool HasPendingPopup() const noexcept;
|
||||
const COOKED_READ_DATA& CookedReadData() const noexcept;
|
||||
|
||||
@ -574,7 +574,7 @@ try
|
||||
|
||||
// GH#13211 - Make sure the terminal obeys the resizing quirk. Otherwise,
|
||||
// defterm connections to the Terminal are going to have weird resizing.
|
||||
const auto commandLine = fmt::format(FMT_COMPILE(L" --headless --resizeQuirk --signal {:#x}"),
|
||||
const auto commandLine = fmt::format(FMT_COMPILE(L" --headless --signal {:#x}"),
|
||||
(int64_t)signalPipeOurSide.release());
|
||||
|
||||
ConsoleArguments consoleArgs(commandLine, inPipeOurSide.release(), outPipeOurSide.release());
|
||||
@ -841,24 +841,25 @@ PWSTR TranslateConsoleTitle(_In_ PCWSTR pwszConsoleTitle, const BOOL fUnexpand,
|
||||
// No matter what, create a renderer.
|
||||
try
|
||||
{
|
||||
g.pRender = nullptr;
|
||||
if (!gci.IsInVtIoMode())
|
||||
{
|
||||
auto renderThread = std::make_unique<RenderThread>();
|
||||
// stash a local pointer to the thread here -
|
||||
// We're going to give ownership of the thread to the Renderer,
|
||||
// but the thread also need to be told who its renderer is,
|
||||
// and we can't do that until the renderer is constructed.
|
||||
auto* const localPointerToThread = renderThread.get();
|
||||
|
||||
auto renderThread = std::make_unique<RenderThread>();
|
||||
// stash a local pointer to the thread here -
|
||||
// We're going to give ownership of the thread to the Renderer,
|
||||
// but the thread also need to be told who its renderer is,
|
||||
// and we can't do that until the renderer is constructed.
|
||||
auto* const localPointerToThread = renderThread.get();
|
||||
g.pRender = new Renderer(gci.GetRenderSettings(), &gci.renderData, nullptr, 0, std::move(renderThread));
|
||||
|
||||
g.pRender = new Renderer(gci.GetRenderSettings(), &gci.renderData, nullptr, 0, std::move(renderThread));
|
||||
THROW_IF_FAILED(localPointerToThread->Initialize(g.pRender));
|
||||
|
||||
THROW_IF_FAILED(localPointerToThread->Initialize(g.pRender));
|
||||
|
||||
// Set up the renderer to be used to calculate the width of a glyph,
|
||||
// should we be unable to figure out its width another way.
|
||||
CodepointWidthDetector::Singleton().SetFallbackMethod([](const std::wstring_view& glyph) {
|
||||
return ServiceLocator::LocateGlobals().pRender->IsGlyphWideByFont(glyph);
|
||||
});
|
||||
// Set up the renderer to be used to calculate the width of a glyph,
|
||||
// should we be unable to figure out its width another way.
|
||||
CodepointWidthDetector::Singleton().SetFallbackMethod([](const std::wstring_view& glyph) {
|
||||
return ServiceLocator::LocateGlobals().pRender->IsGlyphWideByFont(glyph);
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
@ -876,7 +877,10 @@ PWSTR TranslateConsoleTitle(_In_ PCWSTR pwszConsoleTitle, const BOOL fUnexpand,
|
||||
}
|
||||
|
||||
// Allow the renderer to paint once the rest of the console is hooked up.
|
||||
g.pRender->EnablePainting();
|
||||
if (g.pRender)
|
||||
{
|
||||
g.pRender->EnablePainting();
|
||||
}
|
||||
|
||||
if (SUCCEEDED_NTSTATUS(Status) && ConsoleConnectionDeservesVisibleWindow(p))
|
||||
{
|
||||
|
||||
@ -1,465 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
#include <wextestclass.h>
|
||||
#include "../../inc/consoletaeftemplates.hpp"
|
||||
#include "../../types/inc/Viewport.hpp"
|
||||
|
||||
#include "../../renderer/base/Renderer.hpp"
|
||||
#include "../../renderer/vt/Xterm256Engine.hpp"
|
||||
#include "../../renderer/vt/XtermEngine.hpp"
|
||||
#include "../Settings.hpp"
|
||||
|
||||
#include "CommonState.hpp"
|
||||
|
||||
using namespace WEX::Common;
|
||||
using namespace WEX::Logging;
|
||||
using namespace WEX::TestExecution;
|
||||
using namespace Microsoft::Console::Types;
|
||||
using namespace Microsoft::Console::Interactivity;
|
||||
using namespace Microsoft::Console::VirtualTerminal;
|
||||
|
||||
using namespace Microsoft::Console;
|
||||
using namespace Microsoft::Console::Render;
|
||||
using namespace Microsoft::Console::Types;
|
||||
|
||||
class ConptyOutputTests
|
||||
{
|
||||
// !!! DANGER: Many tests in this class expect the Terminal and Host buffers
|
||||
// to be 80x32. If you change these, you'll probably inadvertently break a
|
||||
// bunch of tests !!!
|
||||
static const til::CoordType TerminalViewWidth = 80;
|
||||
static const til::CoordType TerminalViewHeight = 32;
|
||||
|
||||
// This test class is to write some things into the PTY and then check that
|
||||
// the rendering that is coming out of the VT-sequence generator is exactly
|
||||
// as we expect it to be.
|
||||
BEGIN_TEST_CLASS(ConptyOutputTests)
|
||||
TEST_CLASS_PROPERTY(L"IsolationLevel", L"Class")
|
||||
END_TEST_CLASS()
|
||||
|
||||
TEST_CLASS_SETUP(ClassSetup)
|
||||
{
|
||||
m_state = std::make_unique<CommonState>();
|
||||
|
||||
m_state->InitEvents();
|
||||
m_state->PrepareGlobalInputBuffer();
|
||||
m_state->PrepareGlobalScreenBuffer(TerminalViewWidth, TerminalViewHeight, TerminalViewWidth, TerminalViewHeight);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST_CLASS_CLEANUP(ClassCleanup)
|
||||
{
|
||||
m_state->CleanupGlobalScreenBuffer();
|
||||
m_state->CleanupGlobalInputBuffer();
|
||||
|
||||
m_state.release();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST_METHOD_SETUP(MethodSetup)
|
||||
{
|
||||
// Set up some sane defaults
|
||||
auto& g = ServiceLocator::LocateGlobals();
|
||||
auto& gci = g.getConsoleInformation();
|
||||
gci.SetColorTableEntry(TextColor::DEFAULT_FOREGROUND, INVALID_COLOR);
|
||||
gci.SetColorTableEntry(TextColor::DEFAULT_BACKGROUND, INVALID_COLOR);
|
||||
gci.SetFillAttribute(0x07); // DARK_WHITE on DARK_BLACK
|
||||
gci.CalculateDefaultColorIndices();
|
||||
|
||||
g.pRender = new Renderer(gci.GetRenderSettings(), &gci.renderData, nullptr, 0, nullptr);
|
||||
|
||||
m_state->PrepareNewTextBufferInfo(true, TerminalViewWidth, TerminalViewHeight);
|
||||
auto& currentBuffer = gci.GetActiveOutputBuffer();
|
||||
// Make sure a test hasn't left us in the alt buffer on accident
|
||||
VERIFY_IS_FALSE(currentBuffer._IsAltBuffer());
|
||||
VERIFY_SUCCEEDED(currentBuffer.SetViewportOrigin(true, { 0, 0 }, true));
|
||||
VERIFY_ARE_EQUAL(til::point{}, currentBuffer.GetTextBuffer().GetCursor().GetPosition());
|
||||
|
||||
// Set up an xterm-256 renderer for conpty
|
||||
auto hFile = wil::unique_hfile(INVALID_HANDLE_VALUE);
|
||||
auto initialViewport = currentBuffer.GetViewport();
|
||||
|
||||
auto vtRenderEngine = std::make_unique<Xterm256Engine>(std::move(hFile),
|
||||
initialViewport);
|
||||
auto pfn = std::bind(&ConptyOutputTests::_writeCallback, this, std::placeholders::_1, std::placeholders::_2);
|
||||
vtRenderEngine->SetTestCallback(pfn);
|
||||
|
||||
g.pRender->AddRenderEngine(vtRenderEngine.get());
|
||||
gci.GetActiveOutputBuffer().SetTerminalConnection(vtRenderEngine.get());
|
||||
|
||||
expectedOutput.clear();
|
||||
|
||||
// Manually set the console into conpty mode. We're not actually going
|
||||
// to set up the pipes for conpty, but we want the console to behave
|
||||
// like it would in conpty mode.
|
||||
g.EnableConptyModeForTests(std::move(vtRenderEngine));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST_METHOD_CLEANUP(MethodCleanup)
|
||||
{
|
||||
m_state->CleanupNewTextBufferInfo();
|
||||
|
||||
auto& g = ServiceLocator::LocateGlobals();
|
||||
delete g.pRender;
|
||||
|
||||
VERIFY_ARE_EQUAL(0u, expectedOutput.size(), L"Tests should drain all the output they push into the expected output buffer.");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST_METHOD(ConptyOutputTestCanary);
|
||||
TEST_METHOD(SimpleWriteOutputTest);
|
||||
TEST_METHOD(WriteTwoLinesUsesNewline);
|
||||
TEST_METHOD(WriteAFewSimpleLines);
|
||||
TEST_METHOD(InvalidateUntilOneBeforeEnd);
|
||||
TEST_METHOD(SetConsoleTitleWithControlChars);
|
||||
TEST_METHOD(IncludeBackgroundColorChangesInFirstFrame);
|
||||
TEST_METHOD(MoveCursorAfterWrapForced);
|
||||
|
||||
private:
|
||||
bool _writeCallback(const char* const pch, const size_t cch);
|
||||
void _flushFirstFrame();
|
||||
std::deque<std::string> expectedOutput;
|
||||
std::unique_ptr<CommonState> m_state;
|
||||
};
|
||||
|
||||
bool ConptyOutputTests::_writeCallback(const char* const pch, const size_t cch)
|
||||
{
|
||||
// Since rendering happens on a background thread that doesn't have the exception handler on it
|
||||
// we need to rely on VERIFY's return codes instead of exceptions.
|
||||
const WEX::TestExecution::DisableVerifyExceptions disableExceptionsScope;
|
||||
|
||||
auto actualString = std::string(pch, cch);
|
||||
RETURN_BOOL_IF_FALSE(VERIFY_IS_GREATER_THAN(expectedOutput.size(),
|
||||
static_cast<size_t>(0),
|
||||
NoThrowString().Format(L"writing=\"%hs\", expecting %u strings", actualString.c_str(), expectedOutput.size())));
|
||||
|
||||
auto first = expectedOutput.front();
|
||||
expectedOutput.pop_front();
|
||||
|
||||
Log::Comment(NoThrowString().Format(L"Expected =\t\"%hs\"", first.c_str()));
|
||||
Log::Comment(NoThrowString().Format(L"Actual =\t\"%hs\"", actualString.c_str()));
|
||||
|
||||
RETURN_BOOL_IF_FALSE(VERIFY_ARE_EQUAL(first.length(), cch));
|
||||
RETURN_BOOL_IF_FALSE(VERIFY_ARE_EQUAL(first, actualString));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ConptyOutputTests::_flushFirstFrame()
|
||||
{
|
||||
auto& g = ServiceLocator::LocateGlobals();
|
||||
auto& renderer = *g.pRender;
|
||||
|
||||
expectedOutput.push_back("\x1b[2J");
|
||||
expectedOutput.push_back("\x1b[m");
|
||||
expectedOutput.push_back("\x1b[H"); // Go Home
|
||||
expectedOutput.push_back("\x1b[?25h");
|
||||
|
||||
VERIFY_SUCCEEDED(renderer.PaintFrame());
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - Helper function to validate that a number of characters in a row are all
|
||||
// the same. Validates that the next end-start characters are all equal to the
|
||||
// provided string. Will move the provided iterator as it validates. The
|
||||
// caller should ensure that `iter` starts where they would like to validate.
|
||||
// Arguments:
|
||||
// - expectedChar: The character (or characters) we're expecting
|
||||
// - iter: a iterator pointing to the cell we'd like to start validating at.
|
||||
// - start: the first index in the range we'd like to validate
|
||||
// - end: the last index in the range we'd like to validate
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void _verifySpanOfText(const wchar_t* const expectedChar,
|
||||
TextBufferCellIterator& iter,
|
||||
const int start,
|
||||
const int end)
|
||||
{
|
||||
for (auto x = start; x < end; x++)
|
||||
{
|
||||
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
|
||||
if (iter->Chars() != expectedChar)
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(L"character [%d] was mismatched", x));
|
||||
}
|
||||
VERIFY_ARE_EQUAL(expectedChar, (iter++)->Chars());
|
||||
}
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Successfully validated %d characters were '%s'", end - start, expectedChar));
|
||||
}
|
||||
|
||||
void ConptyOutputTests::ConptyOutputTestCanary()
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"This is a simple test to make sure that everything is working as expected."));
|
||||
|
||||
_flushFirstFrame();
|
||||
}
|
||||
|
||||
void ConptyOutputTests::SimpleWriteOutputTest()
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Write some simple output, and make sure it gets rendered largely "
|
||||
L"unmodified to the terminal"));
|
||||
|
||||
auto& g = ServiceLocator::LocateGlobals();
|
||||
auto& renderer = *g.pRender;
|
||||
auto& gci = g.getConsoleInformation();
|
||||
auto& si = gci.GetActiveOutputBuffer();
|
||||
auto& sm = si.GetStateMachine();
|
||||
|
||||
_flushFirstFrame();
|
||||
|
||||
expectedOutput.push_back("Hello World");
|
||||
sm.ProcessString(L"Hello World");
|
||||
|
||||
VERIFY_SUCCEEDED(renderer.PaintFrame());
|
||||
}
|
||||
|
||||
void ConptyOutputTests::WriteTwoLinesUsesNewline()
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Write two lines of output. We should use \r\n to move the cursor"));
|
||||
|
||||
auto& g = ServiceLocator::LocateGlobals();
|
||||
auto& renderer = *g.pRender;
|
||||
auto& gci = g.getConsoleInformation();
|
||||
auto& si = gci.GetActiveOutputBuffer();
|
||||
auto& sm = si.GetStateMachine();
|
||||
auto& tb = si.GetTextBuffer();
|
||||
|
||||
_flushFirstFrame();
|
||||
|
||||
sm.ProcessString(L"AAA");
|
||||
sm.ProcessString(L"\x1b[2;1H");
|
||||
sm.ProcessString(L"BBB");
|
||||
|
||||
{
|
||||
auto iter = tb.GetCellDataAt({ 0, 0 });
|
||||
VERIFY_ARE_EQUAL(L"A", (iter++)->Chars());
|
||||
VERIFY_ARE_EQUAL(L"A", (iter++)->Chars());
|
||||
VERIFY_ARE_EQUAL(L"A", (iter++)->Chars());
|
||||
}
|
||||
{
|
||||
auto iter = tb.GetCellDataAt({ 0, 1 });
|
||||
VERIFY_ARE_EQUAL(L"B", (iter++)->Chars());
|
||||
VERIFY_ARE_EQUAL(L"B", (iter++)->Chars());
|
||||
VERIFY_ARE_EQUAL(L"B", (iter++)->Chars());
|
||||
}
|
||||
|
||||
expectedOutput.push_back("AAA");
|
||||
expectedOutput.push_back("\r\n");
|
||||
expectedOutput.push_back("BBB");
|
||||
|
||||
VERIFY_SUCCEEDED(renderer.PaintFrame());
|
||||
}
|
||||
|
||||
void ConptyOutputTests::WriteAFewSimpleLines()
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Write more lines of output. We should use \r\n to move the cursor"));
|
||||
|
||||
auto& g = ServiceLocator::LocateGlobals();
|
||||
auto& renderer = *g.pRender;
|
||||
auto& gci = g.getConsoleInformation();
|
||||
auto& si = gci.GetActiveOutputBuffer();
|
||||
auto& sm = si.GetStateMachine();
|
||||
auto& tb = si.GetTextBuffer();
|
||||
|
||||
_flushFirstFrame();
|
||||
|
||||
sm.ProcessString(L"AAA\n");
|
||||
sm.ProcessString(L"BBB\n");
|
||||
sm.ProcessString(L"\n");
|
||||
sm.ProcessString(L"CCC");
|
||||
|
||||
{
|
||||
auto iter = tb.GetCellDataAt({ 0, 0 });
|
||||
VERIFY_ARE_EQUAL(L"A", (iter++)->Chars());
|
||||
VERIFY_ARE_EQUAL(L"A", (iter++)->Chars());
|
||||
VERIFY_ARE_EQUAL(L"A", (iter++)->Chars());
|
||||
}
|
||||
{
|
||||
auto iter = tb.GetCellDataAt({ 0, 1 });
|
||||
VERIFY_ARE_EQUAL(L"B", (iter++)->Chars());
|
||||
VERIFY_ARE_EQUAL(L"B", (iter++)->Chars());
|
||||
VERIFY_ARE_EQUAL(L"B", (iter++)->Chars());
|
||||
}
|
||||
{
|
||||
auto iter = tb.GetCellDataAt({ 0, 2 });
|
||||
VERIFY_ARE_EQUAL(L" ", (iter++)->Chars());
|
||||
VERIFY_ARE_EQUAL(L" ", (iter++)->Chars());
|
||||
VERIFY_ARE_EQUAL(L" ", (iter++)->Chars());
|
||||
}
|
||||
{
|
||||
auto iter = tb.GetCellDataAt({ 0, 3 });
|
||||
VERIFY_ARE_EQUAL(L"C", (iter++)->Chars());
|
||||
VERIFY_ARE_EQUAL(L"C", (iter++)->Chars());
|
||||
VERIFY_ARE_EQUAL(L"C", (iter++)->Chars());
|
||||
}
|
||||
|
||||
expectedOutput.push_back("AAA");
|
||||
expectedOutput.push_back("\r\n");
|
||||
expectedOutput.push_back("BBB");
|
||||
// Jump down to the fourth line because emitting spaces didn't do anything
|
||||
// and we will skip to emitting the CCC segment.
|
||||
expectedOutput.push_back("\x1b[4;1H");
|
||||
expectedOutput.push_back("CCC");
|
||||
|
||||
// Cursor goes back on.
|
||||
expectedOutput.push_back("\x1b[?25h");
|
||||
|
||||
VERIFY_SUCCEEDED(renderer.PaintFrame());
|
||||
}
|
||||
|
||||
void ConptyOutputTests::InvalidateUntilOneBeforeEnd()
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Make sure we don't use EL and wipe out the last column of text"));
|
||||
|
||||
auto& g = ServiceLocator::LocateGlobals();
|
||||
auto& renderer = *g.pRender;
|
||||
auto& gci = g.getConsoleInformation();
|
||||
auto& si = gci.GetActiveOutputBuffer();
|
||||
auto& sm = si.GetStateMachine();
|
||||
auto& tb = si.GetTextBuffer();
|
||||
|
||||
_flushFirstFrame();
|
||||
|
||||
// Move the cursor to width-15, draw 15 characters
|
||||
sm.ProcessString(L"\x1b[1;66H");
|
||||
sm.ProcessString(L"ABCDEFGHIJKLMNO");
|
||||
|
||||
{
|
||||
auto iter = tb.GetCellDataAt({ 78, 0 });
|
||||
VERIFY_ARE_EQUAL(L"N", (iter++)->Chars());
|
||||
VERIFY_ARE_EQUAL(L"O", (iter++)->Chars());
|
||||
}
|
||||
|
||||
expectedOutput.push_back("\x1b[65C");
|
||||
expectedOutput.push_back("ABCDEFGHIJKLMNO");
|
||||
|
||||
VERIFY_SUCCEEDED(renderer.PaintFrame());
|
||||
|
||||
// overstrike the first with X and the middle 8 with spaces
|
||||
sm.ProcessString(L"\x1b[1;66H");
|
||||
// ABCDEFGHIJKLMNO
|
||||
sm.ProcessString(L"X ");
|
||||
|
||||
{
|
||||
auto iter = tb.GetCellDataAt({ 78, 0 });
|
||||
VERIFY_ARE_EQUAL(L" ", (iter++)->Chars());
|
||||
VERIFY_ARE_EQUAL(L"O", (iter++)->Chars());
|
||||
}
|
||||
|
||||
expectedOutput.push_back("\x1b[1;66H");
|
||||
expectedOutput.push_back("X"); // sequence optimizer should choose ECH here
|
||||
expectedOutput.push_back("\x1b[13X");
|
||||
expectedOutput.push_back("\x1b[13C");
|
||||
|
||||
VERIFY_SUCCEEDED(renderer.PaintFrame());
|
||||
}
|
||||
|
||||
void ConptyOutputTests::SetConsoleTitleWithControlChars()
|
||||
{
|
||||
BEGIN_TEST_METHOD_PROPERTIES()
|
||||
TEST_METHOD_PROPERTY(L"Data:control", L"{0x00, 0x0A, 0x1B, 0x80, 0x9B, 0x9C}")
|
||||
TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method")
|
||||
END_TEST_METHOD_PROPERTIES()
|
||||
|
||||
int control;
|
||||
VERIFY_SUCCEEDED(TestData::TryGetValue(L"control", control));
|
||||
|
||||
auto& g = ServiceLocator::LocateGlobals();
|
||||
auto& renderer = *g.pRender;
|
||||
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"SetConsoleTitle with a control character (0x%02X) embedded in the text", control));
|
||||
|
||||
std::wstringstream titleText;
|
||||
titleText << L"Hello " << wchar_t(control) << L"World!";
|
||||
g.getConsoleInformation().SetTitle(titleText.str());
|
||||
|
||||
// This is the standard init sequences for the first frame.
|
||||
expectedOutput.push_back("\x1b[2J");
|
||||
expectedOutput.push_back("\x1b[m");
|
||||
expectedOutput.push_back("\x1b[H");
|
||||
|
||||
// The title change is propagated as an OSC 0 sequence.
|
||||
// Control characters are stripped, so it's always "Hello World".
|
||||
expectedOutput.push_back("\x1b]0;Hello World!\a");
|
||||
|
||||
// This is also part of the standard init sequence.
|
||||
expectedOutput.push_back("\x1b[?25h");
|
||||
|
||||
VERIFY_SUCCEEDED(renderer.PaintFrame());
|
||||
}
|
||||
|
||||
void ConptyOutputTests::IncludeBackgroundColorChangesInFirstFrame()
|
||||
{
|
||||
auto& g = ServiceLocator::LocateGlobals();
|
||||
auto& renderer = *g.pRender;
|
||||
auto& gci = g.getConsoleInformation();
|
||||
auto& si = gci.GetActiveOutputBuffer();
|
||||
auto& sm = si.GetStateMachine();
|
||||
|
||||
sm.ProcessString(L"\x1b[41mRun 1 \x1b[42mRun 2 \x1b[43mRun 3 \x1b[m");
|
||||
|
||||
expectedOutput.push_back("\x1b[2J"); // standard init sequence for the first frame
|
||||
expectedOutput.push_back("\x1b[m"); // standard init sequence for the first frame
|
||||
expectedOutput.push_back("\x1b[41m");
|
||||
expectedOutput.push_back("\x1b[H"); // standard init sequence for the first frame
|
||||
expectedOutput.push_back("Run 1 ");
|
||||
expectedOutput.push_back("\x1b[42m");
|
||||
expectedOutput.push_back("Run 2 ");
|
||||
expectedOutput.push_back("\x1b[43m");
|
||||
expectedOutput.push_back("Run 3 ");
|
||||
|
||||
// This is also part of the standard init sequence.
|
||||
expectedOutput.push_back("\x1b[?25h");
|
||||
|
||||
VERIFY_SUCCEEDED(renderer.PaintFrame());
|
||||
}
|
||||
|
||||
void ConptyOutputTests::MoveCursorAfterWrapForced()
|
||||
{
|
||||
auto& g = ServiceLocator::LocateGlobals();
|
||||
auto& renderer = *g.pRender;
|
||||
auto& gci = g.getConsoleInformation();
|
||||
auto& si = gci.GetActiveOutputBuffer();
|
||||
auto& sm = si.GetStateMachine();
|
||||
|
||||
// We write a character in the rightmost column to trigger the _wrapForced
|
||||
// flag. Technically this is a bug, but it's how things currently work.
|
||||
sm.ProcessString(L"\x1b[1;999H*");
|
||||
|
||||
expectedOutput.push_back("\x1b[2J"); // standard init sequence for the first frame
|
||||
expectedOutput.push_back("\x1b[m"); // standard init sequence for the first frame
|
||||
expectedOutput.push_back("\x1b[1;80H");
|
||||
expectedOutput.push_back("*");
|
||||
expectedOutput.push_back("\x1b[?25h");
|
||||
|
||||
VERIFY_SUCCEEDED(renderer.PaintFrame());
|
||||
|
||||
// Position the cursor on line 2, and fill line 1 with A's.
|
||||
sm.ProcessString(L"\x1b[2H");
|
||||
sm.ProcessString(L"\033[65;1;1;1;999$x");
|
||||
|
||||
expectedOutput.push_back("\x1b[H");
|
||||
expectedOutput.push_back("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
|
||||
|
||||
// The cursor must be explicitly moved to line 2 at the end of the frame.
|
||||
// Although that may technically already be the next output location, we
|
||||
// still need the cursor to be shown in that position when the frame ends.
|
||||
expectedOutput.push_back("\r\n");
|
||||
expectedOutput.push_back("\x1b[?25h");
|
||||
|
||||
VERIFY_SUCCEEDED(renderer.PaintFrame());
|
||||
}
|
||||
@ -30,8 +30,6 @@
|
||||
<ClCompile Include="InputBufferTests.cpp" />
|
||||
<ClCompile Include="ViewportTests.cpp" />
|
||||
<ClCompile Include="VtIoTests.cpp" />
|
||||
<ClCompile Include="VtRendererTests.cpp" />
|
||||
<ClCompile Include="ConptyOutputTests.cpp" />
|
||||
<ClCompile Include="..\precomp.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
@ -46,9 +44,6 @@
|
||||
<ProjectReference Include="..\..\renderer\atlas\atlas.vcxproj">
|
||||
<Project>{8222900C-8B6C-452A-91AC-BE95DB04B95F}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\renderer\vt\ut_lib\vt.unittest.vcxproj">
|
||||
<Project>{990F2657-8580-4828-943F-5DD657D11843}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\interactivity\base\lib\InteractivityBase.vcxproj">
|
||||
<Project>{06ec74cb-9a12-429c-b551-8562ec964846}</Project>
|
||||
</ProjectReference>
|
||||
|
||||
@ -57,9 +57,6 @@
|
||||
<ClCompile Include="VtIoTests.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="VtRendererTests.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="AliasTests.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
@ -81,9 +78,6 @@
|
||||
<ClCompile Include="ObjectTests.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ConptyOutputTests.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="UnicodeLiteral.hpp">
|
||||
|
||||
@ -17,7 +17,6 @@
|
||||
#include "../interactivity/inc/ServiceLocator.hpp"
|
||||
#include "../../inc/conattrs.hpp"
|
||||
#include "../../types/inc/Viewport.hpp"
|
||||
#include "../../renderer/vt/Xterm256Engine.hpp"
|
||||
|
||||
#include "../../inc/TestUtils.h"
|
||||
|
||||
@ -7931,11 +7930,9 @@ void ScreenBufferTests::TestReflowBiggerLongLineWithColor()
|
||||
void ScreenBufferTests::TestDeferredMainBufferResize()
|
||||
{
|
||||
BEGIN_TEST_METHOD_PROPERTIES()
|
||||
TEST_METHOD_PROPERTY(L"Data:inConpty", L"{false, true}")
|
||||
TEST_METHOD_PROPERTY(L"Data:reEnterAltBuffer", L"{false, true}")
|
||||
END_TEST_METHOD_PROPERTIES();
|
||||
|
||||
INIT_TEST_PROPERTY(bool, inConpty, L"Should we pretend to be in conpty mode?");
|
||||
INIT_TEST_PROPERTY(bool, reEnterAltBuffer, L"Should we re-enter the alt buffer when we're already in it?");
|
||||
|
||||
// A test for https://github.com/microsoft/terminal/pull/12719#discussion_r834860330
|
||||
@ -7946,31 +7943,6 @@ void ScreenBufferTests::TestDeferredMainBufferResize()
|
||||
gci.LockConsole(); // Lock must be taken to manipulate buffer.
|
||||
auto unlock = wil::scope_exit([&] { gci.UnlockConsole(); });
|
||||
|
||||
// HUGELY cribbed from ConptyRoundtripTests::MethodSetup. This fakes the
|
||||
// console into thinking that it's in ConPTY mode. Yes, we need all this
|
||||
// just to get gci.IsInVtIoMode() to return true. The screen buffer gates
|
||||
// all sorts of internal checks on that.
|
||||
//
|
||||
// This could theoretically be a helper if other tests need it.
|
||||
if (inConpty)
|
||||
{
|
||||
Log::Comment(L"Set up ConPTY");
|
||||
|
||||
auto& currentBuffer = gci.GetActiveOutputBuffer();
|
||||
// Set up an xterm-256 renderer for conpty
|
||||
wil::unique_hfile hFile = wil::unique_hfile(INVALID_HANDLE_VALUE);
|
||||
auto initialViewport = currentBuffer.GetViewport();
|
||||
auto vtRenderEngine = std::make_unique<Microsoft::Console::Render::Xterm256Engine>(std::move(hFile),
|
||||
initialViewport);
|
||||
// We don't care about the output, so let it just drain to the void.
|
||||
vtRenderEngine->SetTestCallback([](auto&&, auto&&) -> bool { return true; });
|
||||
gci.GetActiveOutputBuffer().SetTerminalConnection(vtRenderEngine.get());
|
||||
// Manually set the console into conpty mode. We're not actually going
|
||||
// to set up the pipes for conpty, but we want the console to behave
|
||||
// like it would in conpty mode.
|
||||
ServiceLocator::LocateGlobals().EnableConptyModeForTests(std::move(vtRenderEngine));
|
||||
}
|
||||
|
||||
auto* siMain = &gci.GetActiveOutputBuffer();
|
||||
auto& stateMachine = siMain->GetStateMachine();
|
||||
|
||||
|
||||
@ -2,432 +2,588 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
#include <wextestclass.h>
|
||||
#include "../../inc/consoletaeftemplates.hpp"
|
||||
#include "../../types/inc/Viewport.hpp"
|
||||
|
||||
#include "../VtIo.hpp"
|
||||
#include "../../interactivity/inc/ServiceLocator.hpp"
|
||||
#include "../../renderer/base/Renderer.hpp"
|
||||
#include "../../renderer/vt/Xterm256Engine.hpp"
|
||||
#include "../../renderer/vt/XtermEngine.hpp"
|
||||
#include "CommonState.hpp"
|
||||
|
||||
using namespace WEX::Common;
|
||||
using namespace WEX::Logging;
|
||||
using namespace WEX::TestExecution;
|
||||
|
||||
using namespace Microsoft::Console::Interactivity;
|
||||
|
||||
class Microsoft::Console::VirtualTerminal::VtIoTests
|
||||
{
|
||||
BEGIN_TEST_CLASS(VtIoTests)
|
||||
TEST_CLASS_PROPERTY(L"IsolationLevel", L"Class")
|
||||
END_TEST_CLASS()
|
||||
|
||||
// General Tests:
|
||||
TEST_METHOD(NoOpStartTest);
|
||||
|
||||
TEST_METHOD(DtorTestJustEngine);
|
||||
TEST_METHOD(DtorTestDeleteVtio);
|
||||
TEST_METHOD(DtorTestStackAlloc);
|
||||
TEST_METHOD(DtorTestStackAllocMany);
|
||||
|
||||
TEST_METHOD(RendererDtorAndThread);
|
||||
|
||||
TEST_METHOD(BasicAnonymousPipeOpeningWithSignalChannelTest);
|
||||
};
|
||||
|
||||
using namespace Microsoft::Console;
|
||||
using namespace Microsoft::Console::VirtualTerminal;
|
||||
using namespace Microsoft::Console::Render;
|
||||
using namespace Microsoft::Console::Types;
|
||||
|
||||
void VtIoTests::NoOpStartTest()
|
||||
{
|
||||
VtIo vtio;
|
||||
VERIFY_IS_FALSE(vtio.IsUsingVt());
|
||||
static constexpr WORD red = FOREGROUND_RED | BACKGROUND_GREEN;
|
||||
static constexpr WORD blu = FOREGROUND_BLUE | BACKGROUND_GREEN;
|
||||
|
||||
Log::Comment(L"Verify we succeed at StartIfNeeded even if we weren't initialized");
|
||||
VERIFY_SUCCEEDED(vtio.StartIfNeeded());
|
||||
constexpr CHAR_INFO ci_red(wchar_t ch) noexcept
|
||||
{
|
||||
return { ch, red };
|
||||
}
|
||||
|
||||
Viewport SetUpViewport()
|
||||
constexpr CHAR_INFO ci_blu(wchar_t ch) noexcept
|
||||
{
|
||||
til::inclusive_rect view;
|
||||
view.top = view.left = 0;
|
||||
view.bottom = 31;
|
||||
view.right = 79;
|
||||
|
||||
return Viewport::FromInclusive(view);
|
||||
return { ch, blu };
|
||||
}
|
||||
|
||||
void VtIoTests::DtorTestJustEngine()
|
||||
#define cup(y, x) "\x1b[" #y ";" #x "H" // CUP: Cursor Position
|
||||
#define decawm(h) "\x1b[?7" #h // DECAWM: Autowrap Mode
|
||||
#define decsc() "\x1b\x37" // DECSC: DEC Save Cursor (+ attributes)
|
||||
#define decrc() "\x1b\x38" // DECRC: DEC Restore Cursor (+ attributes)
|
||||
|
||||
// The escape sequences that ci_red() / ci_blu() result in.
|
||||
#define sgr_red(s) "\x1b[0;31;42m" s
|
||||
#define sgr_blu(s) "\x1b[0;34;42m" s
|
||||
// What the default attributes `FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED` result in.
|
||||
#define sgr_rst() "\x1b[0m"
|
||||
|
||||
static constexpr std::wstring_view s_initialContentVT{
|
||||
// clang-format off
|
||||
L""
|
||||
sgr_red("AB") sgr_blu("ab") sgr_red("CD") sgr_blu("cd") "\r\n"
|
||||
sgr_red("EF") sgr_blu("ef") sgr_red("GH") sgr_blu("gh") "\r\n"
|
||||
sgr_blu("ij") sgr_red("IJ") sgr_blu("kl") sgr_red("KL") "\r\n"
|
||||
sgr_blu("mn") sgr_red("MN") sgr_blu("op") sgr_red("OP")
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
class ::Microsoft::Console::VirtualTerminal::VtIoTests
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"This test is going to instantiate a bunch of VtIos in different \n"
|
||||
L"scenarios to see if something causes a weird cleanup.\n"
|
||||
L"It's here because of the strange nature of VtEngine having members\n"
|
||||
L"that are only defined in UNIT_TESTING"));
|
||||
BEGIN_TEST_CLASS(VtIoTests)
|
||||
TEST_CLASS_PROPERTY(L"IsolationLevel", L"Class")
|
||||
END_TEST_CLASS()
|
||||
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"New some engines and delete them"));
|
||||
for (auto i = 0; i < 25; ++i)
|
||||
CommonState commonState;
|
||||
ApiRoutines routines;
|
||||
SCREEN_INFORMATION* screenInfo = nullptr;
|
||||
wil::unique_hfile rx;
|
||||
char rxBuf[4096];
|
||||
|
||||
std::string_view readOutput() noexcept
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"New/Delete loop #%d", i));
|
||||
|
||||
wil::unique_hfile hOutputFile;
|
||||
hOutputFile.reset(INVALID_HANDLE_VALUE);
|
||||
auto pRenderer256 = new Xterm256Engine(std::move(hOutputFile), SetUpViewport());
|
||||
Log::Comment(NoThrowString().Format(L"Made Xterm256Engine"));
|
||||
delete pRenderer256;
|
||||
Log::Comment(NoThrowString().Format(L"Deleted."));
|
||||
|
||||
hOutputFile.reset(INVALID_HANDLE_VALUE);
|
||||
|
||||
auto pRenderEngineXterm = new XtermEngine(std::move(hOutputFile), SetUpViewport(), false);
|
||||
Log::Comment(NoThrowString().Format(L"Made XtermEngine"));
|
||||
delete pRenderEngineXterm;
|
||||
Log::Comment(NoThrowString().Format(L"Deleted."));
|
||||
|
||||
hOutputFile.reset(INVALID_HANDLE_VALUE);
|
||||
|
||||
auto pRenderEngineXtermAscii = new XtermEngine(std::move(hOutputFile), SetUpViewport(), true);
|
||||
Log::Comment(NoThrowString().Format(L"Made XtermEngine"));
|
||||
delete pRenderEngineXtermAscii;
|
||||
Log::Comment(NoThrowString().Format(L"Deleted."));
|
||||
}
|
||||
}
|
||||
|
||||
void VtIoTests::DtorTestDeleteVtio()
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"This test is going to instantiate a bunch of VtIos in different \n"
|
||||
L"scenarios to see if something causes a weird cleanup.\n"
|
||||
L"It's here because of the strange nature of VtEngine having members\n"
|
||||
L"that are only defined in UNIT_TESTING"));
|
||||
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"New some engines and delete them"));
|
||||
for (auto i = 0; i < 25; ++i)
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"New/Delete loop #%d", i));
|
||||
|
||||
auto hOutputFile = wil::unique_hfile(INVALID_HANDLE_VALUE);
|
||||
|
||||
hOutputFile.reset(INVALID_HANDLE_VALUE);
|
||||
|
||||
auto vtio = new VtIo();
|
||||
Log::Comment(NoThrowString().Format(L"Made VtIo"));
|
||||
vtio->_pVtRenderEngine = std::make_unique<Xterm256Engine>(std::move(hOutputFile),
|
||||
SetUpViewport());
|
||||
Log::Comment(NoThrowString().Format(L"Made Xterm256Engine"));
|
||||
delete vtio;
|
||||
Log::Comment(NoThrowString().Format(L"Deleted."));
|
||||
|
||||
hOutputFile = wil::unique_hfile(INVALID_HANDLE_VALUE);
|
||||
vtio = new VtIo();
|
||||
Log::Comment(NoThrowString().Format(L"Made VtIo"));
|
||||
vtio->_pVtRenderEngine = std::make_unique<XtermEngine>(std::move(hOutputFile),
|
||||
SetUpViewport(),
|
||||
false);
|
||||
Log::Comment(NoThrowString().Format(L"Made XtermEngine"));
|
||||
delete vtio;
|
||||
Log::Comment(NoThrowString().Format(L"Deleted."));
|
||||
|
||||
hOutputFile = wil::unique_hfile(INVALID_HANDLE_VALUE);
|
||||
vtio = new VtIo();
|
||||
Log::Comment(NoThrowString().Format(L"Made VtIo"));
|
||||
vtio->_pVtRenderEngine = std::make_unique<XtermEngine>(std::move(hOutputFile),
|
||||
SetUpViewport(),
|
||||
true);
|
||||
Log::Comment(NoThrowString().Format(L"Made XtermEngine"));
|
||||
delete vtio;
|
||||
Log::Comment(NoThrowString().Format(L"Deleted."));
|
||||
}
|
||||
}
|
||||
|
||||
void VtIoTests::DtorTestStackAlloc()
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"This test is going to instantiate a bunch of VtIos in different \n"
|
||||
L"scenarios to see if something causes a weird cleanup.\n"
|
||||
L"It's here because of the strange nature of VtEngine having members\n"
|
||||
L"that are only defined in UNIT_TESTING"));
|
||||
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"make some engines and let them fall out of scope"));
|
||||
for (auto i = 0; i < 25; ++i)
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Scope Exit Auto cleanup #%d", i));
|
||||
|
||||
wil::unique_hfile hOutputFile;
|
||||
|
||||
hOutputFile.reset(INVALID_HANDLE_VALUE);
|
||||
{
|
||||
VtIo vtio;
|
||||
vtio._pVtRenderEngine = std::make_unique<Xterm256Engine>(std::move(hOutputFile),
|
||||
SetUpViewport());
|
||||
}
|
||||
|
||||
hOutputFile.reset(INVALID_HANDLE_VALUE);
|
||||
{
|
||||
VtIo vtio;
|
||||
vtio._pVtRenderEngine = std::make_unique<XtermEngine>(std::move(hOutputFile),
|
||||
SetUpViewport(),
|
||||
false);
|
||||
}
|
||||
|
||||
hOutputFile.reset(INVALID_HANDLE_VALUE);
|
||||
{
|
||||
VtIo vtio;
|
||||
vtio._pVtRenderEngine = std::make_unique<XtermEngine>(std::move(hOutputFile),
|
||||
SetUpViewport(),
|
||||
true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VtIoTests::DtorTestStackAllocMany()
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"This test is going to instantiate a bunch of VtIos in different \n"
|
||||
L"scenarios to see if something causes a weird cleanup.\n"
|
||||
L"It's here because of the strange nature of VtEngine having members\n"
|
||||
L"that are only defined in UNIT_TESTING"));
|
||||
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Try an make a whole bunch all at once, and have them all fall out of scope at once."));
|
||||
for (auto i = 0; i < 25; ++i)
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Multiple engines, one scope loop #%d", i));
|
||||
|
||||
wil::unique_hfile hOutputFile;
|
||||
{
|
||||
hOutputFile.reset(INVALID_HANDLE_VALUE);
|
||||
VtIo vtio1;
|
||||
vtio1._pVtRenderEngine = std::make_unique<Xterm256Engine>(std::move(hOutputFile),
|
||||
SetUpViewport());
|
||||
|
||||
hOutputFile.reset(INVALID_HANDLE_VALUE);
|
||||
VtIo vtio2;
|
||||
vtio2._pVtRenderEngine = std::make_unique<XtermEngine>(std::move(hOutputFile),
|
||||
SetUpViewport(),
|
||||
false);
|
||||
|
||||
hOutputFile.reset(INVALID_HANDLE_VALUE);
|
||||
VtIo vtio3;
|
||||
vtio3._pVtRenderEngine = std::make_unique<XtermEngine>(std::move(hOutputFile),
|
||||
SetUpViewport(),
|
||||
true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MockRenderData : public IRenderData
|
||||
{
|
||||
public:
|
||||
Microsoft::Console::Types::Viewport GetViewport() noexcept override
|
||||
{
|
||||
return Microsoft::Console::Types::Viewport{};
|
||||
DWORD read = 0;
|
||||
ReadFile(rx.get(), &rxBuf[0], sizeof(rxBuf), &read, nullptr);
|
||||
return { &rxBuf[0], read };
|
||||
}
|
||||
|
||||
til::point GetTextBufferEndPosition() const noexcept override
|
||||
void setupInitialContents() const
|
||||
{
|
||||
return {};
|
||||
auto& sm = screenInfo->GetStateMachine();
|
||||
sm.ProcessString(L"\033c");
|
||||
sm.ProcessString(s_initialContentVT);
|
||||
sm.ProcessString(L"\x1b[H" sgr_rst());
|
||||
}
|
||||
|
||||
TextBuffer& GetTextBuffer() const noexcept override
|
||||
void resetContents() const
|
||||
{
|
||||
FAIL_FAST_HR(E_NOTIMPL);
|
||||
auto& sm = screenInfo->GetStateMachine();
|
||||
sm.ProcessString(L"\033c");
|
||||
}
|
||||
|
||||
const FontInfo& GetFontInfo() const noexcept override
|
||||
TEST_CLASS_SETUP(ClassSetup)
|
||||
{
|
||||
FAIL_FAST_HR(E_NOTIMPL);
|
||||
}
|
||||
wil::unique_hfile tx;
|
||||
//std::tie(tx, rx) = createOverlappedPipe(16 * 1024);
|
||||
THROW_IF_WIN32_BOOL_FALSE(CreatePipe(rx.addressof(), tx.addressof(), nullptr, 16 * 1024));
|
||||
|
||||
std::vector<Microsoft::Console::Types::Viewport> GetSelectionRects() noexcept override
|
||||
{
|
||||
return std::vector<Microsoft::Console::Types::Viewport>{};
|
||||
}
|
||||
DWORD mode = PIPE_READMODE_BYTE | PIPE_NOWAIT;
|
||||
THROW_IF_WIN32_BOOL_FALSE(SetNamedPipeHandleState(rx.get(), &mode, nullptr, nullptr));
|
||||
|
||||
void LockConsole() noexcept override
|
||||
{
|
||||
}
|
||||
commonState.PrepareGlobalInputBuffer();
|
||||
commonState.PrepareGlobalScreenBuffer(8, 4, 8, 4);
|
||||
|
||||
void UnlockConsole() noexcept override
|
||||
{
|
||||
}
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
THROW_IF_FAILED(gci.GetVtIo()->_Initialize(nullptr, tx.release(), nullptr));
|
||||
|
||||
std::pair<COLORREF, COLORREF> GetAttributeColors(const TextAttribute& /*attr*/) const noexcept override
|
||||
{
|
||||
return std::make_pair(COLORREF{}, COLORREF{});
|
||||
}
|
||||
|
||||
til::point GetCursorPosition() const noexcept override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
bool IsCursorVisible() const noexcept override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsCursorOn() const noexcept override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ULONG GetCursorHeight() const noexcept override
|
||||
{
|
||||
return 42ul;
|
||||
}
|
||||
|
||||
CursorType GetCursorStyle() const noexcept override
|
||||
{
|
||||
return CursorType::FullBox;
|
||||
}
|
||||
|
||||
ULONG GetCursorPixelWidth() const noexcept override
|
||||
{
|
||||
return 12ul;
|
||||
}
|
||||
|
||||
bool IsCursorDoubleWidth() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool IsGridLineDrawingAllowed() noexcept override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::wstring_view GetConsoleTitle() const noexcept override
|
||||
{
|
||||
return std::wstring_view{};
|
||||
}
|
||||
|
||||
const bool IsSelectionActive() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool IsBlockSelection() const noexcept override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void ClearSelection() override
|
||||
{
|
||||
}
|
||||
|
||||
void SelectNewRegion(const til::point /*coordStart*/, const til::point /*coordEnd*/) override
|
||||
{
|
||||
}
|
||||
|
||||
std::span<const til::point_span> GetSearchHighlights() const noexcept override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
const til::point_span* GetSearchHighlightFocused() const noexcept override
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const til::point GetSelectionAnchor() const noexcept
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
const til::point GetSelectionEnd() const noexcept
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
const bool IsUiaDataInitialized() const noexcept
|
||||
{
|
||||
screenInfo = &gci.GetActiveOutputBuffer();
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::wstring GetHyperlinkUri(uint16_t /*id*/) const
|
||||
TEST_METHOD(SetConsoleCursorPosition)
|
||||
{
|
||||
return {};
|
||||
THROW_IF_FAILED(routines.SetConsoleCursorPositionImpl(*screenInfo, { 2, 3 }));
|
||||
THROW_IF_FAILED(routines.SetConsoleCursorPositionImpl(*screenInfo, { 0, 0 }));
|
||||
THROW_IF_FAILED(routines.SetConsoleCursorPositionImpl(*screenInfo, { 7, 3 }));
|
||||
THROW_IF_FAILED(routines.SetConsoleCursorPositionImpl(*screenInfo, { 3, 2 }));
|
||||
|
||||
const auto expected = cup(4, 3) cup(1, 1) cup(4, 8) cup(3, 4);
|
||||
const auto actual = readOutput();
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
}
|
||||
|
||||
const std::wstring GetHyperlinkCustomId(uint16_t /*id*/) const
|
||||
TEST_METHOD(SetConsoleOutputMode)
|
||||
{
|
||||
return {};
|
||||
const auto initialMode = screenInfo->OutputMode;
|
||||
const auto cleanup = wil::scope_exit([=]() {
|
||||
screenInfo->OutputMode = initialMode;
|
||||
});
|
||||
|
||||
screenInfo->OutputMode = 0;
|
||||
|
||||
THROW_IF_FAILED(routines.SetConsoleOutputModeImpl(*screenInfo, ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN | ENABLE_LVB_GRID_WORLDWIDE)); // DECAWM ✔️
|
||||
THROW_IF_FAILED(routines.SetConsoleOutputModeImpl(*screenInfo, ENABLE_PROCESSED_OUTPUT | DISABLE_NEWLINE_AUTO_RETURN | ENABLE_LVB_GRID_WORLDWIDE)); // DECAWM ✖️
|
||||
THROW_IF_FAILED(routines.SetConsoleOutputModeImpl(*screenInfo, 0)); // DECAWM ✖️
|
||||
THROW_IF_FAILED(routines.SetConsoleOutputModeImpl(*screenInfo, ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | DISABLE_NEWLINE_AUTO_RETURN | ENABLE_LVB_GRID_WORLDWIDE)); // DECAWM ✔️
|
||||
|
||||
const auto expected =
|
||||
decawm(h) // DECAWM ✔️
|
||||
decawm(l) // DECAWM ✖️
|
||||
decawm(h); // DECAWM ✔️
|
||||
const auto actual = readOutput();
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
}
|
||||
|
||||
const std::vector<size_t> GetPatternId(const til::point /*location*/) const
|
||||
TEST_METHOD(SetConsoleTitleW)
|
||||
{
|
||||
return {};
|
||||
std::string_view expected;
|
||||
std::string_view actual;
|
||||
|
||||
THROW_IF_FAILED(routines.SetConsoleTitleWImpl(
|
||||
L"foobar"));
|
||||
expected = "\x1b]0;foobar\a";
|
||||
actual = readOutput();
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
|
||||
THROW_IF_FAILED(routines.SetConsoleTitleWImpl(
|
||||
L"foo"
|
||||
"\u0001\u001f"
|
||||
"bar"));
|
||||
expected = "\x1b]0;foo☺▼bar\a";
|
||||
actual = readOutput();
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
|
||||
THROW_IF_FAILED(routines.SetConsoleTitleWImpl(
|
||||
L"foo"
|
||||
"\u0001\u001f"
|
||||
"bar"
|
||||
"\u007f\u009f"));
|
||||
expected = "\x1b]0;foo☺▼bar⌂?\a";
|
||||
actual = readOutput();
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
}
|
||||
|
||||
TEST_METHOD(SetConsoleCursorInfo)
|
||||
{
|
||||
THROW_IF_FAILED(routines.SetConsoleCursorInfoImpl(*screenInfo, 25, false));
|
||||
THROW_IF_FAILED(routines.SetConsoleCursorInfoImpl(*screenInfo, 25, true));
|
||||
|
||||
const auto expected = "\x1b[?25l"
|
||||
"\x1b[?25h";
|
||||
const auto actual = readOutput();
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
}
|
||||
|
||||
TEST_METHOD(SetConsoleTextAttribute)
|
||||
{
|
||||
for (WORD i = 0; i < 16; i++)
|
||||
{
|
||||
THROW_IF_FAILED(routines.SetConsoleTextAttributeImpl(*screenInfo, i | BACKGROUND_RED));
|
||||
}
|
||||
|
||||
for (WORD i = 0; i < 16; i++)
|
||||
{
|
||||
THROW_IF_FAILED(routines.SetConsoleTextAttributeImpl(*screenInfo, i << 4 | FOREGROUND_RED));
|
||||
}
|
||||
|
||||
THROW_IF_FAILED(routines.SetConsoleTextAttributeImpl(*screenInfo, FOREGROUND_BLUE | FOREGROUND_RED | FOREGROUND_INTENSITY | BACKGROUND_GREEN | COMMON_LVB_REVERSE_VIDEO));
|
||||
THROW_IF_FAILED(routines.SetConsoleTextAttributeImpl(*screenInfo, FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | COMMON_LVB_REVERSE_VIDEO));
|
||||
|
||||
const auto expected =
|
||||
// 16 foreground colors
|
||||
"\x1b[0;30;41m"
|
||||
"\x1b[0;34;41m"
|
||||
"\x1b[0;32;41m"
|
||||
"\x1b[0;36;41m"
|
||||
"\x1b[0;31;41m"
|
||||
"\x1b[0;35;41m"
|
||||
"\x1b[0;33;41m"
|
||||
"\x1b[0;41m" // <-- default foreground (FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED)
|
||||
"\x1b[0;90;41m"
|
||||
"\x1b[0;94;41m"
|
||||
"\x1b[0;92;41m"
|
||||
"\x1b[0;96;41m"
|
||||
"\x1b[0;91;41m"
|
||||
"\x1b[0;95;41m"
|
||||
"\x1b[0;93;41m"
|
||||
"\x1b[0;97;41m"
|
||||
// 16 background colors
|
||||
"\x1b[0;31m" // <-- default background (0)
|
||||
"\x1b[0;31;44m"
|
||||
"\x1b[0;31;42m"
|
||||
"\x1b[0;31;46m"
|
||||
"\x1b[0;31;41m"
|
||||
"\x1b[0;31;45m"
|
||||
"\x1b[0;31;43m"
|
||||
"\x1b[0;31;47m"
|
||||
"\x1b[0;31;100m"
|
||||
"\x1b[0;31;104m"
|
||||
"\x1b[0;31;102m"
|
||||
"\x1b[0;31;106m"
|
||||
"\x1b[0;31;101m"
|
||||
"\x1b[0;31;105m"
|
||||
"\x1b[0;31;103m"
|
||||
"\x1b[0;31;107m"
|
||||
// The remaining two calls
|
||||
"\x1b[0;7;95;42m"
|
||||
"\x1b[0;7m";
|
||||
const auto actual = readOutput();
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
}
|
||||
|
||||
TEST_METHOD(WriteConsoleW)
|
||||
{
|
||||
resetContents();
|
||||
|
||||
size_t written;
|
||||
std::unique_ptr<IWaitRoutine> waiter;
|
||||
std::string_view expected;
|
||||
std::string_view actual;
|
||||
|
||||
THROW_IF_FAILED(routines.WriteConsoleWImpl(*screenInfo, L"", written, false, waiter));
|
||||
expected = "";
|
||||
actual = readOutput();
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
|
||||
// Force-wrap because we write up to the last column.
|
||||
THROW_IF_FAILED(routines.WriteConsoleWImpl(*screenInfo, L"aaaaaaaa", written, false, waiter));
|
||||
expected = "aaaaaaaa\r\n";
|
||||
actual = readOutput();
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
|
||||
// Force-wrap because we write up to the last column, but this time with a tab.
|
||||
THROW_IF_FAILED(routines.WriteConsoleWImpl(*screenInfo, L"a\t\r\nb", written, false, waiter));
|
||||
expected = "a\t\r\n\r\nb";
|
||||
actual = readOutput();
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
}
|
||||
|
||||
TEST_METHOD(WriteConsoleOutputW)
|
||||
{
|
||||
resetContents();
|
||||
|
||||
std::array payload{ ci_red('a'), ci_red('b'), ci_blu('A'), ci_blu('B') };
|
||||
const auto target = Viewport::FromDimensions({ 1, 1 }, { 4, 1 });
|
||||
Viewport written;
|
||||
THROW_IF_FAILED(routines.WriteConsoleOutputWImpl(*screenInfo, payload, target, written));
|
||||
|
||||
const auto expected = decsc() cup(2, 2) sgr_red("ab") sgr_blu("AB") decrc();
|
||||
const auto actual = readOutput();
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
}
|
||||
|
||||
TEST_METHOD(WriteConsoleOutputAttribute)
|
||||
{
|
||||
setupInitialContents();
|
||||
|
||||
static constexpr std::array payload{ red, blu, red, blu };
|
||||
static constexpr til::point target{ 6, 1 };
|
||||
size_t written;
|
||||
THROW_IF_FAILED(routines.WriteConsoleOutputAttributeImpl(*screenInfo, payload, target, written));
|
||||
|
||||
const auto expected =
|
||||
decsc() //
|
||||
cup(2, 7) sgr_red("g") sgr_blu("h") //
|
||||
cup(3, 1) sgr_red("i") sgr_blu("j") //
|
||||
decrc();
|
||||
const auto actual = readOutput();
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
}
|
||||
|
||||
TEST_METHOD(WriteConsoleOutputCharacterW)
|
||||
{
|
||||
setupInitialContents();
|
||||
|
||||
size_t written = 0;
|
||||
std::string_view expected;
|
||||
std::string_view actual;
|
||||
|
||||
THROW_IF_FAILED(routines.WriteConsoleOutputCharacterWImpl(*screenInfo, L"foobar", { 5, 1 }, written));
|
||||
expected =
|
||||
decsc() //
|
||||
cup(2, 6) sgr_red("f") sgr_blu("oo") //
|
||||
cup(3, 1) sgr_blu("ba") sgr_red("r") //
|
||||
decrc();
|
||||
actual = readOutput();
|
||||
VERIFY_ARE_EQUAL(6u, written);
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
|
||||
// Writing past the end of the buffer.
|
||||
THROW_IF_FAILED(routines.WriteConsoleOutputCharacterWImpl(*screenInfo, L"foobar", { 5, 3 }, written));
|
||||
expected =
|
||||
decsc() //
|
||||
cup(4, 6) sgr_blu("f") sgr_red("oo") //
|
||||
decrc();
|
||||
actual = readOutput();
|
||||
VERIFY_ARE_EQUAL(3u, written);
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
|
||||
// Writing 3 wide chars while intersecting the last column.
|
||||
THROW_IF_FAILED(routines.WriteConsoleOutputCharacterWImpl(*screenInfo, L"✨✅❌", { 5, 1 }, written));
|
||||
expected =
|
||||
decsc() //
|
||||
cup(2, 6) sgr_red("✨") sgr_blu(" ") //
|
||||
cup(3, 1) sgr_blu("✅") sgr_red("❌") //
|
||||
decrc();
|
||||
actual = readOutput();
|
||||
VERIFY_ARE_EQUAL(3u, written);
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
}
|
||||
|
||||
TEST_METHOD(FillConsoleOutputAttribute)
|
||||
{
|
||||
setupInitialContents();
|
||||
|
||||
size_t cellsModified = 0;
|
||||
std::string_view expected;
|
||||
std::string_view actual;
|
||||
|
||||
// Writing nothing should produce nothing.
|
||||
THROW_IF_FAILED(routines.FillConsoleOutputAttributeImpl(*screenInfo, red, 0, {}, cellsModified));
|
||||
expected = "";
|
||||
actual = readOutput();
|
||||
VERIFY_ARE_EQUAL(0u, cellsModified);
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
|
||||
// Writing at the start of a line.
|
||||
THROW_IF_FAILED(routines.FillConsoleOutputAttributeImpl(*screenInfo, red, 3, { 0, 0 }, cellsModified));
|
||||
expected =
|
||||
decsc() //
|
||||
cup(1, 1) sgr_red("ABa") //
|
||||
decrc();
|
||||
actual = readOutput();
|
||||
VERIFY_ARE_EQUAL(3u, cellsModified);
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
|
||||
// Writing at the end of a line.
|
||||
THROW_IF_FAILED(routines.FillConsoleOutputAttributeImpl(*screenInfo, red, 3, { 5, 0 }, cellsModified));
|
||||
expected =
|
||||
decsc() //
|
||||
cup(1, 6) sgr_red("Dcd") //
|
||||
decrc();
|
||||
actual = readOutput();
|
||||
VERIFY_ARE_EQUAL(3u, cellsModified);
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
|
||||
// Writing across 2 lines.
|
||||
THROW_IF_FAILED(routines.FillConsoleOutputAttributeImpl(*screenInfo, blu, 8, { 4, 1 }, cellsModified));
|
||||
expected =
|
||||
decsc() //
|
||||
cup(2, 5) sgr_blu("GHgh") //
|
||||
cup(3, 1) sgr_blu("ijIJ") //
|
||||
decrc();
|
||||
actual = readOutput();
|
||||
VERIFY_ARE_EQUAL(8u, cellsModified);
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
}
|
||||
|
||||
TEST_METHOD(FillConsoleOutputCharacterW)
|
||||
{
|
||||
setupInitialContents();
|
||||
|
||||
size_t cellsModified = 0;
|
||||
std::string_view expected;
|
||||
std::string_view actual;
|
||||
|
||||
// Writing nothing should produce nothing.
|
||||
THROW_IF_FAILED(routines.FillConsoleOutputCharacterWImpl(*screenInfo, L'a', 0, {}, cellsModified));
|
||||
expected = "";
|
||||
actual = readOutput();
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
|
||||
// Writing at the start of a line.
|
||||
THROW_IF_FAILED(routines.FillConsoleOutputCharacterWImpl(*screenInfo, L'a', 3, { 0, 0 }, cellsModified));
|
||||
expected =
|
||||
decsc() //
|
||||
cup(1, 1) sgr_red("aa") sgr_blu("a") //
|
||||
decrc();
|
||||
actual = readOutput();
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
|
||||
// Writing at the end of a line.
|
||||
THROW_IF_FAILED(routines.FillConsoleOutputCharacterWImpl(*screenInfo, L'b', 3, { 5, 0 }, cellsModified));
|
||||
expected =
|
||||
decsc() //
|
||||
cup(1, 6) sgr_red("b") sgr_blu("bb") //
|
||||
decrc();
|
||||
actual = readOutput();
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
|
||||
// Writing across 2 lines.
|
||||
THROW_IF_FAILED(routines.FillConsoleOutputCharacterWImpl(*screenInfo, L'c', 8, { 4, 1 }, cellsModified));
|
||||
expected =
|
||||
decsc() //
|
||||
cup(2, 5) sgr_red("cc") sgr_blu("cc") //
|
||||
cup(3, 1) sgr_blu("cc") sgr_red("cc") //
|
||||
decrc();
|
||||
actual = readOutput();
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
|
||||
// Writing 3 wide chars while intersecting the last column.
|
||||
THROW_IF_FAILED(routines.FillConsoleOutputCharacterWImpl(*screenInfo, L'✨', 3, { 5, 1 }, cellsModified));
|
||||
expected =
|
||||
decsc() //
|
||||
cup(2, 6) sgr_red("✨") sgr_blu(" ") //
|
||||
cup(3, 1) sgr_blu("✨") sgr_red("✨") //
|
||||
decrc();
|
||||
actual = readOutput();
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
}
|
||||
|
||||
TEST_METHOD(ScrollConsoleScreenBufferW)
|
||||
{
|
||||
std::string_view expected;
|
||||
std::string_view actual;
|
||||
|
||||
setupInitialContents();
|
||||
|
||||
// Scrolling from nowhere to somewhere are no-ops and should not emit anything.
|
||||
THROW_IF_FAILED(routines.ScrollConsoleScreenBufferWImpl(*screenInfo, { 0, 0, -1, -1 }, {}, std::nullopt, L' ', 0, false));
|
||||
THROW_IF_FAILED(routines.ScrollConsoleScreenBufferWImpl(*screenInfo, { -10, -10, -9, -9 }, {}, std::nullopt, L' ', 0, false));
|
||||
expected = "";
|
||||
actual = readOutput();
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
|
||||
// Scrolling from somewhere to nowhere should clear the area.
|
||||
THROW_IF_FAILED(routines.ScrollConsoleScreenBufferWImpl(*screenInfo, { 0, 0, 1, 1 }, { 10, 10 }, std::nullopt, L' ', red, false));
|
||||
expected =
|
||||
decsc() //
|
||||
cup(1, 1) sgr_red(" ") //
|
||||
cup(2, 1) sgr_red(" ") //
|
||||
decrc();
|
||||
actual = readOutput();
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
|
||||
// cmd uses ScrollConsoleScreenBuffer to clear the buffer contents and that gets translated to a clear screen sequence.
|
||||
THROW_IF_FAILED(routines.ScrollConsoleScreenBufferWImpl(*screenInfo, { 0, 0, 7, 3 }, { 0, -4 }, std::nullopt, 0, 0, true));
|
||||
expected = "\x1b[H\x1b[2J\x1b[3J";
|
||||
actual = readOutput();
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
|
||||
//
|
||||
// A B a b C D c d
|
||||
//
|
||||
// E F e f G H g h
|
||||
//
|
||||
// i j I J k l K L
|
||||
//
|
||||
// m n M N o p O P
|
||||
//
|
||||
setupInitialContents();
|
||||
|
||||
// Scrolling from somewhere to somewhere.
|
||||
//
|
||||
// +-------+
|
||||
// A | Z Z | b C D c d
|
||||
// | src |
|
||||
// E | Z Z | f G H g h
|
||||
// +-------+ +-------+
|
||||
// i j I J k | B a | L
|
||||
// | dst |
|
||||
// m n M N o | F e | P
|
||||
// +-------+
|
||||
THROW_IF_FAILED(routines.ScrollConsoleScreenBufferWImpl(*screenInfo, { 1, 0, 2, 1 }, { 5, 2 }, std::nullopt, L'Z', red, false));
|
||||
expected =
|
||||
decsc() //
|
||||
cup(1, 2) sgr_red("ZZ") //
|
||||
cup(2, 2) sgr_red("ZZ") //
|
||||
cup(3, 6) sgr_red("B") sgr_blu("a") //
|
||||
cup(4, 6) sgr_red("F") sgr_blu("e") //
|
||||
decrc();
|
||||
actual = readOutput();
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
|
||||
// Same, but with a partially out-of-bounds target and clip rect. Clip rects affect both
|
||||
// the source area that gets filled and the target area that gets a copy of the source contents.
|
||||
//
|
||||
// A Z Z b C D c d
|
||||
// +---+~~~~~~~~~~~~~~~~~~~~~~~+
|
||||
// | E $ z z | f G H g $ h
|
||||
// | $ src | +---$-------+
|
||||
// | i $ z z | J k B | E $ L |
|
||||
// +---$-------+ | $ dst |
|
||||
// m $ n M N o F | i $ P |
|
||||
// +~~~~~~~~~~~~~~~~~~~~~~~+-------+
|
||||
// clip rect
|
||||
THROW_IF_FAILED(routines.ScrollConsoleScreenBufferWImpl(*screenInfo, { 0, 1, 2, 2 }, { 6, 2 }, til::inclusive_rect{ 1, 1, 6, 3 }, L'z', blu, false));
|
||||
expected =
|
||||
decsc() //
|
||||
cup(2, 2) sgr_blu("zz") //
|
||||
cup(3, 2) sgr_blu("zz") //
|
||||
cup(3, 7) sgr_red("E") //
|
||||
cup(4, 7) sgr_blu("i") //
|
||||
decrc();
|
||||
actual = readOutput();
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
|
||||
// Same, but with a partially out-of-bounds source.
|
||||
// The boundaries of the buffer act as a clip rect for reading and so only 2 cells get copied.
|
||||
//
|
||||
// +-------+
|
||||
// A Z Z b C D c | Y |
|
||||
// | src |
|
||||
// E z z f G H g | Y |
|
||||
// +---+ +-------+
|
||||
// i z z J | d | B E L
|
||||
// |dst|
|
||||
// m n M N | h | F i P
|
||||
// +---+
|
||||
THROW_IF_FAILED(routines.ScrollConsoleScreenBufferWImpl(*screenInfo, { 7, 0, 8, 1 }, { 4, 2 }, std::nullopt, L'Y', red, false));
|
||||
expected =
|
||||
decsc() //
|
||||
cup(1, 8) sgr_red("Y") //
|
||||
cup(2, 8) sgr_red("Y") //
|
||||
cup(3, 5) sgr_blu("d") //
|
||||
cup(4, 5) sgr_blu("h") //
|
||||
decrc();
|
||||
actual = readOutput();
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
|
||||
static constexpr std::array<CHAR_INFO, 8 * 4> expectedContents{ {
|
||||
// clang-format off
|
||||
ci_red('A'), ci_red('Z'), ci_red('Z'), ci_blu('b'), ci_red('C'), ci_red('D'), ci_blu('c'), ci_red('Y'),
|
||||
ci_red('E'), ci_blu('z'), ci_blu('z'), ci_blu('f'), ci_red('G'), ci_red('H'), ci_blu('g'), ci_red('Y'),
|
||||
ci_blu('i'), ci_blu('z'), ci_blu('z'), ci_red('J'), ci_blu('d'), ci_red('B'), ci_red('E'), ci_red('L'),
|
||||
ci_blu('m'), ci_blu('n'), ci_red('M'), ci_red('N'), ci_blu('h'), ci_red('F'), ci_blu('i'), ci_red('P'),
|
||||
// clang-format on
|
||||
} };
|
||||
std::array<CHAR_INFO, 8 * 4> actualContents{};
|
||||
Viewport actualContentsRead;
|
||||
THROW_IF_FAILED(routines.ReadConsoleOutputWImpl(*screenInfo, actualContents, Viewport::FromDimensions({}, { 8, 4 }), actualContentsRead));
|
||||
VERIFY_IS_TRUE(memcmp(expectedContents.data(), actualContents.data(), sizeof(actualContents)) == 0);
|
||||
}
|
||||
|
||||
TEST_METHOD(SetConsoleActiveScreenBuffer)
|
||||
{
|
||||
SCREEN_INFORMATION* screenInfoAlt;
|
||||
|
||||
VERIFY_NT_SUCCESS(SCREEN_INFORMATION::CreateInstance(
|
||||
screenInfo->GetViewport().Dimensions(),
|
||||
screenInfo->GetCurrentFont(),
|
||||
screenInfo->GetBufferSize().Dimensions(),
|
||||
screenInfo->GetAttributes(),
|
||||
screenInfo->GetPopupAttributes(),
|
||||
screenInfo->GetTextBuffer().GetCursor().GetSize(),
|
||||
&screenInfoAlt));
|
||||
|
||||
routines.SetConsoleActiveScreenBufferImpl(*screenInfoAlt);
|
||||
setupInitialContents();
|
||||
THROW_IF_FAILED(routines.SetConsoleOutputModeImpl(*screenInfoAlt, ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING));
|
||||
readOutput();
|
||||
|
||||
routines.SetConsoleActiveScreenBufferImpl(*screenInfo);
|
||||
|
||||
const auto expected =
|
||||
"\x1b[?1049l" // ASB (Alternate Screen Buffer)
|
||||
cup(1, 1) sgr_red("AB") sgr_blu("ab") sgr_red("CD") sgr_blu("cd") //
|
||||
cup(2, 1) sgr_red("EF") sgr_blu("ef") sgr_red("GH") sgr_blu("gh") //
|
||||
cup(3, 1) sgr_blu("ij") sgr_red("IJ") sgr_blu("kl") sgr_red("KL") //
|
||||
cup(4, 1) sgr_blu("mn") sgr_red("MN") sgr_blu("op") sgr_red("OP") //
|
||||
cup(1, 1) sgr_rst() //
|
||||
"\x1b[?25h" // DECTCEM (Text Cursor Enable)
|
||||
"\x1b[?7h"; // DECAWM (Autowrap Mode)
|
||||
const auto actual = readOutput();
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
}
|
||||
};
|
||||
|
||||
void VtIoTests::RendererDtorAndThread()
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Test deleting a Renderer a bunch of times"));
|
||||
|
||||
for (auto i = 0; i < 16; ++i)
|
||||
{
|
||||
auto data = std::make_unique<MockRenderData>();
|
||||
auto thread = std::make_unique<Microsoft::Console::Render::RenderThread>();
|
||||
auto* pThread = thread.get();
|
||||
auto pRenderer = std::make_unique<Microsoft::Console::Render::Renderer>(RenderSettings{}, data.get(), nullptr, 0, std::move(thread));
|
||||
VERIFY_SUCCEEDED(pThread->Initialize(pRenderer.get()));
|
||||
// Sleep for a hot sec to make sure the thread starts before we enable painting
|
||||
// If you don't, the thread might wait on the paint enabled event AFTER
|
||||
// EnablePainting gets called, and if that happens, then the thread will
|
||||
// never get destructed. This will only ever happen in the vstest test runner,
|
||||
// which is what CI uses.
|
||||
/*Sleep(500);*/
|
||||
|
||||
pThread->EnablePainting();
|
||||
pRenderer->TriggerTeardown();
|
||||
pRenderer.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void VtIoTests::BasicAnonymousPipeOpeningWithSignalChannelTest()
|
||||
{
|
||||
Log::Comment(L"Test using anonymous pipes for the input and adding a signal channel.");
|
||||
|
||||
Log::Comment(L"\tcreating pipes");
|
||||
|
||||
wil::unique_handle inPipeReadSide;
|
||||
wil::unique_handle inPipeWriteSide;
|
||||
wil::unique_handle outPipeReadSide;
|
||||
wil::unique_handle outPipeWriteSide;
|
||||
wil::unique_handle signalPipeReadSide;
|
||||
wil::unique_handle signalPipeWriteSide;
|
||||
|
||||
VERIFY_WIN32_BOOL_SUCCEEDED(CreatePipe(&inPipeReadSide, &inPipeWriteSide, nullptr, 0), L"Create anonymous in pipe.");
|
||||
VERIFY_WIN32_BOOL_SUCCEEDED(CreatePipe(&outPipeReadSide, &outPipeWriteSide, nullptr, 0), L"Create anonymous out pipe.");
|
||||
VERIFY_WIN32_BOOL_SUCCEEDED(CreatePipe(&signalPipeReadSide, &signalPipeWriteSide, nullptr, 0), L"Create anonymous signal pipe.");
|
||||
|
||||
Log::Comment(L"\tinitializing vtio");
|
||||
|
||||
// CreateIoHandlers() assert()s on IsConsoleLocked() to guard against a race condition.
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
gci.LockConsole();
|
||||
const auto cleanup = wil::scope_exit([&]() {
|
||||
gci.UnlockConsole();
|
||||
});
|
||||
|
||||
VtIo vtio;
|
||||
VERIFY_IS_FALSE(vtio.IsUsingVt());
|
||||
VERIFY_ARE_EQUAL(nullptr, vtio._pPtySignalInputThread);
|
||||
VERIFY_SUCCEEDED(vtio._Initialize(inPipeReadSide.release(), outPipeWriteSide.release(), signalPipeReadSide.release()));
|
||||
VERIFY_SUCCEEDED(vtio.CreateAndStartSignalThread());
|
||||
VERIFY_SUCCEEDED(vtio.CreateIoHandlers());
|
||||
VERIFY_IS_TRUE(vtio.IsUsingVt());
|
||||
VERIFY_ARE_NOT_EQUAL(nullptr, vtio._pPtySignalInputThread);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -31,8 +31,6 @@ SOURCES = \
|
||||
TitleTests.cpp \
|
||||
InputBufferTests.cpp \
|
||||
VtIoTests.cpp \
|
||||
VtRendererTests.cpp \
|
||||
ConptyOutputTests.cpp \
|
||||
ViewportTests.cpp \
|
||||
ConsoleArgumentsTests.cpp \
|
||||
ObjectTests.cpp \
|
||||
|
||||
@ -82,16 +82,6 @@
|
||||
#define ENABLE_INTSAFE_SIGNED_FUNCTIONS
|
||||
#include <intsafe.h>
|
||||
|
||||
// LibPopCnt - Fast C/C++ bit population count library (on bits in an array)
|
||||
#include <libpopcnt.h>
|
||||
|
||||
// Dynamic Bitset (optional dependency on LibPopCnt for perf at bit counting)
|
||||
// Variable-size compressed-storage header-only bit flag storage library.
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable:4702) // unreachable code
|
||||
#include <dynamic_bitset.hpp>
|
||||
#pragma warning(pop)
|
||||
|
||||
// {fmt}, a C++20-compatible formatting library
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/compile.h>
|
||||
|
||||
@ -27,9 +27,6 @@
|
||||
#ifndef PSEUDOCONSOLE_INHERIT_CURSOR
|
||||
#define PSEUDOCONSOLE_INHERIT_CURSOR (0x1)
|
||||
#endif
|
||||
#ifndef PSEUDOCONSOLE_RESIZE_QUIRK
|
||||
#define PSEUDOCONSOLE_RESIZE_QUIRK (0x2)
|
||||
#endif
|
||||
#ifndef PSEUDOCONSOLE_GLYPH_WIDTH__MASK
|
||||
#define PSEUDOCONSOLE_GLYPH_WIDTH__MASK 0x18
|
||||
#define PSEUDOCONSOLE_GLYPH_WIDTH_GRAPHEMES 0x08
|
||||
|
||||
@ -15,11 +15,11 @@
|
||||
#define _TIL_INLINEPREFIX __declspec(noinline) inline
|
||||
|
||||
#include "til/at.h"
|
||||
#include "til/bitmap.h"
|
||||
#include "til/coalesce.h"
|
||||
#include "til/color.h"
|
||||
#include "til/enumset.h"
|
||||
#include "til/pmr.h"
|
||||
#include "til/rect.h"
|
||||
#include "til/string.h"
|
||||
#include "til/type_traits.h"
|
||||
#include "til/u8u16convert.h"
|
||||
|
||||
@ -1,593 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rect.h"
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
class BitmapTests;
|
||||
#endif
|
||||
|
||||
namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
||||
{
|
||||
namespace details
|
||||
{
|
||||
template<typename Allocator>
|
||||
class _bitmap_const_iterator
|
||||
{
|
||||
public:
|
||||
using iterator_category = std::input_iterator_tag;
|
||||
using value_type = const til::rect;
|
||||
using difference_type = ptrdiff_t;
|
||||
using pointer = const til::rect*;
|
||||
using reference = const til::rect&;
|
||||
|
||||
_bitmap_const_iterator(const dynamic_bitset<size_t, Allocator>& values, til::rect rc, ptrdiff_t pos) :
|
||||
_values(values),
|
||||
_rc(rc),
|
||||
_pos(pos),
|
||||
_end(rc.size().area())
|
||||
{
|
||||
_calculateArea();
|
||||
}
|
||||
|
||||
_bitmap_const_iterator& operator++()
|
||||
{
|
||||
_pos = _nextPos;
|
||||
_calculateArea();
|
||||
return (*this);
|
||||
}
|
||||
|
||||
_bitmap_const_iterator operator++(int)
|
||||
{
|
||||
const auto prev = *this;
|
||||
++*this;
|
||||
return prev;
|
||||
}
|
||||
|
||||
constexpr bool operator==(const _bitmap_const_iterator& other) const noexcept
|
||||
{
|
||||
return _pos == other._pos && _values == other._values;
|
||||
}
|
||||
|
||||
constexpr bool operator!=(const _bitmap_const_iterator& other) const noexcept
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
constexpr bool operator<(const _bitmap_const_iterator& other) const noexcept
|
||||
{
|
||||
return _pos < other._pos;
|
||||
}
|
||||
|
||||
constexpr bool operator>(const _bitmap_const_iterator& other) const noexcept
|
||||
{
|
||||
return _pos > other._pos;
|
||||
}
|
||||
|
||||
constexpr reference operator*() const noexcept
|
||||
{
|
||||
return _run;
|
||||
}
|
||||
|
||||
constexpr pointer operator->() const noexcept
|
||||
{
|
||||
return &_run;
|
||||
}
|
||||
|
||||
private:
|
||||
const dynamic_bitset<size_t, Allocator>& _values;
|
||||
const til::rect _rc;
|
||||
size_t _pos;
|
||||
size_t _nextPos;
|
||||
const size_t _end;
|
||||
til::rect _run;
|
||||
|
||||
// Update _run to contain the next rectangle of consecutively set bits within this bitmap.
|
||||
// _calculateArea may be called repeatedly to yield all those rectangles.
|
||||
void _calculateArea()
|
||||
{
|
||||
// The following logic first finds the next set bit in this bitmap and the next unset bit past that.
|
||||
// The area in between those positions are thus all set bits and will end up being the next _run.
|
||||
|
||||
// dynamic_bitset allows you to quickly find the next set bit using find_next(prev),
|
||||
// where "prev" is the position _past_ which should be searched (i.e. excluding position "prev").
|
||||
// If _pos is still 0, we thus need to use the counterpart find_first().
|
||||
_nextPos = _pos == 0 ? _values.find_first() : _values.find_next(_pos - 1);
|
||||
|
||||
// If we haven't reached the end yet...
|
||||
if (_nextPos < _end)
|
||||
{
|
||||
// pos is now at the first on bit.
|
||||
// If no next set bit can be found, npos is returned, which is SIZE_T_MAX.
|
||||
// saturated_cast can ensure that this will be converted to CoordType's max (which is greater than _end).
|
||||
const auto runStart = _rc.point_at(base::saturated_cast<CoordType>(_nextPos));
|
||||
|
||||
// We'll only count up until the end of this row.
|
||||
// a run can be a max of one row tall.
|
||||
const size_t rowEndIndex = _rc.index_of<size_t>(til::point(_rc.right - 1, runStart.y)) + 1;
|
||||
|
||||
// Find the length for the rectangle.
|
||||
size_t runLength = 0;
|
||||
|
||||
// We have at least 1 so start with a do/while.
|
||||
do
|
||||
{
|
||||
++_nextPos;
|
||||
++runLength;
|
||||
} while (_nextPos < rowEndIndex && _values[_nextPos]);
|
||||
// Keep going until we reach end of row, end of the buffer, or the next bit is off.
|
||||
|
||||
// Assemble and store that run.
|
||||
_run = til::rect{ runStart, til::size{ base::saturated_cast<CoordType>(runLength), 1 } };
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we reached the end _nextPos may be >= _end (potentially even PTRDIFF_T_MAX).
|
||||
// ---> Mark the end of the iterator by updating the state with _end.
|
||||
_pos = _end;
|
||||
_nextPos = _end;
|
||||
_run = til::rect{};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template<typename Allocator = std::allocator<size_t>>
|
||||
class bitmap
|
||||
{
|
||||
public:
|
||||
using allocator_type = Allocator;
|
||||
using const_iterator = details::_bitmap_const_iterator<allocator_type>;
|
||||
|
||||
private:
|
||||
using run_allocator_type = typename std::allocator_traits<allocator_type>::template rebind_alloc<til::rect>;
|
||||
|
||||
public:
|
||||
explicit bitmap(const allocator_type& allocator) noexcept :
|
||||
_alloc{ allocator },
|
||||
_sz{},
|
||||
_rc{},
|
||||
_bits{ _alloc },
|
||||
_runs{ _alloc }
|
||||
{
|
||||
}
|
||||
|
||||
bitmap() noexcept :
|
||||
bitmap(allocator_type{})
|
||||
{
|
||||
}
|
||||
|
||||
bitmap(til::size sz) :
|
||||
bitmap(sz, false, allocator_type{})
|
||||
{
|
||||
}
|
||||
|
||||
bitmap(til::size sz, const allocator_type& allocator) :
|
||||
bitmap(sz, false, allocator)
|
||||
{
|
||||
}
|
||||
|
||||
bitmap(til::size sz, bool fill, const allocator_type& allocator) :
|
||||
_alloc{ allocator },
|
||||
_sz(sz),
|
||||
_rc(sz),
|
||||
_bits(_sz.area(), fill ? std::numeric_limits<unsigned long long>::max() : 0, _alloc),
|
||||
_runs{ _alloc }
|
||||
{
|
||||
}
|
||||
|
||||
bitmap(til::size sz, bool fill) :
|
||||
bitmap(sz, fill, allocator_type{})
|
||||
{
|
||||
}
|
||||
|
||||
bitmap(const bitmap& other) :
|
||||
_alloc{ std::allocator_traits<allocator_type>::select_on_container_copy_construction(other._alloc) },
|
||||
_sz{ other._sz },
|
||||
_rc{ other._rc },
|
||||
_bits{ other._bits },
|
||||
_runs{ other._runs }
|
||||
{
|
||||
// copy constructor is required to call select_on_container_copy
|
||||
}
|
||||
|
||||
bitmap& operator=(const bitmap& other)
|
||||
{
|
||||
if constexpr (std::allocator_traits<allocator_type>::propagate_on_container_copy_assignment::value)
|
||||
{
|
||||
_alloc = other._alloc;
|
||||
}
|
||||
_sz = other._sz;
|
||||
_rc = other._rc;
|
||||
_bits = other._bits;
|
||||
_runs = other._runs;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bitmap(bitmap&& other) noexcept :
|
||||
_alloc{ std::move(other._alloc) },
|
||||
_sz{ std::move(other._sz) },
|
||||
_rc{ std::move(other._rc) },
|
||||
_bits{ std::move(other._bits) },
|
||||
_runs{ std::move(other._runs) }
|
||||
{
|
||||
}
|
||||
|
||||
bitmap& operator=(bitmap&& other) noexcept
|
||||
{
|
||||
if constexpr (std::allocator_traits<allocator_type>::propagate_on_container_move_assignment::value)
|
||||
{
|
||||
_alloc = std::move(other._alloc);
|
||||
}
|
||||
_bits = std::move(other._bits);
|
||||
_runs = std::move(other._runs);
|
||||
_sz = std::move(other._sz);
|
||||
_rc = std::move(other._rc);
|
||||
return *this;
|
||||
}
|
||||
|
||||
~bitmap() {}
|
||||
|
||||
void swap(bitmap& other)
|
||||
{
|
||||
if constexpr (std::allocator_traits<allocator_type>::propagate_on_container_swap::value)
|
||||
{
|
||||
std::swap(_alloc, other._alloc);
|
||||
}
|
||||
std::swap(_bits, other._bits);
|
||||
std::swap(_runs, other._runs);
|
||||
std::swap(_sz, other._sz);
|
||||
std::swap(_rc, other._rc);
|
||||
}
|
||||
|
||||
constexpr bool operator==(const bitmap& other) const noexcept
|
||||
{
|
||||
return _sz == other._sz &&
|
||||
_rc == other._rc &&
|
||||
_bits == other._bits;
|
||||
// _runs excluded because it's a cache of generated state.
|
||||
}
|
||||
|
||||
constexpr bool operator!=(const bitmap& other) const noexcept
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
const_iterator begin() const
|
||||
{
|
||||
return const_iterator(_bits, til::rect{ _sz }, 0);
|
||||
}
|
||||
|
||||
const_iterator end() const
|
||||
{
|
||||
return const_iterator(_bits, til::rect{ _sz }, _sz.area());
|
||||
}
|
||||
|
||||
const std::span<const til::rect> runs() const
|
||||
{
|
||||
// If we don't have cached runs, rebuild.
|
||||
if (!_runs.has_value())
|
||||
{
|
||||
_runs.emplace(begin(), end());
|
||||
}
|
||||
|
||||
// Return the runs.
|
||||
return _runs.value();
|
||||
}
|
||||
|
||||
// optional fill the uncovered area with bits.
|
||||
void translate(const til::point delta, bool fill = false)
|
||||
{
|
||||
if (delta.x == 0)
|
||||
{
|
||||
// fast path by using bit shifting
|
||||
translate_y(delta.y, fill);
|
||||
return;
|
||||
}
|
||||
|
||||
// FUTURE: PERF: GH #4015: This could use in-place walk semantics instead of a temporary.
|
||||
bitmap<allocator_type> other{ _sz, _alloc };
|
||||
|
||||
for (auto run : *this)
|
||||
{
|
||||
// Offset by the delta
|
||||
run += delta;
|
||||
|
||||
// Intersect with the bounds of our bitmap area
|
||||
// as part of it could have slid out of bounds.
|
||||
run &= _rc;
|
||||
|
||||
// Set it into the new bitmap.
|
||||
other.set(run);
|
||||
}
|
||||
|
||||
// If we were asked to fill... find the uncovered region.
|
||||
if (fill)
|
||||
{
|
||||
// Original Rect of As.
|
||||
//
|
||||
// X <-- origin
|
||||
// A A A A
|
||||
// A A A A
|
||||
// A A A A
|
||||
// A A A A
|
||||
const auto originalRect = _rc;
|
||||
|
||||
// If Delta = (2, 2)
|
||||
// Translated Rect of Bs.
|
||||
//
|
||||
// X <-- origin
|
||||
//
|
||||
//
|
||||
// B B B B
|
||||
// B B B B
|
||||
// B B B B
|
||||
// B B B B
|
||||
const auto translatedRect = _rc + delta;
|
||||
|
||||
// Subtract the B from the A one to see what wasn't filled by the move.
|
||||
// C is the overlap of A and B:
|
||||
//
|
||||
// X <-- origin
|
||||
// A A A A 1 1 1 1
|
||||
// A A A A 1 1 1 1
|
||||
// A A C C B B subtract 2 2
|
||||
// A A C C B B ---------> 2 2
|
||||
// B B B B A - B
|
||||
// B B B B
|
||||
//
|
||||
// 1 and 2 are the spaces to fill that are "uncovered".
|
||||
const auto fillRects = originalRect - translatedRect;
|
||||
for (const auto& f : fillRects)
|
||||
{
|
||||
other.set(f);
|
||||
}
|
||||
}
|
||||
|
||||
// Swap us with the temporary one.
|
||||
std::swap(other, *this);
|
||||
}
|
||||
|
||||
void set(const til::point pt)
|
||||
{
|
||||
if (_rc.contains(pt))
|
||||
{
|
||||
_runs.reset(); // reset cached runs on any non-const method
|
||||
_bits.set(_rc.index_of(pt));
|
||||
}
|
||||
}
|
||||
|
||||
void set(til::rect rc)
|
||||
{
|
||||
_runs.reset(); // reset cached runs on any non-const method
|
||||
|
||||
rc &= _rc;
|
||||
|
||||
const auto width = rc.width();
|
||||
const auto stride = _rc.width();
|
||||
auto idx = _rc.index_of({ rc.left, rc.top });
|
||||
|
||||
for (auto row = rc.top; row < rc.bottom; ++row, idx += stride)
|
||||
{
|
||||
_bits.set(idx, width, true);
|
||||
}
|
||||
}
|
||||
|
||||
void set_all() noexcept
|
||||
{
|
||||
_runs.reset(); // reset cached runs on any non-const method
|
||||
_bits.set();
|
||||
}
|
||||
|
||||
void reset_all() noexcept
|
||||
{
|
||||
_runs.reset(); // reset cached runs on any non-const method
|
||||
_bits.reset();
|
||||
}
|
||||
|
||||
// True if we resized. False if it was the same size as before.
|
||||
// Set fill if you want the new region (on growing) to be marked dirty.
|
||||
bool resize(til::size size, bool fill = false)
|
||||
{
|
||||
_runs.reset(); // reset cached runs on any non-const method
|
||||
|
||||
// Don't resize if it's not different
|
||||
if (_sz != size)
|
||||
{
|
||||
// Make a new bitmap for the other side, empty initially.
|
||||
bitmap<allocator_type> newMap{ size, false, _alloc };
|
||||
|
||||
// Copy any regions that overlap from this map to the new one.
|
||||
// Just iterate our runs...
|
||||
for (const auto& run : *this)
|
||||
{
|
||||
// intersect them with the new map
|
||||
// so we don't attempt to set bits that fit outside
|
||||
// the new one.
|
||||
const auto intersect = run & newMap._rc;
|
||||
|
||||
// and if there is still anything left, set them.
|
||||
if (!intersect.empty())
|
||||
{
|
||||
newMap.set(intersect);
|
||||
}
|
||||
}
|
||||
|
||||
// Then, if we were requested to fill the new space on growing,
|
||||
// find the space in the new rectangle that wasn't in the old
|
||||
// and fill it up.
|
||||
if (fill)
|
||||
{
|
||||
// A subtraction will yield anything in the new that isn't
|
||||
// a part of the old.
|
||||
const auto newAreas = newMap._rc - _rc;
|
||||
for (const auto& area : newAreas)
|
||||
{
|
||||
newMap.set(area);
|
||||
}
|
||||
}
|
||||
|
||||
// Swap and return.
|
||||
std::swap(newMap, *this);
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr bool one() const noexcept
|
||||
{
|
||||
return _bits.count() == 1;
|
||||
}
|
||||
|
||||
constexpr bool any() const noexcept
|
||||
{
|
||||
return !none();
|
||||
}
|
||||
|
||||
constexpr bool none() const noexcept
|
||||
{
|
||||
return _bits.none();
|
||||
}
|
||||
|
||||
constexpr bool all() const noexcept
|
||||
{
|
||||
return _bits.all();
|
||||
}
|
||||
|
||||
constexpr til::size size() const noexcept
|
||||
{
|
||||
return _sz;
|
||||
}
|
||||
|
||||
std::wstring to_string() const
|
||||
{
|
||||
auto str = fmt::format(FMT_COMPILE(L"Bitmap of size {} contains the following dirty regions:\nRuns:"), _sz.to_string());
|
||||
for (auto& item : *this)
|
||||
{
|
||||
fmt::format_to(std::back_inserter(str), FMT_COMPILE(L"\n\t- {}"), item.to_string());
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
private:
|
||||
void translate_y(ptrdiff_t delta_y, bool fill)
|
||||
{
|
||||
if (delta_y == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto bitShift = delta_y * _sz.width;
|
||||
|
||||
#pragma warning(push)
|
||||
// we can't depend on GSL here, so we use static_cast for explicit narrowing
|
||||
#pragma warning(disable : 26472)
|
||||
const auto newBits = static_cast<size_t>(std::abs(bitShift));
|
||||
#pragma warning(pop)
|
||||
const bool isLeftShift = bitShift > 0;
|
||||
|
||||
if (newBits >= _bits.size())
|
||||
{
|
||||
if (fill)
|
||||
{
|
||||
set_all();
|
||||
}
|
||||
else
|
||||
{
|
||||
reset_all();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (isLeftShift)
|
||||
{
|
||||
// This operator doesn't modify the size of `_bits`: the
|
||||
// new bits are set to 0.
|
||||
_bits <<= newBits;
|
||||
}
|
||||
else
|
||||
{
|
||||
_bits >>= newBits;
|
||||
}
|
||||
|
||||
if (fill)
|
||||
{
|
||||
if (isLeftShift)
|
||||
{
|
||||
_bits.set(0, newBits, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
_bits.set(_bits.size() - newBits, newBits, true);
|
||||
}
|
||||
}
|
||||
|
||||
_runs.reset(); // reset cached runs on any non-const method
|
||||
}
|
||||
|
||||
allocator_type _alloc;
|
||||
til::size _sz;
|
||||
til::rect _rc;
|
||||
dynamic_bitset<size_t, allocator_type> _bits;
|
||||
|
||||
mutable std::optional<std::vector<til::rect, run_allocator_type>> _runs;
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
friend class ::BitmapTests;
|
||||
#endif
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
using bitmap = ::til::details::bitmap<>;
|
||||
|
||||
namespace pmr
|
||||
{
|
||||
using bitmap = ::til::details::bitmap<std::pmr::polymorphic_allocator<size_t>>;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef __WEX_COMMON_H__
|
||||
namespace WEX::TestExecution
|
||||
{
|
||||
template<typename T>
|
||||
class VerifyOutputTraits<::til::details::bitmap<T>>
|
||||
{
|
||||
public:
|
||||
static WEX::Common::NoThrowString ToString(const ::til::details::bitmap<T>& rect)
|
||||
{
|
||||
return WEX::Common::NoThrowString(rect.to_string().c_str());
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class VerifyCompareTraits<::til::details::bitmap<T>, ::til::details::bitmap<T>>
|
||||
{
|
||||
public:
|
||||
static bool AreEqual(const ::til::details::bitmap<T>& expected, const ::til::details::bitmap<T>& actual) noexcept
|
||||
{
|
||||
return expected == actual;
|
||||
}
|
||||
|
||||
static bool AreSame(const ::til::details::bitmap<T>& expected, const ::til::details::bitmap<T>& actual) noexcept
|
||||
{
|
||||
return &expected == &actual;
|
||||
}
|
||||
|
||||
static bool IsLessThan(const ::til::details::bitmap<T>& expectedLess, const ::til::details::bitmap<T>& expectedGreater) = delete;
|
||||
|
||||
static bool IsGreaterThan(const ::til::details::bitmap<T>& expectedGreater, const ::til::details::bitmap<T>& expectedLess) = delete;
|
||||
|
||||
static bool IsNull(const ::til::details::bitmap<T>& object) noexcept
|
||||
{
|
||||
return object == til::details::bitmap<T>{};
|
||||
}
|
||||
};
|
||||
|
||||
};
|
||||
#endif
|
||||
@ -497,9 +497,12 @@ void InteractivityFactory::_WritePseudoWindowCallback(bool showOrHide)
|
||||
// this message, if it's already minimized. If the window is maximized a
|
||||
// restore will restore-down the window instead.
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
if (const auto io = gci.GetVtIo())
|
||||
if (auto writer = gci.GetVtWriter())
|
||||
{
|
||||
io->SetWindowVisibility(showOrHide);
|
||||
char buf[] = "\x1b[1t";
|
||||
buf[2] = showOrHide ? '1' : '2';
|
||||
writer.WriteUTF8(buf);
|
||||
writer.Submit();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -67,15 +67,6 @@ void ServiceLocator::RundownAndExit(const HRESULT hr)
|
||||
Sleep(INFINITE);
|
||||
}
|
||||
|
||||
// MSFT:15506250
|
||||
// In VT I/O Mode, a client application might die before we've rendered
|
||||
// the last bit of text they've emitted. So give the VtRenderer one
|
||||
// last chance to paint before it is killed.
|
||||
if (s_globals.pRender)
|
||||
{
|
||||
s_globals.pRender->TriggerTeardown();
|
||||
}
|
||||
|
||||
// MSFT:40226902 - HOTFIX shutdown on OneCore, by leaking the renderer, thereby
|
||||
// reducing the change for existing race conditions to turn into deadlocks.
|
||||
#ifndef NDEBUG
|
||||
|
||||
@ -63,12 +63,6 @@ BgfxEngine::BgfxEngine(PVOID SharedViewBase, LONG DisplayHeight, LONG DisplayWid
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
[[nodiscard]] HRESULT BgfxEngine::PrepareForTeardown(_Out_ bool* const pForcePaint) noexcept
|
||||
{
|
||||
*pForcePaint = false;
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
[[nodiscard]] HRESULT BgfxEngine::StartPaint() noexcept
|
||||
{
|
||||
return S_OK;
|
||||
|
||||
@ -38,7 +38,6 @@ namespace Microsoft::Console::Render
|
||||
[[nodiscard]] HRESULT InvalidateSelection(const std::vector<til::rect>& rectangles) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateScroll(const til::point* pcoordDelta) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateAll() noexcept override;
|
||||
[[nodiscard]] HRESULT PrepareForTeardown(_Out_ bool* const pForcePaint) noexcept override;
|
||||
|
||||
[[nodiscard]] HRESULT StartPaint() noexcept override;
|
||||
[[nodiscard]] HRESULT EndPaint() noexcept override;
|
||||
|
||||
@ -26,9 +26,6 @@
|
||||
<ProjectReference Include="..\..\..\renderer\atlas\atlas.vcxproj">
|
||||
<Project>{8222900C-8B6C-452A-91AC-BE95DB04B95F}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\..\renderer\vt\lib\vt.vcxproj">
|
||||
<Project>{990f2657-8580-4828-943f-5dd657d11842}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\..\types\lib\types.vcxproj">
|
||||
<Project>{18d09a24-8240-42d6-8cb6-236eee820263}</Project>
|
||||
</ProjectReference>
|
||||
|
||||
@ -935,7 +935,11 @@ DWORD WINAPI ConsoleInputThreadProcWin32(LPVOID /*lpParameter*/)
|
||||
// VtIo's CreatePseudoWindow, which will make sure that the window is
|
||||
// successfully created with the owner configured when the window is
|
||||
// first created. See GH#13066 for details.
|
||||
ServiceLocator::LocateGlobals().getConsoleInformation().GetVtIo()->CreatePseudoWindow();
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
if (gci.IsInVtIoMode())
|
||||
{
|
||||
gci.GetVtIo()->CreatePseudoWindow();
|
||||
}
|
||||
|
||||
// Register the pseudoconsole window as being owned by the root process.
|
||||
const auto pseudoWindow = ServiceLocator::LocatePseudoWindow();
|
||||
|
||||
@ -49,8 +49,6 @@ INCLUDES= \
|
||||
$(INCLUDES); \
|
||||
$(CONSOLE_SRC_PATH)\inc; \
|
||||
$(CONSOLE_SRC_PATH)\..\..\inc; \
|
||||
$(CONSOLE_SRC_PATH)\..\oss\dynamic_bitset; \
|
||||
$(CONSOLE_SRC_PATH)\..\oss\libpopcnt; \
|
||||
$(CONSOLE_SRC_PATH)\..\oss\chromium; \
|
||||
$(CONSOLE_SRC_PATH)\..\oss\fmt\include; \
|
||||
$(CONSOLE_SRC_PATH)\..\oss\interval_tree; \
|
||||
|
||||
@ -160,13 +160,6 @@ constexpr HRESULT vec2_narrow(U x, U y, vec2<T>& out) noexcept
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
[[nodiscard]] HRESULT AtlasEngine::InvalidateFlush(_In_ const bool /*circled*/, _Out_ bool* const pForcePaint) noexcept
|
||||
{
|
||||
RETURN_HR_IF_NULL(E_INVALIDARG, pForcePaint);
|
||||
*pForcePaint = false;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
[[nodiscard]] HRESULT AtlasEngine::InvalidateTitle(const std::wstring_view proposedTitle) noexcept
|
||||
{
|
||||
_api.invalidatedTitle = true;
|
||||
|
||||
@ -282,13 +282,6 @@ try
|
||||
}
|
||||
CATCH_RETURN()
|
||||
|
||||
[[nodiscard]] HRESULT AtlasEngine::PrepareForTeardown(_Out_ bool* const pForcePaint) noexcept
|
||||
{
|
||||
RETURN_HR_IF_NULL(E_INVALIDARG, pForcePaint);
|
||||
*pForcePaint = false;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
[[nodiscard]] HRESULT AtlasEngine::ScrollFrame() noexcept
|
||||
{
|
||||
return S_OK;
|
||||
|
||||
@ -27,7 +27,6 @@ namespace Microsoft::Console::Render::Atlas
|
||||
[[nodiscard]] bool RequiresContinuousRedraw() noexcept override;
|
||||
void WaitUntilCanRender() noexcept override;
|
||||
[[nodiscard]] HRESULT Present() noexcept override;
|
||||
[[nodiscard]] HRESULT PrepareForTeardown(_Out_ bool* pForcePaint) noexcept override;
|
||||
[[nodiscard]] HRESULT ScrollFrame() noexcept override;
|
||||
[[nodiscard]] HRESULT Invalidate(const til::rect* psrRegion) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateCursor(const til::rect* psrRegion) noexcept override;
|
||||
@ -36,7 +35,6 @@ namespace Microsoft::Console::Render::Atlas
|
||||
[[nodiscard]] HRESULT InvalidateHighlight(std::span<const til::point_span> highlights, const TextBuffer& buffer) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateScroll(const til::point* pcoordDelta) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateAll() noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateFlush(_In_ const bool circled, _Out_ bool* const pForcePaint) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateTitle(std::wstring_view proposedTitle) noexcept override;
|
||||
[[nodiscard]] HRESULT NotifyNewText(const std::wstring_view newText) noexcept override;
|
||||
[[nodiscard]] HRESULT PrepareRenderInfo(RenderFrameInfo info) noexcept override;
|
||||
|
||||
@ -36,13 +36,6 @@
|
||||
#include <wil/stl.h>
|
||||
#include <wil/win32_helpers.h>
|
||||
|
||||
// Dynamic Bitset (optional dependency on LibPopCnt for perf at bit counting)
|
||||
// Variable-size compressed-storage header-only bit flag storage library.
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4702) // unreachable code
|
||||
#include <dynamic_bitset.hpp>
|
||||
#pragma warning(pop)
|
||||
|
||||
// Chromium Numerics (safe math)
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4100) // '...': unreferenced formal parameter
|
||||
|
||||
@ -92,19 +92,3 @@ void RenderEngineBase::WaitUntilCanRender() noexcept
|
||||
void RenderEngineBase::UpdateHyperlinkHoveredId(const uint16_t /*hoveredId*/) noexcept
|
||||
{
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Notifies us that we're about to circle the buffer, giving us a chance to
|
||||
// force a repaint before the buffer contents are lost.
|
||||
// - The default implementation of flush, is to do nothing for most renderers.
|
||||
// Arguments:
|
||||
// - circled - ignored
|
||||
// - pForcePaint - Always filled with false
|
||||
// Return Value:
|
||||
// - S_FALSE because we don't use this.
|
||||
[[nodiscard]] HRESULT RenderEngineBase::InvalidateFlush(_In_ const bool /*circled*/, _Out_ bool* const pForcePaint) noexcept
|
||||
{
|
||||
RETURN_HR_IF_NULL(E_INVALIDARG, pForcePaint);
|
||||
*pForcePaint = false;
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
@ -316,26 +316,6 @@ void Renderer::TriggerTeardown() noexcept
|
||||
{
|
||||
// We need to shut down the paint thread on teardown.
|
||||
_pThread->WaitForPaintCompletionAndDisable(INFINITE);
|
||||
|
||||
auto repaint = false;
|
||||
|
||||
// Then walk through and do one final paint on the caller's thread.
|
||||
FOREACH_ENGINE(pEngine)
|
||||
{
|
||||
auto fEngineRequestsRepaint = false;
|
||||
auto hr = pEngine->PrepareForTeardown(&fEngineRequestsRepaint);
|
||||
LOG_IF_FAILED(hr);
|
||||
|
||||
repaint |= SUCCEEDED(hr) && fEngineRequestsRepaint;
|
||||
}
|
||||
|
||||
// BODGY: The only time repaint is true is when VtEngine is used.
|
||||
// Coincidentally VtEngine always runs alone, so if repaint is true, there's only a single engine
|
||||
// to repaint anyways and there's no danger is accidentally repainting an engine that didn't want to.
|
||||
if (repaint)
|
||||
{
|
||||
LOG_IF_FAILED(_PaintFrame());
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
@ -486,38 +466,6 @@ void Renderer::TriggerScroll(const til::point* const pcoordDelta)
|
||||
NotifyPaintFrame();
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Called when the text buffer is about to circle its backing buffer.
|
||||
// A renderer might want to get painted before that happens.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Renderer::TriggerFlush(const bool circling)
|
||||
{
|
||||
const auto rects = _GetSelectionRects();
|
||||
auto repaint = false;
|
||||
|
||||
FOREACH_ENGINE(pEngine)
|
||||
{
|
||||
auto fEngineRequestsRepaint = false;
|
||||
auto hr = pEngine->InvalidateFlush(circling, &fEngineRequestsRepaint);
|
||||
LOG_IF_FAILED(hr);
|
||||
|
||||
LOG_IF_FAILED(pEngine->InvalidateSelection(rects));
|
||||
|
||||
repaint |= SUCCEEDED(hr) && fEngineRequestsRepaint;
|
||||
}
|
||||
|
||||
// BODGY: The only time repaint is true is when VtEngine is used.
|
||||
// Coincidentally VtEngine always runs alone, so if repaint is true, there's only a single engine
|
||||
// to repaint anyways and there's no danger is accidentally repainting an engine that didn't want to.
|
||||
if (repaint)
|
||||
{
|
||||
LOG_IF_FAILED(_PaintFrame());
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Called when the title of the console window has changed. Indicates that we
|
||||
// should update the title on the next frame.
|
||||
|
||||
@ -23,14 +23,6 @@ Author(s):
|
||||
|
||||
#include "../../buffer/out/textBuffer.hpp"
|
||||
|
||||
// fwdecl unittest classes
|
||||
#ifdef UNIT_TESTING
|
||||
namespace TerminalCoreUnitTests
|
||||
{
|
||||
class ConptyRoundtripTests;
|
||||
};
|
||||
#endif
|
||||
|
||||
namespace Microsoft::Console::Render
|
||||
{
|
||||
class Renderer
|
||||
@ -60,7 +52,6 @@ namespace Microsoft::Console::Render
|
||||
void TriggerScroll();
|
||||
void TriggerScroll(const til::point* const pcoordDelta);
|
||||
|
||||
void TriggerFlush(const bool circling);
|
||||
void TriggerTitleChange();
|
||||
|
||||
void TriggerNewTextNotification(const std::wstring_view newText);
|
||||
@ -146,10 +137,5 @@ namespace Microsoft::Console::Render
|
||||
std::function<void()> _pfnRendererEnteredErrorState;
|
||||
bool _destructing = false;
|
||||
bool _forceUpdateViewport = false;
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
friend class ConptyOutputTests;
|
||||
friend class TerminalCoreUnitTests::ConptyRoundtripTests;
|
||||
#endif
|
||||
};
|
||||
}
|
||||
|
||||
@ -2,4 +2,3 @@ DIRS= \
|
||||
base \
|
||||
gdi \
|
||||
wddmcon \
|
||||
vt \
|
||||
|
||||
@ -33,7 +33,6 @@ namespace Microsoft::Console::Render
|
||||
[[nodiscard]] HRESULT Invalidate(const til::rect* const psrRegion) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateCursor(const til::rect* const psrRegion) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateAll() noexcept override;
|
||||
[[nodiscard]] HRESULT PrepareForTeardown(_Out_ bool* const pForcePaint) noexcept override;
|
||||
|
||||
[[nodiscard]] HRESULT StartPaint() noexcept override;
|
||||
[[nodiscard]] HRESULT EndPaint() noexcept override;
|
||||
|
||||
@ -100,21 +100,6 @@ HRESULT GdiEngine::InvalidateAll() noexcept
|
||||
RETURN_HR(InvalidateSystem(&rc));
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Notifies us that we're about to be torn down. This gives us a last chance
|
||||
// to force a repaint before the buffer contents are lost. The GDI renderer
|
||||
// doesn't care if we lose text - we're only painting visible text anyways,
|
||||
// so we return false.
|
||||
// Arguments:
|
||||
// - Receives a bool indicating if we should force the repaint.
|
||||
// Return Value:
|
||||
// - S_FALSE - we succeeded, but the result was false.
|
||||
HRESULT GdiEngine::PrepareForTeardown(_Out_ bool* const pForcePaint) noexcept
|
||||
{
|
||||
*pForcePaint = false;
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Helper to combine the given rectangle into the invalid region to be updated on the next paint
|
||||
// Arguments:
|
||||
|
||||
@ -62,7 +62,6 @@ namespace Microsoft::Console::Render
|
||||
[[nodiscard]] virtual bool RequiresContinuousRedraw() noexcept = 0;
|
||||
virtual void WaitUntilCanRender() noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT Present() noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT PrepareForTeardown(_Out_ bool* pForcePaint) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT ScrollFrame() noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT Invalidate(const til::rect* psrRegion) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT InvalidateCursor(const til::rect* psrRegion) noexcept = 0;
|
||||
@ -71,7 +70,6 @@ namespace Microsoft::Console::Render
|
||||
[[nodiscard]] virtual HRESULT InvalidateHighlight(std::span<const til::point_span> highlights, const TextBuffer& buffer) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT InvalidateScroll(const til::point* pcoordDelta) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT InvalidateAll() noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT InvalidateFlush(_In_ const bool circled, _Out_ bool* const pForcePaint) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT InvalidateTitle(std::wstring_view proposedTitle) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT NotifyNewText(const std::wstring_view newText) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT PrepareRenderInfo(RenderFrameInfo info) noexcept = 0;
|
||||
|
||||
@ -48,8 +48,6 @@ namespace Microsoft::Console::Render
|
||||
|
||||
[[nodiscard]] bool RequiresContinuousRedraw() noexcept override;
|
||||
|
||||
[[nodiscard]] HRESULT InvalidateFlush(_In_ const bool circled, _Out_ bool* const pForcePaint) noexcept override;
|
||||
|
||||
void WaitUntilCanRender() noexcept override;
|
||||
void UpdateHyperlinkHoveredId(const uint16_t hoveredId) noexcept override;
|
||||
|
||||
|
||||
@ -191,20 +191,6 @@ try
|
||||
}
|
||||
CATCH_LOG_RETURN_HR(E_FAIL);
|
||||
|
||||
// Routine Description:
|
||||
// - This is unused by this renderer.
|
||||
// Arguments:
|
||||
// - pForcePaint - always filled with false.
|
||||
// Return Value:
|
||||
// - S_FALSE because this is unused.
|
||||
[[nodiscard]] HRESULT UiaEngine::PrepareForTeardown(_Out_ bool* const pForcePaint) noexcept
|
||||
{
|
||||
RETURN_HR_IF_NULL(E_INVALIDARG, pForcePaint);
|
||||
|
||||
*pForcePaint = false;
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Prepares internal structures for a painting operation.
|
||||
// Arguments:
|
||||
|
||||
@ -38,7 +38,6 @@ namespace Microsoft::Console::Render
|
||||
[[nodiscard]] HRESULT EndPaint() noexcept override;
|
||||
void WaitUntilCanRender() noexcept override;
|
||||
[[nodiscard]] HRESULT Present() noexcept override;
|
||||
[[nodiscard]] HRESULT PrepareForTeardown(_Out_ bool* const pForcePaint) noexcept override;
|
||||
[[nodiscard]] HRESULT ScrollFrame() noexcept override;
|
||||
[[nodiscard]] HRESULT Invalidate(const til::rect* const psrRegion) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateCursor(const til::rect* const psrRegion) noexcept override;
|
||||
|
||||
@ -1,527 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
#include "vtrenderer.hpp"
|
||||
|
||||
#include "../renderer/base/renderer.hpp"
|
||||
#include "../../inc/conattrs.hpp"
|
||||
|
||||
#pragma hdrstop
|
||||
using namespace Microsoft::Console::Render;
|
||||
|
||||
// Method Description:
|
||||
// - Formats and writes a sequence to stop the cursor from blinking.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
|
||||
[[nodiscard]] HRESULT VtEngine::_StopCursorBlinking() noexcept
|
||||
{
|
||||
return _Write("\x1b[?12l");
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Formats and writes a sequence to start the cursor blinking. If it's
|
||||
// hidden, this won't also show it.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
|
||||
[[nodiscard]] HRESULT VtEngine::_StartCursorBlinking() noexcept
|
||||
{
|
||||
return _Write("\x1b[?12h");
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Formats and writes a sequence to hide the cursor.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
|
||||
[[nodiscard]] HRESULT VtEngine::_HideCursor() noexcept
|
||||
{
|
||||
return _Write("\x1b[?25l");
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Formats and writes a sequence to show the cursor.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
|
||||
[[nodiscard]] HRESULT VtEngine::_ShowCursor() noexcept
|
||||
{
|
||||
return _Write("\x1b[?25h");
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Formats and writes a sequence to erase the remainder of the line starting
|
||||
// from the cursor position.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
|
||||
[[nodiscard]] HRESULT VtEngine::_EraseLine() noexcept
|
||||
{
|
||||
// The default no-param action of erase line is erase to the right.
|
||||
// telnet client doesn't understand the parameterized version,
|
||||
// so emit the implicit sequence instead.
|
||||
return _Write("\x1b[K");
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Formats and writes a sequence to either insert or delete a number of lines
|
||||
// into the buffer at the current cursor location.
|
||||
// Delete/insert Character removes/adds N characters from/to the buffer, and
|
||||
// shifts the remaining chars in the row to the left/right, while Erase
|
||||
// Character replaces N characters with spaces, and leaves the rest
|
||||
// untouched.
|
||||
// Arguments:
|
||||
// - chars: a number of characters to erase (by overwriting with space)
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
|
||||
[[nodiscard]] HRESULT VtEngine::_EraseCharacter(const til::CoordType chars) noexcept
|
||||
{
|
||||
return _WriteFormatted(FMT_COMPILE("\x1b[{}X"), chars);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Moves the cursor forward (right) a number of characters.
|
||||
// Arguments:
|
||||
// - chars: a number of characters to move cursor right by.
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
|
||||
[[nodiscard]] HRESULT VtEngine::_CursorForward(const til::CoordType chars) noexcept
|
||||
{
|
||||
return _WriteFormatted(FMT_COMPILE("\x1b[{}C"), chars);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Formats and writes a sequence to erase the remainder of the line starting
|
||||
// from the cursor position.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
|
||||
[[nodiscard]] HRESULT VtEngine::_ClearScreen() noexcept
|
||||
{
|
||||
return _Write("\x1b[2J");
|
||||
}
|
||||
|
||||
[[nodiscard]] HRESULT VtEngine::_ClearScrollback() noexcept
|
||||
{
|
||||
return _Write("\x1b[3J");
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Formats and writes a sequence to either insert or delete a number of lines
|
||||
// into the buffer at the current cursor location.
|
||||
// Arguments:
|
||||
// - sLines: a number of lines to insert or delete
|
||||
// - fInsertLine: true iff we should insert the lines, false to delete them.
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
|
||||
[[nodiscard]] HRESULT VtEngine::_InsertDeleteLine(const til::CoordType sLines, const bool fInsertLine) noexcept
|
||||
{
|
||||
if (sLines <= 0)
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
if (sLines == 1)
|
||||
{
|
||||
return _Write(fInsertLine ? "\x1b[L" : "\x1b[M");
|
||||
}
|
||||
|
||||
return _WriteFormatted(FMT_COMPILE("\x1b[{}{}"), sLines, fInsertLine ? 'L' : 'M');
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Formats and writes a sequence to delete a number of lines into the buffer
|
||||
// at the current cursor location.
|
||||
// Arguments:
|
||||
// - sLines: a number of lines to insert
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
|
||||
[[nodiscard]] HRESULT VtEngine::_DeleteLine(const til::CoordType sLines) noexcept
|
||||
{
|
||||
return _InsertDeleteLine(sLines, false);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Formats and writes a sequence to insert a number of lines into the buffer
|
||||
// at the current cursor location.
|
||||
// Arguments:
|
||||
// - sLines: a number of lines to insert
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
|
||||
[[nodiscard]] HRESULT VtEngine::_InsertLine(const til::CoordType sLines) noexcept
|
||||
{
|
||||
return _InsertDeleteLine(sLines, true);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Formats and writes a sequence to move the cursor to the specified
|
||||
// coordinate position. The input coord should be in console coordinates,
|
||||
// where origin=(0,0).
|
||||
// Arguments:
|
||||
// - coord: Console coordinates to move the cursor to.
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
|
||||
[[nodiscard]] HRESULT VtEngine::_CursorPosition(const til::point coord) noexcept
|
||||
{
|
||||
// VT coords start at 1,1
|
||||
auto coordVt = coord;
|
||||
coordVt.x++;
|
||||
coordVt.y++;
|
||||
|
||||
return _WriteFormatted(FMT_COMPILE("\x1b[{};{}H"), coordVt.y, coordVt.x);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Formats and writes a sequence to move the cursor to the origin.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
|
||||
[[nodiscard]] HRESULT VtEngine::_CursorHome() noexcept
|
||||
{
|
||||
return _Write("\x1b[H");
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Formats and writes a sequence to change the current text attributes to the default.
|
||||
// Arguments:
|
||||
// <none>
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
|
||||
[[nodiscard]] HRESULT VtEngine::_SetGraphicsDefault() noexcept
|
||||
{
|
||||
return _Write("\x1b[m");
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Formats and writes a sequence to change the current text attributes to an
|
||||
// indexed color from the 16-color table.
|
||||
// Arguments:
|
||||
// - index: color table index to emit as a VT sequence
|
||||
// - fIsForeground: true if we should emit the foreground sequence, false for background
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
|
||||
[[nodiscard]] HRESULT VtEngine::_SetGraphicsRendition16Color(const BYTE index,
|
||||
const bool fIsForeground) noexcept
|
||||
{
|
||||
// Always check using the foreground flags, because the bg flags constants
|
||||
// are a higher byte
|
||||
// Foreground sequences are in [30,37] U [90,97]
|
||||
// Background sequences are in [40,47] U [100,107]
|
||||
// The "dark" sequences are in the first 7 values, the bright sequences in the second set.
|
||||
// Note that text brightness and intensity are different in VT. Intensity is
|
||||
// handled by _SetIntense. Here, we can emit either bright or
|
||||
// dark colors. For conhost as a terminal, it can't draw bold
|
||||
// characters, so it displays "intense" as bright, and in fact most
|
||||
// terminals display the bright color when displaying intense text.
|
||||
// By specifying the intensity and brightness separately, we'll make sure the
|
||||
// terminal has an accurate representation of our buffer.
|
||||
const auto prefix = WI_IsFlagSet(index, FOREGROUND_INTENSITY) ? (fIsForeground ? 90 : 100) : (fIsForeground ? 30 : 40);
|
||||
return _WriteFormatted(FMT_COMPILE("\x1b[{}m"), prefix + (index & 7));
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Formats and writes a sequence to change the current text attributes to an
|
||||
// indexed color from the 256-color table.
|
||||
// Arguments:
|
||||
// - index: color table index to emit as a VT sequence
|
||||
// - fIsForeground: true if we should emit the foreground sequence, false for background
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
|
||||
[[nodiscard]] HRESULT VtEngine::_SetGraphicsRendition256Color(const BYTE index,
|
||||
const bool fIsForeground) noexcept
|
||||
{
|
||||
return _WriteFormatted(FMT_COMPILE("\x1b[{}8;5;{}m"), fIsForeground ? '3' : '4', index);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Formats and writes a sequence to change the current underline color to an
|
||||
// indexed color from the 256-color table.
|
||||
// - Uses sub parameters.
|
||||
// Arguments:
|
||||
// - index: color table index to emit as a VT sequence
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
|
||||
[[nodiscard]] HRESULT VtEngine::_SetGraphicsRenditionUnderline256Color(const BYTE index) noexcept
|
||||
{
|
||||
return _WriteFormatted(FMT_COMPILE("\x1b[58:5:{}m"), index);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Formats and writes a sequence to change the current text attributes to an
|
||||
// RGB color.
|
||||
// Arguments:
|
||||
// - color: The color to emit a VT sequence for
|
||||
// - fIsForeground: true if we should emit the foreground sequence, false for background
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
|
||||
[[nodiscard]] HRESULT VtEngine::_SetGraphicsRenditionRGBColor(const COLORREF color,
|
||||
const bool fIsForeground) noexcept
|
||||
{
|
||||
const auto r = GetRValue(color);
|
||||
const auto g = GetGValue(color);
|
||||
const auto b = GetBValue(color);
|
||||
return _WriteFormatted(FMT_COMPILE("\x1b[{}8;2;{};{};{}m"), fIsForeground ? '3' : '4', r, g, b);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Formats and writes a sequence to change the current underline color to an
|
||||
// RGB color.
|
||||
// - Uses sub parameters.
|
||||
// Arguments:
|
||||
// - color: The color to emit a VT sequence for.
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
|
||||
[[nodiscard]] HRESULT VtEngine::_SetGraphicsRenditionUnderlineRGBColor(const COLORREF color) noexcept
|
||||
{
|
||||
const auto r = GetRValue(color);
|
||||
const auto g = GetGValue(color);
|
||||
const auto b = GetBValue(color);
|
||||
return _WriteFormatted(FMT_COMPILE("\x1b[58:2::{}:{}:{}m"), r, g, b);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Formats and writes a sequence to change the current text attributes to the
|
||||
// default foreground or background. Does not affect the intensity of text.
|
||||
// Arguments:
|
||||
// - fIsForeground: true if we should emit the foreground sequence, false for background
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
|
||||
[[nodiscard]] HRESULT VtEngine::_SetGraphicsRenditionDefaultColor(const bool fIsForeground) noexcept
|
||||
{
|
||||
return _Write(fIsForeground ? ("\x1b[39m") : ("\x1b[49m"));
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Formats and writes a sequence to change the current underline color to the
|
||||
// default color.
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
|
||||
[[nodiscard]] HRESULT VtEngine::_SetGraphicsRenditionUnderlineDefaultColor() noexcept
|
||||
{
|
||||
return _Write("\x1b[59m");
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Formats and writes a sequence to change the terminal's window size.
|
||||
// Arguments:
|
||||
// - sWidth: number of columns the terminal should display
|
||||
// - sHeight: number of rows the terminal should display
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
|
||||
[[nodiscard]] HRESULT VtEngine::_ResizeWindow(const til::CoordType sWidth, const til::CoordType sHeight) noexcept
|
||||
{
|
||||
if (sWidth < 0 || sHeight < 0)
|
||||
{
|
||||
return E_INVALIDARG;
|
||||
}
|
||||
|
||||
return _WriteFormatted(FMT_COMPILE("\x1b[8;{};{}t"), sHeight, sWidth);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Formats and writes a sequence to request the end terminal to tell us the
|
||||
// cursor position. The terminal will reply back on the vt input handle.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
|
||||
[[nodiscard]] HRESULT VtEngine::_RequestCursor() noexcept
|
||||
{
|
||||
return _Write("\x1b[6n");
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Formats and writes a sequence to change the terminal's title string
|
||||
// Arguments:
|
||||
// - title: string to use as the new title of the window.
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
|
||||
[[nodiscard]] HRESULT VtEngine::_ChangeTitle(_In_ const std::string& title) noexcept
|
||||
{
|
||||
return _WriteFormatted(FMT_COMPILE("\x1b]0;{}\x7"), title);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Formats and writes a sequence to change the intensity of the following text.
|
||||
// Arguments:
|
||||
// - isIntense: If true, we'll make the text intense. Otherwise we'll remove the intensity.
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
|
||||
[[nodiscard]] HRESULT VtEngine::_SetIntense(const bool isIntense) noexcept
|
||||
{
|
||||
return _Write(isIntense ? "\x1b[1m" : "\x1b[22m");
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Formats and writes a sequence to change the faintness of the following text.
|
||||
// Arguments:
|
||||
// - isFaint: If true, we'll make the text faint. Otherwise we'll remove the faintness.
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
|
||||
[[nodiscard]] HRESULT VtEngine::_SetFaint(const bool isFaint) noexcept
|
||||
{
|
||||
return _Write(isFaint ? "\x1b[2m" : "\x1b[22m");
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Formats and writes a sequence to change the extended underline styling of the following text.
|
||||
// - Uses backward compatible SGR 4 (without sub parameter) and SGR 21 for single and doubly underline.
|
||||
// Arguments:
|
||||
// - style: underline style to use.
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
|
||||
[[nodiscard]] HRESULT VtEngine::_SetUnderlineExtended(const UnderlineStyle style) noexcept
|
||||
{
|
||||
switch (style)
|
||||
{
|
||||
case UnderlineStyle::NoUnderline:
|
||||
return _SetUnderlined(false);
|
||||
case UnderlineStyle::SinglyUnderlined:
|
||||
return _SetUnderlined(true);
|
||||
case UnderlineStyle::DoublyUnderlined:
|
||||
return _Write("\x1b[21m");
|
||||
case UnderlineStyle::CurlyUnderlined:
|
||||
return _Write("\x1b[4:3m");
|
||||
case UnderlineStyle::DottedUnderlined:
|
||||
return _Write("\x1b[4:4m");
|
||||
case UnderlineStyle::DashedUnderlined:
|
||||
return _Write("\x1b[4:5m");
|
||||
default:
|
||||
return _SetUnderlined(true); // treat unknown style as singly underlined
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Formats and writes a sequence to change the underline of the following text.
|
||||
// Arguments:
|
||||
// - isUnderlined: If true, we'll underline the text. Otherwise we'll remove the underline.
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
|
||||
[[nodiscard]] HRESULT VtEngine::_SetUnderlined(const bool isUnderlined) noexcept
|
||||
{
|
||||
return _Write(isUnderlined ? "\x1b[4m" : "\x1b[24m");
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Formats and writes a sequence to change the overline of the following text.
|
||||
// Arguments:
|
||||
// - isOverlined: If true, we'll overline the text. Otherwise we'll remove the overline.
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
|
||||
[[nodiscard]] HRESULT VtEngine::_SetOverlined(const bool isOverlined) noexcept
|
||||
{
|
||||
return _Write(isOverlined ? "\x1b[53m" : "\x1b[55m");
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Formats and writes a sequence to change the italics of the following text.
|
||||
// Arguments:
|
||||
// - isItalic: If true, we'll italicize the text. Otherwise we'll remove the italics.
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
|
||||
[[nodiscard]] HRESULT VtEngine::_SetItalic(const bool isItalic) noexcept
|
||||
{
|
||||
return _Write(isItalic ? "\x1b[3m" : "\x1b[23m");
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Formats and writes a sequence to change the blinking of the following text.
|
||||
// Arguments:
|
||||
// - isBlinking: If true, we'll start the text blinking. Otherwise we'll stop the blinking.
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
|
||||
[[nodiscard]] HRESULT VtEngine::_SetBlinking(const bool isBlinking) noexcept
|
||||
{
|
||||
return _Write(isBlinking ? "\x1b[5m" : "\x1b[25m");
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Formats and writes a sequence to change the visibility of the following text.
|
||||
// Arguments:
|
||||
// - isInvisible: If true, we'll make the text invisible. Otherwise we'll make it visible.
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
|
||||
[[nodiscard]] HRESULT VtEngine::_SetInvisible(const bool isInvisible) noexcept
|
||||
{
|
||||
return _Write(isInvisible ? "\x1b[8m" : "\x1b[28m");
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Formats and writes a sequence to change the crossed out state of the following text.
|
||||
// Arguments:
|
||||
// - isCrossedOut: If true, we'll cross out the text. Otherwise we'll stop crossing out.
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
|
||||
[[nodiscard]] HRESULT VtEngine::_SetCrossedOut(const bool isCrossedOut) noexcept
|
||||
{
|
||||
return _Write(isCrossedOut ? "\x1b[9m" : "\x1b[29m");
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Formats and writes a sequence to change the reversed state of the following text.
|
||||
// Arguments:
|
||||
// - isReversed: If true, we'll reverse the text. Otherwise we'll remove the reversed state.
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
|
||||
[[nodiscard]] HRESULT VtEngine::_SetReverseVideo(const bool isReversed) noexcept
|
||||
{
|
||||
return _Write(isReversed ? "\x1b[7m" : "\x1b[27m");
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Send a sequence to the connected terminal to switch to the alternate or main screen buffer.
|
||||
// Arguments:
|
||||
// - useAltBuffer: if true, switch to the alt buffer, otherwise to the main buffer.
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
|
||||
[[nodiscard]] HRESULT VtEngine::_SwitchScreenBuffer(const bool useAltBuffer) noexcept
|
||||
{
|
||||
return _Write(useAltBuffer ? "\x1b[?1049h" : "\x1b[?1049l");
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Formats and writes a sequence to add a hyperlink to the terminal buffer
|
||||
// Arguments:
|
||||
// - The hyperlink URI
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
|
||||
[[nodiscard]] HRESULT VtEngine::_SetHyperlink(const std::wstring_view& uri, const std::wstring_view& customId, const uint16_t& numberId) noexcept
|
||||
{
|
||||
// Opening OSC8 sequence
|
||||
if (customId.empty())
|
||||
{
|
||||
// This is the case of auto-assigned IDs:
|
||||
// send the auto-assigned ID, prefixed with the PID of this session
|
||||
// (we do this so different conpty sessions do not overwrite each other's hyperlinks)
|
||||
const auto sessionID = GetCurrentProcessId();
|
||||
const auto uriStr = til::u16u8(uri);
|
||||
return _WriteFormatted(FMT_COMPILE("\x1b]8;id={}-{};{}\x1b\\"), sessionID, numberId, uriStr);
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is the case of user-defined IDs:
|
||||
// send the user-defined ID, prefixed with a "u"
|
||||
// (we do this so no application can accidentally override a user defined ID)
|
||||
const auto uriStr = til::u16u8(uri);
|
||||
const auto customIdStr = til::u16u8(customId);
|
||||
return _WriteFormatted(FMT_COMPILE("\x1b]8;id=u-{};{}\x1b\\"), customIdStr, uriStr);
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Formats and writes a sequence to end a hyperlink to the terminal buffer
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
|
||||
[[nodiscard]] HRESULT VtEngine::_EndHyperlink() noexcept
|
||||
{
|
||||
// Closing OSC8 sequence
|
||||
return _Write("\x1b]8;;\x1b\\");
|
||||
}
|
||||
@ -1,185 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
#include "Xterm256Engine.hpp"
|
||||
#pragma hdrstop
|
||||
using namespace Microsoft::Console;
|
||||
using namespace Microsoft::Console::Render;
|
||||
using namespace Microsoft::Console::Types;
|
||||
|
||||
Xterm256Engine::Xterm256Engine(_In_ wil::unique_hfile hPipe,
|
||||
const Viewport initialViewport) :
|
||||
XtermEngine(std::move(hPipe), initialViewport, false)
|
||||
{
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Write a VT sequence to change the current colors of text. Writes true RGB
|
||||
// color sequences.
|
||||
// Arguments:
|
||||
// - textAttributes - Text attributes to use for the colors and character rendition
|
||||
// - renderSettings - The color table and modes required for rendering
|
||||
// - pData - The interface to console data structures required for rendering
|
||||
// - usingSoftFont - Whether we're rendering characters from a soft font
|
||||
// - isSettingDefaultBrushes: indicates if we should change the background color of
|
||||
// the window. Unused for VT
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
|
||||
[[nodiscard]] HRESULT Xterm256Engine::UpdateDrawingBrushes(const TextAttribute& textAttributes,
|
||||
const RenderSettings& /*renderSettings*/,
|
||||
const gsl::not_null<IRenderData*> pData,
|
||||
const bool usingSoftFont,
|
||||
const bool isSettingDefaultBrushes) noexcept
|
||||
{
|
||||
RETURN_HR_IF(S_FALSE, _passthrough && isSettingDefaultBrushes);
|
||||
|
||||
RETURN_IF_FAILED(VtEngine::_RgbUpdateDrawingBrushes(textAttributes));
|
||||
|
||||
RETURN_IF_FAILED(_UpdateHyperlinkAttr(textAttributes, pData));
|
||||
|
||||
// If we're using a soft font, it should have already been mapped into the
|
||||
// G1 table, so we just need to switch between G0 and G1 when turning the
|
||||
// soft font on and off. We don't want to do this when setting the default
|
||||
// brushes, though, because that could result in an unnecessary G0 switch
|
||||
// at the start of every frame.
|
||||
if (usingSoftFont != _usingSoftFont && !isSettingDefaultBrushes)
|
||||
{
|
||||
RETURN_IF_FAILED(_Write(usingSoftFont ? "\x0E" : "\x0F"));
|
||||
_usingSoftFont = usingSoftFont;
|
||||
}
|
||||
|
||||
// Only do extended attributes in xterm-256color, as to not break telnet.exe.
|
||||
return _UpdateExtendedAttrs(textAttributes);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Write a VT sequence to update the character rendition attributes.
|
||||
// Arguments:
|
||||
// - textAttributes - text attributes (intense, italic, underline, etc.) to use.
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
|
||||
[[nodiscard]] HRESULT Xterm256Engine::_UpdateExtendedAttrs(const TextAttribute& textAttributes) noexcept
|
||||
{
|
||||
// Turning off Intense and Faint must be handled at the same time,
|
||||
// since there is only one sequence that resets both of them.
|
||||
const auto intenseTurnedOff = !textAttributes.IsIntense() && _lastTextAttributes.IsIntense();
|
||||
const auto faintTurnedOff = !textAttributes.IsFaint() && _lastTextAttributes.IsFaint();
|
||||
if (intenseTurnedOff || faintTurnedOff)
|
||||
{
|
||||
RETURN_IF_FAILED(_SetIntense(false));
|
||||
_lastTextAttributes.SetIntense(false);
|
||||
_lastTextAttributes.SetFaint(false);
|
||||
}
|
||||
|
||||
// Once we've handled the cases where they need to be turned off,
|
||||
// we can then check if either should be turned back on again.
|
||||
if (textAttributes.IsIntense() && !_lastTextAttributes.IsIntense())
|
||||
{
|
||||
RETURN_IF_FAILED(_SetIntense(true));
|
||||
_lastTextAttributes.SetIntense(true);
|
||||
}
|
||||
if (textAttributes.IsFaint() && !_lastTextAttributes.IsFaint())
|
||||
{
|
||||
RETURN_IF_FAILED(_SetFaint(true));
|
||||
_lastTextAttributes.SetFaint(true);
|
||||
}
|
||||
|
||||
// We check the singly, doubly underlined and extended styling together,
|
||||
// since only one of them can be active at a time.
|
||||
const auto ulStyle = textAttributes.GetUnderlineStyle();
|
||||
const auto lastUlStyle = _lastTextAttributes.GetUnderlineStyle();
|
||||
if (ulStyle != lastUlStyle)
|
||||
{
|
||||
// Reset doubly underlined if it was previously set. Avoids an edge case
|
||||
// where a pty client tracks doubly underlined and singly underlined separately,
|
||||
// and setting single underlined would leave the text doubly underlined
|
||||
// because it was never turned-off.
|
||||
if (lastUlStyle == UnderlineStyle::DoublyUnderlined && ulStyle != UnderlineStyle::NoUnderline)
|
||||
{
|
||||
RETURN_IF_FAILED(_SetUnderlined(false));
|
||||
}
|
||||
RETURN_IF_FAILED(_SetUnderlineExtended(ulStyle));
|
||||
_lastTextAttributes.SetUnderlineStyle(ulStyle);
|
||||
}
|
||||
|
||||
if (textAttributes.IsOverlined() != _lastTextAttributes.IsOverlined())
|
||||
{
|
||||
RETURN_IF_FAILED(_SetOverlined(textAttributes.IsOverlined()));
|
||||
_lastTextAttributes.SetOverlined(textAttributes.IsOverlined());
|
||||
}
|
||||
|
||||
if (textAttributes.IsItalic() != _lastTextAttributes.IsItalic())
|
||||
{
|
||||
RETURN_IF_FAILED(_SetItalic(textAttributes.IsItalic()));
|
||||
_lastTextAttributes.SetItalic(textAttributes.IsItalic());
|
||||
}
|
||||
|
||||
if (textAttributes.IsBlinking() != _lastTextAttributes.IsBlinking())
|
||||
{
|
||||
RETURN_IF_FAILED(_SetBlinking(textAttributes.IsBlinking()));
|
||||
_lastTextAttributes.SetBlinking(textAttributes.IsBlinking());
|
||||
}
|
||||
|
||||
if (textAttributes.IsInvisible() != _lastTextAttributes.IsInvisible())
|
||||
{
|
||||
RETURN_IF_FAILED(_SetInvisible(textAttributes.IsInvisible()));
|
||||
_lastTextAttributes.SetInvisible(textAttributes.IsInvisible());
|
||||
}
|
||||
|
||||
if (textAttributes.IsCrossedOut() != _lastTextAttributes.IsCrossedOut())
|
||||
{
|
||||
RETURN_IF_FAILED(_SetCrossedOut(textAttributes.IsCrossedOut()));
|
||||
_lastTextAttributes.SetCrossedOut(textAttributes.IsCrossedOut());
|
||||
}
|
||||
|
||||
if (textAttributes.IsReverseVideo() != _lastTextAttributes.IsReverseVideo())
|
||||
{
|
||||
RETURN_IF_FAILED(_SetReverseVideo(textAttributes.IsReverseVideo()));
|
||||
_lastTextAttributes.SetReverseVideo(textAttributes.IsReverseVideo());
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Write a VT sequence to start/stop a hyperlink
|
||||
// Arguments:
|
||||
// - textAttributes - Text attributes to use for the hyperlink ID
|
||||
// - pData - The interface to console data structures required for rendering
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
|
||||
HRESULT Microsoft::Console::Render::Xterm256Engine::_UpdateHyperlinkAttr(const TextAttribute& textAttributes,
|
||||
const gsl::not_null<IRenderData*> pData) noexcept
|
||||
{
|
||||
if (textAttributes.GetHyperlinkId() != _lastTextAttributes.GetHyperlinkId())
|
||||
{
|
||||
if (textAttributes.IsHyperlink())
|
||||
{
|
||||
const auto id = textAttributes.GetHyperlinkId();
|
||||
const auto customId = pData->GetHyperlinkCustomId(id);
|
||||
RETURN_IF_FAILED(_SetHyperlink(pData->GetHyperlinkUri(id), customId, id));
|
||||
}
|
||||
else
|
||||
{
|
||||
RETURN_IF_FAILED(_EndHyperlink());
|
||||
}
|
||||
_lastTextAttributes.SetHyperlinkId(textAttributes.GetHyperlinkId());
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Manually emit a "Erase Scrollback" sequence to the connected terminal. We
|
||||
// need to do this in certain cases that we've identified where we believe the
|
||||
// client wanted the entire terminal buffer cleared, not just the viewport.
|
||||
// For more information, see GH#3126.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - S_OK if we wrote the sequences successfully, otherwise an appropriate HRESULT
|
||||
[[nodiscard]] HRESULT Xterm256Engine::ManuallyClearScrollback() noexcept
|
||||
{
|
||||
return _ClearScrollback();
|
||||
}
|
||||
@ -1,49 +0,0 @@
|
||||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- Xterm256Engine.hpp
|
||||
|
||||
Abstract:
|
||||
- This is the definition of the VT specific implementation of the renderer.
|
||||
This is the xterm-256color implementation, which supports advanced sequences such as
|
||||
inserting and deleting lines, and true rgb color.
|
||||
|
||||
Author(s):
|
||||
- Mike Griese (migrie) 01-Sept-2017
|
||||
--*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "XtermEngine.hpp"
|
||||
|
||||
namespace Microsoft::Console::Render
|
||||
{
|
||||
class Xterm256Engine : public XtermEngine
|
||||
{
|
||||
public:
|
||||
Xterm256Engine(_In_ wil::unique_hfile hPipe,
|
||||
const Microsoft::Console::Types::Viewport initialViewport);
|
||||
|
||||
virtual ~Xterm256Engine() override = default;
|
||||
|
||||
[[nodiscard]] HRESULT UpdateDrawingBrushes(const TextAttribute& textAttributes,
|
||||
const RenderSettings& renderSettings,
|
||||
const gsl::not_null<IRenderData*> pData,
|
||||
const bool usingSoftFont,
|
||||
const bool isSettingDefaultBrushes) noexcept override;
|
||||
|
||||
[[nodiscard]] HRESULT ManuallyClearScrollback() noexcept override;
|
||||
|
||||
private:
|
||||
[[nodiscard]] HRESULT _UpdateExtendedAttrs(const TextAttribute& textAttributes) noexcept;
|
||||
[[nodiscard]] HRESULT _UpdateHyperlinkAttr(const TextAttribute& textAttributes,
|
||||
const gsl::not_null<IRenderData*> pData) noexcept;
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
friend class VtRendererTest;
|
||||
friend class ConptyOutputTests;
|
||||
#endif
|
||||
};
|
||||
}
|
||||
@ -1,576 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
#include "XtermEngine.hpp"
|
||||
#include "../../types/inc/convert.hpp"
|
||||
#pragma hdrstop
|
||||
using namespace Microsoft::Console;
|
||||
using namespace Microsoft::Console::Render;
|
||||
using namespace Microsoft::Console::Types;
|
||||
|
||||
XtermEngine::XtermEngine(_In_ wil::unique_hfile hPipe,
|
||||
const Viewport initialViewport,
|
||||
const bool fUseAsciiOnly) :
|
||||
VtEngine(std::move(hPipe), initialViewport),
|
||||
_fUseAsciiOnly(fUseAsciiOnly),
|
||||
_needToDisableCursor(false),
|
||||
// GH#12401: Ensure a DECTCEM cursor show/hide sequence
|
||||
// is emitted on the first frame no matter what.
|
||||
_lastCursorIsVisible(Tribool::Invalid),
|
||||
_nextCursorIsVisible(true)
|
||||
{
|
||||
// Set out initial cursor position to -1, -1. This will force our initial
|
||||
// paint to manually move the cursor to 0, 0, not just ignore it.
|
||||
_lastText = VtEngine::INVALID_COORDS;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Prepares internal structures for a painting operation. Turns the cursor
|
||||
// off, so we don't see it flashing all over the client's screen as we
|
||||
// paint the new contents.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - S_OK if we started to paint. S_FALSE if we didn't need to paint. HRESULT
|
||||
// error code if painting didn't start successfully, or we failed to write
|
||||
// the pipe.
|
||||
[[nodiscard]] HRESULT XtermEngine::StartPaint() noexcept
|
||||
{
|
||||
const auto hr = VtEngine::StartPaint();
|
||||
if (hr != S_OK)
|
||||
{
|
||||
// If _noFlushOnEnd was set, and we didn't return early, it would usually
|
||||
// have been reset in the EndPaint call. But since that's not going to
|
||||
// happen now, we need to reset it here, otherwise we may mistakenly skip
|
||||
// the flush on the next frame.
|
||||
if (!_noFlushOnEnd)
|
||||
{
|
||||
_Flush();
|
||||
}
|
||||
_noFlushOnEnd = false;
|
||||
return hr;
|
||||
}
|
||||
|
||||
_trace.TraceLastText(_lastText);
|
||||
|
||||
// Prep us to think that the cursor is not visible this frame. If it _is_
|
||||
// visible, then PaintCursor will be called, and we'll set this to true
|
||||
// during the frame.
|
||||
_nextCursorIsVisible = false;
|
||||
_startOfFrameBufferIndex = _buffer.size();
|
||||
|
||||
// Do not perform synchronization clearing in passthrough mode.
|
||||
// In passthrough, the terminal leads and we follow what it is
|
||||
// handling from the client application.
|
||||
// (This is in contrast to full PTY mode where WE, the ConPTY, lead and
|
||||
// it follows our state.)
|
||||
if (_passthrough)
|
||||
{
|
||||
_firstPaint = false;
|
||||
}
|
||||
|
||||
if (_firstPaint)
|
||||
{
|
||||
// MSFT:17815688
|
||||
// If the caller requested to inherit the cursor, we shouldn't
|
||||
// clear the screen on the first paint. Otherwise, we'll clear
|
||||
// the screen on the first paint, just to make sure that the
|
||||
// terminal's state is consistent with what we'll be rendering.
|
||||
RETURN_IF_FAILED(_ClearScreen());
|
||||
_clearedAllThisFrame = true;
|
||||
_firstPaint = false;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - EndPaint helper to perform the final rendering steps. Turn the cursor back
|
||||
// on.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
|
||||
[[nodiscard]] HRESULT XtermEngine::EndPaint() noexcept
|
||||
{
|
||||
// If during the frame we determined that the cursor needed to be disabled,
|
||||
// then insert a cursor off at the start of the buffer, and re-enable
|
||||
// the cursor here.
|
||||
if (_needToDisableCursor)
|
||||
{
|
||||
// If the cursor was previously visible, let's hide it for this frame,
|
||||
// by prepending a cursor off.
|
||||
if (_lastCursorIsVisible != Tribool::False)
|
||||
{
|
||||
_buffer.insert(_startOfFrameBufferIndex, "\x1b[?25l");
|
||||
_lastCursorIsVisible = Tribool::False;
|
||||
}
|
||||
// If the cursor was NOT previously visible, then that's fine! we don't
|
||||
// need to worry, it's already off.
|
||||
}
|
||||
|
||||
if (_lastCursorIsVisible != static_cast<Tribool>(_nextCursorIsVisible))
|
||||
{
|
||||
RETURN_IF_FAILED(_nextCursorIsVisible ? _ShowCursor() : _HideCursor());
|
||||
_lastCursorIsVisible = static_cast<Tribool>(_nextCursorIsVisible);
|
||||
}
|
||||
|
||||
RETURN_IF_FAILED(VtEngine::EndPaint());
|
||||
|
||||
_needToDisableCursor = false;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Write a VT sequence to change the current colors of text. Only writes
|
||||
// 16-color attributes.
|
||||
// Arguments:
|
||||
// - textAttributes - Text attributes to use for the colors and character rendition
|
||||
// - renderSettings - The color table and modes required for rendering
|
||||
// - pData - The interface to console data structures required for rendering
|
||||
// - usingSoftFont - Whether we're rendering characters from a soft font
|
||||
// - isSettingDefaultBrushes: indicates if we should change the background color of
|
||||
// the window. Unused for VT
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
|
||||
[[nodiscard]] HRESULT XtermEngine::UpdateDrawingBrushes(const TextAttribute& textAttributes,
|
||||
const RenderSettings& /*renderSettings*/,
|
||||
const gsl::not_null<IRenderData*> /*pData*/,
|
||||
const bool /*usingSoftFont*/,
|
||||
const bool /*isSettingDefaultBrushes*/) noexcept
|
||||
{
|
||||
// The base xterm mode only knows about 16 colors
|
||||
RETURN_IF_FAILED(VtEngine::_16ColorUpdateDrawingBrushes(textAttributes));
|
||||
|
||||
// And the only supported meta attributes are reverse video and underline
|
||||
if (textAttributes.IsReverseVideo() != _lastTextAttributes.IsReverseVideo())
|
||||
{
|
||||
RETURN_IF_FAILED(_SetReverseVideo(textAttributes.IsReverseVideo()));
|
||||
_lastTextAttributes.SetReverseVideo(textAttributes.IsReverseVideo());
|
||||
}
|
||||
if (textAttributes.IsUnderlined() != _lastTextAttributes.IsUnderlined())
|
||||
{
|
||||
RETURN_IF_FAILED(_SetUnderlined(textAttributes.IsUnderlined()));
|
||||
_lastTextAttributes.SetUnderlineStyle(textAttributes.GetUnderlineStyle());
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Draws the cursor on the screen
|
||||
// Arguments:
|
||||
// - options - Options that affect the presentation of the cursor
|
||||
// Return Value:
|
||||
// - S_OK or suitable HRESULT error from writing pipe.
|
||||
[[nodiscard]] HRESULT XtermEngine::PaintCursor(const CursorOptions& options) noexcept
|
||||
{
|
||||
// PaintCursor is only called when the cursor is in fact visible in a single
|
||||
// frame. When this is called, mark _nextCursorIsVisible as true. At the end
|
||||
// of the frame, we'll decide to either turn the cursor on or not, based
|
||||
// upon the previous state.
|
||||
|
||||
// When this method is not called during a frame, it's because the cursor
|
||||
// was not visible. In that case, at the end of the frame,
|
||||
// _nextCursorIsVisible will still be false (from when we set it during
|
||||
// StartPaint)
|
||||
_nextCursorIsVisible = true;
|
||||
|
||||
// If we did a delayed EOL wrap because we actually wrapped the line here,
|
||||
// then don't PaintCursor. When we're at the EOL because we've wrapped, our
|
||||
// internal _lastText thinks the cursor is on the cell just past the right
|
||||
// of the viewport (ex { 120, 0 }). However, conhost thinks the cursor is
|
||||
// actually on the last cell of the row. So it'll tell us to paint the
|
||||
// cursor at { 119, 0 }. If we do that movement, then we'll break line
|
||||
// wrapping.
|
||||
// See GH#5113, GH#1245, GH#357
|
||||
const auto nextCursorPosition = options.coordCursor;
|
||||
// Only skip this paint when we think the cursor is in the cell
|
||||
// immediately off the edge of the terminal, and the actual cursor is in
|
||||
// the last cell of the row. We're in a deferred wrap, but the host
|
||||
// thinks the cursor is actually in-frame.
|
||||
// See ConptyRoundtripTests::DontWrapMoveCursorInSingleFrame
|
||||
const auto cursorIsInDeferredWrap = (nextCursorPosition.x == _lastText.x - 1) && (nextCursorPosition.y == _lastText.y);
|
||||
// If all three of these conditions are true, then:
|
||||
// * cursorIsInDeferredWrap: The cursor is in a position where the line
|
||||
// filled the last cell of the row, but the host tried to paint it in
|
||||
// the last cell anyways
|
||||
// - GH#5691 - If we're painting the frame because we circled the
|
||||
// buffer, then the cursor might still be in the position it was
|
||||
// before the text was written to the buffer to cause the buffer to
|
||||
// circle. In that case, then we DON'T want to paint the cursor here
|
||||
// either, because it'll cause us to manually break this line. That's
|
||||
// okay though, the frame will be painted again, after the circling
|
||||
// is complete.
|
||||
// * _delayedEolWrap && _wrappedRow.has_value(): We think we've deferred
|
||||
// the wrap of a line.
|
||||
// If they're all true, DON'T manually paint the cursor this frame.
|
||||
if (!((cursorIsInDeferredWrap || _circled) && _delayedEolWrap && _wrappedRow.has_value()))
|
||||
{
|
||||
return VtEngine::PaintCursor(options);
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Write a VT sequence to move the cursor to the specified coordinates. We
|
||||
// also store the last place we left the cursor for future optimizations.
|
||||
// If the cursor only needs to go to the origin, only write the home sequence.
|
||||
// If the new cursor is only down one line from the current, only write a newline
|
||||
// If the new cursor is only down one line and at the start of the line, write
|
||||
// a carriage return.
|
||||
// Otherwise just write the whole sequence for moving it.
|
||||
// Arguments:
|
||||
// - coord: location to move the cursor to.
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
|
||||
[[nodiscard]] HRESULT XtermEngine::_MoveCursor(const til::point coord) noexcept
|
||||
{
|
||||
auto hr = S_OK;
|
||||
const auto originalPos = _lastText;
|
||||
_trace.TraceMoveCursor(_lastText, coord);
|
||||
if (coord.x != _lastText.x || coord.y != _lastText.y)
|
||||
{
|
||||
if (coord.x == 0 && coord.y == 0)
|
||||
{
|
||||
_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.
|
||||
|
||||
// If the previous line wrapped, then the cursor is already at this
|
||||
// position, we just don't know it yet. Don't emit anything.
|
||||
auto previousLineWrapped = false;
|
||||
if (_wrappedRow.has_value())
|
||||
{
|
||||
previousLineWrapped = coord.y == _wrappedRow.value() + 1;
|
||||
}
|
||||
|
||||
if (previousLineWrapped)
|
||||
{
|
||||
_trace.TraceWrapped();
|
||||
hr = S_OK;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string seq = "\r\n";
|
||||
hr = _Write(seq);
|
||||
}
|
||||
}
|
||||
else if (_delayedEolWrap)
|
||||
{
|
||||
// GH#1245, GH#357 - If we were in the delayed EOL wrap state, make
|
||||
// sure to _manually_ position the cursor now, with a full CUP
|
||||
// sequence, don't try and be clever with \b or \r or other control
|
||||
// sequences. Different terminals (conhost, gnome-terminal, wt) all
|
||||
// behave differently with how the cursor behaves at an end of line.
|
||||
// This is the only solution that works in all of them, and also
|
||||
// works wrapped lines emitted by conpty.
|
||||
//
|
||||
// Make sure to do this _after_ the possible \r\n branch above,
|
||||
// otherwise we might accidentally break wrapped lines (GH#405)
|
||||
hr = _CursorPosition(coord);
|
||||
}
|
||||
else if (coord.x == 0 && coord.y == _lastText.y)
|
||||
{
|
||||
// Start of this line
|
||||
std::string seq = "\r";
|
||||
hr = _Write(seq);
|
||||
}
|
||||
else if (coord.x == _lastText.x && coord.y == (_lastText.y + 1))
|
||||
{
|
||||
// Down one line, same X position
|
||||
std::string seq = "\n";
|
||||
hr = _Write(seq);
|
||||
}
|
||||
else if (coord.x == (_lastText.x - 1) && coord.y == (_lastText.y))
|
||||
{
|
||||
// Back one char, same Y position
|
||||
std::string seq = "\b";
|
||||
hr = _Write(seq);
|
||||
}
|
||||
else if (coord.y == _lastText.y && coord.x > _lastText.x)
|
||||
{
|
||||
// Same line, forward some distance
|
||||
auto distance = coord.x - _lastText.x;
|
||||
hr = _CursorForward(distance);
|
||||
}
|
||||
else
|
||||
{
|
||||
_needToDisableCursor = true;
|
||||
hr = _CursorPosition(coord);
|
||||
}
|
||||
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
_lastText = coord;
|
||||
}
|
||||
}
|
||||
|
||||
_deferredCursorPos = INVALID_COORDS;
|
||||
|
||||
_wrappedRow = std::nullopt;
|
||||
_delayedEolWrap = false;
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Scrolls the existing data on the in-memory frame by the scroll region
|
||||
// deltas we have collectively received through the Invalidate methods
|
||||
// since the last time this was called.
|
||||
// Move the cursor to the origin, and insert or delete rows as appropriate.
|
||||
// The inserted rows will be blank, but marked invalid by InvalidateScroll,
|
||||
// so they will later be written by PaintBufferLine.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
|
||||
[[nodiscard]] HRESULT XtermEngine::ScrollFrame() noexcept
|
||||
try
|
||||
{
|
||||
_trace.TraceScrollFrame(_scrollDelta);
|
||||
|
||||
if (_scrollDelta.x != 0)
|
||||
{
|
||||
// No easy way to shift left-right. Everything needs repainting.
|
||||
return InvalidateAll();
|
||||
}
|
||||
if (_scrollDelta.y == 0)
|
||||
{
|
||||
// There's nothing to do here. Do nothing.
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
const auto dy = _scrollDelta.y;
|
||||
const auto absDy = abs(dy);
|
||||
|
||||
// Save the old wrap state here. We're going to clear it so that
|
||||
// _MoveCursor will definitely move us to the right position. We'll
|
||||
// restore the state afterwards.
|
||||
const auto oldWrappedRow = _wrappedRow;
|
||||
const auto oldDelayedEolWrap = _delayedEolWrap;
|
||||
_delayedEolWrap = false;
|
||||
_wrappedRow = std::nullopt;
|
||||
|
||||
if (dy < 0)
|
||||
{
|
||||
// TODO GH#5228 - We could optimize this by only doing this newline work
|
||||
// when there's more invalid than just the bottom line. If only the
|
||||
// bottom line is invalid, then the next thing the Renderer is going to
|
||||
// tell us to do is print the new line at the bottom of the viewport,
|
||||
// and _MoveCursor will automatically give us the newline we want.
|
||||
// When that's implemented, we'll probably want to make sure to add a
|
||||
// _lastText.y += dy;
|
||||
// statement here.
|
||||
|
||||
// Move the cursor to the bottom of the current viewport
|
||||
const auto bottom = _lastViewport.BottomInclusive();
|
||||
RETURN_IF_FAILED(_MoveCursor({ 0, bottom }));
|
||||
// Emit some number of newlines to create space in the buffer.
|
||||
RETURN_IF_FAILED(_Write(std::string(absDy, '\n')));
|
||||
}
|
||||
else if (dy > 0)
|
||||
{
|
||||
// If we've scrolled _down_, then move the cursor to the top of the
|
||||
// buffer, and insert some newlines using the InsertLines VT sequence
|
||||
RETURN_IF_FAILED(_MoveCursor({ 0, 0 }));
|
||||
RETURN_IF_FAILED(_InsertLine(absDy));
|
||||
}
|
||||
|
||||
// Restore our wrap state.
|
||||
_wrappedRow = oldWrappedRow;
|
||||
_delayedEolWrap = oldDelayedEolWrap;
|
||||
|
||||
// Shift our internal tracker of the last text position according to how
|
||||
// much we've scrolled. If we manually scroll the buffer right now, by
|
||||
// moving the cursor to the bottom row of the viewport and emitting a
|
||||
// newline, we'll cause any wrapped lines to get broken.
|
||||
//
|
||||
// Instead, we'll just update our internal tracker of where the buffer
|
||||
// contents are. On this frame, we'll then still move the cursor correctly
|
||||
// relative to the new frame contents. To do this, we'll shift our
|
||||
// coordinates we're tracking, like the row that we wrapped on and the
|
||||
// position we think we left the cursor.
|
||||
//
|
||||
// See GH#5113
|
||||
_trace.TraceLastText(_lastText);
|
||||
if (_wrappedRow.has_value())
|
||||
{
|
||||
_wrappedRow.value() += dy;
|
||||
_trace.TraceSetWrapped(_wrappedRow.value());
|
||||
}
|
||||
|
||||
if (_delayedEolWrap && _wrappedRow.has_value())
|
||||
{
|
||||
// If we wrapped the last line, and we're in the middle of painting it,
|
||||
// then the newline we did above just manually broke the line. What
|
||||
// we're doing here is a hack: we're going to manually re-invalidate the
|
||||
// last character of the wrapped row. When the PaintBufferLine calls
|
||||
// come back through, we'll paint this last character again, causing us
|
||||
// to get into the wrapped state once again. This is the only way to
|
||||
// ensure that if a line was wrapped, and we painted the first line in
|
||||
// one frame, and the second line in another frame that included other
|
||||
// changes _above_ the wrapped line, that we maintain the wrap state in
|
||||
// the Terminal.
|
||||
const til::rect lastCellOfWrappedRow{
|
||||
til::point{ _lastViewport.RightInclusive(), _wrappedRow.value() },
|
||||
til::size{ 1, 1 }
|
||||
};
|
||||
_trace.TraceInvalidate(lastCellOfWrappedRow);
|
||||
_invalidMap.set(lastCellOfWrappedRow);
|
||||
}
|
||||
|
||||
// If the entire viewport was invalidated this frame, don't mark the bottom
|
||||
// line as new. There are cases where this can cause visual artifacts - see
|
||||
// GH#5039 and ConptyRoundtripTests::ClearHostTrickeryTest
|
||||
const auto allInvalidated = _invalidMap.all();
|
||||
_newBottomLine = !allInvalidated;
|
||||
|
||||
// GH#5502 - keep track of the BG color we had when we emitted this new
|
||||
// bottom line. If the color changes by the time we get to printing that
|
||||
// line, we'll need to make sure that we don't do any optimizations like
|
||||
// _removing spaces_, because the background color of the spaces will be
|
||||
// important information to send to the connected Terminal.
|
||||
if (_newBottomLine)
|
||||
{
|
||||
_newBottomLineBG = _lastTextAttributes.GetBackground();
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
|
||||
// Routine Description:
|
||||
// - Notifies us that the console is attempting to scroll the existing screen
|
||||
// area. Add the top or bottom rows to the invalid region, and update the
|
||||
// total scroll delta accumulated this frame.
|
||||
// Arguments:
|
||||
// - pcoordDelta - Pointer to character dimension (til::point) of the distance the
|
||||
// console would like us to move while scrolling.
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, else an appropriate HRESULT for safemath failure
|
||||
[[nodiscard]] HRESULT XtermEngine::InvalidateScroll(const til::point* const pcoordDelta) noexcept
|
||||
try
|
||||
{
|
||||
const auto delta{ *pcoordDelta };
|
||||
|
||||
if (delta != til::point{ 0, 0 })
|
||||
{
|
||||
_trace.TraceInvalidateScroll(delta);
|
||||
|
||||
// Scroll the current offset and invalidate the revealed area
|
||||
_invalidMap.translate(delta, true);
|
||||
|
||||
_scrollDelta += delta;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
|
||||
// Routine Description:
|
||||
// - Draws one line of the buffer to the screen. Writes the characters to the
|
||||
// pipe, encoded in UTF-8 or ASCII only, depending on the VtIoMode.
|
||||
// (See descriptions of both implementations for details.)
|
||||
// Arguments:
|
||||
// - clusters - text and column counts for each piece of text.
|
||||
// - coord - character coordinate target to render within viewport
|
||||
// - trimLeft - This specifies whether to trim one character width off the left
|
||||
// side of the output. Used for drawing the right-half only of a
|
||||
// double-wide character.
|
||||
// - lineWrapped: true if this run we're painting is the end of a line that
|
||||
// wrapped. If we're not painting the last column of a wrapped line, then this
|
||||
// will be false.
|
||||
// Return Value:
|
||||
// - S_OK or suitable HRESULT error from writing pipe.
|
||||
[[nodiscard]] HRESULT XtermEngine::PaintBufferLine(const std::span<const Cluster> clusters,
|
||||
const til::point coord,
|
||||
const bool /*trimLeft*/,
|
||||
const bool lineWrapped) noexcept
|
||||
{
|
||||
return _fUseAsciiOnly ?
|
||||
VtEngine::_PaintAsciiBufferLine(clusters, coord) :
|
||||
VtEngine::_PaintUtf8BufferLine(clusters, coord, lineWrapped);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Wrapper for _Write. Write either an ascii-only, or a
|
||||
// proper utf-8 string, depending on our mode.
|
||||
// Arguments:
|
||||
// - wstr - wstring of text to be written
|
||||
// - flush - set to true if the string should be flushed immediately
|
||||
// Return Value:
|
||||
// - S_OK or suitable HRESULT error from either conversion or writing pipe.
|
||||
[[nodiscard]] HRESULT XtermEngine::WriteTerminalW(const std::wstring_view wstr, const bool flush) noexcept
|
||||
{
|
||||
RETURN_IF_FAILED(_fUseAsciiOnly ?
|
||||
VtEngine::_WriteTerminalAscii(wstr) :
|
||||
VtEngine::_WriteTerminalUtf8(wstr));
|
||||
// GH#4106, GH#2011, GH#13710 - WriteTerminalW is only ever called by the
|
||||
// StateMachine, when we've encountered a string we don't understand. When
|
||||
// this happens, we will trigger a new frame in the renderer, and
|
||||
// immediately buffer this wstr (representing the sequence we didn't
|
||||
// understand). We won't immediately _Flush to the terminal - that might
|
||||
// cause flickering (where we've buffered some state but not the whole
|
||||
// "frame" as specified by the app). We'll just immediately buffer this
|
||||
// sequence, and flush it when the render thread comes around to paint the
|
||||
// frame normally, unless a flush has been explicitly requested.
|
||||
if (flush)
|
||||
{
|
||||
_flushImpl();
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Sends a command to set the terminal's window to visible or hidden
|
||||
// Arguments:
|
||||
// - showOrHide - True if show; false if hide.
|
||||
// Return Value:
|
||||
// - S_OK or suitable HRESULT error from either conversion or writing pipe.
|
||||
[[nodiscard]] HRESULT XtermEngine::SetWindowVisibility(const bool showOrHide) noexcept
|
||||
{
|
||||
if (showOrHide)
|
||||
{
|
||||
RETURN_IF_FAILED(_Write("\x1b[1t"));
|
||||
}
|
||||
else
|
||||
{
|
||||
RETURN_IF_FAILED(_Write("\x1b[2t"));
|
||||
}
|
||||
_Flush();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Updates the window's title string. Emits the VT sequence to SetWindowTitle.
|
||||
// Arguments:
|
||||
// - newTitle: the new string to use for the title of the window
|
||||
// Return Value:
|
||||
// - S_OK
|
||||
[[nodiscard]] HRESULT XtermEngine::_DoUpdateTitle(const std::wstring_view newTitle) noexcept
|
||||
{
|
||||
// inbox telnet uses xterm-ascii as its mode. If we're in ascii mode, don't
|
||||
// do anything, to maintain compatibility.
|
||||
if (_fUseAsciiOnly)
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
const auto converted = ConvertToA(CP_UTF8, newTitle);
|
||||
return VtEngine::_ChangeTitle(converted);
|
||||
}
|
||||
CATCH_RETURN();
|
||||
}
|
||||
@ -1,82 +0,0 @@
|
||||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- XtermEngine.hpp
|
||||
|
||||
Abstract:
|
||||
- This is the definition of the VT specific implementation of the renderer.
|
||||
This is the xterm implementation, which supports advanced sequences such as
|
||||
inserting and deleting lines, but only 16 colors.
|
||||
|
||||
This engine supports both xterm and xterm-ascii VT modes.
|
||||
The difference being that xterm-ascii will render any characters above 0x7f
|
||||
as '?', in order to support older legacy tools.
|
||||
|
||||
Author(s):
|
||||
- Mike Griese (migrie) 01-Sept-2017
|
||||
--*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "vtrenderer.hpp"
|
||||
|
||||
namespace Microsoft::Console::Render
|
||||
{
|
||||
class XtermEngine : public VtEngine
|
||||
{
|
||||
public:
|
||||
XtermEngine(_In_ wil::unique_hfile hPipe,
|
||||
const Microsoft::Console::Types::Viewport initialViewport,
|
||||
const bool fUseAsciiOnly);
|
||||
|
||||
virtual ~XtermEngine() override = default;
|
||||
|
||||
[[nodiscard]] HRESULT StartPaint() noexcept override;
|
||||
[[nodiscard]] HRESULT EndPaint() noexcept override;
|
||||
|
||||
[[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override;
|
||||
|
||||
[[nodiscard]] virtual HRESULT UpdateDrawingBrushes(const TextAttribute& textAttributes,
|
||||
const RenderSettings& renderSettings,
|
||||
const gsl::not_null<IRenderData*> pData,
|
||||
const bool usingSoftFont,
|
||||
const bool isSettingDefaultBrushes) noexcept override;
|
||||
[[nodiscard]] HRESULT PaintBufferLine(const std::span<const Cluster> clusters,
|
||||
const til::point coord,
|
||||
const bool trimLeft,
|
||||
const bool lineWrapped) noexcept override;
|
||||
[[nodiscard]] HRESULT ScrollFrame() noexcept override;
|
||||
|
||||
[[nodiscard]] HRESULT InvalidateScroll(const til::point* const pcoordDelta) noexcept override;
|
||||
|
||||
[[nodiscard]] HRESULT WriteTerminalW(const std::wstring_view str, const bool flush) noexcept override;
|
||||
|
||||
[[nodiscard]] HRESULT SetWindowVisibility(const bool showOrHide) noexcept override;
|
||||
|
||||
protected:
|
||||
// I'm using a non-class enum here, so that the values
|
||||
// are trivially convertible and comparable to bool.
|
||||
enum class Tribool : uint8_t
|
||||
{
|
||||
False = 0,
|
||||
True,
|
||||
Invalid,
|
||||
};
|
||||
|
||||
const bool _fUseAsciiOnly;
|
||||
bool _needToDisableCursor;
|
||||
Tribool _lastCursorIsVisible;
|
||||
bool _nextCursorIsVisible;
|
||||
|
||||
[[nodiscard]] HRESULT _MoveCursor(const til::point coord) noexcept override;
|
||||
|
||||
[[nodiscard]] HRESULT _DoUpdateTitle(const std::wstring_view newTitle) noexcept override;
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
friend class VtRendererTest;
|
||||
friend class ConptyOutputTests;
|
||||
#endif
|
||||
};
|
||||
}
|
||||
@ -1,3 +0,0 @@
|
||||
DIRS= \
|
||||
lib \
|
||||
ut_lib \
|
||||
@ -1,144 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
|
||||
#include "vtrenderer.hpp"
|
||||
|
||||
using namespace Microsoft::Console::Types;
|
||||
#pragma hdrstop
|
||||
|
||||
using namespace Microsoft::Console::Render;
|
||||
|
||||
// Routine Description:
|
||||
// - Notifies us that the system has requested a particular pixel area of the
|
||||
// client rectangle should be redrawn. (On WM_PAINT)
|
||||
// For VT, this doesn't mean anything. So do nothing.
|
||||
// Arguments:
|
||||
// - prcDirtyClient - Pointer to pixel area (til::rect) of client region the system
|
||||
// believes is dirty
|
||||
// Return Value:
|
||||
// - S_OK
|
||||
[[nodiscard]] HRESULT VtEngine::InvalidateSystem(const til::rect* const /*prcDirtyClient*/) noexcept
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Notifies us that the console has changed the selection region and would
|
||||
// like it updated
|
||||
// Arguments:
|
||||
// - rectangles - Vector of rectangles to draw, line by line
|
||||
// Return Value:
|
||||
// - S_OK
|
||||
[[nodiscard]] HRESULT VtEngine::InvalidateSelection(const std::vector<til::rect>& /*rectangles*/) noexcept
|
||||
{
|
||||
// Selection shouldn't be handled bt the VT Renderer Host, it should be
|
||||
// handled by the client.
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Notifies us that the console has changed the character region specified.
|
||||
// - NOTE: This typically triggers on cursor or text buffer changes
|
||||
// Arguments:
|
||||
// - psrRegion - Character region (til::rect) that has been changed
|
||||
// Return Value:
|
||||
// - S_OK, else an appropriate HRESULT for failing to allocate or write.
|
||||
[[nodiscard]] HRESULT VtEngine::Invalidate(const til::rect* const psrRegion) noexcept
|
||||
try
|
||||
{
|
||||
_trace.TraceInvalidate(*psrRegion);
|
||||
_invalidMap.set(*psrRegion);
|
||||
return S_OK;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
|
||||
// Routine Description:
|
||||
// - Notifies us that the console has changed the position of the cursor.
|
||||
// Arguments:
|
||||
// - psrRegion - the region covered by the cursor
|
||||
// Return Value:
|
||||
// - S_OK
|
||||
[[nodiscard]] HRESULT VtEngine::InvalidateCursor(const til::rect* const psrRegion) noexcept
|
||||
{
|
||||
// If we just inherited the cursor, we're going to get an InvalidateCursor
|
||||
// for both where the old cursor was, and where the new cursor is
|
||||
// (the inherited location). (See Cursor.cpp:Cursor::SetPosition)
|
||||
// We should ignore the first one, but after that, if the client application
|
||||
// is moving the cursor around in the viewport, move our virtual top
|
||||
// up to meet their changes.
|
||||
if (!_skipCursor && _virtualTop > psrRegion->top)
|
||||
{
|
||||
_virtualTop = psrRegion->top;
|
||||
}
|
||||
_skipCursor = false;
|
||||
|
||||
_cursorMoved = psrRegion->origin() != _lastCursorOrigin;
|
||||
_lastCursorOrigin = psrRegion->origin();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Notifies to repaint everything.
|
||||
// - NOTE: Use sparingly. Only use when something that could affect the entire
|
||||
// frame simultaneously occurs.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - S_OK, else an appropriate HRESULT for failing to allocate or write.
|
||||
[[nodiscard]] HRESULT VtEngine::InvalidateAll() noexcept
|
||||
try
|
||||
{
|
||||
_trace.TraceInvalidateAll(_lastViewport.ToOrigin().ToExclusive());
|
||||
_invalidMap.set_all();
|
||||
return S_OK;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
|
||||
// Method Description:
|
||||
// - Notifies us that we're about to circle the buffer, giving us a chance to
|
||||
// force a repaint before the buffer contents are lost. The VT renderer
|
||||
// needs to be able to render all text before it's lost, so we return true.
|
||||
// Arguments:
|
||||
// - Receives a bool indicating if we should force the repaint.
|
||||
// Return Value:
|
||||
// - S_OK
|
||||
[[nodiscard]] HRESULT VtEngine::InvalidateFlush(_In_ const bool circled, _Out_ bool* const pForcePaint) noexcept
|
||||
{
|
||||
*pForcePaint = true;
|
||||
|
||||
// Keep track of the fact that we circled, we'll need to do some work on
|
||||
// end paint to specifically handle this.
|
||||
_circled = circled;
|
||||
|
||||
// If we flushed for any reason other than circling (i.e, a sequence that we
|
||||
// didn't understand), we don't need to push the buffer out on EndPaint.
|
||||
_noFlushOnEnd = !circled;
|
||||
|
||||
_trace.TraceTriggerCircling(*pForcePaint);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Notifies us that we're about to be torn down. This gives us a last chance
|
||||
// to force a repaint before the buffer contents are lost. The VT renderer
|
||||
// needs to be able to render all text before it's lost, so we return true.
|
||||
// Arguments:
|
||||
// - Receives a bool indicating if we should force the repaint.
|
||||
// Return Value:
|
||||
// - S_OK
|
||||
[[nodiscard]] HRESULT VtEngine::PrepareForTeardown(_Out_ bool* const pForcePaint) noexcept
|
||||
{
|
||||
// This must be kept in sync with RequestWin32Input().
|
||||
// It ensures that we disable the modes that we requested on startup.
|
||||
// Linux shells for instance don't understand the win32-input-mode 9001.
|
||||
//
|
||||
// This can be here, instead of being appended at the end of this final rendering pass,
|
||||
// because these two states happen to have no influence on the caller's VT parsing.
|
||||
std::ignore = _Write("\033[?9001l\033[?1004l");
|
||||
|
||||
*pForcePaint = true;
|
||||
return S_OK;
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
!include ..\sources.inc
|
||||
|
||||
# -------------------------------------
|
||||
# Program Information
|
||||
# -------------------------------------
|
||||
|
||||
TARGETNAME = ConRenderVt
|
||||
TARGETTYPE = LIBRARY
|
||||
@ -1,3 +0,0 @@
|
||||
BUILD_PASS1_CONSUMES= \
|
||||
onecore\windows\vcpkg|PASS1 \
|
||||
|
||||
@ -1,18 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<ProjectGuid>{990F2657-8580-4828-943F-5DD657D11842}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>vt</RootNamespace>
|
||||
<ProjectName>RendererVt</ProjectName>
|
||||
<TargetName>ConRenderVt</TargetName>
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(SolutionDir)src\common.build.pre.props"/>
|
||||
<Import Project="$(SolutionDir)src\common.nugetversions.props"/>
|
||||
<!-- DONT ADD NEW FILES HERE, ADD THEM TO vt-renderer-common.vcxitems -->
|
||||
<Import Project="$(SolutionDir)src\renderer\vt\vt-renderer-common.vcxitems" />
|
||||
<!-- Careful reordering these. Some default props (contained in these files) are order sensitive. -->
|
||||
<Import Project="$(SolutionDir)src\common.build.post.props"/>
|
||||
<Import Project="$(SolutionDir)src\common.nugetversions.targets"/>
|
||||
</Project>
|
||||
@ -1,42 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\invalidate.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\math.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\paint.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\state.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\precomp.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\gdirenderer.hpp">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\precomp.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@ -1,54 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
|
||||
#include "vtrenderer.hpp"
|
||||
|
||||
#pragma hdrstop
|
||||
|
||||
using namespace Microsoft::Console::Render;
|
||||
using namespace Microsoft::Console::Types;
|
||||
|
||||
// Routine Description:
|
||||
// - Gets the size in characters of the current dirty portion of the frame.
|
||||
// Arguments:
|
||||
// - area - The character dimensions of the current dirty area of the frame.
|
||||
// This is an Inclusive rect.
|
||||
// Return Value:
|
||||
// - S_OK.
|
||||
[[nodiscard]] HRESULT VtEngine::GetDirtyArea(std::span<const til::rect>& area) noexcept
|
||||
{
|
||||
area = _invalidMap.runs();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Uses the currently selected font to determine how wide the given character will be when rendered.
|
||||
// - NOTE: Only supports determining half-width/full-width status for CJK-type languages (e.g. is it 1 character wide or 2. a.k.a. is it a rectangle or square.)
|
||||
// Arguments:
|
||||
// - glyph - utf16 encoded codepoint to check
|
||||
// - pResult - receives return value, True if it is full-width (2 wide). False if it is half-width (1 wide).
|
||||
// Return Value:
|
||||
// - S_FALSE: This is unsupported by the VT Renderer and should use another engine's value.
|
||||
[[nodiscard]] HRESULT VtEngine::IsGlyphWideByFont(const std::wstring_view /*glyph*/, _Out_ bool* const pResult) noexcept
|
||||
{
|
||||
*pResult = false;
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Performs a "CombineRect" with the "OR" operation.
|
||||
// - Basically extends the existing rect outward to also encompass the passed-in region.
|
||||
// Arguments:
|
||||
// - pRectExisting - Expand this rectangle to encompass the add rect.
|
||||
// - pRectToOr - Add this rectangle to the existing one.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void VtEngine::_OrRect(_Inout_ til::inclusive_rect* const pRectExisting, const til::inclusive_rect* const pRectToOr) const
|
||||
{
|
||||
pRectExisting->left = std::min(pRectExisting->left, pRectToOr->left);
|
||||
pRectExisting->top = std::min(pRectExisting->top, pRectToOr->top);
|
||||
pRectExisting->right = std::max(pRectExisting->right, pRectToOr->right);
|
||||
pRectExisting->bottom = std::max(pRectExisting->bottom, pRectToOr->bottom);
|
||||
}
|
||||
@ -1,722 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
|
||||
#include "vtrenderer.hpp"
|
||||
#include "../../inc/conattrs.hpp"
|
||||
#include "../../types/inc/convert.hpp"
|
||||
|
||||
#pragma hdrstop
|
||||
using namespace Microsoft::Console::Render;
|
||||
using namespace Microsoft::Console::Types;
|
||||
|
||||
// Routine Description:
|
||||
// - Prepares internal structures for a painting operation.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - S_OK if we started to paint. S_FALSE if we didn't need to paint.
|
||||
// HRESULT error code if painting didn't start successfully.
|
||||
[[nodiscard]] HRESULT VtEngine::StartPaint() noexcept
|
||||
{
|
||||
// When unit testing, there may be no pipe, but we still need to paint.
|
||||
if (!_hFile)
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// If we're using line renditions, and this is a full screen paint, we can
|
||||
// potentially stop using them at the end of this frame.
|
||||
_stopUsingLineRenditions = _usingLineRenditions && _AllIsInvalid();
|
||||
|
||||
// If there's nothing to do, we won't need to paint.
|
||||
auto somethingToDo = _invalidMap.any() ||
|
||||
_scrollDelta != til::point{ 0, 0 } ||
|
||||
_cursorMoved ||
|
||||
_titleChanged;
|
||||
|
||||
_trace.TraceStartPaint(!somethingToDo,
|
||||
_invalidMap,
|
||||
_lastViewport.ToExclusive(),
|
||||
_scrollDelta,
|
||||
_cursorMoved,
|
||||
_wrappedRow);
|
||||
|
||||
return somethingToDo ? S_OK : S_FALSE;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - EndPaint helper to perform the final cleanup after painting. If we
|
||||
// returned S_FALSE from StartPaint, there's no guarantee this was called.
|
||||
// That's okay however, EndPaint only zeros structs that would be zero if
|
||||
// StartPaint returns S_FALSE.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - S_OK, else an appropriate HRESULT for failing to allocate or write.
|
||||
[[nodiscard]] HRESULT VtEngine::EndPaint() noexcept
|
||||
{
|
||||
_trace.TraceEndPaint();
|
||||
|
||||
_invalidMap.reset_all();
|
||||
|
||||
_scrollDelta = { 0, 0 };
|
||||
_clearedAllThisFrame = false;
|
||||
_cursorMoved = false;
|
||||
_firstPaint = false;
|
||||
_skipCursor = false;
|
||||
_resized = false;
|
||||
// If we've circled the buffer this frame, move our virtual top upwards.
|
||||
// We do this at the END of the frame, so that during the paint, we still
|
||||
// use the original virtual top.
|
||||
if (_circled)
|
||||
{
|
||||
if (_virtualTop > 0)
|
||||
{
|
||||
_virtualTop--;
|
||||
}
|
||||
}
|
||||
_circled = false;
|
||||
|
||||
// If _stopUsingLineRenditions is still true at the end of the frame, that
|
||||
// means we've refreshed the entire viewport with every line being single
|
||||
// width, so we can safely stop using them from now on.
|
||||
if (_stopUsingLineRenditions)
|
||||
{
|
||||
_usingLineRenditions = false;
|
||||
}
|
||||
|
||||
// If we deferred a cursor movement during the frame, make sure we put the
|
||||
// cursor in the right place before we end the frame.
|
||||
if (_deferredCursorPos != INVALID_COORDS)
|
||||
{
|
||||
RETURN_IF_FAILED(_MoveCursor(_deferredCursorPos));
|
||||
}
|
||||
|
||||
// If this frame was triggered because we encountered a VT sequence which
|
||||
// required the buffered state to get printed, we don't want to flush this
|
||||
// frame to the pipe. That might result in us rendering half the output of a
|
||||
// particular frame (as emitted by the client).
|
||||
//
|
||||
// Instead, we'll leave this frame in _buffer, and just keep appending to
|
||||
// it as needed.
|
||||
if (!_noFlushOnEnd)
|
||||
{
|
||||
_Flush();
|
||||
}
|
||||
|
||||
_noFlushOnEnd = false;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Used to perform longer running presentation steps outside the lock so the
|
||||
// other threads can continue.
|
||||
// - Not currently used by VtEngine.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - S_FALSE since we do nothing.
|
||||
[[nodiscard]] HRESULT VtEngine::Present() noexcept
|
||||
{
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
[[nodiscard]] HRESULT VtEngine::ResetLineTransform() noexcept
|
||||
{
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
[[nodiscard]] HRESULT VtEngine::PrepareLineTransform(const LineRendition lineRendition,
|
||||
const til::CoordType targetRow,
|
||||
const til::CoordType /*viewportLeft*/) noexcept
|
||||
{
|
||||
// We don't want to waste bandwidth writing out line rendition attributes
|
||||
// until we know they're in use. But once they are in use, we have to keep
|
||||
// applying them on every line until we know they definitely aren't being
|
||||
// used anymore (we check that at the end of any fullscreen paint).
|
||||
if (lineRendition != LineRendition::SingleWidth)
|
||||
{
|
||||
_stopUsingLineRenditions = false;
|
||||
_usingLineRenditions = true;
|
||||
}
|
||||
// One simple optimization is that we can skip sending the line attributes
|
||||
// when we're writing out a single character, which should preclude there
|
||||
// being a rendition switch.
|
||||
if (_usingLineRenditions && !_invalidMap.one())
|
||||
{
|
||||
RETURN_IF_FAILED(_MoveCursor({ _lastText.x, targetRow }));
|
||||
switch (lineRendition)
|
||||
{
|
||||
case LineRendition::SingleWidth:
|
||||
return _Write("\x1b#5");
|
||||
case LineRendition::DoubleWidth:
|
||||
return _Write("\x1b#6");
|
||||
case LineRendition::DoubleHeightTop:
|
||||
return _Write("\x1b#3");
|
||||
case LineRendition::DoubleHeightBottom:
|
||||
return _Write("\x1b#4");
|
||||
}
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Paints the background of the invalid area of the frame.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - S_OK
|
||||
[[nodiscard]] HRESULT VtEngine::PaintBackground() noexcept
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Draws one line of the buffer to the screen. Writes the characters to the
|
||||
// pipe. If the characters are outside the ASCII range (0-0x7f), then
|
||||
// instead writes a '?'
|
||||
// Arguments:
|
||||
// - clusters - text and column count data to be written
|
||||
// - trimLeft - This specifies whether to trim one character width off the left
|
||||
// side of the output. Used for drawing the right-half only of a
|
||||
// double-wide character.
|
||||
// - lineWrapped: true if this run we're painting is the end of a line that
|
||||
// wrapped. If we're not painting the last column of a wrapped line, then this
|
||||
// will be false.
|
||||
// Return Value:
|
||||
// - S_OK or suitable HRESULT error from writing pipe.
|
||||
[[nodiscard]] HRESULT VtEngine::PaintBufferLine(const std::span<const Cluster> clusters,
|
||||
const til::point coord,
|
||||
const bool /*trimLeft*/,
|
||||
const bool /*lineWrapped*/) noexcept
|
||||
{
|
||||
return VtEngine::_PaintAsciiBufferLine(clusters, coord);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Draws up to one line worth of grid lines on top of characters.
|
||||
// Arguments:
|
||||
// - lines - Enum defining which edges of the rectangle to draw
|
||||
// - gridlineColor - The color to use for drawing the gridlines.
|
||||
// - underlineColor - The color to use for drawing the underlines.
|
||||
// - cchLine - How many characters we should draw the grid lines along (left to right in a row)
|
||||
// - coordTarget - The starting X/Y position of the first character to draw on.
|
||||
// Return Value:
|
||||
// - S_OK
|
||||
[[nodiscard]] HRESULT VtEngine::PaintBufferGridLines(const GridLineSet /*lines*/,
|
||||
const COLORREF /*gridlineColor*/,
|
||||
const COLORREF /*underlineColor*/,
|
||||
const size_t /*cchLine*/,
|
||||
const til::point /*coordTarget*/) noexcept
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Draws the cursor on the screen
|
||||
// Arguments:
|
||||
// - options - Options that affect the presentation of the cursor
|
||||
// Return Value:
|
||||
// - S_OK or suitable HRESULT error from writing pipe.
|
||||
[[nodiscard]] HRESULT VtEngine::PaintCursor(const CursorOptions& options) noexcept
|
||||
{
|
||||
_trace.TracePaintCursor(options.coordCursor);
|
||||
|
||||
// GH#17270: If the wrappedRow field is set, and the target cursor position
|
||||
// is at the start of the next row, it's expected that any subsequent output
|
||||
// would already be written to that location, so the _MoveCursor method may
|
||||
// decide it doesn't need to do anything. In this case, though, we're not
|
||||
// writing anything else, so the cursor will end up in the wrong location at
|
||||
// the end of the frame. Clearing the wrappedRow field fixes that.
|
||||
_wrappedRow = std::nullopt;
|
||||
_trace.TraceClearWrapped();
|
||||
|
||||
// MSFT:15933349 - Send the terminal the updated cursor information, if it's changed.
|
||||
LOG_IF_FAILED(_MoveCursor(options.coordCursor));
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Inverts the selected region on the current screen buffer.
|
||||
// - Reads the selected area, selection mode, and active screen buffer
|
||||
// from the global properties and dispatches a GDI invert on the selected text area.
|
||||
// Because the selection is the responsibility of the terminal, and not the
|
||||
// host, render nothing.
|
||||
// Arguments:
|
||||
// - rect - Rectangle to invert or highlight to make the selection area
|
||||
// Return Value:
|
||||
// - S_OK
|
||||
[[nodiscard]] HRESULT VtEngine::PaintSelection(const til::rect& /*rect*/) noexcept
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Write a VT sequence to change the current colors of text. Writes true RGB
|
||||
// color sequences.
|
||||
// Arguments:
|
||||
// - textAttributes: Text attributes to use for the colors.
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
|
||||
[[nodiscard]] HRESULT VtEngine::_RgbUpdateDrawingBrushes(const TextAttribute& textAttributes) noexcept
|
||||
{
|
||||
const auto fg = textAttributes.GetForeground();
|
||||
const auto bg = textAttributes.GetBackground();
|
||||
const auto ul = textAttributes.GetUnderlineColor();
|
||||
auto lastFg = _lastTextAttributes.GetForeground();
|
||||
auto lastBg = _lastTextAttributes.GetBackground();
|
||||
auto lastUl = _lastTextAttributes.GetUnderlineColor();
|
||||
|
||||
// If the FG, BG and UL should be the defaults, emit an SGR reset.
|
||||
if (fg.IsDefault() && bg.IsDefault() && ul.IsDefault() && !(lastFg.IsDefault() && lastBg.IsDefault() && lastUl.IsDefault()))
|
||||
{
|
||||
// SGR Reset will clear all attributes (except hyperlink ID) - which means
|
||||
// we cannot reset _lastTextAttributes by simply doing
|
||||
// _lastTextAttributes = {};
|
||||
// because we want to retain the last hyperlink ID
|
||||
RETURN_IF_FAILED(_SetGraphicsDefault());
|
||||
_lastTextAttributes.SetDefaultBackground();
|
||||
_lastTextAttributes.SetDefaultForeground();
|
||||
_lastTextAttributes.SetDefaultUnderlineColor();
|
||||
_lastTextAttributes.SetDefaultRenditionAttributes();
|
||||
lastFg = {};
|
||||
lastBg = {};
|
||||
lastUl = {};
|
||||
}
|
||||
|
||||
if (fg != lastFg)
|
||||
{
|
||||
if (fg.IsDefault())
|
||||
{
|
||||
RETURN_IF_FAILED(_SetGraphicsRenditionDefaultColor(true));
|
||||
}
|
||||
else if (fg.IsIndex16())
|
||||
{
|
||||
RETURN_IF_FAILED(_SetGraphicsRendition16Color(fg.GetIndex(), true));
|
||||
}
|
||||
else if (fg.IsIndex256())
|
||||
{
|
||||
RETURN_IF_FAILED(_SetGraphicsRendition256Color(fg.GetIndex(), true));
|
||||
}
|
||||
else if (fg.IsRgb())
|
||||
{
|
||||
RETURN_IF_FAILED(_SetGraphicsRenditionRGBColor(fg.GetRGB(), true));
|
||||
}
|
||||
_lastTextAttributes.SetForeground(fg);
|
||||
}
|
||||
|
||||
if (bg != lastBg)
|
||||
{
|
||||
if (bg.IsDefault())
|
||||
{
|
||||
RETURN_IF_FAILED(_SetGraphicsRenditionDefaultColor(false));
|
||||
}
|
||||
else if (bg.IsIndex16())
|
||||
{
|
||||
RETURN_IF_FAILED(_SetGraphicsRendition16Color(bg.GetIndex(), false));
|
||||
}
|
||||
else if (bg.IsIndex256())
|
||||
{
|
||||
RETURN_IF_FAILED(_SetGraphicsRendition256Color(bg.GetIndex(), false));
|
||||
}
|
||||
else if (bg.IsRgb())
|
||||
{
|
||||
RETURN_IF_FAILED(_SetGraphicsRenditionRGBColor(bg.GetRGB(), false));
|
||||
}
|
||||
_lastTextAttributes.SetBackground(bg);
|
||||
}
|
||||
|
||||
if (ul != lastUl)
|
||||
{
|
||||
if (ul.IsDefault())
|
||||
{
|
||||
RETURN_IF_FAILED(_SetGraphicsRenditionUnderlineDefaultColor());
|
||||
}
|
||||
else if (ul.IsIndex16()) // underline can't be 16 color
|
||||
{
|
||||
/* do nothing */
|
||||
}
|
||||
else if (ul.IsIndex256())
|
||||
{
|
||||
RETURN_IF_FAILED(_SetGraphicsRenditionUnderline256Color(ul.GetIndex()));
|
||||
}
|
||||
else if (ul.IsRgb())
|
||||
{
|
||||
RETURN_IF_FAILED(_SetGraphicsRenditionUnderlineRGBColor(ul.GetRGB()));
|
||||
}
|
||||
_lastTextAttributes.SetUnderlineColor(ul);
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Write a VT sequence to change the current colors of text. It will try to
|
||||
// find ANSI colors that are nearest to the input colors, and write those
|
||||
// indices to the pipe.
|
||||
// Arguments:
|
||||
// - textAttributes: Text attributes to use for the colors.
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
|
||||
[[nodiscard]] HRESULT VtEngine::_16ColorUpdateDrawingBrushes(const TextAttribute& textAttributes) noexcept
|
||||
{
|
||||
const auto fg = textAttributes.GetForeground();
|
||||
const auto bg = textAttributes.GetBackground();
|
||||
auto lastFg = _lastTextAttributes.GetForeground();
|
||||
auto lastBg = _lastTextAttributes.GetBackground();
|
||||
|
||||
// If either FG or BG have changed to default, emit a SGR reset.
|
||||
// We can't reset FG and BG to default individually.
|
||||
if ((fg.IsDefault() && !lastFg.IsDefault()) || (bg.IsDefault() && !lastBg.IsDefault()))
|
||||
{
|
||||
// SGR Reset will clear all attributes (except hyperlink ID) - which means
|
||||
// we cannot reset _lastTextAttributes by simply doing
|
||||
// _lastTextAttributes = {};
|
||||
// because we want to retain the last hyperlink ID
|
||||
RETURN_IF_FAILED(_SetGraphicsDefault());
|
||||
_lastTextAttributes.SetDefaultBackground();
|
||||
_lastTextAttributes.SetDefaultForeground();
|
||||
_lastTextAttributes.SetDefaultRenditionAttributes();
|
||||
lastFg = {};
|
||||
lastBg = {};
|
||||
}
|
||||
|
||||
// We use the legacy color calculations to generate an approximation of the
|
||||
// colors in the Windows 16-color table, but we need to transpose those
|
||||
// values to obtain an index in an ANSI-compatible order.
|
||||
auto fgIndex = TextColor::TransposeLegacyIndex(fg.GetLegacyIndex(0));
|
||||
auto bgIndex = TextColor::TransposeLegacyIndex(bg.GetLegacyIndex(0));
|
||||
|
||||
// If the intense attribute is set, and the foreground can be brightened, then do so.
|
||||
const auto brighten = textAttributes.IsIntense() && fg.CanBeBrightened();
|
||||
fgIndex |= (brighten ? FOREGROUND_INTENSITY : 0);
|
||||
|
||||
// To actually render bright colors, though, we need to use SGR intense.
|
||||
const auto needIntense = fgIndex > 7;
|
||||
if (needIntense != _lastTextAttributes.IsIntense())
|
||||
{
|
||||
RETURN_IF_FAILED(_SetIntense(needIntense));
|
||||
_lastTextAttributes.SetIntense(needIntense);
|
||||
}
|
||||
|
||||
// After which we drop the high bits, since only colors 0 to 7 are supported.
|
||||
|
||||
fgIndex &= 7;
|
||||
bgIndex &= 7;
|
||||
|
||||
if (!fg.IsDefault() && (lastFg.IsDefault() || fgIndex != lastFg.GetIndex()))
|
||||
{
|
||||
RETURN_IF_FAILED(_SetGraphicsRendition16Color(fgIndex, true));
|
||||
_lastTextAttributes.SetIndexedForeground(fgIndex);
|
||||
}
|
||||
|
||||
if (!bg.IsDefault() && (lastBg.IsDefault() || bgIndex != lastBg.GetIndex()))
|
||||
{
|
||||
RETURN_IF_FAILED(_SetGraphicsRendition16Color(bgIndex, false));
|
||||
_lastTextAttributes.SetIndexedBackground(bgIndex);
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Draws one line of the buffer to the screen. Writes the characters to the
|
||||
// pipe. If the characters are outside the ASCII range (0-0x7f), then
|
||||
// instead writes a '?'.
|
||||
// This is needed because the Windows internal telnet client implementation
|
||||
// doesn't know how to handle >ASCII characters. The old telnetd would
|
||||
// just replace them with '?' characters. If we render the >ASCII
|
||||
// characters to telnet, it will likely end up drawing them wrong, which
|
||||
// will make the client appear buggy and broken.
|
||||
// Arguments:
|
||||
// - clusters - text and column width data to be written
|
||||
// - coord - character coordinate target to render within viewport
|
||||
// Return Value:
|
||||
// - S_OK or suitable HRESULT error from writing pipe.
|
||||
[[nodiscard]] HRESULT VtEngine::_PaintAsciiBufferLine(const std::span<const Cluster> clusters,
|
||||
const til::point coord) noexcept
|
||||
{
|
||||
try
|
||||
{
|
||||
RETURN_IF_FAILED(_MoveCursor(coord));
|
||||
|
||||
_bufferLine.clear();
|
||||
_bufferLine.reserve(clusters.size());
|
||||
|
||||
til::CoordType totalWidth = 0;
|
||||
for (const auto& cluster : clusters)
|
||||
{
|
||||
_bufferLine.append(cluster.GetText());
|
||||
totalWidth += cluster.GetColumns();
|
||||
}
|
||||
|
||||
RETURN_IF_FAILED(VtEngine::_WriteTerminalAscii(_bufferLine));
|
||||
|
||||
// Update our internal tracker of the cursor's position
|
||||
_lastText.x += totalWidth;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Draws one line of the buffer to the screen. Writes the characters to the
|
||||
// pipe, encoded in UTF-8.
|
||||
// Arguments:
|
||||
// - clusters - text and column widths to be written
|
||||
// - coord - character coordinate target to render within viewport
|
||||
// Return Value:
|
||||
// - S_OK or suitable HRESULT error from writing pipe.
|
||||
[[nodiscard]] HRESULT VtEngine::_PaintUtf8BufferLine(const std::span<const Cluster> clusters,
|
||||
const til::point coord,
|
||||
const bool lineWrapped) noexcept
|
||||
{
|
||||
if (coord.y < _virtualTop)
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
_bufferLine.clear();
|
||||
_bufferLine.reserve(clusters.size());
|
||||
til::CoordType totalWidth = 0;
|
||||
for (const auto& cluster : clusters)
|
||||
{
|
||||
_bufferLine.append(cluster.GetText());
|
||||
totalWidth += cluster.GetColumns();
|
||||
}
|
||||
|
||||
// If any of the values in the buffer are C0 or C1 controls, we need to
|
||||
// convert them to printable codepoints, otherwise they'll end up being
|
||||
// evaluated as control characters by the receiving terminal. We use the
|
||||
// DOS 437 code page for the C0 controls and DEL, and just a `?` for the
|
||||
// C1 controls, since that's what you would most likely have seen in the
|
||||
// legacy v1 console with raster fonts.
|
||||
const auto cchLine = _bufferLine.size();
|
||||
std::for_each_n(_bufferLine.begin(), cchLine, [](auto& ch) {
|
||||
static constexpr std::wstring_view C0Glyphs = L" ☺☻♥♦♣♠•◘○◙♂♀♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼";
|
||||
if (ch < C0Glyphs.size())
|
||||
{
|
||||
ch = til::at(C0Glyphs, ch);
|
||||
}
|
||||
else if (ch >= L'\u007F' && ch < L'\u00A0')
|
||||
{
|
||||
ch = (ch == L'\u007F' ? L'⌂' : L'?');
|
||||
}
|
||||
});
|
||||
|
||||
const auto spaceIndex = _bufferLine.find_last_not_of(L' ');
|
||||
const auto foundNonspace = spaceIndex != decltype(_bufferLine)::npos;
|
||||
const auto nonSpaceLength = foundNonspace ? spaceIndex + 1 : 0;
|
||||
|
||||
// Examples:
|
||||
// - " ":
|
||||
// cch = 2, spaceIndex = 0, foundNonSpace = false
|
||||
// cch-nonSpaceLength = 2
|
||||
// - "A "
|
||||
// cch = 2, spaceIndex = 0, foundNonSpace = true
|
||||
// cch-nonSpaceLength = 1
|
||||
// - "AA"
|
||||
// cch = 2, spaceIndex = 1, foundNonSpace = true
|
||||
// cch-nonSpaceLength = 0
|
||||
const auto numSpaces = gsl::narrow_cast<til::CoordType>(cchLine - nonSpaceLength);
|
||||
|
||||
// Optimizations:
|
||||
// If there are lots of spaces at the end of the line, we can try to Erase
|
||||
// Character that number of spaces, then move the cursor forward (to
|
||||
// where it would be if we had written the spaces)
|
||||
// An erase character and move right sequence is 8 chars, and possibly 10
|
||||
// (if there are at least 10 spaces, 2 digits to print)
|
||||
// ESC [ %d X ESC [ %d C
|
||||
// ESC [ %d %d X ESC [ %d %d C
|
||||
// So we need at least 9 spaces for the optimized sequence to make sense.
|
||||
// Also, if we already erased the entire display this frame, then
|
||||
// don't do ANYTHING with erasing at all.
|
||||
|
||||
// Note: We're only doing these optimizations along the UTF-8 path, because
|
||||
// the inbox telnet client doesn't understand the Erase Character sequence,
|
||||
// and it uses xterm-ascii. This ensures that xterm and -256color consumers
|
||||
// get the enhancements, and telnet isn't broken.
|
||||
//
|
||||
// GH#13229: ECH and EL don't fill the space with visual attributes like
|
||||
// underline, reverse video, hyperlinks, etc. If these spaces had those
|
||||
// attrs, then don't try and optimize them out.
|
||||
const auto optimalToUseECH = numSpaces > ERASE_CHARACTER_STRING_LENGTH;
|
||||
const auto useEraseChar = (optimalToUseECH) &&
|
||||
(!_newBottomLine) &&
|
||||
(!_clearedAllThisFrame) &&
|
||||
(!_lastTextAttributes.HasAnyVisualAttributes());
|
||||
const auto printingBottomLine = coord.y == _lastViewport.BottomInclusive();
|
||||
|
||||
// GH#5502 - If the background color of the "new bottom line" is different
|
||||
// than when we emitted the line, we can't optimize out the spaces from it.
|
||||
// We'll still need to emit those spaces, so that the connected terminal
|
||||
// will have the same background color on those blank cells.
|
||||
const auto bgMatched = _newBottomLineBG.has_value() ? (_newBottomLineBG.value() == _lastTextAttributes.GetBackground()) : true;
|
||||
|
||||
// If we're not using erase char, but we did erase all at the start of the
|
||||
// frame, don't add spaces at the end.
|
||||
//
|
||||
// GH#5161: Only removeSpaces when we're in the _newBottomLine state and the
|
||||
// line we're trying to print right now _actually is the bottom line_
|
||||
//
|
||||
// GH#5291: DON'T remove spaces when the row wrapped. We might need those
|
||||
// spaces to preserve the wrap state of this line, or the cursor position.
|
||||
// For example, vim.exe uses "~ "... to clear the line, and then leaves
|
||||
// the lines _wrapped_. It doesn't care to manually break the lines, but if
|
||||
// we trimmed the spaces off here, we'd print all the "~"s one after another
|
||||
// on the same line.
|
||||
static const TextAttribute defaultAttrs{};
|
||||
const auto removeSpaces = !lineWrapped && (useEraseChar // we determined earlier that ECH is optimal
|
||||
|| (_clearedAllThisFrame && _lastTextAttributes == defaultAttrs) // OR we cleared the last frame to the default attributes (specifically)
|
||||
|| (_newBottomLine && printingBottomLine && bgMatched)); // OR we just scrolled a new line onto the bottom of the screen with the correct attributes
|
||||
const auto cchActual = removeSpaces ? nonSpaceLength : cchLine;
|
||||
|
||||
const auto columnsActual = removeSpaces ?
|
||||
(totalWidth - numSpaces) :
|
||||
totalWidth;
|
||||
|
||||
if (cchActual == 0)
|
||||
{
|
||||
// If the previous row wrapped, but this line is empty, then we actually
|
||||
// do want to move the cursor down. Otherwise, we'll possibly end up
|
||||
// accidentally erasing the last character from the previous line, as
|
||||
// the cursor is still waiting on that character for the next character
|
||||
// to follow it.
|
||||
//
|
||||
// GH#5839 - If we've emitted a wrapped row, because the cursor is
|
||||
// sitting just past the last cell of the previous row, if we execute a
|
||||
// EraseCharacter or EraseLine here, then the row won't actually get
|
||||
// cleared here. This logic is important to make sure that the cursor is
|
||||
// in the right position before we do that.
|
||||
|
||||
_wrappedRow = std::nullopt;
|
||||
_trace.TraceClearWrapped();
|
||||
}
|
||||
|
||||
// Move the cursor to the start of this run.
|
||||
RETURN_IF_FAILED(_MoveCursor(coord));
|
||||
|
||||
// Write the actual text string. If we're using a soft font, the character
|
||||
// set should have already been selected, so we just need to map our internal
|
||||
// representation back to ASCII (handled by the _WriteTerminalDrcs method).
|
||||
if (_usingSoftFont) [[unlikely]]
|
||||
{
|
||||
RETURN_IF_FAILED(VtEngine::_WriteTerminalDrcs({ _bufferLine.data(), cchActual }));
|
||||
}
|
||||
else
|
||||
{
|
||||
RETURN_IF_FAILED(VtEngine::_WriteTerminalUtf8({ _bufferLine.data(), cchActual }));
|
||||
}
|
||||
|
||||
// GH#4415, GH#5181
|
||||
// If the renderer told us that this was a wrapped line, then mark
|
||||
// that we've wrapped this line. The next time we attempt to move the
|
||||
// cursor, if we're trying to move it to the start of the next line,
|
||||
// we'll remember that this line was wrapped, and not manually break the
|
||||
// line.
|
||||
if (lineWrapped)
|
||||
{
|
||||
_wrappedRow = coord.y;
|
||||
_trace.TraceSetWrapped(coord.y);
|
||||
}
|
||||
|
||||
// Update our internal tracker of the cursor's position.
|
||||
// See MSFT:20266233 (which is also GH#357)
|
||||
// If the cursor is at the rightmost column of the terminal, and we write a
|
||||
// space, the cursor won't actually move to the next cell (which would
|
||||
// be {0, _lastText.y++}). The cursor will stay visibly in that last
|
||||
// cell until then next character is output.
|
||||
// If in that case, we increment the cursor position here (such that the X
|
||||
// position would be one past the right of the terminal), when we come
|
||||
// back through to MoveCursor in the last PaintCursor of the frame,
|
||||
// we'll determine that we need to emit a \b to put the cursor in the
|
||||
// right position. This is wrong, and will cause us to move the cursor
|
||||
// back one character more than we wanted.
|
||||
//
|
||||
// GH#1245: This needs to be RightExclusive, _not_ inclusive. Otherwise, we
|
||||
// won't update our internal cursor position tracker correctly at the last
|
||||
// character of the row.
|
||||
if (_lastText.x < _lastViewport.RightExclusive())
|
||||
{
|
||||
_lastText.x += columnsActual;
|
||||
}
|
||||
// GH#1245: If we wrote the exactly last char of the row, then we're in the
|
||||
// "delayed EOL wrap" state. Different terminals (conhost, gnome-terminal,
|
||||
// wt) all behave differently with how the cursor behaves at an end of line.
|
||||
// Mark that we're in the delayed EOL wrap state - we don't want to be
|
||||
// clever about how we move the cursor in this state, since different
|
||||
// terminals will handle a backspace differently in this state.
|
||||
if (_lastText.x >= _lastViewport.RightInclusive())
|
||||
{
|
||||
_delayedEolWrap = true;
|
||||
}
|
||||
|
||||
if (useEraseChar)
|
||||
{
|
||||
// ECH doesn't actually move the cursor itself. However, we think that
|
||||
// the cursor *should* be at the end of the area we just erased. Stash
|
||||
// that position as our new deferred position. If we don't move the
|
||||
// cursor somewhere else before the end of the frame, we'll move the
|
||||
// cursor to the deferred position at the end of the frame, or right
|
||||
// before we need to print new text.
|
||||
_deferredCursorPos = { _lastText.x + numSpaces, _lastText.y };
|
||||
|
||||
if (_deferredCursorPos.x <= _lastViewport.RightInclusive())
|
||||
{
|
||||
RETURN_IF_FAILED(_EraseCharacter(numSpaces));
|
||||
}
|
||||
// If we're past the end of the row (i.e. in the "delayed EOL wrap"
|
||||
// state), then there is no need to erase the rest of line. In fact
|
||||
// if we did output an EL sequence at this point, it could reset the
|
||||
// "delayed EOL wrap" state, breaking subsequent output.
|
||||
else if (_lastText.x <= _lastViewport.RightInclusive())
|
||||
{
|
||||
RETURN_IF_FAILED(_EraseLine());
|
||||
}
|
||||
}
|
||||
else if (_newBottomLine && printingBottomLine)
|
||||
{
|
||||
// If we're on a new line, then we don't need to erase the line. The
|
||||
// line is already empty.
|
||||
if (optimalToUseECH)
|
||||
{
|
||||
_deferredCursorPos = { _lastText.x + numSpaces, _lastText.y };
|
||||
}
|
||||
else if (numSpaces > 0 && removeSpaces) // if we deleted the spaces... re-add them
|
||||
{
|
||||
// TODO GH#5430 - Determine why and when we would do this.
|
||||
auto spaces = std::wstring(numSpaces, L' ');
|
||||
RETURN_IF_FAILED(VtEngine::_WriteTerminalUtf8(spaces));
|
||||
|
||||
_lastText.x += numSpaces;
|
||||
}
|
||||
}
|
||||
|
||||
// If we printed to the bottom line, and we previously thought that this was
|
||||
// a new bottom line, it certainly isn't new any longer.
|
||||
if (printingBottomLine)
|
||||
{
|
||||
_newBottomLine = false;
|
||||
_newBottomLineBG = std::nullopt;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Updates the window's title string. Emits the VT sequence to SetWindowTitle.
|
||||
// Because wintelnet does not understand these sequences by default, we
|
||||
// don't do anything by default. Other modes can implement if they support
|
||||
// the sequence.
|
||||
// Arguments:
|
||||
// - newTitle: the new string to use for the title of the window
|
||||
// Return Value:
|
||||
// - S_OK
|
||||
[[nodiscard]] HRESULT VtEngine::_DoUpdateTitle(const std::wstring_view /*newTitle*/) noexcept
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
@ -1,33 +0,0 @@
|
||||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- precomp.h
|
||||
|
||||
Abstract:
|
||||
- Contains external headers to include in the precompile phase of console build process.
|
||||
- Avoid including internal project headers. Instead include them only in the classes that need them (helps with test project building).
|
||||
--*/
|
||||
|
||||
#include <cwchar>
|
||||
#include <sal.h>
|
||||
|
||||
// This includes support libraries from the CRT, STL, WIL, and GSL
|
||||
#include "LibraryIncludes.h"
|
||||
|
||||
#include <windows.h>
|
||||
#include <windowsx.h>
|
||||
|
||||
#if defined(DEBUG) || defined(_DEBUG) || defined(DBG)
|
||||
#define WHEN_DBG(x) x
|
||||
#else
|
||||
#define WHEN_DBG(x)
|
||||
#endif
|
||||
|
||||
// SafeMath
|
||||
#pragma prefast(push)
|
||||
#pragma prefast(disable : 26071, "Range violation in Intsafe. Not ours.")
|
||||
#define ENABLE_INTSAFE_SIGNED_FUNCTIONS // Only unsigned intsafe math/casts available without this def
|
||||
#include <intsafe.h>
|
||||
#pragma prefast(pop)
|
||||
@ -1,38 +0,0 @@
|
||||
!include ..\..\..\project.inc
|
||||
|
||||
# -------------------------------------
|
||||
# Windows Console
|
||||
# - Console Renderer for VT
|
||||
# -------------------------------------
|
||||
|
||||
# This module provides a rendering engine implementation that
|
||||
# renders the display to an outgoing VT stream.
|
||||
|
||||
# -------------------------------------
|
||||
# Build System Settings
|
||||
# -------------------------------------
|
||||
|
||||
# Code in the OneCore depot automatically excludes default Win32 libraries.
|
||||
|
||||
# -------------------------------------
|
||||
# Sources, Headers, and Libraries
|
||||
# -------------------------------------
|
||||
|
||||
PRECOMPILED_CXX = 1
|
||||
PRECOMPILED_INCLUDE = ..\precomp.h
|
||||
|
||||
SOURCES = \
|
||||
..\invalidate.cpp \
|
||||
..\math.cpp \
|
||||
..\paint.cpp \
|
||||
..\state.cpp \
|
||||
..\tracing.cpp \
|
||||
..\XtermEngine.cpp \
|
||||
..\Xterm256Engine.cpp \
|
||||
..\VtSequences.cpp \
|
||||
|
||||
INCLUDES = \
|
||||
$(INCLUDES); \
|
||||
..; \
|
||||
..\..\..\inc; \
|
||||
$(MINWIN_INTERNAL_PRIV_SDK_INC_PATH_L); \
|
||||
@ -1,554 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
#include "vtrenderer.hpp"
|
||||
#include "../../inc/conattrs.hpp"
|
||||
#include "../../host/VtIo.hpp"
|
||||
|
||||
// For _vcprintf
|
||||
#include <conio.h>
|
||||
#include <cstdarg>
|
||||
|
||||
#pragma hdrstop
|
||||
|
||||
using namespace Microsoft::Console;
|
||||
using namespace Microsoft::Console::Render;
|
||||
using namespace Microsoft::Console::Types;
|
||||
|
||||
constexpr til::point VtEngine::INVALID_COORDS = { -1, -1 };
|
||||
|
||||
// Routine Description:
|
||||
// - Creates a new VT-based rendering engine
|
||||
// - NOTE: Will throw if initialization failure. Caller must catch.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - An instance of a Renderer.
|
||||
VtEngine::VtEngine(_In_ wil::unique_hfile pipe,
|
||||
const Viewport initialViewport) :
|
||||
RenderEngineBase(),
|
||||
_hFile(std::move(pipe)),
|
||||
_usingLineRenditions(false),
|
||||
_stopUsingLineRenditions(false),
|
||||
_usingSoftFont(false),
|
||||
_lastTextAttributes(INVALID_COLOR, INVALID_COLOR, INVALID_COLOR),
|
||||
_lastViewport(initialViewport),
|
||||
_pool(til::pmr::get_default_resource()),
|
||||
_invalidMap(initialViewport.Dimensions(), false, &_pool),
|
||||
_scrollDelta(0, 0),
|
||||
_clearedAllThisFrame(false),
|
||||
_cursorMoved(false),
|
||||
_resized(false),
|
||||
_suppressResizeRepaint(true),
|
||||
_virtualTop(0),
|
||||
_circled(false),
|
||||
_firstPaint(true),
|
||||
_skipCursor(false),
|
||||
_terminalOwner{ nullptr },
|
||||
_newBottomLine{ false },
|
||||
_deferredCursorPos{ INVALID_COORDS },
|
||||
_trace{},
|
||||
_bufferLine{},
|
||||
_buffer{},
|
||||
_formatBuffer{},
|
||||
_conversionBuffer{},
|
||||
_pfnSetLookingForDSR{}
|
||||
{
|
||||
#ifndef UNIT_TESTING
|
||||
// When unit testing, we can instantiate a VtEngine without a pipe.
|
||||
THROW_HR_IF(E_HANDLE, !_hFile);
|
||||
#else
|
||||
// member is only defined when UNIT_TESTING is.
|
||||
_usingTestCallback = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Writes a fill of characters to our file handle (repeat of same character over and over)
|
||||
[[nodiscard]] HRESULT VtEngine::_WriteFill(const size_t n, const char c) noexcept
|
||||
try
|
||||
{
|
||||
_trace.TraceStringFill(n, c);
|
||||
#ifdef UNIT_TESTING
|
||||
if (_usingTestCallback)
|
||||
{
|
||||
const std::string str(n, c);
|
||||
// Try to get the last error. If that wasn't set, then the test probably
|
||||
// doesn't set last error. No matter. We'll just return with E_FAIL
|
||||
// then. This is a unit test, we don't particularly care.
|
||||
const auto succeeded = _pfnTestCallback(str.data(), str.size());
|
||||
auto hr = E_FAIL;
|
||||
if (!succeeded)
|
||||
{
|
||||
const auto err = ::GetLastError();
|
||||
// If there wasn't an error in GLE, just use E_FAIL
|
||||
hr = SUCCEEDED_WIN32(err) ? hr : HRESULT_FROM_WIN32(err);
|
||||
}
|
||||
return succeeded ? S_OK : hr;
|
||||
}
|
||||
#endif
|
||||
|
||||
// TODO GH10001: Replace me with REP
|
||||
_buffer.append(n, c);
|
||||
return S_OK;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
|
||||
// Method Description:
|
||||
// - Writes the characters to our file handle. If we're building the unit tests,
|
||||
// we can instead write to the test callback, in order to avoid needing to
|
||||
// set up pipes and threads for unit tests.
|
||||
// Arguments:
|
||||
// - str: The buffer to write to the pipe. Might have nulls in it.
|
||||
// Return Value:
|
||||
// - S_OK or suitable HRESULT error from writing pipe.
|
||||
[[nodiscard]] HRESULT VtEngine::_Write(std::string_view const str) noexcept
|
||||
{
|
||||
_trace.TraceString(str);
|
||||
#ifdef UNIT_TESTING
|
||||
if (_usingTestCallback)
|
||||
{
|
||||
// Try to get the last error. If that wasn't set, then the test probably
|
||||
// doesn't set last error. No matter. We'll just return with E_FAIL
|
||||
// then. This is a unit test, we don't particularly care.
|
||||
const auto succeeded = _pfnTestCallback(str.data(), str.size());
|
||||
auto hr = E_FAIL;
|
||||
if (!succeeded)
|
||||
{
|
||||
const auto err = ::GetLastError();
|
||||
// If there wasn't an error in GLE, just use E_FAIL
|
||||
hr = SUCCEEDED_WIN32(err) ? hr : HRESULT_FROM_WIN32(err);
|
||||
}
|
||||
return succeeded ? S_OK : hr;
|
||||
}
|
||||
#endif
|
||||
|
||||
try
|
||||
{
|
||||
_buffer.append(str);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
}
|
||||
|
||||
void VtEngine::_Flush() noexcept
|
||||
{
|
||||
if (_buffer.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_corked)
|
||||
{
|
||||
_flushImpl();
|
||||
return;
|
||||
}
|
||||
|
||||
// Defer the flush until someone calls Cork(false).
|
||||
_flushRequested = true;
|
||||
}
|
||||
|
||||
// _corked is often true and separating _flushImpl() out allows _flush() to be inlined.
|
||||
void VtEngine::_flushImpl() noexcept
|
||||
{
|
||||
if (_hFile)
|
||||
{
|
||||
const auto fSuccess = WriteFile(_hFile.get(), _buffer.data(), gsl::narrow_cast<DWORD>(_buffer.size()), nullptr, nullptr);
|
||||
_buffer.clear();
|
||||
_startOfFrameBufferIndex = 0;
|
||||
if (!fSuccess)
|
||||
{
|
||||
LOG_LAST_ERROR();
|
||||
_hFile.reset();
|
||||
if (_terminalOwner)
|
||||
{
|
||||
_terminalOwner->CloseOutput();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The name of this method is an analogy to TCP_CORK. It instructs
|
||||
// the VT renderer to stop flushing its buffer to the output pipe.
|
||||
// Don't forget to uncork it!
|
||||
void VtEngine::Cork(bool corked) noexcept
|
||||
{
|
||||
_corked = corked;
|
||||
|
||||
// Now do the deferred flush from a previous call to _Flush().
|
||||
if (!corked && _flushRequested)
|
||||
{
|
||||
_flushRequested = false;
|
||||
_flushImpl();
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Wrapper for _Write.
|
||||
[[nodiscard]] HRESULT VtEngine::WriteTerminalUtf8(const std::string_view str) noexcept
|
||||
{
|
||||
return _Write(str);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Writes a wstring to the tty, encoded as full utf-8. This is one
|
||||
// implementation of the WriteTerminalW method.
|
||||
// Arguments:
|
||||
// - wstr - wstring of text to be written
|
||||
// Return Value:
|
||||
// - S_OK or suitable HRESULT error from either conversion or writing pipe.
|
||||
[[nodiscard]] HRESULT VtEngine::_WriteTerminalUtf8(const std::wstring_view wstr) noexcept
|
||||
{
|
||||
RETURN_IF_FAILED(til::u16u8(wstr, _conversionBuffer));
|
||||
return _Write(_conversionBuffer);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Writes a wstring to the tty, encoded as "utf-8" where characters that are
|
||||
// outside the ASCII range are encoded as '?'
|
||||
// This mainly exists to maintain compatibility with the inbox telnet client.
|
||||
// This is one implementation of the WriteTerminalW method.
|
||||
// Arguments:
|
||||
// - wstr - wstring of text to be written
|
||||
// Return Value:
|
||||
// - S_OK or suitable HRESULT error from writing pipe.
|
||||
[[nodiscard]] HRESULT VtEngine::_WriteTerminalAscii(const std::wstring_view wstr) noexcept
|
||||
{
|
||||
std::string needed;
|
||||
needed.reserve(wstr.size());
|
||||
|
||||
for (const auto& wch : wstr)
|
||||
{
|
||||
// We're explicitly replacing characters outside ASCII with a ? because
|
||||
// that's what telnet wants.
|
||||
needed.push_back((wch > L'\x7f') ? '?' : static_cast<char>(wch));
|
||||
}
|
||||
|
||||
return _Write(needed);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Writes a wstring to the tty when the characters are from the DRCS soft font.
|
||||
// It is assumed that the character set has already been designated in the
|
||||
// client terminal, so we just need to re-map our internal representation
|
||||
// of the characters into ASCII.
|
||||
// Arguments:
|
||||
// - wstr - wstring of text to be written
|
||||
// Return Value:
|
||||
// - S_OK or suitable HRESULT error from writing pipe.
|
||||
[[nodiscard]] HRESULT VtEngine::_WriteTerminalDrcs(const std::wstring_view wstr) noexcept
|
||||
{
|
||||
std::string needed;
|
||||
needed.reserve(wstr.size());
|
||||
|
||||
for (const auto& wch : wstr)
|
||||
{
|
||||
// Our DRCS characters use the range U+EF20 to U+EF7F from the Unicode
|
||||
// Private Use Area. To map them back to ASCII we just mask with 7F.
|
||||
needed.push_back(wch & 0x7F);
|
||||
}
|
||||
|
||||
return _Write(needed);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - This method will update the active font on the current device context
|
||||
// Does nothing for vt, the font is handed by the terminal.
|
||||
// Arguments:
|
||||
// - FontDesired - reference to font information we should use while instantiating a font.
|
||||
// - Font - reference to font information where the chosen font information will be populated.
|
||||
// Return Value:
|
||||
// - HRESULT S_OK
|
||||
[[nodiscard]] HRESULT VtEngine::UpdateFont(const FontInfoDesired& /*pfiFontDesired*/,
|
||||
_Out_ FontInfo& /*pfiFont*/) noexcept
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - This method will modify the DPI we're using for scaling calculations.
|
||||
// Does nothing for vt, the dpi is handed by the terminal.
|
||||
// Arguments:
|
||||
// - iDpi - The Dots Per Inch to use for scaling. We will use this relative to
|
||||
// the system default DPI defined in Windows headers as a constant.
|
||||
// Return Value:
|
||||
// - HRESULT S_OK
|
||||
[[nodiscard]] HRESULT VtEngine::UpdateDpi(const int /*iDpi*/) noexcept
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - This method will update our internal reference for how big the viewport is.
|
||||
// If the viewport has changed size, then we'll need to send an update to
|
||||
// the terminal.
|
||||
// Arguments:
|
||||
// - srNewViewport - The bounds of the new viewport.
|
||||
// Return Value:
|
||||
// - HRESULT S_OK
|
||||
[[nodiscard]] HRESULT VtEngine::UpdateViewport(const til::inclusive_rect& srNewViewport) noexcept
|
||||
{
|
||||
auto hr = S_OK;
|
||||
const auto newView = Viewport::FromInclusive(srNewViewport);
|
||||
const auto oldSize = _lastViewport.Dimensions();
|
||||
const auto newSize = newView.Dimensions();
|
||||
|
||||
if (oldSize != newSize)
|
||||
{
|
||||
// Don't emit a resize event if we've requested it be suppressed
|
||||
if (!_suppressResizeRepaint)
|
||||
{
|
||||
hr = _ResizeWindow(newSize.width, newSize.height);
|
||||
}
|
||||
|
||||
if (_resizeQuirk)
|
||||
{
|
||||
// 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 its 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.
|
||||
_invalidMap.resize(newSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
_invalidMap.resize(newSize, true); // resize while filling in new space with repaint requests.
|
||||
|
||||
// Viewport is smaller now - just update it all.
|
||||
if (oldSize.height > newSize.height || oldSize.width > newSize.width)
|
||||
{
|
||||
hr = InvalidateAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_resized = true;
|
||||
}
|
||||
|
||||
// See MSFT:19408543
|
||||
// Always clear the suppression request, even if the new size was the same
|
||||
// as the last size. We're always going to get a UpdateViewport call
|
||||
// for our first frame. However, we start with _suppressResizeRepaint set,
|
||||
// to prevent that first UpdateViewport call from emitting our size.
|
||||
// If we only clear the flag when the new viewport is different, this can
|
||||
// lead to the first _actual_ resize being suppressed.
|
||||
_suppressResizeRepaint = false;
|
||||
_lastViewport = newView;
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - This method will figure out what the new font should be given the starting font information and a DPI.
|
||||
// - When the final font is determined, the FontInfo structure given will be updated with the actual resulting font chosen as the nearest match.
|
||||
// - NOTE: It is left up to the underling rendering system to choose the nearest font. Please ask for the font dimensions if they are required using the interface. Do not use the size you requested with this structure.
|
||||
// - If the intent is to immediately turn around and use this font, pass the optional handle parameter and use it immediately.
|
||||
// Does nothing for vt, the font is handed by the terminal.
|
||||
// Arguments:
|
||||
// - FontDesired - reference to font information we should use while instantiating a font.
|
||||
// - Font - reference to font information where the chosen font information will be populated.
|
||||
// - iDpi - The DPI we will have when rendering
|
||||
// Return Value:
|
||||
// - S_FALSE: This is unsupported by the VT Renderer and should use another engine's value.
|
||||
[[nodiscard]] HRESULT VtEngine::GetProposedFont(const FontInfoDesired& /*pfiFontDesired*/,
|
||||
_Out_ FontInfo& /*pfiFont*/,
|
||||
const int /*iDpi*/) noexcept
|
||||
{
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Retrieves the current pixel size of the font we have selected for drawing.
|
||||
// Arguments:
|
||||
// - pFontSize - receives the current X by Y size of the font.
|
||||
// Return Value:
|
||||
// - S_FALSE: This is unsupported by the VT Renderer and should use another engine's value.
|
||||
[[nodiscard]] HRESULT VtEngine::GetFontSize(_Out_ til::size* const pFontSize) noexcept
|
||||
{
|
||||
*pFontSize = { 1, 1 };
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Sets the test callback for this instance. Instead of rendering to a pipe,
|
||||
// this instance will instead render to a callback for testing.
|
||||
// Arguments:
|
||||
// - pfn: a callback to call instead of writing to the pipe.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void VtEngine::SetTestCallback(_In_ std::function<bool(const char* const, size_t const)> pfn)
|
||||
{
|
||||
#ifdef UNIT_TESTING
|
||||
|
||||
_pfnTestCallback = pfn;
|
||||
_usingTestCallback = true;
|
||||
|
||||
#else
|
||||
THROW_HR(E_FAIL);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Returns true if the entire viewport has been invalidated. That signals we
|
||||
// should use a VT Clear Screen sequence as an optimization.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - true if the entire viewport has been invalidated
|
||||
bool VtEngine::_AllIsInvalid() const
|
||||
{
|
||||
return _invalidMap.all();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Prevent the renderer from emitting output on the next resize. This prevents
|
||||
// the host from echoing a resize to the terminal that requested it.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - S_OK
|
||||
[[nodiscard]] HRESULT VtEngine::SuppressResizeRepaint() noexcept
|
||||
{
|
||||
_suppressResizeRepaint = true;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - "Inherit" the cursor at the given position. We won't need to move it
|
||||
// anywhere, so update where we last thought the cursor was.
|
||||
// Also update our "virtual top", indicating where should clip all updates to
|
||||
// (we don't want to paint the empty region above the inherited cursor).
|
||||
// Also ignore the next InvalidateCursor call.
|
||||
// Arguments:
|
||||
// - coordCursor: The cursor position to inherit from.
|
||||
// Return Value:
|
||||
// - S_OK
|
||||
[[nodiscard]] HRESULT VtEngine::InheritCursor(const til::point coordCursor) noexcept
|
||||
{
|
||||
_virtualTop = coordCursor.y;
|
||||
_lastText = coordCursor;
|
||||
_skipCursor = true;
|
||||
// Prevent us from clearing the entire viewport on the first paint
|
||||
_firstPaint = false;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
void VtEngine::SetTerminalOwner(Microsoft::Console::VirtualTerminal::VtIo* const terminalOwner)
|
||||
{
|
||||
_terminalOwner = terminalOwner;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - sends a sequence to request the end terminal to tell us the
|
||||
// cursor position. The terminal will reply back on the vt input handle.
|
||||
// Flushes the buffer as well, to make sure the request is sent to the terminal.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
|
||||
HRESULT VtEngine::RequestCursor() noexcept
|
||||
{
|
||||
RETURN_IF_FAILED(_RequestCursor());
|
||||
_Flush();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Sends a notification through to the `VtInputThread` that it should
|
||||
// watch for and capture the response from a DSR message we're about to send.
|
||||
// This is typically `RequestCursor` at the time of writing this, but in theory
|
||||
// could be another DSR as well.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - S_OK if all goes well. Invalid state error if no notification function is installed.
|
||||
// (see `SetLookingForDSRCallback` to install one.)
|
||||
[[nodiscard]] HRESULT VtEngine::_ListenForDSR() noexcept
|
||||
{
|
||||
RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !_pfnSetLookingForDSR);
|
||||
_pfnSetLookingForDSR(true);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// 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 its own buffer specifically for a
|
||||
// conpty scenario.
|
||||
// - See also: GH#3490, #4354, #4741
|
||||
// Arguments:
|
||||
// - resizeQuirk - True to turn on the quirk. False otherwise.
|
||||
// Return Value:
|
||||
// - true iff we were started with the `--resizeQuirk` flag enabled.
|
||||
void VtEngine::SetResizeQuirk(const bool resizeQuirk)
|
||||
{
|
||||
_resizeQuirk = resizeQuirk;
|
||||
}
|
||||
|
||||
void VtEngine::SetLookingForDSRCallback(std::function<void(bool)> pfnLooking) noexcept
|
||||
{
|
||||
_pfnSetLookingForDSR = pfnLooking;
|
||||
}
|
||||
|
||||
void VtEngine::SetTerminalCursorTextPosition(const til::point cursor) noexcept
|
||||
{
|
||||
_lastText = cursor;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Manually emit a "Erase Scrollback" sequence to the connected terminal. We
|
||||
// need to do this in certain cases that we've identified where we believe the
|
||||
// client wanted the entire terminal buffer cleared, not just the viewport.
|
||||
// For more information, see GH#3126.
|
||||
// - This is unimplemented in the win-telnet, xterm-ascii renderers - inbox
|
||||
// telnet.exe doesn't know how to handle a ^[[3J. This _is_ implemented in the
|
||||
// Xterm256Engine.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - S_OK if we wrote the sequences successfully, otherwise an appropriate HRESULT
|
||||
[[nodiscard]] HRESULT VtEngine::ManuallyClearScrollback() noexcept
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Send a sequence to the connected terminal to request win32-input-mode from
|
||||
// them. This will enable the connected terminal to send us full INPUT_RECORDs
|
||||
// as input. If the terminal doesn't understand this sequence, it'll just
|
||||
// ignore it.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
|
||||
HRESULT VtEngine::RequestWin32Input() noexcept
|
||||
{
|
||||
// On startup we request the modes we require for optimal functioning
|
||||
// (namely win32 input mode and focus event mode).
|
||||
//
|
||||
// It's important that any additional modes set here are also mirrored in
|
||||
// the AdaptDispatch::HardReset method, since that needs to re-enable them
|
||||
// in the connected terminal after passing through an RIS sequence.
|
||||
RETURN_IF_FAILED(_Write("\033[?9001h\033[?1004h"));
|
||||
_Flush();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT VtEngine::SwitchScreenBuffer(const bool useAltBuffer) noexcept
|
||||
{
|
||||
RETURN_IF_FAILED(_SwitchScreenBuffer(useAltBuffer));
|
||||
_Flush();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT VtEngine::RequestMouseMode(const bool enable) noexcept
|
||||
{
|
||||
const auto status = _WriteFormatted(FMT_COMPILE("\x1b[?1003;1006{}"), enable ? 'h' : 'l');
|
||||
_Flush();
|
||||
return status;
|
||||
}
|
||||
@ -1,356 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
#include "tracing.hpp"
|
||||
#include <sstream>
|
||||
|
||||
TRACELOGGING_DEFINE_PROVIDER(g_hConsoleVtRendererTraceProvider,
|
||||
"Microsoft.Windows.Console.Render.VtEngine",
|
||||
// tl:{c9ba2a95-d3ca-5e19-2bd6-776a0910cb9d}
|
||||
(0xc9ba2a95, 0xd3ca, 0x5e19, 0x2b, 0xd6, 0x77, 0x6a, 0x09, 0x10, 0xcb, 0x9d),
|
||||
TraceLoggingOptionMicrosoftTelemetry());
|
||||
|
||||
using namespace Microsoft::Console::VirtualTerminal;
|
||||
using namespace Microsoft::Console::Types;
|
||||
|
||||
RenderTracing::RenderTracing()
|
||||
{
|
||||
#ifndef UNIT_TESTING
|
||||
TraceLoggingRegister(g_hConsoleVtRendererTraceProvider);
|
||||
#endif // UNIT_TESTING
|
||||
}
|
||||
|
||||
RenderTracing::~RenderTracing()
|
||||
{
|
||||
#ifndef UNIT_TESTING
|
||||
TraceLoggingUnregister(g_hConsoleVtRendererTraceProvider);
|
||||
#endif // UNIT_TESTING
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - Convert the string to only have printable characters in it. Control
|
||||
// characters are converted to hat notation, spaces are converted to "SPC"
|
||||
// (to be able to see them at the end of a string), and DEL is written as
|
||||
// "\x7f".
|
||||
// Arguments:
|
||||
// - inString: The string to convert
|
||||
// Return Value:
|
||||
// - a string with only printable characters in it.
|
||||
std::string toPrintableString(const std::string_view& inString)
|
||||
{
|
||||
std::string retval = "";
|
||||
for (size_t i = 0; i < inString.length(); i++)
|
||||
{
|
||||
unsigned char c = inString[i];
|
||||
if (c < '\x20')
|
||||
{
|
||||
retval += "^";
|
||||
char actual = (c + 0x40);
|
||||
retval += std::string(1, actual);
|
||||
}
|
||||
else if (c == '\x7f')
|
||||
{
|
||||
retval += "\\x7f";
|
||||
}
|
||||
else if (c == '\x20')
|
||||
{
|
||||
retval += "SPC";
|
||||
}
|
||||
else
|
||||
{
|
||||
retval += std::string(1, c);
|
||||
}
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
void RenderTracing::TraceStringFill(const size_t n, const char c) const
|
||||
{
|
||||
#ifndef UNIT_TESTING
|
||||
if (TraceLoggingProviderEnabled(g_hConsoleVtRendererTraceProvider, WINEVENT_LEVEL_VERBOSE, TIL_KEYWORD_TRACE))
|
||||
{
|
||||
TraceLoggingWrite(g_hConsoleVtRendererTraceProvider,
|
||||
"VtEngine_TraceStringFill",
|
||||
TraceLoggingUInt64(gsl::narrow_cast<uint64_t>(n)),
|
||||
TraceLoggingChar(c),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
}
|
||||
#else
|
||||
UNREFERENCED_PARAMETER(n);
|
||||
UNREFERENCED_PARAMETER(c);
|
||||
#endif // UNIT_TESTING
|
||||
}
|
||||
void RenderTracing::TraceString(const std::string_view& instr) const
|
||||
{
|
||||
#ifndef UNIT_TESTING
|
||||
if (TraceLoggingProviderEnabled(g_hConsoleVtRendererTraceProvider, WINEVENT_LEVEL_VERBOSE, TIL_KEYWORD_TRACE))
|
||||
{
|
||||
const auto _seq = toPrintableString(instr);
|
||||
const auto seq = _seq.c_str();
|
||||
TraceLoggingWrite(g_hConsoleVtRendererTraceProvider,
|
||||
"VtEngine_TraceString",
|
||||
TraceLoggingUtf8String(seq),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
}
|
||||
#else
|
||||
UNREFERENCED_PARAMETER(instr);
|
||||
#endif // UNIT_TESTING
|
||||
}
|
||||
|
||||
void RenderTracing::TraceInvalidate(const til::rect& invalidRect) const
|
||||
{
|
||||
#ifndef UNIT_TESTING
|
||||
if (TraceLoggingProviderEnabled(g_hConsoleVtRendererTraceProvider, WINEVENT_LEVEL_VERBOSE, TIL_KEYWORD_TRACE))
|
||||
{
|
||||
const auto invalidatedStr = invalidRect.to_string();
|
||||
const auto invalidated = invalidatedStr.c_str();
|
||||
TraceLoggingWrite(g_hConsoleVtRendererTraceProvider,
|
||||
"VtEngine_TraceInvalidate",
|
||||
TraceLoggingWideString(invalidated),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
}
|
||||
#else
|
||||
UNREFERENCED_PARAMETER(invalidRect);
|
||||
#endif // UNIT_TESTING
|
||||
}
|
||||
|
||||
void RenderTracing::TraceInvalidateAll(const til::rect& viewport) const
|
||||
{
|
||||
#ifndef UNIT_TESTING
|
||||
if (TraceLoggingProviderEnabled(g_hConsoleVtRendererTraceProvider, WINEVENT_LEVEL_VERBOSE, TIL_KEYWORD_TRACE))
|
||||
{
|
||||
const auto invalidatedStr = viewport.to_string();
|
||||
const auto invalidatedAll = invalidatedStr.c_str();
|
||||
TraceLoggingWrite(g_hConsoleVtRendererTraceProvider,
|
||||
"VtEngine_TraceInvalidateAll",
|
||||
TraceLoggingWideString(invalidatedAll),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
}
|
||||
#else
|
||||
UNREFERENCED_PARAMETER(viewport);
|
||||
#endif // UNIT_TESTING
|
||||
}
|
||||
|
||||
void RenderTracing::TraceTriggerCircling(const bool newFrame) const
|
||||
{
|
||||
#ifndef UNIT_TESTING
|
||||
TraceLoggingWrite(g_hConsoleVtRendererTraceProvider,
|
||||
"VtEngine_TraceTriggerCircling",
|
||||
TraceLoggingBool(newFrame),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
#else
|
||||
UNREFERENCED_PARAMETER(newFrame);
|
||||
#endif // UNIT_TESTING
|
||||
}
|
||||
|
||||
void RenderTracing::TraceInvalidateScroll(const til::point scroll) const
|
||||
{
|
||||
#ifndef UNIT_TESTING
|
||||
if (TraceLoggingProviderEnabled(g_hConsoleVtRendererTraceProvider, WINEVENT_LEVEL_VERBOSE, TIL_KEYWORD_TRACE))
|
||||
{
|
||||
const auto scrollDeltaStr = scroll.to_string();
|
||||
const auto scrollDelta = scrollDeltaStr.c_str();
|
||||
TraceLoggingWrite(g_hConsoleVtRendererTraceProvider,
|
||||
"VtEngine_TraceInvalidateScroll",
|
||||
TraceLoggingWideString(scrollDelta),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
}
|
||||
#else
|
||||
UNREFERENCED_PARAMETER(scroll);
|
||||
#endif
|
||||
}
|
||||
|
||||
void RenderTracing::TraceStartPaint(const bool quickReturn,
|
||||
const til::pmr::bitmap& invalidMap,
|
||||
const til::rect& lastViewport,
|
||||
const til::point scrollDelt,
|
||||
const bool cursorMoved,
|
||||
const std::optional<til::CoordType>& wrappedRow) const
|
||||
{
|
||||
#ifndef UNIT_TESTING
|
||||
if (TraceLoggingProviderEnabled(g_hConsoleVtRendererTraceProvider, WINEVENT_LEVEL_VERBOSE, TIL_KEYWORD_TRACE))
|
||||
{
|
||||
const auto invalidatedStr = invalidMap.to_string();
|
||||
const auto invalidated = invalidatedStr.c_str();
|
||||
const auto lastViewStr = lastViewport.to_string();
|
||||
const auto lastView = lastViewStr.c_str();
|
||||
const auto scrollDeltaStr = scrollDelt.to_string();
|
||||
const auto scrollDelta = scrollDeltaStr.c_str();
|
||||
if (wrappedRow.has_value())
|
||||
{
|
||||
TraceLoggingWrite(g_hConsoleVtRendererTraceProvider,
|
||||
"VtEngine_TraceStartPaint",
|
||||
TraceLoggingBool(quickReturn),
|
||||
TraceLoggingWideString(invalidated),
|
||||
TraceLoggingWideString(lastView),
|
||||
TraceLoggingWideString(scrollDelta),
|
||||
TraceLoggingBool(cursorMoved),
|
||||
TraceLoggingValue(wrappedRow.value()),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
}
|
||||
else
|
||||
{
|
||||
TraceLoggingWrite(g_hConsoleVtRendererTraceProvider,
|
||||
"VtEngine_TraceStartPaint",
|
||||
TraceLoggingBool(quickReturn),
|
||||
TraceLoggingWideString(invalidated),
|
||||
TraceLoggingWideString(lastView),
|
||||
TraceLoggingWideString(scrollDelta),
|
||||
TraceLoggingBool(cursorMoved),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
}
|
||||
}
|
||||
#else
|
||||
UNREFERENCED_PARAMETER(quickReturn);
|
||||
UNREFERENCED_PARAMETER(invalidMap);
|
||||
UNREFERENCED_PARAMETER(lastViewport);
|
||||
UNREFERENCED_PARAMETER(scrollDelt);
|
||||
UNREFERENCED_PARAMETER(cursorMoved);
|
||||
UNREFERENCED_PARAMETER(wrappedRow);
|
||||
#endif // UNIT_TESTING
|
||||
}
|
||||
|
||||
void RenderTracing::TraceEndPaint() const
|
||||
{
|
||||
#ifndef UNIT_TESTING
|
||||
TraceLoggingWrite(g_hConsoleVtRendererTraceProvider,
|
||||
"VtEngine_TraceEndPaint",
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
#else
|
||||
#endif // UNIT_TESTING
|
||||
}
|
||||
|
||||
void RenderTracing::TraceLastText(const til::point lastTextPos) const
|
||||
{
|
||||
#ifndef UNIT_TESTING
|
||||
if (TraceLoggingProviderEnabled(g_hConsoleVtRendererTraceProvider, WINEVENT_LEVEL_VERBOSE, TIL_KEYWORD_TRACE))
|
||||
{
|
||||
const auto lastTextStr = lastTextPos.to_string();
|
||||
const auto lastText = lastTextStr.c_str();
|
||||
TraceLoggingWrite(g_hConsoleVtRendererTraceProvider,
|
||||
"VtEngine_TraceLastText",
|
||||
TraceLoggingWideString(lastText),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
}
|
||||
#else
|
||||
UNREFERENCED_PARAMETER(lastTextPos);
|
||||
#endif // UNIT_TESTING
|
||||
}
|
||||
|
||||
void RenderTracing::TraceScrollFrame(const til::point scrollDeltaPos) const
|
||||
{
|
||||
#ifndef UNIT_TESTING
|
||||
if (TraceLoggingProviderEnabled(g_hConsoleVtRendererTraceProvider, WINEVENT_LEVEL_VERBOSE, TIL_KEYWORD_TRACE))
|
||||
{
|
||||
const auto scrollDeltaStr = scrollDeltaPos.to_string();
|
||||
const auto scrollDelta = scrollDeltaStr.c_str();
|
||||
TraceLoggingWrite(g_hConsoleVtRendererTraceProvider,
|
||||
"VtEngine_TraceScrollFrame",
|
||||
TraceLoggingWideString(scrollDelta),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
}
|
||||
#else
|
||||
UNREFERENCED_PARAMETER(scrollDeltaPos);
|
||||
#endif // UNIT_TESTING
|
||||
}
|
||||
|
||||
void RenderTracing::TraceMoveCursor(const til::point lastTextPos, const til::point cursor) const
|
||||
{
|
||||
#ifndef UNIT_TESTING
|
||||
if (TraceLoggingProviderEnabled(g_hConsoleVtRendererTraceProvider, WINEVENT_LEVEL_VERBOSE, TIL_KEYWORD_TRACE))
|
||||
{
|
||||
const auto lastTextStr = lastTextPos.to_string();
|
||||
const auto lastText = lastTextStr.c_str();
|
||||
|
||||
const auto cursorStr = cursor.to_string();
|
||||
const auto cursorPos = cursorStr.c_str();
|
||||
|
||||
TraceLoggingWrite(g_hConsoleVtRendererTraceProvider,
|
||||
"VtEngine_TraceMoveCursor",
|
||||
TraceLoggingWideString(lastText),
|
||||
TraceLoggingWideString(cursorPos),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
}
|
||||
#else
|
||||
UNREFERENCED_PARAMETER(lastTextPos);
|
||||
UNREFERENCED_PARAMETER(cursor);
|
||||
#endif // UNIT_TESTING
|
||||
}
|
||||
|
||||
void RenderTracing::TraceWrapped() const
|
||||
{
|
||||
#ifndef UNIT_TESTING
|
||||
if (TraceLoggingProviderEnabled(g_hConsoleVtRendererTraceProvider, WINEVENT_LEVEL_VERBOSE, TIL_KEYWORD_TRACE))
|
||||
{
|
||||
const auto* const msg = "Wrapped instead of \\r\\n";
|
||||
TraceLoggingWrite(g_hConsoleVtRendererTraceProvider,
|
||||
"VtEngine_TraceWrapped",
|
||||
TraceLoggingString(msg),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
}
|
||||
#else
|
||||
#endif // UNIT_TESTING
|
||||
}
|
||||
|
||||
void RenderTracing::TraceSetWrapped(const til::CoordType wrappedRow) const
|
||||
{
|
||||
#ifndef UNIT_TESTING
|
||||
if (TraceLoggingProviderEnabled(g_hConsoleVtRendererTraceProvider, WINEVENT_LEVEL_VERBOSE, TIL_KEYWORD_TRACE))
|
||||
{
|
||||
TraceLoggingWrite(g_hConsoleVtRendererTraceProvider,
|
||||
"VtEngine_TraceSetWrapped",
|
||||
TraceLoggingValue(wrappedRow),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
}
|
||||
#else
|
||||
UNREFERENCED_PARAMETER(wrappedRow);
|
||||
#endif // UNIT_TESTING
|
||||
}
|
||||
|
||||
void RenderTracing::TraceClearWrapped() const
|
||||
{
|
||||
#ifndef UNIT_TESTING
|
||||
if (TraceLoggingProviderEnabled(g_hConsoleVtRendererTraceProvider, WINEVENT_LEVEL_VERBOSE, TIL_KEYWORD_TRACE))
|
||||
{
|
||||
const auto* const msg = "Cleared wrap state";
|
||||
TraceLoggingWrite(g_hConsoleVtRendererTraceProvider,
|
||||
"VtEngine_TraceClearWrapped",
|
||||
TraceLoggingString(msg),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
}
|
||||
#else
|
||||
#endif // UNIT_TESTING
|
||||
}
|
||||
|
||||
void RenderTracing::TracePaintCursor(const til::point coordCursor) const
|
||||
{
|
||||
#ifndef UNIT_TESTING
|
||||
if (TraceLoggingProviderEnabled(g_hConsoleVtRendererTraceProvider, WINEVENT_LEVEL_VERBOSE, TIL_KEYWORD_TRACE))
|
||||
{
|
||||
const auto cursorPosString = coordCursor.to_string();
|
||||
const auto cursorPos = cursorPosString.c_str();
|
||||
TraceLoggingWrite(g_hConsoleVtRendererTraceProvider,
|
||||
"VtEngine_TracePaintCursor",
|
||||
TraceLoggingWideString(cursorPos),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
}
|
||||
#else
|
||||
UNREFERENCED_PARAMETER(coordCursor);
|
||||
#endif // UNIT_TESTING
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user