Merged PR 5676764: Migrate OSS up to 16d00a68f
Dustin L. Howett (3) * Move CharToKeyEvents (and friends) into InteractivityBase (GH-9106) * Update Cascadia Code to 2102.03 (GH-9088) * verison: bump to 1.7 on main Josh Soref (1) * ci: update to Spell check to 0.0.17a (CC-9014) Leonard Hecker (3) * Fixed GH-5205: Ctrl+Alt+2 doesn't send ^[^@ (CC-5272) * Fix issues in tests.xml and OpenConsole.psm1 (CC-9011) * Fix GH-8458: Handle all Ctrl-key combinations (CC-8870) Mike Griese (1) * Add support for running a commandline in another WT window (GH-8898) Michael Niksa (1) * Teach the renderer to keep thread alive if engine requests it (GH-9091) Lachlan Picking (1) * Fix shader time input (CC-8994) PankajBhojwani (1) * Separate runtime TerminalSettings from profile-TerminalSettings (CC-8602) Chester Liu (2) * Add support for paste filtering and bracketed paste mode (CC-9034) * Add support for chaining OSC 10-12 (CC-8999) Related work items: MSFT-31692939
|
@ -1 +0,0 @@
|
|||
renamer
|
|
@ -1,7 +0,0 @@
|
|||
autogenerated
|
||||
CPPCORECHECK
|
||||
Debian
|
||||
filepath
|
||||
inplace
|
||||
KEYBDINPUT
|
||||
WINVER
|
|
@ -1,14 +0,0 @@
|
|||
checkboxes
|
||||
CSIDL
|
||||
csv
|
||||
horiz
|
||||
IDispatch
|
||||
inlines
|
||||
IWeb
|
||||
Progman
|
||||
reserialize
|
||||
SHANDLE
|
||||
SHGFP
|
||||
udk
|
||||
unfocus
|
||||
WClass
|
|
@ -9,17 +9,19 @@ By default the command suggestion will generate a file named based on your commi
|
|||
|
||||
If the listed items are:
|
||||
* ... **misspelled**, then please *correct* them instead of using the command.
|
||||
* ... *names*, please add them to `.github/actions/spell-check/dictionary/names.txt`.
|
||||
* ... APIs, you can add them to a file in `.github/actions/spell-check/dictionary/`.
|
||||
* ... just things you're using, please add them to an appropriate file in `.github/actions/spell-check/expect/`.
|
||||
* ... tokens you only need in one place and shouldn't *generally be used*, you can add an item in an appropriate file in `.github/actions/spell-check/patterns/`.
|
||||
* ... *names*, please add them to `.github/actions/spelling/dictionary/names.txt`.
|
||||
* ... APIs, you can add them to a file in `.github/actions/spelling/dictionary/`.
|
||||
* ... just things you're using, please add them to an appropriate file in `.github/actions/spelling/expect/`.
|
||||
* ... tokens you only need in one place and shouldn't *generally be used*, you can add an item in an appropriate file in `.github/actions/spelling/patterns/`.
|
||||
|
||||
See the `README.md` in each directory for more information.
|
||||
|
||||
:microscope: You can test your commits **without** *appending* to a PR by creating a new branch with that extra change and pushing it to your fork. The [:check-spelling](https://github.com/marketplace/actions/check-spelling) action will run in response to your **push** -- it doesn't require an open pull request. By using such a branch, you can limit the number of typos your peers see you make. :wink:
|
||||
:microscope: You can test your commits **without** *appending* to a PR by creating a new branch with that extra change and pushing it to your fork. The [check-spelling](https://github.com/marketplace/actions/check-spelling) action will run in response to your **push** -- it doesn't require an open pull request. By using such a branch, you can limit the number of typos your peers see you make. :wink:
|
||||
|
||||
:clamp: If you see a bunch of garbage and it relates to a binary-ish string, please add a file path to the `.github/actions/spelling/excludes.txt` file instead of just accepting the garbage.
|
||||
|
||||
File paths are Perl 5 Regular Expressions - you can [test](
|
||||
https://www.regexplanet.com/advanced/perl/) yours before committing to verify it will match your files.
|
||||
|
||||
`^` refers to the file's path from the root of the repository, so `^README\.md$` would exclude [README.md](https://github.com/microsoft/terminal/blob/main/README.md) (on whichever branch you're using).
|
||||
</details>
|
||||
|
||||
#### :warning: Reviewers
|
||||
At present, the action that triggered this message will not show its :x: in this PR unless the branch is within this repository.
|
||||
Thus, you **should** make sure that this comment has been addressed before encouraging the merge bot to merge this PR.
|
|
@ -22,10 +22,12 @@ Hashtable
|
|||
HIGHCONTRASTON
|
||||
HIGHCONTRASTW
|
||||
href
|
||||
IAppearance
|
||||
IAsync
|
||||
IBind
|
||||
IBox
|
||||
IClass
|
||||
IConnection
|
||||
IComparable
|
||||
ICustom
|
||||
IDialog
|
||||
|
@ -44,6 +46,7 @@ llu
|
|||
localtime
|
||||
lround
|
||||
LSHIFT
|
||||
MULTIPLEUSE
|
||||
msappx
|
||||
MULTIPLEUSE
|
||||
NCHITTEST
|
||||
|
@ -59,8 +62,8 @@ otms
|
|||
OUTLINETEXTMETRICW
|
||||
overridable
|
||||
PAGESCROLL
|
||||
REGCLS
|
||||
pmr
|
||||
REGCLS
|
||||
RETURNCMD
|
||||
REGCLS
|
||||
rfind
|
|
@ -116574,6 +116574,8 @@ Dowmetal
|
|||
Down
|
||||
down
|
||||
Downall
|
||||
downside
|
||||
downsides
|
||||
down-and-out
|
||||
down-and-outer
|
||||
down-at-heel
|
||||
|
@ -236348,6 +236350,7 @@ Maxama
|
|||
Maxantia
|
||||
Maxatawny
|
||||
Maxbass
|
||||
maxed
|
||||
Maxentia
|
||||
Maxey
|
||||
Maxfield
|
||||
|
@ -327070,6 +327073,7 @@ ptychopterygial
|
|||
ptychopterygium
|
||||
Ptychosperma
|
||||
p-type
|
||||
ptys
|
||||
ptysmagogue
|
||||
ptyxis
|
||||
PU
|
||||
|
@ -354226,6 +354230,7 @@ run-through
|
|||
runtier
|
||||
runtiest
|
||||
runtime
|
||||
runtimes
|
||||
runtiness
|
||||
runtish
|
||||
runtishly
|
||||
|
@ -420369,6 +420374,7 @@ tokening
|
|||
tokenism
|
||||
tokenisms
|
||||
tokenize
|
||||
tokenizes
|
||||
tokenless
|
||||
token-money
|
||||
tokens
|
||||
|
@ -432744,6 +432750,7 @@ uintjie
|
|||
UIP
|
||||
Uird
|
||||
Uirina
|
||||
UIs
|
||||
Uis
|
||||
UIT
|
||||
uit
|
||||
|
@ -459682,6 +459689,7 @@ versines
|
|||
versing
|
||||
version
|
||||
versional
|
||||
versioned
|
||||
versioner
|
||||
versionist
|
||||
versionize
|
|
@ -1,5 +1,6 @@
|
|||
Consolas
|
||||
emoji
|
||||
emojis
|
||||
Extralight
|
||||
Gabriola
|
||||
Iosevka
|
|
@ -1,11 +1,19 @@
|
|||
ACLs
|
||||
ADMINS
|
||||
altform
|
||||
altforms
|
||||
appendwttlogging
|
||||
backplating
|
||||
bitmaps
|
||||
BOMs
|
||||
CPLs
|
||||
CPRs
|
||||
DACL
|
||||
DACLs
|
||||
diffs
|
||||
disposables
|
||||
dotnetfeed
|
||||
DTDs
|
||||
DWINRT
|
||||
enablewttlogging
|
||||
LKG
|
||||
|
@ -31,6 +39,7 @@ systemroot
|
|||
taskkill
|
||||
tasklist
|
||||
tdbuildteamid
|
||||
VCRT
|
||||
vcruntime
|
||||
visualstudio
|
||||
VSTHRD
|
||||
|
@ -39,3 +48,4 @@ wslpath
|
|||
wtl
|
||||
wtt
|
||||
wttlog
|
||||
Xamarin
|
|
@ -50,6 +50,7 @@ oising
|
|||
oldnewthing
|
||||
opengl
|
||||
osgwiki
|
||||
pabhojwa
|
||||
paulcam
|
||||
pauldotknopf
|
||||
PGP
|
||||
|
@ -62,6 +63,7 @@ Somuah
|
|||
sonph
|
||||
sonpham
|
||||
stakx
|
||||
thereses
|
||||
Walisch
|
||||
Wirt
|
||||
Wojciech
|
|
@ -35,7 +35,7 @@ SUMS$
|
|||
\.pbxproj$
|
||||
\.pdf$
|
||||
\.pem$
|
||||
\.png$
|
||||
(?:(?i)\.png$)
|
||||
\.psd$
|
||||
\.runsettings$
|
||||
\.sig$
|
||||
|
@ -59,7 +59,8 @@ SUMS$
|
|||
^src/interactivity/onecore/BgfxEngine\.
|
||||
^src/renderer/wddmcon/WddmConRenderer\.
|
||||
^src/terminal/parser/ft_fuzzer/VTCommandFuzzer\.cpp$
|
||||
^src/types/ut_types/UtilsTests.cpp$
|
||||
^src/tools/U8U16Test/(?:fr|ru|zh)\.txt$
|
||||
^\.github/actions/spell-check/
|
||||
^\.github/actions/spelling/
|
||||
^\.gitignore$
|
||||
^doc/reference/master-sequence-list.csv$
|
|
@ -1,4 +1,3 @@
|
|||
AAAAA
|
||||
AAAAAABBBBBBCCC
|
||||
AAAAABBBBBBBCCC
|
||||
AAAAABBBBBBCCC
|
||||
|
@ -110,6 +109,7 @@ aumid
|
|||
Authenticode
|
||||
AUTOBUDDY
|
||||
AUTOCHECKBOX
|
||||
autogenerated
|
||||
autohide
|
||||
AUTOHSCROLL
|
||||
automagically
|
||||
|
@ -251,6 +251,7 @@ charset
|
|||
CHARSETINFO
|
||||
chcp
|
||||
checkbox
|
||||
checkboxes
|
||||
chh
|
||||
Childitem
|
||||
chk
|
||||
|
@ -353,7 +354,6 @@ conprops
|
|||
conpropsp
|
||||
conpty
|
||||
conptylib
|
||||
consecteturadipiscingelit
|
||||
conserv
|
||||
consoleapi
|
||||
CONSOLECONTROL
|
||||
|
@ -393,6 +393,7 @@ CPINFOEX
|
|||
cplinfo
|
||||
cplusplus
|
||||
cpp
|
||||
CPPCORECHECK
|
||||
cppcorecheckrules
|
||||
cpprest
|
||||
cpprestsdk
|
||||
|
@ -415,6 +416,7 @@ csbi
|
|||
csbiex
|
||||
csharp
|
||||
CSHORT
|
||||
CSIDL
|
||||
csproj
|
||||
Csr
|
||||
csrmsg
|
||||
|
@ -427,6 +429,7 @@ cstdlib
|
|||
cstr
|
||||
cstring
|
||||
cstyle
|
||||
csv
|
||||
CSwitch
|
||||
CText
|
||||
ctime
|
||||
|
@ -502,11 +505,13 @@ DCOLORVALUE
|
|||
dcommon
|
||||
DCompile
|
||||
dcompiler
|
||||
DComposition
|
||||
dde
|
||||
DDESHARE
|
||||
DDevice
|
||||
DEADCHAR
|
||||
dealloc
|
||||
Debian
|
||||
debolden
|
||||
debounce
|
||||
DECALN
|
||||
|
@ -621,7 +626,6 @@ DLLVERSIONINFO
|
|||
DLOAD
|
||||
DLOOK
|
||||
dmp
|
||||
dnceng
|
||||
DOCTYPE
|
||||
docx
|
||||
DONTCARE
|
||||
|
@ -674,6 +678,7 @@ ECH
|
|||
echokey
|
||||
ecount
|
||||
ECpp
|
||||
Edgium
|
||||
EDITKEYS
|
||||
EDITTEXT
|
||||
EDITUPDATE
|
||||
|
@ -684,7 +689,7 @@ EHsc
|
|||
EJO
|
||||
EK
|
||||
ELEMENTNOTAVAILABLE
|
||||
Elems
|
||||
elems
|
||||
elif
|
||||
elseif
|
||||
emacs
|
||||
|
@ -765,6 +770,7 @@ FGHIJ
|
|||
fgidx
|
||||
FILEDESCRIPTION
|
||||
fileno
|
||||
filepath
|
||||
FILESUBTYPE
|
||||
FILESYSPATH
|
||||
filesystem
|
||||
|
@ -987,6 +993,7 @@ hmod
|
|||
hmodule
|
||||
hmon
|
||||
HMONITOR
|
||||
horiz
|
||||
HORZ
|
||||
hostable
|
||||
hostlib
|
||||
|
@ -1047,6 +1054,7 @@ IDesktop
|
|||
IDevice
|
||||
IDictionary
|
||||
IDISHWND
|
||||
IDispatch
|
||||
IDisposable
|
||||
idl
|
||||
idllib
|
||||
|
@ -1099,8 +1107,10 @@ INITMENU
|
|||
inkscape
|
||||
inl
|
||||
INLINEPREFIX
|
||||
inlines
|
||||
INotify
|
||||
inout
|
||||
inplace
|
||||
inproc
|
||||
Inputkeyinfo
|
||||
INPUTPROCESSORPROFILE
|
||||
|
@ -1163,6 +1173,7 @@ IValue
|
|||
IVector
|
||||
IWait
|
||||
iwch
|
||||
IWeb
|
||||
IWin
|
||||
IWindow
|
||||
IXaml
|
||||
|
@ -1191,6 +1202,7 @@ kcuu
|
|||
Kd
|
||||
kernelbase
|
||||
kernelbasestaging
|
||||
KEYBDINPUT
|
||||
keybinding
|
||||
keychord
|
||||
keydown
|
||||
|
@ -1271,7 +1283,6 @@ Loewen
|
|||
LOGFONT
|
||||
LOGFONTW
|
||||
logissue
|
||||
Loremipsumdolorsitamet
|
||||
lowercased
|
||||
loword
|
||||
lparam
|
||||
|
@ -1399,6 +1410,7 @@ mingw
|
|||
minimizeall
|
||||
minkernel
|
||||
MINMAXINFO
|
||||
mintty
|
||||
minwin
|
||||
minwindef
|
||||
Mip
|
||||
|
@ -1437,6 +1449,7 @@ MSGF
|
|||
MSGFILTER
|
||||
MSGFLG
|
||||
MSGMARKMODE
|
||||
MSGS
|
||||
MSGSCROLLMODE
|
||||
MSGSELECTMODE
|
||||
msiexec
|
||||
|
@ -1451,6 +1464,7 @@ mui
|
|||
Mul
|
||||
multiline
|
||||
munged
|
||||
munges
|
||||
mutex
|
||||
mutexes
|
||||
muxes
|
||||
|
@ -1465,7 +1479,6 @@ nameof
|
|||
namespace
|
||||
namespaced
|
||||
namestream
|
||||
Namquiseratal
|
||||
nano
|
||||
natvis
|
||||
nbsp
|
||||
|
@ -1583,15 +1596,16 @@ NTVDM
|
|||
ntverp
|
||||
NTWIN
|
||||
nuget
|
||||
Nullametrutrummetus
|
||||
nullness
|
||||
nullonfailure
|
||||
nullopt
|
||||
nullptr
|
||||
NULs
|
||||
numlock
|
||||
numpad
|
||||
NUMSCROLL
|
||||
nupkg
|
||||
NVDA
|
||||
NVIDIA
|
||||
NVR
|
||||
Nx
|
||||
|
@ -1821,6 +1835,8 @@ prioritization
|
|||
processenv
|
||||
processhost
|
||||
PROCESSINFOCLASS
|
||||
procs
|
||||
Progman
|
||||
proj
|
||||
PROPERTYID
|
||||
PROPERTYKEY
|
||||
|
@ -1885,6 +1901,7 @@ pythonw
|
|||
qi
|
||||
QJ
|
||||
qo
|
||||
QOL
|
||||
QRSTU
|
||||
qsort
|
||||
queryable
|
||||
|
@ -1955,7 +1972,8 @@ REGSTR
|
|||
reingest
|
||||
Relayout
|
||||
RELBINPATH
|
||||
Remoting
|
||||
remoting
|
||||
renamer
|
||||
renderengine
|
||||
rendersize
|
||||
reparent
|
||||
|
@ -1965,6 +1983,7 @@ Replymessage
|
|||
repositorypath
|
||||
rescap
|
||||
Resequence
|
||||
reserialize
|
||||
RESETCONTENT
|
||||
resheader
|
||||
resizable
|
||||
|
@ -1981,7 +2000,6 @@ rgba
|
|||
rgbi
|
||||
rgch
|
||||
rgci
|
||||
rgdx
|
||||
rgfae
|
||||
rgfte
|
||||
rgi
|
||||
|
@ -2031,6 +2049,7 @@ runuia
|
|||
runut
|
||||
rvalue
|
||||
RVERTICAL
|
||||
rxvt
|
||||
RWIN
|
||||
safearray
|
||||
SAFECAST
|
||||
|
@ -2124,6 +2143,7 @@ sfi
|
|||
SFINAE
|
||||
SFUI
|
||||
sgr
|
||||
SHANDLE
|
||||
SHCo
|
||||
shcore
|
||||
shellapi
|
||||
|
@ -2131,6 +2151,7 @@ shellex
|
|||
shellscalingapi
|
||||
SHFILEINFO
|
||||
SHGFI
|
||||
SHGFP
|
||||
SHIFTJIS
|
||||
Shl
|
||||
shlguid
|
||||
|
@ -2336,7 +2357,7 @@ texel
|
|||
TExpected
|
||||
textattribute
|
||||
TEXTATTRIBUTEID
|
||||
Textbox
|
||||
textbox
|
||||
textboxes
|
||||
textbuffer
|
||||
TEXTINCLUDE
|
||||
|
@ -2431,6 +2452,7 @@ typename
|
|||
typeof
|
||||
typeparam
|
||||
TYUI
|
||||
UAC
|
||||
uap
|
||||
uapadmin
|
||||
UAX
|
||||
|
@ -2441,6 +2463,7 @@ ucdxml
|
|||
uch
|
||||
UCHAR
|
||||
ucs
|
||||
udk
|
||||
UDM
|
||||
uer
|
||||
uget
|
||||
|
@ -2463,6 +2486,7 @@ UNCPRIORITY
|
|||
undef
|
||||
Unescape
|
||||
unexpand
|
||||
Unfocus
|
||||
unhighlighting
|
||||
unhosted
|
||||
unicode
|
||||
|
@ -2532,7 +2556,7 @@ UVWXY
|
|||
UWA
|
||||
uwp
|
||||
uxtheme
|
||||
Vals
|
||||
vals
|
||||
Vanara
|
||||
vararg
|
||||
vbproj
|
||||
|
@ -2610,6 +2634,7 @@ wch
|
|||
wchar
|
||||
WCIA
|
||||
WCIW
|
||||
WClass
|
||||
wcout
|
||||
wcschr
|
||||
wcscmp
|
||||
|
@ -2704,6 +2729,7 @@ wintelnet
|
|||
winternl
|
||||
winuser
|
||||
winuserp
|
||||
WINVER
|
||||
wistd
|
||||
wixproj
|
||||
wline
|
||||
|
@ -2763,6 +2789,7 @@ WTEXT
|
|||
WTo
|
||||
wtof
|
||||
wtoi
|
||||
WTs
|
||||
wtw
|
||||
wtypes
|
||||
Wubi
|
||||
|
@ -2837,7 +2864,6 @@ Yw
|
|||
YWalk
|
||||
yx
|
||||
YZ
|
||||
yzx
|
||||
Zc
|
||||
ZCmd
|
||||
ZCtrl
|
||||
|
@ -2848,4 +2874,3 @@ zsh
|
|||
zu
|
||||
zxcvbnm
|
||||
zy
|
||||
|
|
@ -13,3 +13,6 @@ fixterms
|
|||
uk
|
||||
winui
|
||||
appshellintegration
|
||||
cppreference
|
||||
gfycat
|
||||
what3words
|
|
@ -4,7 +4,7 @@ https://www\.itscj\.ipsj\.or\.jp/iso-ir/[-0-9]+\.pdf
|
|||
https://www\.vt100\.net/docs/[-a-zA-Z0-9#_\/.]*
|
||||
https://www.w3.org/[-a-zA-Z0-9?&=\/_#]*
|
||||
https://(?:(?:www\.|)youtube\.com|youtu.be)/[-a-zA-Z0-9?&=]*
|
||||
https://[a-z-]+\.githubusercontent\.com/[-a-zA-Z0-9?&=_\/.]*
|
||||
https://(?:[a-z-]+\.|)github(?:usercontent|)\.com/[-a-zA-Z0-9?%&=_\/.]*
|
||||
https://www.xfree86.org/[-a-zA-Z0-9?&=\/_#]*
|
||||
[Pp]ublicKeyToken="?[0-9a-fA-F]{16}"?
|
||||
(?:[{"]|UniqueIdentifier>)[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}(?:[}"]|</UniqueIdentifier)
|
||||
|
@ -16,7 +16,7 @@ Scro\&ll
|
|||
:\\windows\\syste\b
|
||||
TestUtils::VerifyExpectedString\(tb, L"[^"]+"
|
||||
(?:hostSm|mach)\.ProcessString\(L"[^"]+"
|
||||
\b([A-Za-z])\1{3,}\b
|
||||
\b([A-Za-z])\g{-1}{3,}\b
|
||||
0x[0-9A-Za-z]+
|
||||
Base64::s_(?:En|De)code\(L"[^"]+"
|
||||
VERIFY_ARE_EQUAL\(L"[^"]+"
|
11
.github/workflows/spelling.yml
vendored
|
@ -1,9 +1,7 @@
|
|||
name: Spell checking
|
||||
on:
|
||||
pull_request_target:
|
||||
push:
|
||||
schedule:
|
||||
# * is a special character in YAML so you have to quote this string
|
||||
- cron: '15 * * * *'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
@ -12,9 +10,6 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@v2.0.0
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
fetch-depth: 5
|
||||
- uses: check-spelling/check-spelling@0.0.16-alpha
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
bucket: .github/actions
|
||||
project: spell-check
|
||||
- uses: check-spelling/check-spelling@0.0.17-alpha
|
||||
|
|
|
@ -362,6 +362,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UnitTests_Remoting", "src\c
|
|||
{27B5AAEB-A548-44CF-9777-F8BAA32AF7AE} = {27B5AAEB-A548-44CF-9777-F8BAA32AF7AE}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "wpf", "wpf", "{4DAF0299-495E-4CD1-A982-9BAC16A45932}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
AuditMode|Any CPU = AuditMode|Any CPU
|
||||
|
@ -2475,7 +2477,7 @@ Global
|
|||
{43CE4CE5-0010-4B99-9569-672670D26E26}.AuditMode|DotNet_x64Test.ActiveCfg = AuditMode|Win32
|
||||
{43CE4CE5-0010-4B99-9569-672670D26E26}.AuditMode|DotNet_x86Test.ActiveCfg = AuditMode|Win32
|
||||
{43CE4CE5-0010-4B99-9569-672670D26E26}.AuditMode|x64.ActiveCfg = Release|x64
|
||||
{43CE4CE5-0010-4B99-9569-672670D26E26}.AuditMode|x64.Build.0 = AuditMode|x64
|
||||
{43CE4CE5-0010-4B99-9569-672670D26E26}.AuditMode|x64.Build.0 = Release|x64
|
||||
{43CE4CE5-0010-4B99-9569-672670D26E26}.AuditMode|x86.ActiveCfg = AuditMode|Win32
|
||||
{43CE4CE5-0010-4B99-9569-672670D26E26}.AuditMode|x86.Build.0 = AuditMode|Win32
|
||||
{43CE4CE5-0010-4B99-9569-672670D26E26}.Debug|Any CPU.ActiveCfg = Debug|Win32
|
||||
|
@ -2613,8 +2615,8 @@ Global
|
|||
{05500DEF-2294-41E3-AF9A-24E580B82836} = {89CDCC5C-9F53-4054-97A4-639D99F169CD}
|
||||
{1E4A062E-293B-4817-B20D-BF16B979E350} = {89CDCC5C-9F53-4054-97A4-639D99F169CD}
|
||||
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73} = {89CDCC5C-9F53-4054-97A4-639D99F169CD}
|
||||
{84848BFA-931D-42CE-9ADF-01EE54DE7890} = {59840756-302F-44DF-AA47-441A9D673202}
|
||||
{376FE273-6B84-4EB5-8B30-8DE9D21B022C} = {59840756-302F-44DF-AA47-441A9D673202}
|
||||
{84848BFA-931D-42CE-9ADF-01EE54DE7890} = {4DAF0299-495E-4CD1-A982-9BAC16A45932}
|
||||
{376FE273-6B84-4EB5-8B30-8DE9D21B022C} = {4DAF0299-495E-4CD1-A982-9BAC16A45932}
|
||||
{CA5CAD1A-9333-4D05-B12A-1905CBF112F9} = {BDB237B6-1D1D-400F-84CC-40A58FA59C8E}
|
||||
{CA5CAD1A-9A12-429C-B551-8562EC954746} = {59840756-302F-44DF-AA47-441A9D673202}
|
||||
{CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506} = {BDB237B6-1D1D-400F-84CC-40A58FA59C8E}
|
||||
|
@ -2634,7 +2636,7 @@ Global
|
|||
{024052DE-83FB-4653-AEA4-90790D29D5BD} = {E8F24881-5E37-4362-B191-A3BA0ED7F4EB}
|
||||
{067F0A06-FCB7-472C-96E9-B03B54E8E18D} = {59840756-302F-44DF-AA47-441A9D673202}
|
||||
{6BAE5851-50D5-4934-8D5E-30361A8A40F3} = {81C352DB-1818-45B7-A284-18E259F1CC87}
|
||||
{1588FD7C-241E-4E7D-9113-43735F3E6BAD} = {59840756-302F-44DF-AA47-441A9D673202}
|
||||
{1588FD7C-241E-4E7D-9113-43735F3E6BAD} = {4DAF0299-495E-4CD1-A982-9BAC16A45932}
|
||||
{506FD703-BAA7-4F6E-9361-64F550EC8FCA} = {59840756-302F-44DF-AA47-441A9D673202}
|
||||
{CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32} = {59840756-302F-44DF-AA47-441A9D673202}
|
||||
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907} = {59840756-302F-44DF-AA47-441A9D673202}
|
||||
|
@ -2645,6 +2647,7 @@ Global
|
|||
{43CE4CE5-0010-4B99-9569-672670D26E26} = {59840756-302F-44DF-AA47-441A9D673202}
|
||||
{27B5AAEB-A548-44CF-9777-F8BAA32AF7AE} = {59840756-302F-44DF-AA47-441A9D673202}
|
||||
{68A10CD3-AA64-465B-AF5F-ED4E9700543C} = {BDB237B6-1D1D-400F-84CC-40A58FA59C8E}
|
||||
{4DAF0299-495E-4CD1-A982-9BAC16A45932} = {59840756-302F-44DF-AA47-441A9D673202}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {3140B1B7-C8EE-43D1-A772-D82A7061A271}
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
<!-- This file is read by XES, which we use in our Release builds. -->
|
||||
<PropertyGroup Label="Version">
|
||||
<XesUseOneStoreVersioning>true</XesUseOneStoreVersioning>
|
||||
<XesBaseYearForStoreVersion>2020</XesBaseYearForStoreVersion>
|
||||
<XesBaseYearForStoreVersion>2021</XesBaseYearForStoreVersion>
|
||||
<VersionMajor>1</VersionMajor>
|
||||
<VersionMinor>6</VersionMinor>
|
||||
<VersionMinor>7</VersionMinor>
|
||||
<VersionInfoProductName>Windows Terminal</VersionInfoProductName>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
|
@ -336,6 +336,8 @@ INITIALIZE_BINDABLE_ENUM_SETTING(LaunchMode, LaunchMode, LaunchMode, L"Globals_L
|
|||
|
||||
### Updating the UI
|
||||
|
||||
When adding a setting to the UI, make sure you follow the [UWP design guidance](https://docs.microsoft.com/windows/uwp/design/).
|
||||
|
||||
#### Enum Settings
|
||||
|
||||
Now, create a XAML control in the relevant XAML file. Use the following tips and tricks to style everything appropriately:
|
||||
|
|
|
@ -93,6 +93,8 @@
|
|||
"scrollDownPage",
|
||||
"scrollUp",
|
||||
"scrollUpPage",
|
||||
"scrollToBottom",
|
||||
"scrollToTop",
|
||||
"sendInput",
|
||||
"setColorScheme",
|
||||
"setTabColor",
|
||||
|
@ -103,6 +105,7 @@
|
|||
"toggleFocusMode",
|
||||
"toggleFullscreen",
|
||||
"togglePaneZoom",
|
||||
"toggleReadOnlyMode",
|
||||
"toggleShaderEffects",
|
||||
"wt",
|
||||
"unbound"
|
||||
|
@ -632,6 +635,11 @@
|
|||
"description": "When set to true, a selection is immediately copied to your clipboard upon creation. When set to false, the selection persists and awaits further action.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"focusFollowMouse": {
|
||||
"default": false,
|
||||
"description": "When set to true, the terminal will focus the pane on mouse hover.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"copyFormatting": {
|
||||
"default": true,
|
||||
"description": "When set to `true`, the color and font formatting of selected text is also copied to your clipboard. When set to `false`, only plain text is copied to your clipboard. An array of specific formats can also be used. Supported array values include `html` and `rtf`. Plain text is always copied.",
|
||||
|
|
|
@ -0,0 +1,128 @@
|
|||
---
|
||||
author: Pankaj Bhojwani, pabhojwa@microsoft.com
|
||||
created on: 2020-11-20
|
||||
last updated: 2021-2-5
|
||||
issue id: #8345
|
||||
---
|
||||
|
||||
# Appearance configuration objects for profiles
|
||||
|
||||
## Abstract
|
||||
|
||||
This spec outlines how we can support 'configuration objects' in our profiles, which
|
||||
will allow us to render differently depending on the state of the control. For example, a
|
||||
control can be rendered differently if it's focused as compared to when it's unfocused.
|
||||
|
||||
## Inspiration
|
||||
|
||||
Reference: [#3062](https://github.com/microsoft/terminal/issues/3062)
|
||||
|
||||
Users want there to be a more visible indicator than the one we have currently for which
|
||||
pane is focused and which panes are unfocused. This change would grant us that feature.
|
||||
|
||||
## Solution Design
|
||||
|
||||
The implementation design for appearance config objects centers around the recent change where inheritance was added to the
|
||||
`TerminalSettings` class in the Terminal Settings Model - i.e. different `TerminalSettings` objects can inherit from each other.
|
||||
The reason for this change was that we did not want a settings reload to erase any overrides `TermControl` may have made
|
||||
to the settings during runtime. By instead passing a child of the `TerminalSettings` object to the control, we can change
|
||||
the parent of the child during a settings reload without the overrides being erased (since those overrides live in the child).
|
||||
|
||||
The idea behind unfocused appearance configurations is similar. We will pass in another `TerminalSettings` object to the control,
|
||||
which is simply a child that already has some overrides in it. When the control gains or loses focus, it simply switches between
|
||||
the two settings objects appropriately.
|
||||
|
||||
### Allowed parameters
|
||||
|
||||
For now, these states are meant to be entirely appearance-based. So, not all parameters which can be
|
||||
defined in a `Profile` can be defined in this new object (for example, we do not want parameters which
|
||||
would cause a resize in this object.) Here is the list of parameters we will allow:
|
||||
|
||||
- Anything regarding colors: `colorScheme`, `foreground`, `background`, `cursorColor` etc
|
||||
- Anything regarding background image: `path`, `opacity`, `alignment`, `stretchMode`
|
||||
- `cursorShape`
|
||||
|
||||
We may wish to allow further parameters in these objects in the future (like `bellStyle`?). The addition
|
||||
of further parameters can be discussed in the future and is out of scope for this spec.
|
||||
|
||||
### Inheritance
|
||||
|
||||
The inheritance model can be thought of as an 'all-or-nothing' approach in the sense that the `unfocusedAppearance` object
|
||||
is considered as a *single* setting instead of an object with many settings. We have chosen this model because it is cleaner
|
||||
and easier to understand than the alternative, where each setting within an `unfocusedAppearance` object has a parent from which
|
||||
it obtains its value.
|
||||
|
||||
Note that when `TerminalApp` initializes a control, it creates a `TerminalSettings` object for that profile and passes the
|
||||
control a child of that object (so that the control can store overrides in the child, as described earlier). If an unfocused
|
||||
config is defined in the profile (or in globals/profile defaults), then `TerminalApp` will create a *child of that child*,
|
||||
put the relevant overrides in it, and pass that into the control as well. Thus, the inheritance of any undefined parameters
|
||||
in the unfocused config will be as follows:
|
||||
|
||||
1. The unfocused config specified in the profile (or in globals/profile defaults)
|
||||
2. Overrides made by the terminal control
|
||||
3. The parent profile
|
||||
|
||||
## UI/UX Design
|
||||
|
||||
Users will be able to add a new setting to their profiles that will look like this:
|
||||
|
||||
```
|
||||
"unfocusedAppearance":
|
||||
{
|
||||
"colorScheme": "Campbell",
|
||||
"cursorColor": "#888",
|
||||
"cursorShape": "emptyBox",
|
||||
"foreground": "#C0C0C0",
|
||||
"background": "#000000"
|
||||
}
|
||||
```
|
||||
|
||||
When certain appearance settings are changed via OSC sequences (such as the background color), only the focused/regular
|
||||
appearance will change and the unfocused one will remain unchanged. However, since the unfocused settings object inherits
|
||||
from the regular one, it will still apply the change (provided it does not define its own value for that setting).
|
||||
|
||||
## Capabilities
|
||||
|
||||
### Accessibility
|
||||
|
||||
Does not affect accessibility.
|
||||
|
||||
### Security
|
||||
|
||||
Does not affect security.
|
||||
|
||||
### Reliability
|
||||
|
||||
This is another location in the settings where parsing/loading the settings may fail. However, this is the case
|
||||
for any new setting we add so I would say that this is a reasonable cost for this feature.
|
||||
|
||||
### Compatibility
|
||||
|
||||
Should not affect compatibility.
|
||||
|
||||
### Performance, Power, and Efficiency
|
||||
|
||||
Rapidly switching between many panes, causing several successive appearance changes in a short period of time, could
|
||||
potentially impact performance. However, regular/reasonable pane switching should not have a noticeable effect.
|
||||
|
||||
## Potential Issues
|
||||
|
||||
Inactive tabs will be 'rendered' in the background with the `UnfocusedRenderingParams` object, we need to make
|
||||
sure that switching to an inactive tab (and so causing the renderer to update with the 'normal' parameters)
|
||||
does not cause the window to flash/show a jarring indicator that the rendering values changed.
|
||||
|
||||
## Future considerations
|
||||
|
||||
We will need to decide how this will look in the settings UI.
|
||||
|
||||
We may wish to add more states in the future (like 'elevated'). When that happens, we will need to deal with how
|
||||
these appearance objects can scale/layer over each other. We had a lot of discussion about this and could not find
|
||||
a suitable solution to the problem of multiple states being valid at the same time (like unfocused and elevated).
|
||||
This, along with the fact that it is uncertain if there even will be more states we would want to add led us to
|
||||
the conclusion that we should only support the unfocused state for now, and come back to this issue later. If there
|
||||
are no more states other than unfocused and elevated, we could allow combining them (like having an 'unfocused elevated' state).
|
||||
If there are more states, we could do the implementation as an extension rather than inherently supporting it.
|
||||
|
||||
## Resources
|
||||
|
||||
|
|
@ -0,0 +1,562 @@
|
|||
---
|
||||
author: Mike Griese @zadjii-msft
|
||||
created on: 2020-10-30
|
||||
last updated: 2020-02-05
|
||||
issue id: #4472
|
||||
---
|
||||
|
||||
# Windows Terminal Session Management
|
||||
|
||||
## Abstract
|
||||
This document is intended to serve as an addition to the [Process Model 2.0
|
||||
Spec]. That document provides a big-picture overview of changes to the entirety
|
||||
of the Windows Terminal process architecture, including both the split of
|
||||
window/content processes, as well as the introduction of monarch/peasant
|
||||
processes. The focus of that document was to identify solutions to a set of
|
||||
scenarios that were closely intertwined, and establish these solutions would
|
||||
work together, without preventing any one scenario from working. What that
|
||||
document did not do was prescribe specific solutions to the given scenarios.
|
||||
|
||||
This document offers a deeper dive on a subset of the issues in [#5000], to
|
||||
describe specifics for managing multiple windows with the Windows Terminal. This
|
||||
includes features such as:
|
||||
|
||||
* Run `wt` in the current window ([#4472])
|
||||
* Single Instance Mode ([#2227])
|
||||
|
||||
## Solution Design
|
||||
|
||||
### Monarch and Peasant Processes
|
||||
|
||||
This document assumes the reader is already familiar with the "Monarch and
|
||||
Peasant" architecture as detailed in the [Windows Terminal Process Model 2.0
|
||||
Spec]. As a quick summary:
|
||||
|
||||
* Every Windows Terminal window is a "Peasant" process.
|
||||
* One of the Windows Terminal window processes is also the "Monarch" process.
|
||||
The Monarch is picked randomly from the Terminal windows, and there is only
|
||||
ever one Monarch process at a time.
|
||||
* Peasants can communicate with the monarch when certain state changes (such as
|
||||
their window being activated), and the monarch can send commands to any of the
|
||||
peasants.
|
||||
|
||||
This architecture will be used to enable each of the following scenarios.
|
||||
|
||||
### Scenario: Open new tabs in most recently used window
|
||||
|
||||
A common feature of many browsers is that when a web URL is clicked somewhere,
|
||||
the web page is opened as a new tab in the most recently used window of the
|
||||
browser. This functionality is often referred to as "glomming", as the new tab
|
||||
"gloms" onto the existing window.
|
||||
|
||||
Currently, the terminal does not support such a feature - every `wt` invocation
|
||||
creates a new window. With the monarch/peasant architecture, it'll now be
|
||||
possible to enable such a scenario.
|
||||
|
||||
As each window is activated, it will call a method on the `Monarch` object
|
||||
(hosted by the monarch process) which will indicate that "I am peasant N, and
|
||||
I've been focused". The monarch will use those method calls to update its own
|
||||
internal stack of the most recently used windows.
|
||||
|
||||
Whenever a new `wt.exe` process is launched, that process will _first_ ask the
|
||||
monarch if it should run the commandline in an existing window, or create its
|
||||
own window.
|
||||
|
||||
![auto-glom-wt-exe](auto-glom-wt-exe.png)
|
||||
|
||||
If glomming is enabled, the monarch will dispatch the commandline to the
|
||||
appropriate window for them to handle instead. To the user, it'll seem as if the
|
||||
tab just opened in the most recent window.
|
||||
|
||||
Users should certainly be able to specify if they want new instances to glom
|
||||
onto the MRU window or not. You could imagine that currently, we default to the
|
||||
hypothetical value `"windowingBehavior": "useNew"`, meaning that each new wt gets
|
||||
its own new window.
|
||||
|
||||
If glomming is disabled, then the Monarch will call back to the peasant and tell
|
||||
it to run the provided commandline. The monarch will use the return value of
|
||||
`ExecuteCommandline` to indicate that the calling process should create a window
|
||||
and become a peasant process, and run the commandline itself.
|
||||
|
||||
#### Glomming within the same virtual desktop
|
||||
|
||||
When links are opened in the new Edge browser, they will only glom onto an
|
||||
existing window if that window is open in the current virtual desktop. This
|
||||
seems like a good idea of a feature for the Terminal to follow as well.
|
||||
|
||||
There must be some way for an application to determine which virtual desktop it
|
||||
is open on. We could use that information to have the monarch track the last
|
||||
active window _per-desktop_, and only glom when there's one on the current
|
||||
desktop.
|
||||
|
||||
We could make the `windowingBehavior` property accept a variety of
|
||||
configurations:
|
||||
|
||||
- `"useExisting"`: always glom to the most recent window, regardless of desktop.
|
||||
- `"useExistingOnSameDesktop"`: Only glom if there's an existing window on this
|
||||
virtual desktop, otherwise create a new window. This will be the new default
|
||||
value.
|
||||
- `"useNew"`: Never glom, always create a new window. This is technically the
|
||||
current behavior of the Terminal.
|
||||
|
||||
### Handling the current working directory
|
||||
|
||||
Consider the following scenario: the user runs `wt -d .` in the address bar of
|
||||
explorer, and the monarch determines that this new tab should be created in an
|
||||
existing window. For clarity during this example, we will label the existing
|
||||
window WT[1], and the second `wt.exe` process WT[2].
|
||||
|
||||
An example of this scenario is given in the following diagram:
|
||||
|
||||
![single-instance-mode-cwd](single-instance-mode-cwd.png)
|
||||
|
||||
In this scenario, we want the new tab to be spawned in the current working
|
||||
directory of WT[2], not WT[1]. So when WT[1] is about to run the commands that
|
||||
were passed to WT[2], WT[1] will need to:
|
||||
|
||||
* First, stash its own CWD
|
||||
* Change to the CWD of WT[2]
|
||||
* Run the commands from WT[2]
|
||||
* Then return to its original CWD.
|
||||
|
||||
So, as a part of the interface that a peasant uses to communicate the startup
|
||||
commandline to the monarch, we should also include the current working
|
||||
directory.
|
||||
|
||||
### Scenario: Run `wt` in the current window
|
||||
|
||||
One often requested scenario is the ability to run a `wt.exe` commandline in the
|
||||
current window, as opposed to always creating a new window. Presume we have the
|
||||
ability to communicate between different window processes. The logical extension
|
||||
of this scenario would be "run a `wt` commandline in _any_ given WT window".
|
||||
|
||||
Each window process will have its own unique ID assigned to it by the monarch.
|
||||
This ID will be a positive number. Windows can also have names assigned to them.
|
||||
These names are strings that the user specifies. A window will always have an
|
||||
ID, but not necessarily a name. Running a command in a given window with ID N
|
||||
should be as easy as something like:
|
||||
|
||||
```sh
|
||||
wt.exe --window N new-tab ; split-pane
|
||||
```
|
||||
|
||||
(or for shorthand, `wt -w N new-tab ; split-pane`).
|
||||
|
||||
More formally, we will add the following parameter to the top-level `wt`
|
||||
command:
|
||||
|
||||
#### `--window,-w <window-id>`
|
||||
Run these commands in the given Windows Terminal session. This enables opening
|
||||
new tabs, splits, etc. in already running Windows Terminal windows.
|
||||
* If `window-id` is `0`, run the given commands in _the current window_.
|
||||
* If `window-id` is a negative number, or the reserved name `new`, run the
|
||||
commands in a _new_ Terminal window.
|
||||
* If `window-id` is the ID or name of an existing window, then run the
|
||||
commandline in that window.
|
||||
* If `window-id` is _not_ the ID or name of an existing window, create a new
|
||||
window. That window will be assigned the ID or name provided in the
|
||||
commandline. The provided subcommands will be run in that new window.
|
||||
* If `window-id` is omitted, then obey the value of `windowingBehavior` when
|
||||
determining which window to run the command in.
|
||||
|
||||
_Whenever_ `wt.exe` is started, it must _always_ pass the provided commandline
|
||||
first to the monarch process for handling. This is important for glomming
|
||||
scenarios (as noted above). The monarch will parse the commandline, determine
|
||||
which window the commandline is destined for, then call `ExecuteCommandline` on
|
||||
that peasant, who will then run the command.
|
||||
|
||||
#### Running commands in the current window:`wt --window 0`
|
||||
|
||||
If `wt -w 0 <commands>` is run _outside_ a WT instance, it could attempt to glom
|
||||
onto _the most recent WT window_ instead. This seems more logical than something
|
||||
like `wt --window last` or some other special value indicating "run this in the
|
||||
MRU window".<sup>[[2]](#footnote-2)</sup>
|
||||
|
||||
That might be a simple, but **wrong**, implementation for "the current window".
|
||||
If the peasants always raise an event when their window is focused, and the
|
||||
monarch keeps track of the MRU order for peasants, then one could naively assume
|
||||
that the execution of `wt -w 0 <commands>` would always return the window the
|
||||
user was typing in, the current one. However, if someone were to do something
|
||||
like `sleep 10 ; wt -w 0 <commands>`, then the user could easily focus another
|
||||
WT window during the sleep, which would cause the MRU window to not be the same
|
||||
as the window executing the command.
|
||||
|
||||
To solve this issue, we'll other than
|
||||
attempting to use the `WT_SESSION` environment variable. If a `wt.exe` process
|
||||
is spawned and that's in its environment variables, it could try and ask the
|
||||
monarch for the peasant who's hosting the session corresponding to that GUID.
|
||||
This is more of a theoretical solution than anything else.
|
||||
|
||||
In the past we've been reluctant to rely too heavily on `WT_SESSION`. However,
|
||||
an environment variable does seem to be the only reliable way to be confident
|
||||
where the window was created from. We could introduce another environment
|
||||
variable instead - `WT_WINDOW_ID`. That would allow us to shortcut the session
|
||||
ID lookup. However, I worry about exposing the window ID as an environment
|
||||
variable. If we do that, users will inevitably use that instead of the `wt -0`
|
||||
alias, which should take care of the work for them. Additionally, `WT_WINDOW_ID`
|
||||
wouldn't update in the child processes as tabs are torn out of windows to create
|
||||
new windows.
|
||||
|
||||
Both solutions are prone to the user changing the value of the variable to some
|
||||
garbage value. If they do that, this lookup will most certainly not work as
|
||||
expected. Using the session ID (a GUID) instead of the window ID (an int) makes
|
||||
it less likely that they guess the ID of an existing instance.
|
||||
|
||||
#### Running commands in a new window:`wt --window -1` / `wt --window new`
|
||||
|
||||
If the user passes a negative number, or the reserved name `new` to the
|
||||
`--window` parameter, then we will always create a new window for that
|
||||
commandline, regardless of the value of `windowingBehavior`. This will allow
|
||||
users to do something like `wt -w -1 new-tab` to _always_ create a new window.
|
||||
|
||||
#### `--window` in subcommands
|
||||
|
||||
The `--window` parameter is a setting to `wt.exe` itself, not to one of its
|
||||
subcommands (like `new-tab` or `split-pane`). This means that all of the
|
||||
subcommands in a particular `wt` commandline will all be handled by the same
|
||||
session. For example, let us consider a user who wants to open a new tab in
|
||||
window 2, and split a new pane in window 3, all at once. The user _cannot_ do
|
||||
something like:
|
||||
|
||||
```cmd
|
||||
wt -w 2 new-tab ; -w 3 split-pane
|
||||
```
|
||||
|
||||
Instead, the user will need to separate the commands (by whatever their shell's
|
||||
own command delimiter is) and run two different `wt.exe` instances:
|
||||
|
||||
```cmd
|
||||
wt -w 2 new-tab & wt -w 3 split-pane
|
||||
```
|
||||
|
||||
This is done to make the parsing of the subcommands easier, and for the internal
|
||||
passing of arguments simpler. If the `--window` parameter were a part of each
|
||||
subcommand, then each individual subcommand's parser would need to be
|
||||
enlightened about that parameter, and then it would need to be possible for any
|
||||
single part of the commandline to call out to another process. It would be
|
||||
especially tricky then to coordinate the work being done across process here.
|
||||
The source process would need some sort of way to wait for the other process to
|
||||
notify the source that a particular subcommand completed, before allowing the
|
||||
source to dispatch the next part of the commandline.
|
||||
|
||||
Overall, this is seen as unnecessarily complex, and dispatching whole sets of
|
||||
commands as a simpler solution.
|
||||
|
||||
### Naming Windows
|
||||
|
||||
It's not user-friendly to rely on automatically generated, invisible numbers to
|
||||
identify windows. There's not a great way of identifying which window is which.
|
||||
The user would need to track the IDs in their head manually. Instead, we'll
|
||||
allow the user to provide a string name for the window. This name can be used to
|
||||
address a window in addition to the ID.
|
||||
|
||||
Names can be provided on the commandline, in the original commandline. For
|
||||
example, `wt -w foo nt` would name the new window "foo". Names can also be set
|
||||
with a new action, `NameWindow`<sup>[[3]](#footnote-3)</sup>. `name-window`
|
||||
could also be used as a subcommand. For example, `wt -w 4 name-window bar` would
|
||||
name window 4 "bar".
|
||||
|
||||
To keep identities mentally distinct, we will disallow names that are integers
|
||||
(positive or negative). This will prevent users from renaming a window to `2`,
|
||||
then having `wt -w 2` be ambiguous as to which window it refers to.
|
||||
|
||||
Names must also be unique. If a user attempts to set the name of the window to
|
||||
an already-used name, we'll need to ignore the name change. We could also
|
||||
display a "toast" or some other type of low-impact message to the user. That
|
||||
message would have some text like: "Unable to rename window. Another window with
|
||||
that name already exists".
|
||||
|
||||
The Terminal will reserve the name `new`. It will also reserve any names
|
||||
starting with the character `_`. The user will not be allowed to set the window
|
||||
name to any of these reserved names. Reserving `_*` allows us to add other
|
||||
keywords in the future, without introducing a breaking change.
|
||||
|
||||
## UI/UX Design
|
||||
|
||||
### `windowingBehavior` details
|
||||
|
||||
The following list gives greater breakdown of the values of `windowingBehavior`,
|
||||
and how they operate:
|
||||
|
||||
* `"windowingBehavior": "useExisting", "useExistingOnSameDesktop"`:
|
||||
**Browser-like glomming**
|
||||
- New instances open in the current window by default.
|
||||
- `newWindow` opens a new window.
|
||||
- Tabs can be torn out to create new windows.
|
||||
- `wt -w -1` opens a new window.
|
||||
* `"windowingBehavior": "useNew"`: No auto-glomming. This is **the current
|
||||
behavior** of the Terminal.
|
||||
- New instances open in new windows by default
|
||||
- `newWindow` opens a new window
|
||||
- Tabs can be torn out to create new windows.
|
||||
- `wt -w -1` opens a new window.
|
||||
|
||||
We'll be changing the default behavior from `useNew` to
|
||||
`useExistingOnSameDesktop`. This will be more consistent with other tabbed
|
||||
applications.
|
||||
|
||||
## Concerns
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><strong>Accessibility</strong></td>
|
||||
<td>
|
||||
|
||||
There is no expected accessibility impact from this feature. Each window will
|
||||
handle UIA access as it normally does.
|
||||
|
||||
In the future, we could consider exposing the window IDs and/or names via UIA.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Security</strong></td>
|
||||
<td>
|
||||
|
||||
Many security concerns have already be covered in greater detail in the parent
|
||||
spec, [Process Model 2.0 Spec].
|
||||
|
||||
When attempting to instantiate the Monarch, COM will only return the object from
|
||||
a server running at the same elevation level. We don't need to worry about
|
||||
unelevated peasants connecting to the elevated Monarch, or vice-versa.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Reliability</strong></td>
|
||||
<td>
|
||||
|
||||
We will need to be careful when working with objects hosted by another process.
|
||||
Any work we do with it MUST be in a try/catch, because at _any_ time, the other
|
||||
process could be killed. At any point, a window process could be killed. Both
|
||||
the monarch and peasant code will need to be redundant to such a scenario, and
|
||||
if the other process is killed, make sure to display an appropriate error and
|
||||
either recover or exit gracefully.
|
||||
|
||||
In any and all these situations, we will want to try and be as verbose as
|
||||
possible in the logging. This will make tracking which process had the error
|
||||
occur easier.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Compatibility</strong></td>
|
||||
<td>
|
||||
|
||||
We will be changing the default behavior of the Terminal to auto-glom to the
|
||||
most-recently used window on the same desktop in the course of this work, which
|
||||
will be a breaking UX change. This is behavior that can be reverted with the
|
||||
`"windowingBehavior": "useNew"` setting.
|
||||
|
||||
We acknowledge that this is a pretty massive change to the default experience of
|
||||
the Terminal. We're planning on doing some polling of users to determine which
|
||||
behavior they want by default. Additionally, we'll be staging the rollout of
|
||||
this feature, using the Preview builds of the Terminal. The release notes that
|
||||
first include it will call extra attention to this feature. We'll ask that users
|
||||
provide their feedback in a dedicated thread, so we have time to collect
|
||||
opinions from users before rolling the change out to all users.
|
||||
|
||||
We may choose to only change the default to `useExistingOnSameDesktop` once tab
|
||||
tear out is available, so users who are particularly unhappy about this change
|
||||
can still tear out the tab (if they can't be bothered to change the setting).
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Performance, Power, and Efficiency</strong></td>
|
||||
<td>
|
||||
|
||||
There's no dramatic change expected here. There may be a minor delay in the
|
||||
spawning of new terminal instances, due to requiring cross-process hops for the
|
||||
communication between monarch and peasant processes.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## Potential Issues
|
||||
|
||||
### Mixed Elevation Levels
|
||||
|
||||
As of December 2020, we're no longer pursuing a "mixed-elevation" scenario for
|
||||
the Terminal. This makes many of the cross-elevation scenarios simpler. Elevated
|
||||
and unelevated `wt` instances will always remain separate. The different
|
||||
elevation levels will maintain separate lists of window IDs. If the user is
|
||||
running both an elevated and unelevated window, then there will be two monarchs.
|
||||
One elevated, and the other unelevated.
|
||||
|
||||
There will also be some edge cases when handling the commandline that will need
|
||||
special care. Say the user wanted to open a new tab in the elevated window, from
|
||||
and unelevated `explorer.exe`. That would be a commandline like:
|
||||
|
||||
```sh
|
||||
wt -w 0 new-tab -d . --elevated
|
||||
```
|
||||
|
||||
Typically we first determine which window the commandline is intended for, then
|
||||
dispatch it to that window. In this case, the `-w 0` will cause us to pass the
|
||||
commandline to the current unelevated window. Then, that window will try to open
|
||||
an elevated tab, fail, and create a new `wt.exe` process. This second `wt.exe`
|
||||
process will lose the `-w 0` context. It won't inform the elevated monarch that
|
||||
this commandline should be run in the active session.
|
||||
|
||||
We will need to make sure that special care is taken when creating elevated
|
||||
instances that we maintain the `--window` parameter passed to the Terminal.
|
||||
|
||||
### `wt` Startup Commandline Options
|
||||
|
||||
There are a few commandline options which can be provided to `wt.exe` which
|
||||
don't make sense to pass to another session. These options include (but are not
|
||||
limited to):
|
||||
|
||||
* `--initialSize r,c`
|
||||
* `--initialPosition x,y`
|
||||
* `--fullscreen`, `--maximized`, etc.
|
||||
|
||||
When we're passing a commandline to another instance to handle, these arguments
|
||||
will be ignored. they only apply to the initial creation of a window.
|
||||
`--initialSize 32, 120` doesn't make sense if the window already has a size.
|
||||
|
||||
On startup of a new window, we currently assume that the first command is always
|
||||
`new-tab`. When passing commandlines to existing windows, we won't need to make
|
||||
that assumption anymore. There will already be existing tabs.
|
||||
|
||||
### Monarch MRU Window Tracking
|
||||
|
||||
As stated above, the monarch is responsible for tracking the MRU window stack.
|
||||
However, when the monarch is closed, this state will be lost. The new monarch
|
||||
will be elected, but it will be unable to ask the old monarch for the MRU
|
||||
order of the windows.
|
||||
|
||||
We had previously considered an _acceptable_ UX when this would occur. We would
|
||||
randomize the order (with the new monarch becoming the MRU window). If someone
|
||||
noticed this bug and complained, then we had a theoretical solution prepared.
|
||||
The peasants could inform not only the monarch, but _all other peasants_ when
|
||||
they become activated. This would mean all peasants are simultaneously tracking
|
||||
the MRU stack. This would mean that any given peasant would be prepared always
|
||||
to become the monarch.
|
||||
|
||||
A simpler solution though would be to not track the MRU stack in the Monarch at
|
||||
all. Instead, each peasant could just track internally when they were last
|
||||
activated. The Monarch wouldn't track any state itself. It would be distributed
|
||||
across all the peasants. The Monarch could then iterate over the list of
|
||||
peasants and find the one with the newest `LastActivated` timestamp.
|
||||
|
||||
Now, when a Monarch dies, the new Peasant doesn't have to come up with the stack
|
||||
itself. All the other Peasants keep their state. The new Monarch can query them
|
||||
and get the same answer the old Monarch would have.
|
||||
|
||||
We could further optimize this by having the Monarch also track the stack. Then,
|
||||
the monarch could query the MRU window quickly. The `LastActivated` timestamps
|
||||
would only be used by a new Monarch when it is elected, to reconstruct the MRU
|
||||
stack.
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
This is a list of actionable tasks generated as described by this spec:
|
||||
|
||||
* [ ] Add support for `wt.exe` processes to be Monarchs and Peasants, and
|
||||
communicate that state between themselves. This task does not otherwise add
|
||||
any user-facing features, merely an architectural update.
|
||||
* [ ] Add support for the `windowingBehavior` setting as a boolean. Opening new
|
||||
WT windows will conditionally glom to existing windows.
|
||||
* [ ] Add support for per-desktop `windowingBehavior`, by adding the support for
|
||||
the enum values `"useExisting"`, `"useExistingOnSameDesktop"` and `"useNew"`.
|
||||
* [ ] Add support for `wt.exe` to pass commandlines intended for another window
|
||||
to the monarch, then to the intended window, with the `--window,-w
|
||||
window-id` commandline parameter.
|
||||
* [ ] Add support for targeting and naming windows via the `-w` parameter on the
|
||||
commandline
|
||||
* [ ] Add a `NameWindow` action, subcommand that allows the user to set the name
|
||||
for the window.
|
||||
* [ ] Add an action that will cause all windows to briefly display a overlay
|
||||
with the current window ID and name. This would be something like the
|
||||
"identify" feature of the Windows "Display" settings.
|
||||
|
||||
## Future considerations
|
||||
|
||||
* What if the user wanted to pipe a command to a pane in an existing window?
|
||||
```sh
|
||||
man ping > wt -w 0 split-pane cat
|
||||
```
|
||||
Is there some way for WT to pass its stdin/out handles to the child process
|
||||
it's creating? This is _not_ related to the current spec at hand, just
|
||||
something the author considered while writing the spec. This likely belongs
|
||||
over in [#492], or in its own spec.
|
||||
- Or I suppose, with less confusion, someone could run `wt -w 0 split-pane --
|
||||
man ping > cat`. That's certainly more sensible, and wouldn't require any
|
||||
extra work.
|
||||
* "Single Instance Mode" is a scenario in which there is only ever one single WT
|
||||
window. A user might want this functionality to only ever allow a single
|
||||
terminal window to be open on their desktop. This is especially frequently
|
||||
requested in combination with "quake mode", as discussed in [#653]. When Single
|
||||
Instance Mode is active, and the user runs a new `wt.exe` commandline, it will
|
||||
always end up running in the existing window, if there is one.
|
||||
|
||||
An earlier version of this spec proposed a new value of `glomToLastWindow`.
|
||||
(`glomToLastWindow` was later renamed `windowingBehavior`). The `always` value
|
||||
would disable tab tear out<sup>[[1]](#footnote-1)</sup>. It would additionally
|
||||
disable the `newWindow` action, and prevent `wt -w new` from opening a new
|
||||
window.
|
||||
|
||||
In discussion, it was concluded that this setting didn't make sense. Why did the
|
||||
`glomToLastWindow` setting change the behavior of tear out? Single Instance Mode
|
||||
is most frequently requested in regards to quake mode. We're leaving the
|
||||
implementation of true single instance mode to that spec.
|
||||
* It was suggested in review that we could auto-generate names for windows, from
|
||||
some list of words. Prior art could be the URLS for gfycat.com or
|
||||
what3words.com, which use three random words. I believe `docker` also assigns
|
||||
names from a random selection of `adjective`+`name`. This is an interesting
|
||||
idea, and something that could be pursued in the future.
|
||||
- This would be a massive pain to localize though, hence why this is left as
|
||||
a future consideration.
|
||||
* We will _need_ to provide a commandline tool to list windows and their IDs &
|
||||
names. We're thinking a list of windows, their IDs, names, PIDs, and the title
|
||||
of the window.
|
||||
|
||||
Currently we're stuck with `wt.exe` which is a GUI application, and cannot
|
||||
print to the console. Our need is now fairly high for the ability to print
|
||||
info to the console. To remedy this, we'll need to ship another helper exe as
|
||||
a commandline tool for working with the terminal. The design for this is left
|
||||
for the future.
|
||||
|
||||
## Footnotes
|
||||
|
||||
<a name="footnote-1"><a>[1]: While tear-out is a separate track of work from
|
||||
session management in general, this setting could be implemented along with this
|
||||
set of features, and later used to control tear out as well.
|
||||
|
||||
<a name="footnote-2"><a>[2]: Since we're reserving the keyword `new` to mean "a
|
||||
new window", then we could also reserve `last` or `current` as an alias for "the
|
||||
current window".
|
||||
|
||||
<a name="footnote-3"><a>[3]: We currently have two actions for renaming _tabs_
|
||||
in the Terminal: `renameTab(name)`, and `openTabRenamer()`. We will likely
|
||||
similarly need `nameWindow(name)` and `openWindowNamer()`. `openWindowNamer`
|
||||
could display a dialog to allow the user to rename the current window at
|
||||
runtime.
|
||||
|
||||
|
||||
## Resources
|
||||
|
||||
* [Tab Tear-out in the community toolkit] - this document proved invaluable to
|
||||
the background of tearing a tab out of an application to create a new window.
|
||||
|
||||
<!-- Footnotes -->
|
||||
|
||||
[#5000]: https://github.com/microsoft/terminal/issues/5000
|
||||
[#1256]: https://github.com/microsoft/terminal/issues/1256
|
||||
[#4472]: https://github.com/microsoft/terminal/issues/4472
|
||||
[#2227]: https://github.com/microsoft/terminal/issues/2227
|
||||
[#653]: https://github.com/microsoft/terminal/issues/653
|
||||
[#1032]: https://github.com/microsoft/terminal/issues/1032
|
||||
[#632]: https://github.com/microsoft/terminal/issues/632
|
||||
[#492]: https://github.com/microsoft/terminal/issues/492
|
||||
[#4000]: https://github.com/microsoft/terminal/issues/4000
|
||||
[#7972]: https://github.com/microsoft/terminal/pull/7972
|
||||
[#961]: https://github.com/microsoft/terminal/issues/961
|
||||
[`30b8335`]: https://github.com/microsoft/terminal/commit/30b833547928d6dcbf88d49df0dbd5b3f6a7c879
|
||||
[Tab Tear-out in the community toolkit]: https://github.com/windows-toolkit/Sample-TabView-TearOff
|
||||
[Quake mode scenarios]: https://github.com/microsoft/terminal/issues/653#issuecomment-661370107
|
||||
[`ISwapChainPanelNative2::SetSwapChainHandle`]: https://docs.microsoft.com/en-us/windows/win32/api/windows.ui.xaml.media.dxinterop/nf-windows-ui-xaml-media-dxinterop-iswapchainpanelnative2-setswapchainhandle
|
||||
[Process Model 2.0 Spec]: https://github.com/microsoft/terminal/blob/main/doc/specs/%235000%20-%20Process%20Model%202.0/%235000%20-%20Process%20Model%202.0.md
|
1186
doc/specs/#5000 - Process Model 2.0/#5000 - Process Model 2.0.md
Normal file
BIN
doc/specs/#5000 - Process Model 2.0/auto-glom-wt-exe.png
Normal file
After Width: | Height: | Size: 83 KiB |
After Width: | Height: | Size: 54 KiB |
BIN
doc/specs/#5000 - Process Model 2.0/figure-001.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
doc/specs/#5000 - Process Model 2.0/figure-002.png
Normal file
After Width: | Height: | Size: 37 KiB |
BIN
doc/specs/#5000 - Process Model 2.0/figure-003.png
Normal file
After Width: | Height: | Size: 52 KiB |
BIN
doc/specs/#5000 - Process Model 2.0/mixed-elevation.png
Normal file
After Width: | Height: | Size: 44 KiB |
BIN
doc/specs/#5000 - Process Model 2.0/single-instance-mode-cwd.png
Normal file
After Width: | Height: | Size: 53 KiB |
BIN
doc/specs/#5000 - Process Model 2.0/tear-out-tab.png
Normal file
After Width: | Height: | Size: 46 KiB |
BIN
doc/specs/#5000 - Process Model 2.0/wt-session-0.png
Normal file
After Width: | Height: | Size: 93 KiB |
95
doc/specs/#597 - Tab Sizing.md
Normal file
|
@ -0,0 +1,95 @@
|
|||
---
|
||||
author: Kayla Cinnamon @cinnamon-msft
|
||||
created on: 2020-01-03
|
||||
last updated: 2020-01-03
|
||||
issue id: 597
|
||||
---
|
||||
|
||||
# Tab Sizing
|
||||
|
||||
## Abstract
|
||||
|
||||
This spec outlines the tab sizing feature. This is an application-level feature that is not profile-specific (at least for now).
|
||||
|
||||
Global properties that encompass tab sizing:
|
||||
|
||||
* `tabWidthMode` (accepts pre-defined values for tab sizing behavior)
|
||||
* `tabWidthMin` (can never be smaller than the icon width)
|
||||
* `tabWidthMax` (can never be wider than the tab bar)
|
||||
|
||||
Acceptable values for `tabWidthMode`:
|
||||
|
||||
* [default] `equal` (all tabs are sized the same, regardless of tab title length)
|
||||
* `titleLength` (width of tab contains entire tab title)
|
||||
|
||||
## Inspiration
|
||||
|
||||
Other browsers and terminals have varying tab width behavior, so we should give people options.
|
||||
|
||||
## Solution Design
|
||||
|
||||
`tabWidthMode` will be a global setting that will accept the following strings:
|
||||
|
||||
* `equal`
|
||||
* All tabs are equal in width
|
||||
* If the tab bar has filled, tabs will shrink as additional tabs are added
|
||||
* Utilizes the `equal` setting from WinUI's TabView
|
||||
|
||||
* `titleLength`
|
||||
* Tab width varies depending on title length
|
||||
* Width of tab will fit the whole tab title
|
||||
* Utilizes the `sizeToContent` setting from WinUI's TabView
|
||||
|
||||
In addition to `tabWidthMode`, the following global properties will also be available:
|
||||
|
||||
* `tabWidthMin`
|
||||
* Accepts an integer
|
||||
* Value correlates to the minimum amount of pixels the tab width can be
|
||||
* If value is less than the width of the icon, the minimum width will be the width of the icon
|
||||
* If value is greater than the width of the tab bar, the maximum width will be the width of the tab bar
|
||||
* If not set, the tab will have the system-defined minimum width
|
||||
|
||||
* `tabWidthMax`
|
||||
* Accepts an integer
|
||||
* Value correlates to the maximum amount of pixels the tab width can be
|
||||
* If value is less than the width of the icon, the minimum width will be the width of the icon
|
||||
* If value is greater than the width of the tab bar, the maximum width will be the width of the tab bar
|
||||
* If not set, the tab will have the system-defined maximum width
|
||||
|
||||
If `tabWidthMode` is set to `titleLength`, the tab widths will fall between the `tabWidthMin` and `tabWidthMax` values if they are set, depending on the length of the tab title.
|
||||
|
||||
If `tabWidthMode` isn't set, the default experience will be `equal`. Justification for the default experience is the results from this [twitter poll](https://twitter.com/cinnamon_msft/status/1203093459055210496).
|
||||
|
||||
## UI/UX Design
|
||||
|
||||
[This tweet](https://twitter.com/cinnamon_msft/status/1203094776117022720) displays how the `equal` and `titleLength` values behave for the `tabWidthMode` property.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### Accessibility
|
||||
|
||||
This feature could impact accessibility if the tab title isn't stored within the metadata of the tab. If the tab width is the width of the icon, then the title isn't visible. The tab title will have to be accessible by a screen reader.
|
||||
|
||||
### Security
|
||||
|
||||
This feature will not impact security.
|
||||
|
||||
### Reliability
|
||||
|
||||
This feature will not impact reliability. It provides users with additional customization options.
|
||||
|
||||
### Compatibility
|
||||
|
||||
This feature will not break existing compatibility.
|
||||
|
||||
### Performance, Power, and Efficiency
|
||||
|
||||
## Potential Issues
|
||||
|
||||
This feature will not impact performance, power, nor efficiency.
|
||||
|
||||
## Future considerations
|
||||
|
||||
* Provide tab sizing options per-profile
|
||||
* A `tabWidthMode` value that will evenly divide the entirety of the tab bar by the number of open tabs
|
||||
* i.e. One tab will take the full width of the tab bar, two tabs will each take up half the width of the tab bar, etc.
|
BIN
res/Cascadia.ttf
|
@ -17,5 +17,5 @@ Please consult the [license](https://raw.githubusercontent.com/microsoft/cascadi
|
|||
|
||||
### Fonts Included
|
||||
|
||||
* Cascadia Code, Cascadia Mono (2009.21)
|
||||
* from microsoft/cascadia-code@32f84124db1970fa5d032f0fe9019e6922961beb
|
||||
* Cascadia Code, Cascadia Mono (2102.03)
|
||||
* from microsoft/cascadia-code@b358d1ba3d1629c113671312b18eab52797cc055
|
||||
|
|
42
samples/PixelShaders/Animate_breathe.hlsl
Normal file
|
@ -0,0 +1,42 @@
|
|||
// A simple animated shader that fades the terminal background back and forth between two colours
|
||||
|
||||
// The terminal graphics as a texture
|
||||
Texture2D shaderTexture;
|
||||
SamplerState samplerState;
|
||||
|
||||
// Terminal settings such as the resolution of the texture
|
||||
cbuffer PixelShaderSettings
|
||||
{
|
||||
// The number of seconds since the pixel shader was enabled
|
||||
float Time;
|
||||
// UI Scale
|
||||
float Scale;
|
||||
// Resolution of the shaderTexture
|
||||
float2 Resolution;
|
||||
// Background color as rgba
|
||||
float4 Background;
|
||||
};
|
||||
|
||||
// pi and tau (2 * pi) are useful constants when using trigonometric functions
|
||||
#define TAU 6.28318530718
|
||||
|
||||
// A pixel shader is a program that given a texture coordinate (tex) produces a color.
|
||||
// tex is an x,y tuple that ranges from 0,0 (top left) to 1,1 (bottom right).
|
||||
// Just ignore the pos parameter.
|
||||
float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
|
||||
{
|
||||
// Read the color value at the current texture coordinate (tex)
|
||||
float4 sample = shaderTexture.Sample(samplerState, tex);
|
||||
|
||||
// The number of seconds the breathing effect should span
|
||||
float duration = 5.0;
|
||||
|
||||
float3 color1 = float3(0.3, 0.0, 0.5); // indigo
|
||||
float3 color2 = float3(0.1, 0.1, 0.44); // midnight blue
|
||||
|
||||
// Set background colour based on the time
|
||||
float4 backgroundColor = float4(lerp(color1, color2, 0.5 * cos(TAU / duration * Time) + 0.5), 1.0);
|
||||
|
||||
// Draw the terminal graphics over the background
|
||||
return lerp(backgroundColor, sample, sample.w);
|
||||
}
|
45
samples/PixelShaders/Animate_scan.hlsl
Normal file
|
@ -0,0 +1,45 @@
|
|||
// A simple animated shader that draws an inverted line that scrolls down the screen
|
||||
|
||||
// The terminal graphics as a texture
|
||||
Texture2D shaderTexture;
|
||||
SamplerState samplerState;
|
||||
|
||||
// Terminal settings such as the resolution of the texture
|
||||
cbuffer PixelShaderSettings
|
||||
{
|
||||
// The number of seconds since the pixel shader was enabled
|
||||
float Time;
|
||||
// UI Scale
|
||||
float Scale;
|
||||
// Resolution of the shaderTexture
|
||||
float2 Resolution;
|
||||
// Background color as rgba
|
||||
float4 Background;
|
||||
};
|
||||
|
||||
// A pixel shader is a program that given a texture coordinate (tex) produces a color.
|
||||
// tex is an x,y tuple that ranges from 0,0 (top left) to 1,1 (bottom right).
|
||||
// Just ignore the pos parameter.
|
||||
float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
|
||||
{
|
||||
// Read the color value at the current texture coordinate (tex)
|
||||
float4 color = shaderTexture.Sample(samplerState, tex);
|
||||
|
||||
// Here we spread the animation over 5 seconds. We use time modulo 5 because we want
|
||||
// the timer to count to five repeatedly. We then divide the result by five again
|
||||
// to get a value between 0.0 and 1.0, which maps to our texture coordinate.
|
||||
float linePosition = Time % 5 / 5;
|
||||
|
||||
// Since TEXCOORD ranges from 0.0 to 1.0, we need to divide 1.0 by the height of the
|
||||
// texture to find out the size of a single pixel
|
||||
float lineWidth = 1.0 / Resolution.y;
|
||||
|
||||
// If the current texture coordinate is in the range of our line on the Y axis:
|
||||
if (tex.y > linePosition - lineWidth && tex.y < linePosition)
|
||||
{
|
||||
// Invert the sampled color
|
||||
color.rgb = 1.0 - color.rgb;
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
|
@ -6,7 +6,7 @@ SamplerState samplerState;
|
|||
|
||||
// Terminal settings such as the resolution of the texture
|
||||
cbuffer PixelShaderSettings {
|
||||
// Time since pixel shader was enabled
|
||||
// The number of seconds since the pixel shader was enabled
|
||||
float Time;
|
||||
// UI Scale
|
||||
float Scale;
|
||||
|
@ -16,8 +16,9 @@ cbuffer PixelShaderSettings {
|
|||
float4 Background;
|
||||
};
|
||||
|
||||
// A pixel shader is a program that given a texture coordinate (tex) produces a color
|
||||
// Just ignore the pos parameter
|
||||
// A pixel shader is a program that given a texture coordinate (tex) produces a color.
|
||||
// tex is an x,y tuple that ranges from 0,0 (top left) to 1,1 (bottom right).
|
||||
// Just ignore the pos parameter.
|
||||
float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
|
||||
{
|
||||
// Read the color value at the current texture coordinate (tex)
|
||||
|
|
|
@ -6,7 +6,7 @@ SamplerState samplerState;
|
|||
|
||||
// Terminal settings such as the resolution of the texture
|
||||
cbuffer PixelShaderSettings {
|
||||
// Time since pixel shader was enabled
|
||||
// The number of seconds since the pixel shader was enabled
|
||||
float Time;
|
||||
// UI Scale
|
||||
float Scale;
|
||||
|
@ -16,8 +16,9 @@ cbuffer PixelShaderSettings {
|
|||
float4 Background;
|
||||
};
|
||||
|
||||
// A pixel shader is a program that given a texture coordinate (tex) produces a color
|
||||
// Just ignore the pos parameter
|
||||
// A pixel shader is a program that given a texture coordinate (tex) produces a color.
|
||||
// tex is an x,y tuple that ranges from 0,0 (top left) to 1,1 (bottom right).
|
||||
// Just ignore the pos parameter.
|
||||
float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
|
||||
{
|
||||
// Read the color value at the current texture coordinate (tex)
|
||||
|
|
|
@ -20,7 +20,7 @@ SamplerState samplerState;
|
|||
|
||||
// Terminal settings such as the resolution of the texture
|
||||
cbuffer PixelShaderSettings {
|
||||
// Time since pixel shader was enabled
|
||||
// The number of seconds since the pixel shader was enabled
|
||||
float Time;
|
||||
// UI Scale
|
||||
float Scale;
|
||||
|
@ -30,8 +30,9 @@ cbuffer PixelShaderSettings {
|
|||
float4 Background;
|
||||
};
|
||||
|
||||
// A pixel shader is a program that given a texture coordinate (tex) produces a color
|
||||
// Just ignore the pos parameter
|
||||
// A pixel shader is a program that given a texture coordinate (tex) produces a color.
|
||||
// tex is an x,y tuple that ranges from 0,0 (top left) to 1,1 (bottom right).
|
||||
// Just ignore the pos parameter.
|
||||
float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
|
||||
{
|
||||
// Read the color value at the current texture coordinate (tex)
|
||||
|
@ -49,16 +50,22 @@ float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
|
|||
Save this file as `C:\temp\invert.hlsl`, then update a profile with the setting:
|
||||
|
||||
```
|
||||
"experimental.pixelShaderEffect": "C:\\temp\\invert.hlsl"
|
||||
"experimental.pixelShaderPath": "C:\\temp\\invert.hlsl"
|
||||
```
|
||||
|
||||
Once the settings file is saved, open a terminal with the changed profile. It should now invert the colors of the screen!
|
||||
|
||||
|
||||
Default Terminal | Inverted Terminal
|
||||
---------|---------
|
||||
![Default Terminal](Screenshots/TerminalDefault.PNG) | ![Inverted Terminal](Screenshots/TerminalInvert.PNG)
|
||||
|
||||
|
||||
If your shader fails to compile, the Terminal will display a warning dialog and ignore it temporarily. After fixing your shader, touch the `settings.json` file again, or open a new tab, and the Terminal will try loading the shader again.
|
||||
|
||||
## HLSL
|
||||
|
||||
The language we use to write pixel shaders is called `HLSL`. It a `C`-like language, with some restrictions.You can't allocate memory, use pointers or recursion.
|
||||
The language we use to write pixel shaders is called `HLSL`. It's a `C`-like language, with some restrictions. You can't allocate memory, use pointers or recursion.
|
||||
What you get access to is computing power in the teraflop range on decently recent GPUs. This means writing real-time raytracers or other cool effects are in the realm of possibility.
|
||||
|
||||
[shadertoy](https://shadertoy.com/) is a great site that show case what's possible with pixel shaders (albeit in `GLSL`). For example this [menger sponge](https://www.shadertoy.com/view/4scXzn). Converting from `GLSL` to `HLSL` isn't overly hard once you gotten the hang of it.
|
||||
|
@ -76,7 +83,7 @@ SamplerState samplerState;
|
|||
|
||||
// Terminal settings such as the resolution of the texture
|
||||
cbuffer PixelShaderSettings {
|
||||
// Time since pixel shader was enabled
|
||||
// The number of seconds since the pixel shader was enabled
|
||||
float Time;
|
||||
// UI Scale
|
||||
float Scale;
|
||||
|
@ -86,8 +93,9 @@ cbuffer PixelShaderSettings {
|
|||
float4 Background;
|
||||
};
|
||||
|
||||
// A pixel shader is a program that given a texture coordinate (tex) produces a color
|
||||
// Just ignore the pos parameter
|
||||
// A pixel shader is a program that given a texture coordinate (tex) produces a color.
|
||||
// tex is an x,y tuple that ranges from 0,0 (top left) to 1,1 (bottom right).
|
||||
// Just ignore the pos parameter.
|
||||
float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
|
||||
{
|
||||
// Read the color value at the current texture coordinate (tex)
|
||||
|
@ -130,7 +138,83 @@ float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
|
|||
|
||||
Once reloaded, it should show some retro raster bars in the background, with a drop shadow to make the text more readable.
|
||||
|
||||
![Rasterbars](Screenshots/TerminalRasterbars.PNG)
|
||||
|
||||
## Retro Terminal Effect
|
||||
|
||||
As a more complicated example, the Terminal's built-in `experimental.retroTerminalEffect` is included as the `Retro.hlsl` file in this directory. Feel free to modify and experiment!
|
||||
As a more complicated example, the Terminal's built-in `experimental.retroTerminalEffect` is included as the `Retro.hlsl` file in this directory.
|
||||
|
||||
![Retro](Screenshots/TerminalRetro.PNG)
|
||||
|
||||
## Animated Effects
|
||||
|
||||
You can use the `Time` value in the shader input settings to drive animated effects. `Time` is the number of seconds since the shader first loaded. Here’s a simple example with a line of inverted pixels that scrolls down the terminal (`Animate_scan.hlsl`):
|
||||
|
||||
```hlsl
|
||||
float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
|
||||
{
|
||||
// Read the color value at the current texture coordinate (tex)
|
||||
float4 color = shaderTexture.Sample(samplerState, tex);
|
||||
|
||||
// Here we spread the animation over 5 seconds. We use time modulo 5 because we want
|
||||
// the timer to count to five repeatedly. We then divide the result by five again
|
||||
// to get a value between 0.0 and 1.0, which maps to our texture coordinate.
|
||||
float linePosition = Time % 5 / 5;
|
||||
|
||||
// Since TEXCOORD ranges from 0.0 to 1.0, we need to divide 1.0 by the height of the
|
||||
// texture to find out the size of a single pixel
|
||||
float lineWidth = 1.0 / Resolution.y;
|
||||
|
||||
// If the current texture coordinate is in the range of our line on the Y axis:
|
||||
if (tex.y > linePosition - lineWidth && tex.y < linePosition)
|
||||
{
|
||||
// Invert the sampled color
|
||||
color.rgb = 1.0 - color.rgb;
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
```
|
||||
What if we want an animation that goes backwards and forwards? In this example (`Animate_breathe.hlsl`), we'll make the background fade between two colours. Our `Time` value only ever goes up, so we need a way to generate a value that sweeps back and forth from `0.0` to `1.0`. Trigonometric functions like cosine are perfect for this and are very frequently used in shaders.
|
||||
|
||||
`cos()` outputs a value between `-1.0` and `1.0`. We can adjust the wave with the following formula:
|
||||
|
||||
```
|
||||
a * cos(b * (x - c)) + d
|
||||
```
|
||||
|
||||
Where `a` adjusts the amplitude, `b` adjusts the wavelength/frequency, `c` adjusts the offset along the x axis, and `d` adjusts the offset along the y axis. You can use a graphing calculator (such as the Windows Calculator) to help visualize the output and experiment:
|
||||
|
||||
![Cosine](Screenshots/GraphCosine.png)
|
||||
|
||||
As shown above, by halving the output and then adding `0.5`, we can shift the range of the function to `0.0` - `1.0`. Because `cos()` takes input in radians, if we multiply `x` (`Time`) by tau (`2*pi`), we are effectively setting the wavelength to `1.0`.
|
||||
|
||||
In other words, our full animation will be one second long. We can modify this duration by dividing tau by the number of seconds we want the animation to run for. In this case, we’ll go for five seconds.
|
||||
|
||||
Finally we use linear interpolation to achieve our breathing effect by selecting a color between our two chosen colors based on the output from our cosine.
|
||||
|
||||
```hlsl
|
||||
// pi and tau (2 * pi) are useful constants when using trigonometric functions
|
||||
#define TAU 6.28318530718
|
||||
|
||||
float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
|
||||
{
|
||||
// Read the color value at the current texture coordinate (tex)
|
||||
float4 sample = shaderTexture.Sample(samplerState, tex);
|
||||
|
||||
// The number of seconds the breathing effect should span
|
||||
float duration = 5.0;
|
||||
|
||||
float3 color1 = float3(0.3, 0.0, 0.5); // indigo
|
||||
float3 color2 = float3(0.1, 0.1, 0.44); // midnight blue
|
||||
|
||||
// Set background colour based on the time
|
||||
float4 backgroundColor = float4(lerp(color1, color2, 0.5 * cos(TAU / duration * Time) + 0.5), 1.0);
|
||||
|
||||
// Draw the terminal graphics over the background
|
||||
return lerp(backgroundColor, sample, sample.w);
|
||||
}
|
||||
```
|
||||
|
||||
Feel free to modify and experiment!
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ SamplerState samplerState;
|
|||
|
||||
// Terminal settings such as the resolution of the texture
|
||||
cbuffer PixelShaderSettings {
|
||||
// Time since pixel shader was enabled
|
||||
// The number of seconds since the pixel shader was enabled
|
||||
float Time;
|
||||
// UI Scale
|
||||
float Scale;
|
||||
|
@ -16,8 +16,9 @@ cbuffer PixelShaderSettings {
|
|||
float4 Background;
|
||||
};
|
||||
|
||||
// A pixel shader is a program that given a texture coordinate (tex) produces a color
|
||||
// Just ignore the pos parameter
|
||||
// A pixel shader is a program that given a texture coordinate (tex) produces a color.
|
||||
// tex is an x,y tuple that ranges from 0,0 (top left) to 1,1 (bottom right).
|
||||
// Just ignore the pos parameter.
|
||||
float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
|
||||
{
|
||||
// Read the color value at the current texture coordinate (tex)
|
||||
|
|
BIN
samples/PixelShaders/Screenshots/GraphCosine.png
Normal file
After Width: | Height: | Size: 116 KiB |
BIN
samples/PixelShaders/Screenshots/TerminalDefault.PNG
Normal file
After Width: | Height: | Size: 444 KiB |
BIN
samples/PixelShaders/Screenshots/TerminalInvert.PNG
Normal file
After Width: | Height: | Size: 47 KiB |
BIN
samples/PixelShaders/Screenshots/TerminalRasterbars.PNG
Normal file
After Width: | Height: | Size: 50 KiB |
BIN
samples/PixelShaders/Screenshots/TerminalRetro.PNG
Normal file
After Width: | Height: | Size: 495 KiB |
|
@ -3,7 +3,6 @@
|
|||
<Import Project="..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
|
||||
<Import Project="$(OpenConsoleDir)src\wap-common.build.pre.props" />
|
||||
<PropertyGroup Label="Configuration">
|
||||
|
||||
<TargetPlatformVersion>10.0.18362.0</TargetPlatformVersion>
|
||||
<TargetPlatformMinVersion>10.0.18362.0</TargetPlatformMinVersion>
|
||||
<!--
|
||||
|
|
|
@ -35,6 +35,7 @@ namespace SettingsModelLocalTests
|
|||
TEST_METHOD(CanLayerColorScheme);
|
||||
TEST_METHOD(LayerColorSchemeProperties);
|
||||
TEST_METHOD(LayerColorSchemesOnArray);
|
||||
TEST_METHOD(UpdateSchemeReferences);
|
||||
|
||||
TEST_CLASS_SETUP(ClassSetup)
|
||||
{
|
||||
|
@ -290,4 +291,81 @@ namespace SettingsModelLocalTests
|
|||
VERIFY_ARE_EQUAL(ARGB(0, 7, 7, 7), scheme2->_Background);
|
||||
}
|
||||
}
|
||||
|
||||
void ColorSchemeTests::UpdateSchemeReferences()
|
||||
{
|
||||
const std::string settingsString{ R"json({
|
||||
"defaultProfile": "Inherited reference",
|
||||
"profiles": {
|
||||
"defaults": {
|
||||
"colorScheme": "Scheme 1"
|
||||
},
|
||||
"list": [
|
||||
{
|
||||
"name": "Explicit scheme reference",
|
||||
"colorScheme": "Scheme 1"
|
||||
},
|
||||
{
|
||||
"name": "Explicit reference; hidden",
|
||||
"colorScheme": "Scheme 1",
|
||||
"hidden": true
|
||||
},
|
||||
{
|
||||
"name": "Inherited reference"
|
||||
},
|
||||
{
|
||||
"name": "Different reference",
|
||||
"colorScheme": "Scheme 2"
|
||||
}
|
||||
]
|
||||
},
|
||||
"schemes": [
|
||||
{ "name": "Scheme 1" },
|
||||
{ "name": "Scheme 2" },
|
||||
{ "name": "Scheme 1 (renamed)" }
|
||||
]
|
||||
})json" };
|
||||
|
||||
auto settings{ winrt::make_self<CascadiaSettings>(false) };
|
||||
settings->_ParseJsonString(settingsString, false);
|
||||
settings->_ApplyDefaultsFromUserSettings();
|
||||
settings->LayerJson(settings->_userSettings);
|
||||
settings->_ValidateSettings();
|
||||
|
||||
// update all references to "Scheme 1"
|
||||
const auto newName{ L"Scheme 1 (renamed)" };
|
||||
settings->UpdateColorSchemeReferences(L"Scheme 1", newName);
|
||||
|
||||
// verify profile defaults
|
||||
Log::Comment(L"Profile Defaults");
|
||||
VERIFY_ARE_EQUAL(newName, settings->ProfileDefaults().ColorSchemeName());
|
||||
VERIFY_IS_TRUE(settings->ProfileDefaults().HasColorSchemeName());
|
||||
|
||||
// verify all other profiles
|
||||
const auto& profiles{ settings->AllProfiles() };
|
||||
{
|
||||
const auto& prof{ profiles.GetAt(0) };
|
||||
Log::Comment(prof.Name().c_str());
|
||||
VERIFY_ARE_EQUAL(newName, prof.ColorSchemeName());
|
||||
VERIFY_IS_TRUE(prof.HasColorSchemeName());
|
||||
}
|
||||
{
|
||||
const auto& prof{ profiles.GetAt(1) };
|
||||
Log::Comment(prof.Name().c_str());
|
||||
VERIFY_ARE_EQUAL(newName, prof.ColorSchemeName());
|
||||
VERIFY_IS_TRUE(prof.HasColorSchemeName());
|
||||
}
|
||||
{
|
||||
const auto& prof{ profiles.GetAt(2) };
|
||||
Log::Comment(prof.Name().c_str());
|
||||
VERIFY_ARE_EQUAL(newName, prof.ColorSchemeName());
|
||||
VERIFY_IS_FALSE(prof.HasColorSchemeName());
|
||||
}
|
||||
{
|
||||
const auto& prof{ profiles.GetAt(3) };
|
||||
Log::Comment(prof.Name().c_str());
|
||||
VERIFY_ARE_EQUAL(L"Scheme 2", prof.ColorSchemeName());
|
||||
VERIFY_IS_TRUE(prof.HasColorSchemeName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -226,6 +226,7 @@ namespace SettingsModelLocalTests
|
|||
const std::string settingsString{ R"({
|
||||
"$schema": "https://aka.ms/terminal-profiles-schema",
|
||||
"defaultProfile": "{61c54bbd-1111-5271-96e7-009a87ff44bf}",
|
||||
"disabledProfileSources": [ "Windows.Terminal.Wsl" ],
|
||||
|
||||
"profiles": {
|
||||
"defaults": {
|
||||
|
|
|
@ -1262,7 +1262,7 @@ namespace TerminalAppLocalTests
|
|||
void CommandlineTest::TestSimpleExecuteCommandlineAction()
|
||||
{
|
||||
ExecuteCommandlineArgs args{ L"new-tab" };
|
||||
auto actions = implementation::TerminalPage::ConvertExecuteCommandlineToActions(args);
|
||||
auto actions = winrt::TerminalApp::implementation::TerminalPage::ConvertExecuteCommandlineToActions(args);
|
||||
VERIFY_ARE_EQUAL(1u, actions.size());
|
||||
auto actionAndArgs = actions.at(0);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
|
||||
|
@ -1281,7 +1281,7 @@ namespace TerminalAppLocalTests
|
|||
void CommandlineTest::TestMultipleCommandExecuteCommandlineAction()
|
||||
{
|
||||
ExecuteCommandlineArgs args{ L"new-tab ; split-pane" };
|
||||
auto actions = implementation::TerminalPage::ConvertExecuteCommandlineToActions(args);
|
||||
auto actions = winrt::TerminalApp::implementation::TerminalPage::ConvertExecuteCommandlineToActions(args);
|
||||
VERIFY_ARE_EQUAL(2u, actions.size());
|
||||
{
|
||||
auto actionAndArgs = actions.at(0);
|
||||
|
@ -1317,7 +1317,7 @@ namespace TerminalAppLocalTests
|
|||
{
|
||||
// -H and -V cannot be combined.
|
||||
ExecuteCommandlineArgs args{ L"split-pane -H -V" };
|
||||
auto actions = implementation::TerminalPage::ConvertExecuteCommandlineToActions(args);
|
||||
auto actions = winrt::TerminalApp::implementation::TerminalPage::ConvertExecuteCommandlineToActions(args);
|
||||
VERIFY_ARE_EQUAL(0u, actions.size());
|
||||
}
|
||||
|
||||
|
|
|
@ -899,10 +899,10 @@ namespace TerminalAppLocalTests
|
|||
page->_SelectNextTab(true);
|
||||
});
|
||||
|
||||
const auto palette = winrt::get_self<implementation::CommandPalette>(page->CommandPalette());
|
||||
const auto palette = winrt::get_self<winrt::TerminalApp::implementation::CommandPalette>(page->CommandPalette());
|
||||
|
||||
VERIFY_ARE_EQUAL(1u, palette->_switcherStartIdx, L"Verify the index is 1 as we went right");
|
||||
VERIFY_ARE_EQUAL(implementation::CommandPaletteMode::TabSwitchMode, palette->_currentMode, L"Verify we are in the tab switcher mode");
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::implementation::CommandPaletteMode::TabSwitchMode, palette->_currentMode, L"Verify we are in the tab switcher mode");
|
||||
|
||||
Log::Comment(L"Verify command palette preserves MRU order of tabs");
|
||||
VERIFY_ARE_EQUAL(4u, palette->_filteredActions.Size());
|
||||
|
|
|
@ -13,12 +13,12 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
// It must be defined after CommandlineArgs.g.cpp, otherwise the compiler
|
||||
// will give you just the most impossible template errors to try and
|
||||
// decipher.
|
||||
void CommandlineArgs::Args(winrt::array_view<const winrt::hstring> const& value)
|
||||
void CommandlineArgs::Commandline(winrt::array_view<const winrt::hstring> const& value)
|
||||
{
|
||||
_args = { value.begin(), value.end() };
|
||||
}
|
||||
|
||||
winrt::com_array<winrt::hstring> CommandlineArgs::Args()
|
||||
winrt::com_array<winrt::hstring> CommandlineArgs::Commandline()
|
||||
{
|
||||
return winrt::com_array<winrt::hstring>{ _args.begin(), _args.end() };
|
||||
}
|
||||
|
|
|
@ -23,8 +23,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
|
||||
winrt::hstring CurrentDirectory() { return _cwd; };
|
||||
|
||||
void Args(winrt::array_view<const winrt::hstring> const& value);
|
||||
winrt::com_array<winrt::hstring> Args();
|
||||
void Commandline(winrt::array_view<const winrt::hstring> const& value);
|
||||
winrt::com_array<winrt::hstring> Commandline();
|
||||
|
||||
private:
|
||||
winrt::com_array<winrt::hstring> _args;
|
||||
|
|
5
src/cascadia/Remoting/FindTargetWindowArgs.cpp
Normal file
|
@ -0,0 +1,5 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
#include "pch.h"
|
||||
#include "FindTargetWindowArgs.h"
|
||||
#include "FindTargetWindowArgs.g.cpp"
|
35
src/cascadia/Remoting/FindTargetWindowArgs.h
Normal file
|
@ -0,0 +1,35 @@
|
|||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Class Name:
|
||||
- FindTargetWindowArgs.h
|
||||
|
||||
Abstract:
|
||||
- This is a helper class for determining which window a specific commandline is
|
||||
intended for. The Monarch will create one of these, then toss it over to
|
||||
TerminalApp. TerminalApp actually contains the logic for parsing a
|
||||
commandline, as well as settings like the windowing behavior. Once the
|
||||
TerminalApp determines the correct window, it'll fill in the
|
||||
ResultTargetWindow property. The monarch will then read that value out to
|
||||
invoke the commandline in the appropriate window.
|
||||
|
||||
--*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "FindTargetWindowArgs.g.h"
|
||||
#include "../cascadia/inc/cppwinrt_utils.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
{
|
||||
struct FindTargetWindowArgs : public FindTargetWindowArgsT<FindTargetWindowArgs>
|
||||
{
|
||||
GETSET_PROPERTY(winrt::Microsoft::Terminal::Remoting::CommandlineArgs, Args, nullptr);
|
||||
GETSET_PROPERTY(int, ResultTargetWindow, -1);
|
||||
|
||||
public:
|
||||
FindTargetWindowArgs(winrt::Microsoft::Terminal::Remoting::CommandlineArgs args) :
|
||||
_Args{ args } {};
|
||||
};
|
||||
}
|
|
@ -19,6 +19,15 @@
|
|||
<ClInclude Include="Monarch.h">
|
||||
<DependentUpon>Monarch.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="FindTargetWindowArgs.h">
|
||||
<DependentUpon>Monarch.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ProposeCommandlineResult.h">
|
||||
<DependentUpon>Monarch.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="WindowActivatedArgs.h">
|
||||
<DependentUpon>Peasant.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="MonarchFactory.h" />
|
||||
<ClInclude Include="Peasant.h">
|
||||
|
@ -36,6 +45,15 @@
|
|||
<ClCompile Include="Monarch.cpp">
|
||||
<DependentUpon>Monarch.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="FindTargetWindowArgs.cpp">
|
||||
<DependentUpon>Monarch.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ProposeCommandlineResult.cpp">
|
||||
<DependentUpon>Monarch.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="WindowActivatedArgs.cpp">
|
||||
<DependentUpon>Peasant.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
|
@ -49,6 +67,7 @@
|
|||
<DependentUpon>Peasant.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="$(GeneratedFilesDir)module.g.cpp" />
|
||||
<ClCompile Include="init.cpp" />
|
||||
</ItemGroup>
|
||||
<!-- ========================= idl Files ======================== -->
|
||||
<ItemGroup>
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
#include "pch.h"
|
||||
#include "Monarch.h"
|
||||
#include "CommandlineArgs.h"
|
||||
#include "FindTargetWindowArgs.h"
|
||||
#include "ProposeCommandlineResult.h"
|
||||
|
||||
#include "Monarch.g.cpp"
|
||||
#include "../../types/inc/utils.hpp"
|
||||
|
@ -44,33 +46,48 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
// - the ID assigned to the peasant.
|
||||
uint64_t Monarch::AddPeasant(Remoting::IPeasant peasant)
|
||||
{
|
||||
// TODO:projects/5 This is terrible. There's gotta be a better way
|
||||
// of finding the first opening in a non-consecutive map of int->object
|
||||
const auto providedID = peasant.GetID();
|
||||
|
||||
if (providedID == 0)
|
||||
try
|
||||
{
|
||||
// Peasant doesn't currently have an ID. Assign it a new one.
|
||||
peasant.AssignID(_nextPeasantID++);
|
||||
// TODO:projects/5 This is terrible. There's gotta be a better way
|
||||
// of finding the first opening in a non-consecutive map of int->object
|
||||
const auto providedID = peasant.GetID();
|
||||
|
||||
if (providedID == 0)
|
||||
{
|
||||
// Peasant doesn't currently have an ID. Assign it a new one.
|
||||
peasant.AssignID(_nextPeasantID++);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Peasant already had an ID (from an older monarch). Leave that one
|
||||
// be. Make sure that the next peasant's ID is higher than it.
|
||||
_nextPeasantID = providedID >= _nextPeasantID ? providedID + 1 : _nextPeasantID;
|
||||
}
|
||||
|
||||
auto newPeasantsId = peasant.GetID();
|
||||
// Add an event listener to the peasant's WindowActivated event.
|
||||
peasant.WindowActivated({ this, &Monarch::_peasantWindowActivated });
|
||||
|
||||
_peasants[newPeasantsId] = peasant;
|
||||
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Monarch_AddPeasant",
|
||||
TraceLoggingUInt64(providedID, "providedID", "the provided ID for the peasant"),
|
||||
TraceLoggingUInt64(newPeasantsId, "peasantID", "the ID of the new peasant"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
return newPeasantsId;
|
||||
}
|
||||
else
|
||||
catch (...)
|
||||
{
|
||||
// Peasant already had an ID (from an older monarch). Leave that one
|
||||
// be. Make sure that the next peasant's ID is higher than it.
|
||||
_nextPeasantID = providedID >= _nextPeasantID ? providedID + 1 : _nextPeasantID;
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Monarch_AddPeasant_Failed",
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
|
||||
// We can only get into this try/catch if the peasant died on us. So
|
||||
// the return value doesn't _really_ matter. They're not about to
|
||||
// get it.
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto newPeasantsId = peasant.GetID();
|
||||
_peasants[newPeasantsId] = peasant;
|
||||
|
||||
// Add an event listener to the peasant's WindowActivated event.
|
||||
peasant.WindowActivated({ this, &Monarch::_peasantWindowActivated });
|
||||
|
||||
// TODO:projects/5 Wait on the peasant's PID, and remove them from the
|
||||
// map if they die. This won't work great in tests though, with fake
|
||||
// PIDs.
|
||||
|
||||
return newPeasantsId;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
@ -79,19 +96,13 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
// window".
|
||||
// Arguments:
|
||||
// - sender: the Peasant that raised this event. This might be out-of-proc!
|
||||
// - args: a bundle of the peasant ID, timestamp, and desktop ID, for the activated peasant
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Monarch::_peasantWindowActivated(const winrt::Windows::Foundation::IInspectable& sender,
|
||||
const winrt::Windows::Foundation::IInspectable& /*args*/)
|
||||
void Monarch::_peasantWindowActivated(const winrt::Windows::Foundation::IInspectable& /*sender*/,
|
||||
const Remoting::WindowActivatedArgs& args)
|
||||
{
|
||||
// TODO:projects/5 Pass the desktop and timestamp of when the window was
|
||||
// activated in `args`.
|
||||
|
||||
if (auto peasant{ sender.try_as<Remoting::Peasant>() })
|
||||
{
|
||||
auto theirID = peasant.GetID();
|
||||
_setMostRecentPeasant(theirID);
|
||||
}
|
||||
HandleActivatePeasant(args);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
@ -102,17 +113,81 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
// - the peasant if it exists in our map, otherwise null
|
||||
Remoting::IPeasant Monarch::_getPeasant(uint64_t peasantID)
|
||||
{
|
||||
auto peasantSearch = _peasants.find(peasantID);
|
||||
return peasantSearch == _peasants.end() ? nullptr : peasantSearch->second;
|
||||
try
|
||||
{
|
||||
const auto peasantSearch = _peasants.find(peasantID);
|
||||
auto maybeThePeasant = peasantSearch == _peasants.end() ? nullptr : peasantSearch->second;
|
||||
// Ask the peasant for their PID. This will validate that they're
|
||||
// actually still alive.
|
||||
if (maybeThePeasant)
|
||||
{
|
||||
maybeThePeasant.GetPID();
|
||||
}
|
||||
return maybeThePeasant;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_CAUGHT_EXCEPTION();
|
||||
// Remove the peasant from the list of peasants
|
||||
_peasants.erase(peasantID);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void Monarch::_setMostRecentPeasant(const uint64_t peasantID)
|
||||
void Monarch::HandleActivatePeasant(const Remoting::WindowActivatedArgs& args)
|
||||
{
|
||||
// TODO:projects/5 Use a heap/priority queue per-desktop to track which
|
||||
// peasant was the most recent per-desktop. When we want to get the most
|
||||
// recent of all desktops (WindowingBehavior::UseExisting), then use the
|
||||
// most recent of all desktops.
|
||||
_mostRecentPeasant = peasantID;
|
||||
const auto oldLastActiveTime = _lastActivatedTime.time_since_epoch().count();
|
||||
const auto newLastActiveTime = args.ActivatedTime().time_since_epoch().count();
|
||||
|
||||
// For now, we'll just pay attention to whoever the most recent peasant
|
||||
// was. We're not too worried about the mru peasant dying. Worst case -
|
||||
// when the user executes a `wt -w 0`, we won't be able to find that
|
||||
// peasant, and it'll open in a new window instead of the current one.
|
||||
if (args.ActivatedTime() > _lastActivatedTime)
|
||||
{
|
||||
_mostRecentPeasant = args.PeasantID();
|
||||
_lastActivatedTime = args.ActivatedTime();
|
||||
}
|
||||
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Monarch_SetMostRecentPeasant",
|
||||
TraceLoggingUInt64(args.PeasantID(), "peasantID", "the ID of the activated peasant"),
|
||||
TraceLoggingInt64(oldLastActiveTime, "oldLastActiveTime", "The previous lastActiveTime"),
|
||||
TraceLoggingInt64(newLastActiveTime, "newLastActiveTime", "The provided args.ActivatedTime()"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
}
|
||||
|
||||
uint64_t Monarch::_getMostRecentPeasantID()
|
||||
{
|
||||
if (_mostRecentPeasant != 0)
|
||||
{
|
||||
return _mostRecentPeasant;
|
||||
}
|
||||
|
||||
// We haven't yet been told the MRU peasant. Just use the first one.
|
||||
// This is just gonna be a random one, but really shouldn't happen
|
||||
// in practice. The WindowManager should set the MRU peasant
|
||||
// immediately as soon as it creates the monarch/peasant for the
|
||||
// first window.
|
||||
if (_peasants.size() > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
return _peasants.begin()->second.GetID();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// This shouldn't really happen. If we're the monarch, then the
|
||||
// first peasant should also _be us_. So we should be able to
|
||||
// get our own ID.
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
@ -122,16 +197,91 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
bool Monarch::ProposeCommandline(const Remoting::CommandlineArgs& /*args*/)
|
||||
// - true if the caller should create a new window for this commandline.
|
||||
// False otherwise - the monarch should have dispatched this commandline
|
||||
// to another window in this case.
|
||||
Remoting::ProposeCommandlineResult Monarch::ProposeCommandline(const Remoting::CommandlineArgs& args)
|
||||
{
|
||||
// TODO:projects/5
|
||||
// The branch dev/migrie/f/remote-commandlines has a more complete
|
||||
// version of this function, with a naive implementation. For now, we
|
||||
// always want to create a new window, so we'll just return true. This
|
||||
// will tell the caller that we didn't handle the commandline, and they
|
||||
// should open a new window to deal with it themselves.
|
||||
return true;
|
||||
// Raise an event, to ask how to handle this commandline. We can't ask
|
||||
// the app ourselves - we exist isolated from that knowledge (and
|
||||
// dependency hell). The WindowManager will raise this up to the app
|
||||
// host, which will then ask the AppLogic, who will then parse the
|
||||
// commandline and determine the provided ID of the window.
|
||||
auto findWindowArgs{ winrt::make_self<Remoting::implementation::FindTargetWindowArgs>(args) };
|
||||
|
||||
// This is handled by some handler in-proc
|
||||
_FindTargetWindowRequestedHandlers(*this, *findWindowArgs);
|
||||
|
||||
// After the event was handled, ResultTargetWindow() will be filled with
|
||||
// the parsed result.
|
||||
const auto targetWindow = findWindowArgs->ResultTargetWindow();
|
||||
|
||||
// If there's a valid ID returned, then let's try and find the peasant that goes with it.
|
||||
if (targetWindow >= 0)
|
||||
{
|
||||
uint64_t windowID = ::base::saturated_cast<uint64_t>(targetWindow);
|
||||
|
||||
if (windowID == 0)
|
||||
{
|
||||
windowID = _getMostRecentPeasantID();
|
||||
}
|
||||
|
||||
if (auto targetPeasant{ _getPeasant(windowID) })
|
||||
{
|
||||
auto result{ winrt::make_self<Remoting::implementation::ProposeCommandlineResult>(false) };
|
||||
|
||||
try
|
||||
{
|
||||
// This will raise the peasant's ExecuteCommandlineRequested
|
||||
// event, which will then ask the AppHost to handle the
|
||||
// commandline, which will then pass it to AppLogic for
|
||||
// handling.
|
||||
targetPeasant.ExecuteCommandline(args);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// If we fail to propose the commandline to the peasant (it
|
||||
// died?) then just tell this process to become a new window
|
||||
// instead.
|
||||
result->ShouldCreateWindow(true);
|
||||
|
||||
// If this fails, it'll be logged in the following
|
||||
// TraceLoggingWrite statement, with succeeded=false
|
||||
}
|
||||
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Monarch_ProposeCommandline_Existing",
|
||||
TraceLoggingUInt64(windowID, "peasantID", "the ID of the peasant the commandline waws intended for"),
|
||||
TraceLoggingBoolean(true, "foundMatch", "true if we found a peasant with that ID"),
|
||||
TraceLoggingBoolean(!result->ShouldCreateWindow(), "succeeded", "true if we successfully dispatched the commandline to the peasant"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
return *result;
|
||||
}
|
||||
else if (windowID > 0)
|
||||
{
|
||||
// In this case, an ID was provided, but there's no
|
||||
// peasant with that ID. Instead, we should tell the caller that
|
||||
// they should make a new window, but _with that ID_.
|
||||
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Monarch_ProposeCommandline_Existing",
|
||||
TraceLoggingUInt64(windowID, "peasantID", "the ID of the peasant the commandline waws intended for"),
|
||||
TraceLoggingBoolean(false, "foundMatch", "true if we found a peasant with that ID"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
|
||||
auto result{ winrt::make_self<Remoting::implementation::ProposeCommandlineResult>(true) };
|
||||
result->Id(windowID);
|
||||
return *result;
|
||||
}
|
||||
}
|
||||
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Monarch_ProposeCommandline_NewWindow",
|
||||
TraceLoggingInt64(targetWindow, "targetWindow", "The provided ID"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
|
||||
// In this case, no usable ID was provided. Return { true, nullopt }
|
||||
return winrt::make<Remoting::implementation::ProposeCommandlineResult>(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -52,7 +52,10 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
|
||||
uint64_t AddPeasant(winrt::Microsoft::Terminal::Remoting::IPeasant peasant);
|
||||
|
||||
bool ProposeCommandline(const winrt::Microsoft::Terminal::Remoting::CommandlineArgs& args);
|
||||
winrt::Microsoft::Terminal::Remoting::ProposeCommandlineResult ProposeCommandline(const winrt::Microsoft::Terminal::Remoting::CommandlineArgs& args);
|
||||
void HandleActivatePeasant(const winrt::Microsoft::Terminal::Remoting::WindowActivatedArgs& args);
|
||||
|
||||
TYPED_EVENT(FindTargetWindowRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs);
|
||||
|
||||
private:
|
||||
Monarch(const uint64_t testPID);
|
||||
|
@ -61,14 +64,16 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
uint64_t _nextPeasantID{ 1 };
|
||||
uint64_t _thisPeasantID{ 0 };
|
||||
uint64_t _mostRecentPeasant{ 0 };
|
||||
winrt::Windows::Foundation::DateTime _lastActivatedTime{};
|
||||
|
||||
WindowingBehavior _windowingBehavior{ WindowingBehavior::UseNew };
|
||||
std::unordered_map<uint64_t, winrt::Microsoft::Terminal::Remoting::IPeasant> _peasants;
|
||||
|
||||
winrt::Microsoft::Terminal::Remoting::IPeasant _getPeasant(uint64_t peasantID);
|
||||
void _setMostRecentPeasant(const uint64_t peasantID);
|
||||
uint64_t _getMostRecentPeasantID();
|
||||
|
||||
void _peasantWindowActivated(const winrt::Windows::Foundation::IInspectable& sender,
|
||||
const winrt::Windows::Foundation::IInspectable& args);
|
||||
const winrt::Microsoft::Terminal::Remoting::WindowActivatedArgs& args);
|
||||
|
||||
friend class RemotingUnitTests::RemotingTests;
|
||||
};
|
||||
|
|
|
@ -5,11 +5,26 @@ import "Peasant.idl";
|
|||
|
||||
namespace Microsoft.Terminal.Remoting
|
||||
{
|
||||
|
||||
[default_interface] runtimeclass FindTargetWindowArgs {
|
||||
CommandlineArgs Args { get; };
|
||||
Int32 ResultTargetWindow;
|
||||
}
|
||||
|
||||
[default_interface] runtimeclass ProposeCommandlineResult {
|
||||
Windows.Foundation.IReference<UInt64> Id { get; };
|
||||
// TODO:projects/5 - also return the name here, if the name was set on the commandline
|
||||
Boolean ShouldCreateWindow { get; }; // If you name this `CreateWindow`, the compiler will explode
|
||||
}
|
||||
|
||||
[default_interface] runtimeclass Monarch {
|
||||
Monarch();
|
||||
|
||||
UInt64 GetPID();
|
||||
UInt64 AddPeasant(IPeasant peasant);
|
||||
Boolean ProposeCommandline(CommandlineArgs args);
|
||||
ProposeCommandlineResult ProposeCommandline(CommandlineArgs args);
|
||||
void HandleActivatePeasant(WindowActivatedArgs args);
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<Object, FindTargetWindowArgs> FindTargetWindowRequested;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -50,6 +50,12 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
_initialArgs = args;
|
||||
}
|
||||
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Peasant_ExecuteCommandline",
|
||||
TraceLoggingUInt64(GetID(), "peasantID", "Our ID"),
|
||||
TraceLoggingWideString(args.CurrentDirectory().c_str(), "directory", "the provided cwd"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
|
||||
// Raise an event with these args. The AppHost will listen for this
|
||||
// event to know when to take these args and dispatch them to a
|
||||
// currently-running window.
|
||||
|
@ -63,4 +69,52 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
return _initialArgs;
|
||||
}
|
||||
|
||||
void Peasant::ActivateWindow(const Remoting::WindowActivatedArgs& args)
|
||||
{
|
||||
// TODO: projects/5 - somehow, pass an identifier for the current
|
||||
// desktop into this method. The Peasant shouldn't need to be able to
|
||||
// figure it out, but it will need to report it to the monarch.
|
||||
|
||||
// Store these new args as our last activated state. If a new monarch
|
||||
// comes looking, we can use this info to tell them when we were last
|
||||
// activated.
|
||||
_lastActivatedArgs = args;
|
||||
|
||||
bool successfullyNotified = false;
|
||||
// Raise our WindowActivated event, to let the monarch know we've been
|
||||
// activated.
|
||||
try
|
||||
{
|
||||
// Try/catch this, because the other side of this event is handled
|
||||
// by the monarch. The monarch might have died. If they have, this
|
||||
// will throw an exception. Just eat it, the election thread will
|
||||
// handle hooking up the new one.
|
||||
_WindowActivatedHandlers(*this, args);
|
||||
successfullyNotified = true;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_CAUGHT_EXCEPTION();
|
||||
}
|
||||
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Peasant_ActivateWindow",
|
||||
TraceLoggingUInt64(GetID(), "peasantID", "Our ID"),
|
||||
TraceLoggingBoolean(successfullyNotified, "successfullyNotified", "true if we successfully notified the monarch"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Retrieve the WindowActivatedArgs describing the last activation of this
|
||||
// peasant. New monarchs can use this state to determine when we were last
|
||||
// activated.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - a WindowActivatedArgs with info about when and where we were last activated.
|
||||
Remoting::WindowActivatedArgs Peasant::GetLastActivatedArgs()
|
||||
{
|
||||
return _lastActivatedArgs;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -21,9 +21,12 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
uint64_t GetPID();
|
||||
|
||||
bool ExecuteCommandline(const winrt::Microsoft::Terminal::Remoting::CommandlineArgs& args);
|
||||
void ActivateWindow(const winrt::Microsoft::Terminal::Remoting::WindowActivatedArgs& args);
|
||||
|
||||
winrt::Microsoft::Terminal::Remoting::WindowActivatedArgs GetLastActivatedArgs();
|
||||
|
||||
winrt::Microsoft::Terminal::Remoting::CommandlineArgs InitialArgs();
|
||||
TYPED_EVENT(WindowActivated, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
|
||||
TYPED_EVENT(WindowActivated, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::WindowActivatedArgs);
|
||||
TYPED_EVENT(ExecuteCommandlineRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::CommandlineArgs);
|
||||
|
||||
private:
|
||||
|
@ -33,6 +36,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
uint64_t _id{ 0 };
|
||||
|
||||
winrt::Microsoft::Terminal::Remoting::CommandlineArgs _initialArgs{ nullptr };
|
||||
winrt::Microsoft::Terminal::Remoting::WindowActivatedArgs _lastActivatedArgs{ nullptr };
|
||||
|
||||
friend class RemotingUnitTests::RemotingTests;
|
||||
};
|
||||
|
|
|
@ -9,10 +9,18 @@ namespace Microsoft.Terminal.Remoting
|
|||
CommandlineArgs();
|
||||
CommandlineArgs(String[] args, String cwd);
|
||||
|
||||
String[] Args { get; set; };
|
||||
String[] Commandline { get; set; };
|
||||
String CurrentDirectory();
|
||||
};
|
||||
|
||||
runtimeclass WindowActivatedArgs
|
||||
{
|
||||
WindowActivatedArgs(UInt64 peasantID, Guid desktopID, Windows.Foundation.DateTime activatedTime);
|
||||
UInt64 PeasantID { get; };
|
||||
Guid DesktopID { get; };
|
||||
Windows.Foundation.DateTime ActivatedTime { get; };
|
||||
};
|
||||
|
||||
interface IPeasant
|
||||
{
|
||||
CommandlineArgs InitialArgs { get; };
|
||||
|
@ -21,7 +29,10 @@ namespace Microsoft.Terminal.Remoting
|
|||
UInt64 GetID();
|
||||
UInt64 GetPID();
|
||||
Boolean ExecuteCommandline(CommandlineArgs args);
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> WindowActivated;
|
||||
void ActivateWindow(WindowActivatedArgs args);
|
||||
WindowActivatedArgs GetLastActivatedArgs();
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<Object, WindowActivatedArgs> WindowActivated;
|
||||
event Windows.Foundation.TypedEventHandler<Object, CommandlineArgs> ExecuteCommandlineRequested;
|
||||
};
|
||||
|
||||
|
|
5
src/cascadia/Remoting/ProposeCommandlineResult.cpp
Normal file
|
@ -0,0 +1,5 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
#include "pch.h"
|
||||
#include "ProposeCommandlineResult.h"
|
||||
#include "ProposeCommandlineResult.g.cpp"
|
36
src/cascadia/Remoting/ProposeCommandlineResult.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Class Name:
|
||||
- ProposeCommandlineResult.h
|
||||
|
||||
Abstract:
|
||||
- This is a helper class for encapsulating the result of a
|
||||
Monarch::ProposeCommandline call. The monarch will be telling the new process
|
||||
whether it should create a new window or not. If the value of
|
||||
ShouldCreateWindow is false, that implies that some other window process was
|
||||
given the commandline for handling, and the caller should just exit.
|
||||
- If ShouldCreateWindow is true, the Id property may or may not contain an ID
|
||||
that the new window should use as it's ID.
|
||||
|
||||
--*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ProposeCommandlineResult.g.h"
|
||||
#include "../cascadia/inc/cppwinrt_utils.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
{
|
||||
struct ProposeCommandlineResult : public ProposeCommandlineResultT<ProposeCommandlineResult>
|
||||
{
|
||||
public:
|
||||
GETSET_PROPERTY(Windows::Foundation::IReference<uint64_t>, Id);
|
||||
GETSET_PROPERTY(bool, ShouldCreateWindow, true);
|
||||
|
||||
public:
|
||||
ProposeCommandlineResult(bool shouldCreateWindow) :
|
||||
_ShouldCreateWindow{ shouldCreateWindow } {};
|
||||
};
|
||||
}
|
5
src/cascadia/Remoting/WindowActivatedArgs.cpp
Normal file
|
@ -0,0 +1,5 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
#include "pch.h"
|
||||
#include "WindowActivatedArgs.h"
|
||||
#include "WindowActivatedArgs.g.cpp"
|
38
src/cascadia/Remoting/WindowActivatedArgs.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Class Name:
|
||||
- WindowActivatedArgs.h
|
||||
|
||||
Abstract:
|
||||
- This is a helper class for encapsulating all the information about when and
|
||||
where a window was activated. This will be used by the Monarch to determine
|
||||
who the most recent peasant is.
|
||||
|
||||
--*/
|
||||
#pragma once
|
||||
|
||||
#include "WindowActivatedArgs.g.h"
|
||||
#include "../cascadia/inc/cppwinrt_utils.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
{
|
||||
struct WindowActivatedArgs : public WindowActivatedArgsT<WindowActivatedArgs>
|
||||
{
|
||||
GETSET_PROPERTY(uint64_t, PeasantID, 0);
|
||||
GETSET_PROPERTY(winrt::guid, DesktopID, {});
|
||||
GETSET_PROPERTY(winrt::Windows::Foundation::DateTime, ActivatedTime, {});
|
||||
|
||||
public:
|
||||
WindowActivatedArgs(uint64_t peasantID, winrt::guid desktopID, winrt::Windows::Foundation::DateTime timestamp) :
|
||||
_PeasantID{ peasantID },
|
||||
_DesktopID{ desktopID },
|
||||
_ActivatedTime{ timestamp } {};
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Remoting::factory_implementation
|
||||
{
|
||||
BASIC_FACTORY(WindowActivatedArgs);
|
||||
}
|
|
@ -18,10 +18,29 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
{
|
||||
WindowManager::WindowManager()
|
||||
{
|
||||
_monarchWaitInterrupt.create();
|
||||
|
||||
// Register with COM as a server for the Monarch class
|
||||
_registerAsMonarch();
|
||||
// Instantiate an instance of the Monarch. This may or may not be in-proc!
|
||||
_createMonarch();
|
||||
bool foundMonarch = false;
|
||||
while (!foundMonarch)
|
||||
{
|
||||
try
|
||||
{
|
||||
_createMonarchAndCallbacks();
|
||||
// _createMonarchAndCallbacks will initialize _isKing
|
||||
foundMonarch = true;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// If we fail to find the monarch,
|
||||
// stay in this jail until we do.
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_ExceptionInCtor",
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WindowManager::~WindowManager()
|
||||
|
@ -32,23 +51,82 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
// monarch!
|
||||
CoRevokeClassObject(_registrationHostClass);
|
||||
_registrationHostClass = 0;
|
||||
_monarchWaitInterrupt.SetEvent();
|
||||
|
||||
// A thread is joinable once it's been started. Basically this just
|
||||
// makes sure that the thread isn't just default-constructed.
|
||||
if (_electionThread.joinable())
|
||||
{
|
||||
_electionThread.join();
|
||||
}
|
||||
}
|
||||
|
||||
void WindowManager::ProposeCommandline(const Remoting::CommandlineArgs& args)
|
||||
{
|
||||
const bool isKing = _areWeTheKing();
|
||||
// If we're the king, we _definitely_ want to process the arguments, we were
|
||||
// launched with them!
|
||||
//
|
||||
// Otherwise, the King will tell us if we should make a new window
|
||||
_shouldCreateWindow = isKing ||
|
||||
_monarch.ProposeCommandline(args);
|
||||
_shouldCreateWindow = _isKing;
|
||||
std::optional<uint64_t> givenID;
|
||||
if (!_isKing)
|
||||
{
|
||||
// The monarch may respond back "you should be a new
|
||||
// window, with ID,name of (id, name)". Really the responses are:
|
||||
// * You should not create a new window
|
||||
// * Create a new window (but without a given ID or name). The
|
||||
// Monarch will assign your ID/name later
|
||||
// * Create a new window, and you'll have this ID or name
|
||||
// - This is the case where the user provides `wt -w 1`, and
|
||||
// there's no existing window 1
|
||||
|
||||
const auto result = _monarch.ProposeCommandline(args);
|
||||
_shouldCreateWindow = result.ShouldCreateWindow();
|
||||
if (result.Id())
|
||||
{
|
||||
givenID = result.Id().Value();
|
||||
}
|
||||
|
||||
// TraceLogging doesn't have a good solution for logging an
|
||||
// optional. So we have to repeat the calls here:
|
||||
if (givenID)
|
||||
{
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_ProposeCommandline",
|
||||
TraceLoggingBoolean(_shouldCreateWindow, "CreateWindow", "true iff we should create a new window"),
|
||||
TraceLoggingUInt64(givenID.value(), "Id", "The ID we should assign our peasant"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
}
|
||||
else
|
||||
{
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_ProposeCommandline",
|
||||
TraceLoggingBoolean(_shouldCreateWindow, "CreateWindow", "true iff we should create a new window"),
|
||||
TraceLoggingPointer(nullptr, "Id", "No ID provided"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// We're the monarch, we don't need to propose anything. We're just
|
||||
// going to do it.
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_ProposeCommandline_AsMonarch",
|
||||
TraceLoggingBoolean(_shouldCreateWindow, "CreateWindow", "true iff we should create a new window"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
}
|
||||
|
||||
if (_shouldCreateWindow)
|
||||
{
|
||||
// If we should create a new window, then instantiate our Peasant
|
||||
// instance, and tell that peasant to handle that commandline.
|
||||
_createOurPeasant();
|
||||
_createOurPeasant({ givenID });
|
||||
|
||||
// Spawn a thread to wait on the monarch, and handle the election
|
||||
if (!_isKing)
|
||||
{
|
||||
_createPeasantThread();
|
||||
}
|
||||
|
||||
_peasant.ExecuteCommandline(args);
|
||||
}
|
||||
|
@ -83,27 +161,269 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
CLSCTX_LOCAL_SERVER);
|
||||
}
|
||||
|
||||
// NOTE: This can throw! Callers include:
|
||||
// - the constructor, who performs this in a loop until it successfully
|
||||
// find a a monarch
|
||||
// - the performElection method, which is called in the waitOnMonarch
|
||||
// thread. All the calls in that thread are wrapped in try/catch's
|
||||
// already.
|
||||
// - _createOurPeasant, who might do this in a loop to establish us with the
|
||||
// monarch.
|
||||
void WindowManager::_createMonarchAndCallbacks()
|
||||
{
|
||||
_createMonarch();
|
||||
// Save the result of checking if we're the king. We want to avoid
|
||||
// unnecessary calls back and forth if we can.
|
||||
_isKing = _areWeTheKing();
|
||||
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_ConnectedToMonarch",
|
||||
TraceLoggingUInt64(_monarch.GetPID(), "monarchPID", "The PID of the new Monarch"),
|
||||
TraceLoggingBoolean(_isKing, "isKing", "true if we are the new monarch"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
|
||||
if (_peasant)
|
||||
{
|
||||
// Inform the monarch of the time we were last activated
|
||||
_monarch.HandleActivatePeasant(_peasant.GetLastActivatedArgs());
|
||||
}
|
||||
|
||||
if (!_isKing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Here, we're the king!
|
||||
//
|
||||
// This is where you should do any additional setup that might need to be
|
||||
// done when we become the king. THis will be called both for the first
|
||||
// window, and when the current monarch dies.
|
||||
|
||||
_monarch.FindTargetWindowRequested({ this, &WindowManager::_raiseFindTargetWindowRequested });
|
||||
}
|
||||
|
||||
bool WindowManager::_areWeTheKing()
|
||||
{
|
||||
const auto kingPID{ _monarch.GetPID() };
|
||||
const auto ourPID{ GetCurrentProcessId() };
|
||||
const auto kingPID{ _monarch.GetPID() };
|
||||
return (ourPID == kingPID);
|
||||
}
|
||||
|
||||
Remoting::IPeasant WindowManager::_createOurPeasant()
|
||||
Remoting::IPeasant WindowManager::_createOurPeasant(std::optional<uint64_t> givenID)
|
||||
{
|
||||
auto p = winrt::make_self<Remoting::implementation::Peasant>();
|
||||
if (givenID)
|
||||
{
|
||||
p->AssignID(givenID.value());
|
||||
}
|
||||
_peasant = *p;
|
||||
_monarch.AddPeasant(_peasant);
|
||||
|
||||
// TODO:projects/5 Spawn a thread to wait on the monarch, and handle the election
|
||||
// Try to add us to the monarch. If that fails, try to find a monarch
|
||||
// again, until we find one (we will eventually find us)
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
_monarch.AddPeasant(_peasant);
|
||||
break;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Wrap this in it's own try/catch, because this can throw.
|
||||
_createMonarchAndCallbacks();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_CreateOurPeasant",
|
||||
TraceLoggingUInt64(_peasant.GetID(), "peasantID", "The ID of our new peasant"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
|
||||
return _peasant;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Attempt to connect to the monarch process. This might be us!
|
||||
// - For the new monarch, add us to their list of peasants.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - true iff we're the new monarch process.
|
||||
// NOTE: This can throw!
|
||||
bool WindowManager::_performElection()
|
||||
{
|
||||
_createMonarchAndCallbacks();
|
||||
|
||||
// Tell the new monarch who we are. We might be that monarch!
|
||||
_monarch.AddPeasant(_peasant);
|
||||
|
||||
// This method is only called when a _new_ monarch is elected. So
|
||||
// don't do anything here that needs to be done for all monarch
|
||||
// windows. This should only be for work that's done when a window
|
||||
// _becomes_ a monarch, after the death of the previous monarch.
|
||||
return _isKing;
|
||||
}
|
||||
|
||||
void WindowManager::_createPeasantThread()
|
||||
{
|
||||
// If we catch an exception trying to get at the monarch ever, we can
|
||||
// set the _monarchWaitInterrupt, and use that to trigger a new
|
||||
// election. Though, we wouldn't be able to retry the function that
|
||||
// caused the exception in the first place...
|
||||
|
||||
_electionThread = std::thread([this] {
|
||||
_waitOnMonarchThread();
|
||||
});
|
||||
}
|
||||
|
||||
void WindowManager::_waitOnMonarchThread()
|
||||
{
|
||||
// This is the array of HANDLEs that we're going to wait on in
|
||||
// WaitForMultipleObjects below.
|
||||
// * waits[0] will be the handle to the monarch process. It gets
|
||||
// signalled when the process exits / dies.
|
||||
// * waits[1] is the handle to our _monarchWaitInterrupt event. Another
|
||||
// thread can use that to manually break this loop. We'll do that when
|
||||
// we're getting torn down.
|
||||
HANDLE waits[2];
|
||||
waits[1] = _monarchWaitInterrupt.get();
|
||||
const auto peasantID = _peasant.GetID(); // safe: _peasant is in-proc.
|
||||
|
||||
bool exitThreadRequested = false;
|
||||
while (!exitThreadRequested)
|
||||
{
|
||||
// At any point in all this, the current monarch might die. If it
|
||||
// does, we'll go straight to a new election, in the "jail"
|
||||
// try/catch below. Worst case, eventually, we'll become the new
|
||||
// monarch.
|
||||
try
|
||||
{
|
||||
// This might fail to even ask the monarch for it's PID.
|
||||
wil::unique_handle hMonarch{ OpenProcess(PROCESS_ALL_ACCESS,
|
||||
FALSE,
|
||||
static_cast<DWORD>(_monarch.GetPID())) };
|
||||
|
||||
// If we fail to open the monarch, then they don't exist
|
||||
// anymore! Go straight to an election.
|
||||
if (hMonarch.get() == nullptr)
|
||||
{
|
||||
const auto gle = GetLastError();
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_FailedToOpenMonarch",
|
||||
TraceLoggingUInt64(peasantID, "peasantID", "Our peasant ID"),
|
||||
TraceLoggingUInt64(gle, "lastError", "The result of GetLastError"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
|
||||
exitThreadRequested = _performElection();
|
||||
continue;
|
||||
}
|
||||
|
||||
waits[0] = hMonarch.get();
|
||||
auto waitResult = WaitForMultipleObjects(2, waits, FALSE, INFINITE);
|
||||
|
||||
switch (waitResult)
|
||||
{
|
||||
case WAIT_OBJECT_0 + 0: // waits[0] was signaled, the handle to the monarch process
|
||||
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_MonarchDied",
|
||||
TraceLoggingUInt64(peasantID, "peasantID", "Our peasant ID"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
// Connect to the new monarch, which might be us!
|
||||
// If we become the monarch, then we'll return true and exit this thread.
|
||||
exitThreadRequested = _performElection();
|
||||
break;
|
||||
|
||||
case WAIT_OBJECT_0 + 1: // waits[1] was signaled, our manual interrupt
|
||||
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_MonarchWaitInterrupted",
|
||||
TraceLoggingUInt64(peasantID, "peasantID", "Our peasant ID"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
exitThreadRequested = true;
|
||||
break;
|
||||
|
||||
case WAIT_TIMEOUT:
|
||||
// This should be impossible.
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_MonarchWaitTimeout",
|
||||
TraceLoggingUInt64(peasantID, "peasantID", "Our peasant ID"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
exitThreadRequested = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
{
|
||||
// Returning any other value is invalid. Just die.
|
||||
const auto gle = GetLastError();
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_WaitFailed",
|
||||
TraceLoggingUInt64(peasantID, "peasantID", "Our peasant ID"),
|
||||
TraceLoggingUInt64(gle, "lastError", "The result of GetLastError"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
ExitProcess(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// Theoretically, if window[1] dies when we're trying to get
|
||||
// it's PID we'll get here. If we just try to do the election
|
||||
// once here, it's possible we might elect window[2], but have
|
||||
// it die before we add ourselves as a peasant. That
|
||||
// _performElection call will throw, and we wouldn't catch it
|
||||
// here, and we'd die.
|
||||
|
||||
// Instead, we're going to have a resilient election process.
|
||||
// We're going to keep trying an election, until one _doesn't_
|
||||
// throw an exception. That might mean burning through all the
|
||||
// other dying monarchs until we find us as the monarch. But if
|
||||
// this process is alive, then there's _someone_ in the line of
|
||||
// succession.
|
||||
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_ExceptionInWaitThread",
|
||||
TraceLoggingUInt64(peasantID, "peasantID", "Our peasant ID"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
bool foundNewMonarch = false;
|
||||
while (!foundNewMonarch)
|
||||
{
|
||||
try
|
||||
{
|
||||
exitThreadRequested = _performElection();
|
||||
// It doesn't matter if we're the monarch, or someone
|
||||
// else is, but if we complete the election, then we've
|
||||
// registered with a new one. We can escape this jail
|
||||
// and re-enter society.
|
||||
foundNewMonarch = true;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// If we fail to acknowledge the results of the election,
|
||||
// stay in this jail until we do.
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_ExceptionInNestedWaitThread",
|
||||
TraceLoggingUInt64(peasantID, "peasantID", "Our peasant ID"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Remoting::Peasant WindowManager::CurrentWindow()
|
||||
{
|
||||
return _peasant;
|
||||
}
|
||||
|
||||
void WindowManager::_raiseFindTargetWindowRequested(const winrt::Windows::Foundation::IInspectable& sender,
|
||||
const winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs& args)
|
||||
{
|
||||
_FindTargetWindowRequestedHandlers(sender, args);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,23 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Class Name:
|
||||
- WindowManager.h
|
||||
|
||||
Abstract:
|
||||
- The Window Manager takes care of coordinating the monarch and peasant for this
|
||||
process.
|
||||
- It's responsible for registering as a potential future monarch. It's also
|
||||
responsible for creating the Peasant for this process when it's determined
|
||||
this process should become a window process.
|
||||
- If we aren't the monarch, it's responsible for watching the current monarch
|
||||
process, and finding the new one if the current monarch dies.
|
||||
- When the monarch needs to ask the TerminalApp about how to parse a
|
||||
commandline, it'll ask by raising an event that we'll bubble up to the
|
||||
AppHost.
|
||||
|
||||
--*/
|
||||
|
||||
#pragma once
|
||||
|
||||
|
@ -20,16 +38,29 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
|
||||
winrt::Microsoft::Terminal::Remoting::Peasant CurrentWindow();
|
||||
|
||||
TYPED_EVENT(FindTargetWindowRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs);
|
||||
|
||||
private:
|
||||
bool _shouldCreateWindow{ false };
|
||||
bool _isKing{ false };
|
||||
DWORD _registrationHostClass{ 0 };
|
||||
winrt::Microsoft::Terminal::Remoting::Monarch _monarch{ nullptr };
|
||||
winrt::Microsoft::Terminal::Remoting::Peasant _peasant{ nullptr };
|
||||
|
||||
wil::unique_event _monarchWaitInterrupt;
|
||||
std::thread _electionThread;
|
||||
|
||||
void _registerAsMonarch();
|
||||
void _createMonarch();
|
||||
void _createMonarchAndCallbacks();
|
||||
bool _areWeTheKing();
|
||||
winrt::Microsoft::Terminal::Remoting::IPeasant _createOurPeasant();
|
||||
winrt::Microsoft::Terminal::Remoting::IPeasant _createOurPeasant(std::optional<uint64_t> givenID);
|
||||
|
||||
bool _performElection();
|
||||
void _createPeasantThread();
|
||||
void _waitOnMonarchThread();
|
||||
void _raiseFindTargetWindowRequested(const winrt::Windows::Foundation::IInspectable& sender,
|
||||
const winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs& args);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import "Peasant.idl";
|
||||
import "Monarch.idl";
|
||||
|
||||
|
||||
namespace Microsoft.Terminal.Remoting
|
||||
|
@ -9,5 +10,6 @@ namespace Microsoft.Terminal.Remoting
|
|||
void ProposeCommandline(CommandlineArgs args);
|
||||
Boolean ShouldCreateWindow { get; };
|
||||
IPeasant CurrentWindow();
|
||||
event Windows.Foundation.TypedEventHandler<Object, FindTargetWindowArgs> FindTargetWindowRequested;
|
||||
};
|
||||
}
|
||||
|
|
37
src/cascadia/Remoting/init.cpp
Normal file
|
@ -0,0 +1,37 @@
|
|||
// Copyright (c) Microsoft Corporation
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#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.
|
||||
TRACELOGGING_DEFINE_PROVIDER(
|
||||
g_hRemotingProvider,
|
||||
"Microsoft.Windows.Terminal.Remoting",
|
||||
// {d6f04aad-629f-539a-77c1-73f5c3e4aa7b}
|
||||
(0xd6f04aad, 0x629f, 0x539a, 0x77, 0xc1, 0x73, 0xf5, 0xc3, 0xe4, 0xaa, 0x7b),
|
||||
TraceLoggingOptionMicrosoftTelemetry());
|
||||
|
||||
BOOL WINAPI DllMain(HINSTANCE hInstDll, DWORD reason, LPVOID /*reserved*/)
|
||||
{
|
||||
switch (reason)
|
||||
{
|
||||
case DLL_PROCESS_ATTACH:
|
||||
DisableThreadLibraryCalls(hInstDll);
|
||||
TraceLoggingRegister(g_hRemotingProvider);
|
||||
Microsoft::Console::ErrorReporting::EnableFallbackFailureReporting(g_hRemotingProvider);
|
||||
break;
|
||||
case DLL_PROCESS_DETACH:
|
||||
if (g_hRemotingProvider)
|
||||
{
|
||||
TraceLoggingUnregister(g_hRemotingProvider);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
UTILS_DEFINE_LIBRARY_RESOURCE_SCOPE(L"Microsoft.Terminal.Remoting/Resources");
|
|
@ -38,7 +38,7 @@
|
|||
// Including TraceLogging essentials for the binary
|
||||
#include <TraceLoggingProvider.h>
|
||||
#include <winmeta.h>
|
||||
TRACELOGGING_DECLARE_PROVIDER(g_hSettingsModelProvider);
|
||||
TRACELOGGING_DECLARE_PROVIDER(g_hRemotingProvider);
|
||||
#include <telemetry/ProjectTelemetry.h>
|
||||
#include <TraceLoggingActivity.h>
|
||||
|
||||
|
|
|
@ -266,7 +266,16 @@ std::wstring OpenTerminalHere::_GetPathFromExplorer() const
|
|||
return path;
|
||||
}
|
||||
|
||||
auto shell = create_instance<IShellWindows>(CLSID_ShellWindows);
|
||||
com_ptr<IShellWindows> shell;
|
||||
try
|
||||
{
|
||||
shell = create_instance<IShellWindows>(CLSID_ShellWindows, CLSCTX_ALL);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
//look like try_create_instance is not available no more
|
||||
}
|
||||
|
||||
if (shell == nullptr)
|
||||
{
|
||||
return path;
|
||||
|
@ -285,6 +294,7 @@ std::wstring OpenTerminalHere::_GetPathFromExplorer() const
|
|||
com_ptr<IWebBrowserApp> tmp;
|
||||
if (FAILED(disp->QueryInterface(tmp.put())))
|
||||
{
|
||||
disp = nullptr; // get rid of DEBUG non-nullptr warning
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -293,8 +303,11 @@ std::wstring OpenTerminalHere::_GetPathFromExplorer() const
|
|||
if (hwnd == tmpHWND)
|
||||
{
|
||||
browser = tmp;
|
||||
disp = nullptr; // get rid of DEBUG non-nullptr warning
|
||||
break; //found
|
||||
}
|
||||
|
||||
disp = nullptr; // get rid of DEBUG non-nullptr warning
|
||||
}
|
||||
|
||||
if (browser != nullptr)
|
||||
|
|
|
@ -154,6 +154,17 @@ namespace winrt::TerminalApp::implementation
|
|||
args.Handled(true);
|
||||
}
|
||||
|
||||
void TerminalPage::_HandleTogglePaneReadOnly(const IInspectable& /*sender*/,
|
||||
const ActionEventArgs& args)
|
||||
{
|
||||
if (const auto activeTab{ _GetFocusedTabImpl() })
|
||||
{
|
||||
activeTab->TogglePaneReadOnly();
|
||||
}
|
||||
|
||||
args.Handled(true);
|
||||
}
|
||||
|
||||
void TerminalPage::_HandleScrollUpPage(const IInspectable& /*sender*/,
|
||||
const ActionEventArgs& args)
|
||||
{
|
||||
|
@ -352,7 +363,7 @@ namespace winrt::TerminalApp::implementation
|
|||
{
|
||||
auto controlSettings = activeControl.Settings().as<TerminalSettings>();
|
||||
controlSettings->ApplyColorScheme(scheme);
|
||||
activeControl.UpdateSettings(*controlSettings);
|
||||
activeControl.UpdateSettings();
|
||||
args.Handled(true);
|
||||
}
|
||||
}
|
||||
|
@ -465,18 +476,20 @@ namespace winrt::TerminalApp::implementation
|
|||
return;
|
||||
}
|
||||
|
||||
// Remove tabs after the current one
|
||||
while (_tabs.Size() > index + 1)
|
||||
// Since _RemoveTab is asynchronous, create a snapshot of the tabs we want to remove
|
||||
std::vector<winrt::TerminalApp::TabBase> tabsToRemove;
|
||||
if (index > 0)
|
||||
{
|
||||
_RemoveTabViewItemByIndex(_tabs.Size() - 1);
|
||||
std::copy(begin(_tabs), begin(_tabs) + index, std::back_inserter(tabsToRemove));
|
||||
}
|
||||
|
||||
// Remove all of them leading up to the selected tab
|
||||
while (_tabs.Size() > 1)
|
||||
if (index + 1 < _tabs.Size())
|
||||
{
|
||||
_RemoveTabViewItemByIndex(0);
|
||||
std::copy(begin(_tabs) + index + 1, end(_tabs), std::back_inserter(tabsToRemove));
|
||||
}
|
||||
|
||||
_RemoveTabs(tabsToRemove);
|
||||
|
||||
actionArgs.Handled(true);
|
||||
}
|
||||
}
|
||||
|
@ -502,11 +515,10 @@ namespace winrt::TerminalApp::implementation
|
|||
return;
|
||||
}
|
||||
|
||||
// Remove tabs after the current one
|
||||
while (_tabs.Size() > index + 1)
|
||||
{
|
||||
_RemoveTabViewItemByIndex(_tabs.Size() - 1);
|
||||
}
|
||||
// Since _RemoveTab is asynchronous, create a snapshot of the tabs we want to remove
|
||||
std::vector<winrt::TerminalApp::TabBase> tabsToRemove;
|
||||
std::copy(begin(_tabs) + index + 1, end(_tabs), std::back_inserter(tabsToRemove));
|
||||
_RemoveTabs(tabsToRemove);
|
||||
|
||||
// TODO:GH#7182 For whatever reason, if you run this action
|
||||
// when the tab that's currently focused is _before_ the `index`
|
||||
|
|
|
@ -185,6 +185,10 @@ void AppCommandlineArgs::_buildParser()
|
|||
maximized->excludes(fullscreen);
|
||||
focus->excludes(fullscreen);
|
||||
|
||||
_app.add_option("-w,--window",
|
||||
_windowTarget,
|
||||
RS_A(L"CmdWindowTargetArgDesc"));
|
||||
|
||||
// Subcommands
|
||||
_buildNewTabParser();
|
||||
_buildSplitPaneParser();
|
||||
|
@ -531,6 +535,7 @@ void AppCommandlineArgs::_resetStateToDefault()
|
|||
// DON'T clear _launchMode here! This will get called once for every
|
||||
// subcommand, so we don't want `wt -F new-tab ; split-pane` clearing out
|
||||
// the "global" fullscreen flag (-F).
|
||||
// Same with _windowTarget.
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
|
@ -848,4 +853,11 @@ void AppCommandlineArgs::FullResetState()
|
|||
_startupActions.clear();
|
||||
_exitMessage = "";
|
||||
_shouldExitEarly = false;
|
||||
|
||||
_windowTarget = -1;
|
||||
}
|
||||
|
||||
int AppCommandlineArgs::GetTargetWindow() const noexcept
|
||||
{
|
||||
return _windowTarget;
|
||||
}
|
||||
|
|
|
@ -44,6 +44,8 @@ public:
|
|||
void DisableHelpInExitMessage();
|
||||
void FullResetState();
|
||||
|
||||
int GetTargetWindow() const noexcept;
|
||||
|
||||
private:
|
||||
static const std::wregex _commandDelimiterRegex;
|
||||
|
||||
|
@ -103,6 +105,8 @@ private:
|
|||
std::vector<winrt::Microsoft::Terminal::Settings::Model::ActionAndArgs> _startupActions;
|
||||
std::string _exitMessage;
|
||||
bool _shouldExitEarly{ false };
|
||||
|
||||
int _windowTarget{ -1 };
|
||||
// Are you adding more args or attributes here? If they are not reset in _resetStateToDefault, make sure to reset them in FullResetState
|
||||
|
||||
winrt::Microsoft::Terminal::Settings::Model::NewTerminalArgs _getNewTerminalArgs(NewTerminalSubcommand& subcommand);
|
||||
|
|
|
@ -841,7 +841,8 @@ namespace winrt::TerminalApp::implementation
|
|||
// editors, who will write a temp file, then rename it to be the
|
||||
// actual file you wrote. So listen for that too.
|
||||
if (!(event == wil::FolderChangeEvent::Modified ||
|
||||
event == wil::FolderChangeEvent::RenameNewName))
|
||||
event == wil::FolderChangeEvent::RenameNewName ||
|
||||
event == wil::FolderChangeEvent::Removed))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
@ -1150,17 +1151,84 @@ namespace winrt::TerminalApp::implementation
|
|||
return result;
|
||||
}
|
||||
|
||||
int32_t AppLogic::ExecuteCommandline(array_view<const winrt::hstring> args)
|
||||
// Method Description:
|
||||
// - Parse the provided commandline arguments into actions, and try to
|
||||
// perform them immediately.
|
||||
// - This function returns 0, unless a there was a non-zero result from
|
||||
// trying to parse one of the commands provided. In that case, no commands
|
||||
// after the failing command will be parsed, and the non-zero code
|
||||
// returned.
|
||||
// - If a non-empty cwd is provided, the entire terminal exe will switch to
|
||||
// that CWD while we handle these actions, then return to the original
|
||||
// CWD.
|
||||
// Arguments:
|
||||
// - args: an array of strings to process as a commandline. These args can contain spaces
|
||||
// - cwd: The directory to use as the CWD while performing these actions.
|
||||
// Return Value:
|
||||
// - the result of the first command who's parsing returned a non-zero code,
|
||||
// or 0. (see AppLogic::_ParseArgs)
|
||||
int32_t AppLogic::ExecuteCommandline(array_view<const winrt::hstring> args,
|
||||
const winrt::hstring& cwd)
|
||||
{
|
||||
::TerminalApp::AppCommandlineArgs appArgs;
|
||||
auto result = appArgs.ParseArgs(args);
|
||||
if (result == 0)
|
||||
{
|
||||
auto actions = winrt::single_threaded_vector<ActionAndArgs>(std::move(appArgs.GetStartupActions()));
|
||||
_root->ProcessStartupActions(actions, false);
|
||||
|
||||
_root->ProcessStartupActions(actions, false, cwd);
|
||||
}
|
||||
// Return the result of parsing with commandline, though it may or may not be used.
|
||||
return result;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Parse the given commandline args in an attempt to find the specified
|
||||
// window. The rest of the args are ignored for now (they'll be handled
|
||||
// whenever the commandline gets to the window it was intended for).
|
||||
// - Note that this function will only ever be called by the monarch. A
|
||||
// return value of `0` in this case does not mean "run the commandline in
|
||||
// _this_ process", rather it means "run the commandline in the current
|
||||
// process", whoever that may be.
|
||||
// Arguments:
|
||||
// - args: an array of strings to process as a commandline. These args can contain spaces
|
||||
// Return Value:
|
||||
// - 0: We should handle the args "in the current window".
|
||||
// - -1: We should handle the args in a new window
|
||||
// - anything else: We should handle the commandline in the window with the given ID.
|
||||
int32_t AppLogic::FindTargetWindow(array_view<const winrt::hstring> args)
|
||||
{
|
||||
::TerminalApp::AppCommandlineArgs appArgs;
|
||||
const auto result = appArgs.ParseArgs(args);
|
||||
if (result == 0)
|
||||
{
|
||||
return appArgs.GetTargetWindow();
|
||||
|
||||
// TODO:projects/5
|
||||
//
|
||||
// In the future, we'll want to use the windowingBehavior setting to
|
||||
// determine what happens when a window ID wasn't manually provided.
|
||||
//
|
||||
// Maybe that'd be a special return value out of here, to tell the
|
||||
// monarch to do something special:
|
||||
//
|
||||
// -1 -> create a new window
|
||||
// -2 -> find the mru, this desktop
|
||||
// -3 -> MRU, any desktop (is this not just 0?)
|
||||
}
|
||||
|
||||
return result; // TODO:MG does a return value make sense
|
||||
// Any unsuccessful parse will be a new window. That new window will try
|
||||
// to handle the commandline itself, and find that the commandline
|
||||
// failed to parse. When that happens, the new window will display the
|
||||
// message box.
|
||||
//
|
||||
// This will also work for the case where the user specifies an invalid
|
||||
// commandline in conjunction with `-w 0`. This function will determine
|
||||
// that the commandline has a parse error, and indicate that we should
|
||||
// create a new window. Then, in that new window, we'll try to set the
|
||||
// StartupActions, which will again fail, returning the correct error
|
||||
// message.
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
|
|
@ -29,7 +29,8 @@ namespace winrt::TerminalApp::implementation
|
|||
[[nodiscard]] Microsoft::Terminal::Settings::Model::CascadiaSettings GetSettings() const noexcept;
|
||||
|
||||
int32_t SetStartupCommandline(array_view<const winrt::hstring> actions);
|
||||
int32_t ExecuteCommandline(array_view<const winrt::hstring> actions);
|
||||
int32_t ExecuteCommandline(array_view<const winrt::hstring> actions, const winrt::hstring& cwd);
|
||||
int32_t FindTargetWindow(array_view<const winrt::hstring> actions);
|
||||
winrt::hstring ParseCommandlineMessage();
|
||||
bool ShouldExitEarly();
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ namespace TerminalApp
|
|||
Boolean IsElevated();
|
||||
|
||||
Int32 SetStartupCommandline(String[] commands);
|
||||
Int32 ExecuteCommandline(String[] commands);
|
||||
Int32 ExecuteCommandline(String[] commands, String cwd);
|
||||
String ParseCommandlineMessage { get; };
|
||||
Boolean ShouldExitEarly { get; };
|
||||
|
||||
|
@ -56,6 +56,8 @@ namespace TerminalApp
|
|||
UInt64 GetLastActiveControlTaskbarState();
|
||||
UInt64 GetLastActiveControlTaskbarProgress();
|
||||
|
||||
Int32 FindTargetWindow(String[] args);
|
||||
|
||||
// See IDialogPresenter and TerminalPage's DialogPresenter for more
|
||||
// information.
|
||||
Windows.Foundation.IAsyncOperation<Windows.UI.Xaml.Controls.ContentDialogResult> ShowDialog(Windows.UI.Xaml.Controls.ContentDialog dialog);
|
||||
|
|
|
@ -52,11 +52,8 @@ namespace winrt::TerminalApp::implementation
|
|||
RegisterPropertyChangedCallback(UIElement::VisibilityProperty(), [this](auto&&, auto&&) {
|
||||
if (Visibility() == Visibility::Visible)
|
||||
{
|
||||
if (_filteredActionsView().Items().Size() == 0 && _filteredActions.Size() > 0)
|
||||
{
|
||||
// Force immediate binding update so we can select an item
|
||||
Bindings->Update();
|
||||
}
|
||||
// Force immediate binding update so we can select an item
|
||||
Bindings->Update();
|
||||
|
||||
if (_currentMode == CommandPaletteMode::TabSwitchMode)
|
||||
{
|
||||
|
@ -243,6 +240,9 @@ namespace winrt::TerminalApp::implementation
|
|||
Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e)
|
||||
{
|
||||
auto key = e.OriginalKey();
|
||||
auto const ctrlDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Control), CoreVirtualKeyStates::Down);
|
||||
auto const altDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Menu), CoreVirtualKeyStates::Down);
|
||||
auto const shiftDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Shift), CoreVirtualKeyStates::Down);
|
||||
|
||||
// Some keypresses such as Tab, Return, Esc, and Arrow Keys are ignored by controls because
|
||||
// they're not considered input key presses. While they don't raise KeyDown events,
|
||||
|
@ -253,10 +253,6 @@ namespace winrt::TerminalApp::implementation
|
|||
// a really widely used keyboard navigation key.
|
||||
if (_currentMode == CommandPaletteMode::TabSwitchMode && _keymap)
|
||||
{
|
||||
auto const ctrlDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Control), CoreVirtualKeyStates::Down);
|
||||
auto const altDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Menu), CoreVirtualKeyStates::Down);
|
||||
auto const shiftDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Shift), CoreVirtualKeyStates::Down);
|
||||
|
||||
winrt::Microsoft::Terminal::TerminalControl::KeyChord kc{ ctrlDown, altDown, shiftDown, static_cast<int32_t>(key) };
|
||||
const auto action = _keymap.TryLookup(kc);
|
||||
if (action)
|
||||
|
@ -272,41 +268,21 @@ namespace winrt::TerminalApp::implementation
|
|||
e.Handled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (key == VirtualKey::Home)
|
||||
{
|
||||
auto const state = CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Control);
|
||||
if (WI_IsFlagSet(state, CoreVirtualKeyStates::Down))
|
||||
{
|
||||
ScrollToTop();
|
||||
e.Handled(true);
|
||||
}
|
||||
}
|
||||
else if (key == VirtualKey::End)
|
||||
{
|
||||
auto const state = CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Control);
|
||||
if (WI_IsFlagSet(state, CoreVirtualKeyStates::Down))
|
||||
{
|
||||
ScrollToBottom();
|
||||
e.Handled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Process keystrokes in the input box. This is used for moving focus up
|
||||
// and down the list of commands in Action mode, and for executing
|
||||
// commands in both Action mode and Commandline mode.
|
||||
// Arguments:
|
||||
// - e: the KeyRoutedEventArgs containing info about the keystroke.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void CommandPalette::_keyDownHandler(IInspectable const& /*sender*/,
|
||||
Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e)
|
||||
{
|
||||
auto key = e.OriginalKey();
|
||||
return;
|
||||
}
|
||||
|
||||
if (key == VirtualKey::Up)
|
||||
if (key == VirtualKey::Home && ctrlDown)
|
||||
{
|
||||
ScrollToTop();
|
||||
e.Handled(true);
|
||||
}
|
||||
else if (key == VirtualKey::End && ctrlDown)
|
||||
{
|
||||
ScrollToBottom();
|
||||
e.Handled(true);
|
||||
}
|
||||
else if (key == VirtualKey::Up)
|
||||
{
|
||||
// Action Mode: Move focus to the next item in the list.
|
||||
SelectNextItem(false);
|
||||
|
@ -352,16 +328,22 @@ namespace winrt::TerminalApp::implementation
|
|||
|
||||
e.Handled(true);
|
||||
}
|
||||
else if (key == VirtualKey::Back)
|
||||
else if (key == VirtualKey::Back && _searchBox().Text().empty() && _lastFilterTextWasEmpty && _currentMode == CommandPaletteMode::ActionMode)
|
||||
{
|
||||
// If the last filter text was empty, and we're backspacing from
|
||||
// that state, then the user "backspaced" the virtual '>' we're
|
||||
// using as the action mode indicator. Switch into commandline mode.
|
||||
if (_searchBox().Text().empty() && _lastFilterTextWasEmpty && _currentMode == CommandPaletteMode::ActionMode)
|
||||
{
|
||||
_switchToMode(CommandPaletteMode::CommandlineMode);
|
||||
}
|
||||
|
||||
_switchToMode(CommandPaletteMode::CommandlineMode);
|
||||
e.Handled(true);
|
||||
}
|
||||
else if (key == VirtualKey::C && ctrlDown)
|
||||
{
|
||||
_searchBox().CopySelectionToClipboard();
|
||||
e.Handled(true);
|
||||
}
|
||||
else if (key == VirtualKey::V && ctrlDown)
|
||||
{
|
||||
_searchBox().PasteFromClipboard();
|
||||
e.Handled(true);
|
||||
}
|
||||
}
|
||||
|
@ -451,6 +433,12 @@ namespace winrt::TerminalApp::implementation
|
|||
void CommandPalette::_lostFocusHandler(Windows::Foundation::IInspectable const& /*sender*/,
|
||||
Windows::UI::Xaml::RoutedEventArgs const& /*args*/)
|
||||
{
|
||||
const auto flyout = _searchBox().ContextFlyout();
|
||||
if (flyout && flyout.IsOpen())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto focusedElementOrAncestor = Input::FocusManager::GetFocusedElement(this->XamlRoot()).try_as<DependencyObject>();
|
||||
while (focusedElementOrAncestor)
|
||||
{
|
||||
|
|
|
@ -75,8 +75,7 @@ namespace winrt::TerminalApp::implementation
|
|||
Windows::UI::Xaml::RoutedEventArgs const& args);
|
||||
void _previewKeyDownHandler(Windows::Foundation::IInspectable const& sender,
|
||||
Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e);
|
||||
void _keyDownHandler(Windows::Foundation::IInspectable const& sender,
|
||||
Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e);
|
||||
|
||||
void _keyUpHandler(Windows::Foundation::IInspectable const& sender,
|
||||
Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e);
|
||||
|
||||
|
|
|
@ -16,7 +16,6 @@ the MIT License. See LICENSE in the project root for license information. -->
|
|||
AllowFocusOnInteraction="True"
|
||||
PointerPressed="_rootPointerPressed"
|
||||
PreviewKeyDown="_previewKeyDownHandler"
|
||||
KeyDown="_keyDownHandler"
|
||||
PreviewKeyUp="_keyUpHandler"
|
||||
LostFocus="_lostFocusHandler"
|
||||
mc:Ignorable="d"
|
||||
|
@ -36,6 +35,159 @@ the MIT License. See LICENSE in the project root for license information. -->
|
|||
<local:HasNestedCommandsVisibilityConverter x:Key="HasNestedCommandsVisibilityConverter"/>
|
||||
<model:IconPathConverter x:Key="IconSourceConverter"/>
|
||||
|
||||
<DataTemplate x:Key="GeneralItemTemplate" x:DataType="local:FilteredCommand">
|
||||
|
||||
<!-- This HorizontalContentAlignment="Stretch" is important
|
||||
to make sure it takes the entire width of the line -->
|
||||
<ListViewItem HorizontalContentAlignment="Stretch"
|
||||
IsTabStop="False"
|
||||
AutomationProperties.Name="{x:Bind Item.Name, Mode=OneWay}"
|
||||
AutomationProperties.AcceleratorKey="{x:Bind Item.KeyChordText, Mode=OneWay}">
|
||||
|
||||
<Grid HorizontalAlignment="Stretch" ColumnSpacing="8" >
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="16"/>
|
||||
<!-- icon -->
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<!-- command label -->
|
||||
<ColumnDefinition Width="*"/>
|
||||
<!-- key chord -->
|
||||
<ColumnDefinition Width="16"/>
|
||||
<!-- gutter for scrollbar -->
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<IconSourceElement
|
||||
Grid.Column="0"
|
||||
Width="16"
|
||||
Height="16"
|
||||
IconSource="{x:Bind Item.Icon,
|
||||
Mode=OneWay,
|
||||
Converter={StaticResource IconSourceConverter}}"/>
|
||||
|
||||
<local:HighlightedTextControl
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Left"
|
||||
Text="{x:Bind HighlightedName, Mode=OneWay}"/>
|
||||
|
||||
<!-- The block for the key chord is only visible
|
||||
when there's actual text set as the label. See
|
||||
CommandKeyChordVisibilityConverter for details. -->
|
||||
<Border
|
||||
Grid.Column="2"
|
||||
Visibility="{x:Bind Item.KeyChordText,
|
||||
Mode=OneWay,
|
||||
Converter={StaticResource CommandKeyChordVisibilityConverter}}"
|
||||
Style="{ThemeResource KeyChordBorderStyle}"
|
||||
Padding="2,0,2,0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center">
|
||||
|
||||
<TextBlock
|
||||
Style="{ThemeResource KeyChordTextBlockStyle}"
|
||||
FontSize="12"
|
||||
Text="{x:Bind Item.KeyChordText, Mode=OneWay}" />
|
||||
</Border>
|
||||
|
||||
<!-- xE70E is ChevronUp. Rotated 90 degrees, it's _ChevronRight_ -->
|
||||
<FontIcon
|
||||
FontFamily="Segoe MDL2 Assets"
|
||||
Glyph=""
|
||||
HorizontalAlignment="Right"
|
||||
Visibility="{x:Bind Item,
|
||||
Mode=OneWay,
|
||||
Converter={StaticResource HasNestedCommandsVisibilityConverter}}"
|
||||
Grid.Column="2">
|
||||
|
||||
<FontIcon.RenderTransform>
|
||||
<RotateTransform CenterX="0.5" CenterY="0.5" Angle="90"/>
|
||||
</FontIcon.RenderTransform>
|
||||
</FontIcon>
|
||||
|
||||
</Grid>
|
||||
</ListViewItem>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="TabItemTemplate" x:DataType="local:FilteredCommand">
|
||||
|
||||
<!-- This HorizontalContentAlignment="Stretch" is important
|
||||
to make sure it takes the entire width of the line -->
|
||||
<ListViewItem HorizontalContentAlignment="Stretch"
|
||||
IsTabStop="False"
|
||||
AutomationProperties.Name="{x:Bind Item.Name, Mode=OneWay}"
|
||||
AutomationProperties.AcceleratorKey="{x:Bind Item.KeyChordText, Mode=OneWay}">
|
||||
|
||||
<Grid HorizontalAlignment="Stretch" ColumnSpacing="8" >
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="16"/>
|
||||
<!-- icon / progress -->
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<!-- command label -->
|
||||
<ColumnDefinition Width="*"/>
|
||||
<!-- gutter for indicators -->
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<!-- Indicators -->
|
||||
<ColumnDefinition Width="16"/>
|
||||
<!-- gutter for scrollbar -->
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<mux:ProgressRing
|
||||
Grid.Column="0"
|
||||
IsActive="{x:Bind Item.(local:TabPaletteItem.TabStatus).IsProgressRingActive, Mode=OneWay}"
|
||||
Visibility="{x:Bind Item.(local:TabPaletteItem.TabStatus).IsProgressRingActive, Mode=OneWay}"
|
||||
IsIndeterminate="{x:Bind Item.(local:TabPaletteItem.TabStatus).IsProgressRingIndeterminate, Mode=OneWay}"
|
||||
Value="{x:Bind Item.(local:TabPaletteItem.TabStatus).ProgressValue, Mode=OneWay}"
|
||||
MinHeight="0"
|
||||
MinWidth="0"
|
||||
Height="15"
|
||||
Width="15"/>
|
||||
|
||||
<IconSourceElement
|
||||
Grid.Column="0"
|
||||
Width="16"
|
||||
Height="16"
|
||||
IconSource="{x:Bind Item.Icon,
|
||||
Mode=OneWay,
|
||||
Converter={StaticResource IconSourceConverter}}"/>
|
||||
|
||||
<local:HighlightedTextControl
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Left"
|
||||
Text="{x:Bind HighlightedName, Mode=OneWay}"/>
|
||||
|
||||
<StackPanel
|
||||
Grid.Column="2"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Right"
|
||||
Orientation="Horizontal">
|
||||
|
||||
<FontIcon
|
||||
FontFamily="Segoe MDL2 Assets"
|
||||
Visibility="{x:Bind Item.(local:TabPaletteItem.TabStatus).BellIndicator, Mode=OneWay}"
|
||||
Glyph=""
|
||||
FontSize="12"
|
||||
Margin="0,0,8,0"/>
|
||||
|
||||
<FontIcon
|
||||
FontFamily="Segoe MDL2 Assets"
|
||||
Visibility="{x:Bind Item.(local:TabPaletteItem.TabStatus).IsPaneZoomed, Mode=OneWay}"
|
||||
Glyph=""
|
||||
FontSize="12"
|
||||
Margin="0,0,8,0"/>
|
||||
|
||||
<FontIcon x:Name="HeaderLockIcon"
|
||||
FontFamily="Segoe MDL2 Assets"
|
||||
Visibility="{x:Bind Item.(local:TabPaletteItem.TabStatus).IsReadOnlyActive, Mode=OneWay}"
|
||||
Glyph=""
|
||||
FontSize="12"
|
||||
Margin="0,0,8,0"/>
|
||||
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</ListViewItem>
|
||||
</DataTemplate>
|
||||
|
||||
<local:PaletteItemTemplateSelector x:Key="PaletteItemTemplateSelector" TabItemTemplate="{StaticResource TabItemTemplate}" GeneralItemTemplate="{StaticResource GeneralItemTemplate}"/>
|
||||
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
<ResourceDictionary x:Key="Dark">
|
||||
<Style x:Key="CommandPaletteBackground" TargetType="Grid">
|
||||
|
@ -278,78 +430,8 @@ the MIT License. See LICENSE in the project root for license information. -->
|
|||
AllowDrop="False"
|
||||
IsItemClickEnabled="True"
|
||||
ItemClick="_listItemClicked"
|
||||
PreviewKeyDown="_keyDownHandler"
|
||||
ItemsSource="{x:Bind FilteredActions}">
|
||||
|
||||
<ItemsControl.ItemTemplate >
|
||||
<DataTemplate x:DataType="local:FilteredCommand">
|
||||
|
||||
<!-- This HorizontalContentAlignment="Stretch" is important
|
||||
to make sure it takes the entire width of the line -->
|
||||
<ListViewItem HorizontalContentAlignment="Stretch"
|
||||
IsTabStop="False"
|
||||
AutomationProperties.Name="{x:Bind Item.Name, Mode=OneWay}"
|
||||
AutomationProperties.AcceleratorKey="{x:Bind Item.KeyChordText, Mode=OneWay}">
|
||||
|
||||
<Grid HorizontalAlignment="Stretch" ColumnSpacing="8" >
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="16"/> <!-- icon -->
|
||||
<ColumnDefinition Width="Auto"/> <!-- command label -->
|
||||
<ColumnDefinition Width="*"/> <!-- key chord -->
|
||||
<ColumnDefinition Width="16"/> <!-- gutter for scrollbar -->
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<IconSourceElement
|
||||
Grid.Column="0"
|
||||
Width="16"
|
||||
Height="16"
|
||||
IconSource="{x:Bind Item.Icon,
|
||||
Mode=OneWay,
|
||||
Converter={StaticResource IconSourceConverter}}"/>
|
||||
|
||||
<local:HighlightedTextControl
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Left"
|
||||
Text="{x:Bind HighlightedName, Mode=OneWay}"/>
|
||||
|
||||
<!-- The block for the key chord is only visible
|
||||
when there's actual text set as the label. See
|
||||
CommandKeyChordVisibilityConverter for details. -->
|
||||
<Border
|
||||
Grid.Column="2"
|
||||
Visibility="{x:Bind Item.KeyChordText,
|
||||
Mode=OneWay,
|
||||
Converter={StaticResource CommandKeyChordVisibilityConverter}}"
|
||||
Style="{ThemeResource KeyChordBorderStyle}"
|
||||
Padding="2,0,2,0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center">
|
||||
|
||||
<TextBlock
|
||||
Style="{ThemeResource KeyChordTextBlockStyle}"
|
||||
FontSize="12"
|
||||
Text="{x:Bind Item.KeyChordText, Mode=OneWay}" />
|
||||
</Border>
|
||||
|
||||
<!-- xE70E is ChevronUp. Rotated 90 degrees, it's _ChevronRight_ -->
|
||||
<FontIcon
|
||||
FontFamily="Segoe MDL2 Assets"
|
||||
Glyph=""
|
||||
HorizontalAlignment="Right"
|
||||
Visibility="{x:Bind Item,
|
||||
Mode=OneWay,
|
||||
Converter={StaticResource HasNestedCommandsVisibilityConverter}}"
|
||||
Grid.Column="2">
|
||||
|
||||
<FontIcon.RenderTransform>
|
||||
<RotateTransform CenterX="0.5" CenterY="0.5" Angle="90"/>
|
||||
</FontIcon.RenderTransform>
|
||||
</FontIcon>
|
||||
|
||||
</Grid>
|
||||
</ListViewItem>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
ItemsSource="{x:Bind FilteredActions}"
|
||||
ItemTemplateSelector="{StaticResource PaletteItemTemplateSelector}">
|
||||
</ListView>
|
||||
|
||||
</Grid>
|
||||
|
|
37
src/cascadia/TerminalApp/PaletteItemTemplateSelector.cpp
Normal file
|
@ -0,0 +1,37 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "TabPaletteItem.h"
|
||||
#include "PaletteItemTemplateSelector.h"
|
||||
#include "PaletteItemTemplateSelector.g.cpp"
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
Windows::UI::Xaml::DataTemplate PaletteItemTemplateSelector::SelectTemplateCore(winrt::Windows::Foundation::IInspectable const& item, winrt::Windows::UI::Xaml::DependencyObject const& /*container*/)
|
||||
{
|
||||
return SelectTemplateCore(item);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - This method is called once command palette decides how to render a filtered command.
|
||||
// Currently we support two ways to render command, that depend on its palette item type:
|
||||
// - For TabPalette item we render an icon, a title, and some tab-related indicators like progress bar (as defined by TabItemTemplate)
|
||||
// - All other items are currently rendered with icon, title and optional key-chord (as defined by GeneralItemTemplate)
|
||||
// Arguments:
|
||||
// - item - an instance of filtered command to render
|
||||
// Return Value:
|
||||
// - data template to use for rendering
|
||||
Windows::UI::Xaml::DataTemplate PaletteItemTemplateSelector::SelectTemplateCore(winrt::Windows::Foundation::IInspectable const& item)
|
||||
{
|
||||
if (const auto filteredCommand{ item.try_as<winrt::TerminalApp::FilteredCommand>() })
|
||||
{
|
||||
if (filteredCommand.Item().try_as<winrt::TerminalApp::TabPaletteItem>())
|
||||
{
|
||||
return TabItemTemplate();
|
||||
}
|
||||
}
|
||||
|
||||
return GeneralItemTemplate();
|
||||
}
|
||||
}
|
26
src/cascadia/TerminalApp/PaletteItemTemplateSelector.h
Normal file
|
@ -0,0 +1,26 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "PaletteItemTemplateSelector.g.h"
|
||||
#include "inc/cppwinrt_utils.h"
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
struct PaletteItemTemplateSelector : PaletteItemTemplateSelectorT<PaletteItemTemplateSelector>
|
||||
{
|
||||
PaletteItemTemplateSelector() = default;
|
||||
|
||||
Windows::UI::Xaml::DataTemplate SelectTemplateCore(winrt::Windows::Foundation::IInspectable const&, winrt::Windows::UI::Xaml::DependencyObject const&);
|
||||
Windows::UI::Xaml::DataTemplate SelectTemplateCore(winrt::Windows::Foundation::IInspectable const&);
|
||||
|
||||
GETSET_PROPERTY(winrt::Windows::UI::Xaml::DataTemplate, TabItemTemplate);
|
||||
GETSET_PROPERTY(winrt::Windows::UI::Xaml::DataTemplate, GeneralItemTemplate);
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::TerminalApp::factory_implementation
|
||||
{
|
||||
BASIC_FACTORY(PaletteItemTemplateSelector);
|
||||
}
|
13
src/cascadia/TerminalApp/PaletteItemTemplateSelector.idl
Normal file
|
@ -0,0 +1,13 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace TerminalApp
|
||||
{
|
||||
[default_interface] runtimeclass PaletteItemTemplateSelector : Windows.UI.Xaml.Controls.DataTemplateSelector
|
||||
{
|
||||
PaletteItemTemplateSelector();
|
||||
|
||||
Windows.UI.Xaml.DataTemplate TabItemTemplate;
|
||||
Windows.UI.Xaml.DataTemplate GeneralItemTemplate;
|
||||
}
|
||||
}
|
|
@ -416,8 +416,11 @@ void Pane::_ControlLostFocusHandler(winrt::Windows::Foundation::IInspectable con
|
|||
// - <none>
|
||||
void Pane::Close()
|
||||
{
|
||||
// Fire our Closed event to tell our parent that we should be removed.
|
||||
_ClosedHandlers(nullptr, nullptr);
|
||||
if (!_isClosing.exchange(true))
|
||||
{
|
||||
// Fire our Closed event to tell our parent that we should be removed.
|
||||
_ClosedHandlers(nullptr, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
@ -639,7 +642,13 @@ void Pane::UpdateSettings(const TerminalSettings& settings, const GUID& profile)
|
|||
{
|
||||
if (profile == _profile)
|
||||
{
|
||||
_control.UpdateSettings(settings);
|
||||
// Update the parent of the control's settings object (and not the object itself) so
|
||||
// that any overrides made by the control don't get affected by the reload
|
||||
auto child = winrt::get_self<winrt::TerminalApp::implementation::TerminalSettings>(_control.Settings());
|
||||
auto parent = winrt::get_self<winrt::TerminalApp::implementation::TerminalSettings>(settings);
|
||||
child->ClearParents();
|
||||
child->InsertParent(0, parent->get_strong());
|
||||
_control.UpdateSettings();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1550,9 +1559,9 @@ void Pane::Restore(std::shared_ptr<Pane> zoomedPane)
|
|||
// otherwise the ID value will not make sense (leaves have IDs, parents do not)
|
||||
// Return Value:
|
||||
// - The ID of this pane
|
||||
uint16_t Pane::Id() noexcept
|
||||
std::optional<uint16_t> Pane::Id() noexcept
|
||||
{
|
||||
return _id.value();
|
||||
return _id;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
@ -2085,6 +2094,13 @@ std::optional<SplitState> Pane::PreCalculateAutoSplit(const std::shared_ptr<Pane
|
|||
FAIL_FAST();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Returns true if the pane or one of its descendants is read-only
|
||||
bool Pane::ContainsReadOnly() const
|
||||
{
|
||||
return _IsLeaf() ? _control.ReadOnly() : (_firstChild->ContainsReadOnly() || _secondChild->ContainsReadOnly());
|
||||
}
|
||||
|
||||
DEFINE_EVENT(Pane, GotFocus, _GotFocusHandlers, winrt::delegate<std::shared_ptr<Pane>>);
|
||||
DEFINE_EVENT(Pane, LostFocus, _LostFocusHandlers, winrt::delegate<std::shared_ptr<Pane>>);
|
||||
DEFINE_EVENT(Pane, PaneRaiseBell, _PaneRaiseBellHandlers, winrt::Windows::Foundation::EventHandler<bool>);
|
||||
|
|
|
@ -77,10 +77,12 @@ public:
|
|||
void Maximize(std::shared_ptr<Pane> zoomedPane);
|
||||
void Restore(std::shared_ptr<Pane> zoomedPane);
|
||||
|
||||
uint16_t Id() noexcept;
|
||||
std::optional<uint16_t> Id() noexcept;
|
||||
void Id(uint16_t id) noexcept;
|
||||
void FocusPane(const uint16_t id);
|
||||
|
||||
bool ContainsReadOnly() const;
|
||||
|
||||
WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler<winrt::Windows::Foundation::IInspectable>);
|
||||
DECLARE_EVENT(GotFocus, _GotFocusHandlers, winrt::delegate<std::shared_ptr<Pane>>);
|
||||
DECLARE_EVENT(LostFocus, _LostFocusHandlers, winrt::delegate<std::shared_ptr<Pane>>);
|
||||
|
@ -118,6 +120,8 @@ private:
|
|||
|
||||
Borders _borders{ Borders::None };
|
||||
|
||||
std::atomic<bool> _isClosing{ false };
|
||||
|
||||
bool _zoomed{ false };
|
||||
|
||||
bool _IsLeaf() const noexcept;
|
||||
|
|
|
@ -335,6 +335,9 @@
|
|||
<data name="CmdFocusDesc" xml:space="preserve">
|
||||
<value>Launch the window in focus mode</value>
|
||||
</data>
|
||||
<data name="CmdWindowTargetArgDesc" xml:space="preserve">
|
||||
<value>Specify a terminal window to run the given commandline in. "0" always refers to the current window. </value>
|
||||
</data>
|
||||
<data name="NewTabSplitButton.[using:Windows.UI.Xaml.Automation]AutomationProperties.HelpText" xml:space="preserve">
|
||||
<value>Press the button to open a new terminal tab with your default profile. Open the flyout to select which profile you want to open.</value>
|
||||
</data>
|
||||
|
@ -404,6 +407,18 @@
|
|||
<data name="CloseAllDialog.Title" xml:space="preserve">
|
||||
<value>Do you want to close all tabs?</value>
|
||||
</data>
|
||||
<data name="CloseReadOnlyDialog.CloseButtonText" xml:space="preserve">
|
||||
<value>Close anyway</value>
|
||||
</data>
|
||||
<data name="CloseReadOnlyDialog.PrimaryButtonText" xml:space="preserve">
|
||||
<value>Cancel</value>
|
||||
</data>
|
||||
<data name="CloseReadOnlyDialog.Title" xml:space="preserve">
|
||||
<value>Warning</value>
|
||||
</data>
|
||||
<data name="CloseReadOnlyDialog.Content" xml:space="preserve">
|
||||
<value>You are about to close a read-only terminal. Do you wish to continue?</value>
|
||||
</data>
|
||||
<data name="LargePasteDialog.CloseButtonText" xml:space="preserve">
|
||||
<value>Cancel</value>
|
||||
</data>
|
||||
|
|
|
@ -256,6 +256,11 @@ namespace winrt::TerminalApp::implementation
|
|||
_BreakIntoDebuggerHandlers(*this, eventArgs);
|
||||
break;
|
||||
}
|
||||
case ShortcutAction::TogglePaneReadOnly:
|
||||
{
|
||||
_TogglePaneReadOnlyHandlers(*this, eventArgs);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -65,6 +65,7 @@ namespace winrt::TerminalApp::implementation
|
|||
TYPED_EVENT(TabSearch, TerminalApp::ShortcutActionDispatch, Microsoft::Terminal::Settings::Model::ActionEventArgs);
|
||||
TYPED_EVENT(MoveTab, TerminalApp::ShortcutActionDispatch, Microsoft::Terminal::Settings::Model::ActionEventArgs);
|
||||
TYPED_EVENT(BreakIntoDebugger, TerminalApp::ShortcutActionDispatch, Microsoft::Terminal::Settings::Model::ActionEventArgs);
|
||||
TYPED_EVENT(TogglePaneReadOnly, TerminalApp::ShortcutActionDispatch, Microsoft::Terminal::Settings::Model::ActionEventArgs);
|
||||
// clang-format on
|
||||
|
||||
private:
|
||||
|
|