mirror of
https://github.com/microsoft/terminal.git
synced 2026-04-06 06:09:50 +00:00
Compare commits
57 Commits
main
...
v1.4.3243.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a512dddc08 | ||
|
|
c2bc927e50 | ||
|
|
689be38372 | ||
|
|
744631a293 | ||
|
|
89191f8211 | ||
|
|
fce00ff8f6 | ||
|
|
d1b26085e1 | ||
|
|
da26894434 | ||
|
|
5bfbebe3ac | ||
|
|
82534cf8e0 | ||
|
|
6cc7dfaf85 | ||
|
|
72ac061a8e | ||
|
|
2fcf5d7903 | ||
|
|
952f5b11ef | ||
|
|
489329bf41 | ||
|
|
3098fba203 | ||
|
|
1aa3909fea | ||
|
|
f4d6756b68 | ||
|
|
eecf76478b | ||
|
|
4ede37767a | ||
|
|
830c79be93 | ||
|
|
596597bca3 | ||
|
|
136db72b8a | ||
|
|
1607804dfc | ||
|
|
b2c521a372 | ||
|
|
7ca3e35b94 | ||
|
|
fe4f0f38cb | ||
|
|
bb5549a98d | ||
|
|
8a758f7f85 | ||
|
|
a0a70cc801 | ||
|
|
888b6e95b9 | ||
|
|
9f5967527c | ||
|
|
72bedcc159 | ||
|
|
d9c95ca31c | ||
|
|
74678ff2b0 | ||
|
|
67e205746c | ||
|
|
e3edf180a8 | ||
|
|
b78ceacd84 | ||
|
|
fd1afae214 | ||
|
|
ef5198e231 | ||
|
|
62d22c2a57 | ||
|
|
1279c63819 | ||
|
|
cf25fd0e7f | ||
|
|
afe5860ead | ||
|
|
c66f8feffb | ||
|
|
c529789bb3 | ||
|
|
57ca2ec8a4 | ||
|
|
2592cc41fe | ||
|
|
cf81d8c3c5 | ||
|
|
b49fa5ec59 | ||
|
|
ca0dc3d9fd | ||
|
|
825039ea17 | ||
|
|
21ab0a841d | ||
|
|
6ad0bb9232 | ||
|
|
a5b29b6abf | ||
|
|
a7a7d506d3 | ||
|
|
0e4ffd6f58 |
@@ -4,7 +4,6 @@ alignof
|
||||
bitfield
|
||||
bitfields
|
||||
CLASSNOTAVAILABLE
|
||||
environstrings
|
||||
EXPCMDFLAGS
|
||||
EXPCMDSTATE
|
||||
fullkbd
|
||||
@@ -42,10 +41,12 @@ rfind
|
||||
roundf
|
||||
RSHIFT
|
||||
rx
|
||||
schandle
|
||||
serializer
|
||||
SIZENS
|
||||
spsc
|
||||
STDCPP
|
||||
strchr
|
||||
syscall
|
||||
tmp
|
||||
tx
|
||||
|
||||
@@ -32,6 +32,7 @@ tasklist
|
||||
tdbuildteamid
|
||||
vcruntime
|
||||
visualstudio
|
||||
VSTHRD
|
||||
wlk
|
||||
wslpath
|
||||
wtl
|
||||
|
||||
@@ -31,11 +31,15 @@ mbadolato
|
||||
Mehrain
|
||||
mgravell
|
||||
michaelniksa
|
||||
michkap
|
||||
migrie
|
||||
mikegr
|
||||
mikemaccana
|
||||
miloush
|
||||
miniksa
|
||||
niksa
|
||||
nvaccess
|
||||
nvda
|
||||
oising
|
||||
oldnewthing
|
||||
osgwiki
|
||||
|
||||
7
.github/actions/spell-check/expect/655f007265b351e140d20b3976792523ad689241.txt
vendored
Normal file
7
.github/actions/spell-check/expect/655f007265b351e140d20b3976792523ad689241.txt
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
autogenerated
|
||||
CPPCORECHECK
|
||||
Debian
|
||||
filepath
|
||||
inplace
|
||||
KEYBDINPUT
|
||||
WINVER
|
||||
32
.github/actions/spell-check/expect/expect.txt
vendored
32
.github/actions/spell-check/expect/expect.txt
vendored
@@ -33,7 +33,6 @@ AHelper
|
||||
ahz
|
||||
AImpl
|
||||
AInplace
|
||||
akb
|
||||
ALIGNRIGHT
|
||||
alloc
|
||||
allocing
|
||||
@@ -62,7 +61,6 @@ apiset
|
||||
apos
|
||||
APPBARDATA
|
||||
appconsult
|
||||
appdata
|
||||
APPICON
|
||||
appium
|
||||
applet
|
||||
@@ -109,10 +107,8 @@ aumid
|
||||
Authenticode
|
||||
AUTOBUDDY
|
||||
AUTOCHECKBOX
|
||||
Autogenerated
|
||||
autohide
|
||||
AUTOHSCROLL
|
||||
autologin
|
||||
automagically
|
||||
autopositioning
|
||||
AUTORADIOBUTTON
|
||||
@@ -393,7 +389,6 @@ CPINFOEX
|
||||
cplinfo
|
||||
cplusplus
|
||||
cpp
|
||||
cppcorecheck
|
||||
cppcorecheckrules
|
||||
cpprest
|
||||
cpprestsdk
|
||||
@@ -510,10 +505,8 @@ DDESHARE
|
||||
DDevice
|
||||
DEADCHAR
|
||||
dealloc
|
||||
debian
|
||||
debolden
|
||||
debounce
|
||||
debugbreak
|
||||
DECALN
|
||||
DECANM
|
||||
DECAUPSS
|
||||
@@ -521,11 +514,9 @@ DECAWM
|
||||
DECCKM
|
||||
DECCOLM
|
||||
DECEKBD
|
||||
decf
|
||||
DECKPAM
|
||||
DECKPM
|
||||
DECKPNM
|
||||
DECLL
|
||||
DECLRMM
|
||||
decls
|
||||
declspec
|
||||
@@ -551,7 +542,6 @@ DECSEL
|
||||
DECSET
|
||||
DECSLPP
|
||||
DECSLRM
|
||||
DECSMBV
|
||||
DECSMKR
|
||||
DECSR
|
||||
decstandar
|
||||
@@ -714,7 +704,6 @@ EPres
|
||||
ERASEBKGND
|
||||
errno
|
||||
errorlevel
|
||||
esa
|
||||
ETB
|
||||
etcoreapp
|
||||
ETW
|
||||
@@ -769,7 +758,6 @@ fgetwc
|
||||
fgidx
|
||||
FILEDESCRIPTION
|
||||
fileno
|
||||
FILEPATH
|
||||
FILESUBTYPE
|
||||
FILESYSPATH
|
||||
filesystem
|
||||
@@ -857,6 +845,7 @@ GETAUTOHIDEBAREX
|
||||
GETCARETWIDTH
|
||||
getch
|
||||
getchar
|
||||
GETCLIENTAREAANIMATION
|
||||
GETCOMMANDHISTORY
|
||||
GETCOMMANDHISTORYLENGTH
|
||||
GETCONSOLEINPUT
|
||||
@@ -929,7 +918,6 @@ GTP
|
||||
guc
|
||||
gui
|
||||
guidatom
|
||||
guidgenerator
|
||||
GValue
|
||||
GWL
|
||||
GWLP
|
||||
@@ -997,6 +985,7 @@ hpj
|
||||
hpp
|
||||
HPR
|
||||
HPROPSHEETPAGE
|
||||
HProvider
|
||||
HREDRAW
|
||||
hresult
|
||||
HRSRC
|
||||
@@ -1099,7 +1088,6 @@ Inlines
|
||||
INotify
|
||||
inout
|
||||
INPATHROOT
|
||||
Inplace
|
||||
inproc
|
||||
Inputkeyinfo
|
||||
INPUTPROCESSORPROFILE
|
||||
@@ -1188,11 +1176,9 @@ kcud
|
||||
kcuf
|
||||
kcuu
|
||||
Kd
|
||||
keith
|
||||
kernelbase
|
||||
kernelbasestaging
|
||||
keybinding
|
||||
keybound
|
||||
keychord
|
||||
keydown
|
||||
keyevent
|
||||
@@ -1465,7 +1451,6 @@ namestream
|
||||
Namquiseratal
|
||||
nano
|
||||
natvis
|
||||
naws
|
||||
nbsp
|
||||
Nc
|
||||
NCCALCSIZE
|
||||
@@ -1547,7 +1532,6 @@ NOTNULL
|
||||
NOTOPMOST
|
||||
NOTRACK
|
||||
NOTSUPPORTED
|
||||
notypeopt
|
||||
nouicompat
|
||||
nounihan
|
||||
NOUPDATE
|
||||
@@ -1622,7 +1606,6 @@ opencon
|
||||
openconsole
|
||||
OPENIF
|
||||
OPENLINK
|
||||
openlogo
|
||||
openps
|
||||
opensource
|
||||
openvt
|
||||
@@ -1961,7 +1944,6 @@ resheader
|
||||
resizable
|
||||
resmimetype
|
||||
restrictedcapabilities
|
||||
restrictederrorinfo
|
||||
resw
|
||||
resx
|
||||
retval
|
||||
@@ -1988,7 +1970,6 @@ rgw
|
||||
rgwch
|
||||
rhs
|
||||
ri
|
||||
richturn
|
||||
RIGHTALIGN
|
||||
RIGHTBUTTON
|
||||
riid
|
||||
@@ -2062,7 +2043,6 @@ SCROLLSCALE
|
||||
SCROLLSCREENBUFFER
|
||||
Scrollup
|
||||
Scrolluppage
|
||||
Scs
|
||||
scursor
|
||||
sddl
|
||||
sdeleted
|
||||
@@ -2236,7 +2216,6 @@ subkey
|
||||
SUBLANG
|
||||
sublicensable
|
||||
submenu
|
||||
subnegotiation
|
||||
subresource
|
||||
subspan
|
||||
substr
|
||||
@@ -2247,7 +2226,6 @@ svg
|
||||
swapchain
|
||||
swapchainpanel
|
||||
swappable
|
||||
Switchto
|
||||
SWMR
|
||||
SWP
|
||||
swprintf
|
||||
@@ -2302,7 +2280,6 @@ technet
|
||||
tellp
|
||||
telnet
|
||||
telnetd
|
||||
telnetpp
|
||||
templated
|
||||
terminalcore
|
||||
TERMINALSCROLLING
|
||||
@@ -2699,12 +2676,10 @@ wintelnet
|
||||
winternl
|
||||
winuser
|
||||
winuserp
|
||||
winver
|
||||
wistd
|
||||
wixproj
|
||||
wline
|
||||
wlinestream
|
||||
Wlk
|
||||
wmain
|
||||
WMSZ
|
||||
wnd
|
||||
@@ -2715,6 +2690,7 @@ WNDCLASSW
|
||||
Wndproc
|
||||
WNegative
|
||||
WNull
|
||||
wnwb
|
||||
workarea
|
||||
workaround
|
||||
workflow
|
||||
@@ -2746,7 +2722,6 @@ WRunoff
|
||||
WScript
|
||||
wsl
|
||||
WSLENV
|
||||
wslhome
|
||||
wsmatch
|
||||
WSpace
|
||||
wss
|
||||
@@ -2762,6 +2737,7 @@ wtof
|
||||
wtoi
|
||||
wtw
|
||||
wtypes
|
||||
Wubi
|
||||
WUX
|
||||
WVerify
|
||||
wwaproj
|
||||
|
||||
@@ -18,5 +18,5 @@ TestUtils::VerifyExpectedString\(tb, L"[^"]+"
|
||||
\b([A-Za-z])\1{3,}\b
|
||||
Base64::s_(?:En|De)code\(L"[^"]+"
|
||||
VERIFY_ARE_EQUAL\(L"[^"]+"
|
||||
L"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\+/"
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\+/"
|
||||
std::memory_order_[\w]+
|
||||
|
||||
30
NOTICE.md
30
NOTICE.md
@@ -48,36 +48,6 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
```
|
||||
|
||||
## telnetpp
|
||||
|
||||
**Source**: https://github.com/KazDragon/telnetpp
|
||||
|
||||
### License
|
||||
|
||||
```
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-2017 Matthew Chaplain a.k.a KazDragon
|
||||
|
||||
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.
|
||||
```
|
||||
|
||||
## chromium/base/numerics
|
||||
|
||||
**Source**: https://github.com/chromium/chromium/tree/master/base/numerics
|
||||
|
||||
@@ -620,7 +620,7 @@
|
||||
"type": "boolean"
|
||||
},
|
||||
"useTabSwitcher": {
|
||||
"default": true,
|
||||
"default": false,
|
||||
"description": "When set to \"true\", the \"nextTab\" and \"prevTab\" commands will use the tab switcher UI.",
|
||||
"type": "boolean"
|
||||
}
|
||||
|
||||
BIN
res/Cascadia.ttf
BIN
res/Cascadia.ttf
Binary file not shown.
Binary file not shown.
@@ -17,5 +17,5 @@ Please consult the [license](https://raw.githubusercontent.com/microsoft/cascadi
|
||||
|
||||
### Fonts Included
|
||||
|
||||
* Cascadia Code, Cascadia Mono (2008.25)
|
||||
* from microsoft/cascadia-code@678eea921b0c8b921b9fb009bb16d3d2ca5b8112
|
||||
* Cascadia Code, Cascadia Mono (2009.21)
|
||||
* from microsoft/cascadia-code@32f84124db1970fa5d032f0fe9019e6922961beb
|
||||
|
||||
@@ -88,16 +88,18 @@ bool TextAttribute::IsLegacy() const noexcept
|
||||
// - defaultFgColor: the default foreground color rgb value.
|
||||
// - defaultBgColor: the default background color rgb value.
|
||||
// - reverseScreenMode: true if the screen mode is reversed.
|
||||
// - blinkingIsFaint: true if blinking should be interpreted as faint.
|
||||
// Return Value:
|
||||
// - the foreground and background colors that should be displayed.
|
||||
std::pair<COLORREF, COLORREF> TextAttribute::CalculateRgbColors(const gsl::span<const COLORREF> colorTable,
|
||||
const COLORREF defaultFgColor,
|
||||
const COLORREF defaultBgColor,
|
||||
const bool reverseScreenMode) const noexcept
|
||||
const bool reverseScreenMode,
|
||||
const bool blinkingIsFaint) const noexcept
|
||||
{
|
||||
auto fg = _foreground.GetColor(colorTable, defaultFgColor, IsBold());
|
||||
auto bg = _background.GetColor(colorTable, defaultBgColor);
|
||||
if (IsFaint())
|
||||
if (IsFaint() || (IsBlinking() && blinkingIsFaint))
|
||||
{
|
||||
fg = (fg >> 1) & 0x7F7F7F; // Divide foreground color components by two.
|
||||
}
|
||||
|
||||
@@ -69,7 +69,8 @@ public:
|
||||
std::pair<COLORREF, COLORREF> CalculateRgbColors(const gsl::span<const COLORREF> colorTable,
|
||||
const COLORREF defaultFgColor,
|
||||
const COLORREF defaultBgColor,
|
||||
const bool reverseScreenMode = false) const noexcept;
|
||||
const bool reverseScreenMode = false,
|
||||
const bool blinkingIsFaint = false) const noexcept;
|
||||
|
||||
bool IsLeadingByte() const noexcept;
|
||||
bool IsTrailingByte() const noexcept;
|
||||
@@ -151,6 +152,8 @@ public:
|
||||
return !IsAnyGridLineEnabled() && // grid lines have a visual representation
|
||||
// crossed out, doubly and singly underlined have a visual representation
|
||||
WI_AreAllFlagsClear(_extendedAttrs, ExtendedAttributes::CrossedOut | ExtendedAttributes::DoublyUnderlined | ExtendedAttributes::Underlined) &&
|
||||
// hyperlinks have a visual representation
|
||||
!IsHyperlink() &&
|
||||
// all other attributes do not have a visual representation
|
||||
(_wAttrLegacy & META_ATTRS) == (other._wAttrLegacy & META_ATTRS) &&
|
||||
((checkForeground && _foreground == other._foreground) ||
|
||||
|
||||
@@ -996,19 +996,29 @@ const COORD TextBuffer::GetWordStart(const COORD target, const std::wstring_view
|
||||
// so the words in the example include ["word ", "other "]
|
||||
// NOTE: the start anchor (this one) is inclusive, whereas the end anchor (GetWordEnd) is exclusive
|
||||
|
||||
// can't expand left
|
||||
if (target.X == GetSize().Left())
|
||||
#pragma warning(suppress : 26496)
|
||||
// GH#7664: Treat EndExclusive as EndInclusive so
|
||||
// that it actually points to a space in the buffer
|
||||
auto copy{ target };
|
||||
const auto bufferSize{ GetSize() };
|
||||
if (target == bufferSize.Origin())
|
||||
{
|
||||
// can't expand left
|
||||
return target;
|
||||
}
|
||||
else if (target == bufferSize.EndExclusive())
|
||||
{
|
||||
// treat EndExclusive as EndInclusive
|
||||
copy = { bufferSize.RightInclusive(), bufferSize.BottomInclusive() };
|
||||
}
|
||||
|
||||
if (accessibilityMode)
|
||||
{
|
||||
return _GetWordStartForAccessibility(target, wordDelimiters);
|
||||
return _GetWordStartForAccessibility(copy, wordDelimiters);
|
||||
}
|
||||
else
|
||||
{
|
||||
return _GetWordStartForSelection(target, wordDelimiters);
|
||||
return _GetWordStartForSelection(copy, wordDelimiters);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1108,9 +1118,16 @@ const COORD TextBuffer::GetWordEnd(const COORD target, const std::wstring_view w
|
||||
// so the words in the example include ["word ", "other "]
|
||||
// NOTE: the end anchor (this one) is exclusive, whereas the start anchor (GetWordStart) is inclusive
|
||||
|
||||
// Already at the end. Can't move forward.
|
||||
if (target == GetSize().EndExclusive())
|
||||
{
|
||||
return target;
|
||||
}
|
||||
|
||||
if (accessibilityMode)
|
||||
{
|
||||
return _GetWordEndForAccessibility(target, wordDelimiters);
|
||||
const auto lastCharPos{ GetLastNonSpaceCharacter() };
|
||||
return _GetWordEndForAccessibility(target, wordDelimiters, lastCharPos);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1123,13 +1140,20 @@ const COORD TextBuffer::GetWordEnd(const COORD target, const std::wstring_view w
|
||||
// Arguments:
|
||||
// - target - a COORD on the word you are currently on
|
||||
// - wordDelimiters - what characters are we considering for the separation of words
|
||||
// - lastCharPos - the position of the last nonspace character in the text buffer (to improve performance)
|
||||
// Return Value:
|
||||
// - The COORD for the first character of the next readable "word". If no next word, return one past the end of the buffer
|
||||
const COORD TextBuffer::_GetWordEndForAccessibility(const COORD target, const std::wstring_view wordDelimiters) const
|
||||
const COORD TextBuffer::_GetWordEndForAccessibility(const COORD target, const std::wstring_view wordDelimiters, const COORD lastCharPos) const
|
||||
{
|
||||
const auto bufferSize = GetSize();
|
||||
COORD result = target;
|
||||
|
||||
// Check if we're already on/past the last RegularChar
|
||||
if (bufferSize.CompareInBounds(result, lastCharPos, true) >= 0)
|
||||
{
|
||||
return bufferSize.EndExclusive();
|
||||
}
|
||||
|
||||
// ignore right boundary. Continue through readable text found
|
||||
while (_GetDelimiterClassAt(result, wordDelimiters) == DelimiterClass::RegularChar)
|
||||
{
|
||||
@@ -1139,6 +1163,12 @@ const COORD TextBuffer::_GetWordEndForAccessibility(const COORD target, const st
|
||||
}
|
||||
}
|
||||
|
||||
// we are already on/past the last RegularChar
|
||||
if (bufferSize.CompareInBounds(result, lastCharPos, true) >= 0)
|
||||
{
|
||||
return bufferSize.EndExclusive();
|
||||
}
|
||||
|
||||
// make sure we expand to the beginning of the NEXT word
|
||||
while (_GetDelimiterClassAt(result, wordDelimiters) != DelimiterClass::RegularChar)
|
||||
{
|
||||
@@ -1240,38 +1270,16 @@ void TextBuffer::_PruneHyperlinks()
|
||||
// - pos - The COORD for the first character on the "word" (inclusive)
|
||||
bool TextBuffer::MoveToNextWord(COORD& pos, const std::wstring_view wordDelimiters, COORD lastCharPos) const
|
||||
{
|
||||
auto copy = pos;
|
||||
const auto bufferSize = GetSize();
|
||||
// move to the beginning of the next word
|
||||
// NOTE: _GetWordEnd...() returns the exclusive position of the "end of the word"
|
||||
// This is also the inclusive start of the next word.
|
||||
auto copy{ _GetWordEndForAccessibility(pos, wordDelimiters, lastCharPos) };
|
||||
|
||||
// started on a word, continue until the end of the word
|
||||
while (_GetDelimiterClassAt(copy, wordDelimiters) == DelimiterClass::RegularChar)
|
||||
{
|
||||
if (!bufferSize.IncrementInBounds(copy))
|
||||
{
|
||||
// last char in buffer is a RegularChar
|
||||
// thus there is no next word
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// we are already on/past the last RegularChar
|
||||
if (bufferSize.CompareInBounds(copy, lastCharPos) >= 0)
|
||||
if (copy == GetSize().EndExclusive())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// on whitespace, continue until the beginning of the next word
|
||||
while (_GetDelimiterClassAt(copy, wordDelimiters) != DelimiterClass::RegularChar)
|
||||
{
|
||||
if (!bufferSize.IncrementInBounds(copy))
|
||||
{
|
||||
// last char in buffer is a DelimiterChar or ControlChar
|
||||
// there is no next word
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// successful move, copy result out
|
||||
pos = copy;
|
||||
return true;
|
||||
}
|
||||
@@ -1286,33 +1294,17 @@ bool TextBuffer::MoveToNextWord(COORD& pos, const std::wstring_view wordDelimite
|
||||
// - pos - The COORD for the first character on the "word" (inclusive)
|
||||
bool TextBuffer::MoveToPreviousWord(COORD& pos, std::wstring_view wordDelimiters) const
|
||||
{
|
||||
auto copy = pos;
|
||||
auto bufferSize = GetSize();
|
||||
// move to the beginning of the current word
|
||||
auto copy{ GetWordStart(pos, wordDelimiters, true) };
|
||||
|
||||
// started on whitespace/delimiter, continue until the end of the previous word
|
||||
while (_GetDelimiterClassAt(copy, wordDelimiters) != DelimiterClass::RegularChar)
|
||||
if (!GetSize().DecrementInBounds(copy, true))
|
||||
{
|
||||
if (!bufferSize.DecrementInBounds(copy))
|
||||
{
|
||||
// first char in buffer is a DelimiterChar or ControlChar
|
||||
// there is no previous word
|
||||
return false;
|
||||
}
|
||||
// can't move behind current word
|
||||
return false;
|
||||
}
|
||||
|
||||
// on a word, continue until the beginning of the word
|
||||
while (_GetDelimiterClassAt(copy, wordDelimiters) == DelimiterClass::RegularChar)
|
||||
{
|
||||
if (!bufferSize.DecrementInBounds(copy))
|
||||
{
|
||||
// first char in buffer is a RegularChar
|
||||
// there is no previous word
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// successful move, copy result out
|
||||
pos = copy;
|
||||
// move to the beginning of the previous word
|
||||
pos = GetWordStart(copy, wordDelimiters, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1325,8 +1317,13 @@ bool TextBuffer::MoveToPreviousWord(COORD& pos, std::wstring_view wordDelimiters
|
||||
const til::point TextBuffer::GetGlyphStart(const til::point pos) const
|
||||
{
|
||||
COORD resultPos = pos;
|
||||
|
||||
const auto bufferSize = GetSize();
|
||||
|
||||
if (resultPos == bufferSize.EndExclusive())
|
||||
{
|
||||
bufferSize.DecrementInBounds(resultPos, true);
|
||||
}
|
||||
|
||||
if (resultPos != bufferSize.EndExclusive() && GetCellDataAt(resultPos)->DbcsAttr().IsTrailing())
|
||||
{
|
||||
bufferSize.DecrementInBounds(resultPos, true);
|
||||
@@ -1367,9 +1364,15 @@ const til::point TextBuffer::GetGlyphEnd(const til::point pos) const
|
||||
bool TextBuffer::MoveToNextGlyph(til::point& pos, bool allowBottomExclusive) const
|
||||
{
|
||||
COORD resultPos = pos;
|
||||
const auto bufferSize = GetSize();
|
||||
|
||||
if (resultPos == GetSize().EndExclusive())
|
||||
{
|
||||
// we're already at the end
|
||||
return false;
|
||||
}
|
||||
|
||||
// try to move. If we can't, we're done.
|
||||
const auto bufferSize = GetSize();
|
||||
const bool success = bufferSize.IncrementInBounds(resultPos, allowBottomExclusive);
|
||||
if (resultPos != bufferSize.EndExclusive() && GetCellDataAt(resultPos)->DbcsAttr().IsTrailing())
|
||||
{
|
||||
@@ -1384,20 +1387,19 @@ bool TextBuffer::MoveToNextGlyph(til::point& pos, bool allowBottomExclusive) con
|
||||
// - Update pos to be the beginning of the previous glyph/character. This is used for accessibility
|
||||
// Arguments:
|
||||
// - pos - a COORD on the word you are currently on
|
||||
// - allowBottomExclusive - allow the nonexistent end-of-buffer cell to be encountered
|
||||
// Return Value:
|
||||
// - true, if successfully updated pos. False, if we are unable to move (usually due to a buffer boundary)
|
||||
// - pos - The COORD for the first cell of the previous glyph (inclusive)
|
||||
bool TextBuffer::MoveToPreviousGlyph(til::point& pos, bool allowBottomExclusive) const
|
||||
bool TextBuffer::MoveToPreviousGlyph(til::point& pos) const
|
||||
{
|
||||
COORD resultPos = pos;
|
||||
|
||||
// try to move. If we can't, we're done.
|
||||
const auto bufferSize = GetSize();
|
||||
const bool success = bufferSize.DecrementInBounds(resultPos, allowBottomExclusive);
|
||||
const bool success = bufferSize.DecrementInBounds(resultPos, true);
|
||||
if (resultPos != bufferSize.EndExclusive() && GetCellDataAt(resultPos)->DbcsAttr().IsLeading())
|
||||
{
|
||||
bufferSize.DecrementInBounds(resultPos, allowBottomExclusive);
|
||||
bufferSize.DecrementInBounds(resultPos, true);
|
||||
}
|
||||
|
||||
pos = resultPos;
|
||||
@@ -2279,32 +2281,35 @@ std::wstring TextBuffer::GetHyperlinkUriFromId(uint16_t id) const
|
||||
// - The user-defined id
|
||||
// Return value:
|
||||
// - The internal hyperlink ID
|
||||
uint16_t TextBuffer::GetHyperlinkId(std::wstring_view params)
|
||||
uint16_t TextBuffer::GetHyperlinkId(std::wstring_view uri, std::wstring_view id)
|
||||
{
|
||||
uint16_t id = 0;
|
||||
if (params.empty())
|
||||
uint16_t numericId = 0;
|
||||
if (id.empty())
|
||||
{
|
||||
// no custom id specified, return our internal count
|
||||
id = _currentHyperlinkId;
|
||||
numericId = _currentHyperlinkId;
|
||||
++_currentHyperlinkId;
|
||||
}
|
||||
else
|
||||
{
|
||||
// assign _currentHyperlinkId if the custom id does not already exist
|
||||
const auto result = _hyperlinkCustomIdMap.emplace(params, _currentHyperlinkId);
|
||||
std::wstring newId{ id };
|
||||
// hash the URL and add it to the custom ID - GH#7698
|
||||
newId += L"%" + std::to_wstring(std::hash<std::wstring_view>{}(uri));
|
||||
const auto result = _hyperlinkCustomIdMap.emplace(newId, _currentHyperlinkId);
|
||||
if (result.second)
|
||||
{
|
||||
// the custom id did not already exist
|
||||
++_currentHyperlinkId;
|
||||
}
|
||||
id = (*(result.first)).second;
|
||||
numericId = (*(result.first)).second;
|
||||
}
|
||||
// _currentHyperlinkId could overflow, make sure its not 0
|
||||
if (_currentHyperlinkId == 0)
|
||||
{
|
||||
++_currentHyperlinkId;
|
||||
}
|
||||
return id;
|
||||
return numericId;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -2345,11 +2350,13 @@ std::wstring TextBuffer::GetCustomIdFromId(uint16_t id) const
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Copies the hyperlink/customID maps of the old buffer into this one
|
||||
// - Copies the hyperlink/customID maps of the old buffer into this one,
|
||||
// also copies currentHyperlinkId
|
||||
// Arguments:
|
||||
// - The other buffer
|
||||
void TextBuffer::CopyHyperlinkMaps(const TextBuffer& other)
|
||||
{
|
||||
_hyperlinkMap = other._hyperlinkMap;
|
||||
_hyperlinkCustomIdMap = other._hyperlinkCustomIdMap;
|
||||
_currentHyperlinkId = other._currentHyperlinkId;
|
||||
}
|
||||
|
||||
@@ -137,13 +137,13 @@ public:
|
||||
const til::point GetGlyphStart(const til::point pos) const;
|
||||
const til::point GetGlyphEnd(const til::point pos) const;
|
||||
bool MoveToNextGlyph(til::point& pos, bool allowBottomExclusive = false) const;
|
||||
bool MoveToPreviousGlyph(til::point& pos, bool allowBottomExclusive = false) const;
|
||||
bool MoveToPreviousGlyph(til::point& pos) const;
|
||||
|
||||
const std::vector<SMALL_RECT> GetTextRects(COORD start, COORD end, bool blockSelection = false) const;
|
||||
|
||||
void AddHyperlinkToMap(std::wstring_view uri, uint16_t id);
|
||||
std::wstring GetHyperlinkUriFromId(uint16_t id) const;
|
||||
uint16_t GetHyperlinkId(std::wstring_view params);
|
||||
uint16_t GetHyperlinkId(std::wstring_view uri, std::wstring_view id);
|
||||
void RemoveHyperlinkFromMap(uint16_t id);
|
||||
std::wstring GetCustomIdFromId(uint16_t id) const;
|
||||
void CopyHyperlinkMaps(const TextBuffer& OtherBuffer);
|
||||
@@ -224,7 +224,7 @@ private:
|
||||
const DelimiterClass _GetDelimiterClassAt(const COORD pos, const std::wstring_view wordDelimiters) const;
|
||||
const COORD _GetWordStartForAccessibility(const COORD target, const std::wstring_view wordDelimiters) const;
|
||||
const COORD _GetWordStartForSelection(const COORD target, const std::wstring_view wordDelimiters) const;
|
||||
const COORD _GetWordEndForAccessibility(const COORD target, const std::wstring_view wordDelimiters) const;
|
||||
const COORD _GetWordEndForAccessibility(const COORD target, const std::wstring_view wordDelimiters, const COORD lastCharPos) const;
|
||||
const COORD _GetWordEndForSelection(const COORD target, const std::wstring_view wordDelimiters) const;
|
||||
|
||||
void _PruneHyperlinks();
|
||||
|
||||
@@ -62,6 +62,10 @@ namespace TerminalAppLocalTests
|
||||
TEST_METHOD(TryDuplicateBadTab);
|
||||
TEST_METHOD(TryDuplicateBadPane);
|
||||
|
||||
TEST_METHOD(TryZoomPane);
|
||||
TEST_METHOD(MoveFocusFromZoomedPane);
|
||||
TEST_METHOD(CloseZoomedPane);
|
||||
|
||||
TEST_CLASS_SETUP(ClassSetup)
|
||||
{
|
||||
InitializeJsonReader();
|
||||
@@ -76,6 +80,7 @@ namespace TerminalAppLocalTests
|
||||
private:
|
||||
void _initializeTerminalPage(winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage>& page,
|
||||
winrt::com_ptr<winrt::TerminalApp::implementation::CascadiaSettings>& initialSettings);
|
||||
winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage> _commonSetup();
|
||||
};
|
||||
|
||||
void TabTests::EnsureTestsActivate()
|
||||
@@ -517,4 +522,192 @@ namespace TerminalAppLocalTests
|
||||
});
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - This is a helper method for setting up a TerminalPage with some common
|
||||
// settings, and creating the first tab.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - The initialized TerminalPage, ready to use.
|
||||
winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage> TabTests::_commonSetup()
|
||||
{
|
||||
const std::string settingsJson0{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"profiles": [
|
||||
{
|
||||
"name" : "profile0",
|
||||
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 1
|
||||
},
|
||||
{
|
||||
"name" : "profile1",
|
||||
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 2
|
||||
}
|
||||
]
|
||||
})" };
|
||||
|
||||
CascadiaSettings settings0{ til::u8u16(settingsJson0) };
|
||||
VERIFY_IS_NOT_NULL(settings0);
|
||||
|
||||
const auto guid1 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}");
|
||||
const auto guid2 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}");
|
||||
|
||||
// This is super wacky, but we can't just initialize the
|
||||
// com_ptr<impl::TerminalPage> in the lambda and assign it back out of
|
||||
// the lambda. We'll crash trying to get a weak_ref to the TerminalPage
|
||||
// during TerminalPage::Create() below.
|
||||
//
|
||||
// Instead, create the winrt object, then get a com_ptr to the
|
||||
// implementation _from_ the winrt object. This seems to work, even if
|
||||
// it's weird.
|
||||
winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage> page{ nullptr };
|
||||
_initializeTerminalPage(page, settings0);
|
||||
|
||||
auto result = RunOnUIThread([&page]() {
|
||||
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
|
||||
return page;
|
||||
}
|
||||
|
||||
void TabTests::TryZoomPane()
|
||||
{
|
||||
auto page = _commonSetup();
|
||||
|
||||
Log::Comment(L"Create a second pane");
|
||||
auto result = RunOnUIThread([&page]() {
|
||||
SplitPaneArgs args{ SplitType::Duplicate };
|
||||
ActionEventArgs eventArgs{ args };
|
||||
// eventArgs.Args(args);
|
||||
page->_HandleSplitPane(nullptr, eventArgs);
|
||||
auto firstTab = page->_GetStrongTabImpl(0);
|
||||
|
||||
VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount());
|
||||
VERIFY_IS_FALSE(firstTab->IsZoomed());
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
|
||||
Log::Comment(L"Zoom in on the pane");
|
||||
result = RunOnUIThread([&page]() {
|
||||
ActionEventArgs eventArgs{};
|
||||
page->_HandleTogglePaneZoom(nullptr, eventArgs);
|
||||
auto firstTab = page->_GetStrongTabImpl(0);
|
||||
VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount());
|
||||
VERIFY_IS_TRUE(firstTab->IsZoomed());
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
|
||||
Log::Comment(L"Zoom out of the pane");
|
||||
result = RunOnUIThread([&page]() {
|
||||
ActionEventArgs eventArgs{};
|
||||
page->_HandleTogglePaneZoom(nullptr, eventArgs);
|
||||
auto firstTab = page->_GetStrongTabImpl(0);
|
||||
VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount());
|
||||
VERIFY_IS_FALSE(firstTab->IsZoomed());
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
}
|
||||
|
||||
void TabTests::MoveFocusFromZoomedPane()
|
||||
{
|
||||
auto page = _commonSetup();
|
||||
|
||||
Log::Comment(L"Create a second pane");
|
||||
auto result = RunOnUIThread([&page]() {
|
||||
// Set up action
|
||||
SplitPaneArgs args{ SplitType::Duplicate };
|
||||
ActionEventArgs eventArgs{ args };
|
||||
page->_HandleSplitPane(nullptr, eventArgs);
|
||||
auto firstTab = page->_GetStrongTabImpl(0);
|
||||
|
||||
VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount());
|
||||
VERIFY_IS_FALSE(firstTab->IsZoomed());
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
|
||||
Log::Comment(L"Zoom in on the pane");
|
||||
result = RunOnUIThread([&page]() {
|
||||
// Set up action
|
||||
ActionEventArgs eventArgs{};
|
||||
|
||||
page->_HandleTogglePaneZoom(nullptr, eventArgs);
|
||||
|
||||
auto firstTab = page->_GetStrongTabImpl(0);
|
||||
VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount());
|
||||
VERIFY_IS_TRUE(firstTab->IsZoomed());
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
|
||||
Log::Comment(L"Move focus. This will cause us to un-zoom.");
|
||||
result = RunOnUIThread([&page]() {
|
||||
// Set up action
|
||||
MoveFocusArgs args{ Direction::Left };
|
||||
ActionEventArgs eventArgs{ args };
|
||||
|
||||
page->_HandleMoveFocus(nullptr, eventArgs);
|
||||
|
||||
auto firstTab = page->_GetStrongTabImpl(0);
|
||||
VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount());
|
||||
VERIFY_IS_FALSE(firstTab->IsZoomed());
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
}
|
||||
|
||||
void TabTests::CloseZoomedPane()
|
||||
{
|
||||
auto page = _commonSetup();
|
||||
|
||||
Log::Comment(L"Create a second pane");
|
||||
auto result = RunOnUIThread([&page]() {
|
||||
// Set up action
|
||||
SplitPaneArgs args{ SplitType::Duplicate };
|
||||
ActionEventArgs eventArgs{ args };
|
||||
page->_HandleSplitPane(nullptr, eventArgs);
|
||||
auto firstTab = page->_GetStrongTabImpl(0);
|
||||
|
||||
VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount());
|
||||
VERIFY_IS_FALSE(firstTab->IsZoomed());
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
|
||||
Log::Comment(L"Zoom in on the pane");
|
||||
result = RunOnUIThread([&page]() {
|
||||
// Set up action
|
||||
ActionEventArgs eventArgs{};
|
||||
|
||||
page->_HandleTogglePaneZoom(nullptr, eventArgs);
|
||||
|
||||
auto firstTab = page->_GetStrongTabImpl(0);
|
||||
VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount());
|
||||
VERIFY_IS_TRUE(firstTab->IsZoomed());
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
|
||||
Log::Comment(L"Close Pane. This should cause us to un-zoom, and remove the second pane from the tree");
|
||||
result = RunOnUIThread([&page]() {
|
||||
// Set up action
|
||||
ActionEventArgs eventArgs{};
|
||||
|
||||
page->_HandleClosePane(nullptr, eventArgs);
|
||||
|
||||
auto firstTab = page->_GetStrongTabImpl(0);
|
||||
VERIFY_IS_FALSE(firstTab->IsZoomed());
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
|
||||
// Introduce a slight delay to let the events finish propagating
|
||||
Sleep(250);
|
||||
|
||||
Log::Comment(L"Check to ensure there's only one pane left.");
|
||||
|
||||
result = RunOnUIThread([&page]() {
|
||||
auto firstTab = page->_GetStrongTabImpl(0);
|
||||
VERIFY_ARE_EQUAL(1, firstTab->GetLeafPaneCount());
|
||||
VERIFY_IS_FALSE(firstTab->IsZoomed());
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -433,7 +433,15 @@ void _stdcall TerminalSendOutput(void* terminal, LPCWSTR data)
|
||||
publicTerminal->SendOutput(data);
|
||||
}
|
||||
|
||||
HRESULT _stdcall TerminalTriggerResize(void* terminal, double width, double height, _Out_ COORD* dimensions)
|
||||
/// <summary>
|
||||
/// Triggers a terminal resize using the new width and height in pixel.
|
||||
/// </summary>
|
||||
/// <param name="terminal">Terminal pointer.</param>
|
||||
/// <param name="width">New width of the terminal in pixels.</param>
|
||||
/// <param name="height">New height of the terminal in pixels</param>
|
||||
/// <param name="dimensions">Out parameter containing the columns and rows that fit the new size.</param>
|
||||
/// <returns>HRESULT of the attempted resize.</returns>
|
||||
HRESULT _stdcall TerminalTriggerResize(_In_ void* terminal, _In_ short width, _In_ short height, _Out_ COORD* dimensions)
|
||||
{
|
||||
const auto publicTerminal = static_cast<HwndTerminal*>(terminal);
|
||||
|
||||
@@ -446,10 +454,55 @@ HRESULT _stdcall TerminalTriggerResize(void* terminal, double width, double heig
|
||||
static_cast<int>(height),
|
||||
0));
|
||||
|
||||
const SIZE windowSize{ static_cast<short>(width), static_cast<short>(height) };
|
||||
const SIZE windowSize{ width, height };
|
||||
return publicTerminal->Refresh(windowSize, dimensions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method for resizing the terminal using character column and row counts
|
||||
/// </summary>
|
||||
/// <param name="terminal">Pointer to the terminal object.</param>
|
||||
/// <param name="dimensionsInCharacters">New terminal size in row and column count.</param>
|
||||
/// <param name="dimensionsInPixels">Out parameter with the new size of the renderer.</param>
|
||||
/// <returns>HRESULT of the attempted resize.</returns>
|
||||
HRESULT _stdcall TerminalTriggerResizeWithDimension(_In_ void* terminal, _In_ COORD dimensionsInCharacters, _Out_ SIZE* dimensionsInPixels)
|
||||
{
|
||||
RETURN_HR_IF_NULL(E_INVALIDARG, dimensionsInPixels);
|
||||
|
||||
const auto publicTerminal = static_cast<const HwndTerminal*>(terminal);
|
||||
|
||||
const auto viewInCharacters = Viewport::FromDimensions({ 0, 0 }, { (dimensionsInCharacters.X), (dimensionsInCharacters.Y) });
|
||||
const auto viewInPixels = publicTerminal->_renderEngine->GetViewportInPixels(viewInCharacters);
|
||||
|
||||
dimensionsInPixels->cx = viewInPixels.Width();
|
||||
dimensionsInPixels->cy = viewInPixels.Height();
|
||||
|
||||
COORD unused{ 0, 0 };
|
||||
|
||||
return TerminalTriggerResize(terminal, viewInPixels.Width(), viewInPixels.Height(), &unused);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the amount of rows and columns that fit in the provided width and height.
|
||||
/// </summary>
|
||||
/// <param name="terminal">Terminal pointer</param>
|
||||
/// <param name="width">Width of the terminal area to calculate.</param>
|
||||
/// <param name="height">Height of the terminal area to calculate.</param>
|
||||
/// <param name="dimensions">Out parameter containing the columns and rows that fit the new size.</param>
|
||||
/// <returns>HRESULT of the calculation.</returns>
|
||||
HRESULT _stdcall TerminalCalculateResize(_In_ void* terminal, _In_ short width, _In_ short height, _Out_ COORD* dimensions)
|
||||
{
|
||||
const auto publicTerminal = static_cast<const HwndTerminal*>(terminal);
|
||||
|
||||
const auto viewInPixels = Viewport::FromDimensions({ 0, 0 }, { width, height });
|
||||
const auto viewInCharacters = publicTerminal->_renderEngine->GetViewportInCharacters(viewInPixels);
|
||||
|
||||
dimensions->X = viewInCharacters.Width();
|
||||
dimensions->Y = viewInCharacters.Height();
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
void _stdcall TerminalDpiChanged(void* terminal, int newDpi)
|
||||
{
|
||||
const auto publicTerminal = static_cast<HwndTerminal*>(terminal);
|
||||
@@ -760,18 +813,6 @@ void _stdcall TerminalSetTheme(void* terminal, TerminalTheme theme, LPCWSTR font
|
||||
publicTerminal->Refresh(windowSize, &dimensions);
|
||||
}
|
||||
|
||||
// Resizes the terminal to the specified rows and columns.
|
||||
HRESULT _stdcall TerminalResize(void* terminal, COORD dimensions)
|
||||
{
|
||||
const auto publicTerminal = static_cast<const HwndTerminal*>(terminal);
|
||||
|
||||
auto lock = publicTerminal->_terminal->LockForWriting();
|
||||
publicTerminal->_terminal->ClearSelection();
|
||||
publicTerminal->_renderer->TriggerRedrawAll();
|
||||
|
||||
return publicTerminal->_terminal->UserResize(dimensions);
|
||||
}
|
||||
|
||||
void _stdcall TerminalBlinkCursor(void* terminal)
|
||||
{
|
||||
const auto publicTerminal = static_cast<const HwndTerminal*>(terminal);
|
||||
|
||||
@@ -27,8 +27,9 @@ extern "C" {
|
||||
__declspec(dllexport) HRESULT _stdcall CreateTerminal(HWND parentHwnd, _Out_ void** hwnd, _Out_ void** terminal);
|
||||
__declspec(dllexport) void _stdcall TerminalSendOutput(void* terminal, LPCWSTR data);
|
||||
__declspec(dllexport) void _stdcall TerminalRegisterScrollCallback(void* terminal, void __stdcall callback(int, int, int));
|
||||
__declspec(dllexport) HRESULT _stdcall TerminalTriggerResize(void* terminal, double width, double height, _Out_ COORD* dimensions);
|
||||
__declspec(dllexport) HRESULT _stdcall TerminalResize(void* terminal, COORD dimensions);
|
||||
__declspec(dllexport) HRESULT _stdcall TerminalTriggerResize(_In_ void* terminal, _In_ short width, _In_ short height, _Out_ COORD* dimensions);
|
||||
__declspec(dllexport) HRESULT _stdcall TerminalTriggerResizeWithDimension(_In_ void* terminal, _In_ COORD dimensions, _Out_ SIZE* dimensionsInPixels);
|
||||
__declspec(dllexport) HRESULT _stdcall TerminalCalculateResize(_In_ void* terminal, _In_ short width, _In_ short height, _Out_ COORD* dimensions);
|
||||
__declspec(dllexport) void _stdcall TerminalDpiChanged(void* terminal, int newDpi);
|
||||
__declspec(dllexport) void _stdcall TerminalUserScroll(void* terminal, int viewTop);
|
||||
__declspec(dllexport) void _stdcall TerminalClearSelection(void* terminal);
|
||||
@@ -90,7 +91,9 @@ private:
|
||||
std::optional<til::point> _singleClickTouchdownPos;
|
||||
|
||||
friend HRESULT _stdcall CreateTerminal(HWND parentHwnd, _Out_ void** hwnd, _Out_ void** terminal);
|
||||
friend HRESULT _stdcall TerminalResize(void* terminal, COORD dimensions);
|
||||
friend HRESULT _stdcall TerminalTriggerResize(_In_ void* terminal, _In_ short width, _In_ short height, _Out_ COORD* dimensions);
|
||||
friend HRESULT _stdcall TerminalTriggerResizeWithDimension(_In_ void* terminal, _In_ COORD dimensions, _Out_ SIZE* dimensionsInPixels);
|
||||
friend HRESULT _stdcall TerminalCalculateResize(_In_ void* terminal, _In_ short width, _In_ short height, _Out_ COORD* dimensions);
|
||||
friend void _stdcall TerminalDpiChanged(void* terminal, int newDpi);
|
||||
friend void _stdcall TerminalUserScroll(void* terminal, int viewTop);
|
||||
friend void _stdcall TerminalClearSelection(void* terminal);
|
||||
|
||||
@@ -185,12 +185,17 @@ HRESULT OpenTerminalHere::GetState(IShellItemArray* /*psiItemArray*/,
|
||||
|
||||
HRESULT OpenTerminalHere::GetIcon(IShellItemArray* /*psiItemArray*/,
|
||||
LPWSTR* ppszIcon)
|
||||
try
|
||||
{
|
||||
// the icon ref ("dll,-<resid>") is provided here, in this case none is provided
|
||||
*ppszIcon = nullptr;
|
||||
// TODO GH#6111: Return the Terminal icon here
|
||||
return E_NOTIMPL;
|
||||
std::filesystem::path modulePath{ wil::GetModuleFileNameW<std::wstring>(wil::GetModuleInstanceHandle()) };
|
||||
modulePath.replace_filename(WindowsTerminalExe);
|
||||
// WindowsTerminal.exe,-101 will be the first icon group in WT
|
||||
// We're using WindowsTerminal here explicitly, and not wt (from _getExePath), because
|
||||
// WindowsTerminal is the only one built with the right icons.
|
||||
const auto resource{ modulePath.wstring() + L",-101" };
|
||||
return SHStrDupW(resource.c_str(), ppszIcon);
|
||||
}
|
||||
CATCH_RETURN();
|
||||
|
||||
HRESULT OpenTerminalHere::GetFlags(EXPCMDFLAGS* pFlags)
|
||||
{
|
||||
|
||||
@@ -214,6 +214,9 @@ namespace winrt::TerminalApp::implementation
|
||||
struct MoveFocusArgs : public MoveFocusArgsT<MoveFocusArgs>
|
||||
{
|
||||
MoveFocusArgs() = default;
|
||||
MoveFocusArgs(TerminalApp::Direction direction) :
|
||||
_Direction{ direction } {};
|
||||
|
||||
GETSET_PROPERTY(TerminalApp::Direction, Direction, TerminalApp::Direction::None);
|
||||
|
||||
static constexpr std::string_view DirectionKey{ "direction" };
|
||||
@@ -308,6 +311,11 @@ namespace winrt::TerminalApp::implementation
|
||||
struct SplitPaneArgs : public SplitPaneArgsT<SplitPaneArgs>
|
||||
{
|
||||
SplitPaneArgs() = default;
|
||||
SplitPaneArgs(winrt::TerminalApp::SplitState style, const winrt::TerminalApp::NewTerminalArgs& terminalArgs) :
|
||||
_SplitStyle{ style },
|
||||
_TerminalArgs{ terminalArgs } {};
|
||||
SplitPaneArgs(SplitType splitMode) :
|
||||
_SplitMode{ splitMode } {};
|
||||
GETSET_PROPERTY(winrt::TerminalApp::SplitState, SplitStyle, winrt::TerminalApp::SplitState::Automatic);
|
||||
GETSET_PROPERTY(winrt::TerminalApp::NewTerminalArgs, TerminalArgs, nullptr);
|
||||
GETSET_PROPERTY(winrt::TerminalApp::SplitType, SplitMode, winrt::TerminalApp::SplitType::Manual);
|
||||
@@ -553,4 +561,6 @@ namespace winrt::TerminalApp::factory_implementation
|
||||
{
|
||||
BASIC_FACTORY(ActionEventArgs);
|
||||
BASIC_FACTORY(NewTerminalArgs);
|
||||
BASIC_FACTORY(MoveFocusArgs);
|
||||
BASIC_FACTORY(SplitPaneArgs);
|
||||
}
|
||||
|
||||
@@ -87,6 +87,7 @@ namespace TerminalApp
|
||||
|
||||
[default_interface] runtimeclass MoveFocusArgs : IActionArgs
|
||||
{
|
||||
MoveFocusArgs(Direction direction);
|
||||
Direction Direction { get; };
|
||||
};
|
||||
|
||||
@@ -102,6 +103,9 @@ namespace TerminalApp
|
||||
|
||||
[default_interface] runtimeclass SplitPaneArgs : IActionArgs
|
||||
{
|
||||
SplitPaneArgs(SplitState split, NewTerminalArgs terminalArgs);
|
||||
SplitPaneArgs(SplitType splitMode);
|
||||
|
||||
SplitState SplitStyle { get; };
|
||||
NewTerminalArgs TerminalArgs { get; };
|
||||
SplitType SplitMode { get; };
|
||||
|
||||
@@ -132,10 +132,9 @@ namespace winrt::TerminalApp::implementation
|
||||
// be removed before it's re-added in Pane::Restore
|
||||
_tabContent.Children().Clear();
|
||||
|
||||
// Togging the zoom on the tab will cause the tab to inform us of
|
||||
// the new root Content for this tab.
|
||||
activeTab->ToggleZoom();
|
||||
|
||||
// Update the selected tab, to trigger us to re-add the tab's GetRootElement to the UI tree
|
||||
_UpdatedSelectedTab(_tabView.SelectedIndex());
|
||||
}
|
||||
args.Handled(true);
|
||||
}
|
||||
@@ -488,6 +487,14 @@ namespace winrt::TerminalApp::implementation
|
||||
void TerminalPage::_HandleOpenTabSearch(const IInspectable& /*sender*/,
|
||||
const TerminalApp::ActionEventArgs& args)
|
||||
{
|
||||
// Tab search is always in-order.
|
||||
auto tabCommands = winrt::single_threaded_vector<TerminalApp::Command>();
|
||||
for (const auto& tab : _tabs)
|
||||
{
|
||||
tabCommands.Append(tab.SwitchToTabCommand());
|
||||
}
|
||||
CommandPalette().SetTabActions(tabCommands);
|
||||
|
||||
auto opt = _GetFocusedTabIndex();
|
||||
uint32_t startIdx = opt.value_or(0);
|
||||
|
||||
|
||||
@@ -39,7 +39,8 @@ static const std::array<std::wstring_view, static_cast<uint32_t>(winrt::Terminal
|
||||
USES_RESOURCE(L"TooManyKeysForChord"),
|
||||
USES_RESOURCE(L"MissingRequiredParameter"),
|
||||
USES_RESOURCE(L"LegacyGlobalsProperty"),
|
||||
USES_RESOURCE(L"FailedToParseCommandJson")
|
||||
USES_RESOURCE(L"FailedToParseCommandJson"),
|
||||
USES_RESOURCE(L"FailedToWriteToSettings")
|
||||
};
|
||||
static const std::array<std::wstring_view, static_cast<uint32_t>(winrt::TerminalApp::SettingsLoadErrors::ERRORS_SIZE)> settingsLoadErrorsLabels {
|
||||
USES_RESOURCE(L"NoProfilesText"),
|
||||
@@ -464,6 +465,11 @@ namespace winrt::TerminalApp::implementation
|
||||
void AppLogic::_OnLoaded(const IInspectable& /*sender*/,
|
||||
const RoutedEventArgs& /*eventArgs*/)
|
||||
{
|
||||
const auto keyboardServiceIsDisabled = !_IsKeyboardServiceEnabled();
|
||||
if (keyboardServiceIsDisabled)
|
||||
{
|
||||
_root->ShowKeyboardServiceWarning();
|
||||
}
|
||||
if (FAILED(_settingsLoadedResult))
|
||||
{
|
||||
const winrt::hstring titleKey = USES_RESOURCE(L"InitialJsonParseErrorTitle");
|
||||
@@ -476,6 +482,51 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Helper for determining if the "Touch Keyboard and Handwriting Panel
|
||||
// Service" is enabled. If it isn't, we want to be able to display a
|
||||
// warning to the user, because they won't be able to type in the
|
||||
// Terminal.
|
||||
// Return Value:
|
||||
// - true if the service is enabled, or if we fail to query the service. We
|
||||
// return true in that case, to be less noisy (though, that is unexpected)
|
||||
bool AppLogic::_IsKeyboardServiceEnabled()
|
||||
{
|
||||
if (IsUwp())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// If at any point we fail to open the service manager, the service,
|
||||
// etc, then just quick return true to disable the dialog. We'd rather
|
||||
// not be noisy with this dialog if we failed for some reason.
|
||||
|
||||
// Open the service manager. This will return 0 if it failed.
|
||||
wil::unique_schandle hManager{ OpenSCManager(nullptr, nullptr, 0) };
|
||||
|
||||
if (LOG_LAST_ERROR_IF(!hManager.is_valid()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get a handle to the keyboard service
|
||||
wil::unique_schandle hService{ OpenService(hManager.get(), TabletInputServiceKey.data(), SERVICE_QUERY_STATUS) };
|
||||
if (LOG_LAST_ERROR_IF(!hService.is_valid()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get the current state of the service
|
||||
SERVICE_STATUS status{ 0 };
|
||||
if (!LOG_IF_WIN32_BOOL_FALSE(QueryServiceStatus(hService.get(), &status)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto state = status.dwCurrentState;
|
||||
return (state == SERVICE_RUNNING || state == SERVICE_START_PENDING);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Get the size in pixels of the client area we'll need to launch this
|
||||
// terminal app. This method will use the default profile's settings to do
|
||||
@@ -609,6 +660,17 @@ namespace winrt::TerminalApp::implementation
|
||||
return _settings.GlobalSettings().ShowTabsInTitlebar();
|
||||
}
|
||||
|
||||
bool AppLogic::GetInitialAlwaysOnTop()
|
||||
{
|
||||
if (!_loadedInitialSettings)
|
||||
{
|
||||
// Load settings if we haven't already
|
||||
LoadSettings();
|
||||
}
|
||||
|
||||
return _settings.GlobalSettings().AlwaysOnTop();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - See Pane::CalcSnappedDimension
|
||||
float AppLogic::CalcSnappedDimension(const bool widthOrHeight, const float dimension) const
|
||||
|
||||
@@ -44,6 +44,7 @@ namespace winrt::TerminalApp::implementation
|
||||
winrt::Windows::UI::Xaml::ElementTheme GetRequestedTheme();
|
||||
LaunchMode GetLaunchMode();
|
||||
bool GetShowTabsInTitlebar();
|
||||
bool GetInitialAlwaysOnTop();
|
||||
float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const;
|
||||
|
||||
Windows::UI::Xaml::UIElement GetRoot() noexcept;
|
||||
@@ -88,6 +89,8 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
void _ShowLoadErrorsDialog(const winrt::hstring& titleKey, const winrt::hstring& contentKey, HRESULT settingsLoadedResult);
|
||||
void _ShowLoadWarningsDialog();
|
||||
bool _IsKeyboardServiceEnabled();
|
||||
void _ShowKeyboardServiceDisabledDialog();
|
||||
|
||||
fire_and_forget _LoadErrorsDialogRoutine();
|
||||
fire_and_forget _ShowLoadWarningsDialogRoutine();
|
||||
|
||||
@@ -57,6 +57,7 @@ namespace TerminalApp
|
||||
Windows.UI.Xaml.ElementTheme GetRequestedTheme();
|
||||
LaunchMode GetLaunchMode();
|
||||
Boolean GetShowTabsInTitlebar();
|
||||
Boolean GetInitialAlwaysOnTop();
|
||||
Single CalcSnappedDimension(Boolean widthOrHeight, Single dimension);
|
||||
void TitlebarClicked();
|
||||
void WindowCloseButtonClicked();
|
||||
|
||||
@@ -126,6 +126,16 @@ IVectorView<winrt::TerminalApp::SettingsLoadWarnings> CascadiaSettings::Warnings
|
||||
return _warnings.GetView();
|
||||
}
|
||||
|
||||
void CascadiaSettings::ClearWarnings()
|
||||
{
|
||||
_warnings.Clear();
|
||||
}
|
||||
|
||||
void CascadiaSettings::AppendWarning(SettingsLoadWarnings warning)
|
||||
{
|
||||
_warnings.Append(warning);
|
||||
}
|
||||
|
||||
winrt::Windows::Foundation::IReference<winrt::TerminalApp::SettingsLoadErrors> CascadiaSettings::GetLoadingError()
|
||||
{
|
||||
return _loadError;
|
||||
@@ -148,8 +158,6 @@ winrt::hstring CascadiaSettings::GetSerializationErrorMessage()
|
||||
// - <none>
|
||||
void CascadiaSettings::_ValidateSettings()
|
||||
{
|
||||
_warnings.Clear();
|
||||
|
||||
// Make sure to check that profiles exists at all first and foremost:
|
||||
_ValidateProfilesExist();
|
||||
|
||||
|
||||
@@ -82,6 +82,8 @@ namespace winrt::TerminalApp::implementation
|
||||
TerminalApp::ColorScheme GetColorSchemeForProfile(const guid profileGuid) const;
|
||||
|
||||
Windows::Foundation::Collections::IVectorView<SettingsLoadWarnings> Warnings();
|
||||
void ClearWarnings();
|
||||
void AppendWarning(SettingsLoadWarnings warning);
|
||||
Windows::Foundation::IReference<SettingsLoadErrors> GetLoadingError();
|
||||
hstring GetSerializationErrorMessage();
|
||||
|
||||
|
||||
@@ -106,6 +106,7 @@ winrt::TerminalApp::CascadiaSettings CascadiaSettings::LoadAll()
|
||||
{
|
||||
auto settings = LoadDefaults();
|
||||
auto resultPtr = winrt::get_self<CascadiaSettings>(settings);
|
||||
resultPtr->ClearWarnings();
|
||||
|
||||
// GH 3588, we need this below to know if the user chose something that wasn't our default.
|
||||
// Collect it up here in case it gets modified by any of the other layers between now and when
|
||||
@@ -185,7 +186,14 @@ winrt::TerminalApp::CascadiaSettings CascadiaSettings::LoadAll()
|
||||
// We should re-parse, but not re-layer
|
||||
resultPtr->_ParseJsonString(resultPtr->_userSettingsString, false);
|
||||
|
||||
_WriteSettings(resultPtr->_userSettingsString);
|
||||
try
|
||||
{
|
||||
_WriteSettings(resultPtr->_userSettingsString);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
resultPtr->AppendWarning(SettingsLoadWarnings::FailedToWriteToSettings);
|
||||
}
|
||||
}
|
||||
|
||||
// If this throws, the app will catch it and use the default settings
|
||||
|
||||
@@ -129,7 +129,7 @@
|
||||
</Grid.RowDefinitions>
|
||||
<ColorPicker x:Name="customColorPicker"
|
||||
IsMoreButtonVisible="True"
|
||||
IsColorSliderVisible="False"
|
||||
IsColorSliderVisible="True"
|
||||
IsColorChannelTextInputVisible="True"
|
||||
IsHexInputVisible="True"
|
||||
IsAlphaEnabled="False"
|
||||
|
||||
@@ -95,6 +95,8 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
_sizeChangedRevoker.revoke();
|
||||
});
|
||||
|
||||
_filteredActionsView().SelectionChanged({ this, &CommandPalette::_selectedCommandChanged });
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -117,6 +119,113 @@ namespace winrt::TerminalApp::implementation
|
||||
_filteredActionsView().ScrollIntoView(_filteredActionsView().SelectedItem());
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Scroll the command palette to the specified index
|
||||
// Arguments:
|
||||
// - index within a list view of commands
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void CommandPalette::_scrollToIndex(uint32_t index)
|
||||
{
|
||||
auto numItems = _filteredActionsView().Items().Size();
|
||||
|
||||
if (numItems == 0)
|
||||
{
|
||||
// if the list is empty no need to scroll
|
||||
return;
|
||||
}
|
||||
|
||||
auto clampedIndex = std::clamp<int32_t>(index, 0, numItems - 1);
|
||||
_filteredActionsView().SelectedIndex(clampedIndex);
|
||||
_filteredActionsView().ScrollIntoView(_filteredActionsView().SelectedItem());
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Computes the number of visible commands
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - the approximate number of items visible in the list (in other words the size of the page)
|
||||
uint32_t CommandPalette::_getNumVisibleItems()
|
||||
{
|
||||
const auto container = _filteredActionsView().ContainerFromIndex(0);
|
||||
const auto item = container.try_as<winrt::Windows::UI::Xaml::Controls::ListViewItem>();
|
||||
const auto itemHeight = ::base::saturated_cast<int>(item.ActualHeight());
|
||||
const auto listHeight = ::base::saturated_cast<int>(_filteredActionsView().ActualHeight());
|
||||
return listHeight / itemHeight;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Scrolls the focus one page up the list of commands.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void CommandPalette::ScrollPageUp()
|
||||
{
|
||||
auto selected = _filteredActionsView().SelectedIndex();
|
||||
auto numVisibleItems = _getNumVisibleItems();
|
||||
_scrollToIndex(selected - numVisibleItems);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Scrolls the focus one page down the list of commands.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void CommandPalette::ScrollPageDown()
|
||||
{
|
||||
auto selected = _filteredActionsView().SelectedIndex();
|
||||
auto numVisibleItems = _getNumVisibleItems();
|
||||
_scrollToIndex(selected + numVisibleItems);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Moves the focus to the top item in the list of commands.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void CommandPalette::ScrollToTop()
|
||||
{
|
||||
_scrollToIndex(0);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Moves the focus to the bottom item in the list of commands.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void CommandPalette::ScrollToBottom()
|
||||
{
|
||||
_scrollToIndex(_filteredActionsView().Items().Size() - 1);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Called when the command selection changes. We'll use this in the tab
|
||||
// switcher to "preview" tabs as the user navigates the list of tabs. To
|
||||
// do that, we'll dispatch the switch to tab command for this tab, but not
|
||||
// dismiss the switcher.
|
||||
// Arguments:
|
||||
// - <unused>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void CommandPalette::_selectedCommandChanged(const IInspectable& /*sender*/,
|
||||
const Windows::UI::Xaml::RoutedEventArgs& /*args*/)
|
||||
{
|
||||
if (_currentMode == CommandPaletteMode::TabSwitchMode)
|
||||
{
|
||||
const auto& selectedCommand = _filteredActionsView().SelectedItem();
|
||||
if (const auto& command = selectedCommand.try_as<TerminalApp::Command>())
|
||||
{
|
||||
const auto& actionAndArgs = command.Action();
|
||||
_dispatch.DoAction(actionAndArgs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CommandPalette::_previewKeyDownHandler(IInspectable const& /*sender*/,
|
||||
Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e)
|
||||
{
|
||||
@@ -170,6 +279,30 @@ namespace winrt::TerminalApp::implementation
|
||||
SelectNextItem(true);
|
||||
e.Handled(true);
|
||||
}
|
||||
else if (key == VirtualKey::PageUp)
|
||||
{
|
||||
// Action Mode: Move focus to the first visible item in the list.
|
||||
ScrollPageUp();
|
||||
e.Handled(true);
|
||||
}
|
||||
else if (key == VirtualKey::PageDown)
|
||||
{
|
||||
// Action Mode: Move focus to the last visible item in the list.
|
||||
ScrollPageDown();
|
||||
e.Handled(true);
|
||||
}
|
||||
else if (key == VirtualKey::Home)
|
||||
{
|
||||
// Action Mode: Move focus to the first item in the list.
|
||||
ScrollToTop();
|
||||
e.Handled(true);
|
||||
}
|
||||
else if (key == VirtualKey::End)
|
||||
{
|
||||
// Action Mode: Move focus to the last item in the list.
|
||||
ScrollToBottom();
|
||||
e.Handled(true);
|
||||
}
|
||||
else if (key == VirtualKey::Enter)
|
||||
{
|
||||
// Action, TabSwitch or TabSearchMode Mode: Dispatch the action of the selected command.
|
||||
@@ -600,6 +733,12 @@ namespace winrt::TerminalApp::implementation
|
||||
_updateFilteredActions();
|
||||
}
|
||||
|
||||
void CommandPalette::SetTabActions(Collections::IVector<TerminalApp::Command> const& tabs)
|
||||
{
|
||||
_allTabActions = tabs;
|
||||
_updateFilteredActions();
|
||||
}
|
||||
|
||||
void CommandPalette::EnableCommandPaletteMode()
|
||||
{
|
||||
_switchToMode(CommandPaletteMode::ActionMode);
|
||||
@@ -955,135 +1094,6 @@ namespace winrt::TerminalApp::implementation
|
||||
_currentNestedCommands.Clear();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Listens for changes to TerminalPage's _tabs vector. Updates our vector of
|
||||
// tab switching commands accordingly.
|
||||
// Arguments:
|
||||
// - s: The vector being listened to.
|
||||
// - e: The vector changed args that tells us whether a change, insert, or removal was performed
|
||||
// on the listened-to vector.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void CommandPalette::OnTabsChanged(const IInspectable& s, const IVectorChangedEventArgs& e)
|
||||
{
|
||||
if (auto tabList = s.try_as<IObservableVector<TerminalApp::Tab>>())
|
||||
{
|
||||
auto idx = e.Index();
|
||||
auto changedEvent = e.CollectionChange();
|
||||
|
||||
switch (changedEvent)
|
||||
{
|
||||
case CollectionChange::ItemChanged:
|
||||
{
|
||||
winrt::com_ptr<Command> item;
|
||||
item.copy_from(winrt::get_self<Command>(_allTabActions.GetAt(idx)));
|
||||
item->propertyChangedRevoker.revoke();
|
||||
|
||||
auto tab = tabList.GetAt(idx);
|
||||
GenerateCommandForTab(idx, false, tab);
|
||||
UpdateTabIndices(idx);
|
||||
break;
|
||||
}
|
||||
case CollectionChange::ItemInserted:
|
||||
{
|
||||
auto tab = tabList.GetAt(idx);
|
||||
GenerateCommandForTab(idx, true, tab);
|
||||
UpdateTabIndices(idx);
|
||||
break;
|
||||
}
|
||||
case CollectionChange::ItemRemoved:
|
||||
{
|
||||
winrt::com_ptr<Command> item;
|
||||
item.copy_from(winrt::get_self<Command>(_allTabActions.GetAt(idx)));
|
||||
item->propertyChangedRevoker.revoke();
|
||||
|
||||
_allTabActions.RemoveAt(idx);
|
||||
UpdateTabIndices(idx);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_updateFilteredActions();
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - In the case where a tab is removed or reordered, the given indices of
|
||||
// the tab switch commands following the removed/reordered tab will get out of sync by 1
|
||||
// (e.g. if tab 1 is removed, tabs 2,3,4,... need to become tabs 1,2,3,...)
|
||||
// This function just loops through the tabs following startIdx and adjusts their given indices.
|
||||
// Arguments:
|
||||
// - startIdx: The index to start the update loop at.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void CommandPalette::UpdateTabIndices(const uint32_t startIdx)
|
||||
{
|
||||
for (auto i = startIdx; i < _allTabActions.Size(); ++i)
|
||||
{
|
||||
auto command = _allTabActions.GetAt(i);
|
||||
|
||||
command.Action().Args().as<implementation::SwitchToTabArgs>()->TabIndex(i);
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Create a tab switching command based on the given tab object and insert/update the command
|
||||
// at the given index. The command will call a SwitchToTab action on the given idx.
|
||||
// Arguments:
|
||||
// - idx: The index to insert or update the tab switch command.
|
||||
// - tab: The tab object to refer to when creating the tab switch command.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void CommandPalette::GenerateCommandForTab(const uint32_t idx, bool inserted, TerminalApp::Tab& tab)
|
||||
{
|
||||
auto focusTabAction = winrt::make_self<implementation::ActionAndArgs>();
|
||||
auto args = winrt::make_self<implementation::SwitchToTabArgs>();
|
||||
args->TabIndex(idx);
|
||||
|
||||
focusTabAction->Action(ShortcutAction::SwitchToTab);
|
||||
focusTabAction->Args(*args);
|
||||
|
||||
auto command = winrt::make_self<implementation::Command>();
|
||||
command->Action(*focusTabAction);
|
||||
command->Name(tab.Title());
|
||||
command->IconSource(tab.IconSource());
|
||||
|
||||
// Listen for changes to the Tab so we can update this Command's attributes accordingly.
|
||||
auto weakThis{ get_weak() };
|
||||
auto weakCommand{ command->get_weak() };
|
||||
command->propertyChangedRevoker = tab.PropertyChanged(winrt::auto_revoke, [weakThis, weakCommand, tab](auto&&, const Windows::UI::Xaml::Data::PropertyChangedEventArgs& args) {
|
||||
auto palette{ weakThis.get() };
|
||||
auto command{ weakCommand.get() };
|
||||
|
||||
if (palette && command)
|
||||
{
|
||||
if (args.PropertyName() == L"Title")
|
||||
{
|
||||
if (command->Name() != tab.Title())
|
||||
{
|
||||
command->Name(tab.Title());
|
||||
}
|
||||
}
|
||||
if (args.PropertyName() == L"IconSource")
|
||||
{
|
||||
if (command->IconSource() != tab.IconSource())
|
||||
{
|
||||
command->IconSource(tab.IconSource());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (inserted)
|
||||
{
|
||||
_allTabActions.InsertAt(idx, *command);
|
||||
}
|
||||
else
|
||||
{
|
||||
_allTabActions.SetAt(idx, *command);
|
||||
}
|
||||
}
|
||||
|
||||
void CommandPalette::EnableTabSwitcherMode(const bool searchMode, const uint32_t startIdx)
|
||||
{
|
||||
_switcherStartIdx = startIdx;
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace winrt::TerminalApp::implementation
|
||||
Windows::Foundation::Collections::IObservableVector<TerminalApp::Command> FilteredActions();
|
||||
|
||||
void SetCommands(Windows::Foundation::Collections::IVector<TerminalApp::Command> const& actions);
|
||||
void SetTabActions(Windows::Foundation::Collections::IVector<TerminalApp::Command> const& tabs);
|
||||
void SetKeyBindings(Microsoft::Terminal::TerminalControl::IKeyBindings bindings);
|
||||
|
||||
void EnableCommandPaletteMode();
|
||||
@@ -33,9 +34,13 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
void SelectNextItem(const bool moveDown);
|
||||
|
||||
void ScrollPageUp();
|
||||
void ScrollPageDown();
|
||||
void ScrollToTop();
|
||||
void ScrollToBottom();
|
||||
|
||||
// Tab Switcher
|
||||
void EnableTabSwitcherMode(const bool searchMode, const uint32_t startIdx);
|
||||
void OnTabsChanged(const Windows::Foundation::IInspectable& s, const Windows::Foundation::Collections::IVectorChangedEventArgs& e);
|
||||
|
||||
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
|
||||
OBSERVABLE_GETSET_PROPERTY(winrt::hstring, NoMatchesText, _PropertyChangedHandlers);
|
||||
@@ -64,6 +69,9 @@ namespace winrt::TerminalApp::implementation
|
||||
void _keyUpHandler(Windows::Foundation::IInspectable const& sender,
|
||||
Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e);
|
||||
|
||||
void _selectedCommandChanged(Windows::Foundation::IInspectable const& sender,
|
||||
Windows::UI::Xaml::RoutedEventArgs const& args);
|
||||
|
||||
void _updateUIForStackChange();
|
||||
|
||||
void _rootPointerPressed(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e);
|
||||
@@ -87,8 +95,6 @@ namespace winrt::TerminalApp::implementation
|
||||
Microsoft::Terminal::TerminalControl::IKeyBindings _bindings;
|
||||
|
||||
// Tab Switcher
|
||||
void GenerateCommandForTab(const uint32_t idx, bool inserted, winrt::TerminalApp::Tab& tab);
|
||||
void UpdateTabIndices(const uint32_t startIdx);
|
||||
Windows::Foundation::Collections::IVector<TerminalApp::Command> _allTabActions{ nullptr };
|
||||
uint32_t _switcherStartIdx;
|
||||
void _anchorKeyUpHandler();
|
||||
@@ -98,6 +104,9 @@ namespace winrt::TerminalApp::implementation
|
||||
void _dispatchCommand(const TerminalApp::Command& command);
|
||||
void _dispatchCommandline();
|
||||
void _dismissPalette();
|
||||
|
||||
void _scrollToIndex(uint32_t index);
|
||||
uint32_t _getNumVisibleItems();
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace TerminalApp
|
||||
Windows.Foundation.Collections.IObservableVector<Command> FilteredActions { get; };
|
||||
|
||||
void SetCommands(Windows.Foundation.Collections.IVector<Command> actions);
|
||||
void SetTabActions(Windows.Foundation.Collections.IVector<Command> tabs);
|
||||
void SetKeyBindings(Microsoft.Terminal.TerminalControl.IKeyBindings bindings);
|
||||
void EnableCommandPaletteMode();
|
||||
|
||||
@@ -26,6 +27,5 @@ namespace TerminalApp
|
||||
void SetDispatch(ShortcutActionDispatch dispatch);
|
||||
|
||||
void EnableTabSwitcherMode(Boolean searchMode, UInt32 startIdx);
|
||||
void OnTabsChanged(IInspectable s, Windows.Foundation.Collections.IVectorChangedEventArgs e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ the MIT License. See LICENSE in the project root for license information. -->
|
||||
<Style x:Key="KeyChordBorderStyle" TargetType="Border">
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="CornerRadius" Value="1" />
|
||||
<Setter Property="Background" Value="{ThemeResource SystemControlBackgroundBaseLowBrush}" />
|
||||
<Setter Property="Background" Value="{ThemeResource SystemAltMediumLowColor}" />
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
|
||||
</Style>
|
||||
<Style x:Key="KeyChordTextBlockStyle" TargetType="TextBlock">
|
||||
@@ -89,7 +89,7 @@ the MIT License. See LICENSE in the project root for license information. -->
|
||||
<Style x:Key="KeyChordBorderStyle" TargetType="Border">
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="CornerRadius" Value="1" />
|
||||
<Setter Property="Background" Value="{ThemeResource SystemControlBackgroundBaseLowBrush}" />
|
||||
<Setter Property="Background" Value="{ThemeResource SystemAltMediumLowColor}" />
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
|
||||
</Style>
|
||||
<Style x:Key="KeyChordTextBlockStyle" TargetType="TextBlock">
|
||||
|
||||
@@ -75,7 +75,7 @@ namespace winrt::TerminalApp::implementation
|
||||
GETSET_PROPERTY(bool, DebugFeaturesEnabled); // default value set in constructor
|
||||
GETSET_PROPERTY(bool, StartOnUserLogin, false);
|
||||
GETSET_PROPERTY(bool, AlwaysOnTop, false);
|
||||
GETSET_PROPERTY(bool, UseTabSwitcher, true);
|
||||
GETSET_PROPERTY(bool, UseTabSwitcher, false);
|
||||
|
||||
private:
|
||||
hstring _unparsedDefaultProfile;
|
||||
|
||||
@@ -17,6 +17,40 @@ DEFINE_PROPERTYKEY(PKEY_AppUserModel_DestListLogoUri, 0x9F4C2855, 0x9F79, 0x4B39
|
||||
{ 0x9F4C2855, 0x9F79, 0x4B39, 0xA8, 0xD0, 0xE1, 0xD4, 0x2D, 0xE1, 0xD5, 0xF3 }, 29 \
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - This function guesses whether a string is a file path.
|
||||
static constexpr bool _isProbableFilePath(std::wstring_view path)
|
||||
{
|
||||
// "C:X", "C:\X", "\\?", "\\."
|
||||
// _this function rejects \??\ as a path_
|
||||
if (path.size() >= 3)
|
||||
{
|
||||
const auto firstColon{ path.find(L':') };
|
||||
if (firstColon == 1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto prefix{ path.substr(0, 2) };
|
||||
return prefix == LR"(//)" || prefix == LR"(\\)";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - DestListLogoUri cannot take paths that are separated by / unless they're URLs.
|
||||
// This function uses std::filesystem to normalize strings that appear to be file
|
||||
// paths to have the "correct" slash direction.
|
||||
static std::wstring _normalizeIconPath(std::wstring_view path)
|
||||
{
|
||||
if (_isProbableFilePath(path))
|
||||
{
|
||||
std::filesystem::path asPath{ path };
|
||||
return asPath.make_preferred().wstring();
|
||||
}
|
||||
return std::wstring{ path };
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - Helper function for getting the path to the appropriate executable to use
|
||||
// for this instance of the jumplist. For the dev build, it should be `wtd.exe`,
|
||||
@@ -83,8 +117,13 @@ static std::wstring_view _getExePath()
|
||||
// - settings - The settings object to update the jumplist with.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
HRESULT Jumplist::UpdateJumplist(const CascadiaSettings& settings) noexcept
|
||||
winrt::fire_and_forget Jumplist::UpdateJumplist(const CascadiaSettings& settings) noexcept
|
||||
{
|
||||
// make sure to capture the settings _before_ the co_await
|
||||
const auto strongSettings = settings;
|
||||
|
||||
co_await winrt::resume_background();
|
||||
|
||||
try
|
||||
{
|
||||
auto jumplistInstance = winrt::create_instance<ICustomDestinationList>(CLSID_DestinationList, CLSCTX_ALL);
|
||||
@@ -96,10 +135,10 @@ HRESULT Jumplist::UpdateJumplist(const CascadiaSettings& settings) noexcept
|
||||
|
||||
// It's easier to clear the list and re-add everything. The settings aren't
|
||||
// updated often, and there likely isn't a huge amount of items to add.
|
||||
RETURN_IF_FAILED(jumplistItems->Clear());
|
||||
THROW_IF_FAILED(jumplistItems->Clear());
|
||||
|
||||
// Update the list of profiles.
|
||||
RETURN_IF_FAILED(_updateProfiles(jumplistItems.get(), settings.Profiles().GetView()));
|
||||
THROW_IF_FAILED(_updateProfiles(jumplistItems.get(), strongSettings.Profiles().GetView()));
|
||||
|
||||
// TODO GH#1571: Add items from the future customizable new tab dropdown as well.
|
||||
// This could either replace the default profiles, or be added alongside them.
|
||||
@@ -107,13 +146,11 @@ HRESULT Jumplist::UpdateJumplist(const CascadiaSettings& settings) noexcept
|
||||
// Add the items to the jumplist Task section.
|
||||
// The Tasks section is immutable by the user, unlike the destinations
|
||||
// section that can have its items pinned and removed.
|
||||
RETURN_IF_FAILED(jumplistInstance->AddUserTasks(jumplistItems.get()));
|
||||
THROW_IF_FAILED(jumplistInstance->AddUserTasks(jumplistItems.get()));
|
||||
|
||||
RETURN_IF_FAILED(jumplistInstance->CommitList());
|
||||
|
||||
return S_OK;
|
||||
THROW_IF_FAILED(jumplistInstance->CommitList());
|
||||
}
|
||||
CATCH_RETURN();
|
||||
CATCH_LOG();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -134,7 +171,8 @@ HRESULT Jumplist::UpdateJumplist(const CascadiaSettings& settings) noexcept
|
||||
|
||||
// Create the shell link object for the profile
|
||||
winrt::com_ptr<IShellLinkW> shLink;
|
||||
RETURN_IF_FAILED(_createShellLink(profile.Name(), profile.ExpandedIconPath(), args, shLink.put()));
|
||||
const auto normalizedIconPath{ _normalizeIconPath(profile.ExpandedIconPath()) };
|
||||
RETURN_IF_FAILED(_createShellLink(profile.Name(), normalizedIconPath, args, shLink.put()));
|
||||
|
||||
RETURN_IF_FAILED(jumplistItems->AddObject(shLink.get()));
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ struct IShellLinkW;
|
||||
class Jumplist
|
||||
{
|
||||
public:
|
||||
static HRESULT UpdateJumplist(const winrt::TerminalApp::CascadiaSettings& settings) noexcept;
|
||||
static winrt::fire_and_forget UpdateJumplist(const winrt::TerminalApp::CascadiaSettings& settings) noexcept;
|
||||
|
||||
private:
|
||||
[[nodiscard]] static HRESULT _updateProfiles(IObjectCollection* jumplistItems, winrt::Windows::Foundation::Collections::IVectorView<winrt::TerminalApp::Profile> profiles) noexcept;
|
||||
|
||||
@@ -18,5 +18,4 @@ Author(s):
|
||||
|
||||
static constexpr std::wstring_view WslGeneratorNamespace{ L"Windows.Terminal.Wsl" };
|
||||
static constexpr std::wstring_view AzureGeneratorNamespace{ L"Windows.Terminal.Azure" };
|
||||
static constexpr std::wstring_view TelnetGeneratorNamespace{ L"Windows.Terminal.Telnet" };
|
||||
static constexpr std::wstring_view PowershellCoreGeneratorNamespace{ L"Windows.Terminal.PowershellCore" };
|
||||
|
||||
@@ -657,6 +657,16 @@ void Pane::_CloseChild(const bool closeFirst)
|
||||
if (_lastActive)
|
||||
{
|
||||
_control.Focus(FocusState::Programmatic);
|
||||
|
||||
// See GH#7252
|
||||
// Manually fire off the GotFocus event. Typically, this is done
|
||||
// automatically when the control gets focused. However, if we're
|
||||
// `exit`ing a zoomed pane, then the other sibling isn't in the UI
|
||||
// tree currently. So the above call to Focus won't actually focus
|
||||
// the control. Because Tab is relying on GotFocus to know who the
|
||||
// active pane in the tree is, without this call, _no one_ will be
|
||||
// the active pane any longer.
|
||||
_GotFocusHandlers(shared_from_this());
|
||||
}
|
||||
|
||||
_UpdateBorders();
|
||||
|
||||
@@ -96,7 +96,7 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
GETSET_PROPERTY(hstring, BackgroundImagePath);
|
||||
GETSET_PROPERTY(double, BackgroundImageOpacity, 1.0);
|
||||
GETSET_PROPERTY(Windows::UI::Xaml::Media::Stretch, BackgroundImageStretchMode, Windows::UI::Xaml::Media::Stretch::Fill);
|
||||
GETSET_PROPERTY(Windows::UI::Xaml::Media::Stretch, BackgroundImageStretchMode, Windows::UI::Xaml::Media::Stretch::UniformToFill);
|
||||
|
||||
GETSET_PROPERTY(Microsoft::Terminal::TerminalControl::TextAntialiasingMode, AntialiasingMode, Microsoft::Terminal::TerminalControl::TextAntialiasingMode::Grayscale);
|
||||
GETSET_PROPERTY(bool, RetroTerminalEffect, false);
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
@@ -26,36 +26,36 @@
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
@@ -149,6 +149,16 @@
|
||||
<data name="SettingsValidateErrorTitle" xml:space="preserve">
|
||||
<value>Encountered errors while loading user settings</value>
|
||||
</data>
|
||||
<data name="KeyboardServiceDisabledDialog.PrimaryButtonText" xml:space="preserve">
|
||||
<value>OK</value>
|
||||
</data>
|
||||
<data name="KeyboardServiceDisabledDialog.Title" xml:space="preserve">
|
||||
<value>Warning:</value>
|
||||
</data>
|
||||
<data name="KeyboardServiceWarningText" xml:space="preserve">
|
||||
<value>The "{0}" isn't running on your machine. This can prevent the Terminal from receiving keyboard input.</value>
|
||||
<comment>{0} will be replaced with the OS-localized name of the TabletInputService</comment>
|
||||
</data>
|
||||
<data name="Ok" xml:space="preserve">
|
||||
<value>OK</value>
|
||||
</data>
|
||||
@@ -291,8 +301,11 @@
|
||||
<data name="NewTabSplitButton.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>New Tab</value>
|
||||
</data>
|
||||
<data name="NewTabSplitButton.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>New Tab</value>
|
||||
<data name="NewTabRun.Text" xml:space="preserve">
|
||||
<value>Open a new tab</value>
|
||||
</data>
|
||||
<data name="NewPaneRun.Text" xml:space="preserve">
|
||||
<value>Alt+Click to split the current window</value>
|
||||
</data>
|
||||
<data name="WindowCloseButton.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Close</value>
|
||||
@@ -661,4 +674,7 @@
|
||||
<data name="CouldNotOpenUriDialog.PrimaryButtonText" xml:space="preserve">
|
||||
<value>Cancel</value>
|
||||
</data>
|
||||
</root>
|
||||
<data name="FailedToWriteToSettings" xml:space="preserve">
|
||||
<value>We could not write to your settings file. Check the permissions on that file to ensure that the read-only flag is not set and that write access is granted.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
#include "Tab.g.cpp"
|
||||
#include "Utils.h"
|
||||
#include "ColorHelper.h"
|
||||
#include "ActionAndArgs.h"
|
||||
#include "ActionArgs.h"
|
||||
|
||||
using namespace winrt;
|
||||
using namespace winrt::Windows::UI::Xaml;
|
||||
@@ -32,8 +34,10 @@ namespace winrt::TerminalApp::implementation
|
||||
});
|
||||
|
||||
_activePane = _rootPane;
|
||||
Content(_rootPane->GetRootElement());
|
||||
|
||||
_MakeTabViewItem();
|
||||
_MakeSwitchToTabCommand();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -58,24 +62,6 @@ namespace winrt::TerminalApp::implementation
|
||||
_RecalculateAndApplyTabColor();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Get the root UIElement of this Tab's root pane.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - The UIElement acting as root of the Tab's root pane.
|
||||
UIElement Tab::GetRootElement()
|
||||
{
|
||||
if (_zoomedPane)
|
||||
{
|
||||
return _zoomedPane->GetRootElement();
|
||||
}
|
||||
else
|
||||
{
|
||||
return _rootPane->GetRootElement();
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Returns nullptr if no children of this tab were the last control to be
|
||||
// focused, or the TermControl that _was_ the last control to be focused (if
|
||||
@@ -229,6 +215,9 @@ namespace winrt::TerminalApp::implementation
|
||||
// The TabViewItem Icon needs MUX while the IconSourceElement in the CommandPalette needs WUX...
|
||||
IconSource(GetColoredIcon<winrt::WUX::Controls::IconSource>(_lastIconPath));
|
||||
_tabViewItem.IconSource(GetColoredIcon<winrt::MUX::Controls::IconSource>(_lastIconPath));
|
||||
|
||||
// Update SwitchToTab command's icon
|
||||
SwitchToTabCommand().IconSource(IconSource());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,6 +255,9 @@ namespace winrt::TerminalApp::implementation
|
||||
// Bubble our current tab text to anyone who's listening for changes.
|
||||
Title(GetActiveTitle());
|
||||
|
||||
// Update SwitchToTab command's name
|
||||
SwitchToTabCommand().Name(Title());
|
||||
|
||||
// Update the UI to reflect the changed
|
||||
_UpdateTabHeader();
|
||||
}
|
||||
@@ -502,6 +494,22 @@ namespace winrt::TerminalApp::implementation
|
||||
tab->_RecalculateAndApplyTabColor();
|
||||
}
|
||||
});
|
||||
|
||||
// Add a Closed event handler to the Pane. If the pane closes out from
|
||||
// underneath us, and it's zoomed, we want to be able to make sure to
|
||||
// update our state accordingly to un-zoom that pane. See GH#7252.
|
||||
pane->Closed([weakThis](auto&& /*s*/, auto && /*e*/) -> winrt::fire_and_forget {
|
||||
if (auto tab{ weakThis.get() })
|
||||
{
|
||||
if (tab->_zoomedPane)
|
||||
{
|
||||
co_await winrt::resume_foreground(tab->Content().Dispatcher());
|
||||
|
||||
tab->Content(tab->_rootPane->GetRootElement());
|
||||
tab->ExitZoom();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -1012,6 +1020,7 @@ namespace winrt::TerminalApp::implementation
|
||||
_rootPane->Maximize(_zoomedPane);
|
||||
// Update the tab header to show the magnifying glass
|
||||
_UpdateTabHeader();
|
||||
Content(_zoomedPane->GetRootElement());
|
||||
}
|
||||
void Tab::ExitZoom()
|
||||
{
|
||||
@@ -1019,6 +1028,7 @@ namespace winrt::TerminalApp::implementation
|
||||
_zoomedPane = nullptr;
|
||||
// Update the tab header to hide the magnifying glass
|
||||
_UpdateTabHeader();
|
||||
Content(_rootPane->GetRootElement());
|
||||
}
|
||||
|
||||
bool Tab::IsZoomed()
|
||||
@@ -1026,6 +1036,35 @@ namespace winrt::TerminalApp::implementation
|
||||
return _zoomedPane != nullptr;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Initializes a SwitchToTab command object for this Tab instance.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Tab::_MakeSwitchToTabCommand()
|
||||
{
|
||||
auto focusTabAction = winrt::make_self<implementation::ActionAndArgs>();
|
||||
auto args = winrt::make_self<implementation::SwitchToTabArgs>();
|
||||
args->TabIndex(_TabViewIndex);
|
||||
|
||||
focusTabAction->Action(ShortcutAction::SwitchToTab);
|
||||
focusTabAction->Args(*args);
|
||||
|
||||
winrt::TerminalApp::Command command;
|
||||
command.Action(*focusTabAction);
|
||||
command.Name(Title());
|
||||
command.IconSource(IconSource());
|
||||
|
||||
SwitchToTabCommand(command);
|
||||
}
|
||||
|
||||
void Tab::UpdateTabViewIndex(const uint32_t idx)
|
||||
{
|
||||
TabViewIndex(idx);
|
||||
SwitchToTabCommand().Action().Args().as<implementation::SwitchToTabArgs>()->TabIndex(idx);
|
||||
}
|
||||
|
||||
DEFINE_EVENT(Tab, ActivePaneChanged, _ActivePaneChangedHandlers, winrt::delegate<>);
|
||||
DEFINE_EVENT(Tab, ColorSelected, _colorSelected, winrt::delegate<winrt::Windows::UI::Color>);
|
||||
DEFINE_EVENT(Tab, ColorCleared, _colorCleared, winrt::delegate<>);
|
||||
|
||||
@@ -24,7 +24,6 @@ namespace winrt::TerminalApp::implementation
|
||||
void Initialize(const winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
|
||||
|
||||
winrt::Microsoft::UI::Xaml::Controls::TabViewItem GetTabViewItem();
|
||||
winrt::Windows::UI::Xaml::UIElement GetRootElement();
|
||||
winrt::Microsoft::Terminal::TerminalControl::TermControl GetActiveTerminalControl() const;
|
||||
std::optional<GUID> GetFocusedProfile() const noexcept;
|
||||
|
||||
@@ -68,6 +67,8 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
int GetLeafPaneCount() const noexcept;
|
||||
|
||||
void UpdateTabViewIndex(const uint32_t idx);
|
||||
|
||||
WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler<winrt::Windows::Foundation::IInspectable>);
|
||||
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
|
||||
DECLARE_EVENT(ActivePaneChanged, _ActivePaneChangedHandlers, winrt::delegate<>);
|
||||
@@ -76,6 +77,13 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
OBSERVABLE_GETSET_PROPERTY(winrt::hstring, Title, _PropertyChangedHandlers);
|
||||
OBSERVABLE_GETSET_PROPERTY(winrt::Windows::UI::Xaml::Controls::IconSource, IconSource, _PropertyChangedHandlers, nullptr);
|
||||
OBSERVABLE_GETSET_PROPERTY(winrt::TerminalApp::Command, SwitchToTabCommand, _PropertyChangedHandlers, nullptr);
|
||||
|
||||
// The TabViewIndex is the index this Tab object resides in TerminalPage's _tabs vector.
|
||||
// This is needed since Tab is going to be managing its own SwitchToTab command.
|
||||
OBSERVABLE_GETSET_PROPERTY(uint32_t, TabViewIndex, _PropertyChangedHandlers, 0);
|
||||
|
||||
OBSERVABLE_GETSET_PROPERTY(winrt::Windows::UI::Xaml::UIElement, Content, _PropertyChangedHandlers, nullptr);
|
||||
|
||||
private:
|
||||
std::shared_ptr<Pane> _rootPane{ nullptr };
|
||||
@@ -114,6 +122,8 @@ namespace winrt::TerminalApp::implementation
|
||||
void _ApplyTabColor(const winrt::Windows::UI::Color& color);
|
||||
void _ClearTabBackgroundColor();
|
||||
|
||||
void _MakeSwitchToTabCommand();
|
||||
|
||||
friend class ::TerminalAppLocalTests::TabTests;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import "Command.idl";
|
||||
|
||||
namespace TerminalApp
|
||||
{
|
||||
[default_interface] runtimeclass Tab : Windows.UI.Xaml.Data.INotifyPropertyChanged
|
||||
{
|
||||
String Title { get; };
|
||||
Windows.UI.Xaml.Controls.IconSource IconSource { get; };
|
||||
Windows.UI.Xaml.UIElement Content { get; };
|
||||
Command SwitchToTabCommand { get; };
|
||||
UInt32 TabViewIndex { get; };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,16 @@ the MIT License. See LICENSE in the project root for license information. -->
|
||||
BorderThickness="0"
|
||||
CornerRadius="{Binding Source={ThemeResource OverlayCornerRadius}, Converter={StaticResource TopCornerRadiusFilterConverter}}"
|
||||
AutomationProperties.AccessibilityView="Control">
|
||||
<ToolTipService.ToolTip>
|
||||
<ToolTip Placement="Mouse">
|
||||
<TextBlock IsTextSelectionEnabled="False">
|
||||
<Run x:Uid="NewTabRun"/> <LineBreak />
|
||||
<Run x:Uid="NewPaneRun"
|
||||
FontStyle="Italic">
|
||||
</Run>
|
||||
</TextBlock>
|
||||
</ToolTip>
|
||||
</ToolTipService.ToolTip>
|
||||
<!-- U+E710 is the fancy plus icon. -->
|
||||
<mux:SplitButton.Resources>
|
||||
<!-- Override the SplitButton* resources to match the tab view's button's styles. -->
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- TelnetGenerator
|
||||
|
||||
Abstract:
|
||||
- Information needed to detect a Telnet connection type.
|
||||
|
||||
Author(s):
|
||||
- Michael Niksa - 2019-12-05
|
||||
|
||||
--*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// {311153fb-d3f0-4ac6-b920-038de7cf5289}
|
||||
static constexpr winrt::guid TelnetConnectionType = { 0x311153fb, 0xd3f0, 0x4ac6, { 0xb9, 0x20, 0x03, 0x8d, 0xe7, 0xcf, 0x52, 0x89 } };
|
||||
@@ -129,7 +129,6 @@
|
||||
<ClInclude Include="PowershellCoreProfileGenerator.h" />
|
||||
<ClInclude Include="WslDistroGenerator.h" />
|
||||
<ClInclude Include="AzureCloudShellGenerator.h" />
|
||||
<ClInclude Include="TelnetGenerator.h" />
|
||||
<ClInclude Include="ColorHelper.h" />
|
||||
<ClInclude Include="TerminalSettings.h">
|
||||
<DependentUpon>TerminalSettings.idl</DependentUpon>
|
||||
|
||||
@@ -117,9 +117,6 @@
|
||||
<ClInclude Include="Commandline.h" />
|
||||
<ClInclude Include="DebugTapConnection.h" />
|
||||
<ClInclude Include="ColorHelper.h" />
|
||||
<ClInclude Include="TelnetGenerator.h">
|
||||
<Filter>profileGeneration</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="TerminalSettings.h">
|
||||
<Filter>settings</Filter>
|
||||
</ClInclude>
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
#include "KeyChordSerialization.h"
|
||||
#include "AzureCloudShellGenerator.h" // For AzureConnectionType
|
||||
#include "TelnetGenerator.h" // For TelnetConnectionType
|
||||
#include "TabRowControl.h"
|
||||
#include "ColorHelper.h"
|
||||
#include "DebugTapConnection.h"
|
||||
@@ -46,6 +45,7 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
TerminalPage::TerminalPage() :
|
||||
_tabs{ winrt::single_threaded_observable_vector<TerminalApp::Tab>() },
|
||||
_mruTabActions{ winrt::single_threaded_vector<winrt::TerminalApp::Command>() },
|
||||
_startupActions{ winrt::single_threaded_vector<winrt::TerminalApp::ActionAndArgs>() }
|
||||
{
|
||||
InitializeComponent();
|
||||
@@ -166,6 +166,7 @@ namespace winrt::TerminalApp::implementation
|
||||
auto tab = tabs.GetAt(from.value());
|
||||
tabs.RemoveAt(from.value());
|
||||
tabs.InsertAt(to.value(), tab);
|
||||
page->_UpdateTabIndices();
|
||||
}
|
||||
|
||||
page->_rearranging = false;
|
||||
@@ -243,19 +244,14 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
});
|
||||
|
||||
_tabs.VectorChanged([weakThis{ get_weak() }](auto&& s, auto&& e) {
|
||||
if (auto page{ weakThis.get() })
|
||||
{
|
||||
page->CommandPalette().OnTabsChanged(s, e);
|
||||
}
|
||||
});
|
||||
|
||||
// Once the page is actually laid out on the screen, trigger all our
|
||||
// startup actions. Things like Panes need to know at least how big the
|
||||
// window will be, so they can subdivide that space.
|
||||
//
|
||||
// _OnFirstLayout will remove this handler so it doesn't get called more than once.
|
||||
_layoutUpdatedRevoker = _tabContent.LayoutUpdated(winrt::auto_revoke, { this, &TerminalPage::_OnFirstLayout });
|
||||
|
||||
_isAlwaysOnTop = _settings.GlobalSettings().AlwaysOnTop();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -497,6 +493,21 @@ namespace winrt::TerminalApp::implementation
|
||||
profileMenuItem.FontWeight(FontWeights::Bold());
|
||||
}
|
||||
|
||||
auto newTabRun = WUX::Documents::Run();
|
||||
newTabRun.Text(RS_(L"NewTabRun/Text"));
|
||||
auto newPaneRun = WUX::Documents::Run();
|
||||
newPaneRun.Text(RS_(L"NewPaneRun/Text"));
|
||||
newPaneRun.FontStyle(FontStyle::Italic);
|
||||
|
||||
auto textBlock = WUX::Controls::TextBlock{};
|
||||
textBlock.Inlines().Append(newTabRun);
|
||||
textBlock.Inlines().Append(WUX::Documents::LineBreak{});
|
||||
textBlock.Inlines().Append(newPaneRun);
|
||||
|
||||
auto toolTip = WUX::Controls::ToolTip{};
|
||||
toolTip.Content(textBlock);
|
||||
WUX::Controls::ToolTipService::SetToolTip(profileMenuItem, toolTip);
|
||||
|
||||
profileMenuItem.Click([profileIndex, weakThis{ get_weak() }](auto&&, auto&&) {
|
||||
if (auto page{ weakThis.get() })
|
||||
{
|
||||
@@ -686,6 +697,10 @@ namespace winrt::TerminalApp::implementation
|
||||
// Add the new tab to the list of our tabs.
|
||||
auto newTabImpl = winrt::make_self<Tab>(profileGuid, term);
|
||||
_tabs.Append(*newTabImpl);
|
||||
_mruTabActions.Append(newTabImpl->SwitchToTabCommand());
|
||||
|
||||
// Give the tab its index in the _tabs vector so it can manage its own SwitchToTab command.
|
||||
newTabImpl->UpdateTabViewIndex(_tabs.Size() - 1);
|
||||
|
||||
// Hookup our event handlers to the new terminal
|
||||
_RegisterTerminalEvents(term, *newTabImpl);
|
||||
@@ -792,12 +807,6 @@ namespace winrt::TerminalApp::implementation
|
||||
winrt::guid());
|
||||
}
|
||||
|
||||
else if (hasConnectionType &&
|
||||
connectionType == TelnetConnectionType)
|
||||
{
|
||||
connection = TerminalConnection::TelnetConnection(settings.Commandline());
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
std::wstring guidWString = Utils::GuidToString(profileGuid);
|
||||
@@ -1064,8 +1073,16 @@ namespace winrt::TerminalApp::implementation
|
||||
auto tab{ _GetStrongTabImpl(tabIndex) };
|
||||
tab->Shutdown();
|
||||
|
||||
uint32_t mruIndex;
|
||||
if (_mruTabActions.IndexOf(_tabs.GetAt(tabIndex).SwitchToTabCommand(), mruIndex))
|
||||
{
|
||||
_mruTabActions.RemoveAt(mruIndex);
|
||||
CommandPalette().SetTabActions(_mruTabActions);
|
||||
}
|
||||
|
||||
_tabs.RemoveAt(tabIndex);
|
||||
_tabView.TabItems().RemoveAt(tabIndex);
|
||||
_UpdateTabIndices();
|
||||
|
||||
// To close the window here, we need to close the hosting window.
|
||||
if (_tabs.Size() == 0)
|
||||
@@ -1155,6 +1172,16 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
page->_UpdateTitle(*tab);
|
||||
}
|
||||
else if (args.PropertyName() == L"Content")
|
||||
{
|
||||
if (tab == page->_GetFocusedTab())
|
||||
{
|
||||
page->_tabContent.Children().Clear();
|
||||
page->_tabContent.Children().Append(tab->Content());
|
||||
|
||||
tab->SetFocused(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1191,28 +1218,36 @@ namespace winrt::TerminalApp::implementation
|
||||
// - Sets focus to the tab to the right or left the currently selected tab.
|
||||
void TerminalPage::_SelectNextTab(const bool bMoveRight)
|
||||
{
|
||||
if (auto index{ _GetFocusedTabIndex() })
|
||||
if (CommandPalette().Visibility() == Visibility::Visible)
|
||||
{
|
||||
// If the tab switcher is currently open, don't change its mode.
|
||||
// Just select the new tab.
|
||||
CommandPalette().SelectNextItem(bMoveRight);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_settings.GlobalSettings().UseTabSwitcher())
|
||||
{
|
||||
CommandPalette().SetTabActions(_mruTabActions);
|
||||
|
||||
// Since ATS is always MRU, our focused tab index is always 0.
|
||||
// So, going next should go to index 1, and going prev should wrap to the end.
|
||||
uint32_t tabCount = _mruTabActions.Size();
|
||||
auto newTabIndex = ((tabCount + (bMoveRight ? 1 : -1)) % tabCount);
|
||||
|
||||
// Otherwise, set up the tab switcher in the selected mode, with
|
||||
// the given ordering, and make it visible.
|
||||
CommandPalette().EnableTabSwitcherMode(false, newTabIndex);
|
||||
CommandPalette().Visibility(Visibility::Visible);
|
||||
}
|
||||
else if (auto index{ _GetFocusedTabIndex() })
|
||||
{
|
||||
uint32_t tabCount = _tabs.Size();
|
||||
// Wraparound math. By adding tabCount and then calculating modulo tabCount,
|
||||
// we clamp the values to the range [0, tabCount) while still supporting moving
|
||||
// leftward from 0 to tabCount - 1.
|
||||
const auto newTabIndex = ((tabCount + *index + (bMoveRight ? 1 : -1)) % tabCount);
|
||||
|
||||
if (_settings.GlobalSettings().UseTabSwitcher())
|
||||
{
|
||||
if (CommandPalette().Visibility() == Visibility::Visible)
|
||||
{
|
||||
CommandPalette().SelectNextItem(bMoveRight);
|
||||
}
|
||||
|
||||
CommandPalette().EnableTabSwitcherMode(false, newTabIndex);
|
||||
CommandPalette().Visibility(Visibility::Visible);
|
||||
}
|
||||
else
|
||||
{
|
||||
_SelectTab(newTabIndex);
|
||||
}
|
||||
auto newTabIndex = ((tabCount + *index + (bMoveRight ? 1 : -1)) % tabCount);
|
||||
_SelectTab(newTabIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1267,9 +1302,10 @@ namespace winrt::TerminalApp::implementation
|
||||
// Remove the content from the tab first, so Pane::UnZoom can
|
||||
// re-attach the content to the tree w/in the pane
|
||||
_tabContent.Children().Clear();
|
||||
// In ExitZoom, we'll change the Tab's Content(), triggering the
|
||||
// content changed event, which will re-attach the tab's new content
|
||||
// root to the tree.
|
||||
activeTab->ExitZoom();
|
||||
// Re-attach the tab's content to the UI tree.
|
||||
_tabContent.Children().Append(activeTab->GetRootElement());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1387,8 +1423,9 @@ namespace winrt::TerminalApp::implementation
|
||||
// than one tab opened, show a warning dialog.
|
||||
void TerminalPage::CloseWindow()
|
||||
{
|
||||
if (_tabs.Size() > 1 && _settings.GlobalSettings().ConfirmCloseAllTabs())
|
||||
if (_tabs.Size() > 1 && _settings.GlobalSettings().ConfirmCloseAllTabs() && !_displayingCloseDialog)
|
||||
{
|
||||
_displayingCloseDialog = true;
|
||||
_ShowCloseWarningDialog();
|
||||
}
|
||||
else
|
||||
@@ -1918,10 +1955,6 @@ namespace winrt::TerminalApp::implementation
|
||||
_rearrangeTo = eventArgs.Index();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_UpdateCommandsForPalette();
|
||||
}
|
||||
|
||||
_UpdateTabView();
|
||||
}
|
||||
@@ -1960,9 +1993,23 @@ namespace winrt::TerminalApp::implementation
|
||||
auto tab{ _GetStrongTabImpl(index) };
|
||||
|
||||
_tabContent.Children().Clear();
|
||||
_tabContent.Children().Append(tab->GetRootElement());
|
||||
_tabContent.Children().Append(tab->Content());
|
||||
|
||||
tab->SetFocused(true);
|
||||
// GH#7409: If the tab switcher is open, then we _don't_ want to
|
||||
// automatically focus the new tab here. The tab switcher wants
|
||||
// to be able to "preview" the selected tab as the user tabs
|
||||
// through the menu, but if we toss the focus to the control
|
||||
// here, then the user won't be able to navigate the ATS any
|
||||
// longer.
|
||||
//
|
||||
// When the tab swither is eventually dismissed, the focus will
|
||||
// get tossed back to the focused terminal control, so we don't
|
||||
// need to worry about focus getting lost.
|
||||
if (CommandPalette().Visibility() != Visibility::Visible)
|
||||
{
|
||||
tab->SetFocused(true);
|
||||
_UpdateMRUTab(index);
|
||||
}
|
||||
|
||||
// Raise an event that our title changed
|
||||
_titleChangeHandlers(*this, tab->GetActiveTitle());
|
||||
@@ -2033,6 +2080,20 @@ namespace winrt::TerminalApp::implementation
|
||||
_CloseAllTabs();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Called when the close button of the content dialog is clicked.
|
||||
// This resets the flag _displayingCloseDialog, which was set before
|
||||
// opening the dialog. Otherwise, the Terminal app would be closed
|
||||
// on the next close request without showing the warning dialog.
|
||||
// Arguments:
|
||||
// - sender: unused
|
||||
// - ContentDialogButtonClickEventArgs: unused
|
||||
void TerminalPage::_CloseWarningCloseButtonOnClick(WUX::Controls::ContentDialog /* sender */,
|
||||
WUX::Controls::ContentDialogButtonClickEventArgs /* eventArgs*/)
|
||||
{
|
||||
_displayingCloseDialog = false;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Hook up keybindings, and refresh the UI of the terminal.
|
||||
// This includes update the settings of all the tabs according
|
||||
@@ -2209,6 +2270,15 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
_newTabButton.Flyout().Hide();
|
||||
}
|
||||
|
||||
for (const auto& tab : _tabs)
|
||||
{
|
||||
auto tabImpl{ _GetStrongTabImpl(tab) };
|
||||
if (tabImpl->GetTabViewItem().ContextFlyout())
|
||||
{
|
||||
tabImpl->GetTabViewItem().ContextFlyout().Hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -2493,6 +2563,7 @@ namespace winrt::TerminalApp::implementation
|
||||
if (auto index{ _GetFocusedTabIndex() })
|
||||
{
|
||||
_GetStrongTabImpl(index.value())->SetFocused(true);
|
||||
_UpdateMRUTab(index.value());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2519,6 +2590,109 @@ namespace winrt::TerminalApp::implementation
|
||||
return _isAlwaysOnTop;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Updates all tabs with their current index in _tabs.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void TerminalPage::_UpdateTabIndices()
|
||||
{
|
||||
for (uint32_t i = 0; i < _tabs.Size(); ++i)
|
||||
{
|
||||
_GetStrongTabImpl(i)->UpdateTabViewIndex(i);
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Bumps the tab in its in-order index up to the top of the mru list.
|
||||
// Arguments:
|
||||
// - index: the in-order index of the tab to bump.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void TerminalPage::_UpdateMRUTab(const uint32_t index)
|
||||
{
|
||||
uint32_t mruIndex;
|
||||
auto command = _tabs.GetAt(index).SwitchToTabCommand();
|
||||
if (_mruTabActions.IndexOf(command, mruIndex))
|
||||
{
|
||||
if (mruIndex > 0)
|
||||
{
|
||||
_mruTabActions.RemoveAt(mruIndex);
|
||||
_mruTabActions.InsertAt(0, command);
|
||||
CommandPalette().SetTabActions(_mruTabActions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Displays a dialog stating the "Touch Keyboard and Handwriting Panel
|
||||
// Service" is disabled.
|
||||
void TerminalPage::ShowKeyboardServiceWarning()
|
||||
{
|
||||
if (auto presenter{ _dialogPresenter.get() })
|
||||
{
|
||||
presenter.ShowDialog(FindName(L"KeyboardServiceDisabledDialog").try_as<WUX::Controls::ContentDialog>());
|
||||
}
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - Helper function to get the OS-localized name for the "Touch Keyboard
|
||||
// and Handwriting Panel Service". If we can't open up the service for any
|
||||
// reason, then we'll just return the service's key, "TabletInputService".
|
||||
// Return Value:
|
||||
// - The OS-localized name for the TabletInputService
|
||||
winrt::hstring _getTabletServiceName()
|
||||
{
|
||||
auto isUwp = false;
|
||||
try
|
||||
{
|
||||
isUwp = ::winrt::Windows::UI::Xaml::Application::Current().as<::winrt::TerminalApp::App>().Logic().IsUwp();
|
||||
}
|
||||
CATCH_LOG();
|
||||
|
||||
if (isUwp)
|
||||
{
|
||||
return winrt::hstring{ TabletInputServiceKey };
|
||||
}
|
||||
|
||||
wil::unique_schandle hManager{ OpenSCManager(nullptr, nullptr, 0) };
|
||||
|
||||
if (LOG_LAST_ERROR_IF(!hManager.is_valid()))
|
||||
{
|
||||
return winrt::hstring{ TabletInputServiceKey };
|
||||
}
|
||||
|
||||
DWORD cchBuffer = 0;
|
||||
GetServiceDisplayName(hManager.get(), TabletInputServiceKey.data(), nullptr, &cchBuffer);
|
||||
std::wstring buffer;
|
||||
cchBuffer += 1; // Add space for a null
|
||||
buffer.resize(cchBuffer);
|
||||
|
||||
if (LOG_LAST_ERROR_IF(!GetServiceDisplayName(hManager.get(),
|
||||
TabletInputServiceKey.data(),
|
||||
buffer.data(),
|
||||
&cchBuffer)))
|
||||
{
|
||||
return winrt::hstring{ TabletInputServiceKey };
|
||||
}
|
||||
return winrt::hstring{ buffer };
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Return the fully-formed warning message for the
|
||||
// "KeyboardServiceDisabled" dialog. This dialog is used to warn the user
|
||||
// if the keyboard service is disabled, and uses the OS localization for
|
||||
// the service's actual name. It's bound to the dialog in XAML.
|
||||
// Return Value:
|
||||
// - The warning message, including the OS-localized service name.
|
||||
winrt::hstring TerminalPage::KeyboardServiceDisabledText()
|
||||
{
|
||||
const winrt::hstring serviceName{ _getTabletServiceName() };
|
||||
const winrt::hstring text{ fmt::format(std::wstring_view(RS_(L"KeyboardServiceWarningText")), serviceName) };
|
||||
return text;
|
||||
}
|
||||
|
||||
// -------------------------------- WinRT Events ---------------------------------
|
||||
// Winrt events need a method for adding a callback to the event and removing the callback.
|
||||
// These macros will define them both for you.
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
|
||||
#include "AppCommandlineArgs.h"
|
||||
|
||||
static constexpr std::wstring_view TabletInputServiceKey{ L"TabletInputService" };
|
||||
|
||||
// fwdecl unittest classes
|
||||
namespace TerminalAppLocalTests
|
||||
{
|
||||
@@ -65,6 +67,9 @@ namespace winrt::TerminalApp::implementation
|
||||
winrt::TerminalApp::IDialogPresenter DialogPresenter() const;
|
||||
void DialogPresenter(winrt::TerminalApp::IDialogPresenter dialogPresenter);
|
||||
|
||||
void ShowKeyboardServiceWarning();
|
||||
winrt::hstring KeyboardServiceDisabledText();
|
||||
|
||||
// -------------------------------- WinRT Events ---------------------------------
|
||||
DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(TitleChanged, _titleChangeHandlers, winrt::Windows::Foundation::IInspectable, winrt::hstring);
|
||||
DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(LastTabClosed, _lastTabClosedHandlers, winrt::Windows::Foundation::IInspectable, winrt::TerminalApp::LastTabClosedEventArgs);
|
||||
@@ -91,8 +96,10 @@ namespace winrt::TerminalApp::implementation
|
||||
TerminalApp::CascadiaSettings _settings{ nullptr };
|
||||
|
||||
Windows::Foundation::Collections::IObservableVector<TerminalApp::Tab> _tabs;
|
||||
Windows::Foundation::Collections::IVector<TerminalApp::Command> _mruTabActions;
|
||||
winrt::com_ptr<Tab> _GetStrongTabImpl(const uint32_t index) const;
|
||||
winrt::com_ptr<Tab> _GetStrongTabImpl(const ::winrt::TerminalApp::Tab& tab) const;
|
||||
void _UpdateTabIndices();
|
||||
|
||||
bool _isInFocusMode{ false };
|
||||
bool _isFullscreen{ false };
|
||||
@@ -125,10 +132,12 @@ namespace winrt::TerminalApp::implementation
|
||||
void _CreateNewTabFromSettings(GUID profileGuid, TerminalApp::TerminalSettings settings);
|
||||
winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection _CreateConnectionFromSettings(GUID profileGuid, TerminalApp::TerminalSettings settings);
|
||||
|
||||
bool _displayingCloseDialog{ false };
|
||||
void _SettingsButtonOnClick(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs);
|
||||
void _FeedbackButtonOnClick(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs);
|
||||
void _AboutButtonOnClick(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs);
|
||||
void _CloseWarningPrimaryButtonOnClick(Windows::UI::Xaml::Controls::ContentDialog sender, Windows::UI::Xaml::Controls::ContentDialogButtonClickEventArgs eventArgs);
|
||||
void _CloseWarningCloseButtonOnClick(Windows::UI::Xaml::Controls::ContentDialog sender, Windows::UI::Xaml::Controls::ContentDialogButtonClickEventArgs eventArgs);
|
||||
void _ThirdPartyNoticesOnClick(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs);
|
||||
|
||||
void _HookupKeyBindings(const TerminalApp::KeyMapping& keymap) noexcept;
|
||||
@@ -206,6 +215,9 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
void _UnZoomIfNeeded();
|
||||
|
||||
void _UpdateTabSwitcherCommands(const bool mru);
|
||||
void _UpdateMRUTab(const uint32_t index);
|
||||
|
||||
#pragma region ActionHandlers
|
||||
// These are all defined in AppActionHandlers.cpp
|
||||
void _HandleOpenNewTabDropdown(const IInspectable& sender, const TerminalApp::ActionEventArgs& args);
|
||||
|
||||
@@ -26,6 +26,8 @@ namespace TerminalApp
|
||||
// that there's only one application-global dialog visible at a time,
|
||||
// and because of GH#5224.
|
||||
IDialogPresenter DialogPresenter;
|
||||
void ShowKeyboardServiceWarning();
|
||||
String KeyboardServiceDisabledText { get; };
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<Object, String> TitleChanged;
|
||||
event Windows.Foundation.TypedEventHandler<Object, LastTabClosedEventArgs> LastTabClosed;
|
||||
|
||||
@@ -51,7 +51,8 @@ the MIT License. See LICENSE in the project root for license information. -->
|
||||
x:Name="CloseAllDialog"
|
||||
x:Uid="CloseAllDialog"
|
||||
DefaultButton="Primary"
|
||||
PrimaryButtonClick="_CloseWarningPrimaryButtonOnClick">
|
||||
PrimaryButtonClick="_CloseWarningPrimaryButtonOnClick"
|
||||
CloseButtonClick="_CloseWarningCloseButtonOnClick">
|
||||
</ContentDialog>
|
||||
|
||||
<ContentDialog
|
||||
@@ -73,7 +74,7 @@ the MIT License. See LICENSE in the project root for license information. -->
|
||||
x:Name="CouldNotOpenUriDialog"
|
||||
x:Uid="CouldNotOpenUriDialog"
|
||||
DefaultButton="Primary">
|
||||
<TextBlock IsTextSelectionEnabled="True">
|
||||
<TextBlock IsTextSelectionEnabled="True" TextWrapping="WrapWholeWords">
|
||||
<Run x:Name="CouldNotOpenUriReason" /> <LineBreak />
|
||||
<Run
|
||||
x:Name="UnopenedUri"
|
||||
@@ -82,6 +83,20 @@ the MIT License. See LICENSE in the project root for license information. -->
|
||||
</TextBlock>
|
||||
</ContentDialog>
|
||||
|
||||
|
||||
<ContentDialog
|
||||
x:Load="False"
|
||||
x:Name="KeyboardServiceDisabledDialog"
|
||||
x:Uid="KeyboardServiceDisabledDialog"
|
||||
DefaultButton="Primary">
|
||||
|
||||
<TextBlock
|
||||
Foreground="{ThemeResource ErrorTextBrush}"
|
||||
IsTextSelectionEnabled="True"
|
||||
TextWrapping="WrapWholeWords"
|
||||
Text="{x:Bind KeyboardServiceDisabledText, Mode=OneWay}" />
|
||||
</ContentDialog>
|
||||
|
||||
<local:CommandPalette
|
||||
x:Name="CommandPalette"
|
||||
Grid.Row="1"
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace TerminalApp
|
||||
MissingRequiredParameter = 7,
|
||||
LegacyGlobalsProperty = 8,
|
||||
FailedToParseCommandJson = 9,
|
||||
FailedToWriteToSettings = 10,
|
||||
WARNINGS_SIZE // IMPORTANT: This MUST be the last value in this enum. It's an unused placeholder.
|
||||
};
|
||||
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
// THIS IS AN AUTO-GENERATED FILE! Changes to this file will be ignored.
|
||||
{
|
||||
"defaultProfile": "{550ce7b8-d500-50ad-8a1a-c400c3262db3}",
|
||||
"defaultProfile": "{70bfecf4-bcbb-443b-a8fa-d7ac4f7ad201}",
|
||||
|
||||
// Launch Settings
|
||||
"initialCols": 120,
|
||||
"initialRows": 30,
|
||||
"launchMode": "default",
|
||||
"alwaysOnTop": false,
|
||||
|
||||
// Selection
|
||||
"copyOnSelect": false,
|
||||
"copyFormatting": false,
|
||||
"copyFormatting": true,
|
||||
"wordDelimiters": " /\\()\"'-.,:;<>~!@#$%^&*|+=[]{}~?\u2502",
|
||||
|
||||
// Tab UI
|
||||
@@ -17,30 +18,56 @@
|
||||
"showTabsInTitlebar": true,
|
||||
"showTerminalTitleInTitlebar": true,
|
||||
"tabWidthMode": "equal",
|
||||
"useTabSwitcher": true,
|
||||
|
||||
// Miscellaneous
|
||||
"confirmCloseAllTabs": true,
|
||||
"startOnUserLogin": false,
|
||||
"theme": "system",
|
||||
"snapToGridOnResize": true,
|
||||
|
||||
"profiles":
|
||||
[
|
||||
{
|
||||
"guid": "{550ce7b8-d500-50ad-8a1a-c400c3262db3}",
|
||||
"name": "Telnet Loopback",
|
||||
"commandline": "ms-telnet-loop://127.0.0.1:23",
|
||||
"connectionType" : "{311153fb-d3f0-4ac6-b920-038de7cf5289}",
|
||||
"hidden": false,
|
||||
"startingDirectory": "%USERPROFILE%",
|
||||
"guid": "{70bfecf4-bcbb-443b-a8fa-d7ac4f7ad201}",
|
||||
"name": "PowerShell",
|
||||
"commandline": "pwsh.exe",
|
||||
"icon": "ms-appx:///ProfileIcons/pwsh.png",
|
||||
"colorScheme": "Campbell",
|
||||
"antialiasingMode": "grayscale",
|
||||
"closeOnExit": "graceful",
|
||||
"colorScheme": "Vintage",
|
||||
"cursorColor": "#FFFFFF",
|
||||
"cursorShape": "bar",
|
||||
"fontFace": "Cascadia Mono",
|
||||
"icon": "ms-appx:///ProfileIcons/{550ce7b8-d500-50ad-8a1a-c400c3262db3}.png",
|
||||
"fontSize": 12,
|
||||
"hidden": false,
|
||||
"historySize": 9001,
|
||||
"padding": "8, 8, 8, 8",
|
||||
"snapOnInput": true,
|
||||
"altGrAliasing": true,
|
||||
"startingDirectory": "%SystemRoot%",
|
||||
"useAcrylic": false,
|
||||
"backgroundImage": "ms-appx:///internal-background.png",
|
||||
"backgroundImageAlignment": "bottomRight",
|
||||
"backgroundImageOpacity": 0.4,
|
||||
"backgroundImageStretchMode": "none"
|
||||
},
|
||||
{
|
||||
"guid": "{0caa0dad-35be-5f56-a8ff-afceeeaa6101}",
|
||||
"name": "Command Prompt",
|
||||
"commandline": "%SystemRoot%\\System32\\cmd.exe",
|
||||
"icon": "ms-appx:///ProfileIcons/{0caa0dad-35be-5f56-a8ff-afceeeaa6101}.png",
|
||||
"colorScheme": "Campbell",
|
||||
"antialiasingMode": "grayscale",
|
||||
"closeOnExit": "graceful",
|
||||
"cursorShape": "bar",
|
||||
"fontFace": "Cascadia Mono",
|
||||
"fontSize": 12,
|
||||
"hidden": false,
|
||||
"historySize": 9001,
|
||||
"padding": "8, 8, 8, 8",
|
||||
"snapOnInput": true,
|
||||
"altGrAliasing": true,
|
||||
"startingDirectory": "%SystemRoot%",
|
||||
"useAcrylic": false,
|
||||
"backgroundImage": "ms-appx:///internal-background.png",
|
||||
"backgroundImageAlignment": "bottomRight",
|
||||
@@ -51,25 +78,26 @@
|
||||
"schemes":
|
||||
[
|
||||
{
|
||||
"name": "Vintage",
|
||||
"foreground": "#C0C0C0",
|
||||
"background": "#000000",
|
||||
"black": "#000000",
|
||||
"red": "#800000",
|
||||
"green": "#008000",
|
||||
"yellow": "#808000",
|
||||
"blue": "#000080",
|
||||
"purple": "#800080",
|
||||
"cyan": "#008080",
|
||||
"white": "#C0C0C0",
|
||||
"brightBlack": "#808080",
|
||||
"brightRed": "#FF0000",
|
||||
"brightGreen": "#00FF00",
|
||||
"brightYellow": "#FFFF00",
|
||||
"brightBlue": "#0000FF",
|
||||
"brightPurple": "#FF00FF",
|
||||
"brightCyan": "#00FFFF",
|
||||
"brightWhite": "#FFFFFF"
|
||||
"name": "Campbell",
|
||||
"foreground": "#CCCCCC",
|
||||
"background": "#0C0C0C",
|
||||
"cursorColor": "#FFFFFF",
|
||||
"black": "#0C0C0C",
|
||||
"red": "#C50F1F",
|
||||
"green": "#13A10E",
|
||||
"yellow": "#C19C00",
|
||||
"blue": "#0037DA",
|
||||
"purple": "#881798",
|
||||
"cyan": "#3A96DD",
|
||||
"white": "#CCCCCC",
|
||||
"brightBlack": "#767676",
|
||||
"brightRed": "#E74856",
|
||||
"brightGreen": "#16C60C",
|
||||
"brightYellow": "#F9F1A5",
|
||||
"brightBlue": "#3B78FF",
|
||||
"brightPurple": "#B4009E",
|
||||
"brightCyan": "#61D6D6",
|
||||
"brightWhite": "#F2F2F2"
|
||||
}
|
||||
],
|
||||
"actions":
|
||||
@@ -78,14 +106,21 @@
|
||||
{ "command": "closeWindow", "keys": "alt+f4" },
|
||||
{ "command": "toggleFullscreen", "keys": "alt+enter" },
|
||||
{ "command": "toggleFullscreen", "keys": "f11" },
|
||||
{ "command": "toggleFocusMode" },
|
||||
{ "command": "toggleAlwaysOnTop" },
|
||||
{ "command": "openNewTabDropdown", "keys": "ctrl+shift+space" },
|
||||
{ "command": "openSettings", "keys": "ctrl+," },
|
||||
{ "command": { "action": "openSettings", "target": "defaultsFile" }, "keys": "ctrl+alt+," },
|
||||
{ "command": "find", "keys": "ctrl+shift+f" },
|
||||
{ "command": "toggleRetroEffect" },
|
||||
{ "command": "openTabColorPicker" },
|
||||
{ "command": "commandPalette", "keys":"ctrl+shift+p" },
|
||||
|
||||
// Tab Management
|
||||
// "command": "closeTab" is unbound by default.
|
||||
// The closeTab command closes a tab without confirmation, even if it has multiple panes.
|
||||
{ "command": "closeOtherTabs" },
|
||||
{ "command": "closeTabsAfter" },
|
||||
{ "command": "newTab", "keys": "ctrl+shift+t" },
|
||||
{ "command": { "action": "newTab", "index": 0 }, "keys": "ctrl+shift+1" },
|
||||
{ "command": { "action": "newTab", "index": 1 }, "keys": "ctrl+shift+2" },
|
||||
@@ -121,6 +156,7 @@
|
||||
{ "command": { "action": "moveFocus", "direction": "left" }, "keys": "alt+left" },
|
||||
{ "command": { "action": "moveFocus", "direction": "right" }, "keys": "alt+right" },
|
||||
{ "command": { "action": "moveFocus", "direction": "up" }, "keys": "alt+up" },
|
||||
{ "command": "togglePaneZoom" },
|
||||
|
||||
// Clipboard Integration
|
||||
{ "command": { "action": "copy", "singleLine": false }, "keys": "ctrl+shift+c" },
|
||||
@@ -137,6 +173,53 @@
|
||||
// Visual Adjustments
|
||||
{ "command": { "action": "adjustFontSize", "delta": 1 }, "keys": "ctrl+=" },
|
||||
{ "command": { "action": "adjustFontSize", "delta": -1 }, "keys": "ctrl+-" },
|
||||
{ "command": "resetFontSize", "keys": "ctrl+0" }
|
||||
{ "command": "resetFontSize", "keys": "ctrl+0" },
|
||||
|
||||
// Other commands
|
||||
{
|
||||
// Select color scheme...
|
||||
"name": { "key": "SetColorSchemeParentCommandName" },
|
||||
"commands": [
|
||||
{
|
||||
"iterateOn": "schemes",
|
||||
"name": "${scheme.name}",
|
||||
"command": { "action": "setColorScheme", "colorScheme": "${scheme.name}" }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
// New tab...
|
||||
"name": { "key": "NewTabParentCommandName" },
|
||||
"commands": [
|
||||
{
|
||||
"iterateOn": "profiles",
|
||||
"icon": "${profile.icon}",
|
||||
"name": "${profile.name}",
|
||||
"command": { "action": "newTab", "profile": "${profile.name}" }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
// Split pane...
|
||||
"name": { "key": "SplitPaneParentCommandName" },
|
||||
"commands": [
|
||||
{
|
||||
"iterateOn": "profiles",
|
||||
"icon": "${profile.icon}",
|
||||
"name": "${profile.name}...",
|
||||
"commands": [
|
||||
{
|
||||
"command": { "action": "splitPane", "profile": "${profile.name}", "split": "auto" }
|
||||
},
|
||||
{
|
||||
"command": { "action": "splitPane", "profile": "${profile.name}", "split": "vertical" }
|
||||
},
|
||||
{
|
||||
"command": { "action": "splitPane", "profile": "${profile.name}", "split": "horizontal" }
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"showTabsInTitlebar": true,
|
||||
"showTerminalTitleInTitlebar": true,
|
||||
"tabWidthMode": "equal",
|
||||
"useTabSwitcher": true,
|
||||
"useTabSwitcher": false,
|
||||
|
||||
// Miscellaneous
|
||||
"confirmCloseAllTabs": true,
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "pch.h"
|
||||
#include <LibraryResources.h>
|
||||
#include <WilErrorReporting.h>
|
||||
|
||||
// Note: Generate GUID using TlgGuid.exe tool
|
||||
TRACELOGGING_DEFINE_PROVIDER(
|
||||
@@ -19,6 +20,7 @@ BOOL WINAPI DllMain(HINSTANCE hInstDll, DWORD reason, LPVOID /*reserved*/)
|
||||
case DLL_PROCESS_ATTACH:
|
||||
DisableThreadLibraryCalls(hInstDll);
|
||||
TraceLoggingRegister(g_hTerminalAppProvider);
|
||||
Microsoft::Console::ErrorReporting::EnableFallbackFailureReporting(g_hTerminalAppProvider);
|
||||
break;
|
||||
case DLL_PROCESS_DETACH:
|
||||
if (g_hTerminalAppProvider)
|
||||
|
||||
@@ -52,10 +52,10 @@
|
||||
// To learn more about color schemes, visit https://aka.ms/terminal-color-schemes
|
||||
"schemes": [],
|
||||
|
||||
// Add custom keybindings to this array.
|
||||
// Add custom actions and keybindings to this array.
|
||||
// To unbind a key combination from your defaults.json, set the command to "unbound".
|
||||
// To learn more about keybindings, visit https://aka.ms/terminal-keybindings
|
||||
"keybindings":
|
||||
// To learn more about actions and keybindings, visit https://aka.ms/terminal-keybindings
|
||||
"actions":
|
||||
[
|
||||
// Copy and paste are bound to Ctrl+Shift+C and Ctrl+Shift+V in your defaults.json.
|
||||
// These two lines additionally bind them to Ctrl+C and Ctrl+V.
|
||||
|
||||
@@ -3,10 +3,6 @@
|
||||
|
||||
#include "pch.h"
|
||||
|
||||
// We have to define GSL here, not PCH
|
||||
// because TelnetConnection has a conflicting GSL implementation.
|
||||
#include <gsl/gsl>
|
||||
|
||||
#include "AzureConnection.h"
|
||||
#include "AzureClientID.h"
|
||||
#include <sstream>
|
||||
|
||||
@@ -3,14 +3,9 @@
|
||||
|
||||
#include "pch.h"
|
||||
|
||||
// We have to define GSL here, not PCH
|
||||
// because TelnetConnection has a conflicting GSL implementation.
|
||||
#include <gsl/gsl>
|
||||
|
||||
#include "ConptyConnection.h"
|
||||
|
||||
#include <windows.h>
|
||||
#include <userenv.h>
|
||||
|
||||
#include "ConptyConnection.g.cpp"
|
||||
|
||||
@@ -98,11 +93,8 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
environment.clear();
|
||||
});
|
||||
|
||||
{
|
||||
const auto newEnvironmentBlock{ Utils::CreateEnvironmentBlock() };
|
||||
// Populate the environment map with the current environment.
|
||||
RETURN_IF_FAILED(Utils::UpdateEnvironmentMapW(environment, newEnvironmentBlock.get()));
|
||||
}
|
||||
// Populate the environment map with the current environment.
|
||||
RETURN_IF_FAILED(Utils::UpdateEnvironmentMapW(environment));
|
||||
|
||||
{
|
||||
// Convert connection Guid to string and ignore the enclosing '{}'.
|
||||
|
||||
@@ -5,10 +5,6 @@
|
||||
#include "EchoConnection.h"
|
||||
#include <sstream>
|
||||
|
||||
// We have to define GSL here, not PCH
|
||||
// because TelnetConnection has a conflicting GSL implementation.
|
||||
#include <gsl/gsl>
|
||||
|
||||
#include "EchoConnection.g.cpp"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
|
||||
@@ -212,7 +212,4 @@
|
||||
<comment>The first argument {0...} is the hexadecimal error code. The second argument {1} is the user-specified path to a program.
|
||||
If this string is broken to multiple lines, it will not be displayed properly.</comment>
|
||||
</data>
|
||||
<data name="TelnetInternetOrServerIssue" xml:space="preserve">
|
||||
<value>Could not connect to telnet server.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -1,333 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "TelnetConnection.h"
|
||||
#include <LibraryResources.h>
|
||||
|
||||
#include "TelnetConnection.g.cpp"
|
||||
|
||||
#include "../../types/inc/Utils.hpp"
|
||||
|
||||
using namespace ::Microsoft::Console;
|
||||
|
||||
constexpr std::wstring_view telnetScheme = L"telnet";
|
||||
constexpr std::wstring_view msTelnetLoopbackScheme = L"ms-telnet-loop";
|
||||
|
||||
namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
{
|
||||
TelnetConnection::TelnetConnection(const hstring& uri) :
|
||||
_reader{ nullptr },
|
||||
_writer{ nullptr },
|
||||
_uri{ uri },
|
||||
_receiveBuffer{}
|
||||
{
|
||||
_session.install(_nawsServer);
|
||||
_nawsServer.activate([](auto&&) {});
|
||||
}
|
||||
|
||||
// Method description:
|
||||
// - ascribes to the ITerminalConnection interface
|
||||
// - creates the output thread
|
||||
void TelnetConnection::Start()
|
||||
try
|
||||
{
|
||||
// Create our own output handling thread
|
||||
// Each connection needs to make sure to drain the output from its backing host.
|
||||
_hOutputThread.reset(CreateThread(
|
||||
nullptr,
|
||||
0,
|
||||
[](LPVOID lpParameter) {
|
||||
auto pInstance = static_cast<TelnetConnection*>(lpParameter);
|
||||
if (pInstance)
|
||||
{
|
||||
return pInstance->_outputThread();
|
||||
}
|
||||
return gsl::narrow_cast<DWORD>(ERROR_BAD_ARGUMENTS);
|
||||
},
|
||||
this,
|
||||
0,
|
||||
nullptr));
|
||||
|
||||
THROW_LAST_ERROR_IF_NULL(_hOutputThread);
|
||||
|
||||
_transitionToState(ConnectionState::Connecting);
|
||||
|
||||
// Set initial window title.
|
||||
_TerminalOutputHandlers(L"\x1b]0;Telnet\x7");
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_CAUGHT_EXCEPTION();
|
||||
_transitionToState(ConnectionState::Failed);
|
||||
}
|
||||
|
||||
// Method description:
|
||||
// - ascribes to the ITerminalConnection interface
|
||||
// - handles the different possible inputs in the different states
|
||||
// Arguments:
|
||||
// the user's input
|
||||
void TelnetConnection::WriteInput(hstring const& data)
|
||||
{
|
||||
if (!_isStateOneOf(ConnectionState::Connected, ConnectionState::Connecting))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto str = winrt::to_string(data);
|
||||
if (str.size() == 1 && str.at(0) == L'\r')
|
||||
{
|
||||
str = "\r\n";
|
||||
}
|
||||
|
||||
#pragma warning(suppress : 26490) // Using something that isn't reinterpret_cast to forward stream bytes is more clumsy than just using it.
|
||||
telnetpp::bytes bytes(reinterpret_cast<const uint8_t*>(str.data()), str.size());
|
||||
_session.send(bytes, [=](telnetpp::bytes data) {
|
||||
_socketSend(data);
|
||||
});
|
||||
}
|
||||
|
||||
// Method description:
|
||||
// - ascribes to the ITerminalConnection interface
|
||||
// - resizes the terminal
|
||||
// Arguments:
|
||||
// - the new rows/cols values
|
||||
void TelnetConnection::Resize(uint32_t rows, uint32_t columns)
|
||||
{
|
||||
if (_prevResize.has_value() && _prevResize.value().first == rows && _prevResize.value().second == columns)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_prevResize.emplace(std::pair{ rows, columns });
|
||||
|
||||
_nawsServer.set_window_size(gsl::narrow<uint16_t>(columns),
|
||||
gsl::narrow<uint16_t>(rows),
|
||||
[=](telnetpp::subnegotiation sub) {
|
||||
_session.send(sub,
|
||||
[=](telnetpp::bytes data) {
|
||||
_socketBufferedSend(data);
|
||||
});
|
||||
_socketFlushBuffer();
|
||||
});
|
||||
}
|
||||
|
||||
// Method description:
|
||||
// - ascribes to the ITerminalConnection interface
|
||||
// - closes the socket connection and the output thread
|
||||
void TelnetConnection::Close()
|
||||
try
|
||||
{
|
||||
if (_transitionToState(ConnectionState::Closing))
|
||||
{
|
||||
_socket.Close();
|
||||
if (_hOutputThread)
|
||||
{
|
||||
// Tear down our output thread
|
||||
WaitForSingleObject(_hOutputThread.get(), INFINITE);
|
||||
_hOutputThread.reset();
|
||||
}
|
||||
|
||||
_transitionToState(ConnectionState::Closed);
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_CAUGHT_EXCEPTION();
|
||||
_transitionToState(ConnectionState::Failed);
|
||||
}
|
||||
|
||||
// Method description:
|
||||
// - this is the output thread, where we initiate the connection to the remote host
|
||||
// and establish a socket connection
|
||||
// Return value:
|
||||
// - return status
|
||||
DWORD TelnetConnection::_outputThread()
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (_isStateOneOf(ConnectionState::Failed))
|
||||
{
|
||||
_TerminalOutputHandlers(RS_(L"TelnetInternetOrServerIssue") + L"\r\n");
|
||||
return E_FAIL;
|
||||
}
|
||||
else if (_isStateAtOrBeyond(ConnectionState::Closing))
|
||||
{
|
||||
return S_FALSE;
|
||||
}
|
||||
else if (_isStateOneOf(ConnectionState::Connecting))
|
||||
{
|
||||
try
|
||||
{
|
||||
const auto uri = Windows::Foundation::Uri(_uri);
|
||||
const auto host = Windows::Networking::HostName(uri.Host());
|
||||
|
||||
bool autoLogin = false;
|
||||
// If we specified the special ms loopback scheme, then set autologin and proceed below.
|
||||
if (msTelnetLoopbackScheme == uri.SchemeName())
|
||||
{
|
||||
autoLogin = true;
|
||||
}
|
||||
// Otherwise, make sure we said telnet://, anything else is not supported here.
|
||||
else if (telnetScheme != uri.SchemeName())
|
||||
{
|
||||
THROW_HR(E_INVALIDARG);
|
||||
}
|
||||
|
||||
_socket.ConnectAsync(host, winrt::to_hstring(uri.Port())).get();
|
||||
_writer = Windows::Storage::Streams::DataWriter(_socket.OutputStream());
|
||||
_reader = Windows::Storage::Streams::DataReader(_socket.InputStream());
|
||||
_reader.InputStreamOptions(Windows::Storage::Streams::InputStreamOptions::Partial); // returns when 1 or more bytes ready.
|
||||
_transitionToState(ConnectionState::Connected);
|
||||
|
||||
if (autoLogin)
|
||||
{
|
||||
// Send newline to bypass User Name prompt.
|
||||
const auto newline = winrt::to_hstring("\r\n");
|
||||
WriteInput(newline);
|
||||
|
||||
// Wait for login.
|
||||
Sleep(1000);
|
||||
|
||||
// Send "cls" enter to clear the thing and just look like a prompt.
|
||||
const auto clearScreen = winrt::to_hstring("cls\r\n");
|
||||
WriteInput(clearScreen);
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_CAUGHT_EXCEPTION();
|
||||
_transitionToState(ConnectionState::Failed);
|
||||
}
|
||||
}
|
||||
else if (_isStateOneOf(ConnectionState::Connected))
|
||||
{
|
||||
// Read from socket
|
||||
const auto amountReceived = _socketReceive(_receiveBuffer);
|
||||
|
||||
_session.receive(
|
||||
telnetpp::bytes{ _receiveBuffer.data(), amountReceived },
|
||||
[=](telnetpp::bytes data,
|
||||
std::function<void(telnetpp::bytes)> const& send) {
|
||||
_applicationReceive(data, send);
|
||||
},
|
||||
[=](telnetpp::bytes data) {
|
||||
_socketSend(data);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// If the exception was thrown while we were already supposed to be closing, fine. We're closed.
|
||||
// This is because the socket got mad things were being torn down.
|
||||
if (_isStateAtOrBeyond(ConnectionState::Closing))
|
||||
{
|
||||
_transitionToState(ConnectionState::Closed);
|
||||
return S_OK;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_CAUGHT_EXCEPTION();
|
||||
_transitionToState(ConnectionState::Failed);
|
||||
return E_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Call to buffer up bytes to send to the remote device.
|
||||
// - You must flush before they'll go out.
|
||||
// Arguments:
|
||||
// - data - View of bytes to be sent
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void TelnetConnection::_socketBufferedSend(telnetpp::bytes data)
|
||||
{
|
||||
// winrt::array_view should take data/size but it doesn't.
|
||||
// We contacted the WinRT owners and they said, more or less, that it's not worth fixing
|
||||
// with std::span on the horizon instead of this. So we're suppressing the warning
|
||||
// and hoping for a std::span future that will eliminate winrt::array_view<T>
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 26481)
|
||||
const uint8_t* first = data.data();
|
||||
const uint8_t* last = data.data() + data.size();
|
||||
#pragma warning(pop)
|
||||
|
||||
const winrt::array_view<const uint8_t> arrayView(first, last);
|
||||
_writer.WriteBytes(arrayView);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Flushes any buffered bytes to the underlying socket
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
fire_and_forget TelnetConnection::_socketFlushBuffer()
|
||||
{
|
||||
co_await _writer.StoreAsync();
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Used to send bytes into the socket to the remote device
|
||||
// Arguments:
|
||||
// - data - View of bytes to be sent
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void TelnetConnection::_socketSend(telnetpp::bytes data)
|
||||
{
|
||||
_socketBufferedSend(data);
|
||||
_socketFlushBuffer();
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Reads bytes from the socket into the given array.
|
||||
// Arguments:
|
||||
// - buffer - The array of bytes to use for storage
|
||||
// Return Value:
|
||||
// - The number of bytes actually read (less than or equal to input array size)
|
||||
size_t TelnetConnection::_socketReceive(gsl::span<telnetpp::byte> buffer)
|
||||
{
|
||||
const auto bytesLoaded = _reader.LoadAsync(gsl::narrow<uint32_t>(buffer.size())).get();
|
||||
|
||||
// winrt::array_view, despite having a pointer and size constructor
|
||||
// hides it as protected.
|
||||
// So we have to get first/last (even though cppcorechecks will be
|
||||
// mad at us for it) to use a public array_view constructor.
|
||||
// The WinRT team isn't fixing this because std::span is coming
|
||||
// soon and that will do it.
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 26481)
|
||||
const auto first = buffer.data();
|
||||
const auto last = first + bytesLoaded;
|
||||
#pragma warning(pop)
|
||||
|
||||
const winrt::array_view<uint8_t> arrayView(first, last);
|
||||
_reader.ReadBytes(arrayView);
|
||||
return bytesLoaded;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Called by telnetpp framework when application data is received on the channel
|
||||
// In contrast, telnet metadata payload is consumed by telnetpp and not forwarded to us.
|
||||
// Arguments:
|
||||
// - data - The relevant application-level payload received
|
||||
// - send - A function where we can send a reply to given data immediately
|
||||
// in reaction to the received message.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void TelnetConnection::_applicationReceive(telnetpp::bytes data,
|
||||
std::function<void(telnetpp::bytes)> const& /*send*/)
|
||||
{
|
||||
// Convert telnetpp bytes to standard string_view
|
||||
#pragma warning(suppress : 26490) // Using something that isn't reinterpret_cast to forward stream bytes is more clumsy than just using it.
|
||||
const auto stringView = std::string_view{ reinterpret_cast<const char*>(data.data()), gsl::narrow<size_t>(data.size()) };
|
||||
|
||||
// Convert to hstring
|
||||
const auto hstr = winrt::to_hstring(stringView);
|
||||
|
||||
// Pass the output to our registered event handlers
|
||||
_TerminalOutputHandlers(hstr);
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "TelnetConnection.g.h"
|
||||
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4100)
|
||||
#pragma warning(disable : 4251)
|
||||
#include <telnetpp/core.hpp>
|
||||
#include <telnetpp/session.hpp>
|
||||
#include <telnetpp/options/naws/server.hpp>
|
||||
#pragma warning(pop)
|
||||
|
||||
#include "winrt/Windows.Networking.Sockets.h"
|
||||
#include "winrt/Windows.Storage.Streams.h"
|
||||
|
||||
#include "../cascadia/inc/cppwinrt_utils.h"
|
||||
#include "ConnectionStateHolder.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
{
|
||||
struct TelnetConnection : TelnetConnectionT<TelnetConnection>, ConnectionStateHolder<TelnetConnection>
|
||||
{
|
||||
TelnetConnection(const hstring& uri);
|
||||
|
||||
void Start();
|
||||
void WriteInput(hstring const& data);
|
||||
void Resize(uint32_t rows, uint32_t columns);
|
||||
void Close();
|
||||
|
||||
WINRT_CALLBACK(TerminalOutput, TerminalOutputHandler);
|
||||
|
||||
private:
|
||||
hstring _uri;
|
||||
|
||||
void _applicationReceive(telnetpp::bytes data,
|
||||
std::function<void(telnetpp::bytes)> const& send);
|
||||
|
||||
void _socketBufferedSend(telnetpp::bytes data);
|
||||
fire_and_forget _socketFlushBuffer();
|
||||
void _socketSend(telnetpp::bytes data);
|
||||
size_t _socketReceive(gsl::span<telnetpp::byte> buffer);
|
||||
|
||||
telnetpp::session _session;
|
||||
// NAWS = Negotiation About Window Size
|
||||
telnetpp::options::naws::server _nawsServer;
|
||||
Windows::Networking::Sockets::StreamSocket _socket;
|
||||
Windows::Storage::Streams::DataWriter _writer;
|
||||
Windows::Storage::Streams::DataReader _reader;
|
||||
|
||||
std::optional<std::pair<uint32_t, uint32_t>> _prevResize;
|
||||
|
||||
static constexpr size_t _receiveBufferSize = 1024;
|
||||
std::array<telnetpp::byte, _receiveBufferSize> _receiveBuffer;
|
||||
|
||||
wil::unique_handle _hOutputThread;
|
||||
|
||||
DWORD _outputThread();
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::Microsoft::Terminal::TerminalConnection::factory_implementation
|
||||
{
|
||||
struct TelnetConnection : TelnetConnectionT<TelnetConnection, implementation::TelnetConnection>
|
||||
{
|
||||
};
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import "ITerminalConnection.idl";
|
||||
|
||||
namespace Microsoft.Terminal.TerminalConnection
|
||||
{
|
||||
[default_interface]
|
||||
runtimeclass TelnetConnection : ITerminalConnection
|
||||
{
|
||||
TelnetConnection(String uri);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -28,9 +28,6 @@
|
||||
<ClInclude Include="EchoConnection.h">
|
||||
<DependentUpon>EchoConnection.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="TelnetConnection.h">
|
||||
<DependentUpon>TelnetConnection.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="init.cpp" />
|
||||
@@ -47,16 +44,12 @@
|
||||
<DependentUpon>ConptyConnection.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="$(GeneratedFilesDir)module.g.cpp" />
|
||||
<ClCompile Include="TelnetConnection.cpp">
|
||||
<DependentUpon>TelnetConnection.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Midl Include="ITerminalConnection.idl" />
|
||||
<Midl Include="ConptyConnection.idl" />
|
||||
<Midl Include="EchoConnection.idl" />
|
||||
<Midl Include="AzureConnection.idl" />
|
||||
<Midl Include="TelnetConnection.idl" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PRIResource Include="Resources\en-US\Resources.resw" />
|
||||
@@ -88,14 +81,12 @@
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\..\..\packages\vcpkg-cpprestsdk.2.10.14\build\native\vcpkg-cpprestsdk.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\vcpkg-cpprestsdk.2.10.14\build\native\vcpkg-cpprestsdk.targets'))" />
|
||||
<Error Condition="!Exists('..\..\..\packages\vcpkg-telnetpp.1.0.1\build\native\vcpkg-telnetpp.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\vcpkg-telnetpp.1.0.1\build\native\vcpkg-telnetpp.targets'))" />
|
||||
</Target>
|
||||
<ItemDefinitionGroup>
|
||||
<Link>
|
||||
<AdditionalDependencies>$(OpenConsoleCommonOutDir)\conptylib.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<Import Project="..\..\..\packages\vcpkg-telnetpp.1.0.1\build\native\vcpkg-telnetpp.targets" Condition="Exists('..\..\..\packages\vcpkg-telnetpp.1.0.1\build\native\vcpkg-telnetpp.targets')" />
|
||||
|
||||
<Import Project="$(SolutionDir)build\rules\CollectWildcardResources.targets" />
|
||||
</Project>
|
||||
|
||||
@@ -18,21 +18,18 @@
|
||||
<ClCompile Include="$(GeneratedFilesDir)module.g.cpp" />
|
||||
<ClCompile Include="AzureConnection.cpp" />
|
||||
<ClCompile Include="init.cpp" />
|
||||
<ClCompile Include="TelnetConnection.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="EchoConnection.h" />
|
||||
<ClInclude Include="AzureConnection.h" />
|
||||
<ClInclude Include="AzureClientID.h" />
|
||||
<ClInclude Include="TelnetConnection.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Midl Include="ITerminalConnection.idl" />
|
||||
<Midl Include="EchoConnection.idl" />
|
||||
<Midl Include="AzureConnection.idl" />
|
||||
<Midl Include="ConptyConnection.idl" />
|
||||
<Midl Include="TelnetConnection.idl" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "pch.h"
|
||||
#include <LibraryResources.h>
|
||||
#include <WilErrorReporting.h>
|
||||
|
||||
// Note: Generate GUID using TlgGuid.exe tool
|
||||
#pragma warning(suppress : 26477) // One of the macros uses 0/NULL. We don't have control to make it nullptr.
|
||||
@@ -21,6 +22,7 @@ BOOL WINAPI DllMain(HINSTANCE hInstDll, DWORD reason, LPVOID /*reserved*/)
|
||||
case DLL_PROCESS_ATTACH:
|
||||
DisableThreadLibraryCalls(hInstDll);
|
||||
TraceLoggingRegister(g_hTerminalConnectionProvider);
|
||||
Microsoft::Console::ErrorReporting::EnableFallbackFailureReporting(g_hTerminalConnectionProvider);
|
||||
break;
|
||||
case DLL_PROCESS_DETACH:
|
||||
if (g_hTerminalConnectionProvider)
|
||||
|
||||
@@ -2,5 +2,4 @@
|
||||
<packages>
|
||||
<package id="Microsoft.Windows.CppWinRT" version="2.0.200316.3" targetFramework="native" />
|
||||
<package id="vcpkg-cpprestsdk" version="2.10.14" targetFramework="native" />
|
||||
<package id="vcpkg-telnetpp" version="1.0.1" targetFramework="native" />
|
||||
</packages>
|
||||
</packages>
|
||||
|
||||
@@ -10,8 +10,6 @@
|
||||
// Needs to be defined or we get redeclaration errors
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
|
||||
#define BLOCK_GSL
|
||||
|
||||
// Manually include til after we include Windows.Foundation to give it winrt superpowers
|
||||
#define BLOCK_TIL
|
||||
#include <LibraryIncludes.h>
|
||||
|
||||
@@ -176,6 +176,6 @@
|
||||
<value>Resume</value>
|
||||
</data>
|
||||
<data name="HowToOpenRun.Text" xml:space="preserve">
|
||||
<value>ctrl+click to follow link</value>
|
||||
<value>Ctrl+Click to follow link</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -72,6 +72,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
_actualFont{ DEFAULT_FONT_FACE, 0, DEFAULT_FONT_WEIGHT, { 0, DEFAULT_FONT_SIZE }, CP_UTF8, false },
|
||||
_touchAnchor{ std::nullopt },
|
||||
_cursorTimer{},
|
||||
_blinkTimer{},
|
||||
_lastMouseClickTimestamp{},
|
||||
_lastMouseClickPos{},
|
||||
_selectionNeedsToBeCopied{ false },
|
||||
@@ -721,6 +722,24 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
_cursorTimer = std::nullopt;
|
||||
}
|
||||
|
||||
// Set up blinking attributes
|
||||
BOOL animationsEnabled = TRUE;
|
||||
SystemParametersInfoW(SPI_GETCLIENTAREAANIMATION, 0, &animationsEnabled, 0);
|
||||
if (animationsEnabled && blinkTime != INFINITE)
|
||||
{
|
||||
// Create a timer
|
||||
DispatcherTimer blinkTimer;
|
||||
blinkTimer.Interval(std::chrono::milliseconds(blinkTime));
|
||||
blinkTimer.Tick({ get_weak(), &TermControl::_BlinkTimerTick });
|
||||
blinkTimer.Start();
|
||||
_blinkTimer.emplace(std::move(blinkTimer));
|
||||
}
|
||||
else
|
||||
{
|
||||
// The user has disabled blinking
|
||||
_blinkTimer = std::nullopt;
|
||||
}
|
||||
|
||||
// import value from WinUser (convert from milli-seconds to micro-seconds)
|
||||
_multiClickTimer = GetDoubleClickTime() * 1000;
|
||||
|
||||
@@ -873,7 +892,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// keybindings on the keyUp, then we'll still send the keydown to the
|
||||
// connected terminal application, and something like ctrl+shift+T will
|
||||
// emit a ^T to the pipe.
|
||||
if (!modifiers.IsAltGrPressed() && keyDown && _TryHandleKeyBinding(vkey, modifiers))
|
||||
if (!modifiers.IsAltGrPressed() && keyDown && _TryHandleKeyBinding(vkey, scanCode, modifiers))
|
||||
{
|
||||
e.Handled(true);
|
||||
return;
|
||||
@@ -895,8 +914,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// - Attempt to handle this key combination as a key binding
|
||||
// Arguments:
|
||||
// - vkey: The vkey of the key pressed.
|
||||
// - scanCode: The scan code of the key pressed.
|
||||
// - modifiers: The ControlKeyStates representing the modifier key states.
|
||||
bool TermControl::_TryHandleKeyBinding(const WORD vkey, ::Microsoft::Terminal::Core::ControlKeyStates modifiers) const
|
||||
bool TermControl::_TryHandleKeyBinding(const WORD vkey, const WORD scanCode, ::Microsoft::Terminal::Core::ControlKeyStates modifiers) const
|
||||
{
|
||||
auto bindings = _settings.KeyBindings();
|
||||
if (!bindings)
|
||||
@@ -904,12 +924,44 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
return false;
|
||||
}
|
||||
|
||||
return bindings.TryKeyChord({
|
||||
auto success = bindings.TryKeyChord({
|
||||
modifiers.IsCtrlPressed(),
|
||||
modifiers.IsAltPressed(),
|
||||
modifiers.IsShiftPressed(),
|
||||
vkey,
|
||||
});
|
||||
if (!success)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Let's assume the user has bound the dead key "^" to a sendInput command that sends "b".
|
||||
// If the user presses the two keys "^a" it'll produce "bâ", despite us marking the key event as handled.
|
||||
// The following is used to manually "consume" such dead keys and clear them from the keyboard state.
|
||||
_ClearKeyboardState(vkey, scanCode);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Discards currently pressed dead keys.
|
||||
// Arguments:
|
||||
// - vkey: The vkey of the key pressed.
|
||||
// - scanCode: The scan code of the key pressed.
|
||||
void TermControl::_ClearKeyboardState(const WORD vkey, const WORD scanCode) const noexcept
|
||||
{
|
||||
std::array<BYTE, 256> keyState;
|
||||
if (!GetKeyboardState(keyState.data()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// As described in "Sometimes you *want* to interfere with the keyboard's state buffer":
|
||||
// http://archives.miloush.net/michkap/archive/2006/09/10/748775.html
|
||||
// > "The key here is to keep trying to pass stuff to ToUnicode until -1 is not returned."
|
||||
std::array<wchar_t, 16> buffer;
|
||||
while (ToUnicodeEx(vkey, scanCode, keyState.data(), buffer.data(), gsl::narrow_cast<int>(buffer.size()), 0b1, nullptr) < 0)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -919,6 +971,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// - Makes the cursor briefly visible during typing.
|
||||
// Arguments:
|
||||
// - vkey: The vkey of the key pressed.
|
||||
// - scanCode: The scan code of the key pressed.
|
||||
// - states: The Microsoft::Terminal::Core::ControlKeyStates representing the modifier key states.
|
||||
// - keyDown: If true, the key was pressed, otherwise the key was released.
|
||||
bool TermControl::_TrySendKeyEvent(const WORD vkey,
|
||||
@@ -931,8 +984,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// flow through to the terminal.
|
||||
// GH#6423 - don't dismiss selection if the key that was pressed was a
|
||||
// modifier key. We'll wait for a real keystroke to dismiss the
|
||||
// GH #7395 - don't dismiss selection when taking PrintScreen
|
||||
// selection.
|
||||
if (_terminal->IsSelectionActive() && !KeyEvent::IsModifierKey(vkey))
|
||||
if (_terminal->IsSelectionActive() && !KeyEvent::IsModifierKey(vkey) && vkey != VK_SNAPSHOT)
|
||||
{
|
||||
_terminal->ClearSelection();
|
||||
_renderer->TriggerSelection();
|
||||
@@ -1777,6 +1831,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
_cursorTimer.value().Start();
|
||||
}
|
||||
|
||||
if (_blinkTimer.has_value())
|
||||
{
|
||||
_blinkTimer.value().Start();
|
||||
}
|
||||
|
||||
_UpdateSystemParameterSettings();
|
||||
}
|
||||
|
||||
@@ -1822,6 +1881,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
_cursorTimer.value().Stop();
|
||||
_terminal->SetCursorOn(false);
|
||||
}
|
||||
|
||||
if (_blinkTimer.has_value())
|
||||
{
|
||||
_blinkTimer.value().Stop();
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -2035,6 +2099,22 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
_terminal->SetCursorOn(!_terminal->IsCursorOn());
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Toggle the blinking rendition state when called by the blink timer.
|
||||
// Arguments:
|
||||
// - sender: not used
|
||||
// - e: not used
|
||||
void TermControl::_BlinkTimerTick(Windows::Foundation::IInspectable const& /* sender */,
|
||||
Windows::Foundation::IInspectable const& /* e */)
|
||||
{
|
||||
if (!_closing)
|
||||
{
|
||||
auto& renderTarget = *_renderer;
|
||||
auto& blinkingState = _terminal->GetBlinkingState();
|
||||
blinkingState.ToggleBlinkingRendition(renderTarget);
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Sets selection's end position to match supplied cursor position, e.g. while mouse dragging.
|
||||
// Arguments:
|
||||
@@ -2889,10 +2969,20 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// - Checks if the uri is valid and sends an event if so
|
||||
// Arguments:
|
||||
// - The uri
|
||||
void TermControl::_HyperlinkHandler(const std::wstring_view uri)
|
||||
winrt::fire_and_forget TermControl::_HyperlinkHandler(const std::wstring_view uri)
|
||||
{
|
||||
auto hyperlinkArgs = winrt::make_self<OpenHyperlinkEventArgs>(winrt::hstring{ uri });
|
||||
_openHyperlinkHandlers(*this, *hyperlinkArgs);
|
||||
// Save things we need to resume later.
|
||||
winrt::hstring heldUri{ uri };
|
||||
auto strongThis{ get_strong() };
|
||||
|
||||
// Pop the rest of this function to the tail of the UI thread
|
||||
// Just in case someone was holding a lock when they called us and
|
||||
// the handlers decide to do something that take another lock
|
||||
// (like ShellExecute pumping our messaging thread...GH#7994)
|
||||
co_await Dispatcher();
|
||||
|
||||
auto hyperlinkArgs = winrt::make_self<OpenHyperlinkEventArgs>(heldUri);
|
||||
_openHyperlinkHandlers(*strongThis, *hyperlinkArgs);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
||||
@@ -201,6 +201,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
std::optional<wchar_t> _leadingSurrogate;
|
||||
|
||||
std::optional<Windows::UI::Xaml::DispatcherTimer> _cursorTimer;
|
||||
std::optional<Windows::UI::Xaml::DispatcherTimer> _blinkTimer;
|
||||
|
||||
// If this is set, then we assume we are in the middle of panning the
|
||||
// viewport via touch input.
|
||||
@@ -248,9 +249,10 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
void _LostFocusHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& e);
|
||||
winrt::fire_and_forget _DragDropHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::DragEventArgs const e);
|
||||
void _DragOverHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::DragEventArgs const& e);
|
||||
void _HyperlinkHandler(const std::wstring_view uri);
|
||||
winrt::fire_and_forget _HyperlinkHandler(const std::wstring_view uri);
|
||||
|
||||
void _CursorTimerTick(Windows::Foundation::IInspectable const& sender, Windows::Foundation::IInspectable const& e);
|
||||
void _BlinkTimerTick(Windows::Foundation::IInspectable const& sender, Windows::Foundation::IInspectable const& e);
|
||||
void _SetEndSelectionPointAtCursor(Windows::Foundation::Point const& cursorPosition);
|
||||
void _SendInputToConnection(const winrt::hstring& wstr);
|
||||
void _SendInputToConnection(std::wstring_view wstr);
|
||||
@@ -281,7 +283,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
|
||||
void _KeyHandler(Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e, const bool keyDown);
|
||||
::Microsoft::Terminal::Core::ControlKeyStates _GetPressedModifierKeys() const;
|
||||
bool _TryHandleKeyBinding(const WORD vkey, ::Microsoft::Terminal::Core::ControlKeyStates modifiers) const;
|
||||
bool _TryHandleKeyBinding(const WORD vkey, const WORD scanCode, ::Microsoft::Terminal::Core::ControlKeyStates modifiers) const;
|
||||
void _ClearKeyboardState(const WORD vkey, const WORD scanCode) const noexcept;
|
||||
bool _TrySendKeyEvent(const WORD vkey, const WORD scanCode, ::Microsoft::Terminal::Core::ControlKeyStates modifiers, const bool keyDown);
|
||||
bool _TrySendMouseEvent(Windows::UI::Input::PointerPoint const& point);
|
||||
bool _CanSendVTMouseInput();
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "pch.h"
|
||||
#include <LibraryResources.h>
|
||||
#include <WilErrorReporting.h>
|
||||
|
||||
// Note: Generate GUID using TlgGuid.exe tool
|
||||
TRACELOGGING_DEFINE_PROVIDER(
|
||||
@@ -19,6 +20,7 @@ BOOL WINAPI DllMain(HINSTANCE hInstDll, DWORD reason, LPVOID /*reserved*/)
|
||||
case DLL_PROCESS_ATTACH:
|
||||
DisableThreadLibraryCalls(hInstDll);
|
||||
TraceLoggingRegister(g_hTerminalControlProvider);
|
||||
Microsoft::Console::ErrorReporting::EnableFallbackFailureReporting(g_hTerminalControlProvider);
|
||||
break;
|
||||
case DLL_PROCESS_DETACH:
|
||||
if (g_hTerminalControlProvider)
|
||||
|
||||
@@ -471,15 +471,20 @@ bool Terminal::SendKeyEvent(const WORD vkey,
|
||||
|
||||
_StoreKeyEvent(vkey, scanCode);
|
||||
|
||||
// As a Terminal we're mostly interested in getting key events from physical hardware (mouse & keyboard).
|
||||
// We're thus ignoring events whose values are outside the valid range and unlikely to be generated by the current keyboard.
|
||||
// It's very likely that a proper followup character event will be sent to us.
|
||||
// This prominently happens using AutoHotKey's keyboard remapping feature,
|
||||
// which sends input events whose vkey is 0xff and scanCode is 0.
|
||||
// We need to check for this early, as _CharacterFromKeyEvent() always returns 0 for such invalid values,
|
||||
// making us believe that this is an actual non-character input, while it usually isn't.
|
||||
// Certain applications like AutoHotKey and its keyboard remapping feature,
|
||||
// send us key events using SendInput() whose values are outside of the valid range.
|
||||
// GH#7064
|
||||
if (vkey == 0 || vkey >= 0xff || scanCode == 0)
|
||||
if (vkey == 0 || vkey >= 0xff)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// While not explicitly permitted, a wide range of software, including Windows' own touch keyboard,
|
||||
// sets the wScan member of the KEYBDINPUT structure to 0, resulting in scanCode being 0 as well.
|
||||
// --> Alternatively get the scanCode from the vkey if possible.
|
||||
// GH#7495
|
||||
const auto sc = scanCode ? scanCode : _ScanCodeFromVirtualKey(vkey);
|
||||
if (sc == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -505,7 +510,7 @@ bool Terminal::SendKeyEvent(const WORD vkey,
|
||||
// is the underlying ASCII character (e.g. A-Z) on the keyboard in our case.
|
||||
// See GH#5525/GH#6211 for more details
|
||||
const auto isSuppressedAltGrAlias = !_altGrAliasing && states.IsAltPressed() && states.IsCtrlPressed() && !states.IsAltGrPressed();
|
||||
const auto ch = isSuppressedAltGrAlias ? UNICODE_NULL : _CharacterFromKeyEvent(vkey, scanCode, states);
|
||||
const auto ch = isSuppressedAltGrAlias ? UNICODE_NULL : _CharacterFromKeyEvent(vkey, sc, states);
|
||||
|
||||
// Delegate it to the character event handler if this key event can be
|
||||
// mapped to one (see method description above). For Alt+key combinations
|
||||
@@ -520,7 +525,7 @@ bool Terminal::SendKeyEvent(const WORD vkey,
|
||||
return false;
|
||||
}
|
||||
|
||||
KeyEvent keyEv{ keyDown, 1, vkey, scanCode, ch, states.Value() };
|
||||
KeyEvent keyEv{ keyDown, 1, vkey, sc, ch, states.Value() };
|
||||
return _terminalInput->HandleKey(&keyEv);
|
||||
}
|
||||
|
||||
@@ -637,8 +642,6 @@ WORD Terminal::_VirtualKeyFromCharacter(const wchar_t ch) noexcept
|
||||
wchar_t Terminal::_CharacterFromKeyEvent(const WORD vkey, const WORD scanCode, const ControlKeyStates states) noexcept
|
||||
try
|
||||
{
|
||||
const auto sc = scanCode != 0 ? scanCode : _ScanCodeFromVirtualKey(vkey);
|
||||
|
||||
// We might want to use GetKeyboardState() instead of building our own keyState.
|
||||
// The question is whether that's necessary though. For now it seems to work fine as it is.
|
||||
std::array<BYTE, 256> keyState = {};
|
||||
@@ -657,7 +660,7 @@ try
|
||||
// * If bit 0 is set, a menu is active.
|
||||
// If this flag is not specified ToUnicodeEx will send us character events on certain Alt+Key combinations (e.g. Alt+Arrow-Up).
|
||||
// * If bit 2 is set, keyboard state is not changed (Windows 10, version 1607 and newer)
|
||||
const auto result = ToUnicodeEx(vkey, sc, keyState.data(), buffer.data(), gsl::narrow_cast<int>(buffer.size()), 0b101, nullptr);
|
||||
const auto result = ToUnicodeEx(vkey, scanCode, keyState.data(), buffer.data(), gsl::narrow_cast<int>(buffer.size()), 0b101, nullptr);
|
||||
|
||||
// TODO:GH#2853 We're only handling single UTF-16 code points right now, since that's the only thing KeyEvent supports.
|
||||
return result == 1 || result == -1 ? buffer.at(0) : 0;
|
||||
@@ -1036,3 +1039,8 @@ const std::optional<til::color> Terminal::GetTabColor() const noexcept
|
||||
{
|
||||
return _tabColor;
|
||||
}
|
||||
|
||||
BlinkingState& Terminal::GetBlinkingState() const noexcept
|
||||
{
|
||||
return _blinkingState;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include <conattrs.hpp>
|
||||
|
||||
#include "../../buffer/out/textBuffer.hpp"
|
||||
#include "../../renderer/inc/IRenderData.hpp"
|
||||
#include "../../renderer/inc/BlinkingState.hpp"
|
||||
#include "../../terminal/parser/StateMachine.hpp"
|
||||
#include "../../terminal/input/terminalInput.hpp"
|
||||
|
||||
@@ -187,6 +187,8 @@ public:
|
||||
|
||||
const std::optional<til::color> GetTabColor() const noexcept;
|
||||
|
||||
Microsoft::Console::Render::BlinkingState& GetBlinkingState() const noexcept;
|
||||
|
||||
#pragma region TextSelection
|
||||
// These methods are defined in TerminalSelection.cpp
|
||||
enum class SelectionExpansionMode
|
||||
@@ -224,6 +226,7 @@ private:
|
||||
COLORREF _defaultBg;
|
||||
CursorType _defaultCursorShape;
|
||||
bool _screenReversed;
|
||||
mutable Microsoft::Console::Render::BlinkingState _blinkingState;
|
||||
|
||||
bool _snapOnInput;
|
||||
bool _altGrAliasing;
|
||||
|
||||
@@ -574,7 +574,7 @@ CATCH_LOG_RETURN_FALSE()
|
||||
bool Terminal::AddHyperlink(std::wstring_view uri, std::wstring_view params) noexcept
|
||||
{
|
||||
auto attr = _buffer->GetCurrentAttributes();
|
||||
const auto id = _buffer->GetHyperlinkId(params);
|
||||
const auto id = _buffer->GetHyperlinkId(uri, params);
|
||||
attr.SetHyperlinkId(id);
|
||||
_buffer->SetCurrentAttributes(attr);
|
||||
_buffer->AddHyperlinkToMap(uri, id);
|
||||
|
||||
@@ -132,6 +132,7 @@ bool TerminalDispatch::SetGraphicsRendition(const gsl::span<const DispatchTypes:
|
||||
attr.SetItalic(false);
|
||||
break;
|
||||
case BlinkOrXterm256Index:
|
||||
case RapidBlink: // We just interpret rapid blink as an alias of blink.
|
||||
attr.SetBlinking(true);
|
||||
break;
|
||||
case Steady:
|
||||
|
||||
@@ -52,10 +52,12 @@ const TextAttribute Terminal::GetDefaultBrushColors() noexcept
|
||||
|
||||
std::pair<COLORREF, COLORREF> Terminal::GetAttributeColors(const TextAttribute& attr) const noexcept
|
||||
{
|
||||
_blinkingState.RecordBlinkingUsage(attr);
|
||||
auto colors = attr.CalculateRgbColors({ _colorTable.data(), _colorTable.size() },
|
||||
_defaultFg,
|
||||
_defaultBg,
|
||||
_screenReversed);
|
||||
_screenReversed,
|
||||
_blinkingState.IsBlinkingFaint());
|
||||
colors.first |= 0xff000000;
|
||||
// We only care about alpha for the default BG (which enables acrylic)
|
||||
// If the bg isn't the default bg color, or reverse video is enabled, make it fully opaque.
|
||||
|
||||
@@ -71,9 +71,7 @@ namespace TerminalCoreUnitTests
|
||||
{
|
||||
// Certain applications like AutoHotKey and its keyboard remapping feature,
|
||||
// send us key events using SendInput() whose values are outside of the valid range.
|
||||
// We don't want to handle those events as we're probably going to get a proper followup character event.
|
||||
VERIFY_IS_FALSE(term.SendKeyEvent(0, 123, {}, true));
|
||||
VERIFY_IS_FALSE(term.SendKeyEvent(255, 123, {}, true));
|
||||
VERIFY_IS_FALSE(term.SendKeyEvent(123, 0, {}, true));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ namespace TerminalCoreUnitTests
|
||||
|
||||
TEST_METHOD(AddHyperlink);
|
||||
TEST_METHOD(AddHyperlinkCustomId);
|
||||
TEST_METHOD(AddHyperlinkCustomIdDifferentUri);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -296,15 +297,45 @@ void TerminalCoreUnitTests::TerminalApiTest::AddHyperlinkCustomId()
|
||||
stateMachine.ProcessString(L"\x1b]8;id=myId;test.url\x9c");
|
||||
VERIFY_IS_TRUE(tbi.GetCurrentAttributes().IsHyperlink());
|
||||
VERIFY_ARE_EQUAL(tbi.GetHyperlinkUriFromId(tbi.GetCurrentAttributes().GetHyperlinkId()), L"test.url");
|
||||
VERIFY_ARE_EQUAL(tbi.GetHyperlinkId(L"myId"), tbi.GetCurrentAttributes().GetHyperlinkId());
|
||||
VERIFY_ARE_EQUAL(tbi.GetHyperlinkId(L"test.url", L"myId"), tbi.GetCurrentAttributes().GetHyperlinkId());
|
||||
|
||||
// Send any other text
|
||||
stateMachine.ProcessString(L"Hello World");
|
||||
VERIFY_IS_TRUE(tbi.GetCurrentAttributes().IsHyperlink());
|
||||
VERIFY_ARE_EQUAL(tbi.GetHyperlinkUriFromId(tbi.GetCurrentAttributes().GetHyperlinkId()), L"test.url");
|
||||
VERIFY_ARE_EQUAL(tbi.GetHyperlinkId(L"myId"), tbi.GetCurrentAttributes().GetHyperlinkId());
|
||||
VERIFY_ARE_EQUAL(tbi.GetHyperlinkId(L"test.url", L"myId"), tbi.GetCurrentAttributes().GetHyperlinkId());
|
||||
|
||||
// Process the closing osc 8 sequences
|
||||
stateMachine.ProcessString(L"\x1b]8;;\x9c");
|
||||
VERIFY_IS_FALSE(tbi.GetCurrentAttributes().IsHyperlink());
|
||||
}
|
||||
|
||||
void TerminalCoreUnitTests::TerminalApiTest::AddHyperlinkCustomIdDifferentUri()
|
||||
{
|
||||
// This is a nearly literal copy-paste of ScreenBufferTests::TestAddHyperlinkCustomId, adapted for the Terminal
|
||||
|
||||
Terminal term;
|
||||
DummyRenderTarget emptyRT;
|
||||
term.Create({ 100, 100 }, 0, emptyRT);
|
||||
|
||||
auto& tbi = *(term._buffer);
|
||||
auto& stateMachine = *(term._stateMachine);
|
||||
|
||||
// Process the opening osc 8 sequence
|
||||
stateMachine.ProcessString(L"\x1b]8;id=myId;test.url\x9c");
|
||||
VERIFY_IS_TRUE(tbi.GetCurrentAttributes().IsHyperlink());
|
||||
VERIFY_ARE_EQUAL(tbi.GetHyperlinkUriFromId(tbi.GetCurrentAttributes().GetHyperlinkId()), L"test.url");
|
||||
VERIFY_ARE_EQUAL(tbi.GetHyperlinkId(L"test.url", L"myId"), tbi.GetCurrentAttributes().GetHyperlinkId());
|
||||
|
||||
const auto oldAttributes{ tbi.GetCurrentAttributes() };
|
||||
|
||||
// Send any other text
|
||||
stateMachine.ProcessString(L"\x1b]8;id=myId;other.url\x9c");
|
||||
VERIFY_IS_TRUE(tbi.GetCurrentAttributes().IsHyperlink());
|
||||
VERIFY_ARE_EQUAL(tbi.GetHyperlinkUriFromId(tbi.GetCurrentAttributes().GetHyperlinkId()), L"other.url");
|
||||
VERIFY_ARE_EQUAL(tbi.GetHyperlinkId(L"other.url", L"myId"), tbi.GetCurrentAttributes().GetHyperlinkId());
|
||||
|
||||
// This second URL should not change the URL of the original ID!
|
||||
VERIFY_ARE_EQUAL(tbi.GetHyperlinkUriFromId(oldAttributes.GetHyperlinkId()), L"test.url");
|
||||
VERIFY_ARE_NOT_EQUAL(oldAttributes.GetHyperlinkId(), tbi.GetCurrentAttributes().GetHyperlinkId());
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ AppHost::AppHost() noexcept :
|
||||
std::placeholders::_1,
|
||||
std::placeholders::_2));
|
||||
_window->MouseScrolled({ this, &AppHost::_WindowMouseWheeled });
|
||||
_window->SetAlwaysOnTop(_logic.AlwaysOnTop());
|
||||
_window->SetAlwaysOnTop(_logic.GetInitialAlwaysOnTop());
|
||||
_window->MakeWindow();
|
||||
}
|
||||
|
||||
|
||||
@@ -262,7 +262,7 @@ void IslandWindow::Initialize()
|
||||
void IslandWindow::OnSize(const UINT width, const UINT height)
|
||||
{
|
||||
// update the interop window size
|
||||
SetWindowPos(_interopWindowHandle, nullptr, 0, 0, width, height, SWP_SHOWWINDOW);
|
||||
SetWindowPos(_interopWindowHandle, nullptr, 0, 0, width, height, SWP_SHOWWINDOW | SWP_NOACTIVATE);
|
||||
|
||||
if (_rootGrid)
|
||||
{
|
||||
@@ -498,7 +498,7 @@ void IslandWindow::SetAlwaysOnTop(const bool alwaysOnTop)
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
SWP_NOMOVE | SWP_NOSIZE);
|
||||
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -612,7 +612,7 @@ void IslandWindow::_SetIsBorderless(const bool borderlessEnabled)
|
||||
windowPos.top<int>(),
|
||||
windowPos.width<int>(),
|
||||
windowPos.height<int>(),
|
||||
SWP_SHOWWINDOW | SWP_FRAMECHANGED);
|
||||
SWP_SHOWWINDOW | SWP_FRAMECHANGED | SWP_NOACTIVATE);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -698,7 +698,7 @@ void IslandWindow::_ApplyWindowSize()
|
||||
newSize.top,
|
||||
newSize.right - newSize.left,
|
||||
newSize.bottom - newSize.top,
|
||||
SWP_FRAMECHANGED));
|
||||
SWP_FRAMECHANGED | SWP_NOACTIVATE));
|
||||
}
|
||||
|
||||
DEFINE_EVENT(IslandWindow, DragRegionClicked, _DragRegionClickedHandlers, winrt::delegate<>);
|
||||
|
||||
@@ -263,9 +263,9 @@ void NonClientIslandWindow::SetTitlebarContent(winrt::Windows::UI::Xaml::UIEleme
|
||||
// - the height of the border above the title bar or 0 if it's disabled
|
||||
int NonClientIslandWindow::_GetTopBorderHeight() const noexcept
|
||||
{
|
||||
// No border when maximized, or when the titlebar is invisible (by being in
|
||||
// fullscreen or focus mode).
|
||||
if (_isMaximized || (!_IsTitlebarVisible()))
|
||||
// No border when maximized or fullscreen.
|
||||
// Yet we still need it in the focus mode to allow dragging (GH#7012)
|
||||
if (_isMaximized || _fullscreen)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
@@ -370,7 +370,7 @@ void NonClientIslandWindow::_UpdateIslandPosition(const UINT windowWidth, const
|
||||
newIslandPos.Y,
|
||||
windowWidth,
|
||||
windowHeight - topBorderHeight,
|
||||
SWP_SHOWWINDOW));
|
||||
SWP_SHOWWINDOW | SWP_NOACTIVATE));
|
||||
|
||||
// This happens when we go from maximized to restored or the opposite
|
||||
// because topBorderHeight changes.
|
||||
@@ -849,7 +849,7 @@ void NonClientIslandWindow::_SetIsBorderless(const bool borderlessEnabled)
|
||||
windowPos.top<int>(),
|
||||
windowPos.width<int>(),
|
||||
windowPos.height<int>(),
|
||||
SWP_SHOWWINDOW | SWP_FRAMECHANGED);
|
||||
SWP_SHOWWINDOW | SWP_FRAMECHANGED | SWP_NOACTIVATE);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "AppHost.h"
|
||||
#include "resource.h"
|
||||
#include "../types/inc/User32Utils.hpp"
|
||||
#include <WilErrorReporting.h>
|
||||
|
||||
using namespace winrt;
|
||||
using namespace winrt::Windows::UI;
|
||||
@@ -91,6 +92,7 @@ int __stdcall wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int)
|
||||
TraceLoggingDescription("Event emitted immediately on startup"),
|
||||
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
|
||||
TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));
|
||||
::Microsoft::Console::ErrorReporting::EnableFallbackFailureReporting(g_hWindowsTerminalProvider);
|
||||
|
||||
// If Terminal is spawned by a shortcut that requests that it run in a new process group
|
||||
// while attached to a console session, that request is nonsense. That request will, however,
|
||||
|
||||
@@ -7,7 +7,6 @@ namespace Microsoft.Terminal.Wpf
|
||||
{
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows.Automation.Provider;
|
||||
|
||||
#pragma warning disable SA1600 // Elements should be documented
|
||||
internal static class NativeMethods
|
||||
@@ -187,10 +186,13 @@ namespace Microsoft.Terminal.Wpf
|
||||
public static extern void TerminalSendOutput(IntPtr terminal, string lpdata);
|
||||
|
||||
[DllImport("PublicTerminalCore.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
|
||||
public static extern uint TerminalTriggerResize(IntPtr terminal, double width, double height, out COORD dimensions);
|
||||
public static extern uint TerminalTriggerResize(IntPtr terminal, short width, short height, out COORD dimensions);
|
||||
|
||||
[DllImport("PublicTerminalCore.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
|
||||
public static extern uint TerminalResize(IntPtr terminal, COORD dimensions);
|
||||
public static extern uint TerminalTriggerResizeWithDimension(IntPtr terminal, [MarshalAs(UnmanagedType.Struct)] COORD dimensions, out SIZE dimensionsInPixels);
|
||||
|
||||
[DllImport("PublicTerminalCore.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
|
||||
public static extern uint TerminalCalculateResize(IntPtr terminal, short width, short height, out COORD dimensions);
|
||||
|
||||
[DllImport("PublicTerminalCore.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
|
||||
public static extern void TerminalDpiChanged(IntPtr terminal, int newDpi);
|
||||
@@ -205,10 +207,10 @@ namespace Microsoft.Terminal.Wpf
|
||||
public static extern void TerminalUserScroll(IntPtr terminal, int viewTop);
|
||||
|
||||
[DllImport("PublicTerminalCore.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
|
||||
public static extern uint TerminalStartSelection(IntPtr terminal, NativeMethods.COORD cursorPosition, bool altPressed);
|
||||
public static extern uint TerminalStartSelection(IntPtr terminal, COORD cursorPosition, bool altPressed);
|
||||
|
||||
[DllImport("PublicTerminalCore.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
|
||||
public static extern uint TerminalMoveSelection(IntPtr terminal, NativeMethods.COORD cursorPosition);
|
||||
public static extern uint TerminalMoveSelection(IntPtr terminal, COORD cursorPosition);
|
||||
|
||||
[DllImport("PublicTerminalCore.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
|
||||
public static extern void TerminalClearSelection(IntPtr terminal);
|
||||
@@ -278,10 +280,24 @@ namespace Microsoft.Terminal.Wpf
|
||||
public short X;
|
||||
|
||||
/// <summary>
|
||||
/// The x-coordinate of the point.
|
||||
/// The y-coordinate of the point.
|
||||
/// </summary>
|
||||
public short Y;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct SIZE
|
||||
{
|
||||
/// <summary>
|
||||
/// The x size.
|
||||
/// </summary>
|
||||
public int cx;
|
||||
|
||||
/// <summary>
|
||||
/// The y size.
|
||||
/// </summary>
|
||||
public int cy;
|
||||
}
|
||||
}
|
||||
#pragma warning restore SA1600 // Elements should be documented
|
||||
}
|
||||
|
||||
@@ -20,20 +20,6 @@ namespace Microsoft.Terminal.Wpf
|
||||
/// </remarks>
|
||||
public class TerminalContainer : HwndHost
|
||||
{
|
||||
private static void UnpackKeyMessage(IntPtr wParam, IntPtr lParam, out ushort vkey, out ushort scanCode, out ushort flags)
|
||||
{
|
||||
ulong scanCodeAndFlags = (((ulong)lParam) & 0xFFFF0000) >> 16;
|
||||
scanCode = (ushort)(scanCodeAndFlags & 0x00FFu);
|
||||
flags = (ushort)(scanCodeAndFlags & 0xFF00u);
|
||||
vkey = (ushort)wParam;
|
||||
}
|
||||
|
||||
private static void UnpackCharMessage(IntPtr wParam, IntPtr lParam, out char character, out ushort scanCode, out ushort flags)
|
||||
{
|
||||
UnpackKeyMessage(wParam, lParam, out ushort vKey, out scanCode, out flags);
|
||||
character = (char)vKey;
|
||||
}
|
||||
|
||||
private ITerminalConnection connection;
|
||||
private IntPtr hwnd;
|
||||
private IntPtr terminal;
|
||||
@@ -77,12 +63,29 @@ namespace Microsoft.Terminal.Wpf
|
||||
internal event EventHandler<int> UserScrolled;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the character rows available to the terminal.
|
||||
/// Gets or sets a value indicating whether if the renderer should automatically resize to fill the control
|
||||
/// on user action.
|
||||
/// </summary>
|
||||
internal bool AutoResize { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the size of the parent user control that hosts the terminal hwnd.
|
||||
/// </summary>
|
||||
/// <remarks>Control size is in device independent units, but for simplicity all sizes should be scaled.</remarks>
|
||||
internal Size TerminalControlSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the size of the terminal renderer.
|
||||
/// </summary>
|
||||
internal Size TerminalRendererSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current character rows available to the terminal.
|
||||
/// </summary>
|
||||
internal int Rows { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the character columns available to the terminal.
|
||||
/// Gets the current character columns available to the terminal.
|
||||
/// </summary>
|
||||
internal int Columns { get; private set; }
|
||||
|
||||
@@ -135,7 +138,10 @@ namespace Microsoft.Terminal.Wpf
|
||||
|
||||
NativeMethods.TerminalSetTheme(this.terminal, theme, fontFamily, fontSize, (int)dpiScale.PixelsPerInchX);
|
||||
|
||||
this.TriggerResize(this.RenderSize);
|
||||
if (!this.RenderSize.IsEmpty)
|
||||
{
|
||||
this.Resize(this.TerminalControlSize);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -153,43 +159,89 @@ namespace Microsoft.Terminal.Wpf
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Triggers a refresh of the terminal with the given size.
|
||||
/// Triggers a resize of the terminal with the given size, redrawing the rendered text.
|
||||
/// </summary>
|
||||
/// <param name="renderSize">Size of the rendering window.</param>
|
||||
/// <returns>Tuple with rows and columns.</returns>
|
||||
internal (int rows, int columns) TriggerResize(Size renderSize)
|
||||
internal void Resize(Size renderSize)
|
||||
{
|
||||
var dpiScale = VisualTreeHelper.GetDpi(this);
|
||||
if (renderSize.Width == 0 || renderSize.Height == 0)
|
||||
{
|
||||
throw new ArgumentException(nameof(renderSize), "Terminal column or row count cannot be 0.");
|
||||
}
|
||||
|
||||
NativeMethods.COORD dimensions;
|
||||
NativeMethods.TerminalTriggerResize(this.terminal, renderSize.Width * dpiScale.DpiScaleX, renderSize.Height * dpiScale.DpiScaleY, out dimensions);
|
||||
NativeMethods.TerminalTriggerResize(
|
||||
this.terminal,
|
||||
Convert.ToInt16(renderSize.Width),
|
||||
Convert.ToInt16(renderSize.Height),
|
||||
out NativeMethods.COORD dimensions);
|
||||
|
||||
this.Rows = dimensions.Y;
|
||||
this.Columns = dimensions.X;
|
||||
this.TerminalRendererSize = renderSize;
|
||||
|
||||
this.connection?.Resize((uint)dimensions.Y, (uint)dimensions.X);
|
||||
return (dimensions.Y, dimensions.X);
|
||||
this.Connection?.Resize((uint)dimensions.Y, (uint)dimensions.X);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resizes the terminal.
|
||||
/// Resizes the terminal using row and column count as the new size.
|
||||
/// </summary>
|
||||
/// <param name="rows">Number of rows to show.</param>
|
||||
/// <param name="columns">Number of columns to show.</param>
|
||||
internal void Resize(uint rows, uint columns)
|
||||
{
|
||||
if (rows == 0)
|
||||
{
|
||||
throw new ArgumentException(nameof(rows), "Terminal row count cannot be 0.");
|
||||
}
|
||||
else if (columns == 0)
|
||||
{
|
||||
throw new ArgumentException(nameof(columns), "Terminal column count cannot be 0.");
|
||||
}
|
||||
|
||||
NativeMethods.SIZE dimensionsInPixels;
|
||||
NativeMethods.COORD dimensions = new NativeMethods.COORD
|
||||
{
|
||||
X = (short)columns,
|
||||
Y = (short)rows,
|
||||
};
|
||||
|
||||
NativeMethods.TerminalResize(this.terminal, dimensions);
|
||||
NativeMethods.TerminalTriggerResizeWithDimension(this.terminal, dimensions, out dimensionsInPixels);
|
||||
|
||||
this.Rows = dimensions.Y;
|
||||
this.Columns = dimensions.X;
|
||||
this.Rows = dimensions.Y;
|
||||
|
||||
this.connection?.Resize((uint)dimensions.Y, (uint)dimensions.X);
|
||||
this.TerminalRendererSize = new Size()
|
||||
{
|
||||
Width = dimensionsInPixels.cx,
|
||||
Height = dimensionsInPixels.cy,
|
||||
};
|
||||
|
||||
this.Connection?.Resize((uint)dimensions.Y, (uint)dimensions.X);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the rows and columns that would fit in the given size.
|
||||
/// </summary>
|
||||
/// <param name="size">DPI scaled size.</param>
|
||||
/// <returns>Amount of rows and columns that would fit the given size.</returns>
|
||||
internal (uint columns, uint rows) CalculateRowsAndColumns(Size size)
|
||||
{
|
||||
NativeMethods.TerminalCalculateResize(this.terminal, (short)size.Width, (short)size.Height, out NativeMethods.COORD dimensions);
|
||||
|
||||
return ((uint)dimensions.X, (uint)dimensions.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Triggers the terminal resize event if more space is available in the terminal control.
|
||||
/// </summary>
|
||||
internal void RaiseResizedIfDrawSpaceIncreased()
|
||||
{
|
||||
(var columns, var rows) = this.CalculateRowsAndColumns(this.TerminalControlSize);
|
||||
|
||||
if (this.Columns < columns || this.Rows < rows)
|
||||
{
|
||||
this.connection?.Resize(rows, columns);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -238,6 +290,20 @@ namespace Microsoft.Terminal.Wpf
|
||||
this.terminal = IntPtr.Zero;
|
||||
}
|
||||
|
||||
private static void UnpackKeyMessage(IntPtr wParam, IntPtr lParam, out ushort vkey, out ushort scanCode, out ushort flags)
|
||||
{
|
||||
ulong scanCodeAndFlags = (((ulong)lParam) & 0xFFFF0000) >> 16;
|
||||
scanCode = (ushort)(scanCodeAndFlags & 0x00FFu);
|
||||
flags = (ushort)(scanCodeAndFlags & 0xFF00u);
|
||||
vkey = (ushort)wParam;
|
||||
}
|
||||
|
||||
private static void UnpackCharMessage(IntPtr wParam, IntPtr lParam, out char character, out ushort scanCode, out ushort flags)
|
||||
{
|
||||
UnpackKeyMessage(wParam, lParam, out ushort vKey, out scanCode, out flags);
|
||||
character = (char)vKey;
|
||||
}
|
||||
|
||||
private void TerminalContainer_GotFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
e.Handled = true;
|
||||
@@ -294,18 +360,36 @@ namespace Microsoft.Terminal.Wpf
|
||||
|
||||
case NativeMethods.WindowMessage.WM_WINDOWPOSCHANGED:
|
||||
var windowpos = (NativeMethods.WINDOWPOS)Marshal.PtrToStructure(lParam, typeof(NativeMethods.WINDOWPOS));
|
||||
if (((NativeMethods.SetWindowPosFlags)windowpos.flags).HasFlag(NativeMethods.SetWindowPosFlags.SWP_NOSIZE))
|
||||
if (((NativeMethods.SetWindowPosFlags)windowpos.flags).HasFlag(NativeMethods.SetWindowPosFlags.SWP_NOSIZE)
|
||||
|| (windowpos.cx == 0 && windowpos.cy == 0))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
NativeMethods.TerminalTriggerResize(this.terminal, windowpos.cx, windowpos.cy, out var dimensions);
|
||||
NativeMethods.COORD dimensions;
|
||||
|
||||
this.connection?.Resize((uint)dimensions.Y, (uint)dimensions.X);
|
||||
this.Columns = dimensions.X;
|
||||
this.Rows = dimensions.Y;
|
||||
if (this.AutoResize)
|
||||
{
|
||||
NativeMethods.TerminalTriggerResize(this.terminal, (short)windowpos.cx, (short)windowpos.cy, out dimensions);
|
||||
|
||||
this.Columns = dimensions.X;
|
||||
this.Rows = dimensions.Y;
|
||||
|
||||
this.TerminalRendererSize = new Size()
|
||||
{
|
||||
Width = windowpos.cx,
|
||||
Height = windowpos.cy,
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
// Calculate the new columns and rows that fit the total control size and alert the control to redraw the margins.
|
||||
NativeMethods.TerminalCalculateResize(this.terminal, (short)this.TerminalControlSize.Width, (short)this.TerminalControlSize.Height, out dimensions);
|
||||
}
|
||||
|
||||
this.Connection?.Resize((uint)dimensions.Y, (uint)dimensions.X);
|
||||
break;
|
||||
|
||||
case NativeMethods.WindowMessage.WM_MOUSEWHEEL:
|
||||
var delta = (short)(((long)wParam) >> 16);
|
||||
this.UserScrolled?.Invoke(this, delta);
|
||||
@@ -360,7 +444,7 @@ namespace Microsoft.Terminal.Wpf
|
||||
|
||||
private void OnWrite(string data)
|
||||
{
|
||||
this.connection?.WriteInput(data);
|
||||
this.Connection?.WriteInput(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,9 @@
|
||||
xmlns:local="clr-namespace:Microsoft.Terminal.Wpf"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
Focusable="True">
|
||||
<Grid>
|
||||
Focusable="True"
|
||||
x:Name="terminalUserControl">
|
||||
<Grid x:Name="terminalGrid">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
|
||||
@@ -5,10 +5,13 @@
|
||||
|
||||
namespace Microsoft.Terminal.Wpf
|
||||
{
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using Task = System.Threading.Tasks.Task;
|
||||
|
||||
/// <summary>
|
||||
/// A basic terminal control. This control can receive and render standard VT100 sequences.
|
||||
@@ -17,6 +20,14 @@ namespace Microsoft.Terminal.Wpf
|
||||
{
|
||||
private int accumulatedDelta = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets size of the terminal renderer.
|
||||
/// </summary>
|
||||
private Size TerminalRendererSize
|
||||
{
|
||||
get => this.termContainer.TerminalRendererSize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TerminalControl"/> class.
|
||||
/// </summary>
|
||||
@@ -32,24 +43,31 @@ namespace Microsoft.Terminal.Wpf
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the character rows available to the terminal.
|
||||
/// Gets the current character rows available to the terminal.
|
||||
/// </summary>
|
||||
public int Rows => this.termContainer.Rows;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the character columns available to the terminal.
|
||||
/// Gets the current character columns available to the terminal.
|
||||
/// </summary>
|
||||
public int Columns => this.termContainer.Columns;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether if the renderer should automatically resize to fill the control
|
||||
/// on user action.
|
||||
/// </summary>
|
||||
public bool AutoResize
|
||||
{
|
||||
get => this.termContainer.AutoResize;
|
||||
set => this.termContainer.AutoResize = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the connection to a terminal backend.
|
||||
/// </summary>
|
||||
public ITerminalConnection Connection
|
||||
{
|
||||
set
|
||||
{
|
||||
this.termContainer.Connection = value;
|
||||
}
|
||||
set => this.termContainer.Connection = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -68,6 +86,13 @@ namespace Microsoft.Terminal.Wpf
|
||||
}
|
||||
|
||||
this.termContainer.SetTheme(theme, fontFamily, fontSize);
|
||||
|
||||
// DefaultBackground uses Win32 COLORREF syntax which is BGR instead of RGB.
|
||||
byte b = Convert.ToByte((theme.DefaultBackground >> 16) & 0xff);
|
||||
byte g = Convert.ToByte((theme.DefaultBackground >> 8) & 0xff);
|
||||
byte r = Convert.ToByte(theme.DefaultBackground & 0xff);
|
||||
|
||||
this.terminalGrid.Background = new SolidColorBrush(Color.FromRgb(r, g, b));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -84,19 +109,98 @@ namespace Microsoft.Terminal.Wpf
|
||||
/// </summary>
|
||||
/// <param name="rows">Number of rows to display.</param>
|
||||
/// <param name="columns">Number of columns to display.</param>
|
||||
public void Resize(uint rows, uint columns)
|
||||
/// <param name="cancellationToken">Cancellation token for this task.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
public async Task ResizeAsync(uint rows, uint columns, CancellationToken cancellationToken)
|
||||
{
|
||||
this.termContainer.Resize(rows, columns);
|
||||
|
||||
#pragma warning disable VSTHRD001 // Avoid legacy thread switching APIs
|
||||
await this.Dispatcher.BeginInvoke(
|
||||
new Action(delegate() { this.terminalGrid.Margin = this.CalculateMargins(); }),
|
||||
System.Windows.Threading.DispatcherPriority.Render);
|
||||
#pragma warning restore VSTHRD001 // Avoid legacy thread switching APIs
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resizes the terminal to the specified dimensions.
|
||||
/// </summary>
|
||||
/// <param name="rendersize">Rendering size for the terminal.</param>
|
||||
/// <param name="rendersize">Rendering size for the terminal in device independent units.</param>
|
||||
/// <returns>A tuple of (int, int) representing the number of rows and columns in the terminal.</returns>
|
||||
public (int rows, int columns) TriggerResize(Size rendersize)
|
||||
{
|
||||
return this.termContainer.TriggerResize(rendersize);
|
||||
var dpiScale = VisualTreeHelper.GetDpi(this);
|
||||
rendersize.Width *= dpiScale.DpiScaleX;
|
||||
rendersize.Height *= dpiScale.DpiScaleY;
|
||||
|
||||
this.termContainer.Resize(rendersize);
|
||||
|
||||
return (this.Rows, this.Columns);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
|
||||
{
|
||||
var dpiScale = VisualTreeHelper.GetDpi(this);
|
||||
|
||||
// termContainer requires scaled sizes.
|
||||
this.termContainer.TerminalControlSize = new Size()
|
||||
{
|
||||
Width = (sizeInfo.NewSize.Width - this.scrollbar.ActualWidth) * dpiScale.DpiScaleX,
|
||||
Height = sizeInfo.NewSize.Height * dpiScale.DpiScaleY,
|
||||
};
|
||||
|
||||
if (!this.AutoResize)
|
||||
{
|
||||
// Renderer will not resize on control resize. We have to manually calculate the margin to fill in the space.
|
||||
this.terminalGrid.Margin = this.CalculateMargins(sizeInfo.NewSize);
|
||||
|
||||
// Margins stop resize events, therefore we have to manually check if more space is available and raise
|
||||
// a resize event if needed.
|
||||
this.termContainer.RaiseResizedIfDrawSpaceIncreased();
|
||||
}
|
||||
|
||||
base.OnRenderSizeChanged(sizeInfo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the margins that should surround the terminal renderer, if any.
|
||||
/// </summary>
|
||||
/// <param name="controlSize">New size of the control. Uses the control's current size if not provided.</param>
|
||||
/// <returns>The new terminal control margin thickness in device independent units.</returns>
|
||||
private Thickness CalculateMargins(Size controlSize = default)
|
||||
{
|
||||
var dpiScale = VisualTreeHelper.GetDpi(this);
|
||||
double width = 0, height = 0;
|
||||
|
||||
if (controlSize == default)
|
||||
{
|
||||
controlSize = new Size()
|
||||
{
|
||||
Width = this.terminalUserControl.ActualWidth,
|
||||
Height = this.terminalUserControl.ActualHeight,
|
||||
};
|
||||
}
|
||||
|
||||
// During initialization, the terminal renderer size will be 0 and the terminal renderer
|
||||
// draws on all available space. Therefore no margins are needed until resized.
|
||||
if (this.TerminalRendererSize.Width != 0)
|
||||
{
|
||||
width = controlSize.Width - (this.TerminalRendererSize.Width / dpiScale.DpiScaleX);
|
||||
}
|
||||
|
||||
if (this.TerminalRendererSize.Height != 0)
|
||||
{
|
||||
height = controlSize.Height - (this.TerminalRendererSize.Height / dpiScale.DpiScaleY);
|
||||
}
|
||||
|
||||
width -= this.scrollbar.ActualWidth;
|
||||
|
||||
// Prevent negative margin size.
|
||||
width = width < 0 ? 0 : width;
|
||||
height = height < 0 ? 0 : height;
|
||||
|
||||
return new Thickness(0, 0, width, height);
|
||||
}
|
||||
|
||||
private void TerminalControl_GotFocus(object sender, RoutedEventArgs e)
|
||||
|
||||
@@ -28,6 +28,12 @@ void CursorBlinker::UpdateSystemMetrics()
|
||||
{
|
||||
// This can be -1 in a TS session
|
||||
_uCaretBlinkTime = ServiceLocator::LocateSystemConfigurationProvider()->GetCaretBlinkTime();
|
||||
|
||||
// If animations are disabled, or the blink rate is infinite, blinking is not allowed.
|
||||
BOOL animationsEnabled = TRUE;
|
||||
SystemParametersInfoW(SPI_GETCLIENTAREAANIMATION, 0, &animationsEnabled, 0);
|
||||
auto& blinkingState = ServiceLocator::LocateGlobals().getConsoleInformation().GetBlinkingState();
|
||||
blinkingState.SetBlinkingAllowed(animationsEnabled && _uCaretBlinkTime != INFINITE);
|
||||
}
|
||||
|
||||
void CursorBlinker::SettingsChanged()
|
||||
@@ -53,7 +59,8 @@ void CursorBlinker::FocusStart()
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - This routine is called when the timer in the console with the focus goes off. It blinks the cursor.
|
||||
// - This routine is called when the timer in the console with the focus goes off.
|
||||
// It blinks the cursor and also toggles the rendition of any blinking attributes.
|
||||
// Arguments:
|
||||
// - ScreenInfo - reference to screen info structure.
|
||||
// Return Value:
|
||||
@@ -109,7 +116,7 @@ void CursorBlinker::TimerRoutine(SCREEN_INFORMATION& ScreenInfo)
|
||||
if (cursor.GetDelay())
|
||||
{
|
||||
cursor.SetDelay(false);
|
||||
goto DoScroll;
|
||||
goto DoBlinkingRenditionAndScroll;
|
||||
}
|
||||
|
||||
// Don't blink the cursor for remote sessions.
|
||||
@@ -118,7 +125,7 @@ void CursorBlinker::TimerRoutine(SCREEN_INFORMATION& ScreenInfo)
|
||||
(!cursor.IsBlinkingAllowed())) &&
|
||||
cursor.IsOn())
|
||||
{
|
||||
goto DoScroll;
|
||||
goto DoBlinkingRenditionAndScroll;
|
||||
}
|
||||
|
||||
// Blink only if the cursor isn't turned off via the API
|
||||
@@ -127,6 +134,9 @@ void CursorBlinker::TimerRoutine(SCREEN_INFORMATION& ScreenInfo)
|
||||
cursor.SetIsOn(!cursor.IsOn());
|
||||
}
|
||||
|
||||
DoBlinkingRenditionAndScroll:
|
||||
gci.GetBlinkingState().ToggleBlinkingRendition(ScreenInfo.GetRenderTarget());
|
||||
|
||||
DoScroll:
|
||||
Scrolling::s_ScrollIfNecessary(ScreenInfo);
|
||||
}
|
||||
|
||||
@@ -62,6 +62,10 @@ void ConsoleImeInfo::WriteCompMessage(const std::wstring_view text,
|
||||
{
|
||||
ClearAllAreas();
|
||||
|
||||
// MSFT:29219348 only hide the cursor after the IME produces a string.
|
||||
// See notes in convarea.cpp ImeStartComposition().
|
||||
SaveCursorVisibility();
|
||||
|
||||
// Save copies of the composition message in case we need to redraw it as things scroll/resize
|
||||
_text = text;
|
||||
_attributes.assign(attributes.begin(), attributes.end());
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "..\types\inc\convert.hpp"
|
||||
|
||||
using Microsoft::Console::Interactivity::ServiceLocator;
|
||||
using Microsoft::Console::Render::BlinkingState;
|
||||
using Microsoft::Console::VirtualTerminal::VtIo;
|
||||
|
||||
CONSOLE_INFORMATION::CONSOLE_INFORMATION() :
|
||||
@@ -222,7 +223,8 @@ InputBuffer* const CONSOLE_INFORMATION::GetActiveInputBuffer() const
|
||||
// - the default foreground color of the console.
|
||||
COLORREF CONSOLE_INFORMATION::GetDefaultForeground() const noexcept
|
||||
{
|
||||
return Settings::CalculateDefaultForeground();
|
||||
const auto fg = GetDefaultForegroundColor();
|
||||
return fg != INVALID_COLOR ? fg : GetColorTableEntry(LOBYTE(GetFillAttribute()) & FG_ATTRS);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -236,7 +238,25 @@ COLORREF CONSOLE_INFORMATION::GetDefaultForeground() const noexcept
|
||||
// - the default background color of the console.
|
||||
COLORREF CONSOLE_INFORMATION::GetDefaultBackground() const noexcept
|
||||
{
|
||||
return Settings::CalculateDefaultBackground();
|
||||
const auto bg = GetDefaultBackgroundColor();
|
||||
return bg != INVALID_COLOR ? bg : GetColorTableEntry((LOBYTE(GetFillAttribute()) & BG_ATTRS) >> 4);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Get the colors of a particular text attribute, using our color table,
|
||||
// and our configured default attributes.
|
||||
// Arguments:
|
||||
// - attr: the TextAttribute to retrieve the foreground color of.
|
||||
// Return Value:
|
||||
// - The color values of the attribute's foreground and background.
|
||||
std::pair<COLORREF, COLORREF> CONSOLE_INFORMATION::LookupAttributeColors(const TextAttribute& attr) const noexcept
|
||||
{
|
||||
_blinkingState.RecordBlinkingUsage(attr);
|
||||
return attr.CalculateRgbColors(Get256ColorTable(),
|
||||
GetDefaultForeground(),
|
||||
GetDefaultBackground(),
|
||||
IsScreenReversed(),
|
||||
_blinkingState.IsBlinkingFaint());
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -355,6 +375,17 @@ Microsoft::Console::CursorBlinker& CONSOLE_INFORMATION::GetCursorBlinker() noexc
|
||||
return _blinker;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - return a reference to the console's blinking state.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - a reference to the console's blinking state.
|
||||
BlinkingState& CONSOLE_INFORMATION::GetBlinkingState() const noexcept
|
||||
{
|
||||
return _blinkingState;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Generates a CHAR_INFO for this output cell, using the TextAttribute
|
||||
// GetLegacyAttributes method to generate the legacy style attributes.
|
||||
|
||||
@@ -104,8 +104,11 @@ void WriteConvRegionToScreen(const SCREEN_INFORMATION& ScreenInfo,
|
||||
gci.LockConsole();
|
||||
auto unlock = wil::scope_exit([&] { gci.UnlockConsole(); });
|
||||
|
||||
ConsoleImeInfo* const pIme = &gci.ConsoleIme;
|
||||
pIme->SaveCursorVisibility();
|
||||
// MSFT:29219348 Some IME implementations do not produce composition strings, and
|
||||
// their users have come to rely on the cursor that conhost traditionally left on
|
||||
// until a composition string showed up.
|
||||
// One such IME is WNWB's "Universal Wubi input method" from wnwb.com (v. 10+).
|
||||
// We shouldn't hide the cursor here so as to not break those IMEs.
|
||||
|
||||
gci.pInputBuffer->fInComposition = true;
|
||||
return S_OK;
|
||||
|
||||
@@ -1567,7 +1567,7 @@ void DoSrvAddHyperlink(SCREEN_INFORMATION& screenInfo,
|
||||
const std::wstring_view params)
|
||||
{
|
||||
auto attr = screenInfo.GetAttributes();
|
||||
const auto id = screenInfo.GetTextBuffer().GetHyperlinkId(params);
|
||||
const auto id = screenInfo.GetTextBuffer().GetHyperlinkId(uri, params);
|
||||
attr.SetHyperlinkId(id);
|
||||
screenInfo.GetTextBuffer().SetCurrentAttributes(attr);
|
||||
screenInfo.GetTextBuffer().AddHyperlinkToMap(uri, id);
|
||||
|
||||
@@ -28,6 +28,7 @@ Revision History:
|
||||
#include "..\server\WaitQueue.h"
|
||||
|
||||
#include "..\host\RenderData.hpp"
|
||||
#include "..\renderer\inc\BlinkingState.hpp"
|
||||
|
||||
// clang-format off
|
||||
// Flags flags
|
||||
@@ -125,6 +126,7 @@ public:
|
||||
|
||||
COLORREF GetDefaultForeground() const noexcept;
|
||||
COLORREF GetDefaultBackground() const noexcept;
|
||||
std::pair<COLORREF, COLORREF> LookupAttributeColors(const TextAttribute& attr) const noexcept;
|
||||
|
||||
void SetTitle(const std::wstring_view newTitle);
|
||||
void SetTitlePrefix(const std::wstring& newTitlePrefix);
|
||||
@@ -141,6 +143,7 @@ public:
|
||||
friend class SCREEN_INFORMATION;
|
||||
friend class CommonState;
|
||||
Microsoft::Console::CursorBlinker& GetCursorBlinker() noexcept;
|
||||
Microsoft::Console::Render::BlinkingState& GetBlinkingState() const noexcept;
|
||||
|
||||
CHAR_INFO AsCharInfo(const OutputCellView& cell) const noexcept;
|
||||
|
||||
@@ -157,6 +160,7 @@ private:
|
||||
|
||||
Microsoft::Console::VirtualTerminal::VtIo _vtIo;
|
||||
Microsoft::Console::CursorBlinker _blinker;
|
||||
mutable Microsoft::Console::Render::BlinkingState _blinkingState;
|
||||
};
|
||||
|
||||
#define ConsoleLocked() (ServiceLocator::LocateGlobals()->getConsoleInformation()->ConsoleLock.OwningThread == NtCurrentTeb()->ClientId.UniqueThread)
|
||||
|
||||
@@ -831,51 +831,6 @@ bool Settings::GetUseDx() const noexcept
|
||||
return _fUseDx;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Return the default foreground color of the console. If the settings are
|
||||
// configured to have a default foreground color (separate from the color
|
||||
// table), this will return that value. Otherwise it will return the value
|
||||
// from the colortable corresponding to our default legacy attributes.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - the default foreground color of the console.
|
||||
COLORREF Settings::CalculateDefaultForeground() const noexcept
|
||||
{
|
||||
const auto fg = GetDefaultForegroundColor();
|
||||
return fg != INVALID_COLOR ? fg : GetColorTableEntry(LOBYTE(_wFillAttribute) & FG_ATTRS);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Return the default background color of the console. If the settings are
|
||||
// configured to have a default background color (separate from the color
|
||||
// table), this will return that value. Otherwise it will return the value
|
||||
// from the colortable corresponding to our default legacy attributes.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - the default background color of the console.
|
||||
COLORREF Settings::CalculateDefaultBackground() const noexcept
|
||||
{
|
||||
const auto bg = GetDefaultBackgroundColor();
|
||||
return bg != INVALID_COLOR ? bg : GetColorTableEntry((LOBYTE(_wFillAttribute) & BG_ATTRS) >> 4);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Get the colors of a particular text attribute, using our color table,
|
||||
// and our configured default attributes.
|
||||
// Arguments:
|
||||
// - attr: the TextAttribute to retrieve the foreground color of.
|
||||
// Return Value:
|
||||
// - The color values of the attribute's foreground and background.
|
||||
std::pair<COLORREF, COLORREF> Settings::LookupAttributeColors(const TextAttribute& attr) const noexcept
|
||||
{
|
||||
return attr.CalculateRgbColors(Get256ColorTable(),
|
||||
CalculateDefaultForeground(),
|
||||
CalculateDefaultBackground(),
|
||||
_fScreenReversed);
|
||||
}
|
||||
|
||||
bool Settings::GetCopyColor() const noexcept
|
||||
{
|
||||
return _fCopyColor;
|
||||
|
||||
@@ -185,10 +185,6 @@ public:
|
||||
bool GetUseDx() const noexcept;
|
||||
bool GetCopyColor() const noexcept;
|
||||
|
||||
COLORREF CalculateDefaultForeground() const noexcept;
|
||||
COLORREF CalculateDefaultBackground() const noexcept;
|
||||
std::pair<COLORREF, COLORREF> LookupAttributeColors(const TextAttribute& attr) const noexcept;
|
||||
|
||||
private:
|
||||
DWORD _dwHotKey;
|
||||
DWORD _dwStartupFlags;
|
||||
|
||||
@@ -213,6 +213,7 @@ class ScreenBufferTests
|
||||
|
||||
TEST_METHOD(TestAddHyperlink);
|
||||
TEST_METHOD(TestAddHyperlinkCustomId);
|
||||
TEST_METHOD(TestAddHyperlinkCustomIdDifferentUri);
|
||||
|
||||
TEST_METHOD(UpdateVirtualBottomWhenCursorMovesBelowIt);
|
||||
|
||||
@@ -5956,19 +5957,46 @@ void ScreenBufferTests::TestAddHyperlinkCustomId()
|
||||
stateMachine.ProcessString(L"\x1b]8;id=myId;test.url\x9c");
|
||||
VERIFY_IS_TRUE(tbi.GetCurrentAttributes().IsHyperlink());
|
||||
VERIFY_ARE_EQUAL(tbi.GetHyperlinkUriFromId(tbi.GetCurrentAttributes().GetHyperlinkId()), L"test.url");
|
||||
VERIFY_ARE_EQUAL(tbi.GetHyperlinkId(L"myId"), tbi.GetCurrentAttributes().GetHyperlinkId());
|
||||
VERIFY_ARE_EQUAL(tbi.GetHyperlinkId(L"test.url", L"myId"), tbi.GetCurrentAttributes().GetHyperlinkId());
|
||||
|
||||
// Send any other text
|
||||
stateMachine.ProcessString(L"Hello World");
|
||||
VERIFY_IS_TRUE(tbi.GetCurrentAttributes().IsHyperlink());
|
||||
VERIFY_ARE_EQUAL(tbi.GetHyperlinkUriFromId(tbi.GetCurrentAttributes().GetHyperlinkId()), L"test.url");
|
||||
VERIFY_ARE_EQUAL(tbi.GetHyperlinkId(L"myId"), tbi.GetCurrentAttributes().GetHyperlinkId());
|
||||
VERIFY_ARE_EQUAL(tbi.GetHyperlinkId(L"test.url", L"myId"), tbi.GetCurrentAttributes().GetHyperlinkId());
|
||||
|
||||
// Process the closing osc 8 sequences
|
||||
stateMachine.ProcessString(L"\x1b]8;;\x9c");
|
||||
VERIFY_IS_FALSE(tbi.GetCurrentAttributes().IsHyperlink());
|
||||
}
|
||||
|
||||
void ScreenBufferTests::TestAddHyperlinkCustomIdDifferentUri()
|
||||
{
|
||||
auto& g = ServiceLocator::LocateGlobals();
|
||||
auto& gci = g.getConsoleInformation();
|
||||
auto& si = gci.GetActiveOutputBuffer();
|
||||
auto& tbi = si.GetTextBuffer();
|
||||
auto& stateMachine = si.GetStateMachine();
|
||||
|
||||
// Process the opening osc 8 sequence with a custom id
|
||||
stateMachine.ProcessString(L"\x1b]8;id=myId;test.url\x9c");
|
||||
VERIFY_IS_TRUE(tbi.GetCurrentAttributes().IsHyperlink());
|
||||
VERIFY_ARE_EQUAL(tbi.GetHyperlinkUriFromId(tbi.GetCurrentAttributes().GetHyperlinkId()), L"test.url");
|
||||
VERIFY_ARE_EQUAL(tbi.GetHyperlinkId(L"test.url", L"myId"), tbi.GetCurrentAttributes().GetHyperlinkId());
|
||||
|
||||
const auto oldAttributes{ tbi.GetCurrentAttributes() };
|
||||
|
||||
// Send any other text
|
||||
stateMachine.ProcessString(L"\x1b]8;id=myId;other.url\x9c");
|
||||
VERIFY_IS_TRUE(tbi.GetCurrentAttributes().IsHyperlink());
|
||||
VERIFY_ARE_EQUAL(tbi.GetHyperlinkUriFromId(tbi.GetCurrentAttributes().GetHyperlinkId()), L"other.url");
|
||||
VERIFY_ARE_EQUAL(tbi.GetHyperlinkId(L"other.url", L"myId"), tbi.GetCurrentAttributes().GetHyperlinkId());
|
||||
|
||||
// This second URL should not change the URL of the original ID!
|
||||
VERIFY_ARE_EQUAL(tbi.GetHyperlinkUriFromId(oldAttributes.GetHyperlinkId()), L"test.url");
|
||||
VERIFY_ARE_NOT_EQUAL(oldAttributes.GetHyperlinkId(), tbi.GetCurrentAttributes().GetHyperlinkId());
|
||||
}
|
||||
|
||||
void ScreenBufferTests::UpdateVirtualBottomWhenCursorMovesBelowIt()
|
||||
{
|
||||
auto& g = ServiceLocator::LocateGlobals();
|
||||
|
||||
@@ -148,6 +148,7 @@ class TextBufferTests
|
||||
|
||||
void WriteLinesToBuffer(const std::vector<std::wstring>& text, TextBuffer& buffer);
|
||||
TEST_METHOD(GetWordBoundaries);
|
||||
TEST_METHOD(MoveByWord);
|
||||
TEST_METHOD(GetGlyphBoundaries);
|
||||
|
||||
TEST_METHOD(GetTextRects);
|
||||
@@ -2065,7 +2066,7 @@ void TextBufferTests::GetWordBoundaries()
|
||||
{ { 79, 0 }, {{ 10, 0 }, { 5, 0 }} },
|
||||
|
||||
// tests for second line of text
|
||||
{ { 0, 1 }, {{ 0, 1 }, { 0, 1 }} },
|
||||
{ { 0, 1 }, {{ 0, 1 }, { 5, 0 }} },
|
||||
{ { 1, 1 }, {{ 0, 1 }, { 5, 0 }} },
|
||||
{ { 2, 1 }, {{ 2, 1 }, { 2, 1 }} },
|
||||
{ { 3, 1 }, {{ 2, 1 }, { 2, 1 }} },
|
||||
@@ -2132,6 +2133,87 @@ void TextBufferTests::GetWordBoundaries()
|
||||
}
|
||||
}
|
||||
|
||||
void TextBufferTests::MoveByWord()
|
||||
{
|
||||
COORD bufferSize{ 80, 9001 };
|
||||
UINT cursorSize = 12;
|
||||
TextAttribute attr{ 0x7f };
|
||||
auto _buffer = std::make_unique<TextBuffer>(bufferSize, attr, cursorSize, _renderTarget);
|
||||
|
||||
// Setup: Write lines of text to the buffer
|
||||
const std::vector<std::wstring> text = { L"word other",
|
||||
L" more words" };
|
||||
WriteLinesToBuffer(text, *_buffer);
|
||||
|
||||
// Test Data:
|
||||
// - COORD - starting position
|
||||
// - COORD - expected result (moving forwards)
|
||||
// - COORD - expected result (moving backwards)
|
||||
struct ExpectedResult
|
||||
{
|
||||
COORD moveForwards;
|
||||
COORD moveBackwards;
|
||||
};
|
||||
|
||||
struct Test
|
||||
{
|
||||
COORD startPos;
|
||||
ExpectedResult expected;
|
||||
};
|
||||
|
||||
// Set testData for GetWordStart tests
|
||||
// clang-format off
|
||||
std::vector<Test> testData = {
|
||||
// tests for first line of text
|
||||
{ { 0, 0 }, {{ 5, 0 }, { 0, 0 }} },
|
||||
{ { 1, 0 }, {{ 5, 0 }, { 1, 0 }} },
|
||||
{ { 3, 0 }, {{ 5, 0 }, { 3, 0 }} },
|
||||
{ { 4, 0 }, {{ 5, 0 }, { 4, 0 }} },
|
||||
{ { 5, 0 }, {{ 2, 1 }, { 0, 0 }} },
|
||||
{ { 6, 0 }, {{ 2, 1 }, { 0, 0 }} },
|
||||
{ { 20, 0 }, {{ 2, 1 }, { 0, 0 }} },
|
||||
{ { 79, 0 }, {{ 2, 1 }, { 0, 0 }} },
|
||||
|
||||
// tests for second line of text
|
||||
{ { 0, 1 }, {{ 2, 1 }, { 0, 0 }} },
|
||||
{ { 1, 1 }, {{ 2, 1 }, { 0, 0 }} },
|
||||
{ { 2, 1 }, {{ 9, 1 }, { 5, 0 }} },
|
||||
{ { 3, 1 }, {{ 9, 1 }, { 5, 0 }} },
|
||||
{ { 5, 1 }, {{ 9, 1 }, { 5, 0 }} },
|
||||
{ { 6, 1 }, {{ 9, 1 }, { 5, 0 }} },
|
||||
{ { 7, 1 }, {{ 9, 1 }, { 5, 0 }} },
|
||||
{ { 9, 1 }, {{ 9, 1 }, { 2, 1 }} },
|
||||
{ { 10, 1 }, {{10, 1 }, { 2, 1 }} },
|
||||
{ { 20, 1 }, {{20, 1 }, { 2, 1 }} },
|
||||
{ { 79, 1 }, {{79, 1 }, { 2, 1 }} },
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
BEGIN_TEST_METHOD_PROPERTIES()
|
||||
TEST_METHOD_PROPERTY(L"Data:movingForwards", L"{false, true}")
|
||||
END_TEST_METHOD_PROPERTIES();
|
||||
|
||||
bool movingForwards;
|
||||
VERIFY_SUCCEEDED(TestData::TryGetValue(L"movingForwards", movingForwards), L"Get movingForwards variant");
|
||||
|
||||
const std::wstring_view delimiters = L" ";
|
||||
const COORD lastCharPos = _buffer->GetLastNonSpaceCharacter();
|
||||
for (const auto& test : testData)
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(L"COORD (%hd, %hd)", test.startPos.X, test.startPos.Y));
|
||||
auto pos{ test.startPos };
|
||||
const auto result = movingForwards ?
|
||||
_buffer->MoveToNextWord(pos, delimiters, lastCharPos) :
|
||||
_buffer->MoveToPreviousWord(pos, delimiters);
|
||||
const auto expected = movingForwards ? test.expected.moveForwards : test.expected.moveBackwards;
|
||||
VERIFY_ARE_EQUAL(expected, pos);
|
||||
|
||||
// if we moved, result is true and pos != startPos.
|
||||
// otherwise, result is false and pos == startPos.
|
||||
VERIFY_ARE_EQUAL(result, pos != test.startPos);
|
||||
}
|
||||
}
|
||||
|
||||
void TextBufferTests::GetGlyphBoundaries()
|
||||
{
|
||||
struct ExpectedResult
|
||||
@@ -2464,7 +2546,7 @@ void TextBufferTests::HyperlinkTrim()
|
||||
|
||||
// Set a hyperlink id in the first row and add a hyperlink to our map
|
||||
const COORD pos{ 70, 0 };
|
||||
const auto id = _buffer->GetHyperlinkId(customId);
|
||||
const auto id = _buffer->GetHyperlinkId(url, customId);
|
||||
TextAttribute newAttr{ 0x7f };
|
||||
newAttr.SetHyperlinkId(id);
|
||||
_buffer->GetRowByOffset(pos.Y).GetAttrRow().SetAttrToEnd(pos.X, newAttr);
|
||||
@@ -2472,7 +2554,7 @@ void TextBufferTests::HyperlinkTrim()
|
||||
|
||||
// Set a different hyperlink id somewhere else in the buffer
|
||||
const COORD otherPos{ 70, 5 };
|
||||
const auto otherId = _buffer->GetHyperlinkId(otherCustomId);
|
||||
const auto otherId = _buffer->GetHyperlinkId(otherUrl, otherCustomId);
|
||||
newAttr.SetHyperlinkId(otherId);
|
||||
_buffer->GetRowByOffset(otherPos.Y).GetAttrRow().SetAttrToEnd(otherPos.X, newAttr);
|
||||
_buffer->AddHyperlinkToMap(otherUrl, otherId);
|
||||
@@ -2480,14 +2562,17 @@ void TextBufferTests::HyperlinkTrim()
|
||||
// Increment the circular buffer
|
||||
_buffer->IncrementCircularBuffer();
|
||||
|
||||
const auto finalCustomId = fmt::format(L"{}%{}", customId, std::hash<std::wstring_view>{}(url));
|
||||
const auto finalOtherCustomId = fmt::format(L"{}%{}", otherCustomId, std::hash<std::wstring_view>{}(otherUrl));
|
||||
|
||||
// The hyperlink reference that was only in the first row should be deleted from the map
|
||||
VERIFY_ARE_EQUAL(_buffer->_hyperlinkMap.find(id), _buffer->_hyperlinkMap.end());
|
||||
// Since there was a custom id, that should be deleted as well
|
||||
VERIFY_ARE_EQUAL(_buffer->_hyperlinkCustomIdMap.find(customId), _buffer->_hyperlinkCustomIdMap.end());
|
||||
VERIFY_ARE_EQUAL(_buffer->_hyperlinkCustomIdMap.find(finalCustomId), _buffer->_hyperlinkCustomIdMap.end());
|
||||
|
||||
// The other hyperlink reference should not be deleted
|
||||
VERIFY_ARE_EQUAL(_buffer->_hyperlinkMap[otherId], otherUrl);
|
||||
VERIFY_ARE_EQUAL(_buffer->_hyperlinkCustomIdMap[otherCustomId], otherId);
|
||||
VERIFY_ARE_EQUAL(_buffer->_hyperlinkCustomIdMap[finalOtherCustomId], otherId);
|
||||
}
|
||||
|
||||
// This tests that when we increment the circular buffer, non-obsolete hyperlink references
|
||||
@@ -2505,7 +2590,7 @@ void TextBufferTests::NoHyperlinkTrim()
|
||||
|
||||
// Set a hyperlink id in the first row and add a hyperlink to our map
|
||||
const COORD pos{ 70, 0 };
|
||||
const auto id = _buffer->GetHyperlinkId(customId);
|
||||
const auto id = _buffer->GetHyperlinkId(url, customId);
|
||||
TextAttribute newAttr{ 0x7f };
|
||||
newAttr.SetHyperlinkId(id);
|
||||
_buffer->GetRowByOffset(pos.Y).GetAttrRow().SetAttrToEnd(pos.X, newAttr);
|
||||
@@ -2518,7 +2603,9 @@ void TextBufferTests::NoHyperlinkTrim()
|
||||
// Increment the circular buffer
|
||||
_buffer->IncrementCircularBuffer();
|
||||
|
||||
const auto finalCustomId = fmt::format(L"{}%{}", customId, std::hash<std::wstring_view>{}(url));
|
||||
|
||||
// The hyperlink reference should not be deleted from the map since it is still present in the buffer
|
||||
VERIFY_ARE_EQUAL(_buffer->GetHyperlinkUriFromId(id), url);
|
||||
VERIFY_ARE_EQUAL(_buffer->_hyperlinkCustomIdMap[customId], id);
|
||||
VERIFY_ARE_EQUAL(_buffer->_hyperlinkCustomIdMap[finalCustomId], id);
|
||||
}
|
||||
|
||||
@@ -61,11 +61,9 @@
|
||||
// GSL
|
||||
// Block GSL Multi Span include because it both has C++17 deprecated iterators
|
||||
// and uses the C-namespaced "max" which conflicts with Windows definitions.
|
||||
#ifndef BLOCK_GSL
|
||||
#define GSL_MULTI_SPAN_H
|
||||
#include <gsl/gsl>
|
||||
#include <gsl/span_ext>
|
||||
#endif
|
||||
|
||||
// CppCoreCheck
|
||||
#include <CppCoreCheck/Warnings.h>
|
||||
|
||||
61
src/inc/WilErrorReporting.h
Normal file
61
src/inc/WilErrorReporting.h
Normal file
@@ -0,0 +1,61 @@
|
||||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- WilErrorReporting.h
|
||||
--*/
|
||||
#pragma once
|
||||
|
||||
#include <winmeta.h>
|
||||
#include <wil/common.h>
|
||||
|
||||
#define CONSOLE_WIL_TRACELOGGING_COMMON_FAILURE_PARAMS(failure) \
|
||||
TraceLoggingUInt32((failure).hr, "hresult", "Failure error code"), \
|
||||
TraceLoggingString((failure).pszFile, "fileName", "Source code file name where the error occurred"), \
|
||||
TraceLoggingUInt32((failure).uLineNumber, "lineNumber", "Line number within the source code file where the error occurred"), \
|
||||
TraceLoggingString((failure).pszModule, "module", "Name of the binary where the error occurred"), \
|
||||
TraceLoggingUInt32(static_cast<DWORD>((failure).type), "failureType", "Indicates what type of failure was observed (exception, returned error, logged error or fail fast"), \
|
||||
TraceLoggingWideString((failure).pszMessage, "message", "Custom message associated with the failure (if any)"), \
|
||||
TraceLoggingUInt32((failure).threadId, "threadId", "Identifier of the thread the error occurred on"), \
|
||||
TraceLoggingString((failure).pszCallContext, "callContext", "List of containing this error"), \
|
||||
TraceLoggingUInt32((failure).callContextOriginating.contextId, "originatingContextId", "Identifier for the oldest activity containing this error"), \
|
||||
TraceLoggingString((failure).callContextOriginating.contextName, "originatingContextName", "Name of the oldest activity containing this error"), \
|
||||
TraceLoggingWideString((failure).callContextOriginating.contextMessage, "originatingContextMessage", "Custom message associated with the oldest activity containing this error (if any)"), \
|
||||
TraceLoggingUInt32((failure).callContextCurrent.contextId, "currentContextId", "Identifier for the newest activity containing this error"), \
|
||||
TraceLoggingString((failure).callContextCurrent.contextName, "currentContextName", "Name of the newest activity containing this error"), \
|
||||
TraceLoggingWideString((failure).callContextCurrent.contextMessage, "currentContextMessage", "Custom message associated with the newest activity containing this error (if any)")
|
||||
|
||||
#define CONSOLE_WIL_TRACELOGGING_FAILURE_PARAMS(failure) \
|
||||
TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), \
|
||||
TraceLoggingStruct(14, "wilResult"), \
|
||||
CONSOLE_WIL_TRACELOGGING_COMMON_FAILURE_PARAMS(failure)
|
||||
|
||||
namespace Microsoft::Console::ErrorReporting
|
||||
{
|
||||
__declspec(selectany) TraceLoggingHProvider FallbackProvider;
|
||||
__declspec(noinline) inline void WINAPI ReportFailureToFallbackProvider(bool alreadyReported, const wil::FailureInfo& failure) noexcept
|
||||
try
|
||||
{
|
||||
if (!alreadyReported && FallbackProvider)
|
||||
{
|
||||
#pragma warning(suppress : 26477 26485 26494 26482 26446) // We don't control TraceLoggingWrite
|
||||
TraceLoggingWrite(FallbackProvider, "FallbackError", TraceLoggingKeyword(MICROSOFT_KEYWORD_TELEMETRY), TraceLoggingLevel(WINEVENT_LEVEL_ERROR), CONSOLE_WIL_TRACELOGGING_FAILURE_PARAMS(failure));
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// Don't log anything. We just failed to trace, where will we go now?
|
||||
}
|
||||
|
||||
__declspec(noinline) inline void EnableFallbackFailureReporting(TraceLoggingHProvider provider) noexcept
|
||||
try
|
||||
{
|
||||
FallbackProvider = provider;
|
||||
::wil::SetResultTelemetryFallback(::Microsoft::Console::ErrorReporting::ReportFailureToFallbackProvider);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// Don't log anything. We just failed to set up WIL -- how are we going to log anything?
|
||||
}
|
||||
}
|
||||
@@ -405,7 +405,7 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
||||
const auto bitShift = delta_y * _sz.width();
|
||||
|
||||
#pragma warning(push)
|
||||
// we can't depend on GSL here (some libraries use BLOCK_GSL), so we use static_cast for explicit narrowing
|
||||
// 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)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user